Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/error_v/creation_wrapper.f90',
'src/example_fgen_basic/error_v/error_v_wrapper.f90',
'src/example_fgen_basic/error_v/passing_wrapper.f90',
'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',
)

# Specify all the other source Fortran files (original files and managers)
Expand All @@ -66,11 +68,15 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/error_v/passing.f90',
'src/example_fgen_basic/fpyfgen/base_finalisable.f90',
'src/example_fgen_basic/fpyfgen/derived_type_manager_helpers.f90',
'src/example_fgen_basic/get_square_root.f90',
'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/result0D_int.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_int1D.f90',
'src/example_fgen_basic/result/result_none.f90',
)

# All Python files (wrappers and otherwise)
Expand All @@ -82,9 +88,12 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/error_v/error_v.py',
'src/example_fgen_basic/error_v/passing.py',
'src/example_fgen_basic/exceptions.py',
'src/example_fgen_basic/get_square_root.py',
'src/example_fgen_basic/get_wavelength.py',
'src/example_fgen_basic/pyfgen_runtime/__init__.py',
'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/typing.py',
)

Expand Down
1 change: 1 addition & 0 deletions scripts/inject-srcs-into-meson-build.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def main():
meson_variable, sorted(src_paths), REPO_ROOT
)

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

with open(REPO_ROOT / "meson.build", "w") as fh:
Expand Down
4 changes: 3 additions & 1 deletion src/example_fgen_basic/error_v/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ def create_errors(invs: NP_ARRAY_OF_INT) -> tuple[ErrorV, ...]:
Created errors
"""
# Get the result, but receiving an instance index rather than the object itself
instance_indexes: NP_ARRAY_OF_INT = m_error_v_creation_w.create_errors(invs)
instance_indexes: NP_ARRAY_OF_INT = m_error_v_creation_w.create_errors(
invs, len(invs)
)

# Initialise the result from the received index
res = tuple(ErrorV.from_instance_index(i) for i in instance_indexes)
Expand Down
11 changes: 7 additions & 4 deletions src/example_fgen_basic/error_v/error_v.f90
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module m_error_v

procedure, public :: build
procedure, public :: finalise
final :: clean_up
final :: finalise_auto
! get_res sort of not needed (?)
! get_err sort of not needed (?)

Expand Down Expand Up @@ -95,14 +95,17 @@ subroutine finalise(self)

end subroutine finalise

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

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

call self%finalise()
call self % finalise()

end subroutine clean_up
end subroutine finalise_auto

end module m_error_v
1 change: 1 addition & 0 deletions src/example_fgen_basic/error_v/error_v_manager.f90
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ subroutine get_available_instance_index(available_instance_index)

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

end subroutine get_available_instance_index
Expand Down
36 changes: 36 additions & 0 deletions src/example_fgen_basic/get_square_root.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
!> Get square root of a number
module m_get_square_root

use kind_parameters, only: dp
use m_error_v, only: ErrorV
use m_result_dp, only: ResultDP

implicit none(type, external)
private

public :: get_square_root

contains

function get_square_root(inv) result(res)
!! Get square root of a number

real(kind=dp), intent(in) :: inv
!! Frequency

type(ResultDP) :: res
!! Result
!!
!! Square root if the number is positive or zero.
!! Error otherwise.

if (inv >= 0) then
res = ResultDP(data_v=sqrt(inv))
else
! TODO: include input value in the message
res = ResultDP(error_v=ErrorV(code=1, message="Input value was negative"))
end if

end function get_square_root

end module m_get_square_root
72 changes: 72 additions & 0 deletions src/example_fgen_basic/get_square_root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Get square root of a number
"""

from __future__ import annotations

from example_fgen_basic.pyfgen_runtime.exceptions import (
CompiledExtensionNotFoundError,
FortranError,
)
from example_fgen_basic.result import ResultDP

try:
from example_fgen_basic._lib import m_get_square_root_w # type: ignore
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
raise CompiledExtensionNotFoundError(
"example_fgen_basic._lib.m_get_square_root_w"
) from exc

try:
from example_fgen_basic._lib import m_result_dp_w
except (ModuleNotFoundError, ImportError) as exc: # pragma: no cover
raise CompiledExtensionNotFoundError(
"example_fgen_basic._lib.m_result_dp_w"
) from exc


def get_square_root(inv: float) -> float:
"""
Get square root

Parameters
----------
inv
Value for which to get the square root

Returns
-------
:
Square root of `inv`

Raises
------
FortranError
`inv` is negative

TODO: use a more specific error
"""
result_instance_index: int = m_get_square_root_w.get_square_root(inv)
result = ResultDP.from_instance_index(result_instance_index)

if result.error_v is not None:
# TODO: be more specific
raise FortranError(result.error_v.message)
# raise LessThanZeroError(result.error_v.message)

if result.data_v is None:
raise AssertionError

