Skip to content

Commit 4322463

Browse files
authored
Merge pull request #34 from openscm/result-type-zn
Add first test of raising error on python side
2 parents 02b5392 + 3c2a6a4 commit 4322463

23 files changed

Lines changed: 1133 additions & 175 deletions

meson.build

Lines changed: 10 additions & 1 deletion
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)
@@ -66,11 +68,15 @@ if pyprojectwheelbuild_enabled
6668
'src/example_fgen_basic/error_v/passing.f90',
6769
'src/example_fgen_basic/fpyfgen/base_finalisable.f90',
6870
'src/example_fgen_basic/fpyfgen/derived_type_manager_helpers.f90',
71+
'src/example_fgen_basic/get_square_root.f90',
6972
'src/example_fgen_basic/get_wavelength.f90',
7073
'src/example_fgen_basic/kind_parameters.f90',
7174
'src/example_fgen_basic/result/result.f90',
72-
'src/example_fgen_basic/result/result0D_int.f90',
75+
'src/example_fgen_basic/result/result_dp.f90',
76+
'src/example_fgen_basic/result/result_dp_manager.f90',
7377
'src/example_fgen_basic/result/result_int.f90',
78+
'src/example_fgen_basic/result/result_int1D.f90',
79+
'src/example_fgen_basic/result/result_none.f90',
7480
)
7581

7682
# All Python files (wrappers and otherwise)
@@ -82,9 +88,12 @@ if pyprojectwheelbuild_enabled
8288
'src/example_fgen_basic/error_v/error_v.py',
8389
'src/example_fgen_basic/error_v/passing.py',
8490
'src/example_fgen_basic/exceptions.py',
91+
'src/example_fgen_basic/get_square_root.py',
8592
'src/example_fgen_basic/get_wavelength.py',
8693
'src/example_fgen_basic/pyfgen_runtime/__init__.py',
8794
'src/example_fgen_basic/pyfgen_runtime/exceptions.py',
95+
'src/example_fgen_basic/result/__init__.py',
96+
'src/example_fgen_basic/result/result_dp.py',
8897
'src/example_fgen_basic/typing.py',
8998
)
9099

scripts/inject-srcs-into-meson-build.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def main():
9393
meson_variable, sorted(src_paths), REPO_ROOT
9494
)
9595

96+
# TODO: something wrong in here
9697
meson_build_out = re.sub(pattern, substitution, meson_build_out)
9798

9899
with open(REPO_ROOT / "meson.build", "w") as fh:

