Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sampling Reactors #1331

Merged
merged 14 commits into from
May 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions documentation/source/users/rmg/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,70 @@ sensitivities for dln(C_i)/dln(k_j) > sensitivityThreshold or dlnC_i/d(G_j) > s
Note that in the RMG job, after the model has been generated to completion, sensitivity analysis will be conducted
in one final simulation (sensitivity is not performed in intermediate iterations of the job).

Advanced Setting: Range Based Reactors
-------------------------------------------------

Under this setting rather than using reactors at fixed points, reaction conditions are sampled from a range of conditions.
Conditions are chosen using a weighted stochastic grid sampling algorithm. An implemented objective function measures how
desirable it is to sample from a point condition (T, P, concentrations) based on prior run conditions (weighted by how
recent they were and how many objects they returned). Each iteration this objective function is evaluated at a grid of
points spaning the reactor range (the grid has 20^N points where N is the number of dimensions). The grid values are then normalized to one and a grid point is chosen with probability
equal to its normalized objective function value. Then a random step of maximum length sqrt(2)/2 times the distance between grid
points is taken from that grid point to give the chosen condition point. The random numbers are seeded so that this does
not make the algorithm non-deterministic.

.. figure:: images/RangedReactorDiagram.png
:width: 300px
:align: center
:height: 300px

These variable condition reactors terminate after a defined number of
consecutive successfully terminating iterations (``nSimsTerm``). Use of these reactors tends to improve treatment of reaction
conditions that otherwise would be between reactors and reduce the number of simulations needed by focusing on reaction
conditions at which the model terminates earlier. An example with sensitivity analysis at a specified reaction condition
is available below::

simpleReactor(
temperature=[(1000,'K'),(1500,'K')],
pressure=[(1.0,'bar'),(10.0,'bar')],
nSimsTerm=12,
initialMoleFractions={
"ethane": [0.05,0.15],
"O2": 0.1,
"N2": 0.9,
},
terminationConversion={
'ethane': 0.1,
},
terminationTime=(1e1,'s'),
sensitivityTemperature = (1000,'K'),
sensitivityPressure = (10.0,'bar'),
sensitivityMoleFractions = {"ethane":0.1,"O2":0.9},
sensitivity=["ethane","O2"],
sensitivityThreshold=0.001,
balanceSpecies = "N2",
)

Note that increasing ``nSimsTerm`` improves convergence over the entire range, but convergence is only guaranteed at the
last set of ``nSimsTerm`` reaction conditions. Theoretically if ``nSimsTerm`` is set high enough the RMG model converges over the
entire interval. Except at very small values for ``nSimsTerm`` the convergence achieved is usually as good or superior to
that achieved using the same number of evenly spaced fixed reactors.

If there is a particular reaction condition you expect to converge more slowly than the rest of the range
there is virually no cost to using a single condition reactor (or a ranged reactor at a smaller range) at that condition
and a ranged reactor with a smaller value for nSimsTerm. This is because the fixed reactor simulations will almost always
be useful and keep the overall RMG job from terminating when the ranged reactor samples the faster converging conditions.

What you should actually set ``nSimsTerm`` to is very system dependent. The value you choose should be at least 2 + N
where N is the number of dimensions the reactor spans (T=>N=1, T and P=>N=2, etc...). There may be benefits to setting it as high
as 2 + 5N. The first should give you convergence over most of the interval that is almost always better than the same
number of fixed reactors. The second should get you reasonably close to convergence over the entire range for N <= 2.

For gas phase reactors if normalization of the ranged mole fractions is undesirable (eg. perhaps a specific species mole
fractions needs to be kept constant) one can use a ``balanceSpecies``. When a ``balanceSpecies`` is used instead of
normalizing the mole fractions the concentration of the defined ``balanceSpecies`` is adjusted to maintain an overall mole
fraction of one. This ensures that all species except the ``balanceSpecies`` have mole fractions within the range specified.

.. _simulatortolerances:

Simulator Tolerances
Expand Down
67 changes: 67 additions & 0 deletions examples/rmg/SR_test/input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Data sources
database(
thermoLibraries = ['primaryThermoLibrary'],
reactionLibraries = [],
seedMechanisms = [],
kineticsDepositories = ['training'],
kineticsFamilies = 'default',
kineticsEstimator = 'rate rules',
)

# List of species
species(
label='ethane',
reactive=True,
structure=SMILES("CC"),
)

species(
label='O2',
reactive=True,
structure=SMILES('[O][O]')
)

species(
label='N2',
reactive=False,
structure=SMILES('N#N'),
)

