Skip to content

Commit 61ba5dc

Browse files
committed
Get test failing
1 parent 053fafd commit 61ba5dc

6 files changed

Lines changed: 432 additions & 3 deletions

File tree

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ if pyprojectwheelbuild_enabled
5454
'src/example_fgen_basic/error_v/creation_wrapper.f90',
5555
'src/example_fgen_basic/error_v/error_v_wrapper.f90',
5656
'src/example_fgen_basic/error_v/passing_wrapper.f90',
57+
'src/example_fgen_basic/get_square_root_wrapper.f90',
5758
'src/example_fgen_basic/get_wavelength_wrapper.f90',
59+
'src/example_fgen_basic/result/result_dp_wrapper.f90',
5860
)
5961

6062
# Specify all the other source Fortran files (original files and managers)
@@ -71,6 +73,7 @@ if pyprojectwheelbuild_enabled
7173
'src/example_fgen_basic/kind_parameters.f90',
7274
'src/example_fgen_basic/result/result.f90',
7375
'src/example_fgen_basic/result/result_dp.f90',
76+
'src/example_fgen_basic/result/result_dp_manager.f90',
7477
'src/example_fgen_basic/result/result_int.f90',
7578
'src/example_fgen_basic/result/result_int1D.f90',
7679
'src/example_fgen_basic/result/result_none.f90',

