1196 lines
42 KiB
Python
1196 lines
42 KiB
Python
"""
|
|
Zero-dimensional reactors.
|
|
"""
|
|
|
|
import _cantera
|
|
from Cantera.num import array, zeros
|
|
from Cantera.exceptions import CanteraError
|
|
import types
|
|
|
|
_ilr = {'left':0, 'right':1, 'unknown':-1}
|
|
|
|
class ReactorBase:
|
|
"""Base class for reactors and reservoirs.
|
|
|
|
Classes Reactor and Reservoir derive from a common base class
|
|
ReactorBase. They have the same set of methods, which are all
|
|
inherited from ReactorBase.
|
|
|
|
(This is not quite true in the corresponding classes in the
|
|
Cantera C++ kernel. There class Reactor defines some methods that
|
|
class Reservoir doesn't. These are used internally by the
|
|
ReactorNet instance that integrates the system of ODEs describing
|
|
the network to evaluate the portion of the ODE system associated
|
|
with that reactor.)
|
|
"""
|
|
|
|
def __init__(self, name = '', contents = None,
|
|
volume = 1.0, energy = 'on',
|
|
type = -1, verbose = 0):
|
|
"""
|
|
See :class:`.Reactor` for a description of the constructor parameters.
|
|
The *type* parameter specifies whether a :class:`.Reactor` (type = 2) or
|
|
:class:`.Reservoir` (type = 1) will be created.
|
|
"""
|
|
self.__reactor_id = _cantera.reactor_new(type)
|
|
self._type = type
|
|
self._inlets = []
|
|
self._outlets = []
|
|
self._walls = []
|
|
self._reservoirs = []
|
|
self._name = name
|
|
self._verbose = verbose
|
|
self._paramid = []
|
|
self.insert(contents)
|
|
self._setInitialVolume(volume)
|
|
self._setEnergy(energy)
|
|
if self._verbose:
|
|
print 'Created '+self._name
|
|
print ' Volume = ',volume,' m^3'
|
|
if energy <> 'on':
|
|
print ' Temperature will be held constant'
|
|
print ' Initial State:'
|
|
print contents
|
|
|
|
|
|
def __del__(self):
|
|
"""Delete the reactor instance."""
|
|
if self._verbose:
|
|
print 'Deleting '+self._name
|
|
_cantera.reactor_del(self.__reactor_id)
|
|
|
|
def __str__(self):
|
|
s = self._name
|
|
s += ':\n Volume = '+`self.volume()`
|
|
if self._contents:
|
|
s += "\n"+`self._contents`
|
|
return s
|
|
|
|
def __repr__(self):
|
|
s = self._name
|
|
s += ':\n Volume = '+`self.volume()`
|
|
if self._contents:
|
|
s += ": \n"+`self._contents`
|
|
return s
|
|
|
|
def name(self):
|
|
"""The name of the reactor."""
|
|
return self._name
|
|
|
|
def reactor_id(self):
|
|
"""The integer index used to access the kernel reactor
|
|
object. For internal use."""
|
|
return self.__reactor_id
|
|
|
|
def insert(self, contents):
|
|
"""
|
|
Insert *contents* into the reactor. Sets the objects used to compute
|
|
thermodynamic properties and kinetic rates.
|
|
"""
|
|
# store a reference to contents so that it will live as long
|
|
# as this object
|
|
self._contents = contents
|
|
if contents:
|
|
_cantera.reactor_setThermoMgr(self.__reactor_id, contents._phase_id)
|
|
_cantera.reactor_setKineticsMgr(self.__reactor_id, contents.ckin)
|
|
|
|
|
|
def setInitialTime(self, T0):
|
|
"""Deprecated.
|
|
Set the initial time. Restarts integration from this time
|
|
using the current state as the initial condition. Default: 0.0 s"""
|
|
raise "use method setInitialTime of class ReactorNet"
|
|
#_cantera.reactor_setInitialTime(self.__reactor_id, T0)
|
|
|
|
def _setInitialVolume(self, V0):
|
|
"""Set the initial reactor volume. """
|
|
_cantera.reactor_setInitialVolume(self.__reactor_id, V0)
|
|
|
|
def _setEnergy(self, eflag):
|
|
"""Turn the energy equation on or off. If the argument is the
|
|
string ``'off'`` or the number 0, the energy equation is disabled,
|
|
and the reactor temperature is held constant at its initial
|
|
value."""
|
|
ie = 1
|
|
if eflag == 'off' or eflag == 0:
|
|
ie = 0
|
|
if self._verbose:
|
|
if ie:
|
|
print 'enabling energy equation for reactor',self._name
|
|
else:
|
|
print 'disabling energy equation for reactor',self._name
|
|
_cantera.reactor_setEnergy(self.__reactor_id, ie)
|
|
|
|
def temperature(self):
|
|
"""The temperature in the reactor [K]."""
|
|
return _cantera.reactor_temperature(self.__reactor_id)
|
|
|
|
def density(self):
|
|
"""The density of the fluid in the reactor [kg/m^3]."""
|
|
return _cantera.reactor_density(self.__reactor_id)
|
|
|
|
def volume(self):
|
|
"""The total reactor volume [m^3]. The volume may change with time
|
|
if non-rigid walls are installed on the reactor."""
|
|
return _cantera.reactor_volume(self.__reactor_id)
|
|
|
|
def time(self):
|
|
"""Deprecated. The current time [s]."""
|
|
raise "use method time of class ReactorNet"
|
|
#return _cantera.reactor_time(self.__reactor_id)
|
|
|
|
def mass(self):
|
|
"""The total mass of fluid in the reactor [kg]."""
|
|
return _cantera.reactor_mass(self.__reactor_id)
|
|
|
|
def enthalpy_mass(self):
|
|
"""The specific enthalpy of the fluid in the reactor [J/kg]."""
|
|
return _cantera.reactor_enthalpy_mass(self.__reactor_id)
|
|
|
|
def intEnergy_mass(self):
|
|
"""The specific internal energy of the fluid in the reactor [J/kg]."""
|
|
return _cantera.reactor_intEnergy_mass(self.__reactor_id)
|
|
|
|
def pressure(self):
|
|
"""The pressure in the reactor [Pa]."""
|
|
return _cantera.reactor_pressure(self.__reactor_id)
|
|
|
|
def advance(self, time):
|
|
"""Deprecated.
|
|
Advance the state of the reactor in time from the current
|
|
time to time *time*. Note: this method is deprecated. See
|
|
:class:`.ReactorNet`."""
|
|
raise "use method advance of class ReactorNet"
|
|
#return _cantera.reactor_advance(self.__reactor_id, time)
|
|
|
|
def step(self, time):
|
|
"""Deprecated.
|
|
Take one internal time step from the current time toward
|
|
time *time*. Note: this method is deprecated. See class
|
|
:class:`.ReactorNet`."""
|
|
raise "use method step of class ReactorNet"
|
|
#return _cantera.reactor_step(self.__reactor_id, time)
|
|
|
|
def massFraction(self, s):
|
|
"""The mass fraction of species *s*, specified either by name or
|
|
index number.
|
|
|
|
>>> y1 = r.massFraction(7)
|
|
0.02
|
|
>>> y2 = r.massFraction('CH3O')
|
|
0.02
|
|
"""
|
|
if type(s) == types.StringType:
|
|
kk = self._contents.speciesIndex(s)
|
|
else:
|
|
kk = s
|
|
return _cantera.reactor_massFraction(self.__reactor_id, kk)
|
|
|
|
def massFractions(self):
|
|
"""Return an array of the species mass fractions."""
|
|
nsp = self._contents.nSpecies()
|
|
y = zeros(nsp,'d')
|
|
for k in range(nsp):
|
|
y[k] = self.massFraction(k)
|
|
return y
|
|
|
|
def moleFractions(self):
|
|
"""Return an array of the species mole fractions."""
|
|
y = self.massFractions()
|
|
self._contents.setMassFractions(y)
|
|
return self._contents.moleFractions()
|
|
|
|
def moleFraction(self, s):
|
|
"""The mole fraction of species s, specified either by name or
|
|
index number.
|
|
|
|
>>> x1 = r.moleFraction(9)
|
|
0.00012
|
|
>>> x2 = r.moleFraction('CH3')
|
|
0.00012
|
|
"""
|
|
if type(s) == types.StringType:
|
|
kk = self._contents.speciesIndex(s)
|
|
else:
|
|
kk = s
|
|
x = self.moleFractions()
|
|
return x[kk]
|
|
|
|
def inlets(self):
|
|
"""Return the list of flow devices installed on inlets to this reactor.
|
|
This method can be used to access information about the flows entering
|
|
the reactor:
|
|
|
|
>>> for n in r.inlets():
|
|
... print n.name(), n.massFlowRate()
|
|
|
|
See: :class:`.MassFlowController`, :class:`.Valve`,
|
|
:class:`.PressureController`.
|
|
"""
|
|
return self._inlets
|
|
|
|
def outlets(self):
|
|
"""Return the list of flow devices installed on outlets
|
|
on this reactor.
|
|
|
|
>>> for o in r.outlets():
|
|
... print o.name(), o.massFlowRate()
|
|
|
|
See: :class:`.MassFlowController`, :class:`.Valve`,
|
|
:class:`.PressureController`.
|
|
"""
|
|
return self._outlets
|
|
|
|
def walls(self):
|
|
"""Return the list of walls installed on this reactor.
|
|
|
|
>>> for w in r.walls():
|
|
... print w.name()
|
|
|
|
See: :class:`.Wall`.
|
|
"""
|
|
return self._walls
|
|
|
|
def _addInlet(self, inlet, other):
|
|
"""For internal use. Store a reference to *inlet*
|
|
so that it will not be deleted before this object."""
|
|
self._inlets.append(inlet)
|
|
if self._type == 2 and other._type == 1:
|
|
self._reservoirs.append(other)
|
|
|
|
def _addOutlet(self, outlet, other):
|
|
"""For internal use. Store a reference to *outlet*
|
|
so that it will not be deleted before this object."""
|
|
self._outlets.append(outlet)
|
|
if self._type == 2 and other._type == 1:
|
|
self._reservoirs.append(other)
|
|
|
|
def _addWall(self, wall, other):
|
|
"""For internal use. Store a reference to *wall*
|
|
so that it will not be deleted before this object."""
|
|
self._walls.append(wall)
|
|
if self._type == 2 and other._type == 1:
|
|
self._reservoirs.append(other)
|
|
|
|
def syncContents(self):
|
|
"""Set the state of the object representing the reactor contents
|
|
to the current reactor state.
|
|
|
|
>>> r = Reactor(gas)
|
|
>>> (statements that change the state of object 'gas')
|
|
>>> r.syncContents()
|
|
|
|
After this statement, the state of object 'gas' is synchronized
|
|
with the reactor state.
|
|
See :meth:`.contents`.
|
|
"""
|
|
self._contents.setState_TRY(self.temperature(),
|
|
self.density(),
|
|
self.massFractions())
|
|
|
|
def contents(self):
|
|
"""Return an object representing the reactor contents, after first
|
|
synchronizing its state with the current reactor state. This method
|
|
is useful when some property of the fluid in the reactor is
|
|
needed that is not provided by a method of :class:`.Reactor`.
|
|
|
|
>>> r = Reactor(gas)
|
|
>>> (statements that change the state of object 'gas')
|
|
>>> c = r.contents()
|
|
>>> print c.gibbs_mole(), c.chemPotentials()
|
|
|
|
Note that after calling :meth:`.contents`, object *c*
|
|
references the same underlying kernel object as object *gas*
|
|
does. Therefore, all properties of *c* and *gas* are
|
|
identical. (Remember that Python objects are really C
|
|
pointers; at the C level, both point to the same data
|
|
structure.)
|
|
It is also allowed to write
|
|
|
|
>>> gas = r.contents()
|
|
"""
|
|
self.syncContents()
|
|
return self._contents
|
|
|
|
|
|
def nSensParams(self):
|
|
"""Number of sensitivity parameters for this reactor."""
|
|
return _cantera.reactor_nSensParams(self.__reactor_id)
|
|
|
|
def addSensitivityReaction(self, reactions = []):
|
|
if len(reactions) == 0:
|
|
nr = self._contents.nReactions()
|
|
for n in range(nr):
|
|
self._paramid.append(self._contents.reactionEqn(n))
|
|
_cantera.reactor_addSensitivityReaction(self.__reactor_id,
|
|
n)
|
|
else:
|
|
for n in reactions:
|
|
self._paramid.append(self._contents.reactionEqn(n))
|
|
_cantera.reactor_addSensitivityReaction(self.__reactor_id,
|
|
n)
|
|
|
|
def sensParamName(self, n = -1):
|
|
if n < 0:
|
|
return self._paramid
|
|
else:
|
|
return self._paramid[n]
|
|
|
|
_reactorcount = 0
|
|
_reservoircount = 0
|
|
|
|
class Reactor(ReactorBase):
|
|
"""
|
|
Zero-dimensional reactors. Instances of class Reactor represent
|
|
zero-dimensional reactors. By default, they are closed (no inlets or
|
|
outlets), have fixed volume, and have adiabatic, chemically-inert walls.
|
|
These properties may all be changed by adding appropriate components.
|
|
See :class:`.Wall`, :class:`.MassFlowController`, and :class:`.Valve`.
|
|
"""
|
|
def __init__(self, contents = None, name = '',
|
|
volume = 1.0, energy = 'on',
|
|
verbose = 0):
|
|
"""
|
|
:param contents:
|
|
Reactor contents. If not specified, the reactor is initially empty.
|
|
In this case, call :meth:`.insert` to specify the contents.
|
|
:param name:
|
|
Used only to identify this reactor in output. If not specified,
|
|
defaults to ``'Reactor_n'``, where *n* is an integer assigned in
|
|
the order :class:`.Reactor` objects are created.
|
|
:param volume:
|
|
Initial reactor volume. Defaults to 1 m^3.
|
|
:param energy:
|
|
Set to ``'on'`` or ``'off'``. If set to ``'off'``, the energy
|
|
equation is not solved, and the temperature is held at its
|
|
initial value. The default in ``'on'``.
|
|
:param verbose:
|
|
If set to a non-zero value, additional diagnostic information
|
|
will be printed.
|
|
|
|
Some examples showing how to create :class:`Reactor` objects are
|
|
shown below.
|
|
|
|
>>> gas = GRI30()
|
|
>>> r1 = Reactor(gas)
|
|
|
|
This is equivalent to:
|
|
|
|
>>> r1 = Reactor()
|
|
>>> r1.insert(gas)
|
|
|
|
Arguments may be specified using keywords in any order:
|
|
|
|
>>> r2 = Reactor(contents = gas, energy = 'off',
|
|
... name = 'isothermal_reactor')
|
|
>>> r3 = Reactor(contents = gas, name = 'adiabatic_reactor')
|
|
|
|
Here's an array of reactors:
|
|
|
|
>>> reactor_array = [Reactor(), Reactor(gas), Reactor(Air())]
|
|
"""
|
|
global _reactorcount
|
|
if name == '':
|
|
name = 'Reactor_'+`_reactorcount`
|
|
_reactorcount += 1
|
|
ReactorBase.__init__(self, contents = contents, name = name,
|
|
volume = volume, energy = energy,
|
|
verbose = verbose, type = 2)
|
|
|
|
|
|
class FlowReactor(ReactorBase):
|
|
def __init__(self, contents = None, name = '',
|
|
volume = 1.0, energy = 'on',
|
|
mdot = -1.0,
|
|
verbose = 0):
|
|
"""
|
|
:param contents:
|
|
Reactor contents. If not specified, the reactor is initially empty.
|
|
In this case, call :meth:`.insert` to specify the contents.
|
|
:param name:
|
|
Used only to identify this reactor in output. If not specified,
|
|
defaults to ``Reactor_n``, where n is an integer assigned in the
|
|
order Reactor objects are created.
|
|
:param volume:
|
|
Initial reactor volume. Defaults to 1 m^3.
|
|
:param energy:
|
|
Set to ``'on'`` or ``'off'``. If set to ``'off'``, the energy
|
|
equation is not solved, and the temperature is held at its
|
|
initial value. The default in ``'on'``.
|
|
:param verbose:
|
|
if set to a non-zero value, additional diagnostic information
|
|
will be printed.
|
|
"""
|
|
global _reactorcount
|
|
if name == '':
|
|
name = 'FlowReactor_'+`_reactorcount`
|
|
_reactorcount += 1
|
|
ReactorBase.__init__(self, contents = contents, name = name,
|
|
volume = volume, energy = energy,
|
|
verbose = verbose, type = 3)
|
|
if mdot > 0.0:
|
|
self.setMassFlowRate(mdot)
|
|
|
|
def setMassFlowRate(self, mdot):
|
|
_cantera.flowReactor_setMassFlowRate(self.reactor_id(), mdot)
|
|
|
|
|
|
class ConstPressureReactor(ReactorBase):
|
|
def __init__(self, contents = None, name = '',
|
|
volume = 1.0, energy = 'on',
|
|
verbose = 0):
|
|
"""
|
|
:param contents:
|
|
Reactor contents. If not specified, the reactor is
|
|
initially empty. In this case, call :meth:`.insert` to specify
|
|
the contents.
|
|
:param name:
|
|
Used only to identify this reactor in output. If not specified,
|
|
defaults to ``'Reactor_n'``, where n is an integer assigned in the
|
|
order :class:`.Reactor` objects are created.
|
|
:param volume:
|
|
Initial reactor volume. Defaults to 1 m^3.
|
|
:param energy:
|
|
Set to ``'on'`` or ``'off'``. If set to ``'off'``, the energy
|
|
equation is not solved, and the temperature is held at its
|
|
initial value. The default in ``'on'``.
|
|
:param verbose:
|
|
If set to a non-zero value, additional diagnostic
|
|
information will be printed.
|
|
"""
|
|
global _reactorcount
|
|
if name == '':
|
|
name = 'ConstPressureReactor_'+`_reactorcount`
|
|
_reactorcount += 1
|
|
ReactorBase.__init__(self, contents = contents, name = name,
|
|
volume = volume, energy = energy,
|
|
verbose = verbose, type = 4)
|
|
|
|
|
|
class Reservoir(ReactorBase):
|
|
"""
|
|
A reservoir is a reactor with a constant state. The temperature,
|
|
pressure, and chemical composition in a reservoir never change from
|
|
their initial values.
|
|
"""
|
|
def __init__(self, contents = None, name = '', verbose = 0):
|
|
"""
|
|
:param contents:
|
|
Reservoir contents. If not specified, the reservoir is initially
|
|
empty. In this case, call :meth:`.insert` to specify the contents.
|
|
:param name:
|
|
Used only to identify this reservoir in output. If not specified,
|
|
defaults to ``'Reservoir_n'``, where n is an integer assigned in
|
|
the order Reservoir objects are created.
|
|
:param verbose:
|
|
if set to a non-zero value, additional diagnostic information will
|
|
be printed.
|
|
|
|
Some examples showing how to create Reservoir objects are shown below.
|
|
|
|
>>> gas = GRI30()
|
|
>>> res1 = Reservoir(gas)
|
|
|
|
This is equivalent to:
|
|
|
|
>>> res1 = Reactor()
|
|
>>> res1.insert(gas)
|
|
|
|
Arguments may be specified using keywords in any order:
|
|
|
|
>>> res2 = Reservoir(contents=Air(), name='environment')
|
|
>>> res3 = Reservoir(contents=gas, name='upstream_state')
|
|
"""
|
|
global _reservoircount
|
|
if name == '':
|
|
name = 'Reservoir_'+`_reservoircount`
|
|
_reservoircount += 1
|
|
ReactorBase.__init__(self, contents = contents,
|
|
name = name, verbose = verbose, type = 1)
|
|
|
|
def advance(self, time):
|
|
"""Deprecated. Do nothing."""
|
|
pass
|
|
|
|
|
|
#------------------ FlowDevice ---------------------------------
|
|
|
|
class FlowDevice:
|
|
"""
|
|
Base class for devices that regulate the flow rate in a fluid line.
|
|
"""
|
|
def __init__(self, type, name, verbose):
|
|
"""
|
|
Create a new instance of type *type*
|
|
"""
|
|
self._name = name
|
|
self._verbose = verbose
|
|
self.__fdev_id = _cantera.flowdev_new(type)
|
|
|
|
def __del__(self):
|
|
"""
|
|
Delete the instance.
|
|
"""
|
|
if self._verbose:
|
|
print 'deleting '+self._name
|
|
_cantera.flowdev_del(self.__fdev_id)
|
|
|
|
def name(self):
|
|
"""The name specified when initially constructed."""
|
|
return self._name
|
|
|
|
def ready(self):
|
|
"""
|
|
Deprecated. Returns true if the device is ready to use.
|
|
"""
|
|
return _cantera.flowdev_ready(self.__fdev_id)
|
|
|
|
def massFlowRate(self, time = -999.0):
|
|
"""Mass flow rate (kg/s). """
|
|
return _cantera.flowdev_massFlowRate(self.__fdev_id, time)
|
|
|
|
def install(self, upstream, downstream):
|
|
"""
|
|
Install the device between the upstream and downstream
|
|
reactors or reservoirs.
|
|
|
|
>>> f.install(upstream=reactor1, downstream=reservoir2)
|
|
"""
|
|
if self._verbose:
|
|
print
|
|
print self._name+': installing between '+upstream.name()+' and '+downstream.name()
|
|
upstream._addOutlet(self, downstream)
|
|
downstream._addInlet(self, upstream)
|
|
_cantera.flowdev_install(self.__fdev_id, upstream.reactor_id(),
|
|
downstream.reactor_id())
|
|
def _setParameters(self, c):
|
|
params = array(c,'d')
|
|
n = len(params)
|
|
return _cantera.flowdev_setParameters(self.__fdev_id, n, params)
|
|
|
|
def setFunction(self, f):
|
|
self._f = f # Hold on to a reference so it doesn't get deleted
|
|
_cantera.flowdev_setFunction(self.__fdev_id, f.func_id())
|
|
|
|
def flowdev_id(self):
|
|
return self.__fdev_id
|
|
|
|
_mfccount = 0
|
|
|
|
class MassFlowController(FlowDevice):
|
|
r"""
|
|
Mass flow controllers. A mass flow controller maintains a specified mass
|
|
flow rate independent of upstream and downstream conditions. The equation
|
|
used to compute the mass flow rate is
|
|
|
|
.. math::
|
|
|
|
\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.
|
|
|
|
Unlike a real mass flow controller, a MassFlowController object will
|
|
maintain the flow even if the downstream pressure is greater than the
|
|
upstream pressure. This allows simple implementation of loops, in which
|
|
exhaust gas from a reactor is fed back into it through an inlet. But note
|
|
that this capability should be used with caution, since no account is
|
|
taken of the work required to do this.
|
|
|
|
A mass flow controller is assumed to be adiabatic, non-reactive, and have
|
|
negligible volume, so that it is internally always in steady-state even if
|
|
the upstream and downstream reactors are not. The fluid enthalpy, chemical
|
|
composition, and mass flow rate are constant across a mass flow controller,
|
|
and the pressure difference equals the difference in pressure between the
|
|
upstream and downstream reactors.
|
|
|
|
Examples:
|
|
|
|
>>> mfc1 = MassFlowController(upstream=res1, downstream=reactr,
|
|
... name='fuel_mfc', mdot = 0.1)
|
|
>>> air_mdot = Gaussian(A=0.1, t0=2.0, FWHM=0.1)
|
|
>>> mfc2 = MassFlowController(upstream=res2, downstream=reactr,
|
|
... name='air_mfc', mdot=air_mdot)
|
|
"""
|
|
def __init__(self, upstream=None,
|
|
downstream=None,
|
|
name='',
|
|
verbose=0, mdot = 0.0):
|
|
"""
|
|
:param upstream:
|
|
upstream reactor or reservoir.
|
|
:param downstream:
|
|
downstream reactor or reservoir.
|
|
:param name:
|
|
name used to identify the mass flow controller in output. If no
|
|
name is specified, it defaults to ``MFC_n``, where n is an integer
|
|
assigned in the order the MassFlowController object was created.
|
|
:param mdot:
|
|
Mass flow rate [kg/s]. This mass flow rate, which may be a constant
|
|
or a function of time, will be maintained, independent of upstream
|
|
and downstream conditions, unless reset by calling method
|
|
:meth:`.set`.
|
|
:param verbose:
|
|
if set to a positive integer, additional diagnostic information
|
|
will be printed.
|
|
"""
|
|
global _mfccount
|
|
if name == '':
|
|
name = 'MFC_'+`_mfccount`
|
|
_mfccount += 1
|
|
FlowDevice.__init__(self,1,name,verbose)
|
|
if upstream and downstream:
|
|
self.install(upstream, downstream)
|
|
if mdot:
|
|
self.set(mdot = mdot)
|
|
|
|
def _setMassFlowRate(self, mdot):
|
|
"""Set or reset the mass flow rate to 'mdot' [kg/s].
|
|
"""
|
|
if self._verbose:
|
|
print self._name+': setting mdot to '+`mdot`+' kg/s'
|
|
if type(mdot) == types.InstanceType:
|
|
self.setFunction(mdot)
|
|
else:
|
|
_cantera.flowdev_setMassFlowRate(self.flowdev_id(), mdot)
|
|
|
|
|
|
def set(self, mdot = 0.0):
|
|
"""Set the mass flow rate [kg/s]. May be called at any time to
|
|
change the mass flow rate to a new value, or to a new function
|
|
of time.
|
|
|
|
>>> mfc.set(mdot = 0.2)
|
|
"""
|
|
self._setMassFlowRate(mdot)
|
|
|
|
|
|
_valvecount = 0
|
|
|
|
class Valve(FlowDevice):
|
|
r"""Valves. In Cantera, a Valve object is a flow devices with mass
|
|
flow rate that is a function of the pressure drop across it. The default behavior
|
|
is linear:
|
|
|
|
.. math:: \dot m = K_v (P_1 - P_2)
|
|
|
|
if :math:`P_1 > P_2.` Otherwise, :math:`\dot m = 0`.
|
|
However, an arbitrary function can also be specified, such that
|
|
|
|
.. math:: \dot m = F(P_1 - P_2)
|
|
|
|
if :math:`P_1 > P_2`, or :math:`\dot m = 0` otherwise.
|
|
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.
|
|
|
|
:class:`Valve` objects are often used between an upstream reactor and a
|
|
downstream reactor or reservoir to maintain them both at nearly the same
|
|
pressure. By setting the constant :math:`K_v` to a sufficiently large
|
|
value, very small pressure differences will result in flow between the
|
|
reactors that counteracts the pressure difference.
|
|
|
|
A Valve is assumed to be adiabatic, non-reactive, and have negligible
|
|
internal volume, so that it is internally always in steady-state even if
|
|
the upstream and downstream reactors are not. The fluid enthalpy, chemical
|
|
composition, and mass flow rate are constant across a Valve, and the
|
|
pressure difference equals the difference in pressure between the upstream
|
|
and downstream reactors.
|
|
|
|
"""
|
|
def __init__(self, upstream=None, downstream=None,
|
|
name='', Kv = 0.0, mdot0 = 0.0, verbose=0):
|
|
"""
|
|
:param upstream:
|
|
upstream reactor or reservoir.
|
|
:param downstream:
|
|
downstream reactor or reservoir.
|
|
:param name:
|
|
name used to identify the valve in output. If no name is specified,
|
|
it defaults to ``Valve_n``, where n is an integer assigned in the
|
|
order the Valve object was created.
|
|
:param Kv:
|
|
the constant in the mass flow rate equation.
|
|
:param verbose:
|
|
if set to a positive integer, additional diagnostic information
|
|
will be printed.
|
|
"""
|
|
global _valvecount
|
|
if name == '':
|
|
name = 'Valve_'+`_valvecount`
|
|
_valvecount += 1
|
|
FlowDevice.__init__(self,3,name,verbose)
|
|
if upstream and downstream:
|
|
self.install(upstream, downstream)
|
|
self.setValveCoeff(Kv)
|
|
|
|
|
|
def setValveCoeff(self, Kv = -1.0):
|
|
"""Set or reset the valve coefficient :math:`K_v`."""
|
|
vv = zeros(1,'d')
|
|
vv[0] = Kv
|
|
if self._verbose:
|
|
print
|
|
print self._name+': setting valve coefficient to '+`Kv`+' kg/Pa-s'
|
|
self._setParameters(vv)
|
|
|
|
def _setValveCharacteristic(self, f):
|
|
"""Set or reset the valve characteristics.
|
|
"""
|
|
if type(f) == types.InstanceType:
|
|
self.setFunction(f)
|
|
else:
|
|
raise CanteraError("Wrong type for valve characteristic function.")
|
|
|
|
def set(self, Kv = -1.0, F = None):
|
|
r"""Set or reset valve properties. All keywords are optional.
|
|
|
|
:param Kv:
|
|
constant in linear mass flow rate equation.
|
|
:param F:
|
|
function of :math:`\Delta P`.
|
|
"""
|
|
if F:
|
|
self.setFunction(F)
|
|
if Kv > 0.0:
|
|
self.setValveCoeff(Kv)
|
|
|
|
|
|
|
|
_pccount = 0
|
|
|
|
class PressureController(FlowDevice):
|
|
r"""
|
|
A PressureController is designed to be used in conjunction with another
|
|
'master' flow controller, typically a :class:`.MassFlowController`. The
|
|
master flow controller is installed on the inlet of the reactor, and the
|
|
corresponding :class:`.PressureController` is installed on on outlet of the
|
|
reactor. The :class:`.PressureController` mass flow rate is equal to the
|
|
master mass flow rate, plus a small correction dependent on the pressure
|
|
difference:
|
|
|
|
.. math:: \dot m = \dot m_{\rm master} + K_v(P_1 - P_2).
|
|
"""
|
|
|
|
def __init__(self, upstream=None, downstream=None,
|
|
name='', master = None, Kv = 0.0, verbose=0):
|
|
"""
|
|
:param upstream:
|
|
upstream reactor or reservoir.
|
|
:param downstream:
|
|
downstream reactor or reservoir.
|
|
:param name:
|
|
name used to identify the pressure controller in output. If no
|
|
name is specified, it defaults to ``PressureController_n``, where
|
|
n is an integer assigned in the order the PressureController
|
|
object was created.
|
|
:param Kv:
|
|
the constant in the mass flow rate equation.
|
|
:param verbose:
|
|
if set to a positive integer, additional diagnostic information
|
|
will be printed.
|
|
"""
|
|
global _pccount
|
|
if name == '':
|
|
name = 'PressureController_'+`_pccount`
|
|
_pccount += 1
|
|
FlowDevice.__init__(self,2,name,verbose)
|
|
if upstream and downstream:
|
|
self.install(upstream, downstream)
|
|
self.setPressureCoeff(Kv)
|
|
self.setMaster(master)
|
|
|
|
|
|
def setPressureCoeff(self, Kv):
|
|
"""Set or reset the pressure coefficient :math:`K_v`."""
|
|
vv = zeros(1,'d')
|
|
vv[0] = Kv
|
|
if self._verbose:
|
|
print
|
|
print self._name+': setting pressure coefficient to '+`Kv`+' kg/Pa-s'
|
|
self._setParameters(vv)
|
|
|
|
def setMaster(self, master):
|
|
"""Set the master flow controller."""
|
|
_cantera.flowdev_setMaster(self.flowdev_id(),
|
|
master.flowdev_id())
|
|
|
|
def set(self, Kv = -1.0, master = None):
|
|
if master:
|
|
self.setMaster(master)
|
|
if Kv > 0.0:
|
|
self.setPressureCoeff(Kv)
|
|
|
|
|
|
|
|
#------------- Wall ---------------------------
|
|
|
|
_wallcount = 0
|
|
|
|
class Wall:
|
|
r"""
|
|
Reactor walls.
|
|
|
|
A Wall separates two reactors, or a reactor and a reservoir. A wall has a
|
|
finite area, may conduct or radiate heat between the two reactors on either
|
|
side, and may move like a piston.
|
|
|
|
Walls are stateless objects in Cantera, meaning that no differential
|
|
equation is integrated to determine any wall property. Since it is the wall
|
|
(piston) velocity that enters the energy equation, this means that it is
|
|
the velocity, not the acceleration or displacement, that is specified.
|
|
The wall velocity is computed from
|
|
|
|
.. math:: v = K(P_{\rm left} - P_{\rm right}) + v_0(t),
|
|
|
|
where :math:`K` is a non-negative constant, and :math:`v_0(t)` is a
|
|
specified function of time. The velocity is positive if the wall is
|
|
moving to the right.
|
|
|
|
The heat flux through the wall is computed from
|
|
|
|
.. math:: q = U(T_{\rm left} - T_{\rm right}) + \epsilon\sigma (T_{\rm left}^4 - T_{\rm right}^4) + q_0(t),
|
|
|
|
where :math:`U` is the overall heat transfer coefficient for
|
|
conduction/convection, and :math:`\epsilon` is the emissivity. The function
|
|
:math:`q_0(t)` is a specified function of time. The heat flux is positive
|
|
when heat flows from the reactor on the left to the reactor on the right.
|
|
|
|
A heterogeneous reaction mechanism may be specified for one or both of the
|
|
wall surfaces. The mechanism object (typically an instance of class
|
|
:class:`.Interface`) must be constructed so that it is properly linked to
|
|
the object representing the fluid in the reactor the surface in question
|
|
faces. The surface temperature on each side is taken to be equal to the
|
|
temperature of the reactor it faces.
|
|
|
|
"""
|
|
def __init__(self, left, right, name = '',
|
|
A = 1.0, K = 0.0, U = 0.0,
|
|
Q = None, velocity = None,
|
|
kinetics = [None, None]):
|
|
"""
|
|
:param left:
|
|
Reactor or reservoir on the left. Required.
|
|
:param right:
|
|
Reactor or reservoir on the right. Required.
|
|
:param name:
|
|
Name string. If omitted, the name is ``'Wall_n'``, where ``'n'``
|
|
is an integer assigned in the order walls are created.
|
|
:param A:
|
|
Wall area [m^2]. Defaults to 1.0 m^2.
|
|
:param K:
|
|
Wall expansion rate parameter [m/s/Pa]. Defaults to 0.0.
|
|
:param U:
|
|
Overall heat transfer coefficient [W/m^2]. Defaults to 0.0
|
|
(adiabatic wall).
|
|
:param Q:
|
|
Heat flux function :math:`q_0(t)` [W/m^2]. Optional. Default:
|
|
:math:`q_0(t) = 0.0`.
|
|
:param velocity:
|
|
Wall velocity function :math:`v_0(t)` [m/s].
|
|
Default: :math:`v_0(t) = 0.0`.
|
|
:param kinetics:
|
|
Surface reaction mechanisms for the left-facing and right-facing
|
|
surface, respectively. These must be instances of class Kinetics,
|
|
or of a class derived from Kinetics, such as Interface. If
|
|
chemistry occurs on only one side, enter ``None`` for the
|
|
non-reactive side.
|
|
"""
|
|
typ = 0
|
|
self.__wall_id = _cantera.wall_new(typ)
|
|
|
|
global _wallcount
|
|
if name == '':
|
|
_nm = 'Wall_'+`_wallcount`
|
|
else:
|
|
_nm = name
|
|
_wallcount += 1
|
|
|
|
if left and right:
|
|
self.install(left, right)
|
|
else:
|
|
raise CanteraError('both left and right reactors must be specified.')
|
|
self.setArea(A)
|
|
self.setExpansionRateCoeff(K)
|
|
self.setVelocity(velocity)
|
|
self.setHeatTransferCoeff(U)
|
|
self.setHeatFlux(Q)
|
|
|
|
self.setKinetics(kinetics[0],kinetics[1])
|
|
|
|
self._paramid = []
|
|
|
|
def __del__(self):
|
|
""" Delete the Wall instance. This method is called
|
|
automatically when no Python object stores a reference to this
|
|
Wall. Since reactors and reservoirs store references to all
|
|
Walls installed on them, this method will only be called after
|
|
the reactors/reservoirs have been deleted. """
|
|
|
|
_cantera.wall_del(self.__wall_id)
|
|
|
|
def ready(self):
|
|
"""
|
|
Return 1 if the wall instance is ready for use, 0 otherwise. Deprecated.
|
|
"""
|
|
return _cantera.wall_ready(self.__wall_id)
|
|
|
|
def area(self):
|
|
"""
|
|
The wall area (m^2).
|
|
"""
|
|
return _cantera.wall_area(self.__wall_id)
|
|
|
|
def setArea(self, a):
|
|
"""
|
|
Set the area (m^2). The wall area may be changed manually at any time
|
|
during a simulation.
|
|
"""
|
|
_cantera.wall_setArea(self.__wall_id, a)
|
|
|
|
def setThermalResistance(self, rth):
|
|
"""Deprecated."""
|
|
return _cantera.wall_setThermalResistance(self.__wall_id, rth)
|
|
|
|
def setHeatTransferCoeff(self, u):
|
|
"""
|
|
Set the overall heat transfer coefficient [W/m^2/K]
|
|
"""
|
|
return _cantera.wall_setHeatTransferCoeff(self.__wall_id, u)
|
|
|
|
def setEmissivity(self, epsilon):
|
|
"""
|
|
Set the emissivity.
|
|
"""
|
|
_cantera.wall_setEmissivity(self.__wall_id, epsilon)
|
|
|
|
|
|
def setHeatFlux(self, qfunc):
|
|
"""
|
|
Specify the time-dependent heat flux function [W/m2].
|
|
*qfunc* must be a functor (an instance of :class:`.Func1`).
|
|
"""
|
|
if qfunc:
|
|
self._qfunc = qfunc # hold on to a reference so it doesn't get deleted
|
|
n = qfunc.func_id()
|
|
else:
|
|
n = 0
|
|
return _cantera.wall_setHeatFlux(self.__wall_id, n)
|
|
|
|
def setExpansionRateCoeff(self, k):
|
|
"""Set the coefficient K that determines the expansion rate
|
|
resulting from a unit pressure drop."""
|
|
_cantera.wall_setExpansionRateCoeff(self.__wall_id, k)
|
|
|
|
def setVelocity(self, vfunc):
|
|
"""
|
|
Specify the velocity function [m/s]. *vfunc* must
|
|
be a functor (an instance of :class:`.Func1`)
|
|
"""
|
|
if vfunc:
|
|
self._vfunc = vfunc # hold on to a reference so it doesn't get deleted
|
|
n = vfunc.func_id()
|
|
else:
|
|
n = 0
|
|
_cantera.wall_setVelocity(self.__wall_id, n)
|
|
|
|
def vdot(self):
|
|
"""Rate of volume change [m^3]. A positive value corresponds
|
|
to the left-hand reactor volume increasing, and the right-hand
|
|
reactor volume decreasing."""
|
|
return _cantera.wall_vdot(self.__wall_id)
|
|
|
|
def velocity(self):
|
|
return self.vdot()/self.area()
|
|
|
|
def heatFlowRate(self):
|
|
"""Rate of heat flow through the wall. A positive value
|
|
corresponds to heat flowing from the left-hand reactor to the
|
|
right-hand one."""
|
|
return _cantera.wall_Q(self.__wall_id)
|
|
|
|
def heatFlux(self):
|
|
return self.heatFlowRate()/self.area()
|
|
|
|
def install(self, left, right):
|
|
left._addWall(self, right)
|
|
right._addWall(self, left)
|
|
_cantera.wall_install(self.__wall_id, left.reactor_id(),
|
|
right.reactor_id())
|
|
|
|
def setKinetics(self, left, right):
|
|
"""Specify surface reaction mechanisms for the left and right sides of the wall."""
|
|
ileft = 0
|
|
iright = 0
|
|
if left:
|
|
ileft = left.kinetics_hndl()
|
|
if right:
|
|
iright = right.kinetics_hndl()
|
|
self._leftkin = left
|
|
self._rightkin = right
|
|
_cantera.wall_setkinetics(self.__wall_id, ileft, iright)
|
|
|
|
def kinetics(self, side = 'left'):
|
|
if side == 'left':
|
|
return self._leftkin
|
|
elif side == 'right':
|
|
return self._rightkin
|
|
else:
|
|
raise CanteraError("side must be 'left' or 'right'")
|
|
|
|
def set(self, **p):
|
|
"""Set various wall parameters: *A*, *U*, *K*, *Q*, *velocity*.
|
|
These have the same meanings as in the constructor.
|
|
"""
|
|
for item in p.keys():
|
|
if item == 'A' or item == 'area':
|
|
self.setArea(p[item])
|
|
elif item == 'R':
|
|
self.setThermalResistance(p[item])
|
|
elif item == 'U':
|
|
self.setHeatTransferCoeff(p[item])
|
|
elif item == 'K':
|
|
self.setExpansionRateCoeff(p[item])
|
|
elif item == 'Q':
|
|
self.setHeatFlux(p[item])
|
|
elif item == 'velocity':
|
|
self.setVelocity(p[item])
|
|
else:
|
|
raise 'unknown parameter: ',item
|
|
|
|
|
|
def addSensitivityReaction(self, side = 'unknown', reactions = []):
|
|
k = self.kinetics(side)
|
|
if len(reactions) == 0:
|
|
nr = k.nReactions()
|
|
for n in range(nr):
|
|
self._paramid.append(k.reactionEqn(n))
|
|
_cantera.wall_addSensitivityReaction(self.__wall_id,
|
|
_ilr[side], n)
|
|
else:
|
|
for n in reactions:
|
|
self._paramid.append(k.reactionEqn(n))
|
|
_cantera.wall_addSensitivityReaction(self.__wall_id,
|
|
_ilr[side], n)
|
|
|
|
|
|
class ReactorNet:
|
|
"""Networks of reactors. ReactorNet objects are used to
|
|
simultaneously advance the state of a set of coupled reactors.
|
|
|
|
Example:
|
|
|
|
>>> r1 = Reactor(gas1)
|
|
>>> r2 = Reactor(gas2)
|
|
>>> <... install walls, inlets, outlets, etc...>
|
|
|
|
>>> reactor_network = ReactorNet([r1, r2])
|
|
>>> reactor_network.advance(time)
|
|
"""
|
|
def __init__(self, reactorlist = None):
|
|
"""
|
|
Create a new ReactorNet instance. If a list of reactors is supplied,
|
|
these will be added to the network.
|
|
"""
|
|
self._reactors = []
|
|
self.__reactornet_id = _cantera.reactornet_new()
|
|
if reactorlist:
|
|
for r in reactorlist:
|
|
self.add(r)
|
|
|
|
|
|
def __del__(self):
|
|
"""Delete the reactor network instance. The reactors in the
|
|
network are not deleted."""
|
|
_cantera.reactornet_del(self.__reactornet_id)
|
|
|
|
|
|
def reactornet_id(self):
|
|
""" The integer index used to access the
|
|
kernel reactornet object. For internal use. """
|
|
return self.__reactornet_id
|
|
|
|
|
|
def add(self, reactor):
|
|
"""
|
|
Add a reactor to the network.
|
|
"""
|
|
self._reactors.append(reactor)
|
|
_cantera.reactornet_addreactor(self.__reactornet_id,
|
|
reactor.reactor_id())
|
|
|
|
|
|
def setInitialTime(self, t0):
|
|
"""Set the initial time. Restarts integration from this time
|
|
using the current state as the initial condition. Default: 0.0 s"""
|
|
_cantera.reactornet_setInitialTime(self.__reactornet_id, t0)
|
|
|
|
def time(self):
|
|
"""The current time [s]."""
|
|
return _cantera.reactornet_time(self.__reactornet_id)
|
|
|
|
def setTolerances(self, rtol = 1.0e-9,
|
|
atol = 1.0e-20, rtolsens= -1.0, atolsens = -1.0):
|
|
"""Set the relative and absolute error tolerances used in
|
|
integrating the reactor equations."""
|
|
_cantera.reactornet_setTolerances(self.__reactornet_id, rtol, atol)
|
|
_cantera.reactornet_setSensitivityTolerances(self.__reactornet_id, rtolsens, atolsens)
|
|
|
|
def advance(self, time):
|
|
"""Advance the state of the reactor network in time from the current
|
|
time to time 'time'."""
|
|
return _cantera.reactornet_advance(self.__reactornet_id, time)
|
|
|
|
def step(self, time):
|
|
"""Take a single internal time step toward time *time*.
|
|
The time after taking the step is returned."""
|
|
return _cantera.reactornet_step(self.__reactornet_id, time)
|
|
|
|
def reactors(self):
|
|
"""Return the list of reactors in the network."""
|
|
return self._reactors
|
|
|
|
def nSensParams(self):
|
|
"""Number of sensitivity parameters."""
|
|
sum = 0
|
|
for r in self._reactors:
|
|
sum += r.nSensParams()
|
|
return sum
|
|
|
|
def sensitivity(self, component = '', parameter = -1, reactor = ''):
|
|
|
|
"""Sensitivity of solution component *component* with respect
|
|
to one or more parameters.
|
|
|
|
:param component:
|
|
name of the species or other variable for which sensitivity
|
|
information is desired.
|
|
:param parameter:
|
|
single integer or sequence of integers specifying the parameters.
|
|
The parameters are numbered from zero, beginning with the parameters
|
|
for the first reactor and continuing through those for the last
|
|
reactor in the network. If omitted, the sensitivity with respect
|
|
to all parameters will be returned.
|
|
:param reactor:
|
|
reactor containing the desired component.
|
|
"""
|
|
|
|
n = 0
|
|
if reactor <> '':
|
|
for reac in self._reactors:
|
|
if reac.name() == reactor:
|
|
break
|
|
else:
|
|
n = n+1
|
|
np = self.nSensParams()
|
|
if parameter >= 0 and parameter < np:
|
|
return _cantera.reactornet_sensitivity(self.__reactornet_id,
|
|
component, parameter, n)
|
|
elif parameter == -1:
|
|
s = []
|
|
for m in range(np):
|
|
s.append(_cantera.reactornet_sensitivity(self.__reactornet_id,
|
|
component, m, n))
|
|
return s
|
|
else:
|
|
raise CanteraError("sensitivity requested for illegal parameter number:"+`parameter`)
|