diff --git a/arkane/data/methoxy_explore.py b/arkane/data/methoxy_explore.py index 20142caef7..3376c0f078 100644 --- a/arkane/data/methoxy_explore.py +++ b/arkane/data/methoxy_explore.py @@ -238,5 +238,5 @@ source=['methoxy'], explore_tol=0.01, energy_tol=4.5e1, - flux_tol=1e-10, + flux_tol=1e-15, ) diff --git a/arkane/explorer.py b/arkane/explorer.py index 56fb3ada36..5ba764be44 100644 --- a/arkane/explorer.py +++ b/arkane/explorer.py @@ -206,6 +206,7 @@ def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesLis forbiddenStructures = getDB('forbidden') incomplete = True + checkedSpecies = [] while incomplete: incomplete = False @@ -214,6 +215,15 @@ def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesLis for network in self.networks: # compute the characteristic rate coefficient by summing all rate coefficients # from the reactant channel + for spc in reaction_model.edge.species: + if spc in checkedSpecies: + continue + if forbiddenStructures.isMoleculeForbidden(spc.molecule[0]): + reaction_model.removeSpeciesFromEdge(reaction_model.reactionSystems, spc) + reaction_model.removeEmptyPdepNetworks() + else: + checkedSpecies.append(spc) + kchar = 0.0 for rxn in network.netReactions: # reaction_model.core.reactions+reaction_model.edge.reactions: if (set(rxn.reactants) == set(self.source) @@ -227,21 +237,14 @@ def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesLis if network.getLeakCoefficient(T=T, P=P) > self.explore_tol * kchar: incomplete = True spc = network.getMaximumLeakSpecies(T=T, P=P) - if forbiddenStructures.isMoleculeForbidden(spc.molecule[0]): - reaction_model.removeSpeciesFromEdge(reaction_model.reactionSystems, spc) - reaction_model.removeEmptyPdepNetworks() - logging.error(spc.label) - else: - logging.info('adding new isomer {0} to network'.format(spc)) - flags = np.array([s.molecule[0].getFormula() == form - for s in reaction_model.core.species]) - reaction_model.enlarge((network, spc), reactEdge=False, unimolecularReact=flags, + logging.info('adding new isomer {0} to network'.format(spc)) + flags = np.array([s.molecule[0].getFormula() == form for s in reaction_model.core.species]) + reaction_model.enlarge((network, spc), reactEdge=False, unimolecularReact=flags, bimolecularReact=np.zeros((len(reaction_model.core.species), len(reaction_model.core.species)))) - flags = np.array( - [s.molecule[0].getFormula() == form for s in reaction_model.core.species]) - reaction_model.enlarge(reactEdge=True, unimolecularReact=flags, + flags = np.array([s.molecule[0].getFormula() == form for s in reaction_model.core.species]) + reaction_model.enlarge(reactEdge=True, unimolecularReact=flags, bimolecularReact=np.zeros((len(reaction_model.core.species), len(reaction_model.core.species)))) for network in self.networks: @@ -279,6 +282,7 @@ def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesLis if self.energy_tol != np.inf or self.flux_tol != 0.0: rxnSet = None + productSet = None for T in Tlist: if self.energy_tol != np.inf: @@ -290,17 +294,26 @@ def execute(self, outputFile, plot, format='pdf', print_summary=True, speciesLis for P in Plist: if self.flux_tol != 0.0: - rxns = network.get_rate_filtered_reactions(T, P, self.flux_tol) - if rxnSet is not None: - rxnSet &= set(rxns) + products = network.get_rate_filtered_products(T, P, self.flux_tol) + products = [tuple(x) for x in products] + if productSet is not None: + productSet &= set(products) else: - rxnSet = set(rxns) + productSet = set(products) + - logging.info('removing reactions during reduction:') - for rxn in rxnSet: - logging.info(rxn) + if rxnSet: + logging.info('removing reactions during reduction:') + for rxn in rxnSet: + logging.info(rxn) + rxnSet = list(rxnSet) + if productSet: + logging.info('removing products during reduction:') + for prod in productSet: + logging.info([x.label for x in prod]) + productSet = list(productSet) - network.remove_reactions(reaction_model, list(rxnSet)) + network.remove_reactions(reaction_model, rxns=rxnSet, prods=productSet) for rxn in jobRxns: if rxn not in network.pathReactions: diff --git a/rmgpy/rmg/pdep.py b/rmgpy/rmg/pdep.py index fe5cef42a3..3363fb0b56 100644 --- a/rmgpy/rmg/pdep.py +++ b/rmgpy/rmg/pdep.py @@ -322,7 +322,7 @@ def addPathReaction(self, newReaction): self.pathReactions.append(newReaction) self.invalidate() - def get_energy_filtered_reactions(self,T,tol): + def get_energy_filtered_reactions(self, T, tol): """ Returns a list of products and isomers that are greater in Free Energy than a*R*T + Gfsource(T) @@ -354,7 +354,7 @@ def get_energy_filtered_reactions(self,T,tol): return filtered_rxns - def get_rate_filtered_reactions(self,T,P,tol): + def get_rate_filtered_products(self, T, P, tol): """ determines the set of pathReactions that have fluxes less than tol at steady state where all A => B + C reactions are irreversible @@ -362,25 +362,35 @@ def get_rate_filtered_reactions(self,T,P,tol): """ c = self.solve_SS_network(T,P) isomerSpcs = [iso.species[0] for iso in self.isomers] - filtered_rxns = [] - for rxn in self.pathReactions: - val = 0.0 - val2 = 0.0 - if rxn.reactants[0] in isomerSpcs: - ind = isomerSpcs.index(rxn.reactants[0]) - kf = rxn.getRateCoefficient(T,P) - val = kf*c[ind] - if rxn.products[0] in isomerSpcs: - ind2 = isomerSpcs.index(rxn.products[0]) - kr = rxn.getRateCoefficient(T,P)/rxn.getEquilibriumConstant(T) - val2 = kr*c[ind2] - - if max(val,val2) < tol: - filtered_rxns.append(rxn) - - return filtered_rxns - - def solve_SS_network(self,T,P): + filtered_prod = [] + if c is not None: + for rxn in self.netReactions: + val = 0.0 + val2 = 0.0 + if rxn.reactants[0] in isomerSpcs: + ind = isomerSpcs.index(rxn.reactants[0]) + kf = rxn.getRateCoefficient(T,P) + val = kf*c[ind] + if rxn.products[0] in isomerSpcs: + ind2 = isomerSpcs.index(rxn.products[0]) + kr = rxn.getRateCoefficient(T,P)/rxn.getEquilibriumConstant(T) + val2 = kr*c[ind2] + + if max(val,val2) < tol: + filtered_prod.append(rxn.products) + + return filtered_prod + + else: + logging.warn("Falling back flux reduction from Steady State analysis to rate coefficient analysis") + ks = np.array([rxn.getRateCoefficient(T,P) for rxn in self.netReactions]) + frs = ks/ks.sum() + inds = [i for i in xrange(len(frs)) if frs[i] < tol] + filtered_prod = [self.netReactions[i].products for i in inds] + return filtered_prod + + + def solve_SS_network(self, T, P): """ calculates the steady state concentrations if all A => B + C reactions are irreversible and the flux from/to the source @@ -392,8 +402,8 @@ def solve_SS_network(self,T,P): isomerSpcs = [iso.species[0] for iso in self.isomers] - for rxn in self.pathReactions: + for rxn in self.netReactions: if rxn.reactants[0] in isomerSpcs: ind = isomerSpcs.index(rxn.reactants[0]) kf = rxn.getRateCoefficient(T,P) @@ -406,16 +416,16 @@ def solve_SS_network(self,T,P): A[ind2,ind2] -= kr else: ind2 = None - - if ind and ind2: + + if ind is not None and ind2 is not None: A[ind,ind2] += kr A[ind2,ind] += kf - + if bimolecular: - if rxn.reactants[0].species == self.source: + if rxn.reactants[0] == self.source: kf = rxn.getRateCoefficient(T,P) b[ind2] += kf - elif rxn.products[0].species == self.source: + elif rxn.products[0] == self.source: kr = rxn.getRateCoefficient(T,P)/rxn.getEquilibriumConstant(T) b[ind] += kr @@ -429,20 +439,27 @@ def solve_SS_network(self,T,P): if len(b) == 1: return np.array([b[0]/A[0,0]]) - con = np.linalg.cond(A) #this matrix can be very ill-conditioned so we enhance precision accordingly - mp.dps = 30+int(np.log10(con)) - Amp = mp.matrix(A.tolist()) - bmp = mp.matrix(b.tolist()) - - c = mp.qr_solve(Amp,bmp) + con = np.linalg.cond(A) - c = np.array(list(c[0])) - - if any(c<=0.0): - c, rnorm = opt.nnls(A,b) + if np.log10(con) < 15: + c = np.linalg.solve(A,b) + else: + logging.warn("Matrix Ill-conditioned, attempting to use Arbitrary Precision Arithmetic") + mp.dps = 30+int(np.log10(con)) + Amp = mp.matrix(A.tolist()) + bmp = mp.matrix(b.tolist()) - c = c.astype(np.float64) - + try: + c = mp.qr_solve(Amp,bmp) + + c = np.array(list(c[0])) + + if any(c<=0.0): + c, rnorm = opt.nnls(A,b) + + c = c.astype(np.float64) + except: #fall back to raw flux analysis rather than solve steady state problem + return None return c @@ -472,33 +489,88 @@ def remove_disconnected_reactions(self): logging.info('Removing rxn: {}'.format(rxn)) self.pathReactions.remove(rxn) + nrxns = [] + for nrxn in self.netReactions: + if nrxn.products not in keptProducts or nrxn.reactants not in keptProducts: + logging.info('Removing net rxn: {}'.format(nrxn)) + else: + logging.info('Keeping net rxn: {}'.format(nrxn)) + nrxns.append(nrxn) + self.netReactions = nrxns + + prods = [] for prod in self.products: if prod.species not in keptProducts: logging.info('Removing product: {}'.format(prod)) - self.products.remove(prod) - + else: + logging.info("Keeping product: {}".format(prod)) + prods.append(prod) + + self.products = prods + + rcts = [] for rct in self.reactants: if rct.species not in keptProducts: logging.info('Removing product: {}'.format(rct)) - self.reactants.remove(react) - + else: + logging.info("Keeping product: {}".format(rct)) + rcts.append(rct) + self.reactants = rcts + + isos = [] for iso in self.isomers: if iso.species not in keptProducts: logging.info('Removing isomer: {}'.format(iso)) - self.isomers.remove(iso) - if iso in self.explored: - self.explored.remove(iso) + else: + logging.info("Keeping isomer: {}".format(iso)) + isos.append(iso) + + self.isomers = isos + self.explored = [iso.species[0] for iso in isos] - def remove_reactions(self,reactionModel,rxns): + self.Nisom = len(self.isomers) + self.Nreac = len(self.reactants) + self.Nprod = len(self.products) + + def remove_reactions(self, reactionModel, rxns=None, prods=None): """ removes a list of reactions from the network and all reactions/products left disconnected by removing those reactions """ - for rxn in rxns: - self.pathReactions.remove(rxn) - + if rxns: + for rxn in rxns: + self.pathReactions.remove(rxn) + + if prods: + isomers = [x.species[0] for x in self.isomers] + + for prod in prods: + prod = [x for x in prod] + if prod[0] in isomers: #skip isomers + continue + for rxn in self.pathReactions: + if rxn.products == prod or rxn.reactants == prod: + self.pathReactions.remove(rxn) + + prodspc = [x[0] for x in prods] + for prod in prods: + prod = [x for x in prod] + if prod[0] in isomers: #deal with isomers + for rxn in self.pathReactions: + if rxn.reactants == prod and rxn.products[0] not in isomers and rxn.products[0] not in prodspc: + break + if rxn.products == prod and rxn.reactants[0] not in isomers and rxn.reactants not in prodspc: + break + else: + for rxn in self.pathReactions: + if rxn.reactants == prod or rxn.products == prod: + self.pathReactions.remove(rxn) + + self.remove_disconnected_reactions() - + + self.cleanup() + self.invalidate() assert self.pathReactions != [], 'Reduction process removed all reactions, cannot update network with no reactions' diff --git a/rmgpy/rmg/pdepTest.py b/rmgpy/rmg/pdepTest.py index 9b33ff39de..28fded4032 100644 --- a/rmgpy/rmg/pdepTest.py +++ b/rmgpy/rmg/pdepTest.py @@ -162,19 +162,14 @@ def setUp(self): self.pdepnetwork.index = 1 self.pdepnetwork.explored = [] - - def test_SS_solver(self): - c = self.pdepnetwork.solve_SS_network(1000.0,100000.0) - self.assertAlmostEquals(c[0],4.791463e-06,2) - def test_energy_filter(self): rxns = self.pdepnetwork.get_energy_filtered_reactions(1000.0,0.0) self.assertEquals(len(rxns),1) self.assertEquals(rxns[0],self.pdepnetwork.pathReactions[0]) - + def test_flux_filter(self): - rxns = self.pdepnetwork.get_rate_filtered_reactions(1000.0,100000.0,1.0) - self.assertEquals(len(rxns),0) - + prods = self.pdepnetwork.get_rate_filtered_products(1000.0,100000.0,1.0) + self.assertEquals(len(prods),0) + if __name__ == '__main__': unittest.main()