diff --git a/CItest-chem.py b/CItest-chem.py index 14875aca85529d052ecdfe837a265298cf51c7b1..340f138edee2befc2891d92d49167b6deb215bad 100755 --- a/CItest-chem.py +++ b/CItest-chem.py @@ -47,7 +47,6 @@ l = chemtest_reacs( 1.0, 1.0, 1.0e-3 / (1.0e-9 * defaultairdensity) ) # Lifetime about 1s for A and B, about 1e3sec for C mech = mechanism(l) -print(mech) inidict = {} inidict["A"] = initialization("A", "unifppb", (1.5,)) @@ -93,6 +92,7 @@ for exp in experimentlist: ts_c = ((myctm.t, myctm.activeConc_ppb("C")[0, 0, 0]),) print("running for", exp[2], "....") + print(myctm.mech) while myctm.t < myctm.finaltime - 0.00001: myctm.update() ts_a = ts_a + ((myctm.t, myctm.activeConc_ppb("A")[0, 0, 0]),) diff --git a/toyctm/toyctm.py b/toyctm/toyctm.py index d8df99117cc03186d7d1bc6a64dde62210fa79c6..451c26c6277ae40deb31573c73c09dfe1c713f45 100644 --- a/toyctm/toyctm.py +++ b/toyctm/toyctm.py @@ -216,7 +216,12 @@ class ctm: self.zdconc = np.zeros(self.conc.shape) self.mdconc = np.zeros(self.conc.shape) - # MIsc initializations + # Initialize the chemistry solver + if self.chemIntegrator is not None : + self.mech.define_integrator(self.chemIntegrator, + errortarget_relative = self.ebitolerance, + nitersmax = self.ebinitersmax) + # Misc initializations self.niters = -999 self.vdconc = 0. @@ -595,8 +600,7 @@ class ctm: self.prod.fill(0.0) self.loss.fill(0.0) - if self.chemIntegrator == "EulerForward": - self.mech.eulerForwardTimestep( + self.mech.integrator( self.t, self.dtchem, self.conc, @@ -609,24 +613,9 @@ class ctm: self.sunrise, self.sunset, ) - elif self.chemIntegrator == "EulerBackward": - self.mech.eulerBackwardTimestep( - self.t, - self.dtchem, - self.ebinitersmax, - self.ebitolerance, - self.conc, - self.tdgrid.activezIterator, - self.tdgrid.hgrid.activeCellsIterator, - ReactionConditions(self.t + self.dtchem), - self.prod, - self.loss, - self.PrescSet, - self.sunrise, - self.sunset, - self.niters, - np.max(self.density) / 1.0e23, - ) + + if 'niters' in self.mech.integrator_feedback : + self.niters = self.mech.integrator_feedback['niters'] def lagrangianupdate(self): """ diff --git a/toyctm_addons/chemistry.py b/toyctm_addons/chemistry.py index 223098b2eae6c498ad18bf2861495174d216e6b4..51459ae1b24ecfc7446ce051df8dcf443184f387 100644 --- a/toyctm_addons/chemistry.py +++ b/toyctm_addons/chemistry.py @@ -26,11 +26,13 @@ class ReactionParameters: def __init__(self): pass - class StandardReactionParameters(ReactionParameters): def __init__(self, k): self.k = k + def __str__(self): + return f'reaction rate constant {self.k}' + class PhotoReactionParameters(ReactionParameters): def __init__(self, peakval=None, sunrise=86400.0 / 4, sunset=3.0 * 86400.0 / 4.0): @@ -39,6 +41,8 @@ class PhotoReactionParameters(ReactionParameters): self.sunset = sunset self.noon = (self.sunrise + self.sunset) / 2.0 + def __str__(self): + return f'peakval: {self.peakval}, sunrise: {self.sunrise}, sunset:{self.sunset}' def createReaction(reactant_tup, products_tup, reaction_type, parameters): # reactant_tup : a tuple of strings @@ -129,7 +133,7 @@ class Reaction: return st -class StandardReaction: +class StandardReaction(Reaction): def __init__(self, reactant_tup, products_tup, reaction_type, parameters): Reaction.__init__(self, reactant_tup, products_tup, reaction_type, parameters) @@ -137,7 +141,7 @@ class StandardReaction: return self.parameters.k -class PhotoReaction: +class PhotoReaction(Reaction): def __init__(self, reactant_tup, products_tup, reaction_type, parameters): Reaction.__init__(self, reactant_tup, products_tup, reaction_type, parameters) @@ -262,13 +266,53 @@ class mechanism: self.species = {} for sp in self.specdict: self.species[sp] = speciesInstance(sp) + self.integrator = None + self.integrator_feedback = None def __str__(self): - s = "" + s = '-----------------------------------\n' + s = "List of reactions in the mechanism:\n" for r in self.reactions: - s = s + str(r) + "\n" + s = s + r.__str__() + "\n" + + s = s + "List of species in the mechanism:\n" + for i in sorted(self.revspecdict): + s = s + "{i} : {spname}\n".format( i = i, spname = self.revspecdict[i] ) + + s = s + "Number of species: {nspec}\n".format(nspec = self.nspec) + if self.integrator is not None: + s = s + "A time integrator has been defined in this mechanism:\n" + s = s + self.integrator_name + '\n' + if self.integrator_name == 'EulerBackward' : + s = s + "Max number of iterations {nitersmax}\n".format(nitersmax = self.nitersmax) + s = s + "Target for relative error {et}\n".format(et = self.errortarget_relative) + s = s + "Absolute error target (concentration) {ae}\n".format(ae = self.errortarget_absolute) + s = s + '-----------------------------------\n' return s + def define_integrator(self,integrator_name, + nitersmax = 1000, + errortarget_relative = 1.0e-2, + minimalconcentration = 1.e-23 + ): + """Initialise an integrator. Still work in progress. + the purpose is to make chemistry into a standalone package + able to integrate chemistry.""" + self.integrator_name = integrator_name + self.integrator_feedback = {} + if integrator_name == 'EulerBackward' : + self.integrator = self.eulerBackwardTimestep + self.integrator_feedback['niters'] = -999 + self.nitersmax = nitersmax + self.errortarget_relative = errortarget_relative + self.minimalconcentration = minimalconcentration + self.errortarget_absolute = self.minimalconcentration + elif integrator_name == 'EulerForward' : + self.integrator = self.eulerForwardTimestep + else : + print('ERROR : integrator', integrator_name, 'not available') + sys.exit(1) + def count_species(self): """ Counts the active species in the mechanism (without double-count of species that appear more than once). @@ -418,8 +462,6 @@ class mechanism: self, t, dtchem, - nitersmax, - errortarget_relative, conc, activezIterator, activeCellsIterator, @@ -429,8 +471,6 @@ class mechanism: prescriptionSet, sunrise, sunset, - niters, - minimalconcentration, ): """ Integrate over one time step dtchem with Euler Backward strategy @@ -443,8 +483,6 @@ class mechanism: # set convergence criteria - errortarget_absolute = minimalconcentration - concini = np.copy( conc ) # concini serves all along the fixed point search and should not be destroyed @@ -473,7 +511,7 @@ class mechanism: np.seterr( invalid="ignore", divide="ignore" ) # A division by zero is possible where conc=0 but the resulting values are ignored due to 'where'. We do not want these warnings on screen. - for i in range(nitersmax): + for i in range(self.nitersmax): prod.fill(0.0) loss.fill(0.0) self.chem( @@ -500,25 +538,28 @@ class mechanism: error = np.max( np.abs( np.where( - conc > minimalconcentration, - (concnew - conc) / conc / errortarget_relative, - np.abs(concnew - conc) / errortarget_absolute, + conc > self.minimalconcentration, + (concnew - conc) / conc / self.errortarget_relative, + np.abs(concnew - conc) / self.errortarget_absolute, ) ) ) conc[:, :, :, :] = concnew[:, :, :, :] if error < 1 and ntheo >= 2: + self.integrator_feedback['niters'] = i break - if i > nitersmax - 10: + if i > self.nitersmax - 10: print(i, error) np.seterr(invalid="warn", divide="ignore") - if i == nitersmax - 1: - exit( - "ERROR: toyctm.py: Backward Euler integration failed to converge. Try reducing time step" - ) + if i == self.nitersmax - 1: + print("""ERROR: toyctm.py: Backward Euler integration + failed to converge. Try reducing time step""") + sys.exit(1) + + prescriptionSet.apply( activeCellsIterator, activezIterator,