[Reactor] clarify FlowDevice interface

* differentiated Valve::setValveCoeff from PressureController::setPressureCoeff
 and introduced MassFlowController::setMassFlowCoeff for consistency.
 * introduced FlowDevice::setTimeFunction and FlowDevice::setPressureFunction to
 differentiate time-dependent and pressure-dependent functions.
 * introduced arbitrary pressure dependence for PressureController
 * deprecated FlowDevice::setFunction which is replaced by time and pressure
 specific functions.
 * introduced properties Valve.valve_coeff / PressureController.pressure_coeff /
 MassFlowController.mass_flow_coeff in Cython interface and deprecated
 Valve.set_pressure_coeff / PressureController.set_pressure_coeff
 * deprecated corresponding function calls in clib interface
 * deprecate FlowDevice.setParameters (which was only used by MATLAB interface)
This commit is contained in:
Ingmar Schoegl 2019-08-05 14:34:36 -05:00 committed by Ray Speth
parent 7523022d71
commit bc8b4be654
14 changed files with 527 additions and 108 deletions

View file

@ -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.

View file

@ -54,7 +54,7 @@ public:
}
//! 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 @@ public:
//! 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 @@ public:
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:

View file

@ -7,6 +7,7 @@
#define CT_FLOWCONTR_H
#include "FlowDevice.h"
#include "cantera/base/ctexceptions.h"
namespace Cantera
{
@ -28,6 +29,27 @@ public:
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 @@ public:
}
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 @@ public:
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) {
m_coeffs = {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 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.

View file

@ -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

View file

@ -125,15 +125,15 @@ for n1, t_i in enumerate(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)

View file

@ -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)
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 (<CxxMassFlowController*>self.dev).getMassFlowCoeff()
def __set__(self, double value):
(<CxxMassFlowController*>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 (<CxxValve*>self.dev).getValveCoeff()
def __set__(self, double value):
(<CxxValve*>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
"""
cdef Func1 f
if isinstance(k, _numbers.Real):
kv = k
(<CxxValve*>self.dev).setPressureCoeff(k)
return
>>> V.set_valve_coeff(lambda dP: (1e-5 * dP)**2) # Set to a function
if isinstance(k, Func1):
f = k
.. 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`.
"""
warnings.warn("To be removed after Cantera 2.5. "
"Use property `valve_coeff` and/or function "
"`set_pressure_function` instead.", DeprecationWarning)
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 (<CxxPressureController*>self.dev).getPressureCoeff()
def __set__(self, double value):
(<CxxPressureController*>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)
(<CxxPressureController*>self.dev).setPressureCoeff(k)
def set_master(self, FlowDevice d):

View file

@ -9,6 +9,7 @@ from .utilities import unittest
import cantera as ct
from . import utilities
import warnings
class TestReactor(utilities.CanteraTest):
reactorClass = ct.Reactor
@ -441,6 +442,7 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
# 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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestReactor(utilities.CanteraTest):
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)
@ -611,20 +717,20 @@ class TestReactor(utilities.CanteraTest):
p = ct.PressureController(self.r1, self.r2, K=0.5)
p.mdot(0.0)
with self.assertRaisesRegex(ct.CanteraError, 'Device is not ready'):
p = ct.PressureController(self.r1, self.r2, master=mfc)
p.mdot(0.0)
with self.assertRaisesRegex(ct.CanteraError, 'Device is not ready'):
p = ct.PressureController(self.r1, self.r2)
p.mdot(0.0)
with self.assertRaisesRegex(ct.CanteraError, 'NotImplementedError'):
p = ct.PressureController(self.r1, self.r2)
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 @@ class TestReactor(utilities.CanteraTest):
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 @@ class TestWellStirredReactorIgnition(utilities.CanteraTest):
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)

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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_setFunction(int i, int n)
int flowdev_setMassFlowCoeff(int i, double v)
{
try {
FlowDeviceCabinet::get<MassFlowController>(i).setMassFlowCoeff(v);
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
}
int flowdev_setValveCoeff(int i, double v)
{
try {
FlowDeviceCabinet::get<Valve>(i).setValveCoeff(v);
return 0;
} catch (...) {
return handleAllExceptions(-1, ERR);
}
}
int flowdev_setPressureCoeff(int i, double v)
{
try {
FlowDeviceCabinet::get<PressureController>(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)

View file

@ -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");
}

View file

@ -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();
}

View file

@ -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 delta_P = in().pressure() - out().pressure();
if (m_func) {
m_mdot = m_func->eval(delta_P);
} else {
m_mdot = m_coeffs[0]*delta_P;
double mdot = m_coeff;
if (m_tfunc) {
mdot *= m_tfunc->eval(time);
}
m_mdot = std::max(m_mdot, 0.0);
double delta_P = in().pressure() - out().pressure();
if (m_pfunc) {
mdot *= m_pfunc->eval(delta_P);
} else {
mdot *= delta_P;
}
m_mdot = std::max(mdot, 0.0);
}
}