diff --git a/include/cantera/clib/ctreactor.h b/include/cantera/clib/ctreactor.h index 5e105257a7..d535c9913c 100644 --- a/include/cantera/clib/ctreactor.h +++ b/include/cantera/clib/ctreactor.h @@ -3,7 +3,7 @@ */ // This file is part of Cantera. See License.txt in the top-level directory or -// at http://www.cantera.org/license.txt for license and copyright information. +// at https://cantera.org/license.txt for license and copyright information. #ifndef CTC_REACTOR_H #define CTC_REACTOR_H @@ -55,8 +55,13 @@ extern "C" { CANTERA_CAPI int flowdev_setMaster(int i, int n); CANTERA_CAPI double flowdev_massFlowRate(int i, double time); CANTERA_CAPI int flowdev_setMassFlowRate(int i, double mdot); - CANTERA_CAPI int flowdev_setParameters(int i, int n, const double* v); - CANTERA_CAPI int flowdev_setFunction(int i, int n); + CANTERA_CAPI int flowdev_setParameters(int i, int n, const double* v); //!< @deprecated To be removed after Cantera 2.5. + CANTERA_CAPI int flowdev_setMassFlowCoeff(int i, double v); + CANTERA_CAPI int flowdev_setValveCoeff(int i, double v); + CANTERA_CAPI int flowdev_setPressureCoeff(int i, double v); + CANTERA_CAPI int flowdev_setFunction(int i, int n); //!< @deprecated To be removed after Cantera 2.5. + CANTERA_CAPI int flowdev_setPressureFunction(int i, int n); + CANTERA_CAPI int flowdev_setTimeFunction(int i, int n); CANTERA_CAPI int wall_new2(const char* type); CANTERA_CAPI int wall_new(int type); //!< @deprecated To be changed after Cantera 2.5. diff --git a/include/cantera/zeroD/FlowDevice.h b/include/cantera/zeroD/FlowDevice.h index 5bf5caed77..999bb9c77e 100644 --- a/include/cantera/zeroD/FlowDevice.h +++ b/include/cantera/zeroD/FlowDevice.h @@ -54,7 +54,7 @@ class FlowDevice } //! Mass flow rate (kg/s). - doublereal massFlowRate(double time = -999.0) { + double massFlowRate(double time = -999.0) { if (time != -999.0) { updateMassFlowRate(time); } @@ -63,14 +63,14 @@ class FlowDevice //! Update the mass flow rate at time 'time'. This must be overloaded in //! subclassess to update m_mdot. - virtual void updateMassFlowRate(doublereal time) {} + virtual void updateMassFlowRate(double time) {} //! Mass flow rate (kg/s) of outlet species k. Returns zero if this species //! is not present in the upstream mixture. - doublereal outletSpeciesMassFlowRate(size_t k); + double outletSpeciesMassFlowRate(size_t k); //! specific enthalpy - doublereal enthalpy_mass(); + double enthalpy_mass(); //! Install a flow device between two reactors. /*! @@ -93,28 +93,64 @@ class FlowDevice return *m_out; } - //! set parameters. Generic function used only in the Matlab interface. From - //! Python or C++, device-specific functions like Valve::setPressureCoeff - //! should be used instead. + //! Set parameters. Generic function formerly used in the Matlab interface. + //! @deprecated To be removed after Cantera 2.5. virtual void setParameters(int n, const double* coeffs) { - m_coeffs.resize(n); - std::copy(coeffs, coeffs + n, m_coeffs.begin()); + warn_deprecated("FlowDevice::setParameters()", + "To be removed after Cantera 2.5. " + "Use device-specific functions (e.g. " + "Valve::setValveCoeff) instead."); + m_coeff = coeffs[0]; // vectorized coefficients are not used } //! Set a function of a single variable that is used in determining the //! mass flow rate through the device. The meaning of this function //! depends on the parameterization of the derived type. - void setFunction(Func1* f); + //! @deprecated To be removed after Cantera 2.5. + void setFunction(Func1* f) { + warn_deprecated("FlowDevice::setFunction()", + "To be removed after Cantera 2.5. " + "Use FlowDevice::setTimeFunction or " + "FlowDevice::setPressureFunction instead."); + if (typeStr()=="MassFlowController") { + setTimeFunction(f); + } else if (typeStr()=="Valve") { + setPressureFunction(f); + } + } + + //! Set a function of pressure that is used in determining the + //! mass flow rate through the device. The evaluation of mass flow + //! depends on the derived flow device class. + virtual void setPressureFunction(Func1* f); + + //! Set a function of time that is used in determining + //! the mass flow rate through the device. The evaluation of mass flow + //! depends on the derived flow device class. + virtual void setTimeFunction(Func1* g); //! Set the fixed mass flow rate (kg/s) through the flow device. - void setMassFlowRate(doublereal mdot) { + //! @deprecated To be removed after Cantera 2.5. + void setMassFlowRate(double mdot) { + warn_deprecated("FlowDevice::setMassFlowRate()", + "To be removed after Cantera 2.5. " + "Use device-specific functions (e.g. " + "Valve::setValveCoeff) instead."); m_mdot = mdot; } protected: - doublereal m_mdot; - Func1* m_func; - vector_fp m_coeffs; + double m_mdot; + + //! Function set by setPressureFunction; used by updateMassFlowRate + Func1* m_pfunc; + + //! Function set by setTimeFunction; used by updateMassFlowRate + Func1* m_tfunc; + + //! Coefficient set by derived classes; used by updateMassFlowRate + double m_coeff; + int m_type; //!< @deprecated To be removed after Cantera 2.5. private: diff --git a/include/cantera/zeroD/flowControllers.h b/include/cantera/zeroD/flowControllers.h index 4b1efb3826..8f5977b2bf 100644 --- a/include/cantera/zeroD/flowControllers.h +++ b/include/cantera/zeroD/flowControllers.h @@ -7,6 +7,7 @@ #define CT_FLOWCONTR_H #include "FlowDevice.h" +#include "cantera/base/ctexceptions.h" namespace Cantera { @@ -28,6 +29,27 @@ class MassFlowController : public FlowDevice return FlowDevice::ready() && m_mdot >= 0.0; } + //! Set the mass flow coefficient. + /*! + * *m* has units of kg/s. The mass flow rate is computed as: + * \f[\dot{m} = m g(t) \f] + * where *g* is a function of time that is set by `setTimeFunction`. + * If no function is specified, the mass flow rate defaults to: + * \f[\dot{m} = m \f] + */ + void setMassFlowCoeff(double m) { + m_coeff = m; + } + + //! Get the mass flow coefficient. + double getMassFlowCoeff() { + return m_coeff; + } + + virtual void setPressureFunction(Func1* f) { + throw NotImplementedError("MassFlowController::setPressureFunction"); + } + /// If a function of time has been specified for mdot, then update the /// stored mass flow rate. Otherwise, mdot is a constant, and does not /// need updating. @@ -49,21 +71,34 @@ class PressureController : public FlowDevice } virtual bool ready() { - return FlowDevice::ready() && m_master != 0 && m_coeffs.size() == 1; + return FlowDevice::ready() && m_master != 0; } void setMaster(FlowDevice* master) { m_master = master; } + virtual void setTimeFunction(Func1* g) { + throw NotImplementedError("PressureController::setTimeFunction"); + } + //! Set the proportionality constant between pressure drop and mass flow //! rate /*! * *c* has units of kg/s/Pa. The mass flow rate is computed as: + * \f[\dot{m} = \dot{m}_{master} + c f(\Delta P) \f] + * where *f* is a functions of pressure drop that is set by + * `setPressureFunction`. If no functions is specified, the mass flow + * rate defaults to: * \f[\dot{m} = \dot{m}_{master} + c \Delta P \f] */ void setPressureCoeff(double c) { - m_coeffs = {c}; + m_coeff = c; + } + + //! Get the pressure coefficient. + double getPressureCoeff() { + return m_coeff; } virtual void updateMassFlowRate(double time); @@ -88,18 +123,37 @@ class Valve : public FlowDevice return "Valve"; } - virtual bool ready() { - return FlowDevice::ready() && (m_coeffs.size() == 1 || m_func); + //! Set the proportionality constant between pressure drop and mass flow + //! rate + /*! + * *c* has units of kg/s/Pa. The mass flow rate is computed as: + * \f[\dot{m} = c \Delta P \f] + */ + //! @deprecated To be removed after Cantera 2.5. + void setPressureCoeff(double c) { + warn_deprecated("Valve::setParameters()", + "To be removed after Cantera 2.5. " + "Use Valve::setValveCoeff instead."); + m_coeff = c; } //! Set the proportionality constant between pressure drop and mass flow //! rate /*! * *c* has units of kg/s/Pa. The mass flow rate is computed as: + * \f[\dot{m} = c g(t) f(\Delta P) \f] + * where *g* and *f* are functions of time and pressure drop that are set + * by `setTimeFunction` and `setPressureFunction`, respectively. If no functions are + * specified, the mass flow rate defaults to: * \f[\dot{m} = c \Delta P \f] */ - void setPressureCoeff(double c) { - m_coeffs = {c}; + void setValveCoeff(double c) { + m_coeff = c; + } + + //! Get the valve coefficient. + double getValveCoeff() { + return m_coeff; } /// Compute the currrent mass flow rate, based on the pressure difference. diff --git a/interfaces/cython/cantera/_cantera.pxd b/interfaces/cython/cantera/_cantera.pxd index ded63dc255..26eff9b62e 100644 --- a/interfaces/cython/cantera/_cantera.pxd +++ b/interfaces/cython/cantera/_cantera.pxd @@ -585,17 +585,22 @@ cdef extern from "cantera/zerodim.h" namespace "Cantera": string typeStr() double massFlowRate(double) except +translate_exception cbool install(CxxReactorBase&, CxxReactorBase&) except +translate_exception - void setFunction(CxxFunc1*) + void setPressureFunction(CxxFunc1*) except +translate_exception + void setTimeFunction(CxxFunc1*) except +translate_exception cdef cppclass CxxMassFlowController "Cantera::MassFlowController" (CxxFlowDevice): CxxMassFlowController() + void setMassFlowCoeff(double) + double getMassFlowCoeff() cdef cppclass CxxValve "Cantera::Valve" (CxxFlowDevice): - void setPressureCoeff(double) CxxValve() + double getValveCoeff() + void setValveCoeff(double) cdef cppclass CxxPressureController "Cantera::PressureController" (CxxFlowDevice): CxxPressureController() + double getPressureCoeff() void setPressureCoeff(double) void setMaster(CxxFlowDevice*) @@ -1032,6 +1037,7 @@ cdef class Wall(WallBase): cdef class FlowDevice: cdef CxxFlowDevice* dev cdef Func1 _rate_func + cdef Func1 _time_func cdef str name cdef ReactorBase _upstream cdef ReactorBase _downstream diff --git a/interfaces/cython/cantera/examples/reactors/ic_engine.py b/interfaces/cython/cantera/examples/reactors/ic_engine.py index 190939bbf2..15a939a22b 100644 --- a/interfaces/cython/cantera/examples/reactors/ic_engine.py +++ b/interfaces/cython/cantera/examples/reactors/ic_engine.py @@ -125,15 +125,15 @@ def piston_speed(t): # define opening and closing of valves and injector if (np.mod(crank_angle(t_i) - inlet_open, 4 * np.pi) < np.mod(inlet_close - inlet_open, 4 * np.pi)): - inlet_valve.set_valve_coeff(inlet_valve_coeff) + inlet_valve.valve_coeff = inlet_valve_coeff test[n1] = 1 else: - inlet_valve.set_valve_coeff(0) + inlet_valve.valve_coeff = 0. if (np.mod(crank_angle(t_i) - outlet_open, 4 * np.pi) < np.mod(outlet_close - outlet_open, 4 * np.pi)): - outlet_valve.set_valve_coeff(outlet_valve_coeff) + outlet_valve.valve_coeff = outlet_valve_coeff else: - outlet_valve.set_valve_coeff(0) + outlet_valve.valve_coeff = 0. if (np.mod(crank_angle(t_i) - injector_open, 4 * np.pi) < np.mod(injector_close - injector_open, 4 * np.pi)): injector_mfc.set_mass_flow_rate(injector_mass / injector_t_open) diff --git a/interfaces/cython/cantera/reactor.pyx b/interfaces/cython/cantera/reactor.pyx index ade9de5d7a..c355582819 100644 --- a/interfaces/cython/cantera/reactor.pyx +++ b/interfaces/cython/cantera/reactor.pyx @@ -1,6 +1,7 @@ # This file is part of Cantera. See License.txt in the top-level directory or # at https://cantera.org/license.txt for license and copyright information. +import warnings from collections import defaultdict as _defaultdict import numbers as _numbers @@ -712,6 +713,45 @@ cdef class FlowDevice: """ return self.dev.massFlowRate(t) + def set_pressure_function(self, k): + r""" + Set the relationship between mass flow rate and the pressure drop across a + flow device. The mass flow rate [kg/s] is calculated given the pressure + drop [Pa] and a coefficient set by a flow device specific function. + The calculation of mass flow rate depends to the flow device. + + >>> F = FlowDevice(res1, reactor1) + >>> F.set_pressure_function(lambda dP: dP**2) + + where FlowDevice is either a Valve or PressureController object. + """ + cdef Func1 f + if isinstance(k, Func1): + f = k + else: + f = Func1(k) + self._rate_func = f + self.dev.setPressureFunction(f.func) + + def set_time_function(self, k): + r""" + Set the time dependence of a flow device. The mass flow rate [kg/s] is + calculated for a flow device, and multiplied by a function of time. + The calculation of mass flow rate depends to the flow device. + + >>> F = FlowDevice(res1, reactor1) + >>> F.set_time_function(lambda t: exp(-10 * (t - 0.5)**2)) + + where FlowDevice is either a Valve or MassFlowController object. + """ + cdef Func1 g + if isinstance(k, Func1): + g = k + else: + g = Func1(k) + self._time_func = g + self.dev.setTimeFunction(g.func) + cdef class MassFlowController(FlowDevice): r""" @@ -719,13 +759,14 @@ cdef class MassFlowController(FlowDevice): flow rate independent of upstream and downstream conditions. The equation used to compute the mass flow rate is - .. math:: + .. math:: \dot m = \max(\dot m_0*g(t), 0.), - \dot m = \max(\dot m_0, 0.0), - - where :math:`\dot m_0` is either a constant value or a function of time. - Note that if :math:`\dot m_0 < 0`, the mass flow rate will be set to zero, - since reversal of the flow direction is not allowed. + where :math:`\dot m_0` is a constant value and :math:`g(t)` is a function of + time. Both :math:`\dot m_0` and :math:`g(t)` can be set individually by + the property `mass_flow_coeff` and the method `set_time_function`, + respectively. The method `set_mass_flow_rate` combines the former + into a single function. Note that if :math:`\dot m_0*g(t) < 0`, the mass flow + rate will be set to zero, since reversal of the flow direction is not allowed. Unlike a real mass flow controller, a MassFlowController object will maintain the flow even if the downstream pressure is greater than the @@ -737,27 +778,44 @@ cdef class MassFlowController(FlowDevice): flowdevice_type = "MassFlowController" # The signature of this function causes warnings for Sphinx documentation - def __init__(self, upstream, downstream, *, name=None, mdot=None): + def __init__(self, upstream, downstream, *, name=None, mdot=1.): super().__init__(upstream, downstream, name=name) - if mdot is not None: - self.set_mass_flow_rate(mdot) + if isinstance(mdot, _numbers.Real): + self.mass_flow_coeff = mdot + else: + self.mass_flow_coeff = 1. + self.set_time_function(mdot) - def set_mass_flow_rate(self, m): + property mass_flow_coeff: + r"""Set the mass flow rate [kg/s] through the mass flow controller + as a constant, which may be modified by a function of time, see + `set_time_function`. + + >>> mfc = MassFlowController(res1, reactor1) + >>> mfc.mass_flow_coeff = 1e-4 # Set the flow rate to a constant + >>> mfc.mass_flow_coeff # Get the flow rate value """ + def __get__(self): + return (self.dev).getMassFlowCoeff() + def __set__(self, double value): + (self.dev).setMassFlowCoeff(value) + + def set_mass_flow_rate(self, m): + r""" Set the mass flow rate [kg/s] through this controller to be either a constant or an arbitrary function of time. See `Func1`. + Note that depending on the argument type, this method either changes + the property `mass_flow_coeff` or calls the `set_time_function` method. + >>> mfc.set_mass_flow_rate(0.3) >>> mfc.set_mass_flow_rate(lambda t: 2.5 * exp(-10 * (t - 0.5)**2)) """ - cdef Func1 f - if isinstance(m, Func1): - f = m + if isinstance(m, _numbers.Real): + self.mass_flow_coeff = m else: - f = Func1(m) - - self._rate_func = f - self.dev.setFunction(f.func) + self.mass_flow_coeff = 1. + self.set_time_function(m) cdef class Valve(FlowDevice): @@ -772,13 +830,18 @@ cdef class Valve(FlowDevice): :math:`\dot m = 0`. However, an arbitrary function can also be specified, such that - .. math:: \dot m = f(P_1 - P_2) + .. math:: \dot m = K_v*f(P_1 - P_2) - where :math:`f` is the arbitrary function that returns the mass flow rate given - a single argument, the pressure differential. See the documentation for the - `set_valve_coeff` method for an example. Note that it is never possible for - the flow to reverse and go from the downstream to the upstream - reactor/reservoir through a line containing a `Valve` object. + where :math:`f` is the arbitrary function that multiplies :math:`K_v` given + a single argument, the pressure differential. Further, a valve opening function + :math:`g` may be specified using the method `set_time_function`, such that + + .. math:: \dot m = K_v*g(t)*f(P_1 - P_2) + + See the documentation for the `valve_coeff` property as well as the + `set_pressure_function` and `set_time_function` methods for examples. Note that + it is never possible for the flow to reverse and go from the downstream to the + upstream reactor/reservoir through a line containing a `Valve` object. `Valve` objects are often used between an upstream reactor and a downstream reactor or reservoir to maintain them both at nearly the same @@ -789,10 +852,44 @@ cdef class Valve(FlowDevice): flowdevice_type = "Valve" # The signature of this function causes warnings for Sphinx documentation - def __init__(self, upstream, downstream, *, name=None, K=None): + def __init__(self, upstream, downstream, *, name=None, K=1.): super().__init__(upstream, downstream, name=name) - if K is not None: - self.set_valve_coeff(K) + if isinstance(K, _numbers.Real): + self.valve_coeff = K + else: + self.valve_coeff = 1. + self.set_pressure_function(K) + + property valve_coeff: + r"""Set valve coefficient, i.e. the proportionality constant between mass + flow rate and pressure drop [kg/s/Pa]. + + >>> V = Valve(res1, reactor1) + >>> V.valve_coeff = 1e-4 # Set the value of K to a constant + >>> V.valve_coeff # Get the value of K + """ + def __get__(self): + return (self.dev).getValveCoeff() + def __set__(self, double value): + (self.dev).setValveCoeff(value) + + def set_valve_function(self, k): + r""" + Set the relationship between mass flow rate and the pressure drop across the + valve. The mass flow rate [kg/s] is calculated given the pressure drop [Pa]. + + >>> V = Valve(res1, reactor1) + >>> V.set_valve_function(lambda dP: (1e-5 * dP)**2) + + .. deprecated:: 2.5 + + To be deprecated with version 2.5, and removed thereafter. + Renamed to `set_pressure_function`. + """ + warnings.warn("To be removed after Cantera 2.5. " + "Renamed to `set_pressure_function` instead", DeprecationWarning) + + self.set_pressure_function(k) def set_valve_coeff(self, k): """ @@ -803,20 +900,23 @@ cdef class Valve(FlowDevice): >>> V = Valve(res1, reactor1) >>> V.set_valve_coeff(1e-4) # Set the value of K to a constant - >>> V.set_valve_coeff(lambda dP: (1e-5 * dP)**2) # Set the value of K to a function + >>> V.set_valve_coeff(lambda dP: (1e-5 * dP)**2) # Set to a function + + .. deprecated:: 2.5 + + To be deprecated with version 2.5, and removed thereafter. + Functionality is now handled by property `valve_coeff` and + `set_pressure_function`. """ - cdef Func1 f - if isinstance(k, _numbers.Real): - kv = k - (self.dev).setPressureCoeff(k) - return + warnings.warn("To be removed after Cantera 2.5. " + "Use property `valve_coeff` and/or function " + "`set_pressure_function` instead.", DeprecationWarning) - if isinstance(k, Func1): - f = k + if isinstance(k, _numbers.Real): + self.valve_coeff = k else: - f = Func1(k) - self._rate_func = f - self.dev.setFunction(f.func) + self.valve_coeff = 1. + self.set_pressure_function(k) cdef class PressureController(FlowDevice): @@ -830,22 +930,49 @@ cdef class PressureController(FlowDevice): difference: .. math:: \dot m = \dot m_{\rm master} + K_v(P_1 - P_2). + + As an alternative, an arbitrary function of pressure differential can be + specified using the method `set_pressure_function`, such that + + .. math:: \dot m = \dot m_{\rm master} + K_v*f(P_1 - P_2) + + where :math:`f` is the arbitrary function of a single argument. """ flowdevice_type = "PressureController" # The signature of this function causes warnings for Sphinx documentation - def __init__(self, upstream, downstream, *, name=None, master=None, K=None): + def __init__(self, upstream, downstream, *, name=None, master=None, K=1.): super().__init__(upstream, downstream, name=name) if master is not None: self.set_master(master) - if K is not None: - self.set_pressure_coeff(K) + if isinstance(K, _numbers.Real): + self.pressure_coeff = K + else: + self.pressure_coeff = 1. + self.set_pressure_function(K) + + property pressure_coeff: + """ + Get/set the proportionality constant :math:`K_v` [kg/s/Pa] between the + pressure drop and the mass flow rate. + """ + def __get__(self): + return (self.dev).getPressureCoeff() + def __set__(self, double value): + (self.dev).setPressureCoeff(value) def set_pressure_coeff(self, double k): """ Set the proportionality constant :math:`K_v` [kg/s/Pa] between the pressure drop and the mass flow rate. + + .. deprecated:: 2.5 + + To be deprecated with version 2.5, and removed thereafter. + Replaced by property `pressure_coeff`. """ + warnings.warn("To be removed after Cantera 2.5. " + "Use property `pressure_coeff` instead", DeprecationWarning) (self.dev).setPressureCoeff(k) def set_master(self, FlowDevice d): diff --git a/interfaces/cython/cantera/test/test_reactor.py b/interfaces/cython/cantera/test/test_reactor.py index cbe490ce0e..47078dce52 100644 --- a/interfaces/cython/cantera/test/test_reactor.py +++ b/interfaces/cython/cantera/test/test_reactor.py @@ -9,6 +9,7 @@ import cantera as ct from . import utilities +import warnings class TestReactor(utilities.CanteraTest): reactorClass = ct.Reactor @@ -441,6 +442,7 @@ def test_mass_flow_controller(self): mfc = ct.MassFlowController(reservoir, self.r1) mfc.set_mass_flow_rate(lambda t: 0.1 if 0.2 <= t < 1.2 else 0.0) + self.assertEqual(mfc.mass_flow_coeff, 1.) self.assertEqual(mfc.type, type(mfc).__name__) self.assertEqual(len(reservoir.inlets), 0) @@ -453,6 +455,11 @@ def test_mass_flow_controller(self): ma = self.r1.volume * self.r1.density Ya = self.r1.Y + self.assertNear(mfc.mdot(0.1), 0.) + self.assertNear(mfc.mdot(0.2), 0.1) + self.assertNear(mfc.mdot(1.1), 0.1) + self.assertNear(mfc.mdot(1.2), 0.) + self.net.rtol = 1e-11 self.net.set_max_time_step(0.05) self.net.advance(2.5) @@ -463,7 +470,7 @@ def test_mass_flow_controller(self): self.assertNear(ma + 0.1, mb) self.assertArrayNear(ma * Ya + 0.1 * gas2.Y, mb * Yb) - def test_user_function_error(self): + def test_mass_flow_controller_errors(self): # Make sure Python error message actually gets displayed self.make_reactors(n_reactors=2) mfc = ct.MassFlowController(self.r1, self.r2) @@ -472,14 +479,18 @@ def test_user_function_error(self): with self.assertRaisesRegex(Exception, 'eggs'): self.net.step() + with self.assertRaisesRegex(ct.CanteraError, 'NotImplementedError'): + mfc.set_pressure_function(lambda p: p**2) + def test_valve1(self): self.make_reactors(P1=10*ct.one_atm, X1='AR:1.0', X2='O2:1.0') self.net.rtol = 1e-12 valve = ct.Valve(self.r1, self.r2) k = 2e-5 - valve.set_valve_coeff(k) + valve.valve_coeff = k self.assertEqual(self.r1.outlets, self.r2.inlets) + self.assertEqual(valve.valve_coeff, k) self.assertTrue(self.r1.energy_enabled) self.assertTrue(self.r2.energy_enabled) self.assertNear((self.r1.thermo.P - self.r2.thermo.P) * k, @@ -513,7 +524,8 @@ def test_valve2(self): self.r2.energy_enabled = False valve = ct.Valve(self.r1, self.r2) k = 2e-5 - valve.set_valve_coeff(k) + valve.valve_coeff = k + self.assertEqual(valve.valve_coeff, k) self.assertFalse(self.r1.energy_enabled) self.assertFalse(self.r2.energy_enabled) @@ -544,7 +556,9 @@ def test_valve3(self): self.net.atol = 1e-20 valve = ct.Valve(self.r1, self.r2) mdot = lambda dP: 5e-3 * np.sqrt(dP) if dP > 0 else 0.0 - valve.set_valve_coeff(mdot) + valve.set_pressure_function(mdot) + self.assertEqual(valve.valve_coeff, 1.) + Y1 = self.r1.Y kO2 = self.gas1.species_index('O2') kAr = self.gas1.species_index('AR') @@ -564,6 +578,50 @@ def speciesMass(k): self.assertNear(speciesMass(kAr), mAr) self.assertNear(speciesMass(kO2), mO2) + def test_valve_timing(self): + # test timed valve + self.make_reactors(P1=10*ct.one_atm, X1='AR:1.0', X2='O2:1.0') + self.net.rtol = 1e-12 + valve = ct.Valve(self.r1, self.r2) + k = 2e-5 + valve.valve_coeff = k + valve.set_time_function(lambda t: t>.01) + + mdot = valve.valve_coeff * (self.r1.thermo.P - self.r2.thermo.P) + self.assertTrue(valve.mdot(0.0)==0.) + self.assertTrue(valve.mdot(0.01)==0.) + self.assertTrue(valve.mdot(0.01 + 1e-9)==mdot) + self.assertTrue(valve.mdot(0.02)==mdot) + + def test_valve_deprecations(self): + # Make sure Python deprecation warnings actually get displayed + + self.make_reactors() + valve = ct.Valve(self.r1, self.r2) + k = 2e-5 + + with warnings.catch_warnings(record=True) as w: + + # cause all warnings to always be triggered. + warnings.simplefilter("always") + valve.set_valve_coeff(k) + + self.assertTrue(len(w) == 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + self.assertTrue("To be removed after Cantera 2.5. " + in str(w[-1].message)) + + with warnings.catch_warnings(record=True) as w: + + # cause all warnings to always be triggered. + warnings.simplefilter("always") + valve.set_valve_function(lambda t: t>.01) + + self.assertTrue(len(w) == 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + self.assertTrue("To be removed after Cantera 2.5. " + in str(w[-1].message)) + def test_valve_errors(self): self.make_reactors() res = ct.Reservoir() @@ -577,7 +635,7 @@ def test_valve_errors(self): # inlet and outlet cannot be reassigned v._install(self.r2, self.r1) - def test_pressure_controller(self): + def test_pressure_controller1(self): self.make_reactors(n_reactors=1) g = ct.Solution('h2o2.xml') g.TPX = 500, 2*101325, 'H2:1.0' @@ -587,11 +645,13 @@ def test_pressure_controller(self): mfc = ct.MassFlowController(inlet_reservoir, self.r1) mdot = lambda t: np.exp(-100*(t-0.5)**2) - mfc.set_mass_flow_rate(mdot) + mfc.mass_flow_coeff = 1. + mfc.set_time_function(mdot) pc = ct.PressureController(self.r1, outlet_reservoir) pc.set_master(mfc) - pc.set_pressure_coeff(1e-5) + pc.pressure_coeff = 1e-5 + self.assertEqual(pc.pressure_coeff, 1e-5) t = 0 while t < 1.0: @@ -600,6 +660,52 @@ def test_pressure_controller(self): dP = self.r1.thermo.P - outlet_reservoir.thermo.P self.assertNear(mdot(t) + 1e-5 * dP, pc.mdot(t)) + def test_pressure_controller2(self): + self.make_reactors(n_reactors=1) + g = ct.Solution('h2o2.xml') + g.TPX = 500, 2*101325, 'H2:1.0' + inlet_reservoir = ct.Reservoir(g) + g.TP = 300, 101325 + outlet_reservoir = ct.Reservoir(g) + + mfc = ct.MassFlowController(inlet_reservoir, self.r1) + mdot = lambda t: np.exp(-100*(t-0.5)**2) + mfc.mass_flow_coeff = 1. + mfc.set_time_function(mdot) + + pc = ct.PressureController(self.r1, outlet_reservoir) + pc.set_master(mfc) + pfunc = lambda dp: 1.e-5 * abs(dp)**.5 + pc.set_pressure_function(pfunc) + self.assertEqual(pc.pressure_coeff, 1.) + + t = 0 + while t < 1.0: + t = self.net.step() + self.assertNear(mdot(t), mfc.mdot(t)) + dP = self.r1.thermo.P - outlet_reservoir.thermo.P + self.assertNear(mdot(t) + pfunc(dP), pc.mdot(t)) + + def test_pressure_controller_deprecations(self): + # Make sure Python deprecation warnings actually get displayed + + self.make_reactors() + res = ct.Reservoir(self.gas1) + mfc = ct.MassFlowController(res, self.r1, mdot=0.6) + + p = ct.PressureController(self.r1, self.r2, master=mfc, K=0.5) + + with warnings.catch_warnings(record=True) as w: + + # cause all warnings to always be triggered. + warnings.simplefilter("always") + p.set_pressure_coeff(2.) + + self.assertTrue(len(w) == 1) + self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + self.assertTrue("To be removed after Cantera 2.5. " + in str(w[-1].message)) + def test_pressure_controller_errors(self): self.make_reactors() res = ct.Reservoir(self.gas1) @@ -612,19 +718,19 @@ def test_pressure_controller_errors(self): p.mdot(0.0) with self.assertRaisesRegex(ct.CanteraError, 'Device is not ready'): - p = ct.PressureController(self.r1, self.r2, master=mfc) + p = ct.PressureController(self.r1, self.r2) p.mdot(0.0) - with self.assertRaisesRegex(ct.CanteraError, 'Device is not ready'): + with self.assertRaisesRegex(ct.CanteraError, 'NotImplementedError'): p = ct.PressureController(self.r1, self.r2) - p.mdot(0.0) + p.set_time_function(lambda t: t>1.) def test_set_initial_time(self): self.make_reactors(P1=10*ct.one_atm, X1='AR:1.0', X2='O2:1.0') self.net.rtol = 1e-12 valve = ct.Valve(self.r1, self.r2) mdot = lambda dP: 5e-3 * np.sqrt(dP) if dP > 0 else 0.0 - valve.set_valve_coeff(mdot) + valve.set_pressure_function(mdot) t0 = 0.0 tf = t0 + 0.5 @@ -637,7 +743,7 @@ def test_set_initial_time(self): self.net.rtol = 1e-12 valve = ct.Valve(self.r1, self.r2) mdot = lambda dP: 5e-3 * np.sqrt(dP) if dP > 0 else 0.0 - valve.set_valve_coeff(mdot) + valve.set_pressure_function(mdot) t0 = 0.2 self.net.set_initial_time(t0) @@ -734,7 +840,7 @@ def setup(self, T0, P0, mdot_fuel, mdot_ox): self.oxidizer_mfc = ct.MassFlowController(self.oxidizer_in, self.combustor) self.oxidizer_mfc.set_mass_flow_rate(mdot_ox) self.valve = ct.Valve(self.combustor, self.exhaust) - self.valve.set_valve_coeff(1.0) + self.valve.valve_coeff = 1.0 self.net = ct.ReactorNet() self.net.add_reactor(self.combustor) diff --git a/interfaces/matlab/toolbox/@FlowDevice/setFunction.m b/interfaces/matlab/toolbox/@FlowDevice/setFunction.m index 50c765545c..36e4b55f41 100644 --- a/interfaces/matlab/toolbox/@FlowDevice/setFunction.m +++ b/interfaces/matlab/toolbox/@FlowDevice/setFunction.m @@ -11,7 +11,7 @@ function setFunction(f, mf) % if strcmp(f.type, 'MassFlowController') - k = flowdevicemethods(5, f.index, func_hndl(mf)); + k = flowdevicemethods(9, f.index, func_hndl(mf)); if k < 0 error(geterr); end diff --git a/interfaces/matlab/toolbox/@FlowDevice/setMassFlowRate.m b/interfaces/matlab/toolbox/@FlowDevice/setMassFlowRate.m index 6f1a867d1f..7dad0f6141 100644 --- a/interfaces/matlab/toolbox/@FlowDevice/setMassFlowRate.m +++ b/interfaces/matlab/toolbox/@FlowDevice/setMassFlowRate.m @@ -10,7 +10,7 @@ function setMassFlowRate(f, mdot) % Mass flow rate % if strcmp(f.type, 'MassFlowController') - k = flowdevicemethods(3, f.index, mdot); + k = flowdevicemethods(10, f.index, mdot); if k < 0 error(geterr); end diff --git a/samples/cxx/combustor/combustor.cpp b/samples/cxx/combustor/combustor.cpp index 4d4e2db33f..11d31f6e5e 100644 --- a/samples/cxx/combustor/combustor.cpp +++ b/samples/cxx/combustor/combustor.cpp @@ -86,7 +86,7 @@ void runexample() MassFlowController m3; m3.install(igniter, combustor); - m3.setFunction(&igniter_mdot); + m3.setTimeFunction(&igniter_mdot); // put a valve on the exhaust line to regulate the pressure Valve v; diff --git a/src/clib/ctreactor.cpp b/src/clib/ctreactor.cpp index 00b7820476..16872411a7 100644 --- a/src/clib/ctreactor.cpp +++ b/src/clib/ctreactor.cpp @@ -420,6 +420,7 @@ extern "C" { int flowdev_setMassFlowRate(int i, double mdot) { + /* @deprecated To be removed after Cantera 2.5. */ try { FlowDeviceCabinet::item(i).setMassFlowRate(mdot); return 0; @@ -430,6 +431,7 @@ extern "C" { int flowdev_setParameters(int i, int n, const double* v) { + /* @deprecated To be removed after Cantera 2.5. */ try { FlowDeviceCabinet::item(i).setParameters(n, v); return 0; @@ -438,8 +440,39 @@ extern "C" { } } + int flowdev_setMassFlowCoeff(int i, double v) + { + try { + FlowDeviceCabinet::get(i).setMassFlowCoeff(v); + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + } + + int flowdev_setValveCoeff(int i, double v) + { + try { + FlowDeviceCabinet::get(i).setValveCoeff(v); + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + } + + int flowdev_setPressureCoeff(int i, double v) + { + try { + FlowDeviceCabinet::get(i).setPressureCoeff(v); + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + } + int flowdev_setFunction(int i, int n) { + /* @deprecated To be removed after Cantera 2.5. */ try { FlowDeviceCabinet::item(i).setFunction(&FuncCabinet::item(n)); return 0; @@ -448,6 +481,26 @@ extern "C" { } } + int flowdev_setPressureFunction(int i, int n) + { + try { + FlowDeviceCabinet::item(i).setPressureFunction(&FuncCabinet::item(n)); + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + } + + int flowdev_setTimeFunction(int i, int n) + { + try { + FlowDeviceCabinet::item(i).setTimeFunction(&FuncCabinet::item(n)); + return 0; + } catch (...) { + return handleAllExceptions(-1, ERR); + } + } + ///////////// Walls /////////////////////// int wall_new(int type) diff --git a/src/matlab/flowdevicemethods.cpp b/src/matlab/flowdevicemethods.cpp index 6fb43d9241..fbcb9a4300 100644 --- a/src/matlab/flowdevicemethods.cpp +++ b/src/matlab/flowdevicemethods.cpp @@ -43,17 +43,28 @@ void flowdevicemethods(int nlhs, mxArray* plhs[], iok = flowdev_install(i, int(v), m); break; case 3: + // @deprecated To be removed after Cantera 2.5. iok = flowdev_setMassFlowRate(i, v); break; case 4: - iok = flowdev_setParameters(i, 1, &v); + iok = flowdev_setValveCoeff(i, v); break; case 5: + // @deprecated To be removed after Cantera 2.5. iok = flowdev_setFunction(i, int(v)); break; case 7: iok = flowdev_setMaster(i, int(v)); break; + case 8: + iok = flowdev_setPressureFunction(i, int(v)); + break; + case 9: + iok = flowdev_setTimeFunction(i, int(v)); + break; + case 10: + iok = flowdev_setMassFlowCoeff(i, v); + break; default: mexErrMsgTxt("unknown job parameter"); } diff --git a/src/zeroD/FlowDevice.cpp b/src/zeroD/FlowDevice.cpp index 9a894f79df..fa428c953e 100644 --- a/src/zeroD/FlowDevice.cpp +++ b/src/zeroD/FlowDevice.cpp @@ -10,7 +10,8 @@ namespace Cantera { -FlowDevice::FlowDevice() : m_mdot(0.0), m_func(0), m_type(0), +FlowDevice::FlowDevice() : m_mdot(0.0), m_pfunc(0), m_tfunc(0), + m_coeff(1.0), m_type(0), m_nspin(0), m_nspout(0), m_in(0), m_out(0) {} @@ -45,12 +46,17 @@ bool FlowDevice::install(ReactorBase& in, ReactorBase& out) return true; } -void FlowDevice::setFunction(Func1* f) +void FlowDevice::setPressureFunction(Func1* f) { - m_func = f; + m_pfunc = f; } -doublereal FlowDevice::outletSpeciesMassFlowRate(size_t k) +void FlowDevice::setTimeFunction(Func1* g) +{ + m_tfunc = g; +} + +double FlowDevice::outletSpeciesMassFlowRate(size_t k) { if (k >= m_nspout) { return 0.0; @@ -62,7 +68,7 @@ doublereal FlowDevice::outletSpeciesMassFlowRate(size_t k) return m_mdot * m_in->massFraction(ki); } -doublereal FlowDevice::enthalpy_mass() +double FlowDevice::enthalpy_mass() { return m_in->enthalpy_mass(); } diff --git a/src/zeroD/flowControllers.cpp b/src/zeroD/flowControllers.cpp index 43b5218268..ee9b051c18 100644 --- a/src/zeroD/flowControllers.cpp +++ b/src/zeroD/flowControllers.cpp @@ -16,10 +16,15 @@ MassFlowController::MassFlowController() : FlowDevice() { void MassFlowController::updateMassFlowRate(double time) { - if (m_func) { - m_mdot = m_func->eval(time); + if (!ready()) { + throw CanteraError("MassFlowController::updateMassFlowRate", + "Device is not ready; some parameters have not been set."); } - m_mdot = std::max(m_mdot, 0.0); + double mdot = m_coeff; + if (m_tfunc) { + mdot *= m_tfunc->eval(time); + } + m_mdot = std::max(mdot, 0.0); } PressureController::PressureController() : FlowDevice(), m_master(0) { @@ -32,9 +37,15 @@ void PressureController::updateMassFlowRate(double time) throw CanteraError("PressureController::updateMassFlowRate", "Device is not ready; some parameters have not been set."); } - m_mdot = m_master->massFlowRate(time) - + m_coeffs[0]*(in().pressure() - out().pressure()); - m_mdot = std::max(m_mdot, 0.0); + double mdot = m_coeff; + double delta_P = in().pressure() - out().pressure(); + if (m_pfunc) { + mdot *= m_pfunc->eval(delta_P); + } else { + mdot *= delta_P; + } + mdot += m_master->massFlowRate(time); + m_mdot = std::max(mdot, 0.0); } Valve::Valve() : FlowDevice() { @@ -47,13 +58,17 @@ void Valve::updateMassFlowRate(double time) throw CanteraError("Valve::updateMassFlowRate", "Device is not ready; some parameters have not been set."); } + double mdot = m_coeff; + if (m_tfunc) { + mdot *= m_tfunc->eval(time); + } double delta_P = in().pressure() - out().pressure(); - if (m_func) { - m_mdot = m_func->eval(delta_P); + if (m_pfunc) { + mdot *= m_pfunc->eval(delta_P); } else { - m_mdot = m_coeffs[0]*delta_P; + mdot *= delta_P; } - m_mdot = std::max(m_mdot, 0.0); + m_mdot = std::max(mdot, 0.0); } - + }