Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3c57801
Add notes
znicholls Aug 31, 2025
9402f91
Add ResultInt and clean up suggested Result implemenations
znicholls Sep 1, 2025
300694f
class(results) fun
mzecc Sep 2, 2025
844d29e
Playing around
mzecc Sep 3, 2025
89a8c79
Result Integer 0D
mzecc Sep 9, 2025
2f4ce95
Result Integer 0D
mzecc Sep 9, 2025
f23cf17
Result-type : 1D integer
mzecc Sep 9, 2025
a5fba54
Merge branch 'main' into result-type
mzecc Sep 9, 2025
53b1910
Fortitude CI
mzecc Sep 9, 2025
02b5392
Result type
mzecc Sep 11, 2025
e2145a0
Get fortran compiling
znicholls Sep 11, 2025
7937334
Add square root for easier illustration
znicholls Sep 11, 2025
4b6f728
Add failing tests
znicholls Sep 11, 2025
053fafd
Up to writing wrapper
znicholls Sep 11, 2025
61ba5dc
Get test failing
znicholls Sep 11, 2025
9dc7b3f
Get one test passing
znicholls Sep 11, 2025
31ebe4c
Pass error raising test
znicholls Sep 11, 2025
0174330
Skip test
mzecc Sep 12, 2025
f60b408
Corrected create_errors small mistake
mzecc Sep 12, 2025
999f35f
Corrected error
mzecc Sep 12, 2025
3c2a6a4
Mypy
mzecc Sep 12, 2025
4322463
Merge pull request #34 from openscm/result-type-zn
mzecc Sep 12, 2025
1c249f9
Errors bubble-up:1
mzecc Sep 24, 2025
fbec11d
Improvements and bubbling up errors
mzecc Oct 13, 2025
989bfa4
Removed '(type, external)' from 'implicit none'
mzecc Oct 13, 2025
2c1f383
Removed '(type, external)' from 'implicit none':2
mzecc Oct 13, 2025
a773f9c
Advancements and bubble-up working (message only)
mzecc Nov 12, 2025
daeb786
Corrected bug
mzecc Nov 14, 2025
c42bb2d
Tests Passing
mzecc Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/get_square_root_wrapper.f90',
'src/example_fgen_basic/get_wavelength_wrapper.f90',
'src/example_fgen_basic/result/result_dp_wrapper.f90',
'src/example_fgen_basic/result/result_int_wrapper.f90',
)

# Specify all the other source Fortran files (original files and managers)
Expand All @@ -72,11 +73,12 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/get_wavelength.f90',
'src/example_fgen_basic/kind_parameters.f90',
'src/example_fgen_basic/result/result.f90',
'src/example_fgen_basic/result/result_none.f90',
'src/example_fgen_basic/result/result_dp.f90',
'src/example_fgen_basic/result/result_dp_manager.f90',
'src/example_fgen_basic/result/result_int.f90',
'src/example_fgen_basic/result/result_int_manager.f90',
'src/example_fgen_basic/result/result_int1D.f90',
'src/example_fgen_basic/result/result_none.f90',
)

# All Python files (wrappers and otherwise)
Expand All @@ -94,6 +96,7 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/pyfgen_runtime/exceptions.py',
'src/example_fgen_basic/result/__init__.py',
'src/example_fgen_basic/result/result_dp.py',
'src/example_fgen_basic/result/result_int.py',
'src/example_fgen_basic/typing.py',
)

Expand Down
13 changes: 13 additions & 0 deletions src/example_fgen_basic/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ srcs += files(
'error_v/creation.f90',
'error_v/error_v.f90',
'error_v/passing.f90',
'error_v/creation_wrapper.f90',
'error_v/error_v_manager.f90',
'error_v/error_v_wrapper.f90',
'error_v/passing_wrapper.f90',
'fpyfgen/base_finalisable.f90',
'get_wavelength.f90',
'kind_parameters.f90',
'result/result_dp_manager.f90',
'result/result_dp_wrapper.f90',
'result/result_int1D.f90',
'result/result_int_manager.f90',
'result/result_int_wrapper.f90',
'result/result_dp.f90',
'result/result.f90',
'result/result_int.f90',
'result/result_none.f90',
)
3 changes: 2 additions & 1 deletion src/example_fgen_basic/result/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"""

from example_fgen_basic.result.result_dp import ResultDP
from example_fgen_basic.result.result_int import ResultInt

__all__ = ["ResultDP"]
__all__ = ["ResultDP", "ResultInt"]
2 changes: 1 addition & 1 deletion src/example_fgen_basic/result/result_dp.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def from_instance_index(cls, instance_index: int) -> ResultDP:
"""
# Different wrapping strategies are needed

