""" The classes in this module are designed to allow constructing user-defined functions of one variable in Python that can be used with the Cantera C++ kernel. These classes are mostly shadow classes for corresponding classes in the C++ kernel. """ from Cantera.num import array, asarray, ravel, shape, transpose import _cantera import types class Func1: """Functors of one variable. A Functor is an object that behaves like a function. Class 'Func1' is the base class from which several functor classes derive. These classes are designed to allow specifying functions of time from Python that can be used by the C++ kernel. Functors can be added, multiplied, and divided to yield new functors. >>> f1 = Polynomial([1.0, 0.0, 3.0]) # 3*t*t + 1 >>> f1(2.0) ___13 >>> f2 = Polynomial([-1.0, 2.0]) # 2*t - 1 >>> f2(2.0) ___5 >>> f3 = f1/f2 # (3*t*t + 1)/(2*t - 1) >>> f3(2.0) ___4.3333333 """ def __init__(self, typ, n, coeffs=[]): """ The constructor is meant to be called from constructors of subclasses of Func1. See: Polynomial, Gaussian, Arrhenius, Fourier, Const, PeriodicFunction """ self.n = n self._own = 1 self._func_id = 0 self._typ = typ if _cantera.nummod == 'numpy': self.coeffs = array(coeffs, dtype=float, ndmin=1) else: self.coeffs = asarray(coeffs,'d') self._func_id = _cantera.func_new(typ, n, self.coeffs) def __del__(self): if self._func_id and self._own: _cantera.func_del(self._func_id) def __repr__(self): return self.write() def __call__(self, t): """Implements function syntax, so that F(t) is equivalent to F.value(t).""" if type(t) == types.NoneType: return self if type(t) == types.InstanceType: return CompositeFunction(self, t) else: return _cantera.func_value(self._func_id, t) def __add__(self, other): """Overloads operator '+' Returns a new function self(t) + other(t)""" # if 'other' is a number, then create a 'Const' functor for # it. if type(other) == types.FloatType: return SumFunction(self, Const(other)) return SumFunction(self, other) def __radd__(self, other): """Overloads operator '+' Returns a new function other(t) + self(t)""" # if 'other' is a number, then create a 'Const' functor for # it. if type(other) == types.FloatType: return SumFunction(Const(other),self) return SumFunction(other, self) def __sub__(self, other): """Overloads operator '-' Returns a new function self(t) - other(t)""" # if 'other' is a number, then create a 'Const' functor for # it. if type(other) != types.InstanceType: return DiffFunction(self, Const(other)) return DiffFunction(self, other) def __rsub__(self, other): """Overloads operator '-' Returns a new function other(t) - self(t)""" # if 'other' is a number, then create a 'Const' functor for # it. if type(other) != types.InstanceType: return DiffFunction(Const(other), self) return DiffFunction(other, self) def __mul__(self, other): """Overloads operator '*' Return a new function self(t)*other(t)""" if type(other) != types.InstanceType: return ProdFunction(self, Const(other)) return ProdFunction(self, other) def __rmul__(self, other): """Overloads operator '*' Returns a new function other(t)*self(t)""" if type(other) != types.InstanceType: return ProdFunction(Const(other), self) return ProdFunction(other, self) def __div__(self, other): """Overloads operator '/' Returns a new function self(t)/other(t)""" if type(other) != types.InstanceType: return RatioFunction(self, Const(other)) return RatioFunction(self, other) def __rdiv__(self, other): """Overloads operator '/' Returns a new function other(t)/self(t)""" if type(other) != types.InstanceType: return RatioFunction(Const(other), self) return RatioFunction(other, self) def func_id(self): """Internal. Return the integer index used internally to access the kernel-level object.""" return self._func_id def write(self, arg = 'x', length = 1000): return _cantera.func_write(self._func_id, length, arg) class Sin(Func1): def __init__(self,omega=1.0): Func1.__init__(self,100,1,omega) class Cos(Func1): def __init__(self, omega=1.0): Func1.__init__(self,102,1,omega) class Exp(Func1): def __init__(self,A=1.0): Func1.__init__(self,104,1,A) class Pow(Func1): def __init__(self, n): Func1.__init__(self,106,1,n) class Polynomial(Func1): """A polynomial. Instances of class 'Polynomial' evaluate \f[ f(t) = \sum_{n = 0}^N a_n t^n. \f] The coefficients are supplied as a list, beginning with \f$a_N\f$ and ending with \f$a_0\f$. >>> p1 = Polynomial([1.0, -2.0, 3.0]) # 3t^2 - 2t + 1 >>> p2 = Polynomial([6.0, 8.0]) # 8t + 6 """ def __init__(self, coeffs=[]): """ coeffs - polynomial coefficients """ Func1.__init__(self, 2, len(coeffs)-1, coeffs) class Gaussian(Func1): """A Gaussian pulse. Instances of class 'Gaussian' evaluate \f[ f(t) = A \exp[-(t - t_0) / \tau] \f] where \f[ \tau = \frac{\mbox{FWHM}}{2.0\sqrt{\ln(2.0)}} \f] 'FWHM' denotes the full width at half maximum. As an example, here is how to create a Gaussian pulse with peak amplitude 10.0, centered at time 2.0, with full-width at half max = 0.2: >>> f = Gaussian(A = 10.0, t0 = 2.0, FWHM = 0.2) >>> f(2.0) ___10 >>> f(1.9) ___5 >>> f(2.1) ___5 """ def __init__(self, A, t0, FWHM): coeffs = array([A, t0, FWHM], 'd') Func1.__init__(self, 4, 0, coeffs) class Fourier(Func1): """Fourier series. Instances of class 'Fourier' evaluate the Fourier series \f[ f(t) = \frac{a_0}{2} + \sum_{n=1}^N [a_n \cos(n\omega t) + b_n \sin(n \omega t)] \f] where \f[ a_n = \frac{\omega}{\pi} \int_{-\pi/\omega}^{\pi/\omega} f(t) \cos(n \omega t) dt \f] and \f[ b_n = \frac{\omega}{\pi} \int_{-\pi/\omega}^{\pi/\omega} f(t) \sin(n \omega t) dt. \f] The function \f$ f(t) \f$ is periodic, with period \f$ T = 2\pi/\omega \f$. As an example, a function with Fourier components up to the second harmonic is constructed as follows: >>> coeffs = [(a0, b0), (a1, b1), (a2, b2)] >>> f = Fourier(omega, coeffs) Note that 'b0' must be specified, but is not used. The value of 'b0' is arbitrary. """ def __init__(self, omega, coefficients): """ omega - fundamental frequency [radians/sec]. coefficients - List of (a,b) pairs, beginning with \f$n = 0\f$. """ cc = asarray(coefficients,'d') n, m = cc.shape if m <> 2: raise CanteraError('provide (a, b) for each term') cc[0,1] = omega Func1.__init__(self, 1, n-1, ravel(transpose(cc))) ##Sum of modified Arrhenius terms. Instances of class 'Arrhenius' evaluate # \f[ # f(T) = \sum_{n=1}^N A_n T^{b_n}\exp(-E_n/T) # \f] # # Example: # # >>> f = Arrhenius([(a0, b0, e0), (a1, b1, e1)]) # class Arrhenius(Func1): """Sum of modified Arrhenius terms. Instances of class 'Arrhenius' evaluate \f[ f(T) = \sum_{n=1}^N A_n T^{b_n}\exp(-E_n/T) \f] Example: >>> f = Arrhenius([(a0, b0, e0), (a1, b1, e1)]) """ def __init__(self, coefficients): """ coefficients - sequence of \f$(A, b, E)\f$ triplets. """ cc = asarray(coefficients,'d') n, m = cc.shape if m <> 3: raise CanteraError('Three Arrhenius parameters (A, b, E) required.') Func1.__init__(self, 3, n, ravel(cc)) class Const(Func1): """Constant function. Objects created by function Const act as functions that have a constant value. These are used internally whenever a statement like >>> f = Gausian(2.0, 1.0, 0.1) + 4.0 is encountered. The addition operator of class Func1 is defined so that this is equivalent to >>> f = SumFunction(Gaussian(2.0, 1.0, 0.1), Const(4.0)) Function Const returns instances of class Polynomial that have degree zero, with the constant term set to the desired value. """ def __init__(self, value): Func1.__init__(self,110,1,value) #return Polynomial([value]) class PeriodicFunction(Func1): """Converts a function into a periodic function with period T.""" def __init__(self, func, T): """ func - initial non-periodic function T - period [s] """ Func1.__init__(self, 50, func.func_id(), array([T],'d')) func._own = 0 # functions that combine two functions class ComboFunc1(Func1): """ Combines two functions. This class is the base class for functors that combine two other functors in a binary operation. """ def __init__(self, typ, f1, f2): self._own = 1 self._func_id = 0 self._typ = typ if type(f1) == types.IntType: f1 = Const(f1) if type(f2) == types.IntType: f2 = Const(f2) self.f1 = f1 self.f2 = f2 self.f1._own = 0 self.f2._own = 0 self._func_id = _cantera.func_newcombo(typ, f1.func_id(), f2.func_id()) class SumFunction(ComboFunc1): """Sum of two functions. Instances of class SumFunction evaluate the sum of two supplied functors. It is not necessary to explicitly create an instance of SumFunction, since the addition operator of the base class is overloaded to return a SumFunction instance. >>> f1 = Polynomial([2.0, 1.0]) >>> f2 = Polynomial([3.0, -5.0]) >>> f3 = f1 + f2 # functor to evaluate (2t + 1) + (3t - 5) In this example, object 'f3' is a functor of class'SumFunction' that calls f1 and f2 and returns their sum. """ def __init__(self, f1, f2): """ f1 - first functor. f2 - second functor. """ ComboFunc1.__init__(self, 20, f1, f2) class DiffFunction(ComboFunc1): """Difference of two functions. Instances of class DiffFunction evaluate the difference of two supplied functors. It is not necessary to explicitly create an instance of DiffFunction, since the subtraction operator of the base class is overloaded to return a DiffFunction instance. >>> f1 = Polynomial([2.0, 1.0]) >>> f2 = Polynomial([3.0, -5.0]) >>> f3 = f1 - f2 # functor to evaluate (2t + 1) - (3t - 5) In this example, object 'f3' is a functor of class'DiffFunction' that calls f1 and f2 and returns their difference. """ def __init__(self, f1, f2): """ f1 - first functor. f2 - second functor. """ ComboFunc1.__init__(self, 25, f1, f2) class ProdFunction(ComboFunc1): """Product of two functions. Instances of class ProdFunction evaluate the product of two supplied functors. It is not necessary to explicitly create an instance of 'ProdFunction', since the multiplication operator of the base class is overloaded to return a 'ProdFunction' instance. >>> f1 = Polynomial([2.0, 1.0]) >>> f2 = Polynomial([3.0, -5.0]) >>> f3 = f1 * f2 # functor to evaluate (2t + 1)*(3t - 5) In this example, object 'f3' is a functor of class'ProdFunction' that calls f1 and f2 and returns their product. """ def __init__(self, f1, f2): """ f1 - first functor. f2 - second functor. """ ComboFunc1.__init__(self, 30, f1, f2) class RatioFunction(ComboFunc1): """Ratio of two functions. Instances of class RatioFunction evaluate the ratio of two supplied functors. It is not necessary to explicitly create an instance of 'RatioFunction', since the division operator of the base class is overloaded to return a RatioFunction instance. >>> f1 = Polynomial([2.0, 1.0]) >>> f2 = Polynomial([3.0, -5.0]) >>> f3 = f1 / f2 # functor to evaluate (2t + 1)/(3t - 5) In this example, object 'f3' is a functor of class'RatioFunction' that calls f1 and f2 and returns their ratio. """ def __init__(self, f1, f2): """ f1 - first functor. f2 - second functor. """ ComboFunc1.__init__(self, 40, f1, f2) ## Function of a function. # Instances of class CompositeFunction evaluate f(g(t)) for two supplied # functors f and g. It is not necessary to explicitly create an instance # of 'CompositeFunction', since the () operator of the base class is # overloaded to return a CompositeFunction when called with a functor # argument. # @example # >>> f1 = Polynomial([2.0, 1.0]) # >>> f2 = Polynomial([3.0, -5.0]) # >>> f3 = f1(f2) # functor to evaluate 2(3t - 5) + 1 # In this example, object 'f3' is a functor of class'CompositeFunction' # that calls f1 and f2 and returns f1(f2(t)). class CompositeFunction(ComboFunc1): def __init__(self, f1, f2): """ f1 - first functor. f2 - second functor. """ ComboFunc1.__init__(self, 60, f1, f2) class DerivativeFunction(Func1): def __init__(self, f): self.f = f #f._own = 0 self._own = 1 self._func_id = _cantera.func_derivative(f.func_id()) ## # The derivative of f # def derivative(f): return DerivativeFunction(f)