# Reaction systems
simpleReactor(
temperature=[(1000,'K'),(1500,'K')],
pressure=[(1.0,'bar'),(10.0,'bar')],
nSimsTerm=12,
initialMoleFractions={
"ethane": [0.05,0.15],
"O2": 0.1,
"N2": 0.9,
},
terminationConversion={
'ethane': 0.1,
},
terminationTime=(1e1,'s'),
balanceSpecies = "N2",
)

simulator(
atol=1e-16,
rtol=1e-8,
)

model(
toleranceKeepInEdge=0.0,
toleranceMoveToCore=0.1,
toleranceInterruptSimulation=0.1,
maximumEdgeSpecies=100000,
filterReactions=True,
)

options(
units='si',
saveRestartPeriod=None,
generateOutputHTML=False,
generatePlots=False,
saveEdgeSpecies=False,
saveSimulationProfiles=False,
)
2 changes: 1 addition & 1 deletion rmgpy/reduction/reduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def simulateOne(reactionModel, atol, rtol, reactionSystem):
simulatorSettings = SimulatorSettings(atol,rtol)
modelSettings = ModelSettings(toleranceKeepInEdge=0,toleranceMoveToCore=1,toleranceInterruptSimulation=1)

terminated,resurrected,obj,sspcs,srxns = reactionSystem.simulate(
terminated,resurrected,obj,sspcs,srxns,t,conv = reactionSystem.simulate(
coreSpecies = reactionModel.core.species,
coreReactions = reactionModel.core.reactions,
edgeSpecies = reactionModel.edge.species,
Expand Down
134 changes: 102 additions & 32 deletions rmgpy/rmg/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import quantities
import os
import numpy
from copy import deepcopy

from rmgpy import settings

Expand Down Expand Up @@ -133,36 +134,49 @@ def adjacencyList(string):
def simpleReactor(temperature,
pressure,
initialMoleFractions,
nSimsTerm=6,
terminationConversion=None,
terminationTime=None,
balanceSpecies=None,
sensitivity=None,
sensitivityThreshold=1e-3
sensitivityThreshold=1e-3,
sensitivityTemperature=None,
sensitivityPressure=None,
sensitivityMoleFractions=None,
):
logging.debug('Found SimpleReactor reaction system')

for value in initialMoleFractions.values():
if value < 0:
raise InputError('Initial mole fractions cannot be negative.')
for key,value in initialMoleFractions.iteritems():
if not isinstance(value,list):
initialMoleFractions[key] = float(value)
if value < 0:
raise InputError('Initial mole fractions cannot be negative.')
else:
if len(value) != 2:
raise InputError("Initial mole fraction values must either be a number or a list with 2 entries")
initialMoleFractions[key] = [float(value[0]),float(value[1])]
if value[0] < 0 or value[1] < 0:
raise InputError('Initial mole fractions cannot be negative.')
elif value[1] < value[0]:
raise InputError('Initial mole fraction range out of order: {0}'.format(key))

if not isinstance(temperature,list):
T = Quantity(temperature)
else:
if len(temperature) != 2:
raise InputError('Temperature and pressure ranges can either be in the form of (number,units) or a list with 2 entries of the same format')
T = [Quantity(t) for t in temperature]

if not isinstance(pressure,list):
P = Quantity(pressure)
else:
if len(pressure) != 2:
raise InputError('Temperature and pressure ranges can either be in the form of (number,units) or a list with 2 entries of the same format')
P = [Quantity(p) for p in pressure]


for spec in initialMoleFractions:
initialMoleFractions[spec] = float(initialMoleFractions[spec])

totalInitialMoles = sum(initialMoleFractions.values())
if totalInitialMoles != 1:
logging.warning('Initial mole fractions do not sum to one; normalizing.')
logging.info('')
logging.info('Original composition:')
for spec, molfrac in initialMoleFractions.iteritems():
logging.info("{0} = {1}".format(spec,molfrac))
for spec in initialMoleFractions:
initialMoleFractions[spec] /= totalInitialMoles
logging.info('')
logging.info('Normalized mole fractions:')
for spec, molfrac in initialMoleFractions.iteritems():
logging.info("{0} = {1}".format(spec,molfrac))

T = Quantity(temperature)
P = Quantity(pressure)
if not isinstance(temperature,list) and not isinstance(pressure,list) and all([not isinstance(x,list) for x in initialMoleFractions.values()]):
nSimsTerm=1

termination = []
if terminationConversion is not None:
Expand All @@ -178,26 +192,73 @@ def simpleReactor(temperature,
if isinstance(sensitivity, str): sensitivity = [sensitivity]
for spec in sensitivity:
sensitiveSpecies.append(speciesDict[spec])
system = SimpleReactor(T, P, initialMoleFractions, termination, sensitiveSpecies, sensitivityThreshold)

if not isinstance(T,list):
sensitivityTemperature = T
if not isinstance(P,list):
sensitivityPressure = P
if not any([isinstance(x,list) for x in initialMoleFractions.itervalues()]):
sensitivityMoleFractions = deepcopy(initialMoleFractions)
if sensitivityMoleFractions is None or sensitivityTemperature is None or sensitivityPressure is None:
sensConditions = None
else:
sensConditions = sensitivityMoleFractions
sensConditions['T'] = Quantity(sensitivityTemperature).value_si
sensConditions['P'] = Quantity(sensitivityPressure).value_si


system = SimpleReactor(T, P, initialMoleFractions, nSimsTerm, termination, sensitiveSpecies, sensitivityThreshold,sensConditions)
rmg.reactionSystems.append(system)

assert balanceSpecies is None or isinstance(balanceSpecies,str), 'balanceSpecies should be the string corresponding to a single species'
rmg.balanceSpecies = balanceSpecies
if balanceSpecies: #check that the balanceSpecies can't be taken to zero
total = 0.0
for key,item in initialMoleFractions.iteritems():
if key == balanceSpecies:
assert not isinstance(item,list), 'balanceSpecies must not have a defined range'
xbspcs = item
if isinstance(item,list):
total += item[1]-item[0]

if total > xbspcs:
raise ValueError('The sum of the differences in the ranged mole fractions is greater than the mole fraction of the balance species, this would require the balanceSpecies mole fraction to be negative in some cases which is not allowed, either reduce the maximum mole fractions or dont use balanceSpecies')

# Reaction systems
def liquidReactor(temperature,
initialConcentrations,
terminationConversion=None,
nSimsTerm = 4,
terminationTime=None,
sensitivity=None,
sensitivityThreshold=1e-3,
sensitivityTemperature=None,
sensitivityConcentrations=None,
constantSpecies=None):

logging.debug('Found LiquidReactor reaction system')
T = Quantity(temperature)

if not isinstance(temperature,list):
T = Quantity(temperature)
else:
if len(temperature) != 2:
raise InputError('Temperature and pressure ranges can either be in the form of (number,units) or a list with 2 entries of the same format')
T = [Quantity(t) for t in temperature]

for spec,conc in initialConcentrations.iteritems():
concentration = Quantity(conc)
# check the dimensions are ok
# convert to mol/m^3 (or something numerically nice? or must it be SI)
initialConcentrations[spec] = concentration.value_si
if not isinstance(conc,list):
concentration = Quantity(conc)
# check the dimensions are ok
# convert to mol/m^3 (or something numerically nice? or must it be SI)
initialConcentrations[spec] = concentration.value_si
else:
if len(conc) != 2:
raise InputError("Concentration values must either be in the form of (number,units) or a list with 2 entries of the same format")
initialConcentrations[spec] = [Quantity(conc[0]),Quantity(conc[1])]

if not isinstance(temperature,list) and all([not isinstance(x,list) for x in initialConcentrations.itervalues()]):
nSimsTerm=1

termination = []
if terminationConversion is not None:
for spec, conv in terminationConversion.iteritems():
Expand All @@ -219,9 +280,18 @@ def liquidReactor(temperature,
logging.debug(" {0}".format(constantSpecie))
if not speciesDict.has_key(constantSpecie):
raise InputError('Species {0} not found in the input file'.format(constantSpecie))


system = LiquidReactor(T, initialConcentrations, termination, sensitiveSpecies, sensitivityThreshold,constantSpecies)

if not isinstance(T,list):
sensitivityTemperature = T
if not any([isinstance(x,list) for x in initialConcentrations.itervalues()]):
sensitivityConcentrations = initialConcentrations
if sensitivityConcentrations is None or sensitivityTemperature is None:
sensConditions = None
else:
sensConditions = sensitivityConcentrations
sensConditions['T'] = Quantity(sensitivityTemperature).value_si

system = LiquidReactor(T, initialConcentrations, nSimsTerm, termination, sensitiveSpecies, sensitivityThreshold, sensConditions, constantSpecies)
rmg.reactionSystems.append(system)

def simulator(atol, rtol, sens_atol=1e-6, sens_rtol=1e-4):
Expand Down
Loading