Skip to content

Commit

Permalink
[numerics] Break out tabulated Func1 to Cython interface
Browse files Browse the repository at this point in the history
- the C++ defined Tabulated1 class is added as a variant of the Func1 object
  defined in Python
- this approach is compatible with existing interfaces of various Python
  methods with Func1 arguments
  • Loading branch information
ischoegl committed Jan 23, 2020
1 parent f6b6c6d commit 241efe4
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 15 deletions.
7 changes: 7 additions & 0 deletions interfaces/cython/cantera/_cantera.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ cdef extern from "cantera/cython/funcWrapper.h":
CxxFunc1(callback_wrapper, void*)
double eval(double) except +translate_exception

cdef extern from "cantera/numerics/Func1.h":
cdef cppclass CxxTabulated1 "Cantera::Tabulated1":
CxxTabulated1(vector[double]&, vector[double]&) except +translate_exception
double eval(double) except +translate_exception

cdef extern from "cantera/base/xml.h" namespace "Cantera":
cdef cppclass XML_Node:
XML_Node* findByName(string)
Expand Down Expand Up @@ -1010,6 +1015,8 @@ cdef class Func1:
cdef CxxFunc1* func
cdef object callable
cdef object exception
cpdef void __set_callback(self, object) except *
cpdef void __set_tables(self, object, object) except *

cdef class ReactorBase:
cdef CxxReactorBase* rbase
Expand Down
93 changes: 78 additions & 15 deletions interfaces/cython/cantera/func1.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# at https://cantera.org/license.txt for license and copyright information.

import sys
from numbers import Real as _real

cdef double func_callback(double t, void* obj, void** err):
"""
Expand Down Expand Up @@ -46,36 +47,98 @@ cdef class Func1:
>>> f3(6)
30.0
For simplicity, constant functions can be defined by passing the constant
For simplicity, constant functions can be defined by passing a constant
value directly::
>>> f4 = Func1(2.5)
>>> f4(0.1)
2.5
A `Func1` object representing a tabulated function is defined by sample
points and corresponding function values. Inputs are specified either by
two iterable objects containing sample point location and function values,
or a single array that concatenates those inputs in two columns. Between
sample points, values are evaluated using a linear interpolation, whereas
outside the sample interval, the value at the closest end point is returned.
Examples for tabulated `Func1` object are::
>>> t1 = Func1([[0, 2], [1, 1], [2, 0]])
>>> [t1(v) for v in [-0.5, 0, 0.5, 1.5, 2, 2.5]]
[2.0, 2.0, 1.5, 0.5, 0.0, 0.0]
>>> t2 = Func1([0, 1, 2], [2, 1, 0])
>>> [t2(v) for v in [-0.5, 0, 0.5, 1.5, 2, 2.5]]
[2.0, 2.0, 1.5, 0.5, 0.0, 0.0]
>>> t3 = Func1(np.array([0, 1, 2]), (2, 1, 0))
>>> [t3(v) for v in [-0.5, 0, 0.5, 1.5, 2, 2.5]]
[2.0, 2.0, 1.5, 0.5, 0.0, 0.0]
Note that all methods which accept `Func1` objects will also accept the
callable object and create the wrapper on their own, so it is generally
unnecessary to explicitly create a `Func1` object.
"""
def __cinit__(self, c):
def __cinit__(self, *args, **kwargs):
self.exception = None
if hasattr(c, '__call__'):
self.callable = c
self.callable = None

def __init__(self, *args):
if len(args) == 1:
c = args[0]
if hasattr(c, '__call__'):
# callback function
self.__set_callback(c)
else:
arr = np.array(c)
try:
if arr.ndim == 0:
# handle constants or unsized numpy arrays
k = float(c)
self.__set_callback(lambda t: k)
elif arr.size == 1:
# handle lists, tuples or numpy arrays with a single element
k = float(c[0])
self.__set_callback(lambda t: k)
elif arr.ndim == 2:
# tabulated function (single argument)
if arr.shape[1] == 2:
time = arr[:, 0]
fval = arr[:, 1]
self.__set_tables(time, fval)
else:
raise ValueError(
"Invalid dimensions: specification of "
"tabulated function with a single array "
"requires two columns")
else:
raise TypeError

except TypeError:
raise TypeError(
"Func1 must be constructed from a callable object, "
"a number or a numeric array with two columns")

elif len(args) == 2:
# tabulated function (two arguments mimic C++ interface)
time, fval = args
self.__set_tables(time, fval)

else:
try:
# calling float() converts numpy arrays of size 1 to scalars
k = float(c)
except TypeError:
if hasattr(c, '__len__') and len(c) == 1:
# Handle lists or tuples with a single element
k = float(c[0])
else:
raise TypeError('Func1 must be constructed from a number or'
' a callable object')
self.callable = lambda t: k
raise ValueError("Invalid number of arguments")


cpdef void __set_callback(self, c) except *:
self.callable = c
self.func = new CxxFunc1(func_callback, <void*>self)

cpdef void __set_tables(self, time, fval) except *:
cdef vector[double] tvec, fvec
for t in time:
tvec.push_back(t)
for f in fval:
fvec.push_back(f)
self.func = <CxxFunc1*>(new CxxTabulated1(tvec, fvec))

def __dealloc__(self):
del self.func

Expand Down

0 comments on commit 241efe4

Please sign in to comment.