src/example_fgen_basic/error_v/creation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ def create_errors(invs: NP_ARRAY_OF_INT) -> tuple[ErrorV, ...]:
8080
Created errors
8181
"""
8282
# Get the result, but receiving an instance index rather than the object itself
83-
instance_indexes: NP_ARRAY_OF_INT = m_error_v_creation_w.create_errors(invs)
83+
instance_indexes: NP_ARRAY_OF_INT = m_error_v_creation_w.create_errors(
84+
invs, len(invs)
85+
)
8486

8587
# Initialise the result from the received index
8688
res = tuple(ErrorV.from_instance_index(i) for i in instance_indexes)

src/example_fgen_basic/error_v/error_v.f90

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module m_error_v
3737

3838
procedure, public :: build
3939
procedure, public :: finalise
40-
final :: clean_up
40+
final :: finalise_auto
4141
! get_res sort of not needed (?)
4242
! get_err sort of not needed (?)
4343

@@ -95,14 +95,17 @@ subroutine finalise(self)
9595

9696
end subroutine finalise
9797

98-
subroutine clean_up(self)
98+
subroutine finalise_auto(self)
9999
!! Finalise the instance (i.e. free/deallocate)
100+
!!
101+
!! This method is expected to be called automatically
102+
!! by clever clean up, which is why it differs from [TODO x-ref] `finalise`
100103

101104
type(ErrorV), intent(inout) :: self
102105
! Hopefully can leave without docstring (like Python)
103106

104-
call self%finalise()
107+
call self % finalise()
105108

106-
end subroutine clean_up
109+
end subroutine finalise_auto
107110

108111
end module m_error_v

src/example_fgen_basic/error_v/error_v_manager.f90

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ subroutine get_available_instance_index(available_instance_index)
7878

7979
! TODO: switch to returning a Result type with an error set
8080
! res = ResultInt(ErrorV(code=1, message="No available instances"))
81+
print *, "print"
8182
error stop 1
8283

8384
end subroutine get_available_instance_index
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
!> Get square root of a number
2+
module m_get_square_root
3+
4+
use kind_parameters, only: dp
5+
use m_error_v, only: ErrorV
6+
use m_result_dp, only: ResultDP
7+
8+
implicit none(type, external)
9+
private
10+
11+
public :: get_square_root
12+
13+
contains
14+
15+
function get_square_root(inv) result(res)
16+
!! Get square root of a number
17+
18+
real(kind=dp), intent(in) :: inv
19+
!! Frequency
20+
21+
type(ResultDP) :: res
22+
!! Result
23+
!!
24+
!! Square root if the number is positive or zero.
25+
!! Error otherwise.
26+
27+
if (inv >= 0) then
28+
res = ResultDP(data_v=sqrt(inv))
29+
else
30+
! TODO: include input value in the message
31+
res = ResultDP(error_v=ErrorV(code=1, message="Input value was negative"))
32+
end if
33+
34+
end function get_square_root
35+
36+
end module m_get_square_root
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Get square root of a number
3+
"""
4+
5+
from __future__ import annotations
6+
7+
from example_fgen_basic.pyfgen_runtime.exceptions import (
8+
CompiledExtensionNotFoundError,
9+
FortranError,
10+
)
11+
from example_fgen_basic.result import ResultDP
12+
13+
try:
14+
from example_fgen_basic._lib import m_get_square_root_w # type: ignore
15+
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
16+
raise CompiledExtensionNotFoundError(
17+
"example_fgen_basic._lib.m_get_square_root_w"
18+
) from exc
19+
20+
try:
21+
from example_fgen_basic._lib import m_result_dp_w
22+
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
23+
raise CompiledExtensionNotFoundError(
24+
"example_fgen_basic._lib.m_result_dp_w"
25+
) from exc
26+
27+
28+
def get_square_root(inv: float) -> float:
29+
"""
30+
Get square root
31+
32+
Parameters
33+
----------
34+
inv
35+
Value for which to get the square root
36+
37+
Returns
38+
-------
39+
:
40+
Square root of `inv`
41+
42+
Raises
43+
------
44+
FortranError
45+
`inv` is negative
46+
47+
TODO: use a more specific error
48+
"""
49+
result_instance_index: int = m_get_square_root_w.get_square_root(inv)
50+
result = ResultDP.from_instance_index(result_instance_index)
51+
52+
if result.error_v is not None:
53+
# TODO: be more specific
54+
raise FortranError(result.error_v.message)
55+
# raise LessThanZeroError(result.error_v.message)
56+
57+
if result.data_v is None:
58+
raise AssertionError
59+
60+
res = result.data_v
61+
62+
# TODO: think
63+
# I like the clarity of finalising result_instance_index here
64+
# by having an explicit call
65+
# (so you can see creation and finalisation in same place).
66+
# (Probably the above is my preferred right now, but we should think about it.)
67+
# I like the safety of finalising in `from_instance_index`.
68+
# if not finalised(result_instance_index):
69+
# finalise(result_instance_index)
70+
m_result_dp_w.finalise_instance(result_instance_index)
71+
72+
return res
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/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
srcs += files(
22
'error_v/creation.f90',
33
'error_v/error_v.f90',
4+
'error_v/passing.f90',
45
'fpyfgen/base_finalisable.f90',
56
'get_wavelength.f90',
67
'kind_parameters.f90',

src/example_fgen_basic/pyfgen_runtime/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ def __init__(self, compiled_extension_name: str):
1818
super().__init__(error_msg)
1919

2020

21+
class FortranError(Exception):
22+
"""
23+
Base class for errors that originated on the Fortran side
24+
"""
25+
26+
2127
class MissingOptionalDependencyError(ImportError):
2228
"""
2329
Raised when an optional dependency is missing

0 commit comments

Comments
 (0)