# Integer is very simple
# Float is very simple
if m_result_dp_w.data_v_is_set(instance_index):
data_v: float | None = m_result_dp_w.get_data_v(instance_index)
# data_v: np.Float64 = m_result_dp_w.get_data_v(instance_index)
Expand Down
69 changes: 46 additions & 23 deletions src/example_fgen_basic/result/result_dp_manager.f90
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module m_result_dp_manager
use kind_parameters, only: dp
use m_error_v, only: ErrorV
use m_result_dp, only: ResultDP
use m_result_int, only: ResultInt
use m_result_none, only: ResultNone

implicit none(type, external)
Expand All @@ -18,7 +19,7 @@ module m_result_dp_manager

contains

function build_instance(data_v_in, error_v_in) result(instance_index)
function build_instance(data_v_in, error_v_in) result(res_available_instance_index)
!! Build an instance

real(kind=dp), intent(in), optional :: data_v_in
Expand All @@ -27,14 +28,18 @@ function build_instance(data_v_in, error_v_in) result(instance_index)
class(ErrorV), intent(in), optional :: error_v_in
!! Error message

integer :: instance_index
type(ResultInt) :: res_available_instance_index
!! Index of the built instance

type(ResultNone) :: res_build

call ensure_instance_array_size_is_at_least(1)
call get_available_instance_index(instance_index)
res_build = instance_array(instance_index) % build(data_v_in=data_v_in, error_v_in=error_v_in)
call get_available_instance_index(res_available_instance_index)
! MZ check for errors ?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep we want to switch this to returniing ResultInt

! MZ function with side effect: good idea??
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ye I don't like it either, see my comments on the function

! MZ why res_build is ResultNone??
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're calling a method on the object we already have so I was following the python convention of returning none which means the result has to be ResultNone. Is there another option?

res_build = instance_array(res_available_instance_index%data_v) % &
build(data_v_in=data_v_in, error_v_in=error_v_in)

! TODO: check build has no error

Expand All @@ -46,44 +51,46 @@ subroutine finalise_instance(instance_index)
integer, intent(in) :: instance_index
!! Index of the instance to finalise

call check_index_claimed(instance_index)
type(ResultNone) :: res_check_index_claimed

res_check_index_claimed = check_index_claimed(instance_index)
if(res_check_index_claimed%is_error()) return

call instance_array(instance_index) % finalise()
instance_available(instance_index) = .true.

end subroutine finalise_instance

subroutine get_available_instance_index(available_instance_index)
subroutine get_available_instance_index(res_available_instance_index)
!! Get a free instance index

! TODO: think through whether race conditions are possible
! e.g. while returning a free index number to one Python call
! a different one can be looking up a free instance index at the same time
! and something goes wrong (maybe we need a lock)

integer, intent(out) :: available_instance_index
type(ResultInt), intent(out) :: res_available_instance_index
! integer, intent(out) :: available_instance_index
!! Available instance index

integer :: i

do i = 1, size(instance_array)

if (instance_available(i)) then

instance_available(i) = .false.
available_instance_index = i
! available_instance_index = i
! TODO: switch to returning a Result type
! res = ResultInt(data=i)
res_available_instance_index = ResultInt(data_v=i)
return

end if

end do

! TODO: switch to returning a Result type with an error set
! res = ResultInt(ResultDP(code=1, message="No available instances"))
error stop 1

res_available_instance_index = ResultInt(error_v=ErrorV(code=1, message="No available instances"))
! error stop 1
end subroutine get_available_instance_index

! Change to pure function when we update check_index_claimed to be pure
Expand All @@ -95,8 +102,14 @@ function get_instance(instance_index) result(inst)
type(ResultDP) :: inst
!! Instance at `instance_array(instance_index)`

call check_index_claimed(instance_index)
inst = instance_array(instance_index)
type(ResultNone) :: res_check_index_claimed

res_check_index_claimed = check_index_claimed(instance_index)
if(res_check_index_claimed%is_error()) then
inst = ResultDP(error_v=res_check_index_claimed%error_v)
else
inst = instance_array(instance_index)
end if

end function get_instance

Expand All @@ -106,19 +119,22 @@ subroutine set_instance_index_to(instance_index, val)
!! Index in `instance_array` of which to set the value equal to `val`

type(ResultDP), intent(in) :: val
type(ResultNone) :: res_check_index_claimed

call check_index_claimed(instance_index)
instance_array(instance_index) = val
res_check_index_claimed = check_index_claimed(instance_index)
if(res_check_index_claimed%is_error()) instance_array(instance_index) = val

end subroutine set_instance_index_to

subroutine check_index_claimed(instance_index)
function check_index_claimed(instance_index) result(res_check_index_claimed)
!! Check that an index has already been claimed
!!
!! Stops execution if the index has not been claimed.

integer, intent(in) :: instance_index
!! Instance index to check
type(ResultNone) :: res_check_index_claimed
character(len=:), allocatable :: msg

