def __init__(self, cantera, outputSpeciesList, kParams, kUncertainty, gParams, gUncertainty, correlated=False): self.reactorMod = ReactorModPiece(cantera=cantera, outputSpeciesList=outputSpeciesList, kParams=kParams, kUncertainty = kUncertainty, gParams = gParams, gUncertainty = gUncertainty, correlated = correlated, ) # Define the polynomials and quadrature rules in each dimension using a VariableCollection object. # We can do this directly using the classes from libmuqUtilities. Build the PCE factory for the ReactorModPiece this way. # Uniform random variables used chemical kinetics uncertainty propagation uses Legendre polynomials # We select the Gauss-Patterson quadrature as it is recommended as the fastest in the Patrick Conrad, Youssef Marzouk paper # Select the polynomial and quadrature families polyFamily = LegendrePolynomials1DRecursive() quadFamily = GaussPattersonQuadrature1D() # Create a random variable collection for each of the uncertain variables varCollection = VariableCollection() for i, rxnIndex in enumerate(kParams): varCollection.PushVariable("k{0}".format(i+1), polyFamily, quadFamily) for i, speciesIndex in enumerate(gParams): varCollection.PushVariable("G{0}".format(i+1), polyFamily, quadFamily) # Initialize the PCE Factory self.factory = SmolyakPCEFactory(varCollection, self.reactorMod) self.pce = None
class ReactorPCEFactory(object): """ This class uses MUQ to generate adaptive Polynomial Chaos Expansions for global uncertainty analysis in chemical reaction systems. It uses RMG, Cantera, and MUQ dependencies. Methodology 1. Set up reactor conditions and load chemical kinetic mechanism. Select desired outputs 2. Run local uncertainty analysis 3. Extract top N most uncertain parameters 4. Input these set of parameters into a MUQ model class (which is a child of the ModPiece class) 5. Create EvaluateImpl function within model class that runs simulation based on reactor conditions through Cantera 6. Perform PCE analysis of desired outputs """ def __init__(self, cantera, outputSpeciesList, kParams, kUncertainty, gParams, gUncertainty, correlated=False, logx=True): self.reactorMod = ReactorModPiece( cantera=cantera, outputSpeciesList=outputSpeciesList, kParams=kParams, kUncertainty=kUncertainty, gParams=gParams, gUncertainty=gUncertainty, correlated=correlated, logx=logx, ) # Define the polynomials and quadrature rules in each dimension using a VariableCollection object. # We can do this directly using the classes from libmuqUtilities. Build the PCE factory for the ReactorModPiece this way. # Uniform random variables used chemical kinetics uncertainty propagation uses Legendre polynomials # We select the Gauss-Patterson quadrature as it is recommended as the fastest in the Patrick Conrad, Youssef Marzouk paper # Select the polynomial and quadrature families polyFamily = LegendrePolynomials1DRecursive() quadFamily = GaussPattersonQuadrature1D() # Create a random variable collection for each of the uncertain variables varCollection = VariableCollection() for i, rxnIndex in enumerate(kParams): varCollection.PushVariable("k{0}".format(i + 1), polyFamily, quadFamily) for i, speciesIndex in enumerate(gParams): varCollection.PushVariable("G{0}".format(i + 1), polyFamily, quadFamily) # Initialize the PCE Factory self.factory = SmolyakPCEFactory(varCollection, self.reactorMod) self.pce = None self.logx = logx def generatePCE(self, runTime=None, startOrder=2, tolerance=None, fixedTerms=False): """ Generate the PCEs adaptively. There are three methods for doing so. `runTime` should be given in seconds Option 1: Adaptive for a pre-specified amount of time Option 2: Adaptively construct PCE to error tolerance Option 3: Used a fixed order, and (optionally) adapt later. """ # Also monitor the amount of time it takes start_time = time() if runTime: # Option 1: Adaptive for a pre-specified amount of time self.pce = self.factory.StartAdaptiveTimed(startOrder, runTime) elif tolerance: # Option 2: adaptively construct PCE to error tolerance self.pce = self.factory.StartAdaptiveToTolerance( startOrder, tolerance) elif fixedTerms: # Option 3: Used a fixed order, and (optionally) adapt later self.pce = self.factory.StartFixedTerms(startOrder) # # Optionally adapt to tolerance later: # pce = self.AdaptToTolerance(tolerance) else: raise Exception('Must have at least one chosen method') end_time = time() time_taken = end_time - start_time logging.info( 'Polynomial Chaos Expansion construction took {0:2f} seconds.'. format(time_taken)) def compareOutput(self, testPoint, log=True): """ Evaluate the PCEs against what the real output might give for a test point. testPoint is an array of all the values in terms of factor of f Returns a tuple containing the (true output mole fractions, pce output mole fractions) evaluated at the test point. """ trueOutput = self.reactorMod.Evaluate([testPoint]) pceOutput = self.pce.Evaluate(testPoint) reactorMod = self.reactorMod output = '' for i in range(reactorMod.numConditions): output += """============================================================ Condition {0} ------------------------------------------------------------ {1!s} ============================================================ Condition {0} {2}Mole Fractions Evaluated at Test Point ------------------------------------------------------------ Species True Output PCE Output ------------------------------------------------------------ """.format(i + 1, reactorMod.cantera.conditions[i], 'Log ' if self.logx else '') for j, outputSpecies in enumerate(reactorMod.outputSpeciesList): outputIndex = i * reactorMod.numOutputSpecies + j output += '{0:<20}{1:>20.3f}{2:>20.3f}\n'.format( outputSpecies.to_chemkin(), trueOutput[outputIndex], pceOutput[outputIndex]) output += '============================================================\n' if log: logging.info(output) else: print(output) return trueOutput, pceOutput def analyzeResults(self, log=True): """ Obtain the results: the prediction mean and variance, as well as the global sensitivity indices Returns a tuple containing the following statistics (mean species mole fractions, variance, covariance, main sensitivity indices, total sensitivity indices) """ reactorMod = self.reactorMod pce = self.pce # Compute the mean and variance for each of the uncertain parameters mean = np.array(pce.ComputeMean()) var = np.array(pce.ComputeVariance()) stddev = np.sqrt(var) stddev_percent = stddev / mean * 100.0 cov = pce.ComputeCovariance() # Extract the global sensitivity indices mainSens = np.array(pce.ComputeAllMainSensitivityIndices()) totalSens = np.array(pce.ComputeAllSobolTotalSensitivityIndices()) output = '' for i in range(reactorMod.numConditions): output += """============================================================ Condition {0} ------------------------------------------------------------ {1!s} ============================================================ Condition {0} {2}Mole Fractions ------------------------------------------------------------ Species Mean Stddev Stddev (%) ------------------------------------------------------------ """.format(i + 1, reactorMod.cantera.conditions[i], 'Log ' if self.logx else '') for j, outputSpecies in enumerate(reactorMod.outputSpeciesList): outputIndex = i * reactorMod.numOutputSpecies + j output += '{0:<15}{1:>15.3e}{2:>15.3e}{3:>15.3f}\n'.format( outputSpecies.to_chemkin(), mean[outputIndex], stddev[outputIndex], stddev_percent[outputIndex]) output += '============================================================\n\n' if reactorMod.kParams: output += """==================================================================================================== Condition {0} Reaction Rate Sensitivity Indices ---------------------------------------------------------------------------------------------------- Description sens_main sens_total """.format(i + 1) for j, outputSpecies in enumerate( reactorMod.outputSpeciesList): output += '----------------------------------------------------------------------------------------------------\n' outputIndex = i * reactorMod.numOutputSpecies + j for k, descriptor in enumerate(reactorMod.kParams): parameterIndex = k if not reactorMod.correlated: description = 'dln[{0}]/dln[{1}]'.format( outputSpecies.to_chemkin(), reactorMod.cantera.reactionList[descriptor]. to_chemkin(kinetics=False)) else: description = 'dln[{0}]/dln[{1}]'.format( outputSpecies.to_chemkin(), descriptor) output += '{0:<70}{1:>14.3f}%{2:>14.3f}%\n'.format( description, 100 * mainSens[outputIndex][parameterIndex], 100 * totalSens[outputIndex][parameterIndex]) output += '====================================================================================================\n\n' if reactorMod.gParams: output += """==================================================================================================== Condition {0} Thermochemistry Sensitivity Indices ---------------------------------------------------------------------------------------------------- Description sens_main sens_total """.format(i + 1) for j, outputSpecies in enumerate( reactorMod.outputSpeciesList): output += '----------------------------------------------------------------------------------------------------\n' outputIndex = i * reactorMod.numOutputSpecies + j for g, descriptor in enumerate(reactorMod.gParams): parameterIndex = len(reactorMod.kParams) + g if not reactorMod.correlated: description = 'dln[{0}]/dG[{1}]'.format( outputSpecies.to_chemkin(), reactorMod.cantera. speciesList[descriptor].to_chemkin()) else: description = 'dln[{0}]/dG[{1}]'.format( outputSpecies.to_chemkin(), descriptor) output += '{0:<70}{1:>14.3f}%{2:>14.3f}%\n'.format( description, 100 * mainSens[outputIndex][parameterIndex], 100 * totalSens[outputIndex][parameterIndex]) output += '====================================================================================================\n\n' if log: logging.info(output) else: print(output) return mean, var, cov, mainSens, totalSens