From 031b93ca4823a9f8c7d138769533849b4efe138a Mon Sep 17 00:00:00 2001 From: Matt Johnson Date: Fri, 27 Apr 2018 12:17:05 -0400 Subject: [PATCH] Adapt solver/reactor object syntax to deal with variable condition reactors Also adaption of conversion calculations and return type for simulate so that the time and conversion can be put into the RMG_Memory objects. Addition of sensConditions for specifying the conditions under, which to do sensitivity analysis. --- rmgpy/rmg/input.py | 121 ++++++++++++++++++++++++++-------------- rmgpy/rmg/main.py | 5 +- rmgpy/solver/base.pxd | 5 +- rmgpy/solver/base.pyx | 39 ++++++++----- rmgpy/solver/liquid.pyx | 29 +++++----- rmgpy/solver/simple.pyx | 31 +++++----- 6 files changed, 146 insertions(+), 84 deletions(-) diff --git a/rmgpy/rmg/input.py b/rmgpy/rmg/input.py index 1d669383da..8e039a838b 100644 --- a/rmgpy/rmg/input.py +++ b/rmgpy/rmg/input.py @@ -133,47 +133,51 @@ def adjacencyList(string): def simpleReactor(temperature, pressure, initialMoleFractions, + nSimsTerm=6, terminationConversion=None, terminationTime=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 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)) - - if type(temperature) != list and type(pressure) != list: + 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 len(value) > 2: + raise InputError('mole ranges can only have one or two entries') + elif 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) - P = Quantity(pressure) else: - if type(temperature) != list: - temperature = list(temperature) - if type(pressure) != list: - pressure = list(pressure) + 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] - if len(T) > 2 or len(P) > 2: - raise InputError('Temperature and pressure ranges can only have one or two entries') + + 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: for spec, conv in terminationConversion.iteritems(): @@ -188,7 +192,21 @@ 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 = initialMoleFractions + if sensitivityMoleFractions is None or sensitivityTemperature is None or sensitivityPressure is None: + sensConditions = None + else: + sensConditions = sensitivityMoleFractions + sensConditions['T'] = Quantity(sensitivityTemperature) + sensConditions['P'] = Quantity(sensitivityPressure) + + system = SimpleReactor(T, P, initialMoleFractions, nSimsTerm, termination, sensitiveSpecies, sensitivityThreshold,sensConditions) rmg.reactionSystems.append(system) @@ -196,25 +214,37 @@ def simpleReactor(temperature, 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') - if type(temperature) != list: + 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 len(T) > 2: - raise InputError('Temperature ranges can only have one or two entries') 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(): @@ -236,9 +266,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 = Quantity(sensitivityConcentrations) + sensConditions['T'] = Quantity(sensitivityTemperature) + + 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): diff --git a/rmgpy/rmg/main.py b/rmgpy/rmg/main.py index e88bf9a31c..5f1b9d87c4 100644 --- a/rmgpy/rmg/main.py +++ b/rmgpy/rmg/main.py @@ -647,7 +647,7 @@ def execute(self, **kwargs): # Turn pruning off if we haven't reached minimum core size. prune = False - try: terminated,resurrected,obj,newSurfaceSpecies,newSurfaceReactions = reactionSystem.simulate( + try: terminated,resurrected,obj,newSurfaceSpecies,newSurfaceReactions,t,x = reactionSystem.simulate( coreSpecies = self.reactionModel.core.species, coreReactions = self.reactionModel.core.reactions, edgeSpecies = self.reactionModel.edge.species, @@ -806,7 +806,7 @@ def execute(self, **kwargs): # Run sensitivity analysis post-model generation if sensitivity analysis is on for index, reactionSystem in enumerate(self.reactionSystems): - if reactionSystem.sensitiveSpecies: + if reactionSystem.sensitiveSpecies and reactionSystem.sensConditions: logging.info('Conducting sensitivity analysis of reaction system %s...' % (index+1)) sensWorksheet = [] @@ -826,6 +826,7 @@ def execute(self, **kwargs): sensWorksheet = sensWorksheet, modelSettings = self.modelSettingsList[-1], simulatorSettings = self.simulatorSettingsList[-1], + conditions = reactionSystem.sensConditions, ) plot_sensitivity(self.outputDirectory, index, reactionSystem.sensitiveSpecies) diff --git a/rmgpy/solver/base.pxd b/rmgpy/solver/base.pxd index 7119645e36..a155ca3334 100644 --- a/rmgpy/solver/base.pxd +++ b/rmgpy/solver/base.pxd @@ -112,12 +112,13 @@ cdef class ReactionSystem(DASx): # methods cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list surfaceSpecies=?, - list surfaceReactions=?, list pdepNetworks=?, atol=?, rtol=?, sensitivity=?, sens_atol=?, sens_rtol=?, filterReactions=?) + list surfaceReactions=?, list pdepNetworks=?, atol=?, rtol=?, sensitivity=?, sens_atol=?, sens_rtol=?, filterReactions=?, + dict conditions=?) cpdef simulate(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions,list surfaceSpecies, list surfaceReactions, list pdepNetworks=?, bool prune=?, bool sensitivity=?, list sensWorksheet=?, object modelSettings=?, - object simulatorSettings=?) + object simulatorSettings=?, dict conditions=?) cpdef logRates(self, double charRate, object species, double speciesRate, double maxDifLnAccumNum, object network, double networkRate) diff --git a/rmgpy/solver/base.pyx b/rmgpy/solver/base.pyx index 6c8f9112e4..ab95be8657 100644 --- a/rmgpy/solver/base.pyx +++ b/rmgpy/solver/base.pyx @@ -188,7 +188,7 @@ cdef class ReactionSystem(DASx): cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list surfaceSpecies=None, list surfaceReactions=None, list pdepNetworks=None, atol=1e-16, rtol=1e-8, sensitivity=False, - sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False): + sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False, dict conditions=None): """ Initialize a simulation of the reaction system using the provided kinetic model. You will probably want to create your own version of this @@ -200,7 +200,21 @@ cdef class ReactionSystem(DASx): if surfaceReactions is None: surfaceReactions = [] - + if conditions: + isConc = hasattr(self,'initialConcentrations') + keys = conditions.keys() + if 'T' in keys and hasattr(self,'T'): + self.T = Quantity(conditions['T'],'K') + if 'P' in keys and hasattr(self,'P'): + self.P = Quantity(conditions['P'],'Pa') + for k in keys: + if isConc: + if k in self.initialConcentrations.keys(): + self.initialConcentrations[k] = conditions[k] #already in SI units + else: + if k in self.initialMoleFractions.keys(): + self.initialMoleFractions[k] = conditions[k] + self.numCoreSpecies = len(coreSpecies) self.numCoreReactions = len(coreReactions) self.numEdgeSpecies = len(edgeSpecies) @@ -520,7 +534,7 @@ cdef class ReactionSystem(DASx): cpdef simulate(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions,list surfaceSpecies, list surfaceReactions, list pdepNetworks=None, bool prune=False, bool sensitivity=False, list sensWorksheet=None, object modelSettings=None, - object simulatorSettings=None): + object simulatorSettings=None, dict conditions=None): """ Simulate the reaction system with the provided reaction model, consisting of lists of core species, core reactions, edge species, and @@ -549,7 +563,7 @@ cdef class ReactionSystem(DASx): cdef bint terminated cdef object maxSpecies, maxNetwork cdef int i, j, k - cdef numpy.float64_t maxSurfaceDifLnAccumNum, maxSurfaceSpeciesRate + cdef numpy.float64_t maxSurfaceDifLnAccumNum, maxSurfaceSpeciesRate, conversion cdef int maxSurfaceAccumReactionIndex, maxSurfaceSpeciesIndex cdef object maxSurfaceAccumReaction, maxSurfaceSpecies cdef numpy.ndarray[numpy.float64_t,ndim=1] surfaceSpeciesProduction, surfaceSpeciesConsumption @@ -563,7 +577,7 @@ cdef class ReactionSystem(DASx): # cython declations for sensitivity analysis cdef numpy.ndarray[numpy.int_t, ndim=1] sensSpeciesIndices cdef numpy.ndarray[numpy.float64_t, ndim=1] moleSens, dVdk, normSens - cdef list time_array, normSens_array, newSurfaceReactions, newSurfaceReactionInds, newObjects, newObjectInds, conversions + cdef list time_array, normSens_array, newSurfaceReactions, newSurfaceReactionInds, newObjects, newObjectInds zeroProduction = False zeroConsumption = False @@ -609,7 +623,7 @@ cdef class ReactionSystem(DASx): self.initializeModel(coreSpecies, coreReactions, edgeSpecies, edgeReactions, surfaceSpecies, surfaceReactions, pdepNetworks, absoluteTolerance, relativeTolerance, sensitivity, sensitivityAbsoluteTolerance, - sensitivityRelativeTolerance, filterReactions) + sensitivityRelativeTolerance, filterReactions,conditions) prunableSpeciesIndices = self.prunableSpeciesIndices prunableNetworkIndices = self.prunableNetworkIndices @@ -630,6 +644,7 @@ cdef class ReactionSystem(DASx): maxNetwork = None maxNetworkRate = 0.0 iteration = 0 + conversion = 0.0 maxEdgeSpeciesRateRatios = self.maxEdgeSpeciesRateRatios maxNetworkLeakRateRatios = self.maxNetworkLeakRateRatios @@ -667,12 +682,11 @@ cdef class ReactionSystem(DASx): logging.info('Resurrecting Model...') - conversions = [] - + conversion = 0.0 for term in self.termination: if isinstance(term, TerminationConversion): index = speciesIndex[term.species] - conversions.append(1-(y_coreSpecies[index] / y0[index])) + conversion = 1-(y_coreSpecies[index] / y0[index]) if invalidObjects == []: #species flux criterion @@ -695,7 +709,7 @@ cdef class ReactionSystem(DASx): invalidObjects.append(obj) if invalidObjects != []: - return False,True,invalidObjects,surfaceSpecies,surfaceReactions,self.t,conversions + return False,True,invalidObjects,surfaceSpecies,surfaceReactions,self.t,conversion else: logging.error('Model Resurrection has failed') logging.error("Core species names: {!r}".format([getSpeciesIdentifier(s) for s in coreSpecies])) @@ -1031,7 +1045,6 @@ cdef class ReactionSystem(DASx): logging.info('terminating simulation due to interrupt...') break - conversions = [] # Finish simulation if any of the termination criteria are satisfied for term in self.termination: if isinstance(term, TerminationTime): @@ -1042,7 +1055,7 @@ cdef class ReactionSystem(DASx): break elif isinstance(term, TerminationConversion): index = speciesIndex[term.species] - conversions.append(1-(y_coreSpecies[index] / y0[index])) + conversion = 1-(y_coreSpecies[index] / y0[index]) if 1 - (y_coreSpecies[index] / y0[index]) > term.conversion: terminated = True logging.info('At time {0:10.4e} s, reached target termination conversion: {1:f} of {2}'.format(self.t,term.conversion,term.species)) @@ -1088,7 +1101,7 @@ cdef class ReactionSystem(DASx): # Return the invalid object (if the simulation was invalid) or None # (if the simulation was valid) - return terminated, False, invalidObjects, surfaceSpecies, surfaceReactions, self.t, conversions + return terminated, False, invalidObjects, surfaceSpecies, surfaceReactions, self.t, conversion cpdef logRates(self, double charRate, object species, double speciesRate, double maxDifLnAccumNum, object network, double networkRate): """ diff --git a/rmgpy/solver/liquid.pyx b/rmgpy/solver/liquid.pyx index 8a979fcfe5..7245332294 100644 --- a/rmgpy/solver/liquid.pyx +++ b/rmgpy/solver/liquid.pyx @@ -54,19 +54,22 @@ cdef class LiquidReactor(ReactionSystem): cdef public ScalarQuantity P cdef public double V cdef public bint constantVolume - cdef public constSPCNames + cdef public list constSPCNames cdef public list constSPCIndices cdef public dict initialConcentrations - - def __init__(self, T, initialConcentrations, termination, sensitiveSpecies=None, sensitivityThreshold=1e-3, constSPCNames=None): + cdef public list Trange + cdef public int nSimsTerm + cdef public dict sensConditions + + def __init__(self, T, initialConcentrations, nSimsTerm=None, termination=None, sensitiveSpecies=None, sensitivityThreshold=1e-3, sensConditions=None, constSPCNames=None): ReactionSystem.__init__(self, termination, sensitiveSpecies, sensitivityThreshold) if type(T) != list: self.T = Quantity(T) else: - self.Tlist = [Quantity(t) for t in T] - + self.Trange = [Quantity(t) for t in T] + self.P = Quantity(100000.,'kPa') # Arbitrary high pressure (1000 Bar) to get reactions in the high-pressure limit! self.initialConcentrations = initialConcentrations # should be passed in SI self.V = 0 # will be set from initialConcentrations in initializeModel @@ -74,6 +77,8 @@ cdef class LiquidReactor(ReactionSystem): #Constant concentration attributes self.constSPCIndices=None self.constSPCNames = constSPCNames #store index of constant species + self.sensConditions = sensConditions + self.nSimsTerm = nSimsTerm def convertInitialKeysToSpeciesObjects(self, speciesDict): """ @@ -82,6 +87,8 @@ cdef class LiquidReactor(ReactionSystem): """ initialConcentrations = {} for label, moleFrac in self.initialConcentrations.iteritems(): + if label == 'T': + continue initialConcentrations[speciesDict[label]] = moleFrac self.initialConcentrations = initialConcentrations @@ -96,7 +103,7 @@ cdef class LiquidReactor(ReactionSystem): cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list surfaceSpecies=None, list surfaceReactions=None, list pdepNetworks=None, atol=1e-16, rtol=1e-8, sensitivity=False, - sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False, T=None, P=None): + sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False, dict conditions=None): """ Initialize a simulation of the liquid reactor using the provided kinetic model. @@ -105,15 +112,11 @@ cdef class LiquidReactor(ReactionSystem): surfaceSpecies = [] if surfaceReactions is None: surfaceReactions = [] - - if T: - self.T = T - if P: - raise ValueError('P should always be None') #P was added because it helps avoid messy code duplication in main.py - + # First call the base class version of the method # This initializes the attributes declared in the base class - ReactionSystem.initializeModel(self, coreSpecies, coreReactions, edgeSpecies, edgeReactions, surfaceSpecies, surfaceReactions, pdepNetworks, atol, rtol, sensitivity, sens_atol, sens_rtol) + ReactionSystem.initializeModel(self, coreSpecies, coreReactions, edgeSpecies, edgeReactions, surfaceSpecies, surfaceReactions, + pdepNetworks, atol, rtol, sensitivity, sens_atol, sens_rtol, filterReactions, conditions) # Set initial conditions self.set_initial_conditions() diff --git a/rmgpy/solver/simple.pyx b/rmgpy/solver/simple.pyx index 7b4b4ce798..21e6a30bc0 100644 --- a/rmgpy/solver/simple.pyx +++ b/rmgpy/solver/simple.pyx @@ -101,17 +101,25 @@ cdef class SimpleReactor(ReactionSystem): a specifcCollider attribyte. E.g. [16, 155, 90] """ cdef public numpy.ndarray pdepSpecificColliderReactionIndices + + cdef public dict sensConditions + + cdef public list Trange + cdef public list Prange + cdef public int nSimsTerm - - def __init__(self, T, P, initialMoleFractions, termination, sensitiveSpecies=None, sensitivityThreshold=1e-3): + def __init__(self, T, P, initialMoleFractions, nSimsTerm=None, termination=None, sensitiveSpecies=None, sensitivityThreshold=1e-3,sensConditions=None): ReactionSystem.__init__(self, termination, sensitiveSpecies, sensitivityThreshold) - if type(T) != list and type(P) != list: + if type(T) != list: self.T = Quantity(T) - self.P = Quantity(P) else: self.Trange = [Quantity(t) for t in T] + + if type(P) != list: + self.P = Quantity(P) + else: self.Prange = [Quantity(p) for p in P] self.initialMoleFractions = initialMoleFractions @@ -125,14 +133,15 @@ cdef class SimpleReactor(ReactionSystem): self.pdepSpecificColliderReactionIndices = None self.pdepSpecificColliderKinetics = None self.specificColliderSpecies = None - + self.sensConditions = sensConditions + self.nSimsTerm = nSimsTerm def __reduce__(self): """ A helper function used when pickling an object. """ return (self.__class__, - (self.T, self.P, self.initialMoleFractions, self.termination)) + (self.T, self.P, self.initialMoleFractions, self.nSimsTerm, self.termination)) def convertInitialKeysToSpeciesObjects(self, speciesDict): @@ -147,7 +156,7 @@ cdef class SimpleReactor(ReactionSystem): cpdef initializeModel(self, list coreSpecies, list coreReactions, list edgeSpecies, list edgeReactions, list surfaceSpecies=None, list surfaceReactions=None, list pdepNetworks=None, atol=1e-16, rtol=1e-8, sensitivity=False, - sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False, T=None, P=None): + sens_atol=1e-6, sens_rtol=1e-4, filterReactions=False, dict conditions=None): """ Initialize a simulation of the simple reactor using the provided kinetic model. @@ -158,17 +167,13 @@ cdef class SimpleReactor(ReactionSystem): if surfaceReactions is None: surfaceReactions = [] - if T: - self.T = T - if P: - self.P = P - + # First call the base class version of the method # This initializes the attributes declared in the base class ReactionSystem.initializeModel(self, coreSpecies=coreSpecies, coreReactions=coreReactions, edgeSpecies=edgeSpecies, edgeReactions=edgeReactions, surfaceSpecies=surfaceSpecies, surfaceReactions=surfaceReactions, pdepNetworks=pdepNetworks, atol=atol, rtol=rtol, sensitivity=sensitivity, sens_atol=sens_atol, - sens_rtol=sens_rtol) + sens_rtol=sens_rtol,filterReactions=filterReactions,conditions=conditions) # Set initial conditions self.set_initial_conditions()