diff --git a/interfaces/python/ck2cti.py b/interfaces/python/ck2cti.py index 62f84ebfd..7690afbed 100755 --- a/interfaces/python/ck2cti.py +++ b/interfaces/python/ck2cti.py @@ -39,6 +39,9 @@ import numpy as np import re import itertools +reFloat = re.compile(r'\+?\d*\.?\d+([eEdD][-+]?\d+)?$') +reInt = re.compile(r'\+?\d+$') + QUANTITY_UNITS = {'MOL': 'mol', 'MOLE': 'mol', 'MOLES': 'mol', @@ -997,6 +1000,18 @@ class Parser(object): return species, thermo, composition, note + def setupKinetics(self): + self.valid_tokens = dict((k,'species') for k in self.speciesDict) + self.valid_tokens.update(('(+%s)' % k, 'falloff3b: %s' % k) for k in self.speciesDict) + self.valid_tokens['M'] = 'third-body' + self.valid_tokens['m'] = 'third-body' + self.valid_tokens['(+M)'] = 'falloff3b' + self.valid_tokens['(+m)'] = 'falloff3b' + self.valid_tokens['<=>'] = 'equal' + self.valid_tokens['=>'] = 'equal' + self.valid_tokens['='] = 'equal' + self.Slen = max(map(len, self.valid_tokens)) + def readKineticsEntry(self, entry): """ Read a kinetics `entry` for a single reaction as loaded from a @@ -1034,19 +1049,48 @@ class Parser(object): n = float(tokens[-2]) Ea = float(tokens[-1]) reaction = ''.join(tokens[:-3]) - revReaction = None - # Split the reaction equation into reactants and products - if '<=>' in reaction: - reversible = True - reactants, products = reaction.split('<=>') - elif '=>' in reaction: - reversible = False - reactants, products = reaction.split('=>') - elif '=' in reaction: - reversible = True - reactants, products = reaction.split('=') - else: + # Identify tokens comprising the reaction expression. Look for the + # longest possible sub-sequences first. + locs = {} + for i in range(self.Slen, 0, -1): + for j in range(len(reaction)-i+1): + test = reaction[j:j+i] + if test in self.valid_tokens: + reaction = reaction[:j] + ' '*i + reaction[j+i:] + locs[j] = test, self.valid_tokens[test] + + # Anything that's left should be a stoichiometric coefficient or a '+' + # between species + for token in reaction.split(): + j = reaction.find(token) + i = len(token) + reaction = reaction[:j] + ' '*i + reaction[j+i:] + if reInt.match(token): + locs[j] = int(token), 'coeff' + elif reFloat.match(token): + locs[j] = float(token), 'coeff' + elif token != '+': + raise InputParseError('Unexpected token "{0}" in reaction expression "{1}".'.format(token, reaction)) + + reactants = [] + products = [] + stoichiometry = 1 + lhs = True + for token,kind in [v for k,v in sorted(locs.items())]: + if kind == 'equal': + reversible = token in ('<=>', '=') + lhs = False + elif kind == 'coeff': + stoichiometry = token + elif lhs: + reactants.append((stoichiometry,token,kind)) + stoichiometry = 1 + else: + products.append((stoichiometry,token,kind)) + stoichiometry = 1 + + if lhs is True: raise InputParseError("Failed to find reactant/product delimiter in reaction string.") # Create a new Reaction object for this reaction @@ -1055,39 +1099,13 @@ class Parser(object): def parseExpression(expression, dest): falloff3b = None thirdBody = False # simple third body reaction (non-falloff) - - # Look for third-body species for falloff reactions - if re.search(r'\(\+[Mm]\)', expression): - falloff3b = 'M' - expression = re.sub(r'(\(\+[Mm]\))', '', expression) - elif re.search(r'\(\+.*\)', expression): - # See if it matches a known species - for species in self.speciesDict: - if re.search(r'\(\+%s\)' % re.escape(species), expression): - falloff3b = species - expression = re.sub(r'(\(\+%s\))' % re.escape(species), - '', expression) - break - - for term in expression.split('+'): - term = term.strip() - if term[0].isdigit() or term[0] == '.': - # This allows for for non-unity stoichiometric coefficients, e.g. - # 2A=B+C or .85A+.15B=>C - j = [i for i in range(len(term)) if isnumberlike(term[:i])][-1] - if term[:j].isdigit(): - stoichiometry = int(term[:j]) - else: - stoichiometry = float(term[:j]) - species = term[j:] - else: - species = term - stoichiometry = 1 - - if species == 'M' or species == 'm': + for stoichiometry,species,kind in expression: + if kind == 'third-body': thirdBody = True - elif species not in self.speciesDict: - raise InputParseError('Unexpected species "{0}" in reaction expression "{1}".'.format(species, expression)) + elif kind == 'falloff3b': + falloff3b = 'M' + elif kind.startswith('falloff3b:'): + falloff3b = kind.split()[1] else: dest.append((stoichiometry, self.speciesDict[species])) @@ -1116,7 +1134,6 @@ class Parser(object): quantity_dim + 1, quantity_units) # The rest of the first line contains Arrhenius parameters - tokens = lines[0].split()[1:] arrhenius = Arrhenius( A=(A,kunits), n=n, @@ -1554,6 +1571,7 @@ class Parser(object): if len(kineticsList) != len(commentsList): commentsList = ['' for kinetics in kineticsList] + self.setupKinetics() for kinetics, comments, line_number in zip(kineticsList, commentsList, startLines): try: reaction,revReaction = self.readKineticsEntry(kinetics) diff --git a/test/data/species-names.inp b/test/data/species-names.inp index e6796509c..706cb81dc 100644 --- a/test/data/species-names.inp +++ b/test/data/species-names.inp @@ -2,7 +2,7 @@ Elements H C End SPEC -(Parens) @#$%^-2 [xy2]*{.} +(Parens) @#$%^-2 [xy2]*{.} plus+ eq=uals end thermo @@ -19,9 +19,20 @@ thermo 7.48514950E-02 1.33909467E-02-5.73285809E-06 1.22292535E-09-1.01815230E-13 2 -9.46834459E+03 1.84373180E+01 5.14987613E+00-1.36709788E-02 4.91800599E-05 3 -4.84743026E-08 1.66693956E-11-1.02466476E+04-4.64130376E+00 4 +plus+ C 1H 4 G 200.000 3500.000 1000.000 1 + 7.48514950E-02 1.33909467E-02-5.73285809E-06 1.22292535E-09-1.01815230E-13 2 +-9.46834459E+03 1.84373180E+01 5.14987613E+00-1.36709788E-02 4.91800599E-05 3 +-4.84743026E-08 1.66693956E-11-1.02466476E+04-4.64130376E+00 4 +eq=uals C 1H 4 G 200.000 3500.000 1000.000 1 + 7.48514950E-02 1.33909467E-02-5.73285809E-06 1.22292535E-09-1.01815230E-13 2 +-9.46834459E+03 1.84373180E+01 5.14987613E+00-1.36709788E-02 4.91800599E-05 3 +-4.84743026E-08 1.66693956E-11-1.02466476E+04-4.64130376E+00 4 end reaction (Parens) + @#$%^-2 = 2 [xy2]*{.} 1.2345e12 1.0 200.0 2(Parens) + [xy2]*{.} = 3@#$%^-2 5.4321e10 1.0 500.0 +plus+ + (Parens) = 2plus+ 9.999e9 9.9 999.9 +2 plus+ + eq=uals = 3(Parens) 9.999e9 9.9 999.9 + end diff --git a/test/python/testConvert.py b/test/python/testConvert.py index ffbb5f7af..5d7870e3b 100644 --- a/test/python/testConvert.py +++ b/test/python/testConvert.py @@ -119,15 +119,19 @@ class chemkinConverterTest(utilities.CanteraTest): outName='species-names.cti', quiet=True) gas = ct.IdealGasMix('species-names.cti') - self.assertEqual(gas.nSpecies(), 3) + self.assertEqual(gas.nSpecies(), 5) self.assertEqual(gas.speciesName(0), '(Parens)') self.assertEqual(gas.speciesName(1), '@#$%^-2') self.assertEqual(gas.speciesName(2), '[xy2]*{.}') + self.assertEqual(gas.speciesName(3), 'plus+') + self.assertEqual(gas.speciesName(4), 'eq=uals') - self.assertEqual(gas.nReactions(), 2) + self.assertEqual(gas.nReactions(), 4) nu = gas.productStoichCoeffs() - gas.reactantStoichCoeffs() - self.assertEqual(list(nu[:,0]), [-1, -1, 2]) - self.assertEqual(list(nu[:,1]), [-2, 3, -1]) + self.assertEqual(list(nu[:,0]), [-1, -1, 2, 0, 0]) + self.assertEqual(list(nu[:,1]), [-2, 3, -1, 0, 0]) + self.assertEqual(list(nu[:,2]), [-1, 0, 0, 1, 0]) + self.assertEqual(list(nu[:,3]), [3, 0, 0, -2, -1]) def test_unterminatedSections(self): self.assertRaises(ck2cti.InputParseError,