Skip to content

Commit 52a6ec8

Browse files
committed
Pass all tests
1 parent 274a61c commit 52a6ec8

File tree

10 files changed

+296
-466
lines changed

10 files changed

+296
-466
lines changed

meson.build

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ if pyprojectwheelbuild_enabled
5353
srcs = files(
5454
'src/example_fgen_basic/error_v/creation_wrapper.f90',
5555
'src/example_fgen_basic/error_v/error_v_wrapper.f90',
56+
'src/example_fgen_basic/error_v/passing_wrapper.f90',
5657
'src/example_fgen_basic/get_wavelength_wrapper.f90',
5758
)
5859

@@ -62,6 +63,7 @@ if pyprojectwheelbuild_enabled
6263
'src/example_fgen_basic/error_v/creation.f90',
6364
'src/example_fgen_basic/error_v/error_v.f90',
6465
'src/example_fgen_basic/error_v/error_v_manager.f90',
66+
'src/example_fgen_basic/error_v/passing.f90',
6567
'src/example_fgen_basic/fpyfgen/base_finalisable.f90',
6668
'src/example_fgen_basic/fpyfgen/derived_type_manager_helpers.f90',
6769
'src/example_fgen_basic/get_wavelength.f90',
@@ -75,12 +77,11 @@ if pyprojectwheelbuild_enabled
7577
'src/example_fgen_basic/error_v/__init__.py',
7678
'src/example_fgen_basic/error_v/creation.py',
7779
'src/example_fgen_basic/error_v/error_v.py',
80+
'src/example_fgen_basic/error_v/passing.py',
7881
'src/example_fgen_basic/exceptions.py',
7982
'src/example_fgen_basic/get_wavelength.py',
8083
'src/example_fgen_basic/pyfgen_runtime/__init__.py',
81-
'src/example_fgen_basic/pyfgen_runtime/base_finalisable.py',
8284
'src/example_fgen_basic/pyfgen_runtime/exceptions.py',
83-
'src/example_fgen_basic/pyfgen_runtime/formatting.py',
8485
)
8586

8687
# The ancillary library,

src/example_fgen_basic/error_v/error_v_manager.f90

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,29 @@ module m_error_v_manager
1515
logical, dimension(:), allocatable :: instance_available
1616

1717
! TODO: think about ordering here, alphabetical probably easiest
18-
public :: finalise_instance, get_available_instance_index, get_instance, set_instance_index_to, &
18+
public :: build_instance, finalise_instance, get_available_instance_index, get_instance, set_instance_index_to, &
1919
ensure_instance_array_size_is_at_least
2020

2121
contains
2222

23+
function build_instance(code, message) result(instance_index)
24+
!! Build an instance
25+
26+
integer, intent(in) :: code
27+
!! Error code
28+
29+
character(len=*), optional, intent(in) :: message
30+
!! Error message
31+
32+
integer :: instance_index
33+
!! Index of the built instance
34+
35+
call ensure_instance_array_size_is_at_least(1)
36+
call get_available_instance_index(instance_index)
37+
call instance_array(instance_index) % build(code=code, message=message)
38+
39+
end function build_instance
40+
2341
subroutine finalise_instance(instance_index)
2442
!! Finalise an instance
2543

@@ -59,7 +77,6 @@ subroutine get_available_instance_index(available_instance_index)
5977
end do
6078

6179
! TODO: switch to returning a Result type with an error set
62-
print *, "No free indexes"
6380
error stop 1
6481

6582
end subroutine get_available_instance_index
@@ -112,9 +129,6 @@ subroutine check_index_claimed(instance_index)
112129

113130
end subroutine check_index_claimed
114131

115-
! TODO: think about exposing this to Python
116-
! so power users can ensure there are enough arrays at the start
117-
! rather than relying on automatic resizing.
118132
subroutine ensure_instance_array_size_is_at_least(n)
119133
!! Ensure that `instance_array` and `instance_available` have at least `n` slots
120134

@@ -139,6 +153,7 @@ subroutine ensure_instance_array_size_is_at_least(n)
139153

140154
allocate(tmp_available(n))
141155
tmp_available(1:size(instance_available)) = instance_available
156+
tmp_available(size(instance_available) + 1:size(tmp_available)) = .true.
142157
call move_alloc(tmp_available, instance_available)
143158

144159
end if

src/example_fgen_basic/error_v/error_v_wrapper.f90

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,41 @@ module m_error_v_w
99

1010
! The manager module, which makes this all work
1111
use m_error_v_manager, only: &
12+
error_v_manager_build_instance => build_instance, &
1213
error_v_manager_finalise_instance => finalise_instance, &
13-
error_v_manager_get_instance => get_instance
14+
error_v_manager_get_instance => get_instance, &
15+
error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least
1416

1517
implicit none
1618
private
1719