if (instance_available(instance_index)) then
! TODO: Switch to using Result here
Expand All @@ -128,8 +144,12 @@ subroutine check_index_claimed(instance_index)
! if it fails, the result_dp attribute will be set).
! So the code would be something like
! res = ResultNone(ResultDP(code=1, message="Index ", instance_index, " has not been claimed"))
print *, "Index ", instance_index, " has not been claimed"
error stop 1
! print *, "Index ", instance_index, " has not been claimed"
! error stop 1
! MZ Weird thing allocatable message
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep lots of mucking around. We may end up writing something like this https://gitlab.com/magicc/fgen/-/blob/switch-to-uv/packages/fgen-core/src/char_conversions.f90?ref_type=heads

or using someone else's equivalent if we can find a nice one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok the pros are here: https://github.com/fortran-lang/stdlib

we would either use their functions or could also just look at them and write our own versions because a) stdlib is MIT licensed so it's fine and b) we don't introduce the entire stdlib as a dependency that way

msg = ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this line?

write(msg,fmt="(A, I0, A)") "Index ", instance_index," has not been claimed"
res_check_index_claimed = ResultNone(error_v=ErrorV(code=1, message=msg))
end if

if (instance_index < 1) then
Expand All @@ -140,8 +160,10 @@ subroutine check_index_claimed(instance_index)
! if it fails, the result_dp attribute will be set).
! So the code would be something like
! res = ResultNone(ResultDP(code=2, message="Requested index is ", instance_index, " which is less than 1"))
print *, "Requested index is ", instance_index, " which is less than 1"
error stop 1
! print *, "Requested index is ", instance_index, " which is less than 1"
! error stop 1
write(msg,fmt="(A, I0, A)") "Requested index is ", instance_index, " which is less than 1"
res_check_index_claimed = ResultNone(error_v=ErrorV(code=2, message=msg))
end if

! ! Here, result becomes
Expand All @@ -151,8 +173,9 @@ subroutine check_index_claimed(instance_index)
! ! We will no longer have subroutines that return nothing
! ! (like this one currently does).
! res = ResultNone()
res_check_index_claimed = ResultNone()

end subroutine check_index_claimed
end function check_index_claimed

subroutine ensure_instance_array_size_is_at_least(n)
!! Ensure that `instance_array` and `instance_available` have at least `n` slots
Expand Down
8 changes: 5 additions & 3 deletions src/example_fgen_basic/result/result_dp_wrapper.f90
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module m_result_dp_w

use m_error_v, only: ErrorV
use m_result_dp, only: ResultDP
use m_result_int, only: ResultInt

! The manager module, which makes this all work
use m_error_v_manager, only: &
Expand All @@ -26,7 +27,7 @@ module m_result_dp_w

contains

subroutine build_instance(data_v, error_v_instance_index, instance_index)
subroutine build_instance(data_v, error_v_instance_index, res_available_instance_index)
!! Build an instance

! Annoying that this has to be injected everywhere,
Expand All @@ -39,7 +40,8 @@ subroutine build_instance(data_v, error_v_instance_index, instance_index)
integer, intent(in), optional :: error_v_instance_index
!! Error

integer, intent(out) :: instance_index
! integer, intent(out) :: instance_index
type(ResultInt), intent(out) :: res_available_instance_index
!! Instance index of the built instance
!
! This is the major trick for wrapping.
Expand All @@ -51,7 +53,7 @@ subroutine build_instance(data_v, error_v_instance_index, instance_index)

error_v = error_v_manager_get_instance(error_v_instance_index)

instance_index = result_dp_manager_build_instance(data_v, error_v)
res_available_instance_index = result_dp_manager_build_instance(data_v, error_v)

end subroutine build_instance

Expand Down
7 changes: 4 additions & 3 deletions src/example_fgen_basic/result/result_int.f90
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
!> https://github.com/samharrison7/fortran-error-handler
module m_result_int

use kind_parameters, only: i8
use m_error_v, only: ErrorV
use m_result, only: ResultBase
use m_result_none, only: ResultNone
Expand All @@ -14,7 +15,7 @@ module m_result_int
type, extends(ResultBase), public :: ResultInt
!! Result type that holds integer values

integer, allocatable :: data_v
integer(kind=i8), allocatable :: data_v
!! Data i.e. the result (if no error occurs)

! Note: the error_v attribute comes from ResultBase
Expand Down Expand Up @@ -42,7 +43,7 @@ function constructor(data_v, error_v) result(self)
type(ResultInt) :: self
! Hopefully can leave without docstring (like Python)

integer, intent(in), optional :: data_v
integer(kind=i8), intent(in), optional :: data_v
!! Data

class(ErrorV), intent(in), optional :: error_v
Expand Down Expand Up @@ -73,7 +74,7 @@ function build(self, data_v_in, error_v_in) result(res)
class(ResultInt), intent(out) :: self
! Hopefully can leave without docstring (like Python)

integer, intent(in), optional :: data_v_in
integer(kind=i8), intent(in), optional :: data_v_in
!! Data

class(ErrorV), intent(in), optional :: error_v_in
Expand Down
Loading
Loading