modelutils¶
This module provides utilities to build Cython models based on Python models automatically.
Most model developers do not need to be aware of the features implemented in module
modelutils
, except that they need to initialise class Cythonizer
within the main
modules of their base and application models (see, for example, the source code of base
model hland
and application model hland_96
).
However, when implementing models with functionalities not envisaged so far, problems
might arise. Please contact the HydPy developer team then, preferably by opening an
issue on GitHub. Potentially, problems could occur when defining parameters or
sequences with larger dimensionality than anticipated. The following example shows the
Cython code lines for the get_point_states()
method of class ELSModel
, used
for deriving the test
model. By now, we did only implement 0-dimensional and
1-dimensional sequences requiring this method. After hackishly changing the
dimensionality of sequences S
, we still seem to get plausible results,
but these are untested in model applications:
>>> from hydpy.models.test import cythonizer
>>> pyxwriter = cythonizer.pyxwriter
>>> from hydpy.cythons.modelutils import PyxPxdLines
>>> lines = PyxPxdLines()
>>> pyxwriter.get_point_states(lines)
. get_point_states
>>> lines.pyx
cpdef inline void get_point_states(self) noexcept nogil:
cdef ...int... idx0
self.sequences.states.s = self.sequences.states._s_points[self.numvars.idx_stage]
for idx0 in range(self.sequences.states._sv_length):
self.sequences.states.sv[idx0] = self.sequences.states._sv_points[self.numvars.idx_stage][idx0]
>>> pyxwriter.model.sequences.states.s.NDIM = 2
>>> lines.pyx.clear()
>>> pyxwriter.get_point_states(lines)
. get_point_states
>>> lines.pyx
cpdef inline void get_point_states(self) noexcept nogil:
cdef ...int... idx0, idx1
for idx0 in range(self.sequences.states._s_length0):
for idx1 in range(self.sequences.states._s_length1):
self.sequences.states.s[idx0, idx1] = self.sequences.states._s_points[self.numvars.idx_stage][idx0, idx1]
for idx0 in range(self.sequences.states._sv_length):
self.sequences.states.sv[idx0] = self.sequences.states._sv_points[self.numvars.idx_stage][idx0]
>>> pyxwriter.model.sequences.states.s.NDIM = 3
>>> pyxwriter.get_point_states(lines)
Traceback (most recent call last):
...
NotImplementedError: NDIM of sequence `s` is higher than expected.
The following examples show the results for some methods which are also related to
numerical integration but deal with FluxSequence
objects. We start with the method
integrate_fluxes()
:
>>> lines.pyx.clear()
>>> pyxwriter.integrate_fluxes(lines)
. integrate_fluxes
>>> lines.pyx
cpdef inline void integrate_fluxes(self) noexcept nogil:
cdef ...int... jdx, idx0
self.sequences.fluxes.q = 0.
for jdx in range(self.numvars.idx_method):
self.sequences.fluxes.q = self.sequences.fluxes.q +self.numvars.dt * self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*self.sequences.fluxes._q_points[jdx]
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes.qv[idx0] = 0.
for jdx in range(self.numvars.idx_method):
self.sequences.fluxes.qv[idx0] = self.sequences.fluxes.qv[idx0] + self.numvars.dt * self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*self.sequences.fluxes._qv_points[jdx, idx0]
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
>>> lines.pyx.clear()
>>> pyxwriter.integrate_fluxes(lines)
. integrate_fluxes
>>> lines.pyx
cpdef inline void integrate_fluxes(self) noexcept nogil:
cdef ...int... jdx, idx0, idx1
for idx0 in range(self.sequences.fluxes._q_length0):
for idx1 in range(self.sequences.fluxes._q_length1):
self.sequences.fluxes.q[idx0, idx1] = 0.
for jdx in range(self.numvars.idx_method):
self.sequences.fluxes.q[idx0, idx1] = self.sequences.fluxes.q[idx0, idx1] + self.numvars.dt * self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*self.sequences.fluxes._q_points[jdx, idx0, idx1]
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes.qv[idx0] = 0.
for jdx in range(self.numvars.idx_method):
self.sequences.fluxes.qv[idx0] = self.sequences.fluxes.qv[idx0] + self.numvars.dt * self.numconsts.a_coefs[self.numvars.idx_method-1, self.numvars.idx_stage, jdx]*self.sequences.fluxes._qv_points[jdx, idx0]
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
>>> pyxwriter.integrate_fluxes(lines)
Traceback (most recent call last):
...
NotImplementedError: NDIM of sequence `q` is higher than expected.
Method reset_sum_fluxes()
:
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
>>> lines.pyx.clear()
>>> pyxwriter.reset_sum_fluxes(lines)
. reset_sum_fluxes
>>> lines.pyx
cpdef inline void reset_sum_fluxes(self) noexcept nogil:
cdef ...int... idx0
self.sequences.fluxes._q_sum = 0.
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes._qv_sum[idx0] = 0.
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
>>> lines.pyx.clear()
>>> pyxwriter.reset_sum_fluxes(lines)
. reset_sum_fluxes
>>> lines.pyx
cpdef inline void reset_sum_fluxes(self) noexcept nogil:
cdef ...int... idx0, idx1
for idx0 in range(self.sequences.fluxes._q_length0):
for idx1 in range(self.sequences.fluxes._q_length1):
self.sequences.fluxes._q_sum[idx0, idx1] = 0.
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes._qv_sum[idx0] = 0.
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
>>> pyxwriter.reset_sum_fluxes(lines)
Traceback (most recent call last):
...
NotImplementedError: NDIM of sequence `q` is higher than expected.
Method addup_fluxes()
:
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
>>> lines.pyx.clear()
>>> pyxwriter.addup_fluxes(lines)
. addup_fluxes
>>> lines.pyx
cpdef inline void addup_fluxes(self) noexcept nogil:
cdef ...int... idx0
self.sequences.fluxes._q_sum = self.sequences.fluxes._q_sum + self.sequences.fluxes.q
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes._qv_sum[idx0] = self.sequences.fluxes._qv_sum[idx0] + self.sequences.fluxes.qv[idx0]
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
>>> lines.pyx.clear()
>>> pyxwriter.addup_fluxes(lines)
. addup_fluxes
>>> lines.pyx
cpdef inline void addup_fluxes(self) noexcept nogil:
cdef ...int... idx0, idx1
for idx0 in range(self.sequences.fluxes._q_length0):
for idx1 in range(self.sequences.fluxes._q_length1):
self.sequences.fluxes._q_sum[idx0, idx1] = self.sequences.fluxes._q_sum[idx0, idx1] + self.sequences.fluxes.q[idx0, idx1]
for idx0 in range(self.sequences.fluxes._qv_length):
self.sequences.fluxes._qv_sum[idx0] = self.sequences.fluxes._qv_sum[idx0] + self.sequences.fluxes.qv[idx0]
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
>>> pyxwriter.addup_fluxes(lines)
Traceback (most recent call last):
...
NotImplementedError: NDIM of sequence `q` is higher than expected.
Method calculate_error()
:
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 0
>>> lines.pyx.clear()
>>> pyxwriter.calculate_error(lines)
. calculate_error
>>> lines.pyx
cpdef inline void calculate_error(self) noexcept nogil:
cdef ...int... idx0
cdef double abserror
self.numvars.abserror = 0.
if self.numvars.use_relerror:
self.numvars.relerror = 0.
else:
self.numvars.relerror = inf
abserror = fabs(self.sequences.fluxes._q_results[self.numvars.idx_method]-self.sequences.fluxes._q_results[self.numvars.idx_method-1])
self.numvars.abserror = max(self.numvars.abserror, abserror)
if self.numvars.use_relerror:
if self.sequences.fluxes._q_results[self.numvars.idx_method] == 0.:
self.numvars.relerror = inf
else:
self.numvars.relerror = max(self.numvars.relerror, fabs(abserror/self.sequences.fluxes._q_results[self.numvars.idx_method]))
for idx0 in range(self.sequences.fluxes._qv_length):
abserror = fabs(self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]-self.sequences.fluxes._qv_results[self.numvars.idx_method-1, idx0])
self.numvars.abserror = max(self.numvars.abserror, abserror)
if self.numvars.use_relerror:
if self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0] == 0.:
self.numvars.relerror = inf
else:
self.numvars.relerror = max(self.numvars.relerror, fabs(abserror/self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]))
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 2
>>> lines.pyx.clear()
>>> pyxwriter.calculate_error(lines)
. calculate_error
>>> lines.pyx
cpdef inline void calculate_error(self) noexcept nogil:
cdef ...int... idx0, idx1
cdef double abserror
self.numvars.abserror = 0.
if self.numvars.use_relerror:
self.numvars.relerror = 0.
else:
self.numvars.relerror = inf
for idx0 in range(self.sequences.fluxes._q_length0):
for idx1 in range(self.sequences.fluxes._q_length1):
abserror = fabs(self.sequences.fluxes._q_results[self.numvars.idx_method, idx0, idx1]-self.sequences.fluxes._q_results[self.numvars.idx_method-1, idx0, idx1])
self.numvars.abserror = max(self.numvars.abserror, abserror)
if self.numvars.use_relerror:
if self.sequences.fluxes._q_results[self.numvars.idx_method, idx0, idx1] == 0.:
self.numvars.relerror = inf
else:
self.numvars.relerror = max(self.numvars.relerror, fabs(abserror/self.sequences.fluxes._q_results[self.numvars.idx_method, idx0, idx1]))
for idx0 in range(self.sequences.fluxes._qv_length):
abserror = fabs(self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]-self.sequences.fluxes._qv_results[self.numvars.idx_method-1, idx0])
self.numvars.abserror = max(self.numvars.abserror, abserror)
if self.numvars.use_relerror:
if self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0] == 0.:
self.numvars.relerror = inf
else:
self.numvars.relerror = max(self.numvars.relerror, fabs(abserror/self.sequences.fluxes._qv_results[self.numvars.idx_method, idx0]))
>>> pyxwriter.model.sequences.fluxes.q.NDIM = 3
>>> pyxwriter.calculate_error(lines)
Traceback (most recent call last):
...
NotImplementedError: NDIM of sequence `q` is higher than expected.
Module modelutils
implements the following members:
get_dllextension()
Return the DLL file extension for the current operating system.
Lines
Handles the code lines for a .pyx or a pxd file.
PyxPxdLines
Handles the code lines for a .pyx and a pxd file.
get_methodheader()
Returns the Cython method header for methods without arguments except`self`.
decorate_method()
The decorated method returns aLines
object including a method header. However, theLines
object is empty if the respective model does not implement a method with the same name as the wrapped method.
compile_()
Translate Cython code to C code and compile it.
move_dll()
Try to find the DLL file created by functioncompile_()
and try to move it to the autogen folder of the cythons subpackage.
Cythonizer
Handles the writing, compiling and initialisation of Cython models.
PyxWriter
Translates the source code of Python models into Cython source code.
FuncConverter
Helper class for classPyxWriter
that analyses Python functions and provides the required Cython code via propertypyxlines
.
get_callbackcymodule()
Return the cython module containing the required callback module after, if necessary, creating or updating.
exp()
Cython wrapper for theexp()
function of modulenumpy
applied on a singlefloat
object.
log()
Cython wrapper for thelog()
function of modulenumpy
applied on a singlefloat
object.
fabs()
Cython wrapper for theexp()
function of modulemath
applied on a singlefloat
object.
sin()
Cython wrapper for thesin()
function of modulenumpy
applied on a singlefloat
object.
cos()
Cython wrapper for thecos()
function of modulenumpy
applied on a singlefloat
object.
tan()
Cython wrapper for thetan()
function of modulenumpy
applied on a singlefloat
object.
asin()
Cython wrapper for thearcsin()
function of modulenumpy
applied on a singlefloat
object.
acos()
Cython wrapper for thearccos()
function of modulenumpy
applied on a singlefloat
object.
atan()
Cython wrapper for thearctan()
function of modulenumpy
applied on a singlefloat
object.
isnan()
Cython wrapper for theisnan()
function of modulenumpy
applied on a singlefloat
object.
isinf()
Cython wrapper for theisinf()
function of modulenumpy
applied on a singlefloat
object.
- hydpy.cythons.modelutils.get_dllextension() str [source]¶
Return the DLL file extension for the current operating system.
The returned value depends on the response of function
system()
of moduleplatform
.get_dllextension()
returns .pyd ifsystem()
returns the string “windows” and .so for all other strings:>>> from hydpy.cythons.modelutils import get_dllextension >>> import platform >>> from unittest import mock >>> with mock.patch.object( ... platform, "system", side_effect=lambda: "Windows") as mocked: ... get_dllextension() '.pyd' >>> with mock.patch.object( ... platform, "system", side_effect=lambda: "Linux") as mocked: ... get_dllextension() '.so'
- hydpy.cythons.modelutils.TYPE2STR: dict[type[Any] | str | None, str] = {'IntConstant': 'numpy.int64_t', 'None': 'void', 'Vector': 'double[:]', 'VectorFloat': 'double[:]', 'bool': 'numpy.npy_bool', 'float': 'double', 'int': 'numpy.int64_t', 'parametertools.IntConstant': 'numpy.int64_t', 'str': 'str', <class 'NoneType'>: 'void', <class 'bool'>: 'numpy.npy_bool', <class 'float'>: 'double', <class 'hydpy.core.parametertools.IntConstant'>: 'numpy.int64_t', <class 'int'>: 'numpy.int64_t', <class 'str'>: 'str', None: 'void', numpy.ndarray[typing.Any, numpy.dtype[numpy.float64]]: 'double[:]', numpy.ndarray[typing.Any, numpy.dtype[~T]]: 'double[:]'}¶
Maps Python types to Cython compatible type declarations.
The Cython type belonging to Python’s
int
is selected to agree with numpy’s default integer type on the current platform/system.
- hydpy.cythons.modelutils.CHECKABLE_TYPES: tuple[type[Any], ...] = (<class 'bool'>, <class 'int'>, <class 'hydpy.core.parametertools.IntConstant'>, <class 'float'>, <class 'str'>, <class 'NoneType'>)¶
“Real types” of
TYPE2STR
allowed as second arguments of functionisinstance()
.
- class hydpy.cythons.modelutils.Lines(*args: str)[source]¶
-
Handles the code lines for a .pyx or a pxd file.
- class hydpy.cythons.modelutils.PyxPxdLines[source]¶
Bases:
object
Handles the code lines for a .pyx and a pxd file.
- hydpy.cythons.modelutils.get_methodheader(methodname: str, nogil: bool = False, idxarg: bool = False, inline: bool = True) str [source]¶
Returns the Cython method header for methods without arguments except`self`.
Note the influence of the configuration flag FASTCYTHON:
>>> from hydpy.cythons.modelutils import get_methodheader >>> from hydpy import config >>> config.FASTCYTHON = False >>> print(get_methodheader("test", nogil=True, idxarg=False, inline=True)) cpdef inline void test(self): >>> config.FASTCYTHON = True >>> methodheader = get_methodheader("test", nogil=True, idxarg=True, inline=False) >>> print(methodheader) cpdef void test(self, ...int... idx) noexcept nogil:
- hydpy.cythons.modelutils.decorate_method(wrapped: Callable[[PyxWriter], Iterator[str]]) Callable[[PyxWriter, PyxPxdLines], None] [source]¶
The decorated method returns a
Lines
object including a method header. However, theLines
object is empty if the respective model does not implement a method with the same name as the wrapped method.
- hydpy.cythons.modelutils.compile_(cyname: str, pyxfilepath: str, buildpath: str) None [source]¶
Translate Cython code to C code and compile it.
- hydpy.cythons.modelutils.move_dll(pyname: str, cyname: str, cydirpath: str, buildpath: str) None [source]¶
Try to find the DLL file created by function
compile_()
and try to move it to the autogen folder of the cythons subpackage.Usually, one does not need to apply
move_dll()
directly. However, if you are a model developer, you might see one of the following error messages from time to time:>>> from hydpy.cythons.modelutils import move_dll >>> from hydpy.models.hland_96 import cythonizer as c >>> move_dll(pyname=c.pyname, cyname=c.cyname, ... cydirpath=c.cydirpath, buildpath=c.buildpath) Traceback (most recent call last): ... OSError: After trying to cythonize `hland_96`, the resulting file `c_hland_96...` could not be found in directory `.../hydpy/cythons/autogen/_build` nor any of its subdirectories. The distutil report should tell whether the file has been stored somewhere else, is named somehow else, or could not be build at all.
>>> import os >>> from unittest import mock >>> from hydpy import TestIO >>> with TestIO(): ... with mock.patch.object( ... type(c), "buildpath", new_callable=mock.PropertyMock ... ) as mocked_buildpath: ... mocked_buildpath.return_value = "_build" ... os.makedirs("_build/subdir", exist_ok=True) ... filepath = f"_build/subdir/c_hland_96{get_dllextension()}" ... with open(filepath, "w"): ... pass ... with mock.patch( ... "shutil.move", ... side_effect=PermissionError("Denied!")): ... move_dll(pyname=c.pyname, cyname=c.cyname, ... cydirpath=c.cydirpath, buildpath=c.buildpath) Traceback (most recent call last): ... PermissionError: After trying to cythonize `hland_96`, when trying to move the final cython module `c_hland_96...` from directory `_build` to directory `.../hydpy/cythons/autogen`, the following error occurred: Denied! A likely error cause is that the cython module `c_hland_96...` does already exist in this directory and is currently blocked by another Python process. Maybe it helps to close all Python processes and restart the cythonization afterwards.
- class hydpy.cythons.modelutils.Cythonizer[source]¶
Bases:
object
Handles the writing, compiling and initialisation of Cython models.
- Parameters: type[Parameters]¶
- cythonize() None [source]¶
Translate Python source code of the relevant model first into Cython and then into C, compile it, and move the resulting dll file to the autogen subfolder of subpackage cythons.
- property pyname: str¶
Name of the original Python module or package.
>>> from hydpy.models.hland import cythonizer >>> cythonizer.pyname 'hland' >>> from hydpy.models.hland_96 import cythonizer >>> cythonizer.pyname 'hland_96'
- property cyname: str¶
Name of the compiled module.
>>> from hydpy.models.hland import cythonizer >>> cythonizer.cyname 'c_hland' >>> from hydpy.models.hland_96 import cythonizer >>> cythonizer.cyname 'c_hland_96'
- property cydirpath: str¶
The absolute path of the directory containing the compiled modules.
>>> from hydpy.models.hland import cythonizer >>> from hydpy import repr_ >>> repr_(cythonizer.cydirpath) '.../hydpy/cythons/autogen' >>> import os >>> os.path.exists(cythonizer.cydirpath) True
- property cymodule: ModuleType¶
The compiled module.
Property
cymodule
returns the relevant DLL module:>>> from hydpy.models.hland_96 import cythonizer >>> from hydpy.cythons.autogen import c_hland_96 >>> c_hland_96 is cythonizer.cymodule True
However, if this module is missing for some reasons, it tries to create the module first and returns it afterwards. For demonstration purposes, we define a wrong
cyname
:>>> from hydpy.cythons.modelutils import Cythonizer >>> cyname = Cythonizer.cyname >>> Cythonizer.cyname = "wrong" >>> cythonizer._cymodule = None >>> from unittest import mock >>> with mock.patch.object(Cythonizer, "cythonize") as mock: ... cythonizer.cymodule Traceback (most recent call last): ... ModuleNotFoundError: No module named 'hydpy.cythons.autogen.wrong' >>> mock.call_args_list [call()]
>>> Cythonizer.cyname = cyname
- property pyxfilepath: str¶
The absolute path of the compiled module.
>>> from hydpy.models.hland_96 import cythonizer >>> from hydpy import repr_ >>> repr_(cythonizer.pyxfilepath) '.../hydpy/cythons/autogen/c_hland_96.pyx' >>> import os >>> os.path.exists(cythonizer.pyxfilepath) True
- property dllfilepath: str¶
The absolute path of the compiled module.
>>> from hydpy.models.hland_96 import cythonizer >>> from hydpy import repr_ >>> repr_(cythonizer.dllfilepath) '.../hydpy/cythons/autogen/c_hland_96...' >>> import os >>> os.path.exists(os.path.split(cythonizer.dllfilepath)[0]) True
- class hydpy.cythons.modelutils.PyxWriter(cythonizer: Cythonizer, model: Model, pyxpath: str)[source]¶
Bases:
object
Translates the source code of Python models into Cython source code.
Method
PyxWriter
serves as a master method, which triggers the complete writing process. The other properties and methods supply the required code lines. Their names are selected to match the names of the original Python models as closely as possible.- cythonizer: Cythonizer¶
- write() None [source]¶
Collect the source code and write it into a Cython extension file (“pyx”) and its definition file (“pxd”).
- cythondistutilsoptions(lines: PyxPxdLines) None [source]¶
Cython and Distutils option lines.
Use the configuration options “FASTCYTHON” and “PROFILECYTHON” to configure the cythonization processes as follows:
>>> from hydpy.cythons.modelutils import PyxWriter >>> pyxwriter = PyxWriter(None, None, "file.pyx") >>> from hydpy.cythons.modelutils import PyxPxdLines >>> lines = PyxPxdLines() >>> pyxwriter.cythondistutilsoptions(lines) >>> lines.pyx #!python # distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION # cython: language_level=3 # cython: cpow=True # cython: boundscheck=False # cython: wraparound=False # cython: initializedcheck=False # cython: cdivision=True
>>> from hydpy import config >>> config.FASTCYTHON = False >>> config.PROFILECYTHON = True >>> lines.pyx.clear() >>> pyxwriter.cythondistutilsoptions(lines) >>> lines.pyx #!python # distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION # cython: language_level=3 # cython: cpow=True # cython: boundscheck=True # cython: wraparound=True # cython: initializedcheck=True # cython: cdivision=False # cython: linetrace=True # distutils: define_macros=CYTHON_TRACE=1 # distutils: define_macros=CYTHON_TRACE_NOGIL=1
>>> config.FASTCYTHON = True >>> config.PROFILECYTHON = False
- cimports(lines: PyxPxdLines) None [source]¶
Import command lines.
- constants(lines: PyxPxdLines) None [source]¶
Constants declaration lines.
- parameters(lines: PyxPxdLines) None [source]¶
Parameter declaration lines.
- sequences(lines: PyxPxdLines) None [source]¶
Sequence declaration lines.
- static iosequence(lines: PyxPxdLines, seq: IOSequence) None [source]¶
Declaration lines for the given
IOSequence
object.
- reset_reuseflags(lines: PyxPxdLines) None [source]¶
Reset reuse flag statements.
- classmethod load_data(lines: PyxPxdLines, subseqs: IOSequences[Any, Any, Any]) None [source]¶
Load data statements.
- classmethod save_data(lines: PyxPxdLines, subseqs: IOSequences[Any, Any, Any]) None [source]¶
Save data statements.
- set_pointer(lines: PyxPxdLines, subseqs: InputSequences | OutputSequences[Any] | LinkSequences[Any]) None [source]¶
Set pointer statements for all input, output, and link sequences.
- static set_pointer0d(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Set pointer statements for 0-dimensional link sequences.
- static get_value(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Get value statements for link sequences.
- static set_value(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Set value statements for link sequences.
- static alloc(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Allocate memory statements for 1-dimensional link sequences.
- static dealloc(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Deallocate memory statements for 1-dimensional link sequences.
- static set_pointer1d(lines: PyxPxdLines, subseqs: LinkSequences[Any]) None [source]¶
Set_pointer statements for 1-dimensional link sequences.
- classmethod set_pointerinput(lines: PyxPxdLines, subseqs: InputSequences) None [source]¶
Set pointer statements for input sequences.
- classmethod set_pointeroutput(lines: PyxPxdLines, subseqs: OutputSequences[Any]) None [source]¶
Set pointer statements for output sequences.
- numericalparameters(lines: PyxPxdLines) None [source]¶
Numeric parameter declaration lines.
- submodels(lines: PyxPxdLines) None [source]¶
Submodel declaration lines.
- modeldeclarations(lines: PyxPxdLines) None [source]¶
The attribute declarations of the model class.
- modelstandardfunctions(lines: PyxPxdLines) None [source]¶
The standard functions of the model class.
- modelnumericfunctions(lines: PyxPxdLines) None [source]¶
Numerical integration functions of the model class.
- simulate(lines: PyxPxdLines) None [source]¶
Simulation statements.
- iofunctions(lines: PyxPxdLines) None [source]¶
Input/output functions of the model class.
The result of property
iofunctions()
depends on the availability of different types of sequences. So far, the models implemented in HydPy do not reflect all possible combinations, which is why we modify thehland_96
application model in the following examples:>>> from hydpy.models.hland_96 import cythonizer >>> pyxwriter = cythonizer.pyxwriter >>> from hydpy.cythons.modelutils import PyxPxdLines >>> lines = PyxPxdLines() >>> pyxwriter.iofunctions(lines) . load_data . save_data >>> lines.pyx cpdef void load_data(self, ...int... idx) noexcept nogil: self.idx_sim = idx self.sequences.inputs.load_data(idx) if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel: self.aetmodel.load_data(idx) if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel: self.rconcmodel.load_data(idx) cpdef void save_data(self, ...int... idx) noexcept nogil: self.idx_sim = idx self.sequences.inputs.save_data(idx) self.sequences.factors.save_data(idx) self.sequences.fluxes.save_data(idx) self.sequences.states.save_data(idx) if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel: self.aetmodel.save_data(idx) if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel: self.rconcmodel.save_data(idx)
>>> pyxwriter.model.sequences.factors = None >>> pyxwriter.model.sequences.fluxes = None >>> pyxwriter.model.sequences.states = None >>> lines.pyx.clear() >>> pyxwriter.iofunctions(lines) . load_data . save_data >>> lines.pyx cpdef void load_data(self, ...int... idx) noexcept nogil: self.idx_sim = idx self.sequences.inputs.load_data(idx) if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel: self.aetmodel.load_data(idx) if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel: self.rconcmodel.load_data(idx) cpdef void save_data(self, ...int... idx) noexcept nogil: self.idx_sim = idx self.sequences.inputs.save_data(idx) if (self.aetmodel is not None) and not self.aetmodel_is_mainmodel: self.aetmodel.save_data(idx) if (self.rconcmodel is not None) and not self.rconcmodel_is_mainmodel: self.rconcmodel.save_data(idx)
>>> pyxwriter.model.sequences.inputs = None >>> lines.pyx.clear() >>> pyxwriter.iofunctions(lines) >>> lines.pyx
- new2old(lines: PyxPxdLines) None [source]¶
Old states to new states statements.
- update_receivers(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name.
- update_inlets(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name.
- run(lines: PyxPxdLines, model: RunModel) None [source]¶
Return the lines of the model method with the same name.
- update_outlets(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name.
- update_senders(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name.
- update_outputs_model(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name (except the _model suffix).
- update_outputs(lines: PyxPxdLines, subseqs: OutputSequences[Any]) None [source]¶
Lines of the subsequences method with the same name.
- calculate_single_terms(lines: PyxPxdLines, model: SolverModel) None [source]¶
Return the lines of the model method with the same name.
- calculate_full_terms(lines: PyxPxdLines, model: SolverModel) None [source]¶
Return the lines of the model method with the same name.
- property automethod2name: dict[str, tuple[type[Method], ...]]¶
Submethods selected by
AutoMethod
andSetAutoMethod
subclasses.
- property interfacemethods: set[str]¶
The full and abbreviated names of the selected model’s interface methods.
- modeluserfunctions(lines: PyxPxdLines) None [source]¶
Model-specific functions.
- callbackfeatures(lines: PyxPxdLines) None [source]¶
Features to let users define callback functions.
- automethod(lines: PyxPxdLines, name: str, submethods: tuple[type[Method], ...]) None [source]¶
Lines of a method defined by a
AutoMethod
orSetAutoMethod
subclass.
- solve(lines: PyxPxdLines) None [source]¶
Lines of the model method with the same name.
- extrapolate_error(lines: PyxPxdLines) None [source]¶
Extrapolate error statements.
- write_stubfile() None [source]¶
Write a stub file for the actual base or application model.
At the moment, HydPy creates model objects quite dynamically. In many regards, this comes with lots of conveniences. However, there two critical drawbacks compared to more static approaches: some amount of additional initialisation time and, more important, much opaqueness for code inspection tools. In this context, we experiment with “stub files” at the moment. These could either contain typing information only or define statically predefined model classes. The following example uses method
write_stubfile()
to write a (far from perfect) prototype stub file for base modelhland
:>>> from hydpy.models.hland import * >>> cythonizer.pyxwriter.write_stubfile()
This is the path to the written file:
>>> import os >>> import hydpy >>> filepath = os.path.join(hydpy.__path__[0], "hland.py") >>> os.path.exists(filepath) True
However, it’s just an experimental prototype, so we better remove it:
>>> os.remove(filepath) >>> os.path.exists(filepath) False
- class hydpy.cythons.modelutils.FuncConverter(model: Model, funcname: str, func: MethodType | Callable[[Model], None], inline: bool = True)[source]¶
Bases:
object
Helper class for class
PyxWriter
that analyses Python functions and provides the required Cython code via propertypyxlines
.- property argnames: list[str]¶
The argument names of the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).argnames ['model']
- property varnames: tuple[str, ...]¶
The variable names of the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).varnames ('self', 'con', 'der', 'inp', 'fac', 'k')
- property locnames: list[str]¶
The variable names of the handled function except for the argument names.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).locnames ['self', 'con', 'der', 'inp', 'fac', 'k']
- property subgroupnames: list[str]¶
The complete names of the subgroups relevant for the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).subgroupnames ['parameters.control', 'parameters.derived', 'sequences.inputs', 'sequences.factors']
- property subgroupshortcuts: list[str]¶
The abbreviated names of the subgroups relevant for the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).subgroupshortcuts ['con', 'der', 'inp', 'fac']
- property untypedvarnames: list[str]¶
The names of the untyped variables used in the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).untypedvarnames ['k']
- property untypedarguments: list[str]¶
The names of the untyped arguments used by the current function.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).untypedarguments []
- property untypedinternalvarnames: list[str]¶
The names of the untyped variables used in the current function except for those of the arguments.
>>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96") >>> FuncConverter(model, None, model.calc_tc_v1).untypedinternalvarnames ['k']
- property reusablemethod: type[ReusableMethod] | None¶
If the currently handled function object is a reusable method, return the corresponding subclass of
ReusableMethod
.
- property cleanlines: list[str]¶
The leaned code lines of the current function.
- The implemented cleanups:
eventually, remove method version
remove all docstrings
remove all comments
remove all empty lines
remove line bracks within brackets
remove the phrase modelutils
remove all lines containing the phrase fastaccess
replace all shortcuts with complete reference names
replace “ model.” with “ self.”
remove “.values” and “value”
remove the “: float” annotation
- static remove_linebreaks_within_equations(code: str) str [source]¶
Remove line breaks within equations.
The following example is not an exhaustive test but shows how the method works in principle:
>>> code = "asdf = \\\n(a\n+b)" >>> from hydpy.cythons.modelutils import FuncConverter >>> FuncConverter.remove_linebreaks_within_equations(code) 'asdf = (a+b)'
- static remove_imath_operators(lines: list[str]) None [source]¶
Remove mathematical expressions that require Pythons global interpreter locking mechanism.
The following example is not an exhaustive test but shows how the method works in principle:
>>> lines = [" x += 1*1"] >>> from hydpy.cythons.modelutils import FuncConverter >>> FuncConverter.remove_imath_operators(lines) >>> lines [' x = x + (1*1)']
- property pyxlines: Lines¶
Cython code lines of the current function.
- Assumptions:
The function shall be a method.
Annotations specify all argument and return types.
Non-default argument and return types are translate to “modulename.classname” strings.
Local variables are generally of type int but of type double when their name starts with d_.
Identical type names in Python and Cython when casting.
We import some classes and prepare a pure-Python instance of application model
hland_96
:>>> from types import MethodType >>> from hydpy.core.modeltools import Method, Model >>> from hydpy.core.typingtools import Vector >>> from hydpy.cythons.modelutils import FuncConverter >>> from hydpy import prepare_model, pub >>> with pub.options.usecython(False): ... model = prepare_model("hland_96")
First, we show an example of a standard method without additional arguments and returning nothing but requiring two local variables:
>>> class Calc_Test_V1(Method): ... @staticmethod ... def __call__(model: Model) -> None: ... con = model.parameters.control.fastaccess ... flu = model.sequences.fluxes.fastaccess ... inp = model.sequences.inputs.fastaccess ... for k in range(con.nmbzones): ... d_pc = con.kg[k]*inp.p[k] ... flu.pc[k] = d_pc >>> model.calc_test_v1 = MethodType(Calc_Test_V1.__call__, model) >>> FuncConverter( ... model, "calc_test_v1", model.calc_test_v1 ... ).pyxlines cpdef inline void calc_test_v1(self) noexcept nogil: cdef double d_pc cdef ...int... k for k in range(self.parameters.control.nmbzones): d_pc = self.parameters.control.kg[k]*self.sequences.inputs.p[k] self.sequences.fluxes.pc[k] = d_pc
The second example shows that float and Vector annotations translate into double and double[:] types, respectively:
>>> class Calc_Test_V2(Method): ... @staticmethod ... def __call__(model: Model, value: float, values: Vector) -> float: ... con = model.parameters.control.fastaccess ... return con.kg[0]*value*values[1] >>> model.calc_test_discontinous = MethodType(Calc_Test_V2.__call__, model) >>> FuncConverter( ... model, "calc_test_discontinous", model.calc_test_discontinous ... ).pyxlines cpdef inline double calc_test_discontinous(self, double value, double[:] values) noexcept nogil: return self.parameters.control.kg[0]*value*values[1]
Third, Python’s standard cast function translates into Cython’s cast syntax:
>>> from hydpy.interfaces import routinginterfaces >>> class Calc_Test_V3(Method): ... @staticmethod ... def __call__(model: Model) -> routinginterfaces.StorageModel_V1: ... return cast(routinginterfaces.StorageModel_V1, model.soilmodel) >>> model.calc_test_stiff1d = MethodType(Calc_Test_V3.__call__, model) >>> FuncConverter(model, "calc_test_stiff1d", model.calc_test_stiff1d).pyxlines cpdef inline masterinterface.MasterInterface calc_test_stiff1d(self) noexcept nogil: return (<masterinterface.MasterInterface>self.soilmodel)
>>> class Calc_Test_V4(Method): ... @staticmethod ... def __call__(model: Model) -> None: ... cast( ... Union[ ... routinginterfaces.RoutingModel_V1, ... routinginterfaces.RoutingModel_V2, ... ], ... model.routingmodels[0], ... ).get_partialdischargedownstream() >>> model.calc_test_v4 = MethodType(Calc_Test_V4.__call__, model) >>> FuncConverter(model, "calc_test_v4", model.calc_test_v4).pyxlines cpdef inline void calc_test_v4(self) noexcept nogil: (<masterinterface.MasterInterface>self.routingmodels[0]).get_partialdischargedownstream()
- hydpy.cythons.modelutils.get_callbackcymodule(model: Model, parameter: CallbackParameter, callback: Callable[[Model], None]) ModuleType [source]¶
Return the cython module containing the required callback module after, if necessary, creating or updating.
- hydpy.cythons.modelutils.exp(double: float) float [source]¶
Cython wrapper for the
exp()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import exp >>> from unittest import mock >>> with mock.patch("numpy.exp") as func: ... _ = exp(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.log(double: float) float [source]¶
Cython wrapper for the
log()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import log >>> from unittest import mock >>> with mock.patch("numpy.log") as func: ... _ = log(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.fabs(double: float) float [source]¶
Cython wrapper for the
exp()
function of modulemath
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import fabs >>> from unittest import mock >>> with mock.patch("math.fabs") as func: ... _ = fabs(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.sin(double: float) float [source]¶
Cython wrapper for the
sin()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import sin >>> from unittest import mock >>> with mock.patch("numpy.sin") as func: ... _ = sin(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.cos(double: float) float [source]¶
Cython wrapper for the
cos()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import cos >>> from unittest import mock >>> with mock.patch("numpy.cos") as func: ... _ = cos(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.tan(double: float) float [source]¶
Cython wrapper for the
tan()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import tan >>> from unittest import mock >>> with mock.patch("numpy.tan") as func: ... _ = tan(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.asin(double: float) float [source]¶
Cython wrapper for the
arcsin()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import asin >>> from unittest import mock >>> with mock.patch("numpy.arcsin") as func: ... _ = asin(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.acos(double: float) float [source]¶
Cython wrapper for the
arccos()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import acos >>> from unittest import mock >>> with mock.patch("numpy.arccos") as func: ... _ = acos(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.atan(double: float) float [source]¶
Cython wrapper for the
arctan()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import atan >>> from unittest import mock >>> with mock.patch("numpy.arctan") as func: ... _ = atan(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.isnan(double: float) float [source]¶
Cython wrapper for the
isnan()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import isnan >>> from unittest import mock >>> with mock.patch("numpy.isnan") as func: ... _ = isnan(123.4) >>> func.call_args call(123.4)
- hydpy.cythons.modelutils.isinf(double: float) float [source]¶
Cython wrapper for the
isinf()
function of modulenumpy
applied on a singlefloat
object.>>> from hydpy.cythons.modelutils import isnan >>> from unittest import mock >>> with mock.patch("numpy.isinf") as func: ... _ = isinf(123.4) >>> func.call_args call(123.4)