18-
public :: finalise_instance, finalise_instances, &
20+
public :: build_instance, finalise_instance, finalise_instances, &
21+
ensure_at_least_n_instances_can_be_passed_simultaneously, &
1922
get_code, get_message
2023

2124
contains
2225

26+
subroutine build_instance(code, message, instance_index)
27+
!! Build an instance
28+
29+
integer, intent(in) :: code
30+
!! Error code
31+
!!
32+
!! Use [TODO: figure out xref] `NO_ERROR_CODE` if there is no error
33+
34+
character(len=*), optional, intent(in) :: message
35+
!! Error message
36+
37+
integer, intent(out) :: instance_index
38+
!! Instance index of the built instance
39+
!
40+
! This is the major trick for wrapping.
41+
! We pass instance indexes (integers) to Python rather than the instance itself.
42+
43+
instance_index = error_v_manager_build_instance(code, message)
44+
45+
end subroutine build_instance
46+
2347
subroutine finalise_instance(instance_index)
2448
!! Finalise an instance
2549

@@ -33,7 +57,6 @@ subroutine finalise_instance(instance_index)
3357

3458
end subroutine finalise_instance
3559

36-
3760
subroutine finalise_instances(instance_indexes)
3861
!! Finalise an instance
3962

@@ -51,6 +74,15 @@ subroutine finalise_instances(instance_indexes)
5174

5275
end subroutine finalise_instances
5376

