-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresult_dp_wrapper.f90
More file actions
256 lines (186 loc) · 9 KB
/
result_dp_wrapper.f90
File metadata and controls
256 lines (186 loc) · 9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
!> Wrapper for interfacing `m_result_dp` with Python
module m_result_dp_w
use m_error_v, only: ErrorV, NO_ERROR_CODE
use m_result_dp, only: ResultDP
use m_result_int, only: ResultInt
use m_result_none, only: ResultNone
! The manager module, which makes this all work
use m_error_v_manager, only: &
error_v_manager_get_instance => get_instance, &
error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least, &
error_v_manager_get_available_instance_index => get_available_instance_index, &
error_v_manager_set_instance_index_to => set_instance_index_to
use m_result_dp_manager, only: &
result_dp_manager_build_instance => build_instance, &
result_dp_manager_finalise_instance => finalise_instance, &
result_dp_manager_get_instance => get_instance, &
result_dp_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least
use m_result_int_manager, only: &
result_int_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least, &
result_int_manager_get_available_instance_index => get_available_instance_index, &
result_int_manager_set_instance_index_to => set_instance_index_to, &
result_int_manager_force_claim_instance_index => force_claim_instance_index
implicit none
private
public :: build_instance, finalise_instance, finalise_instances, &
ensure_at_least_n_instances_can_be_passed_simultaneously, &
data_v_is_set, get_data_v, error_v_is_set, get_error_v
! Annoying that this has to be injected everywhere,
! but ok it can be automated.
integer, parameter :: dp = selected_real_kind(15, 307)
contains
subroutine build_instance(data_v, error_v_instance_index, res_build_instance_index)
!! Build an instance
! Optional in wrappers a bad idea as f2py does something funny.
! Have to use some 'not supplied' equivalent instead.
real(kind=dp), intent(in) :: data_v
!! Data
integer, intent(in) :: error_v_instance_index
!! Error
integer, intent(out) :: res_build_instance_index
!! Instance index of the result of trying to build the instance
!
! This is the major trick for wrapping.
! We pass instance indexes (integers) back to Python rather than the instance itself.
type(ResultInt) :: res_build
type(ResultInt) :: res_int_get_available_instance_index
! This is the major trick for wrapping derived types with other derived types as attributes.
! We use the manager layer to initialise the attributes before passing on.
type(ErrorV) :: error_v
! In wrapper layer, something always gets passed by f2py.
! Assume that any error_v_instance_index greater than 0 indicates that the error is what we want
! (check this first as there is no `None` in Fortran/f2py we can use to check data instead).
if (error_v_instance_index > 0) then
error_v = error_v_manager_get_instance(error_v_instance_index)
call result_dp_manager_build_instance( &
error_v_in=error_v, &
res=res_build &
)
else
! Assume no error
call result_dp_manager_build_instance( &
data_v_in=data_v, &
res=res_build &
)
end if
! Get the instance index to return to Python
call result_int_manager_get_available_instance_index(res_int_get_available_instance_index)
if (.not. (res_int_get_available_instance_index % is_error())) then
! Could allocate a result type to handle the return to Python.
!
! Set the derived type value in the manager's array,
! ready for its attributes to be retrieved from Python.
call result_int_manager_set_instance_index_to( &
! Hmm ok downcasting maybe not so smart
int(res_int_get_available_instance_index % data_v, kind=4), &
res_build &
)
return
end if
! Could not allocate a result type to handle the return to Python.
!
! Logic here is trickier.
! If you can't create a result type to return to Python,
! then you also can't return errors so you're stuck.
! As an escape hatch
call result_int_manager_ensure_instance_array_size_is_at_least(1)
res_build_instance_index = 1
! Just use the first instance and write a message that the program
! is fully broken.
res_build = ResultInt( &
error_v = ErrorV( &
code=1, &
message=( &
"I wanted to return an error, " &
// "but I couldn't even get an available instance to do so. " &
// "I have forced a return, but your program is probably fully broken. " &
// "Please be very careful." &
) &
) &
)
call result_int_manager_force_claim_instance_index(res_build_instance_index)
call result_int_manager_set_instance_index_to(res_build_instance_index, res_build)
end subroutine build_instance
! build_instances is very hard to do
! because you need to pass an array of variable-length characters which is non-trivial.
! Maybe we will try this another day, for now this isn't that important
! (we can just use a loop from the Python side)
! so we just don't bother implementing `build_instances`.
subroutine finalise_instance(instance_index)
!! Finalise an instance
integer, intent(in) :: instance_index
!! Instance index
!
! This is the major trick for wrapping.
! We pass instance indexes (integers) to Python rather than the instance itself.
call result_dp_manager_finalise_instance(instance_index)
end subroutine finalise_instance
subroutine finalise_instances(instance_indexes)
!! Finalise an instance
integer, dimension(:), intent(in) :: instance_indexes
!! Instance indexes to finalise
!
! This is the major trick for wrapping.
! We pass instance indexes (integers) to Python rather than the instance itself.
integer :: i
do i = 1, size(instance_indexes)
call result_dp_manager_finalise_instance(instance_indexes(i))
end do
end subroutine finalise_instances
subroutine ensure_at_least_n_instances_can_be_passed_simultaneously(n)
!! Ensure that at least `n` instances of `ResultDP` can be passed via the manager simultaneously
integer, intent(in) :: n
call result_dp_manager_ensure_instance_array_size_is_at_least(n)
end subroutine ensure_at_least_n_instances_can_be_passed_simultaneously
! Full set of wrapping strategies to get/pass different types in e.g.
! https://gitlab.com/magicc/fgen/-/blob/switch-to-uv/tests/test-data/exposed_attrs/src/exposed_attrs/exposed_attrs_wrapped.f90
! (we will do a full re-write of the code which generates this,
! but the strategies will probably stay as they are)
! For optional stuff, need to be able to check whether they're set or not
subroutine data_v_is_set( &
instance_index, &
res &
)
integer, intent(in) :: instance_index
logical, intent(out) :: res
type(ResultDP) :: instance
instance = result_dp_manager_get_instance(instance_index)
res = allocated(instance % data_v)
end subroutine data_v_is_set
subroutine get_data_v( &
instance_index, &
data_v &
)
integer, intent(in) :: instance_index
real(kind=dp), intent(out) :: data_v
type(ResultDP) :: instance
instance = result_dp_manager_get_instance(instance_index)
data_v = instance % data_v
end subroutine get_data_v
subroutine error_v_is_set( &
instance_index, &
res &
)
integer, intent(in) :: instance_index
logical, intent(out) :: res
type(ResultDP) :: instance
instance = result_dp_manager_get_instance(instance_index)
res = allocated(instance % error_v)
end subroutine error_v_is_set
subroutine get_error_v( &
instance_index, &
error_v_instance_index &
)
integer, intent(in) :: instance_index
! trick: return instance index, not the instance.
! Build on the python side
integer, intent(out) :: error_v_instance_index
type(ResultDP) :: instance
type(ErrorV) :: error_v
instance = result_dp_manager_get_instance(instance_index)
error_v = instance % error_v
call error_v_manager_ensure_instance_array_size_is_at_least(1)
call error_v_manager_get_available_instance_index(error_v_instance_index)
call error_v_manager_set_instance_index_to(error_v_instance_index, error_v)
end subroutine get_error_v
end module m_result_dp_w