res = result.data_v

# TODO: think
# I like the clarity of finalising result_instance_index here
# by having an explicit call
# (so you can see creation and finalisation in same place).
# (Probably the above is my preferred right now, but we should think about it.)
# I like the safety of finalising in `from_instance_index`.
# if not finalised(result_instance_index):
# finalise(result_instance_index)
m_result_dp_w.finalise_instance(result_instance_index)

return res
47 changes: 47 additions & 0 deletions src/example_fgen_basic/get_square_root_wrapper.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
!> Wrapper for interfacing `m_get_square_root` with python
module m_get_square_root_w

use m_result_dp, only: ResultDP
use m_get_square_root, only: o_get_square_root => get_square_root

! The manager module, which makes this all work
use m_result_dp_manager, only: &
result_dp_manager_get_available_instance_index => get_available_instance_index, &
result_dp_manager_set_instance_index_to => set_instance_index_to, &
result_dp_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least

implicit none(type, external)
private

public :: get_square_root

contains

function get_square_root(inv) result(res_instance_index)

! Annoying that this has to be injected everywhere,
! but ok it can be automated.
integer, parameter :: dp = selected_real_kind(15, 307)

real(kind=dp), intent(in) :: inv
!! inv

integer :: res_instance_index
!! Instance index of the result type

type(ResultDP) :: res

res = o_get_square_root(inv)

call result_dp_manager_ensure_instance_array_size_is_at_least(1)

! Get the instance index to return to Python
call result_dp_manager_get_available_instance_index(res_instance_index)

! Set the derived type value in the manager's array,
! ready for its attributes to be retrieved from Python.
call result_dp_manager_set_instance_index_to(res_instance_index, res)

end function get_square_root

end module m_get_square_root_w
1 change: 1 addition & 0 deletions src/example_fgen_basic/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
srcs += files(
'error_v/creation.f90',
'error_v/error_v.f90',
'error_v/passing.f90',
'fpyfgen/base_finalisable.f90',
'get_wavelength.f90',
'kind_parameters.f90',
Expand Down
6 changes: 6 additions & 0 deletions src/example_fgen_basic/pyfgen_runtime/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ def __init__(self, compiled_extension_name: str):
super().__init__(error_msg)


class FortranError(Exception):
"""
Base class for errors that originated on the Fortran side
"""


class MissingOptionalDependencyError(ImportError):
"""
Raised when an optional dependency is missing
Expand Down
7 changes: 7 additions & 0 deletions src/example_fgen_basic/result/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Definition of result values
"""

from example_fgen_basic.result.result_dp import ResultDP

__all__ = ["ResultDP"]
44 changes: 14 additions & 30 deletions src/example_fgen_basic/result/result.f90
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,15 @@ module m_result
implicit none (type, external)
private

type, abstract, public :: Result
type, abstract, public :: ResultBase
!! Result type
!!
!! Holds either the result or an error.

! class(*), allocatable :: data_v(..)
! MZ: assumed rank can only be dummy argument NOT type/class argument
! Data i.e. the result (if no error occurs)
!
! Assumed rank array
! (https://fortran-lang.discourse.group/t/assumed-rank-arrays/1049)
! Technically a Fortran 2018 feature,
! so maybe we need to update our file extensions.
! If we can't use this, just comment this out
! and leave each subclass of Result to set its data type
! (e.g. ResultInteger will have `integer :: data`,
! ResultDP1D will have `real(dp), dimension(:), allocatable :: data`)
! assumed rank can only be dummy argument NOT type/class argument
! hence leave this undefined
! Sub-classes have to define what kind of data value they support

class(ErrorV), allocatable :: error_v
!! Error
Expand All @@ -34,35 +26,27 @@ module m_result

private

! Expect sub-classes to implement
! procedure, public:: build
! TODO: Think about whether build should be on the abstract class
! or just on each concrete implementation
procedure, public :: is_error
procedure, public :: clean_up
! Expect sub-classes to implement
! procedure, public :: finalise
! final :: finalise_auto

end type Result
end type ResultBase

! interface Result
!! Constructor interface - see build (TODO: figure out cross-ref syntax) for details
! Expect sub-classes to implement
! interface ResultSubClass
!! Constructor interface - see build [cross-ref goes here] for details
! module procedure :: constructor
! end interface Result
! end interface ResultSubClass

contains

subroutine clean_up(self)
!! Finalise the instance (i.e. free/deallocate)

class(Result), intent(inout) :: self
! Hopefully can leave without docstring (like Python)

deallocate (self % error_v)

end subroutine clean_up

pure function is_error(self) result(is_err)
!! Determine whether `self` contains an error or not

class(Result), intent(in) :: self
class(ResultBase), intent(in) :: self
! Hopefully can leave without docstring (like Python)

logical :: is_err
Expand Down
Loading
Loading