src/example_fgen_basic/get_square_root.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ def get_square_root(inv: float) -> float:
4646
4747
TODO: use a more specific error
4848
"""
49-
result_instance_index: int = m_get_square_root_w.get_wavelength(inv)
49+
result_instance_index: int = m_get_square_root_w.get_square_root(inv)
5050

5151
result = ResultDP.from_instance_index(result_instance_index)
5252

53-
if result.is_error:
53+
if result.has_error:
5454
# TODO: be more specific
5555
raise FortranError(result.error_v.message)
5656

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
!> Wrapper for interfacing `m_get_square_root` with python
2+
module m_get_square_root_w
3+
4+
use m_result_dp, only: ResultDP
5+
use m_get_square_root, only: o_get_square_root => get_square_root
6+
7+
! The manager module, which makes this all work
8+
use m_result_dp_manager, only: &
9+
result_dp_manager_get_available_instance_index => get_available_instance_index, &
10+
result_dp_manager_set_instance_index_to => set_instance_index_to, &
11+
result_dp_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least
12+
13+
implicit none(type, external)
14+
private
15+
16+
public :: get_square_root
17+
18+
contains
19+
20+
function get_square_root(inv) result(res_instance_index)
21+
22+
! Annoying that this has to be injected everywhere,
23+
! but ok it can be automated.
24+
integer, parameter :: dp = selected_real_kind(15, 307)
25+
26+
real(kind=dp), intent(in) :: inv
27+
!! inv
28+
29+
integer :: res_instance_index
30+
!! Instance index of the result type
31+
32+
type(ResultDP) :: res
33+
34+
res = o_get_square_root(inv)
35+
36+
call result_dp_manager_ensure_instance_array_size_is_at_least(1)
37+
38+
! Get the instance index to return to Python
39+
call result_dp_manager_get_available_instance_index(res_instance_index)
40+
41+
! Set the derived type value in the manager's array,
42+
! ready for its attributes to be retrieved from Python.
43+
call result_dp_manager_set_instance_index_to(res_instance_index, res)
44+
45+
end function get_square_root
46+
47+
end module m_get_square_root_w

src/example_fgen_basic/result/result_dp.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ def from_instance_index(cls, instance_index: int) -> ErrorV:
5353

5454
# Integer is very simple
5555
if m_result_dp_w.data_v_is_set(instance_index):
56-
data_v = m_result_dp_w.get_data_v(instance_index)
56+
data_v: float = m_result_dp_w.get_data_v(instance_index)
57+
# data_v: np.Float64 = m_result_dp_w.get_data_v(instance_index)
5758

5859
else:
5960
data_v = None
@@ -72,6 +73,18 @@ def from_instance_index(cls, instance_index: int) -> ErrorV:
7273

7374
return res
7475

76+
@property
77+
def has_error(self) -> bool:
78+
"""
79+
Whether this instance holds an error or not
80+
81+
Returns
82+
-------
83+
:
84+
`True` if this instance holds an error, `False` otherwise
85+
"""
86+
return self.error_v is not None
87+
7588
def build_fortran_instance(self) -> int:
7689
"""
7790
Build an instance equivalent to `self` on the Fortran side
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
!> Manager of `ResultDP` (TODO: xref) across the Fortran-Python interface
2+
module m_result_dp_manager
3+
4+
use kind_parameters, only: dp
5+
use m_error_v, only: ErrorV
6+
use m_result_dp, only: ResultDP
7+
use m_result_none, only: ResultNone
8+
9+
implicit none(type, external)
10+
private
11+
12+
type(ResultDP), dimension(:), allocatable :: instance_array
13+
logical, dimension(:), allocatable :: instance_available
14+
15+
! TODO: think about ordering here, alphabetical probably easiest
16+
public :: build_instance, finalise_instance, get_available_instance_index, get_instance, set_instance_index_to, &
17+
ensure_instance_array_size_is_at_least
18+
19+
contains
20+
21+
function build_instance(data_v_in, error_v_in) result(instance_index)
22+
!! Build an instance
23+
24+
real(kind=dp), intent(in), optional :: data_v_in
25+
!! Data
26+
27+
class(ErrorV), intent(in), optional :: error_v_in
28+
!! Error message
29+
30+
integer :: instance_index
31+
!! Index of the built instance
32+
33+
type(ResultNone) :: res_build
34+
35+
call ensure_instance_array_size_is_at_least(1)
36+
call get_available_instance_index(instance_index)
37+
res_build = instance_array(instance_index) % build(data_v_in=data_v_in, error_v_in=error_v_in)
38+
39+
! TODO: check build has no error
40+
41+
end function build_instance
42+
43+
subroutine finalise_instance(instance_index)
44+
!! Finalise an instance
45+
46+
integer, intent(in) :: instance_index
47+
!! Index of the instance to finalise
48+
49+
call check_index_claimed(instance_index)
50+
51+
call instance_array(instance_index) % finalise()
52+
instance_available(instance_index) = .true.
53+
54+
end subroutine finalise_instance
55+
56+
subroutine get_available_instance_index(available_instance_index)
57+
!! Get a free instance index
58+
59+
! TODO: think through whether race conditions are possible
60+
! e.g. while returning a free index number to one Python call
61+
! a different one can be looking up a free instance index at the same time
62+
! and something goes wrong (maybe we need a lock)
63+
64+
integer, intent(out) :: available_instance_index
65+
!! Available instance index
66+
67+
integer :: i
68+
69+
do i = 1, size(instance_array)
70+
71+
if (instance_available(i)) then
72+
73+
instance_available(i) = .false.
74+
available_instance_index = i
75+
! TODO: switch to returning a Result type
76+
! res = ResultInt(data=i)
77+
return
78+
79+
end if
80+
81+
end do
82+
83+
! TODO: switch to returning a Result type with an error set
84+
! res = ResultInt(ResultDP(code=1, message="No available instances"))
85+
error stop 1
86+
87+
end subroutine get_available_instance_index
88+
89+
! Change to pure function when we update check_index_claimed to be pure
90+
function get_instance(instance_index) result(inst)
91+
92+
integer, intent(in) :: instance_index
93+
!! Index in `instance_array` of which to set the value equal to `val`
94+
95+
type(ResultDP) :: inst
96+
!! Instance at `instance_array(instance_index)`
97+
98+
call check_index_claimed(instance_index)
99+
inst = instance_array(instance_index)
100+
101+
end function get_instance
102+
103+
subroutine set_instance_index_to(instance_index, val)
104+
105+
integer, intent(in) :: instance_index
106+
!! Index in `instance_array` of which to set the value equal to `val`
107+
108+
type(ResultDP), intent(in) :: val
109+
110+
call check_index_claimed(instance_index)
111+
instance_array(instance_index) = val
112+
113+
end subroutine set_instance_index_to
114+
115+
subroutine check_index_claimed(instance_index)
116+
!! Check that an index has already been claimed
117+
!!
118+
!! Stops execution if the index has not been claimed.
119+
120+
integer, intent(in) :: instance_index
121+
!! Instance index to check
122+
123+
if (instance_available(instance_index)) then
124+
! TODO: Switch to using Result here
125+
! Use `ResultNone` which is a Result type
126+
! that doesn't have a `data` attribute
127+
! (i.e. if this succeeds, there is no data to check,
128+
! if it fails, the result_dp attribute will be set).
129+
! So the code would be something like
130+
! res = ResultNone(ResultDP(code=1, message="Index ", instance_index, " has not been claimed"))
131+
print *, "Index ", instance_index, " has not been claimed"
132+
error stop 1
133+
end if
134+
135+
if (instance_index < 1) then
136+
! TODO: Switch to using Result here
137+
! Use `ResultNone` which is a Result type
138+
! that doesn't have a `data` attribute
139+
! (i.e. if this succeeds, there is no data to check,
140+
! if it fails, the result_dp attribute will be set).
141+
! So the code would be something like
142+
! res = ResultNone(ResultDP(code=2, message="Requested index is ", instance_index, " which is less than 1"))
143+
print *, "Requested index is ", instance_index, " which is less than 1"
144+
error stop 1
145+
end if
146+
147+
! ! Here, result becomes
148+
! ! Now that I've thought about this, it's also clear
149+
! ! that we will only use functions
150+
! ! or subroutines with a result type that has `intent(out)`.
151+
! ! We will no longer have subroutines that return nothing
152+
! ! (like this one currently does).
153+
! res = ResultNone()
154+
155+
end subroutine check_index_claimed
156+
157+
subroutine ensure_instance_array_size_is_at_least(n)
158+
!! Ensure that `instance_array` and `instance_available` have at least `n` slots
159+
160+
integer, intent(in) :: n
161+
162+
type(ResultDP), dimension(:), allocatable :: tmp_instances
163+
logical, dimension(:), allocatable :: tmp_available
164+
165+
if (.not. allocated(instance_array)) then
166+
167+
allocate (instance_array(n))
168+
169+
allocate (instance_available(n))
170+
! Race conditions ?
171+
instance_available = .true.
172+
173+
else if (size(instance_available) < n) then
174+
175+
allocate (tmp_instances(n))
176+
tmp_instances(1:size(instance_array)) = instance_array
177+
call move_alloc(tmp_instances, instance_array)
178+
179+
allocate (tmp_available(n))
180+
tmp_available(1:size(instance_available)) = instance_available
181+
tmp_available(size(instance_available) + 1:size(tmp_available)) = .true.
182+
call move_alloc(tmp_available, instance_available)
183+
184+
end if
185+
186+
end subroutine ensure_instance_array_size_is_at_least
187+
188+
end module m_result_dp_manager

0 commit comments

Comments
 (0)