1"""
2PySCeS - Python Simulator for Cellular Systems (http://pysces.sourceforge.net)
3
4Copyright (C) 2004-2020 B.G. Olivier, J.M. Rohwer, J.-H.S Hofmeyr all rights reserved,
5
6Brett G. Olivier (bgoli@users.sourceforge.net)
7Triple-J Group for Molecular Cell Physiology
8Stellenbosch University, South Africa.
9
10Permission to use, modify, and distribute this software is given under the
11terms of the PySceS (BSD style) license. See LICENSE.txt that came with
12this distribution for specifics.
13
14NO WARRANTY IS EXPRESSED OR IMPLIED.  USE AT YOUR OWN RISK.
15Brett G. Olivier
16"""
17from __future__ import division, print_function
18from __future__ import absolute_import
19from __future__ import unicode_literals
20
21from pysces.version import __version__
22__doc__ = "PySCeS parser module -- uses  PLY 1.5 or newer"
23
24
25import os, copy
26import pysces.lib.lex
27import pysces.lib.yacc
28from getpass import getuser
29from time import sleep, strftime
30
31try:
32    from importlib import reload   # Python 3
33except ImportError:
34    pass
35
36# use net stoichiometry, disable for StomPy
37__USE_NET_STOICH__ = True
38
39RESERVEDTERMS = (\
40     'Vvec',\
41     '__CDvarUpString__',\
42     '__SALL__',\
43     '__SI__',\
44     '__epmatrix__',\
45     '__evmatrix__',\
46     '__fixed_species__',\
47     '__kmatrix__',\
48     '__kzeromatrix__',\
49     '__lmatrix__',\
50     '__lzeromatrix__',\
51     '__mapFunc_R__',\
52     '__mapFunc__',\
53     '__mode_suppress_info__',\
54     '__modifiers__',\
55     '__nmatrix__',\
56     '__nrmatrix__',\
57     '__parameters__',\
58     '__reactions__',\
59     '__s_varFunc__',\
60     '__species__',\
61     '__vFunc__',\
62     'cc_all',\
63     'cc_all_col',\
64     'cc_all_row',\
65     'cc_conc',\
66     'cc_conc_col',\
67     'cc_conc_row',\
68     'cc_flux',\
69     'cc_flux_col',\
70     'cc_flux_row',\
71     'conservation_matrix',\
72     'conservation_matrix_col',\
73     'conservation_matrix_row',\
74     'display_debug',\
75     'eModeExe_dbl',\
76     'eModeExe_int',\
77     'elas_epar_upsymb',\
78     'elas_evar_upsymb',\
79     'elas_par',\
80     'elas_par_col',\
81     'elas_par_row',\
82     'elas_par_u',\
83     'elas_var',\
84     'elas_var_col',\
85     'elas_var_row',\
86     'elas_var_u',\
87     'emode_file',\
88     'emode_intmode',\
89     'emode_userout',\
90     'fintslv_range',\
91     'fintslv_rmult',\
92     'fintslv_step',\
93     'fintslv_tol',\
94     'fixed_species',\
95     'hybrd_epsfcn',\
96     'hybrd_factor',\
97     'hybrd_maxfev',\
98     'hybrd_mesg',\
99     'hybrd_xtol',\
100     'info_flux_conserve',\
101     'info_moiety_conserve',\
102     'init_species_array',\
103     'kel_unscaled',\
104     'kmatrix',\
105     'kmatrix_col',\
106     'kmatrix_row',\
107     'kmatrix_scaled',\
108     'kzeromatrix',\
109     'kzeromatrix_col',\
110     'kzeromatrix_row',\
111     'lmatrix',\
112     'lmatrix_col',\
113     'lmatrix_row',\
114     'lmatrix_scaled',\
115     'lsoda_atol',\
116     'lsoda_h0',\
117     'lsoda_hmax',\
118     'lsoda_hmin',\
119     'lsoda_mesg',\
120     'lsoda_mxordn',\
121     'lsoda_mxords',\
122     'lsoda_mxstep',\
123     'lsoda_rtol',\
124     'lzeromatrix',\
125     'lzeromatrix_col',\
126     'lzeromatrix_row',\
127     'mach_floateps',\
128     'mca_ccall_altout',\
129     'mca_ccall_concout',\
130     'mca_ccall_fluxout',\
131     'mca_ccj_upsymb',\
132     'mca_ccs_upsymb',\
133     'mca_ci',\
134     'mca_ci_col',\
135     'mca_ci_row',\
136     'mca_cjd',\
137     'mca_cjd_col',\
138     'mca_cjd_row',\
139     'mca_csd',\
140     'mca_csd_col',\
141     'mca_csd_row',\
142     'misc_write_arr_lflush',\
143     'mode_eigen_output',\
144     'mode_elas_deriv',\
145     'mode_elas_deriv_dx',\
146     'mode_elas_deriv_factor',\
147     'mode_elas_deriv_min',\
148     'mode_elas_deriv_order',\
149     'mode_mca_scaled',\
150     'mode_number_format',\
151     'mode_sim_init',\
152     'mode_sim_max_iter',\
153     'mode_solver',\
154     'mode_solver_fallback',\
155     'mode_solver_fallback_integration',\
156     'mode_state_init',\
157     'mode_state_init2_array',\
158     'mode_state_init3_factor',\
159     'mode_state_mesg',\
160     'modifiers',\
161     'nleq2_ibdamp',\
162     'nleq2_iormon',\
163     'nleq2_iscal',\
164     'nleq2_iter',\
165     'nleq2_jacgen',\
166     'nleq2_mesg',\
167     'nleq2_mprerr',\
168     'nleq2_nonlin',\
169     'nleq2_qnscal',\
170     'nleq2_qrank1',\
171     'nleq2_rtol',\
172     'nmatrix',\
173     'nmatrix_col',\
174     'nmatrix_row',\
175     'nrmatrix',\
176     'nrmatrix_col',\
177     'nrmatrix_row',\
178     'parameters',\
179     'pitcon_abs_tol',\
180     'pitcon_allow_badstate',\
181     'pitcon_filter_neg',\
182     'pitcon_filter_neg_res',\
183     'pitcon_fix_small',\
184     'pitcon_flux_gen',\
185     'pitcon_init_par',\
186     'pitcon_iter',\
187     'pitcon_jac_opt',\
188     'pitcon_jac_upd',\
189     'pitcon_limit_point_idx',\
190     'pitcon_limit_points',\
191     'pitcon_max_grow',\
192     'pitcon_max_step',\
193     'pitcon_max_steps',\
194     'pitcon_min_step',\
195     'pitcon_output_lvl',\
196     'pitcon_par_opt',\
197     'pitcon_par_space',\
198     'pitcon_rel_tol',\
199     'pitcon_start_dir',\
200     'pitcon_start_step',\
201     'pitcon_targ_val',\
202     'pitcon_targ_val_idx',\
203     'pitcon_target_points',\
204     'reactions',\
205     'scan1_dropbad',\
206     'scan1_mca_mode',\
207     'scan_in',\
208     'scan_out',\
209     'scan_res',\
210     'sim_end',\
211     'sim_points',\
212     'sim_res',\
213     'sim_start',\
214     'sim_time',\
215     'species',\
216     'state_flux',\
217     'state_set_conserve',\
218     'state_species',\
219     'write_array_header',\
220     'write_array_html_footer',\
221     'write_array_html_format',\
222     'write_array_html_header',\
223     'write_array_spacer',\
224     'zero_val',\
225    )
226
227class PySCeSParser:
228    """The original PySCeS parser has been rewritten and extended to include
229    many features that allow for SBML compatibility such as MathML 2.0 support,
230    function definitions, assignment/rate rules and events."""
231
232    MathmlToNumpy_funcs = {
233        'pow' : 'pow', 'root' : 'pow', 'abs' : 'abs',
234        'exp' : 'math.exp', 'ln' : 'math.log', 'log' : 'math.log10',
235        'floor' : 'numpy.floor', 'ceiling' : 'numpy.ceil', 'factorial' : None,
236        'sin' : 'numpy.sin', 'cos' : 'numpy.cos', 'tan' : 'numpy.tan',
237        'sec' : None, 'csc' : None, 'cot' : None,
238        'sinh' : 'numpy.sinh', 'cosh' : 'numpy.cosh','tanh' : 'numpy.tanh',
239        'sech' : None, 'csch' : None, 'coth' : None,
240        'arcsin' : 'numpy.arcsin', 'arccos' : 'numpy.arccos', 'arctan' : 'numpy.arctan',
241        'arcsec' : None, 'arccsc' : None, 'arccot' : None,
242        'arcsinh' : 'numpy.arcsinh', 'arccosh' : 'numpy.arccosh', 'arctanh' : 'numpy.arctanh',
243        'arcsech' : None, 'arccsch' : None, 'arccoth' : None,
244        'eq' : 'operator.eq', 'neq' : 'operator.ne',
245        'gt' : 'operator.gt', 'geq' : 'operator.ge',
246        'lt' : 'operator.lt', 'leq' : 'operator.le',
247        'ceil' : 'numpy.ceil', 'sqrt' : 'numpy.sqrt',        # libsbml aliases
248        'equal' : 'operator.eq', 'not_equal' : 'operator.ne',   # numpy2numpy aliases
249        'greater' : 'operator.gt', 'greater_equal' : 'operator.ge', # numpy2numpy aliases
250        'less' : 'operator.lt', 'less_equal' : 'operator.le', # numpy2numpy aliases
251        'ne' : 'operator.ne', 'ge' : 'operator.ge', 'le' : 'operator.le', # operator2operator
252        'xor' : 'operator.xor', 'piecewise' : 'self._piecewise_', '_piecewise_' : 'self._piecewise_',
253        'not' : 'operator.not_', 'not_' : 'operator.not_',
254        'max':'max', 'min' : 'min'
255    }
256
257    MathmlToNumpy_symb = {
258        'notanumber' : 'numpy.NaN', 'pi' : 'numpy.pi',
259        'infinity' : 'numpy.Infinity', 'exponentiale' : 'numpy.e',
260        'true' : 'True', 'false' : 'False', 'True' : 'True', 'False' : 'False'
261    }
262
263    SymbolReplacements = None
264
265    MathmlToInfix = {
266        'and' : 'and', 'or' : 'or', 'true' : 'True', 'false' : 'False', 'xor' : 'xor'
267    }
268
269    BOOLEANTRUE = ('True','TRUE','true')
270    BOOLEANFALSE = ('False','False','false')
271
272    precedence = (
273        ('left',  'PLUS', 'MINUS'),
274        ('left',  'TIMES', 'DIVIDE'),
275        ('left',  'POWER'),
276        ('right', 'UMINUS')
277        )
278
279    # List of token names
280    tokens = ('FIXDEC',
281              'FUNCDEC',
282              'EVENTDEC',
283              'COMPDEC',
284              'RRULEDEC',
285              'OBJFUNCDEC',
286              'FLUXBNDDEC',
287              'USERCNSTRDEC',
288              'IRREV',
289              'REAL',
290              'INT',
291              'PLUS',
292              'MINUS',
293              'TIMES',
294              'DIVIDE',
295              'POWER',
296              'LPAREN',
297              'RPAREN',
298              'EQUALS',
299              'SYMBEQUI',
300              'COMMA',
301              'REACTION_ID',
302              'STOICH_COEF',
303              'NAME',
304              'FORCEFUNC',
305              'TIMEFUNC',
306              'USERFUNC',
307              'INITFUNC',
308              'IN',
309              'POOL',
310              'KEYWORD',
311              'KEYWORDB',
312              'UNIT',
313              'MULTICOMMENT')
314
315    ReservedTerms = RESERVEDTERMS
316
317#################################################################
318#################################################################
319
320    def __init__(self,debug=0):
321        self.display_debug = debug
322        # Lists
323        self.LexErrors = []   # List of lexing errors
324        self.ParseErrors = []
325        self.SymbolErrors = []
326        self.ParseOK = True
327        self.LexOK = True
328        self.ModelUsesNumpyFuncs = False
329
330        self.ReactionIDs = [] # List of reaction names
331        self.Names = []       # List of all reagent, parameter and function names
332        self.nDict = {}    # Dictionary containing all reaction information
333        self.InDict = {}         # spatial dictionary
334        self.sDict = {}
335        self.pDict = {}    #parameter dict
336        self.uDict = {'substance': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'mole'},
337                       'volume': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'litre'},
338                       'time': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'second'},
339                       'length': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'},
340                       'area': {'exponent': 2, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'}
341                      }
342        self.KeyWords = {'Modelname' : None,
343                         'Description' : None,
344                         'Species_In_Conc' : None,
345                         'Output_In_Conc' : None
346                         }
347        self.compartments = {}
348        self.InitStrings = []    # Initialisation strings
349        self.Inits = []          # Initialised entities
350        self.Reagents = []       # All reagents found during parsing of reactions
351        self.species = []    # Variable reagents that occur in reactions
352        self.FixedReagents = []  # Fixed reagents
353        self.ReacParams = []     # Temporary list of reaction parameters
354        self.InitParams = []     # Initialised parameters
355
356        self.ForceFunc = []        # Forcing function definition
357        self.TimeFunc = []    # Time function definition
358        self.UserFunc = []    # User function definition
359        self.InitFunc = []    # Intitialization function definition
360        self.Functions = {}   # new Function blocks
361        self.Events = {}       # new event blocks
362        self.AssignmentRules = {}   # new forcing function blocks
363        self.UserFuncs = {}   # new user function blocks
364        self.ModelInit = {}   # new model initialisation blocks
365        self.cbm_FluxBounds = {}  # flux bounds in a CB model
366        self.cbm_ObjectiveFunctions = {} # objective functions for a CB model
367        self.cbm_UserFluxConstraints = {} # objective functions for a CB model
368
369
370        self.mach_spec_eps2k = 2.2204460492503131e-14
371        self.AllRateEqsGiven = 1 # Flag to check that all rate equations have been given
372        self.Debug = 0
373
374        # elementary regular expressions used as building blocks
375        self.Int = r'\d+'                                      # Integer
376        self.Dec = self.Int + '\.' + self.Int                            # Decimal
377        self.Exp = r'([E|e][\+|\-]?)' + self.Int                    # Exponent
378        self.Real = self.Dec  + '(' + self.Exp + ')?' + '|' + self.Int + self.Exp  # Real - dec w/o optional exp or int with exp
379
380        # Simple tokens
381        self.t_IRREV = r'>'
382        self.t_REAL = self.Real
383        self.t_INT = self.Int
384        self.t_PLUS = r'\+'
385        self.t_MINUS = r'-'
386        self.t_TIMES = r'\*'
387        self.t_DIVIDE = r'/'
388        self.t_POWER = '\*\*'
389        self.t_LPAREN = r'\('
390        self.t_RPAREN = r'\)'
391        self.t_EQUALS = r'='
392        self.t_COMMA = r','
393        self.t_POOL = r'\$pool'
394        self.t_IN = r'@'
395
396    t_ignore = ' \t\r'    # Ignore spaces and tabs --- and windows return - brett 20040229
397
398    def t_comment(self,t):
399        r'\#.+\n'       # Match from # to newline
400        t.lineno += 1   # Increment line number
401
402    def t_multilinecomment(self,t):
403        r'"""(.|\n)*?"""'
404        t.type = 'MULTICOMMENT'
405
406    def t_newline(self,t):
407        r'\n+'          # Match newline
408        t.lineno += len(t.value) # Increment with number of consecutive newlines
409
410    def t_SYMBEQUI(self,t):
411        r'!=|<'
412        t.type = 'SYMBEQUI'
413        print('t', t)
414        return t
415
416    def t_FIXDEC(self,t):
417        r'FIX:'
418        t.type = 'FIXDEC'
419        t.value = 'FIX:'
420        return t
421
422    def t_KW_Name(self, t):
423        r'Modelname:.+\n'
424        t.type = 'KEYWORD'
425        return t
426
427    def t_KW_Description(self, t):
428        r'Description:(.| )+\n'
429        t.type = 'KEYWORD'
430        return t
431
432    def t_KW_ModelType(self, t):
433        r'ModelType:(.| )+\n'
434        t.type = 'KEYWORD'
435        return t
436
437    def t_KW_Species_In_Conc(self, t):
438        r'Species_In_Conc:.+\n'
439        t.type = 'KEYWORDB'
440        return t
441
442    def t_KW_Output_In_Conc(self, t):
443        r'Output_In_Conc:.+\n'
444        t.type = 'KEYWORDB'
445        return t
446
447    def t_Unit(self, t):
448        r'Unit(Substance|Time|Length|Area|Volume):.+\n'
449        t.type = 'UNIT'
450        return t
451
452    def t_FUNCDEC(self, t):
453        r'Function:(.|\n)*?}'
454        t.type = 'FUNCDEC'
455        return t
456
457    def t_RATERULEDEC(self, t):
458        r'RateRule:(.|\n)*?}'
459        t.type = 'RRULEDEC'
460        return t
461
462    def t_EVENTDEC(self, t):
463        r'Event:(.|\n)*?}'
464        t.type = 'EVENTDEC'
465        return t
466
467    def t_OBJFUNCDEC(self,t):
468        r'ObjectiveFunctions:(.|\n)*?}'
469        t.type = 'OBJFUNCDEC'
470        return t
471
472    def t_FLUXBNDDEC(self,t):
473        r'FluxBounds:(.|\n)*?}'
474        t.type = 'FLUXBNDDEC'
475        return t
476
477    def t_USERCNSTRDEC(self,t):
478        r'UserFluxConstraints:(.|\n)*?}'
479        t.type = 'USERCNSTRDEC'
480        return t
481
482
483    def t_COMPDEC(self, t):
484        r'Compartment:.+\n' # match from cc<space> to end of line
485        t.type = 'COMPDEC'
486        return t
487
488    def t_FORCEFUNC(self,t):
489        r'!F\ .+\n' # match from !F<space> to end of line
490        t.type = 'FORCEFUNC'
491        t.lineno += 1   # Increment line number
492        t.value = t.value[3:]
493        return t
494
495    def t_TIMEFUNC(self,t):
496        r'!T\ .+\n' # match from !T<space> to end of line
497        t.type = 'TIMEFUNC'
498        t.lineno += 1   # Increment line number
499        t.value = t.value[3:]
500        return t
501
502    def t_USERFUNC(self,t):
503        r'!U\ .+\n' # match from !U<space> to end of line
504        t.type = 'USERFUNC'
505        t.lineno += 1   # Increment line number
506        t.value = t.value[3:]
507        return t
508
509    def t_INITFUNC(self,t):
510        r'!I\ .+\n' # match from !I<space> to end of line
511        t.type = 'INITFUNC'
512        t.lineno += 1   # Increment line number
513        t.value = t.value[3:]
514        return t
515
516    def t_REACTION_ID(self,t):
517        r'[a-zA-Z_][\w@]*:' # Match any letter in [a-zA-Z0-9_] up to a colon
518                            # allow "_" to startswith - brett 20050823
519        t.type = 'REACTION_ID'
520        t.value = t.value[:-1]  # remove the colon
521
522        #print t.value, self.ReactionIDs
523        #sleep(1)
524        if '@' in t.value:
525            ts = t.value.split('@')
526            t.value = ts[0]
527            self.InDict.update({ts[0] : ts[1]})
528        if t.value in self.ReactionIDs:
529            # brett I think this is hyperactive reporting ... removing 20050321
530            # self.LexErrors.append(('Duplicate ReactionID ', t.lineno, t.value, t.type))
531            pass
532        else:
533            self.ReactionIDs.append(t.value)
534        # possible alternative to above - brett
535        #if t.value not in self.ReactionIDs:
536        #    self.ReactionIDs.append(t.value)
537        return t
538
539    def t_STOICH_COEF(self,t):
540        r'\{[\d+|\.|E|e|\+|\-]+\}'
541
542        # old regex upgraded to handle anything that resembles a number, might cause downstream problems
543        # r'\{\d+\}|\{\d+\.\d+\}'
544        t.type = 'STOICH_COEF'
545        t.value = t.value[1:-1]  # Remove left and right curly brackets
546        return t
547
548    def t_NAME(self,t):
549        r'numpy\.[\w]*|math\.[\w]*|operator\.[\w]*|random\.[\w]*|[a-zA-Z_][\w@]*'
550        SciCons = False
551        if '@' in t.value:
552            ts = t.value.split('@')
553            t.value = ts[0]
554            self.InDict.update({ts[0] : ts[1]})
555        if t.value in self.MathmlToNumpy_symb:
556            if self.MathmlToNumpy_symb[t.value] == None:
557                self.SymbolErrors.append(t.value)
558                print('\nSymbol \"%s\" not yet supported by PySCeS.' % t.value)
559                gt = 'unknown_symbol_' + t.value
560                t.value = gt
561            else:
562                gt = self.MathmlToNumpy_symb[t.value]
563                t.value = gt
564            self.ModelUsesNumpyFuncs = 1
565            SciCons = True
566        elif self.SymbolReplacements != None and t.value in self.SymbolReplacements:
567            if t.value not in self.Names:
568                self.Names.append('self.' + self.SymbolReplacements[t.value])
569            gt = 'self.' +  self.SymbolReplacements[t.value]
570            t.value = gt
571        elif t.value not in self.Names and t.value != '_TIME_': # Only add to list if absent in list
572            self.Names.append('self.' + t.value)
573        if t.value[:6] == 'numpy.' or t.value[:5] == 'math.' or t.value[:9] == 'operator.' or t.value[:7] == 'random.':
574            pass
575        elif t.value not in self.MathmlToNumpy_funcs and not SciCons: # make class attributes, ignore function names
576            gt = 'self.' + t.value
577            t.value = gt
578        t.type = 'NAME'
579        return t
580
581    def t_error(self,t):
582        ##  try:
583            ##  self.LexErrors.append(('Lexer error ', t.lineno, t.value, t.type))
584        ##  except:
585            ##  print 'Lexer error'
586        ##  #print 'Illegal character, Line ' + str(t.lineno) + ' :' + str(t.value[0])
587        ##  t.skip(1)
588
589        print("Illegal character '%s'" % t.value[0])
590        self.LexErrors.append(t.value[0])
591        self.LexOK = False
592        t.lexer.skip(1)
593
594    ##############
595    # The parser #
596    ##############
597
598    def Show(self,name,tok):
599        if self.Debug:
600            print(name,tok)
601
602    def p_error(self,t):
603        try:
604            ##  self.ParseErrors.append(('Syntax error ', t.lineno, t.value, t.type))
605            self.ParseErrors.append(t)
606        except:
607            print('p_error generated a parsing error')
608        tok = pysces.lib.yacc.token()
609        while tok and tok.type != 'REACTION_ID':
610            tok = pysces.lib.yacc.token()
611        self.ParseOK = False
612        return tok
613
614    def p_model(self,t):
615        '''Model : Statement
616                 | Model Statement '''
617
618        self.Show('Model',t[0])
619
620    def p_statement(self,t):
621        '''Statement : Fixed
622                     | FunctionDec
623                     | RateRuleDec
624                     | EventDec
625                     | ObjFuncDec
626                     | FluxBndDec
627                     | UserCnstrDec
628                     | CompartmentDec
629                     | ReactionLine
630                     | Initialise
631                     | Forcedfunc
632                     | Timefunc
633                     | Userfunc
634                     | Initfunc
635                     | NameInName
636                     | KeyWord
637                     | KeyWordB
638                     | Unit
639                     | MultiComment
640                     | SymbEqui'''
641        self.Show('Statement',t[0])
642
643    def p_nameinname(self, t):
644        '''NameInName : NAME IN NAME'''
645        print('This should never fire, what we need to do is ...')
646
647    def p_inequalities_symb(self, t):
648        '''SymbEqui : SYMBEQUI'''
649        print('p_inequalities_symb', t[:])
650        t[0] = t[1]
651
652    def p_multicomment(self, t):
653        '''MultiComment : MULTICOMMENT'''
654        self.Show('KeyWord:',t[0])
655
656    def p_keyword(self, t):
657        '''KeyWord : KEYWORD'''
658        ##  print 'KeyWord:', t[:]
659        kpair = [e.strip() for e in t[1].split(':')]
660        if kpair[0] in self.KeyWords:
661            self.KeyWords.update({kpair[0] : kpair[1]})
662        ##  print self.KeyWords
663        self.Show('KeyWord:',t[0])
664
665    def p_keywordboolean(self, t):
666        '''KeyWordB : KEYWORDB'''
667        ##  print 'KeyWordB:', t[:]
668        kpair = [e.strip() for e in t[1].split(':')]
669        if kpair[0] in self.KeyWords:
670            if kpair[1] in self.BOOLEANTRUE:
671                self.KeyWords.update({kpair[0] : True})
672            elif kpair[1] in self.BOOLEANFALSE:
673                self.KeyWords.update({kpair[0] : False})
674        ##  print self.KeyWords
675        self.Show('KeyWordB:',t[0])
676
677    def p_unit(self, t):
678        '''Unit : UNIT'''
679        ##  print 'u', t[:]
680        u = t[1].split(',')
681        u[0] = u[0].split(':')
682        u.append(u[0][1])
683        u[0] = u[0][0].lower()
684        ##  print u
685        u[0] = u[0].replace('unit','')
686        ##  print u
687        for i in range(len(u)):
688            u[i] = u[i].strip()
689            if i in [1,2,3]:
690                u[i] = float(u[i])
691        ##  print u
692        self.uDict.update({u[0] : {'multiplier': u[1],
693                                    'scale': u[2],
694                                    'exponent': u[3],
695                                    'kind': u[4]
696                                    }
697                           })
698        self.Show('Unit:',t[0])
699
700    def p_fixed(self,t):
701        '''Fixed : FIXDEC FixList'''
702        self.Show('Fixed:',t[0])
703
704    def p_functiondec(self, t):
705        '''FunctionDec : FUNCDEC'''
706        ##  print 'p_functiondec', t[:]
707        rawf = t[1].replace('Function:','').lstrip()
708        args = rawf[:rawf.find('{')].strip().split(',')
709        name = args.pop(0)
710        func = rawf[rawf.find('{')+1:rawf.find('}')]
711        self.Functions.update({name : {
712                                      'name' : name,
713                                      'args' : args,
714                                      'formula' : func
715                                    }
716                            })
717        self.Show('FunctionDec:',t[0])
718
719    def p_rateruledec(self, t):
720        '''RateRuleDec : RRULEDEC'''
721        ##  print 'p_functiondec', t[:]
722        rawf = t[1].replace('RateRule:','').lstrip()
723        name = rawf[:rawf.find('{')].strip()
724        func = rawf[rawf.find('{')+1:rawf.find('}')]
725        self.AssignmentRules.update({name : {'name' : name,
726                                              'formula' : func.strip(),
727                                              'type' : 'rate'
728                                            }
729                                    })
730        self.Show('RateRuleDec:',t[0])
731
732    def p_eventdec(self, t):
733        '''EventDec : EVENTDEC'''
734        rawf = t[1].replace('Event:','').lstrip()
735        args = rawf[:rawf.find('{')].strip().split(',')
736        name = args.pop(0)
737        delay = float(args.pop(-1))
738        trigger = ''
739        for a in args:
740            trigger = trigger + a + ','
741        trigger = trigger[:-1]
742
743        rawF = rawf[rawf.find('{')+1:rawf.find('}')].split('\n')
744        assignments = {}
745        for ass in rawF:
746            if len(ass.strip()) > 0:
747                ass = ass.split('=')
748                assignments.update({ass[0].strip() : ass[1].strip()})
749        self.Events.update({name : { 'delay' : delay,
750                                      'name' : name,
751                                      'trigger' : trigger,
752                                      'assignments' : assignments,
753                                      'tsymb' : None
754                                    }
755                            })
756        self.Show('EventDec:',t[0])
757
758    def p_objfuncdec(self, t):
759        '''ObjFuncDec : OBJFUNCDEC'''
760        rawf = t[1].replace('ObjectiveFunctions:','').lstrip()
761        args = rawf[:rawf.find('{')].strip().split(',')
762        rawf = [r.strip() for r in rawf[rawf.find('{')+1:rawf.find('}')].split('\n')]
763        objectives = {}
764        for oo in rawf:
765            print(rawf)
766            print(oo)
767            if len(oo.strip()) > 0:
768                oS = [t.strip() for t in oo.split(':')]
769                active = False
770                if oS[0] == args[0]:
771                    active = True
772                objectives.update({oS[0] : {'id' : oS[0],
773                                             'active' : active,
774                                             'osense' : oS[1],
775                                             'fluxes' : oS[2]}
776                                            })
777        self.cbm_ObjectiveFunctions.update(objectives)
778        self.Show('ObjFuncDec:',t[0])
779
780    def p_fluxbnddec(self, t):
781        '''FluxBndDec : FLUXBNDDEC'''
782        rawf = t[1].replace('FluxBounds:','').lstrip()
783        rawf = [r.strip() for r in rawf[rawf.find('{')+1:rawf.find('}')].split('\n')]
784
785        fluxbnds = {}
786        cntr = 0
787        for oo in rawf:
788            if len(oo.strip()) > 0:
789                operator = None
790                lhs = None
791                rhs = None
792                fid = 'fbnd_%i' % cntr
793                for oper in ['>=','<=','>','<','=']:
794                    if  oper in oo:
795                        bnd = oo.split(oper)
796                        operator = oper
797                        lhs = bnd[0].strip()
798                        rhs = float(bnd[1].strip())
799                        break
800                fluxbnds.update({fid : {'id' : fid,
801                                           'flux' : lhs,
802                                           'rhs' : rhs,
803                                           'operator' : operator}
804                                          })
805                cntr += 1
806        self.cbm_FluxBounds.update(fluxbnds)
807        self.Show('FluxBndDec:',t[0])
808
809    def p_usercnstrdec(self, t):
810        '''UserCnstrDec : USERCNSTRDEC'''
811        rawf = t[1].replace('UserFluxConstraints:','').lstrip()
812        rawf = [r.strip() for r in rawf[rawf.find('{')+1:rawf.find('}')].split('\n')]
813
814        fluxcnstr = {}
815        for oo in rawf:
816            if len(oo.strip()) > 0:
817                oS = [t.strip() for t in oo.split(':')]
818                id = oS[0]
819                operator = None
820                lhs = None
821                rhs = None
822                for oper in ['>=','<=','>','<','=']:
823                    if  oper in oS[1]:
824                        cnstr = oS[1].split(oper)
825                        operator = oper
826                        lhs = [c.strip() for c in cnstr[0].split(',')]
827                        lhs2 = []
828                        for c2 in lhs:
829                            c = c2.split(' ')
830                            if len(c) == 1:
831                                coeff = '+1'
832                                val = c[0]
833                            else:
834                                coeff = c[0]
835                                val = c[1]
836                            lhs2.append((float(coeff),val))
837                        rhs = float(cnstr[1].strip())
838                        break
839                fluxcnstr.update({oS[0] : {'id' : oS[0],
840                                             'constraint' : tuple(lhs2),
841                                             'rhs' : rhs,
842                                             'operator' : operator}
843                                            })
844        self.cbm_UserFluxConstraints.update(fluxcnstr)
845        self.Show('UserCnstrDec:',t[0])
846
847
848
849
850        self.Show('UserCnstrDec:',t[0])
851
852
853
854
855
856    def p_compartmentdec(self,t):
857        '''CompartmentDec : COMPDEC'''
858        rawf = t[1].strip().replace('Compartment:','')
859        strAr = [st.strip() for st in rawf.split(',')]
860        if len(strAr) <= 3:
861            name = strAr[0]
862            size = strAr[1]
863            dimensions = strAr[2]
864        if len(strAr) == 4:
865            area = strAr[3]
866        else:
867            area = None
868
869        self.compartments.update({name:{'name':name,
870                                         'size': float(size),
871                                         'dimensions' : int(dimensions),
872                                         'compartment': None,
873                                         'area' : None
874                                         ##  'volume' : None
875                                          }})
876
877    def p_forcedfunc(self,t):
878        '''Forcedfunc : FORCEFUNC'''
879        self.ForceFunc.append(t[1])
880        ar = t[1].split('=')
881        name = ar[0].replace('self.','').strip()
882        func = ar[1].replace('self.','').strip()
883        self.AssignmentRules.update({name : {'name' : name,
884                                              'formula' : func,
885                                              'type' : 'assignment'
886                                            }
887                                    })
888        self.Show('Forcedfunc:',t[0])
889
890    def p_timefunc(self,t):
891        '''Timefunc : TIMEFUNC'''
892        self.TimeFunc.append(t[1])
893        self.Show('Timefunc:',t[0])
894
895    def p_userfunc(self,t):
896        '''Userfunc : USERFUNC'''
897        self.UserFunc.append(t[1])
898        ar = t[1].split('=')
899        name = ar[0].replace('self.','').strip()
900        func = ar[1].replace('self.','').strip()
901        self.UserFuncs.update({name : {'name' : name,
902                                            'formula' : func,
903                                            'type' : 'userfuncs'}
904                                    })
905        self.Show('Userfunc:',t[0])
906
907    def p_initfunc(self,t):
908        '''Initfunc : INITFUNC'''
909        self.InitFunc.append(t[1])
910        ar = t[1].split('=')
911        name = ar[0].replace('self.','').strip()
912        value = ar[1].replace('self.','').replace('\'','').replace('"','').strip()
913        self.ModelInit.update({name : value})
914        self.Show('Initfunc:',t[0])
915
916    def p_fixedreagents(self,t):
917        '''FixList : NAME
918                   | NAME FixList'''
919        if t[1] != None:
920            self.FixedReagents.append(t[1])
921        t[0] = [t[1]]
922        try:
923            t[0] += t[2]
924        except:
925            pass
926        self.Show('FixList', t[0])
927
928    # TODO:
929    def p_initialise(self,t):
930        '''Initialise : NAME EQUALS Expression'''
931        value = None
932        name = t[1].replace('self.','')
933        try:
934            value = eval(t[3])
935            # 20090402 we need to keep track of species parameter initialisations and then
936            # perform a lookup or implement a proxy class that can store
937            # evaluated assignments
938        except Exception as ex:
939            print('Initialisation error: %s' % t[3])
940            print(ex)
941
942        self.sDict.update({name : {'name' : name,
943                                    'initial' : value
944                                   }
945                           })
946        t[0] = t[1] + t[2] + t[3]
947
948        self.InitStrings.append(t[0])
949        self.Inits.append(t[1])
950        self.Show('Initialisation',t[0])
951
952    def p_reaction_line(self,t):
953        '''ReactionLine : REACTION_ID ReactionEq
954                        | REACTION_ID ReactionEq Expression'''
955
956        #global AllRateEqsGiven, self.ReacParams
957        ReacID = t[1]
958        if ReacID in self.nDict:
959            self.ParseErrors.append(('Duplicate Reaction ', t.lineno, ReacID, None))
960        self.nDict[ReacID] = {} # Reaction dictionary for ReacID
961        ##  self.nDict[ReacID]['Reagents'] = {} # Reagent dictionary within ReacID
962        self.nDict[ReacID].update({'Reagents' : {}, 'name' : ReacID})
963        # a list of all reagents specified in the stoichiometry, unmodified and not taken into consideration for anything
964        self.nDict[ReacID].update({'AllReagents' : t[2][0]})
965
966        # brett: if an index exists sum the coefficients instead of adding a new one
967        # this seems to deal with multiple definitions like X + X > Y and 2{X} + Y > Z + X
968        for i in t[2][0]: # First tuple member of ReactionEq contains list of (name,stoichcoef)
969            if i[0] in self.nDict[ReacID]['Reagents']:
970                self.nDict[ReacID]['Reagents'][i[0]] = self.nDict[ReacID]['Reagents'][i[0]] + i[1]
971            else:
972                self.nDict[ReacID]['Reagents'][i[0]] = i[1] # Key for reagent with stoichcoef value
973        killList = []
974        if __USE_NET_STOICH__:
975            # brett: however for the case of X + Y > Y + Z where the sum of the coefficients
976            # is zero we can delete the key (Y) out of the reaction list altgether (hopefully!)
977            for i in self.nDict[ReacID]['Reagents']:
978                if abs(self.nDict[ReacID]['Reagents'][i]) < self.mach_spec_eps2k:
979                    killList.append(i)
980                    #print self.mach_spec_eps2k, self.nDict[ReacID]['Reagents']
981            #print killList, self.nDict[ReacID]['Reagents']
982            # brett: and the easiest way of doing this is putting the zero keys in a list
983            # and deleting them out of the dictionary
984            if len(killList) != 0:
985                for i in killList:
986                    del self.nDict[ReacID]['Reagents'][i]
987            #print killList, self.nDict[ReacID]['Reagents']
988
989        self.nDict[ReacID]['Type'] = t[2][1] # Second tuple member of ReactionEq contains type
990        try: # Save rate equation and create parameter list
991            self.nDict[ReacID]['RateEq']   = t[3]
992            self.nDict[ReacID]['Params']   = self.ReacParams
993            self.ReacParams = [] # Reset global self.ReacParams list
994        except:
995            self.nDict[ReacID]['RateEq']   = ''
996            self.nDict[ReacID]['Params']   = []
997            self.AllRateEqsGiven = 0 # Set global flag to false
998        self.Show('ReactionLine',t[0])
999
1000    def p_reaction_eq(self,t):
1001        '''ReactionEq : LeftHalfReaction EQUALS RightHalfReaction
1002                      | LeftHalfReaction IRREV  RightHalfReaction
1003                      | POOL EQUALS  RightHalfReaction
1004                      | POOL IRREV  RightHalfReaction
1005                      | LeftHalfReaction EQUALS POOL
1006                      | LeftHalfReaction IRREV POOL'''
1007
1008        ReacType = ''
1009        if   t[2] == '=':
1010            ReacType = 'Rever'
1011        elif t[2] == '>':
1012            ReacType = 'Irrev'
1013
1014        # Yeah well you know the story ... 4 hrs of trying the "cool, other" way of doing it at work
1015        # and then solving the original problem in 90 mins with 14 lines of code at home ...
1016        # oh almost forgot: anonymous source and sink pools now work in PySCeS - brett 20050908
1017        if t[1] == '$pool':
1018            t[0] = (t[3], ReacType)
1019        elif t[3] == '$pool':
1020            t[0] = (t[1], ReacType)
1021        else:
1022            t[0] = (t[1] + t[3], ReacType)
1023        #print 'reaction_eq',t[0]
1024        self.Show('ReactionEq',t[0])
1025
1026    def p_left_half_reaction(self,t):
1027        ''' LeftHalfReaction : SubstrateTerm
1028                             | SubstrateTerm PLUS LeftHalfReaction'''
1029        # Make a list of substrate terms
1030        t[0] = [t[1]]
1031        try:
1032            t[0] += t[3]
1033        except:
1034            pass
1035        self.Show('LeftHalfReaction', t[0])
1036
1037    def p_right_half_reaction(self,t):
1038        ''' RightHalfReaction : ProductTerm
1039                              | ProductTerm PLUS RightHalfReaction'''
1040
1041        t[0] = [t[1]]
1042
1043        try:
1044            t[0] += t[3]
1045        except:
1046            pass
1047        self.Show('RightHalfReaction', t[0])
1048
1049    def p_substrate_term(self,t):
1050        '''SubstrateTerm : STOICH_COEF NAME
1051                         | NAME'''
1052
1053        # Make tuple of NAME and stoichiometric coefficient
1054        # (< 0 because substrate)
1055        try:
1056            t[0] = (t[2], -float(t[1]))
1057            if t[2] not in self.Reagents:# and t[2] != 'self.pool':
1058                self.Reagents.append(t[2])
1059        except:
1060            t[0] = (t[1], -1.0)
1061            if t[1] not in self.Reagents:#  and t[1] != 'self.pool':
1062                self.Reagents.append(t[1])
1063        self.Show('SubstrateTerm', t[0])
1064
1065    def p_product_term(self,t):
1066        '''ProductTerm : STOICH_COEF NAME
1067                       | NAME'''
1068        # Make tuple of NAME and stoichiometric coefficient
1069        # (> 0 because product)
1070        try:
1071            t[0] = (t[2], float(t[1]))
1072            if t[2] not in self.Reagents:# and t[2] != 'self.pool':
1073                self.Reagents.append(t[2])
1074        except:
1075            t[0] = (t[1], 1.0)
1076            if t[1] not in self.Reagents:# and t[1] != 'self.pool':
1077                self.Reagents.append(t[1])
1078        self.Show('ProductTerm', t[0])
1079
1080    def p_rate_eq(self,t):
1081        '''Expression : Expression PLUS Expression
1082                      | Expression MINUS Expression
1083                      | Expression TIMES Expression
1084                      | Expression DIVIDE Expression
1085                      | Power
1086                      | Number
1087                      | Func'''
1088                    # |UMINUS : add if the
1089                    #  alternative for p_uminus is used
1090
1091        if len(t.slice)==4:
1092            t[0] = t[1] + t[2] + t[3]
1093        else:
1094            t[0] = t[1]
1095
1096    def p_power(self,t):
1097        '''Power : Expression POWER Expression'''
1098
1099        t[0] = 'pow('+ t[1] + ',' + t[3] + ')' #changed to make it DeriVar compatible
1100        ##  t[0] = t[1] + '**' + t[3]
1101
1102    def p_uminus(self,t):
1103        '''Expression : MINUS Expression %prec UMINUS'''
1104        # Alternative '''UMINUS : MINUS Expression'''
1105
1106        t[0] = t[1] + t[2]
1107
1108    def p_number(self,t):
1109        '''Number : REAL
1110                  | INT
1111                  | NAME'''
1112
1113    # Build list of entities
1114        ttype = 'param'
1115        try:
1116            tx = float(t[1])
1117            ttype = 'float'
1118        except ValueError as v:
1119            pass
1120            ##  try:
1121                ##  t[1] = complex(t[1])
1122                ##  ttype = 'complex'
1123            ##  except:
1124                ##  pass
1125        if ttype in ['complex','float']:
1126            t[1] = str(tx)
1127        else:
1128            # if this is not a number add it as a parameter EXCEPT if it is a function's
1129            # name or numpy.constant
1130            if (t[1] not in self.ReacParams) and\
1131                (t[1].replace('numpy.','').replace('math.','').replace('operator.','') not in self.MathmlToNumpy_funcs) and\
1132                (t[1].replace('numpy.','').replace('math.','').replace('operator.','') not in self.MathmlToNumpy_symb): # ignore function names and duplications
1133                self.ReacParams.append(t[1])
1134        t[0] = t[1]
1135
1136     # new Core2 based InfixParser code
1137    def p_function(self,t):
1138        '''Func : LPAREN ArgList RPAREN
1139                | NAME LPAREN ArgList RPAREN'''
1140
1141        # convert root(degree,<expr>) to pow(<expr>, 1/degree)
1142        if t[1].strip() == 'root':
1143            t[1] = self.MathmlToNumpy_funcs[t[1]]
1144            t[3] = '%s, %s' % (t[3][t[3].index(',')+1:], 1.0/float(t[3][:t[3].index(',')])  )
1145            t[0] = t[1] + t[2] + t[3] + t[4]
1146        elif t[1] in self.MathmlToNumpy_funcs:
1147            if self.MathmlToNumpy_funcs[t[1]] == None:
1148                self.SymbolErrors.append(t[1])
1149                print('\nFunction \"%s\" not supported by PySCeS' % t[1])
1150                t[0] = 'unknown_function_'+t[1] + t[2] + t[3] + t[4]
1151            else:
1152                try:
1153                    t[0] = self.MathmlToNumpy_funcs[t[1]] + t[2] + t[3] + t[4]
1154                except Exception as EX:
1155                    print('Function Parse error 1 (please report!)\n', EX)
1156            self.ModelUsesNumpyFuncs = True
1157        # differentiate between bracketed functions and expressions:
1158        # func( S1 ) and ( S/S05 )
1159        elif len(t) == 4:
1160            t[0] = t[1] + t[2] + t[3]
1161        else:
1162            # assume some arbitrary function definition
1163            if t[1][:6] == 'numpy.' or t[1][:5] == 'math.' or t[1][:9] == 'operator.': # NEW UNTESTED
1164                t[0] = t[1] + t[2] + t[3] + t[4]
1165            else:
1166                t[0] = t[1] + t[2] + t[3] + t[4]
1167
1168    # arbitrary number of arguments in an argument list
1169    # adapted from Andrew Dalke's GardenSnake
1170    # http://www.dalkescientific.com/writings/diary/GardenSnake.py
1171    def p_arglist(self,t):
1172        '''ArgList : Expression
1173                   | ArgList COMMA Expression'''
1174        try:
1175            if len(t) == 2:
1176                t[0] = t[1]
1177            elif len(t) == 4:
1178                t[0] = t[1] + ',' + t[3]
1179            else:
1180                pass
1181        except Exception as EX:
1182            print('Function ArgList error (please report!)\n', EX)
1183
1184###################################################################
1185###################################################################
1186
1187    def ParsePSC(self,modelfile,modeldir,modeloutput):
1188        """
1189        ParsePSC(modelfile,modeldir,modeloutput)
1190
1191        Parse the .psc file into a Network Dictionary and associated property arrays
1192
1193        modelfile: PSC filename
1194        modeldir: PSC input directory
1195        modeloutput: working directory for lex/parse temporary files
1196
1197        """
1198        # we clear stuff so we have a shiny new instance
1199        self.nDict = {}    # Dictionary containing all reaction information
1200        self.InDict = {}
1201        self.sDict = {}
1202        self.pDict = {}    #parameter dict
1203        self.uDict = {'substance': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'mole'},
1204                       'volume': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'litre'},
1205                       'time': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'second'},
1206                       'length': {'exponent': 1, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'},
1207                       'area': {'exponent': 2, 'multiplier': 1.0, 'scale': 0, 'kind': 'metre'}
1208                      }
1209        self.KeyWords = {'Modelname' : None,
1210                         'Description' : None,
1211                         'Species_In_Conc' : None,
1212                         'Output_In_Conc' : None,
1213                         'ModelType' : None
1214                         }
1215        self.compartments = {}
1216        self.ReactionIDs = [] # List of reaction names
1217        self.InitStrings = []    # Initialisation strings
1218        self.species = []    # Variable reagents that occur in reactions
1219        self.FixedReagents = []  # Fixed reagents
1220        self.InitParams = []     # Initialised parameters
1221        self.Names = []       # List of all reagent, parameter and function names
1222        self.Inits = []          # Initialised entities
1223        self.Reagents = []       # All reagents found during parsing of reactions
1224        self.ReacParams = []     # Temporary list of reaction parameters
1225        self.LexErrors = []   # List of lexing errors
1226        self.ParseErrors = []
1227        self.SymbolErrors = []
1228        self.ForceFunc = []
1229        self.TimeFunc = []
1230        self.UserFunc = []
1231        self.InitFunc = []
1232        self.Functions = {}
1233        self.Events = {}
1234        self.AssignmentRules = {}
1235        self.UserFuncs = {}
1236        self.ModelInit = {}
1237        self.cbm_FluxBounds = {}  # flux bounds in a CB model
1238        self.cbm_ObjectiveFunctions = {} # objective functions for a CB model
1239        self.cbm_UserFluxConstraints = {} # objective functions for a CB model
1240
1241        self.ModelUsesNumpyFuncs = False
1242
1243        # 4 hrs of debugging and these two lines solve the problem .... I'm out of here!
1244        reload(pysces.lib.lex)
1245        reload(pysces.lib.yacc)
1246        # fixes the obscure reload <--> garbage collection reference overload bug ... who said scripted lang's were
1247        # easy to use :-) - brett 20040122
1248
1249        self.AllRateEqsGiven = 1
1250        self.ParseOK = True
1251        self.LexOK = True
1252
1253        # check to see if the last line of the file is an os defined empty line
1254        self.CheckLastLine(os.path.join(modeldir,modelfile))
1255
1256        # load model data from file
1257        Data = open(os.path.join(modeldir,modelfile),'r')
1258        self.__Model = Data.read()
1259        Data.close()
1260
1261        # try and find a temporary workspace or use ModelOutput
1262        if 'TMP' in os.environ:
1263            tempDir = os.environ['TMP']
1264        elif 'TEMP' in os.environ:
1265            tempDir = os.environ['TEMP']
1266        elif os.path.isdir(modeloutput):
1267            tempDir = modeloutput
1268        else:
1269            tempDir = os.getcwd()
1270
1271        os.chdir(tempDir)
1272
1273        # fix filenames for intermediary files - brett
1274        if not modelfile[:-4].isalnum():
1275            FileL = list(modelfile)
1276            FileT = ''
1277            for let in FileL:
1278                if let.isalnum():
1279                    FileT += let
1280            self.debugfile = '_pys' + FileT[:-3] + ".dbg"
1281            self.tabmodule = '_pys' + FileT[:-3] + "_" + "parsetab"
1282        else:
1283            self.debugfile = '_pys' + modelfile[:-4] + ".dbg"
1284            self.tabmodule = '_pys' + modelfile[:-4] + "_" + "parsetab"
1285
1286        if self.display_debug:
1287            print(self.tabmodule)
1288            print(self.debugfile)
1289            print('cwd: ', os.getcwd())
1290
1291        self.__lexer = pysces.lib.lex.lex(module=self, debug=self.display_debug)
1292        self.__lexer.input(self.__Model)
1293        self.__parser = pysces.lib.yacc.yacc(module=self,
1294                debug=self.display_debug,
1295                debugfile=self.debugfile,
1296                tabmodule=self.tabmodule,
1297                outputdir=tempDir)
1298
1299        while 1:
1300            tok = self.__lexer.token()
1301            if not tok: break
1302
1303        while 1:
1304            p = self.__parser.parse(self.__Model)
1305            if not p: break
1306
1307        # get to our work directory
1308        os.chdir(modeloutput)
1309
1310        # we have the dictionary get rid of this stuff
1311        del self.__Model, self.__lexer, self.__parser, p
1312
1313        # Create forcing function code blocks - brett 20050621
1314        Fstr = ''
1315        Tstr = ''
1316        Ustr = ''
1317        Istr = ''
1318        for f in self.ForceFunc:
1319            if os.sys.platform != 'win32' and f[-2] == '\r':
1320                Fstr = Fstr + f[:-2] + '\n'
1321            else:
1322                Fstr += f
1323        for t in self.TimeFunc:
1324            if os.sys.platform != 'win32' and t[-2] == '\r':
1325                Tstr = Tstr + t[:-2] + '\n'
1326            else:
1327                Tstr += t
1328        for u in self.UserFunc:
1329            if os.sys.platform != 'win32' and u[-2] == '\r':
1330                Ustr = Ustr + u[:-2] + '\n'
1331            else:
1332                Ustr += u
1333        for i in self.InitFunc:
1334            if os.sys.platform != 'win32' and i[-2] == '\r':
1335                Istr = Istr + i[:-2] + '\n'
1336            else:
1337                Istr += i
1338
1339        self.ForceFunc = Fstr
1340        self.TimeFunc = Tstr
1341        self.UserFunc = Ustr
1342        self.InitFunc = Istr
1343        del Fstr,Tstr,Ustr,Istr
1344
1345        # Create list of variable species
1346        for reag in self.Reagents:
1347            if reag not in self.FixedReagents:
1348                self.species.append(reag)
1349
1350        # Warn if no reagents have been fixed
1351        if self.FixedReagents == []:
1352            print('Info: No reagents have been fixed')
1353        else: # Warn if a fixed reagent does not occur in a reaction equation
1354            for reag in self.FixedReagents:
1355                if reag not in self.Reagents:
1356                    print('Warning: species "%s" (fixed) does not occur in any reaction' % reag.replace('self.',''))
1357
1358        # for the initialised ones
1359        for s in list(self.sDict.keys()):
1360            if 'self.'+s in self.FixedReagents+self.Reagents:
1361                fixed = False
1362                compartment = None
1363                isamount = False
1364                initial = self.sDict[s]['initial']
1365                name = self.sDict[s]['name']
1366
1367                if 'self.'+s in self.FixedReagents:
1368                    fixed = True
1369                if s in list(self.InDict.keys()):
1370                    compartment = self.InDict[s]
1371                self.sDict.update({s : {'name' : name,
1372                                        'initial' : initial,
1373                                        'compartment' : compartment,
1374                                        'fixed' : fixed,
1375                                        'isamount' : isamount
1376                                        }})
1377            else:
1378                self.pDict.update({s : self.sDict.pop(s)})
1379
1380
1381        # for the uninitialised ones
1382        ##  print self.FixedReagents, self.Reagents, self.Inits
1383        for s in self.species+self.FixedReagents:
1384            if s.replace('self.','') not in self.sDict:
1385                fixed = False
1386                compartment = None
1387                isamount = False
1388                initial = None
1389                name = s.replace('self.','')
1390
1391                if s in self.FixedReagents:
1392                    fixed = True
1393                if name in list(self.InDict.keys()):
1394                    compartment = self.InDict[name]
1395                self.sDict.update({name : {'name' : name,
1396                                        'initial' : initial,
1397                                        'compartment' : compartment,
1398                                        'fixed' : fixed,
1399                                        'isamount' : isamount
1400                                        }})
1401
1402
1403        # Create list of initialised parameters
1404        # eventually this must be switched over to pDict
1405        for elem in self.Inits:
1406            el = elem.replace('self.', '')
1407            names = [self.sDict[s]['name'] for s in self.sDict]
1408            if el not in names:
1409                self.InitParams.append(elem)
1410            if el in names and self.sDict[el]['fixed'] == True:
1411                self.InitParams.append(elem)
1412
1413        # In self.nDict, clean rate equation pameter list of variables that occur in that reaction
1414        for id in list(self.nDict.keys()):
1415            # create 'Modifiers' key for each reaction - brett 20050606
1416            self.nDict[id].update({'Modifiers':[]})
1417            reagcomp = None
1418            if id in list(self.InDict.keys()):
1419                reagcomp = self.InDict[id]
1420            self.nDict[id].update({'compartment' : reagcomp})
1421            for reag in self.species:
1422                if reag in self.nDict[id]['Params']:
1423                    # if the reagent is a reaction reagent delete - brett 20050606
1424                    if reag in self.nDict[id]['Reagents']:
1425                        self.nDict[id]['Params'].remove(reag)
1426                    # if not it is a modifier ... add to modifiers and delete - brett 20050606
1427                    else:
1428                        #print 'INFO: variable modifier \"' + reag.replace('self.','') + '\" detected in', id
1429                        self.nDict[id]['Modifiers'].append(reag)
1430                        self.nDict[id]['Params'].remove(reag)
1431
1432        # Check whether all parameters have been initialised and if not add to dictionary
1433        for id in list(self.nDict.keys()):
1434            cnames = [self.compartments[s]['name'] for s in self.compartments]
1435            for param in self.nDict[id]['Params']:
1436                ##  print param
1437                ##  print self.InitParams
1438                if param not in self.InitParams and param.replace('self.','') not in cnames:
1439                    ##  print param, self.InitParams, self.pDict
1440                    print('Warning: %s parameter "%s"  has not been initialised' % (id, param.replace('self.','')))
1441                    pname = param.replace('self.','')
1442                    self.pDict.update({pname : {'name':pname, 'initial':None}})
1443                    self.InitParams.append(param)
1444
1445        # Check whether all variable reagents have been initialised
1446        for reag in self.species+self.FixedReagents:
1447            if reag not in self.Inits and reag in self.species:
1448                print('Warning: species "%s" has not been initialised' % reag.replace('self.',''))
1449            if reag not in self.Inits and reag in self.FixedReagents:
1450                print('Warning: species "%s" (fixed) has not been initialised' % reag.replace('self.',''))
1451
1452        # Check that all initialised parameters actually occur in self.Inits
1453        known = 0
1454        for param in self.InitParams:
1455            for id in list(self.nDict.keys()):
1456                if param in self.nDict[id]['Params']:
1457                    known = 1
1458                    break
1459                else:
1460                    known = 0
1461            if not known: print('Info: "%s" has been initialised but does not occur in a rate equation' % param.replace('self.',''))
1462
1463        # find modifiers for each reaction - brett 20050606
1464        self.modifiers = []
1465        for i in self.nDict:
1466            for j in self.nDict[i]['Params']:
1467                if j in self.FixedReagents and j not in self.nDict[i]['Reagents']:
1468                    self.nDict[i]['Modifiers'].append(j)
1469            self.modifiers.append((i,tuple([j.replace('self.','') for j in self.nDict[i]['Modifiers']])))
1470
1471        self.fixed_species = copy.copy(self.FixedReagents)
1472        self.parameters = copy.copy(self.InitParams)
1473        self.reactions = copy.copy(self.ReactionIDs)
1474
1475        for x in range(len(self.fixed_species)):
1476            self.fixed_species[x] = self.fixed_species[x].replace('self.','')
1477        for x in range(len(self.species)):
1478            self.species[x] = self.species[x].replace('self.','')
1479        for x in range(len(self.parameters)):
1480            self.parameters[x] = self.parameters[x].replace('self.','')
1481
1482        if self.display_debug == 1:
1483            print(self.fixed_species)
1484            print(self.species)
1485            print(self.parameters)
1486            print(self.reactions)
1487
1488        if self.display_debug == 1:
1489            print('\nDebugging Part 1')
1490            print('\nFixedReagents')
1491            print(self.FixedReagents)
1492            print('\nInitStrings')
1493            print(self.InitStrings)
1494            print('\nInitParams')
1495            print(self.InitParams)
1496            print('\nReactionIDs')
1497            print(self.ReactionIDs)
1498            print('\nspecies')
1499            print(self.species)
1500            print('\nNetworkDict')
1501            print(self.nDict)
1502            print('\n\n')
1503
1504    def KeywordCheck(self,inputarr,bad=[]):
1505        """
1506        KeywordCheck(inputarr,bad=[])
1507
1508        Check a list of names for reserved PySCeS keywords
1509
1510        Arguments:
1511        =========
1512        inputarr: a list of names to check
1513        bad [default=[]]: a list of illegal keywords (new if not supplied
1514
1515        """
1516        for x in inputarr:
1517            if x.replace('self.','') in self.ReservedTerms:
1518                bad.append(x.replace('self.',''))
1519                print(x.replace('self.',''), '\t\tERROR: keyword')
1520        return bad
1521
1522    def CheckLastLine(self,name):
1523        """
1524        CheckLastLine(name)
1525
1526        Checks to see if file ends with an empty line ... if not it adds one
1527
1528        Arguments:
1529        =========
1530        name: filename of the PySCeS input file
1531
1532        """
1533        F = open(name,'r+')
1534        #F.seek(-1,2)
1535        F.seek(0, os.SEEK_END)
1536        F.seek(F.tell() - 1, os.SEEK_SET)
1537        if F.read() != '\n':
1538            if os.sys.platform == 'win32':
1539                F.read()
1540            try:
1541                F.write('\n')
1542            except Exception as e:
1543                print('EOL add error', e)
1544            print('Adding \\n to input file')
1545        F.close()
1546
1547##      # you REALLY do NOT want to know what this was for! - brett
1548##      if gc.isenabled() == 1:
1549##          gc.collect()
1550##          del gc.garbage[:]
1551##          gc.set_debug(0)
1552##          print 'gc debug level = ' + `gc.get_debug()`
1553
1554