77+
subroutine ensure_at_least_n_instances_can_be_passed_simultaneously(n)
78+
!! Ensure that at least `n` instances of `ErrorV` can be passed via the manager simultaneously
79+
80+
integer, intent(in) :: n
81+
82+
call error_v_manager_ensure_instance_array_size_is_at_least(n)
83+
84+
end subroutine ensure_at_least_n_instances_can_be_passed_simultaneously
85+
5486
! Full set of wrapping strategies to get/pass different types in e.g.
5587
! https://gitlab.com/magicc/fgen/-/blob/switch-to-uv/tests/test-data/exposed_attrs/src/exposed_attrs/exposed_attrs_wrapped.f90
5688
! (we will do a full re-write of the code which generates this,
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
!> Error passing
2+
!>
3+
!> A very basic demo to get the idea.
4+
!
5+
! TODO: discuss - we should probably have some convention for module names.
6+
! The hard part is avoiding them becoming too long...
7+
module m_error_v_passing
8+
9+
use m_error_v, only: ErrorV, NO_ERROR_CODE
10+
11+
implicit none
12+
private
13+
14+
public :: pass_error, pass_errors
15+
16+
contains
17+
18+
function pass_error(inv) result(is_err)
19+
!! Pass an error
20+
!!
21+
!! If an error is supplied, we return `.true.`, otherwise `.false.`.
22+
23+
type(ErrorV), intent(in) :: inv
24+
!! Input error value
25+
26+
logical :: is_err
27+
!! Whether `inv` is an error or not
28+
29+
is_err = (inv % code /= NO_ERROR_CODE)
30+
31+
end function pass_error
32+
33+
function pass_errors(invs, n) result(is_errs)
34+
!! Pass a number of errors
35+
!!
36+
!! For each value in `invs`, if an error is supplied, we return `.true.`, otherwise `.false.`.
37+
38+
type(ErrorV), dimension(n), intent(in) :: invs
39+
!! Input error values
40+
41+
integer, intent(in) :: n
42+
!! Number of values being passed
43+
44+
logical, dimension(n) :: is_errs
45+
!! Whether each value in `invs` is an error or not
46+
47+
integer :: i
48+
49+
do i = 1, n
50+
51+
is_errs(i) = pass_error(invs(i))
52+
53+
end do
54+
55+
end function pass_errors
56+
57+
end module m_error_v_passing
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""
2+
Wrappers of `m_error_v_passing` [TODO think about naming and x-referencing]
3+
4+
At the moment, all written by hand.
5+
We will auto-generate this in future.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import TYPE_CHECKING
11+
12+
import numpy as np
13+
14+
from example_fgen_basic.error_v import ErrorV
15+
from example_fgen_basic.pyfgen_runtime.exceptions import CompiledExtensionNotFoundError
16+
17+
try:
18+
from example_fgen_basic._lib import ( # type: ignore
19+
m_error_v_w,
20+
)
21+
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
22+
raise CompiledExtensionNotFoundError("example_fgen_basic._lib.m_error_v_w") from exc
23+
try:
24+
from example_fgen_basic._lib import ( # type: ignore
25+
m_error_v_passing_w,
26+
)
27+
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
28+
raise CompiledExtensionNotFoundError(
29+
"example_fgen_basic._lib.m_error_v_passing_w"
30+
) from exc
31+
32+
if TYPE_CHECKING:
33+
# TODO: bring back in numpy type hints
34+
NP_ARRAY_OF_INT = None
35+
NP_ARRAY_OF_BOOL = None
36+
37+
38+
def pass_error(inv: ErrorV) -> bool:
39+
"""
40+
Pass an error to Fortran
41+
42+
Parameters
43+
----------
44+
inv
45+
Input value to pass to Fortran
46+
47+
Returns
48+
-------
49+
:
50+
If `inv` is an error, `True`, otherwise `False`.
51+
"""
52+
# Tell Fortran to build the object on the Fortran side
53+
instance_index: int = m_error_v_w.build_instance(code=inv.code, message=inv.message)
54+
55+
# Call the Fortran function
56+
# Boolean wrapping strategy, have to cast to bool
57+
res_raw: int = m_error_v_passing_w.pass_error(instance_index)
58+
res = bool(res_raw)
59+
60+
# Tell Fortran to finalise the object on the Fortran side
61+
# (all data has been used for the call now)
62+
m_error_v_w.finalise_instance(instance_index)
63+
64+
return res
65+
66+
67+
def pass_errors(invs: tuple[ErrorV, ...]) -> NP_ARRAY_OF_BOOL:
68+
"""
69+
Pass a number of errors to Fortran
70+
71+
Parameters
72+
----------
73+
invs
74+
Errors to pass to Fortran
75+
76+
Returns
77+
-------
78+
:
79+
Whether each value in `invs` is an error or not
80+
"""
81+
# Controlling memory from the Python side
82+
m_error_v_w.ensure_at_least_n_instances_can_be_passed_simultaneously(len(invs))
83+
# TODO: consider adding `build_instances` too, might be headache
84+
instance_indexes: NP_ARRAY_OF_INT = np.array(
85+
[m_error_v_w.build_instance(code=inv.code, message=inv.message) for inv in invs]
86+
)
87+
88+
# Convert the result to boolean
89+
res_raw = m_error_v_passing_w.pass_errors(instance_indexes, n=instance_indexes.size)
90+
res = res_raw.astype(bool)
91+
92+
# Tell Fortran to finalise the objects on the Fortran side
93+
# (all data has been copied to Python now)
94+
m_error_v_w.finalise_instances(instance_indexes)
95+
96+
return res
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
!> Wrapper for interfacing `m_error_v_passing` with Python
2+
!>
3+
!> Written by hand here.
4+
!> Generation to be automated in future (including docstrings of some sort).
5+
module m_error_v_passing_w
6+
7+
! => allows us to rename on import to avoid clashes
8+
! "o_" for original (TODO: better naming convention)
9+
use m_error_v_passing, only: &
10+
o_pass_error => pass_error, &
11+
o_pass_errors => pass_errors
12+
use m_error_v, only: ErrorV
13+
14+
! The manager module, which makes this all work
15+
use m_error_v_manager, only: &
16+
error_v_manager_get_instance => get_instance
17+
! error_v_manager_get_available_instance_index => get_available_instance_index, &
18+
! error_v_manager_set_instance_index_to => set_instance_index_to, &
19+
! error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least
20+
21+
implicit none
22+
private
23+
24+
public :: pass_error, pass_errors
25+
26+
contains
27+
28+
function pass_error(inv_instance_index) result(res)
29+
!! Wrapper around `m_error_v_passing.pass_error` (TODO: x-ref)
30+
31+
integer, intent(in) :: inv_instance_index
32+
!! Input values
33+
!!
34+
!! See docstring of `m_error_v_passing.pass_error` for details.
35+
!! [TODO: x-ref]
36+
!! The trick here is to pass in the instance index, not the instance itself
37+
38+
logical :: res
39+
!! Whether the instance referred to by `inv_instance_index` is an error or not
40+
41+
type(ErrorV) :: instance
42+
43+
instance = error_v_manager_get_instance(inv_instance_index)
44+
45+
! Do the Fortran call
46+
res = o_pass_error(instance)
47+
48+
end function pass_error
49+
50+
function pass_errors(inv_instance_indexes, n) result(res)
51+
!! Wrapper around `m_error_v_passing.pass_errors` (TODO: x-ref)
52+
53+
integer, dimension(n), intent(in) :: inv_instance_indexes
54+
!! Input values
55+
!!
56+
!! See docstring of `m_error_v_passing.pass_errors` for details.
57+
!! [TODO: x-ref]
58+
59+
integer, intent(in) :: n
60+
!! Number of values to pass
61+
62+
logical, dimension(n) :: res
63+
!! Whether each instance in the array backed by `inv_instance_indexes` is an error or not
64+
!
65+
! This is the major trick for wrapping.
66+
! We pass instance indexes (integers) from Python rather than the instance itself.
67+
68+
type(ErrorV), dimension(n) :: instances
69+
70+
integer :: i
71+
72+
do i = 1, n
73+
instances(i) = error_v_manager_get_instance(inv_instance_indexes(i))
74+
end do
75+
76+
! Do the Fortran call
77+
res = o_pass_errors(instances, n)
78+
79+
end function pass_errors
80+
81+
end module m_error_v_passing_w

0 commit comments

Comments
 (0)