Package PyDSTool :: Module FuncSpec'
[hide private]
[frames] | no frames]

Source Code for Module PyDSTool.FuncSpec'

   1  """Functional specification classes.
 
   2  
 
   3     Robert Clewley, August 2005.
 
   4  
 
   5  This module aids in building internal representations of ODEs, etc.,
 
   6  particularly for the benefit of Automatic Differentiation
 
   7  and for manipulation of abstraction digraphs.
 
   8  """ 
   9  
 
  10  # PyDSTool imports
 
  11  from __future__ import division 
  12  from utils import * 
  13  from common import * 
  14  from parseUtils import * 
  15  from errors import * 
  16  from utils import info as utils_info 
  17  from Symbolic import QuantSpec 
  18  
 
  19  # Other imports
 
  20  from copy import copy, deepcopy 
  21  import math, random, numpy, scipy, scipy.special 
  22  from numpy import any 
  23  
 
  24  __all__ = ['RHSfuncSpec', 'ImpFuncSpec', 'ExpFuncSpec', 'FuncSpec',
 
  25             'getSpecFromFile', 'resolveClashingAuxFnPars', 'makePartialJac'] 
  26  
 
  27  # ---------------------------------------------------------------
 
  28  
 
29 -class FuncSpec(object):
30 """Functional specification of dynamics: abstract class. 31 32 NOTES ON BUILT-IN AUX FUNCTIONS (WITH SYNTAX AS USED IN SPEC STRING): 33 34 globalindepvar(t) -> global independent variable (time) reference 35 36 initcond(varname) -> initial condition of that variable in this DS 37 38 heav(x) = 1 if x > 0, 0 otherwise 39 40 getindex(varname) -> index of varname in internal representation of 41 variables as array 42 43 getbound(name, which_bd) -> value of user-defined bound on the named 44 variable or parameter, either the lower (which_bd=0) or higher 45 (which_bd=1) 46 47 if(condition, expr1, expr2) -> if condition as a function of state, 48 parameters and time is true, then evaluate <expr1>, else evaluate 49 <expr2>. 50 51 MACRO `for` SYNTAX: 52 53 for(i, ilo, ihi, expr_in_i) -> list of expressions where each 54 occurrence of `[i]` is replaced with the appropriate integer. 55 The letter i can be replaced with any other single character. 56 57 MACRO `sum` SYNTAX: 58 59 sum(i, ilo, ihi, expr_in_i) -> an expression that sums 60 over the expression replacing any occurrence of `[i]` with 61 the appropriate integer. 62 """ 63
64 - def __init__(self, kw):
65 # All math package names are reserved 66 self._protected_mathnames = protected_mathnames 67 self._protected_randomnames = protected_randomnames 68 self._protected_scipynames = protected_scipynames 69 self._protected_specialfns = protected_specialfns 70 # We add internal default auxiliary function names for use by 71 # functional specifications. 72 self._builtin_auxnames = builtin_auxnames 73 self._protected_macronames = protected_macronames 74 self._protected_auxnames = copy(self._builtin_auxnames) 75 self._protected_reusenames = [] # for reusable sub-expression terms 76 needKeys = ['name', 'vars'] 77 optionalKeys = ['pars', 'inputs', 'varspecs', 'spec', '_for_macro_info', 78 'targetlang', 'fnspecs', 'auxvars', 'reuseterms', 79 'codeinsert_start', 'codeinsert_end', 'ignorespecial'] 80 self._initargs = deepcopy(kw) 81 # PROCESS NECESSARY KEYS ------------------- 82 try: 83 # spec name 84 if 'name' in kw: 85 self.name = kw['name'] 86 else: 87 self.name = 'untitled' 88 # declare variables (name list) 89 if isinstance(kw['vars'], list): 90 vars = kw['vars'][:] # take copy 91 else: 92 assert isinstance(kw['vars'], str), 'Invalid variable name' 93 vars = [kw['vars']] 94 except KeyError: 95 raise PyDSTool_KeyError('Necessary keys missing from argument dict') 96 foundKeys = len(needKeys) 97 # PROCESS OPTIONAL KEYS -------------------- 98 # declare pars (name list) 99 if 'pars' in kw: 100 if isinstance(kw['pars'], list): 101 pars = kw['pars'][:] # take copy 102 else: 103 assert isinstance(kw['pars'], str), 'Invalid parameter name' 104 pars = [kw['pars']] 105 foundKeys += 1 106 else: 107 pars = [] 108 # declare external inputs (name list) 109 if 'inputs' in kw: 110 if isinstance(kw['inputs'], list): 111 inputs = kw['inputs'][:] # take copy 112 else: 113 assert isinstance(kw['inputs'], str), 'Invalid input name' 114 inputs = [kw['inputs']] 115 foundKeys += 1 116 else: 117 inputs = [] 118 if 'targetlang' in kw: 119 try: 120 tlang = kw['targetlang'].lower() 121 except AttributeError: 122 raise TypeError("Expected string type for target language") 123 if tlang not in targetLangs: 124 raise ValueError('Invalid specification for targetlang') 125 self.targetlang = tlang 126 foundKeys += 1 127 else: 128 self.targetlang = 'python' # default 129 if self.targetlang == 'c': 130 self._defstr = "#define" 131 self._undefstr = "#undef" 132 else: 133 self._defstr = "" 134 self._undefstr = "" 135 if 'ignorespecial' in kw: 136 self._ignorespecial = kw['ignorespecial'] 137 foundKeys += 1 138 else: 139 self._ignorespecial = [] 140 # ------------------------------------------ 141 # reusable terms in function specs 142 if 'reuseterms' in kw: 143 if isinstance(kw['reuseterms'], dict): 144 self.reuseterms = deepcopy(kw['reuseterms']) 145 else: 146 raise ValueError('reuseterms must be a dictionary of strings ->' 147 ' replacement strings') 148 ignore_list = [] 149 for term, repterm in self.reuseterms.iteritems(): 150 assert isinstance(term, str), \ 151 "terms in 'reuseterms' dictionary must be strings" 152 ## if term[0] in num_chars+['.']: 153 ## raise ValueError('terms must not be numerical values') 154 if isNumericToken(term): 155 # don't replace numeric terms (sometimes these are 156 # generated automatically by Constructors when resolving 157 # explicit variable inter-dependencies) 158 ignore_list.append(term) 159 # not sure about the next check any more... 160 # what is the point of not allowing subs terms to begin with op? 161 if term[0] in '+/*': 162 print "Error in term:", term 163 raise ValueError('terms to be substituted must not begin ' 164 'with arithmetic operators') 165 if term[0] == '-': 166 term = '(' + term + ')' 167 if term[-1] in '+-/*': 168 print "Error in term:", term 169 raise ValueError('terms to be substituted must not end with ' 170 'arithmetic operators') 171 for s in term: 172 if self.targetlang == 'python': 173 if s in '[]{}~@#$%&\|?^': # <>! now OK, e.g. for "if" statements 174 print "Error in term:", term 175 raise ValueError('terms to be substituted must be ' 176 'alphanumeric or contain arithmetic operators ' 177 '+ - / *') 178 else: 179 if s in '[]{}~!@#$%&\|?><': # removed ^ from this list 180 print "Error in term:", term 181 raise ValueError('terms to be substituted must be alphanumeric or contain arithmetic operators + - / *') 182 if repterm[0] in num_chars: 183 print "Error in replacement term:", repterm 184 raise ValueError('replacement terms must not begin with numbers') 185 for s in repterm: 186 if s in '+-/*.()[]{}~!@#$%^&\|?><,': 187 print "Error in replacement term:", repterm 188 raise ValueError('replacement terms must be alphanumeric') 189 for t in ignore_list: 190 del self.reuseterms[t] 191 foundKeys += 1 192 else: 193 self.reuseterms = {} 194 # auxiliary variables declaration 195 if 'auxvars' in kw: 196 if isinstance(kw['auxvars'], list): 197 auxvars = kw['auxvars'][:] # take copy 198 else: 199 assert isinstance(kw['auxvars'], str), 'Invalid variable name' 200 auxvars = [kw['auxvars']] 201 foundKeys += 1 202 else: 203 auxvars = [] 204 # auxfns dict of functionality for auxiliary functions (in 205 # either python or C). for instance, these are used for global 206 # time reference, access of regular variables to initial 207 # conditions, and user-defined quantities. 208 self.auxfns = {} 209 if 'fnspecs' in kw: 210 self._auxfnspecs = deepcopy(kw['fnspecs']) 211 foundKeys += 1 212 else: 213 self._auxfnspecs = {} 214 # spec dict of functionality, as a string for each var 215 # (in either python or C, or just for python?) 216 assert 'varspecs' in kw or 'spec' in kw, ("Require a functional " 217 "specification key -- 'spec' or 'varspecs'") 218 if '_for_macro_info' in kw: 219 foundKeys += 1 220 self._varsbyforspec = kw['_for_macro_info'].varsbyforspec 221 else: 222 self._varsbyforspec = {} 223 if 'varspecs' in kw: 224 if auxvars == []: 225 numaux = 0 226 else: 227 numaux = len(auxvars) 228 if '_for_macro_info' in kw: 229 if kw['_for_macro_info'].numfors > 0: 230 num_varspecs = numaux + len(vars) - kw['_for_macro_info'].totforvars + \ 231 kw['_for_macro_info'].numfors 232 else: 233 num_varspecs = numaux + len(vars) 234 else: 235 num_varspecs = numaux + len(vars) 236 if len(kw['varspecs']) != len(self._varsbyforspec) and \ 237 len(kw['varspecs']) != num_varspecs: 238 print "# state variables: ", len(vars) 239 print "# auxiliary variables: ", numaux 240 print "# of variable specs: ", len(kw['varspecs']) 241 raise ValueError('Incorrect size of varspecs') 242 self.varspecs = deepcopy(kw['varspecs']) 243 foundKeys += 1 # for varspecs 244 else: 245 self.varspecs = {} 246 self.codeinserts = {'start': '', 'end': ''} 247 if 'codeinsert_start' in kw: 248 codestr = kw['codeinsert_start'] 249 assert isinstance(codestr, str), 'code insert must be a string' 250 if self.targetlang == 'python': 251 # check initial indentation (as early predictor of whether 252 # indentation has been done properly) 253 if codestr[:4] != _indentstr: 254 codestr = _indentstr+codestr 255 # additional spacing in function spec 256 if codestr[-1] != '\n': 257 addnl = '\n' 258 else: 259 addnl = '' 260 self.codeinserts['start'] = codestr+addnl 261 foundKeys += 1 262 if 'codeinsert_end' in kw: 263 codestr = kw['codeinsert_end'] 264 assert isinstance(codestr, str), 'code insert must be a string' 265 if self.targetlang == 'python': 266 # check initial indentation (as early predictor of whether 267 # indentation has been done properly) 268 assert codestr[:4] == " ", ("First line of inserted " 269 "python code at start of spec was " 270 "wrongly indented") 271 # additional spacing in function spec 272 if codestr[-1] != '\n': 273 addnl = '\n' 274 else: 275 addnl = '' 276 self.codeinserts['end'] = codestr+addnl 277 foundKeys += 1 278 # spec dict of functionality, as python functions, 279 # or the paths/names of C dynamic linked library files 280 # can be user-defined or generated from generateSpec 281 if 'spec' in kw: 282 if 'varspecs' in kw: 283 raise PyDSTool_KeyError, \ 284 "Cannot provide both 'spec' and 'varspecs' keys" 285 assert isinstance(kw['spec'], tuple), ("'spec' must be a pair:" 286 " (spec body, spec name)") 287 assert len(kw['spec'])==2, ("'spec' must be a pair:" 288 " (spec body, spec name)") 289 self.spec = deepcopy(kw['spec']) 290 # auxspec not used for explicitly-given specs. it's only for 291 # auto-generated python auxiliary variable specs (as py functions) 292 self.auxspec = {} 293 if 'dependencies' in kw: 294 self.dependencies = kw['dependencies'] 295 else: 296 raise PyDSTool_KeyError("Dependencies must be provided " 297 "explicitly when using 'spec' form of initialization") 298 foundKeys += 2 299 else: 300 self.spec = {} 301 self.auxspec = {} 302 self.dependencies = [] 303 if len(kw) > foundKeys: 304 raise PyDSTool_KeyError('Invalid keys passed in argument dict') 305 self.defined = False # initial value 306 self.validateDef(vars, pars, inputs, auxvars, self._auxfnspecs.keys()) 307 # ... exception if not valid 308 # Fine to do the following if we get this far: 309 # sort for final order that will be used for determining array indices 310 vars.sort() 311 pars.sort() 312 inputs.sort() 313 auxvars.sort() 314 self.vars = vars 315 self.pars = pars 316 self.inputs = inputs 317 self.auxvars = auxvars 318 # pre-process specification string for built-in macros (like `for`, 319 # i.e. that are not also auxiliary functions, like the in-line `if`) 320 self.doPreMacros() 321 # !!! 322 # want to create _pyauxfns but not C versions until after main spec 323 # !!! 324 self.generateAuxFns() 325 if self.spec == {}: 326 assert self.varspecs != {}, \ 327 'No functional specification provided!' 328 self.generateSpec() 329 # exception if the following is not successful 330 self.validateDependencies(self.dependencies) 331 #self.generateAuxFns() 332 ## self.validateSpecs() 333 # algparams is only used by ImplicitFnGen to pass extra info to Variable 334 self.algparams = {} 335 self.defined = True
336
337 - def __hash__(self):
338 """Unique identifier for this specification.""" 339 deflist = [self.name, self.targetlang] 340 # lists 341 for l in [self.pars, self.vars, self.auxvars, self.inputs, 342 self.spec, self.auxspec]: 343 deflist.append(tuple(l)) 344 # dicts 345 for d in [self.auxfns, self.codeinserts]: 346 deflist.append(tuple(sortedDictItems(d, byvalue=False))) 347 return hash(tuple(deflist))
348
349 - def recreate(self, targetlang):
350 if targetlang == self.targetlang: 351 # print "Returning a deep copy of self" 352 return deepcopy(self) 353 fs = FuncSpec.__new__(self.__class__) 354 new_args = deepcopy(self._initargs) 355 if self.codeinserts['start'] != '': 356 del new_args['codeinsert_start'] 357 print "Warning: code insert (start) ignored for new target" 358 if self.codeinserts['end'] != '': 359 del new_args['codeinsert_end'] 360 print "Warning: code insert (end) ignored for new target" 361 new_args['targetlang'] = targetlang 362 fs.__init__(new_args) 363 return fs
364 365
366 - def __call__(self):
367 # info is defined in utils.py 368 utils_info(self.__dict__, "FuncSpec " + self.name)
369 370 371 # def info(self, verbose=1): 372 # if verbose > 0: 373 # # info is defined in utils.py 374 # utils_info(self.__dict__, "FuncSpec " + self.name, 375 # recurseDepthLimit=1+verbose) 376 # else: 377 # print self.__repr__() 378 379 380 # This function doesn't work -- it generates: 381 # global name 'self' is not defined 382 # in the _specfn call 383 ## def validateSpecs(self): 384 ## # dummy values for internal values possibly needed by auxiliary fns 385 ## self.globalt0 = 0 386 ## self.initialconditions = {}.fromkeys(self.vars, 0) 387 ## lenparsinps = len(self.pars)+len(self.inputs) 388 ## pi_vals = zeros(lenparsinps, float64) 389 ## _specfn(1, self.initialconditions.values(), pi_vals) 390 391
392 - def validateDef(self, vars, pars, inputs, auxvars, auxfns):
393 """Validate definition of the functional specification.""" 394 # verify that vars, pars, and inputs are non-overlapping lists 395 assert not intersect(vars, pars), 'variable and param names overlap' 396 assert not intersect(vars, inputs), 'variable and input names overlap' 397 assert not intersect(pars, inputs), 'param and input names overlap' 398 assert not intersect(vars, auxfns), ('variable and auxiliary function ' 399 'names overlap') 400 assert not intersect(pars, auxfns), ('param and auxiliary function ' 401 'names overlap') 402 assert not intersect(inputs, auxfns), ('input and auxiliary function ' 403 'names overlap') 404 assert not intersect(vars, auxvars), ('variable and auxiliary variable ' 405 'names overlap') 406 assert not intersect(pars, auxvars), ('param and auxiliary variable ' 407 'names overlap') 408 assert not intersect(inputs, auxvars), ('input and auxiliary variable ' 409 'names overlap') 410 # verify uniqueness of all names 411 assert isUniqueSeq(vars), 'variable names are repeated' 412 assert isUniqueSeq(pars), 'parameter names are repeated' 413 assert isUniqueSeq(inputs), 'input names are repeated' 414 if auxvars != []: 415 assert isUniqueSeq(auxvars), 'auxiliary variable names are repeated' 416 if auxfns != []: 417 assert isUniqueSeq(auxfns), 'auxiliary function names are repeated' 418 allnames = vars+pars+inputs+auxvars 419 allprotectednames = self._protected_mathnames + \ 420 self._protected_scipynames + \ 421 self._protected_specialfns + \ 422 self._protected_randomnames + \ 423 self._protected_auxnames + \ 424 ['abs', 'min', 'max', 'and', 'or', 'not', 425 'True', 'False'] 426 # other checks 427 assert reduce(bool.__and__, [name_chars_RE.match(n[0]) \ 428 is not None for n in allnames]), \ 429 ('variable, parameter, and input names must not ' 430 'begin with non-alphabetic chars') 431 assert reduce(bool.__and__, [n not in allnames for n in \ 432 allprotectednames]), \ 433 ('variable, parameter, and input names must not ' 434 'overlap with protected math / aux function names')
435 ## Not yet implemented ? 436 # verify that targetlang is consistent with spec contents? 437 # verify that spec is consistent with specstring (if not empty)? 438 439
440 - def validateDependencies(self, dependencies):
441 """Validate the stored dependency pairs for self-consistency.""" 442 # dependencies is a list of unique ordered pairs (i,o) 443 # where (i,o) means 'variable i directly depends on variable o' 444 # (o can include inputs) 445 assert isinstance(dependencies, list), ('dependencies must be a list ' 446 'of unique ordered pairs') 447 # Verify all names in dependencies are in self.vars 448 # and that (i,o) pairs are unique in dependencies 449 for d in dependencies: 450 assert len(d) == 2, 'dependencies must be ordered pairs' 451 i = d[0] 452 o = d[1] 453 firstpos = dependencies.index(d) 454 assert d not in dependencies[firstpos+1:], \ 455 'dependency pairs must be unique' 456 assert i in self.vars+self.auxvars, 'unknown variable name in dependencies' 457 assert o in self.vars or o in self.inputs, \ 458 'unknown variable name in dependencies'
459 # No need to verify that dependencies are consistent with spec, 460 # if spec was generated automatically 461 462
463 - def generateAuxFns(self):
464 # Always makes a set of python versions of the functions for future 465 # use by user at python level 466 if self.targetlang == 'python': 467 self._genAuxFnPy(pytarget=True) 468 elif self.targetlang == 'c': 469 self._genAuxFnC() 470 self._genAuxFnPy() 471 elif self.targetlang == 'matlab': 472 self._genAuxFnMatlab() 473 self._genAuxFnPy() 474 elif self.targetlang == 'dstool': 475 raise NotImplementedError 476 elif self.targetlang == 'xpp': 477 raise NotImplementedError 478 else: 479 raise ValueError('targetlang attribute must be in '+str(targetLangs))
480 481
482 - def generateSpec(self):
483 """Automatically generate callable target-language functions from 484 the user-defined specification strings.""" 485 if self.targetlang == 'python': 486 self._genSpecPy() 487 elif self.targetlang == 'c': 488 self._genSpecC() 489 elif self.targetlang == 'matlab': 490 self._genSpecMatlab() 491 elif self.targetlang == 'odetools': 492 raise NotImplementedError 493 elif self.targetlang == 'xpp': 494 raise NotImplementedError 495 else: 496 raise ValueError('targetlang attribute must be in '+str(targetLangs))
497 498
499 - def doPreMacros(self):
500 """Pre-process any macro spec definitions (e.g. `for` loops).""" 501 502 assert self.varspecs != {}, 'varspecs attribute must be defined' 503 specnames_unsorted = self.varspecs.keys() 504 _vbfs_inv = invertMap(self._varsbyforspec) 505 # Process state variable specifications 506 if len(_vbfs_inv) > 0: 507 specname_vars = [] 508 specname_auxvars = [] 509 for varname in self.vars: 510 # check if varname belongs to a for macro grouping in self.varspecs 511 specname = _vbfs_inv[varname] 512 if specname not in specname_vars: 513 specname_vars.append(specname) 514 for varname in self.auxvars: 515 # check if varname belongs to a for macro grouping in self.varspecs 516 specname = _vbfs_inv[varname] 517 if specname not in specname_auxvars: 518 specname_auxvars.append(specname) 519 else: 520 specname_vars = intersect(self.vars, specnames_unsorted) 521 specname_auxvars = intersect(self.auxvars, specnames_unsorted) 522 specname_vars.sort() 523 specname_auxvars.sort() 524 specnames = specname_vars + specname_auxvars # sorted *individually* 525 specnames_temp = copy(specnames) 526 for specname in specnames_temp: 527 leftbrack_ix = specname.find('[') 528 rightbrack_ix = specname.find(']') 529 test_sum = leftbrack_ix + rightbrack_ix 530 if test_sum > 0: 531 # both brackets found -- we expect a `for` macro in specstr 532 assert rightbrack_ix - leftbrack_ix == 2, ('Misuse of square ' 533 'brackets in spec definition. Expected single' 534 ' character between left and right brackets.') 535 ## if remain(self._varsbyforspec[specname], self.vars) == []: 536 ## foundvar = True 537 ## else: 538 ## foundvar = False # auxiliary variable instead 539 rootstr = specname[:leftbrack_ix] 540 istr = specname[leftbrack_ix+1] 541 specstr = self.varspecs[specname] 542 assert specstr[:4] == 'for(', ('Expected `for` macro when ' 543 'square brackets used in name definition') 544 # read contents of braces 545 arginfo = readArgs(specstr[3:]) 546 if not arginfo[0]: 547 raise ValueError('Error finding ' 548 'arguments applicable to `for` ' 549 'macro') 550 arglist = arginfo[1] 551 assert len(arglist) == 4, ('Wrong number of arguments passed ' 552 'to `for` macro. Expected 4') 553 istr = arglist[0] 554 allnames = self.vars + self.pars + self.inputs + self.auxvars \ 555 + self._protected_mathnames \ 556 + self._protected_randomnames \ 557 + self._protected_auxnames \ 558 + self._protected_scipynames \ 559 + self._protected_specialfns \ 560 + self._protected_macronames \ 561 + ['abs', 'and', 'or', 'not', 'True', 'False'] 562 assert istr not in allnames, ('loop index in `for` macro ' 563 'must not be a reserved name') 564 for ichar in istr: 565 assert name_chars_RE.match(ichar) is not None, \ 566 ('loop index in `for` macro ' 567 'must be alphanumeric') 568 ilo = int(arglist[1]) 569 ihi = int(arglist[2]) 570 # NOTE: rootstr + '['+istr+'] = ' + arglist[3] 571 expr = arglist[3] 572 # add macro text 573 varspecs = self._macroFor(rootstr, istr, ilo, ihi, expr) 574 specnames_gen = varspecs.keys() 575 # now we update the dictionary of specnames with the 576 # processed, expanded versions 577 specnames.remove(specname) 578 ## if foundvar: 579 ## assert rootstr+'['+istr+']' in self.varspecs, ('Mismatch ' 580 ## 'between declared variables ' 581 ## 'and loop index in `for` macro') 582 ## #self.vars.remove(specname) 583 ## else: 584 ## assert rootstr+'['+istr+']' in self.varspecs, ('Mismatch ' 585 ## 'between declared variables ' 586 ## 'and loop index in `for` macro') 587 ## self.auxvars.remove(specname) 588 del(self.varspecs[specname]) 589 for sname in specnames_gen: 590 self.varspecs[sname] = varspecs[sname] 591 specnames.append(sname) 592 ## if foundvar: 593 ## self.vars.append(sname) 594 ## else: 595 ## self.auxvars.append(sname) 596 elif test_sum == -2: 597 pass 598 # no brackets found. regular definition line. take no action. 599 else: 600 raise AssertionError('Misuse of square brackets in spec ' 601 'definition. Expected single' 602 ' character between left and right brackets.')
603 604
605 - def _macroFor(self, rootstr, istr, ilo, ihi, expr_in_i):
606 """Internal utility function to build multiple instances of expression 607 'expr_in_i' where integer i has been substituted for values from ilo to ihi. 608 Returns dictionary keyed by rootstr+str(i) for each i. 609 """ 610 # already tested for the same number of [ and ] occurrences 611 retdict = {} 612 q = QuantSpec('__temp__', expr_in_i) 613 eval_pieces = {} 614 avoid_toks = [] 615 for ix, tok in enumerate(q): 616 if tok[0] == '[': 617 eval_str = tok[1:-1] 618 if istr in eval_str: 619 eval_pieces[ix] = eval_str 620 # otherwise may be a different, embedded temp index for another 621 # sum, etc., so don't touch it 622 keys = eval_pieces.keys() 623 keys.sort() 624 ranges = remove_indices_from_range(keys, len(q.parser.tokenized)-1) 625 # By virtue of this syntax, the first [] cannot be before some other text 626 pieces = [] 627 eval_ixs = [] 628 for ri, r in enumerate(ranges): 629 if len(r) == 1: 630 pieces.append(q[r[0]]) 631 else: 632 # len(r) == 2 633 pieces.append(''.join(q[r[0]:r[1]])) 634 if ri+1 == len(ranges): 635 # last one - check if there's an eval piece placeholder to append at the end 636 if len(keys) > 0 and keys[-1] == r[-1]: 637 pieces.append('') 638 eval_ixs.append(len(pieces)-1) 639 # else do nothing 640 else: 641 # in-between pieces, so append a placeholder for an eval piece 642 pieces.append('') 643 eval_ixs.append(len(pieces)-1) 644 for i in range(ilo, ihi+1): 645 for k, ei in zip(keys, eval_ixs): 646 s = eval_pieces[k].replace(istr, str(i)) 647 try: 648 pieces[ei] = str(int(eval(s))) 649 except NameError: 650 # maybe recursive 'sum' syntax, so a different index letter 651 pieces[ei] = s 652 retdict[rootstr+str(i)] = ''.join(pieces)+'\n' 653 return retdict
654
655 - def _macroSum(self, istr, ilo, ihi, expr_in_i):
656 def_dict = self._macroFor('', istr, int(ilo), int(ihi), expr_in_i) 657 retstr = '(' + "+".join([term.strip() for term in def_dict.values()]) + ')' 658 return retstr
659 660 # ----------------- Python specifications ---------------- 661
662 - def _genAuxFnPy(self, pytarget=False):
663 if pytarget: 664 assert self.targetlang == 'python', \ 665 'Wrong target language for this call' 666 auxnames = self._auxfnspecs.keys() 667 # User aux fn interface 668 uafi = {} 669 ## protectednames = auxnames + self._protected_mathnames + \ 670 ## self._protected_randomnames + \ 671 ## self._protected_scipynames + \ 672 ## self._protected_specialfns + \ 673 ## ['abs', 'and', 'or', 'not', 'True', 'False'] 674 # Deal with built-in auxiliary functions (don't make their names unique) 675 # In this version, the textual code here doesn't get executed. Only 676 # the function names in the second position of the tuple are needed. 677 # Later, the text will probably be removed. 678 auxfns = {} 679 auxfns['globalindepvar'] = \ 680 ("def _auxfn_globalindepvar(ds, parsinps, t):\n" \ 681 + _indentstr \ 682 + "return ds.globalt0 + t", '_auxfn_globalindepvar') 683 auxfns['initcond'] = \ 684 ("def _auxfn_initcond(ds, parsinps, varname):\n" \ 685 + _indentstr \ 686 + "return ds.initialconditions[varname]",'_auxfn_initcond') 687 auxfns['heav'] = \ 688 ("def _auxfn_heav(ds, parsinps, x):\n" + _indentstr \ 689 + "if x>0:\n" + 2*_indentstr \ 690 + "return 1\n" + _indentstr + "else:\n" \ 691 + 2*_indentstr + "return 0", '_auxfn_heav') 692 auxfns['if'] = \ 693 ("def _auxfn_if(ds, parsinps, c, e1, e2):\n" \ 694 + _indentstr + "if c:\n" + 2*_indentstr \ 695 + "return e1\n" + _indentstr \ 696 + "else:\n" + 2*_indentstr + "return e2", '_auxfn_if') 697 auxfns['getindex'] = \ 698 ("def _auxfn_getindex(ds, parsinps, varname):\n" \ 699 + _indentstr \ 700 + "return ds._var_namemap[varname]", '_auxfn_getindex') 701 auxfns['getbound'] = \ 702 ("def _auxfn_getbound(ds, parsinps, name, bd):\n" \ 703 + _indentstr + "try:\n" \ 704 + 2*_indentstr + "return ds.xdomain[name][bd]\n" \ 705 + _indentstr + "except KeyError:\n" + 2*_indentstr \ 706 + "try:\n" + 3*_indentstr \ 707 + "return ds.pdomain[name][bd]\n" + 2*_indentstr \ 708 + "except KeyError, e:\n" + 3*_indentstr \ 709 + "print 'Invalid var / par name %s'%name,\n" \ 710 + 3*_indentstr + "print 'or bounds not well defined:'\n" \ 711 + 3*_indentstr + "print ds.xdomain, ds.pdomain\n" \ 712 + 3*_indentstr + "raise (RuntimeError, e)", 713 '_auxfn_getbound') 714 # the internal functions may be used by user-defined functions, 715 # so need them to be accessible to __processTokens when parsing 716 self._pyauxfns = auxfns 717 # add the user-defined function names for cross-referencing checks 718 # (without their definitions) 719 for auxname in auxnames: 720 self._pyauxfns[auxname] = None 721 # don't process the built-in functions -> unique fns because 722 # they are global definitions existing throughout the 723 # namespace 724 self._protected_auxnames.extend(['Jacobian','Jacobian_pars']) 725 # protected names are the names that must not be used for 726 # user-specified auxiliary fn arguments 727 protectednames = self.pars + self.inputs \ 728 + ['abs', 'pow', 'and', 'or', 'not', 'True', 'False'] \ 729 + self._protected_auxnames + auxnames \ 730 + self._protected_scipynames + self._protected_specialfns \ 731 + self._protected_macronames + self._protected_mathnames \ 732 + self._protected_randomnames + self._protected_reusenames 733 ### checks for user-defined auxiliary fns 734 # name map for fixing inter-auxfn references 735 auxfn_namemap = {} 736 specials_base = self.pars + self._protected_auxnames \ 737 + ['abs', 'pow', 'and', 'or', 'not', 'True', 'False'] \ 738 + auxnames + self._protected_scipynames \ 739 + self._protected_specialfns \ 740 + self._protected_macronames + self._protected_mathnames \ 741 + self._protected_randomnames + self._protected_reusenames 742 for auxname in auxnames: 743 auxinfo = self._auxfnspecs[auxname] 744 try: 745 if len(auxinfo) != 2: 746 raise ValueError('auxinfo tuple must be of length 2') 747 except TypeError: 748 raise TypeError('fnspecs argument must contain pairs') 749 # auxinfo[0] = tuple or list of parameter names 750 # auxinfo[1] = string containing body of function definition 751 assert isinstance(auxinfo[0], list), ('aux function arguments ' 752 'must be given as a list') 753 assert isinstance(auxinfo[1], str), ('aux function specification ' 754 'must be a string ' 755 'of the function code') 756 # Process Jacobian functions, etc., specially, if present 757 if auxname == 'Jacobian': 758 if not compareList(auxinfo[0],['t']+self.vars): 759 print ['t']+self.vars 760 print "Auxinfo =", auxinfo[0] 761 raise ValueError("Invalid argument list given in Jacobian.") 762 auxparlist = ["t","x","parsinps"] 763 # special symbols to allow in parsing function body 764 specials = ["t","x"] 765 auxstr = auxinfo[1] 766 if any([pt in auxstr for pt in ('^', '**')]): 767 auxstr = convertPowers(auxstr, 'pow') 768 specvars = self.vars 769 specvars.sort() 770 specdict = {}.fromkeys(specvars) 771 if len(specvars) == 1: 772 assert '[' not in auxstr, \ 773 "'[' character invalid in Jacobian for 1D system" 774 assert ']' not in auxstr, \ 775 "']' character invalid in Jacobian for 1D system" 776 specdict[specvars[0]] = auxstr 777 else: 778 specdict = parseMatrixStrToDictStr(auxstr, specvars) 779 reusestr, body_processed_dict = self._processReusedPy(specvars, 780 specdict, 781 specials=specials+specials_base) 782 body_processed = self._specStrParse(specvars, 783 body_processed_dict, 'xjac', 784 specials=specials+specials_base) 785 auxstr_py = self._genSpecFnPy('_auxfn_Jac', 786 reusestr+body_processed, 787 'xjac', specvars) 788 # check Jacobian 789 m = n = len(specvars) 790 specdict_check = {}.fromkeys(specvars) 791 for specname in specvars: 792 temp = body_processed_dict[specname] 793 specdict_check[specname] = \ 794 count_sep(temp.replace("[","").replace("]",""))+1 795 body_processed = "" 796 for row in range(m): 797 if specdict_check[specvars[row]] != n: 798 print "Row %i: "%m, specdict[specvars[row]] 799 print "Found length %i"%specdict_check[specvars[row]] 800 raise ValueError("Jacobian should be %sx%s"%(m,n)) 801 elif auxname == 'Jacobian_pars': 802 if not compareList(auxinfo[0],['t']+self.vars): 803 print ['t']+self.vars 804 print "Auxinfo =", auxinfo[0] 805 raise ValueError("Invalid argument list given in Jacobian.") 806 auxparlist = ["t","x","parsinps"] 807 # special symbols to allow in parsing function body 808 specials = ["t","x"] 809 auxstr = auxinfo[1] 810 if any([pt in auxstr for pt in ('^', '**')]): 811 auxstr = convertPowers(auxstr, 'pow') 812 specvars = self.vars 813 specvars.sort() 814 specdict = {}.fromkeys(self.vars) 815 if len(specvars) == len(self.vars) == 1: 816 assert '[' not in auxstr, \ 817 "'[' character invalid in Jacobian for 1D system" 818 assert ']' not in auxstr, \ 819 "']' character invalid in Jacobian for 1D system" 820 specdict[specvars[0]] = auxstr 821 else: 822 specdict = parseMatrixStrToDictStr(auxstr, self.vars) 823 reusestr, body_processed_dict = self._processReusedPy(self.vars, 824 specdict, 825 specials=specials+specials_base) 826 body_processed = self._specStrParse(self.vars, 827 body_processed_dict, 'pjac', 828 specials=specials+specials_base) 829 auxstr_py = self._genSpecFnPy('_auxfn_Jac_p', 830 reusestr+body_processed, 831 'pjac', self.vars) 832 # check Jacobian 833 n = len(specvars) 834 m = len(self.vars) 835 specdict_check = {}.fromkeys(self.vars) 836 for specname in self.vars: 837 temp = body_processed_dict[specname] 838 specdict_check[specname] = \ 839 count_sep(temp.replace("[","").replace("]",""))+1 840 body_processed = "" 841 for row in range(m): 842 try: 843 if specdict_check[self.vars[row]] != n: 844 print "Row %i: "%m, specdict[self.vars[row]] 845 print "Found length %i"%specdict_check[self.vars[row]] 846 raise ValueError("Jacobian w.r.t. pars should be %sx%s"%(m,n)) 847 except IndexError: 848 print "\nFound:\n" 849 info(specdict) 850 raise ValueError("Jacobian w.r.t. pars should be %sx%s"%(m,n)) 851 elif auxname == 'massMatrix': 852 if not compareList(auxinfo[0],['t']+self.vars): 853 print ['t']+self.vars 854 print "Auxinfo =", auxinfo[0] 855 raise ValueError("Invalid argument list given in Mass Matrix.") 856 auxparlist = ["t","x","parsinps"] 857 # special symbols to allow in parsing function body 858 specials = ["t","x"] 859 auxstr = auxinfo[1] 860 if any([pt in auxstr for pt in ('^', '**')]): 861 auxstr = convertPowers(auxstr, 'pow') 862 specvars = self.vars 863 specvars.sort() 864 specdict = {}.fromkeys(specvars) 865 if len(specvars) == 1: 866 assert '[' not in auxstr, \ 867 "'[' character invalid in mass matrix for 1D system" 868 assert ']' not in auxstr, \ 869 "']' character invalid in mass matrix for 1D system" 870 specdict[specvars.values()[0]] = auxstr 871 else: 872 specdict = parseMatrixStrToDictStr(auxstr, specvars) 873 reusestr, body_processed_dict = self._processReusedPy(specvars, 874 specdict, 875 specials=specials+specials_base) 876 body_processed = self._specStrParse(specvars, 877 body_processed_dict, 'xmat', 878 specials=specials+specials_base) 879 auxstr_py = self._genSpecFnPy('_auxfn_massMatrix', 880 reusestr+body_processed, 881 'xmat', specvars) 882 # check matrix 883 m = n = len(specvars) 884 specdict_check = {}.fromkeys(specvars) 885 for specname in specvars: 886 specdict_check[specname] = 1 + \ 887 count_sep(body_processed_dict[specname].replace("[","").replace("]","")) 888 body_processed = "" 889 for row in range(m): 890 if specdict_check[specvars[row]] != n: 891 print "Row %i: "%m, specdict[specvars[row]] 892 print "Found length %i"%specdict_check[specvars[row]] 893 raise ValueError("Mass matrix should be %sx%s"%(m,n)) 894 else: 895 user_parstr = makeParList(auxinfo[0]) 896 # `parsinps` is always added to allow reference to own 897 # parameters 898 if user_parstr == '': 899 # no arguments, user calls as fn() 900 auxparstr = 'parsinps' 901 else: 902 auxparstr = 'parsinps, ' + user_parstr 903 auxstr_py = 'def _auxfn_' + auxname + '(ds, ' + auxparstr \ 904 +'):\n' 905 auxparlist = auxparstr.replace(" ","").split(",") 906 badparnames = intersect(auxparlist, 907 remain(protectednames,auxnames)) 908 if badparnames != []: 909 print "Bad parameter names in auxiliary function", \ 910 auxname, ":", badparnames 911 #print auxinfo[0] 912 #print auxparlist 913 raise ValueError("Cannot use protected names for auxiliary " 914 "function parameters") 915 # special symbols to allow in parsing function body 916 specials = auxparlist 917 specials.remove('parsinps') 918 illegalterms = remain(self.vars + self.auxvars, specials) 919 auxstr = auxinfo[1] 920 if any([pt in auxstr for pt in ('^', '**')]): 921 auxstr = convertPowers(auxstr, 'pow') 922 reusestr, body_processed_dict = self._processReusedPy([auxname], 923 {auxname:auxstr}, 924 specials=specials+specials_base, 925 dovars=False, 926 illegal=illegalterms) 927 body_processed = self._specStrParse([auxname], 928 body_processed_dict, 929 specials=specials+specials_base, 930 dovars=False, 931 noreturndefs=True, 932 illegal=illegalterms) 933 auxstr_py += reusestr + _indentstr + 'return ' \ 934 + body_processed 935 # syntax validation done in makeUniqueFn 936 try: 937 auxfns[auxname] = makeUniqueFn(auxstr_py) 938 # Note: this automatically updates self._pyauxfns too 939 except: 940 print 'Error in supplied auxiliary spec dictionary code' 941 raise 942 auxfn_namemap['ds.'+auxname] = 'ds.'+auxfns[auxname][1] 943 # prepare user-interface wrapper function (not method) 944 if specials == [''] or specials == []: 945 fn_args = '' 946 else: 947 fn_args = ','+','.join(specials) 948 fn_elts = ['def ', auxname, '(self', fn_args, 949 ',__parsinps__=None):\n\t', 'if __parsinps__ is None:\n\t\t', 950 '__parsinps__=self.map_ixs(self.genref)\n\t', 951 'return self.genref.', auxfns[auxname][1], 952 '(__parsinps__', fn_args, ')\n'] 953 uafi[auxname] = ''.join(fn_elts) 954 # resolve inter-auxiliary function references 955 for auxname, auxspec in auxfns.iteritems(): 956 dummyQ = QuantSpec('dummy', auxspec[0], preserveSpace=True, 957 treatMultiRefs=False) 958 dummyQ.mapNames(auxfn_namemap) 959 auxfns[auxname] = (dummyQ(), auxspec[1]) 960 if pytarget: 961 self.auxfns = auxfns 962 # keep _pyauxfns handy for users to access python versions of functions 963 # from python, even using non-python target languages 964 # 965 # Changes to auxfns was already changing self._pyauxfns so the following line 966 # is not needed 967 #self._pyauxfns.update(auxfns) # same thing if pytarget==True 968 self._user_auxfn_interface = uafi 969 self._protected_auxnames.extend(auxnames)
970 971 972
973 - def _genSpecFnPy(self, name, specstr, resname, specnames, 974 docodeinserts=False):
975 # Set up function header 976 retstr = 'def '+name+'(ds, t, x, parsinps):\n' # print t, x, parsinps\n' 977 # add arbitrary code inserts, if present and option is switched on 978 # (only used for vector field definitions) 979 lstart = len(self.codeinserts['start']) 980 lend = len(self.codeinserts['end']) 981 if docodeinserts: 982 if lstart>0: 983 start_code = self._specStrParse(['inserts'], 984 {'inserts':self.codeinserts['start']}, '', 985 noreturndefs=True, ignoreothers=True, 986 doing_inserts=True) 987 else: 988 start_code = '' 989 if lend > 0: 990 end_code = self._specStrParse(['inserts'], 991 {'inserts':self.codeinserts['end']}, '', 992 noreturndefs=True, ignoreothers=True, 993 doing_inserts=True) 994 else: 995 end_code = '' 996 else: 997 start_code = end_code = '' 998 retstr += start_code + specstr + end_code 999 # Add the return line to the function 1000 if len(specnames) == 1: 1001 retstr += _indentstr + 'return array([' + resname + '0])\n' 1002 else: 1003 retstr += _indentstr + 'return array([' \ 1004 + makeParList(range(len(specnames)), resname) + '])\n' 1005 return retstr
1006 1007
1008 - def _genSpecPy(self):
1009 assert self.targetlang == 'python', ('Wrong target language for this' 1010 ' call') 1011 assert self.varspecs != {}, 'varspecs attribute must be defined' 1012 specnames_unsorted = self.varspecs.keys() 1013 _vbfs_inv = invertMap(self._varsbyforspec) 1014 # Process state variable specifications 1015 if len(_vbfs_inv) > 0: 1016 specname_vars = [] 1017 specname_auxvars = [] 1018 for varname in self.vars: 1019 # check if varname belongs to a for macro grouping in self.varspecs 1020 specname = _vbfs_inv[varname] 1021 if varname not in specname_vars: 1022 specname_vars.append(varname) 1023 for varname in self.auxvars: 1024 # check if varname belongs to a for macro grouping in self.varspecs 1025 specname = _vbfs_inv[varname] 1026 if varname not in specname_auxvars: 1027 specname_auxvars.append(varname) 1028 else: 1029 specname_vars = intersect(self.vars, specnames_unsorted) 1030 specname_auxvars = intersect(self.auxvars, specnames_unsorted) 1031 specname_vars.sort() 1032 for vn, vs in self.varspecs.items(): 1033 if any([pt in vs for pt in ('^', '**')]): 1034 self.varspecs[vn] = convertPowers(vs, 'pow') 1035 self.vars.sort() 1036 reusestr, specupdated = self._processReusedPy(specname_vars, 1037 self.varspecs) 1038 self.varspecs.update(specupdated) 1039 temp = self._specStrParse(specname_vars, self.varspecs, 'xnew') 1040 specstr_py = self._genSpecFnPy('_specfn', reusestr+temp, 'xnew', 1041 specname_vars, docodeinserts=True) 1042 # Process auxiliary variable specifications 1043 specname_auxvars.sort() 1044 assert self.auxvars == specname_auxvars, \ 1045 ('Mismatch between declared auxiliary' 1046 ' variable names and varspecs keys') 1047 reusestraux, specupdated = self._processReusedPy(specname_auxvars, 1048 self.varspecs) 1049 self.varspecs.update(specupdated) 1050 tempaux = self._specStrParse(specname_auxvars, self.varspecs, 'auxvals') 1051 auxspecstr_py = self._genSpecFnPy('_auxspecfn', reusestraux+tempaux, 1052 'auxvals', specname_auxvars) 1053 try: 1054 spec_info = makeUniqueFn(specstr_py) 1055 except SyntaxError: 1056 print "Syntax error in specification:\n", specstr_py 1057 raise 1058 try: 1059 auxspec_info = makeUniqueFn(auxspecstr_py) 1060 except SyntaxError: 1061 print "Syntax error in auxiliary spec:\n", auxspecstr_py 1062 raise 1063 self.spec = spec_info 1064 self.auxspec = auxspec_info
1065 1066
1067 - def _processReusedPy(self, specnames, specdict, specials=[], 1068 dovars=True, dopars=True, doinps=True, illegal=[]):
1069 """Process reused subexpression terms for Python code.""" 1070 1071 reused, specupdated, new_protected, order = _processReused(specnames, 1072 specdict, 1073 self.reuseterms, 1074 _indentstr) 1075 self._protected_reusenames = new_protected 1076 # symbols to parse are at indices 2 and 4 of 'reused' dictionary 1077 reusedParsed = self._parseReusedTermsPy(reused, [2,4], 1078 specials=specials, dovars=dovars, 1079 dopars=dopars, doinps=doinps, 1080 illegal=illegal) 1081 reusedefs = {}.fromkeys(new_protected) 1082 for vname, deflist in reusedParsed.iteritems(): 1083 for d in deflist: 1084 reusedefs[d[2]] = d 1085 return (concatStrDict(reusedefs, intersect(order,reusedefs.keys())), 1086 specupdated)
1087 1088
1089 - def _parseReusedTermsPy(self, d, symbol_ixs, specials=[], 1090 dovars=True, dopars=True, doinps=True, illegal=[]):
1091 """Process dictionary of reused term definitions (in spec syntax).""" 1092 # ... to parse special symbols to actual Python. 1093 # expect symbols to be processed at d list's entries given in 1094 # symbol_ixs. 1095 allnames = self.vars + self.pars + self.inputs + self.auxvars \ 1096 + ['abs'] + self._protected_auxnames \ 1097 + self._protected_scipynames + self._protected_specialfns \ 1098 + self._protected_macronames + self._protected_mathnames \ 1099 + self._protected_randomnames + self._protected_reusenames 1100 allnames = remain(allnames, illegal) 1101 if dovars: 1102 var_arrayixstr = dict(zip(self.vars, map(lambda i: str(i), \ 1103 range(len(self.vars))) )) 1104 aux_arrayixstr = dict(zip(self.auxvars, map(lambda i: str(i), \ 1105 range(len(self.auxvars))) )) 1106 else: 1107 var_arrayixstr = {} 1108 aux_arrayixstr = {} 1109 if dopars: 1110 if doinps: 1111 # parsinps_names is pars and inputs, each sorted 1112 # *individually* 1113 parsinps_names = self.pars+self.inputs 1114 else: 1115 parsinps_names = self.pars 1116 parsinps_arrayixstr = dict(zip(parsinps_names, 1117 map(lambda i: str(i), \ 1118 range(len(parsinps_names))) )) 1119 else: 1120 parsinps_names = [] 1121 parsinps_arrayixstr = {} 1122 specialtokens = remain(allnames,specials) + ['(', 't'] + specials 1123 for specname, itemlist in d.iteritems(): 1124 listix = -1 1125 for strlist in itemlist: 1126 listix += 1 1127 if strlist == []: 1128 continue 1129 if len(strlist) < max(symbol_ixs): 1130 raise ValueError("Symbol indices out of range in " 1131 "call to _parseReusedTermsPy") 1132 for ix in symbol_ixs: 1133 symbol = strlist[ix] 1134 parsedsymbol = self.__processTokens(allnames, 1135 specialtokens, symbol, 1136 var_arrayixstr, aux_arrayixstr, 1137 parsinps_names, parsinps_arrayixstr, 1138 specname) 1139 # must strip possible trailing whitespace! 1140 d[specname][listix][ix] = parsedsymbol.strip() 1141 return d
1142 1143
1144 - def _specStrParse(self, specnames, specdict, resname='', specials=[], 1145 dovars=True, dopars=True, doinps=True, 1146 noreturndefs=False, forexternal=False, illegal=[], 1147 ignoreothers=False, doing_inserts=False):
1148 # use 'noreturndefs' switch if calling this function just to "parse" 1149 # a spec string for other purposes, e.g. for using in an event setup 1150 # or an individual auxiliary function spec 1151 assert isinstance(specnames, list), "specnames must be a list" 1152 if noreturndefs or forexternal: 1153 assert len(specnames) == 1, ("can only pass a single specname for " 1154 "'forexternal' or 'noreturndefs' options") 1155 allnames = self.vars + self.pars + self.inputs + self.auxvars \ 1156 + ['abs', 'and', 'or', 'not', 'True', 'False'] \ 1157 + self._protected_auxnames \ 1158 + self._protected_scipynames + self._protected_specialfns \ 1159 + self._protected_macronames + self._protected_mathnames \ 1160 + self._protected_randomnames + self._protected_reusenames 1161 allnames = remain(allnames, illegal) 1162 if dovars: 1163 if forexternal: 1164 var_arrayixstr = dict(zip(self.vars, 1165 ["'"+v+"'" for v in self.vars])) 1166 aux_arrayixstr = dict(zip(self.auxvars, 1167 ["'"+v+"'" for v in self.auxvars])) 1168 else: 1169 var_arrayixstr = dict(zip(self.vars, map(lambda i: str(i), \ 1170 range(len(self.vars))) )) 1171 aux_arrayixstr = dict(zip(self.auxvars, map(lambda i: str(i),\ 1172 range(len(self.auxvars))) )) 1173 else: 1174 var_arrayixstr = {} 1175 aux_arrayixstr = {} 1176 # ODE solvers typically don't recognize external inputs 1177 # so they have to be lumped in with the parameters 1178 # argument `parsinps` holds the combined pars and inputs 1179 if dopars: 1180 if forexternal: 1181 if doinps: 1182 # parsinps_names is pars and inputs, each sorted 1183 # *individually* 1184 parsinps_names = self.pars+self.inputs 1185 else: 1186 parsinps_names = self.pars 1187 # for external calls we want parname -> 'parname' 1188 parsinps_arrayixstr = dict(zip(parsinps_names, 1189 ["'"+pn+"'" for pn in parsinps_names])) 1190 else: 1191 if doinps: 1192 # parsinps_names is pars and inputs, each sorted 1193 # *individually* 1194 parsinps_names = self.pars+self.inputs 1195 else: 1196 parsinps_names = self.pars 1197 parsinps_arrayixstr = dict(zip(parsinps_names, 1198 map(lambda i: str(i), \ 1199 range(len(parsinps_names))) )) 1200 else: 1201 parsinps_names = [] 1202 parsinps_arrayixstr = {} 1203 specialtokens = remain(allnames,specials) + ['(', 't'] \ 1204 + remain(specials,['t']) 1205 specstr_lang = '' 1206 specname_count = 0 1207 for specname in specnames: 1208 specstr = specdict[specname] 1209 assert type(specstr)==str, "Specification for %s was not a string"%specname 1210 if not noreturndefs: 1211 specstr_lang += _indentstr + resname+str(specname_count)+' = ' 1212 specname_count += 1 1213 specstr_lang += self.__processTokens(allnames, specialtokens, 1214 specstr, var_arrayixstr, 1215 aux_arrayixstr, parsinps_names, 1216 parsinps_arrayixstr, specname, ignoreothers, 1217 doing_inserts) 1218 if not noreturndefs or not forexternal: 1219 specstr_lang += '\n' # prepare for next line 1220 return specstr_lang
1221 1222
1223 - def __processTokens(self, allnames, specialtokens, specstr, 1224 var_arrayixstr, aux_arrayixstr, parsinps_names, 1225 parsinps_arrayixstr, specname, ignoreothers=False, 1226 doing_inserts=False):
1227 # This function is an earlier version of parseUtils.py's 1228 # parse method of a parserObject. 1229 # This function should be replaced with an adapted version 1230 # of parserObject that can handle auxiliary function call 1231 # parsing and the inbuilt macros. This is some of the worst-organized 1232 # code I ever wrote, early in my experience with Python. My apologies... 1233 returnstr = '' 1234 if specstr[-1] != ')': 1235 # temporary hack because strings not ending in ) lose their last 1236 # character! 1237 specstr += ' ' 1238 scount = 0 1239 speclen = len(specstr) 1240 valid_depnames = self.vars+self.auxvars 1241 s = '' 1242 ignore_list = ['', ' ', '\n'] + allnames 1243 foundtoken = False 1244 # initial value for special treatment of the 'initcond' built-in 1245 # auxiliary function's argument 1246 strname_arg_imminent = False 1247 auxfn_args_imminent = False 1248 while scount < speclen: 1249 stemp = specstr[scount] 1250 scount += 1 1251 if name_chars_RE.match(stemp) is None: 1252 # found a non-alphanumeric char 1253 # so just add to returnstr with accumulated s characters 1254 # (these will have been deleted if s contained a target 1255 # name) 1256 if not ignoreothers and s not in ignore_list: 1257 # adding allnames catches var names etc. that are valid 1258 # in auxiliary functions but are not special tokens 1259 # and must be left alone 1260 print "Error in specification `" + specname + \ 1261 "` with token `"+s+"` :\n", specstr 1262 raise ValueError('Undeclared or illegal token `'+s+'` in' 1263 ' spec string `'+specname+'`') 1264 if stemp == '^' and self.targetlang == 'python': 1265 raise ValueError('Character `^` is not allowed. ' 1266 'Please use the pow() call') 1267 if stemp == '(': 1268 returnstr += s 1269 s = stemp 1270 else: 1271 returnstr += s 1272 if len(returnstr)>1 and stemp == returnstr[-1] == "*": 1273 # check for ** case 1274 raise ValueError('Operator ** is not allowed. ' 1275 'Please use the pow() call') 1276 returnstr += stemp 1277 s = '' 1278 continue 1279 else: 1280 if s == '' and stemp not in num_chars: 1281 s += stemp 1282 elif s != '': 1283 s += stemp 1284 else: 1285 returnstr += stemp 1286 continue 1287 if s in specialtokens + self._ignorespecial: 1288 if s != '(': 1289 if scount < speclen - 1: 1290 if name_chars_RE.match(specstr[scount]) is None: 1291 foundtoken = True 1292 else: 1293 if s in ['e','E'] and \ 1294 name_chars_RE.match(specstr[scount]).group() \ 1295 in num_chars+['-']: 1296 # not expecting an arithmetic symbol or space 1297 # ... we *are* expecting a numeric 1298 foundtoken = True 1299 else: 1300 foundtoken = True 1301 else: 1302 foundtoken = True 1303 if foundtoken: 1304 if s == '(': 1305 if auxfn_args_imminent: 1306 returnstr += s+'parsinps, ' 1307 auxfn_args_imminent = False 1308 else: 1309 returnstr += s 1310 elif s == 'abs': 1311 returnstr += s 1312 elif s in var_arrayixstr and \ 1313 (len(returnstr)==0 or len(returnstr)>0 and \ 1314 returnstr[-1] not in ["'", '"']): 1315 if strname_arg_imminent: 1316 returnstr += "'"+s+"'" 1317 strname_arg_imminent = False 1318 else: 1319 if specname in valid_depnames \ 1320 and (specname, s) not in self.dependencies: 1321 self.dependencies.append((specname,s)) 1322 returnstr += 'x['+var_arrayixstr[s]+']' 1323 elif s in aux_arrayixstr: 1324 if strname_arg_imminent: 1325 returnstr += "'"+s+"'" 1326 strname_arg_imminent = False 1327 else: 1328 print "Spec name:", specname 1329 print "Spec string:", specstr 1330 print "Problem symbol:", s 1331 raise NameError('auxiliary variables cannot ' 1332 'appear on any right-hand side ' 1333 'except their initial value') 1334 elif s in parsinps_arrayixstr and \ 1335 (len(returnstr)==0 or len(returnstr)>0 and \ 1336 returnstr[-1] not in ["'", '"']): 1337 if s in self.inputs: 1338 if specname in valid_depnames and \ 1339 (specname, s) not in self.dependencies: 1340 self.dependencies.append((specname,s)) 1341 if strname_arg_imminent: 1342 returnstr += "'"+s+"'" 1343 strname_arg_imminent = False 1344 else: 1345 returnstr += 'parsinps[' + \ 1346 parsinps_arrayixstr[s] + ']' 1347 elif s in self._protected_mathnames: 1348 if s in ['e','E']: 1349 # special case where e is either = exp(0) 1350 # as a constant or it's an exponent in 1e-4 1351 if len(returnstr)>0: 1352 if returnstr[-1] not in num_chars+['.']: 1353 returnstr += 'math.'+s.lower() 1354 else: 1355 returnstr += s 1356 else: 1357 returnstr += 'math.'+s.lower() 1358 else: 1359 returnstr += 'math.'+s 1360 elif s in self._protected_randomnames: 1361 if len(returnstr) > 0: 1362 if returnstr[-1] == '.': 1363 # not a standalone name (e.g. "sample" may be a method call 1364 # in an embedded system) 1365 returnstr += s 1366 else: 1367 returnstr += 'random.'+s 1368 else: 1369 returnstr += 'random.'+s 1370 elif s in self._protected_scipynames: 1371 if len(returnstr) > 0: 1372 if returnstr[-1] == '.': 1373 # not a standalone name (e.g. may be a method call in an 1374 # embedded system) 1375 returnstr += s 1376 else: 1377 returnstr += 'scipy.'+s 1378 else: 1379 returnstr += 'scipy.'+s 1380 elif s in self._protected_specialfns: 1381 if self.targetlang != 'python': 1382 print "Function %s is currently not supported "%s, \ 1383 "outside of python target language definitions" 1384 raise ValueError("Invalid special function for " 1385 "non-python target definition") 1386 # replace the underscore in the name with a dot 1387 # to access scipy.special 1388 returnstr += 'scipy.'+s.replace('_','.') 1389 elif s in self._protected_macronames: 1390 if doing_inserts: 1391 # Code inserts don't use macro versions of "if", "for", etc. 1392 # They are interpreted as regular python 1393 returnstr += s 1394 else: 1395 if specname in self._pyauxfns: 1396 # remove vars, auxs, inputs 1397 to_remove = self.vars + self.auxvars + self.inputs 1398 filtfunc = lambda n: n not in to_remove 1399 specialtokens_temp = filter(filtfunc, 1400 specialtokens+self._ignorespecial) 1401 else: 1402 specialtokens_temp = specialtokens+self._ignorespecial 1403 if s == 'if': 1404 # hack for special 'if' case 1405 # read contents of braces 1406 endargbrace = findEndBrace(specstr[scount:]) \ 1407 + scount + 1 1408 argstr = specstr[scount:endargbrace] 1409 procstr = self.__processTokens(allnames, 1410 specialtokens_temp, argstr, 1411 var_arrayixstr, 1412 aux_arrayixstr, parsinps_names, 1413 parsinps_arrayixstr, specname) 1414 arginfo = readArgs(procstr) 1415 if not arginfo[0]: 1416 raise ValueError('Error finding ' 1417 'arguments applicable to `if` ' 1418 'macro') 1419 # advance pointer in specstr according to 1420 # how many tokens/characters were read in for 1421 # the argument list 1422 scount += len(argstr) # not arginfo[2] 1423 arglist = arginfo[1] 1424 assert len(arglist) == 3, ('Wrong number of' 1425 ' arguments passed to `if`' 1426 ' macro. Expected 3') 1427 returnstr += 'ds.' + self._pyauxfns[s][1] + \ 1428 '(parsinps, '+procstr[1:] 1429 elif s == 'for': 1430 raise ValueError('Macro '+s+' cannot ' 1431 'be used here') 1432 elif s == 'sum': 1433 endargbrace = findEndBrace(specstr[scount:]) \ 1434 + scount + 1 1435 argstr = specstr[scount:endargbrace] 1436 arginfo = readArgs(argstr) 1437 if not arginfo[0]: 1438 raise ValueError('Error finding ' 1439 'arguments applicable to `sum` ' 1440 'macro') 1441 arglist = arginfo[1] 1442 assert len(arglist) == 4, ('Wrong number of' 1443 ' arguments passed to `sum`' 1444 ' macro. Expected 4') 1445 # advance pointer in specstr according to 1446 # how many tokens/characters were read in for 1447 # the argument list 1448 scount += len(argstr) 1449 # recursively process main argument 1450 returnstr += self.__processTokens(allnames, 1451 specialtokens_temp, 1452 self._macroSum(*arglist), var_arrayixstr, 1453 aux_arrayixstr, parsinps_names, 1454 parsinps_arrayixstr, specname) 1455 else: 1456 # max and min just pass through 1457 returnstr += s 1458 elif s in self._protected_auxnames: 1459 if s in ['initcond', 'getbound']: 1460 # must prepare parser for upcoming variable 1461 # name in argument that must only be 1462 # converted to its index in x[] 1463 strname_arg_imminent = True 1464 # add internal prefix (to avoid method name clashes 1465 # in DS objects, for instance) unless built-in function 1466 returnstr += 'ds.' + self._pyauxfns[s][1] 1467 auxfn_args_imminent = True 1468 elif s in self._pyauxfns: 1469 # treat inter-aux function dependencies: 1470 # any protected auxnames will already have been 1471 # processed because this is placed after that check. 1472 # don't reference self._pyauxfns[s] because it doesn't 1473 # contain the processed definition of the function. 1474 if s in ['initcond', 'getbound']: 1475 # must prepare parser for upcoming variable 1476 # name in argument that must only be 1477 # converted to its index in x[] 1478 strname_arg_imminent = True 1479 # add internal prefix (to avoid method name clashes 1480 # in DS objects, for instance) unless built-in function 1481 returnstr += 'ds.' + s 1482 auxfn_args_imminent = True 1483 elif s in self._protected_reusenames: 1484 returnstr += s 1485 else: 1486 # s is e.g. a declared argument to an aux fn but 1487 # only want to ensure it is present. no action to take. 1488 returnstr += s 1489 # reset for next iteration 1490 s = '' 1491 foundtoken = False 1492 # end of scount while loop 1493 return returnstr
1494 1495 1496 # --------------------- C code specifications ----------------------- 1497
1498 - def _processReusedC(self, specnames, specdict):
1499 """Process reused subexpression terms for C code.""" 1500 1501 if self.auxfns: 1502 def addParToCall(s): 1503 return addArgToCalls(self._processSpecialC(s), 1504 self.auxfns.keys(), "p_, wk_, xv_")
1505 parseFunc = addParToCall 1506 else: 1507 parseFunc = self._processSpecialC 1508 reused, specupdated, new_protected, order = _processReused(specnames, 1509 specdict, 1510 self.reuseterms, 1511 '', 'double', ';', 1512 parseFunc) 1513 self._protected_reusenames = new_protected 1514 reusedefs = {}.fromkeys(new_protected) 1515 for vname, deflist in reused.iteritems(): 1516 for d in deflist: 1517 reusedefs[d[2]] = d 1518 return (concatStrDict(reusedefs, intersect(order, reusedefs.keys())), 1519 specupdated)
1520 1521
1522 - def _genAuxFnC(self):
1523 auxnames = self._auxfnspecs.keys() 1524 # parameter and variable definitions 1525 # sorted version of var and par names sorted version of par 1526 # names (vars not #define'd in aux functions unless Jacobian) 1527 vnames = self.vars 1528 pnames = self.pars 1529 vnames.sort() 1530 pnames.sort() 1531 for auxname in auxnames: 1532 assert auxname not in ['auxvars', 'vfieldfunc'], \ 1533 ("auxiliary function name '" +auxname+ "' clashes with internal" 1534 " names") 1535 # must add parameter argument so that we can name 1536 # parameters inside the functions! this would either 1537 # require all calls to include this argument (yuk!) or 1538 # else we add these extra parameters automatically to 1539 # every call found in the .c code (as is done currently. 1540 # this is still an untidy solution, but there you go...) 1541 for auxname in auxnames: 1542 auxspec = self._auxfnspecs[auxname] 1543 assert len(auxspec) == 2, 'auxspec tuple must be of length 2' 1544 if not isinstance(auxspec[0], list): 1545 print "Found type ", type(auxspec[0]) 1546 print "Containing: ", auxspec[0] 1547 raise TypeError('aux function arguments ' 1548 'must be given as a list') 1549 if not isinstance(auxspec[1], str): 1550 print "Found type ", type(auxspec[1]) 1551 print "Containing: ", auxspec[1] 1552 raise TypeError('aux function specification ' 1553 'must be a string of the function code') 1554 # Process Jacobian functions specially, if present 1555 if auxname == 'Jacobian': 1556 sig = "void jacobian(" 1557 if not compareList(auxspec[0],['t']+self.vars): 1558 print ['t']+self.vars 1559 print "Auxspec =", auxspec[0] 1560 raise ValueError("Invalid argument list given in Jacobian.") 1561 if any([pt in auxspec[1] for pt in ('^', '**')]): 1562 auxstr = convertPowers(auxspec[1], 'pow') 1563 else: 1564 auxstr = auxspec[1] 1565 parlist = "unsigned n_, unsigned np_, double t, double *Y_," 1566 ismat = True 1567 sig += parlist + " double *p_, double **f_, unsigned wkn_, double *wk_, unsigned xvn_, double *xv_)" 1568 specvars = self.vars 1569 specvars.sort() 1570 n = len(specvars) 1571 m = n 1572 specdict_temp = {}.fromkeys(specvars) 1573 if m == 1: 1574 assert '[' not in auxstr, \ 1575 "'[' character invalid in Jacobian for 1D system" 1576 assert ']' not in auxstr, \ 1577 "']' character invalid in Jacobian for 1D system" 1578 specdict_temp[specvars[0]] = auxstr 1579 else: 1580 specdict_temp = parseMatrixStrToDictStr(auxstr, specvars) 1581 reusestr, body_processed_dict = self._processReusedC(specvars, 1582 specdict_temp) 1583 specdict = {}.fromkeys(specvars) 1584 for specname in specvars: 1585 temp = body_processed_dict[specname] 1586 specdict[specname] = splitargs(temp.replace("[","").replace("]","")) 1587 body_processed = "" 1588 # C integrators expect column-major matrices 1589 for col in range(n): 1590 for row in range(m): 1591 try: 1592 body_processed += "f_[" + str(col) + "][" + str(row) \ 1593 + "] = " + specdict[specvars[row]][col] + ";\n" 1594 except IndexError: 1595 raise ValueError("Jacobian should be %sx%s"%(m,n)) 1596 body_processed += "\n" 1597 auxspec_processedDict = {auxname: body_processed} 1598 elif auxname == 'Jacobian_pars': 1599 sig = "void jacobianParam(" 1600 if not compareList(auxspec[0],['t']+self.vars): 1601 print ['t']+self.vars 1602 print "Auxspec =", auxspec[0] 1603 raise ValueError("Invalid argument list given in Jacobian.") 1604 parlist = "unsigned n_, unsigned np_, double t, double *Y_," 1605 if any([pt in auxspec[1] for pt in ('^', '**')]): 1606 auxstr = convertPowers(auxspec[1], 'pow') 1607 else: 1608 auxstr = auxspec[1] 1609 ismat = True 1610 # specials = ["t","Y_","n_","np_","wkn_","wk_"] 1611 sig += parlist + " double *p_, double **f_, unsigned wkn_, double *wk_, unsigned xvn_, double *xv_)" 1612 specvars = self.vars 1613 specvars.sort() 1614 n = len(specvars) 1615 if n == 0: 1616 raise ValueError("Cannot have a Jacobian w.r.t. pars" 1617 " because no pars are defined") 1618 m = len(self.vars) 1619 specdict_temp = {}.fromkeys(self.vars) 1620 if m == n == 1: 1621 assert '[' not in auxstr, \ 1622 "'[' character invalid in Jacobian for 1D system" 1623 assert ']' not in auxstr, \ 1624 "']' character invalid in Jacobian for 1D system" 1625 specdict_temp[self.vars.values()[0]] = auxstr 1626 else: 1627 specdict_temp = parseMatrixStrToDictStr(auxstr, self.vars, m) 1628 reusestr, body_processed_dict = self._processReusedC(self.vars, 1629 specdict_temp) 1630 specdict = {}.fromkeys(self.vars) 1631 for specname in self.vars: 1632 temp = body_processed_dict[specname] 1633 specdict[specname] = splitargs(temp.replace("[","").replace("]","")) 1634 body_processed = "" 1635 # C integrators expect column-major matrices 1636 for col in range(n): 1637 for row in range(m): 1638 try: 1639 body_processed += "f_[" + str(col) + "][" + str(row) \ 1640 + "] = " + specdict[self.vars[row]][col] + ";\n" 1641 except (IndexError, KeyError): 1642 print "\nFound matrix:\n" 1643 info(specdict) 1644 raise ValueError("Jacobian should be %sx%s"%(m,n)) 1645 body_processed += "\n" 1646 auxspec_processedDict = {auxname: body_processed} 1647 elif auxname == 'massMatrix': 1648 sig = "void massMatrix(" 1649 if not compareList(auxspec[0],['t']+self.vars): 1650 raise ValueError("Invalid argument list given in Mass Matrix.") 1651 if any([pt in auxspec[1] for pt in ('^', '**')]): 1652 auxstr = convertPowers(auxspec[1], 'pow') 1653 else: 1654 auxstr = auxspec[1] 1655 parlist = "unsigned n_, unsigned np_," 1656 ismat = True 1657 # specials = ["n_","np_","wkn_","wk_"] 1658 sig += parlist + " double t, double *Y_, double *p_, double **f_, unsigned wkn_, double *wk_, unsigned xvn_, double *xv_)" 1659 specvars = self.vars 1660 specvars.sort() 1661 n = len(specvars) 1662 m = n 1663 specdict_temp = {}.fromkeys(specvars) 1664 if m == 1: 1665 assert '[' not in auxstr, \ 1666 "'[' character invalid in mass matrix for 1D system" 1667 assert ']' not in auxstr, \ 1668 "']' character invalid in mass matrix for 1D system" 1669 specdict_temp[specvars.values()[0]] = auxstr 1670 else: 1671 specdict_temp = parseMatrixStrToDictStr(auxstr, specvars, m) 1672 reusestr, body_processed_dict = self._processReusedC(specvars, 1673 specdict_temp) 1674 specdict = {}.fromkeys(specvars) 1675 for specname in specvars: 1676 temp = body_processed_dict[specname].replace("[","").replace("]","") 1677 specdict[specname] = splitargs(temp) 1678 body_processed = "" 1679 # C integrators expect column-major matrices 1680 for col in range(n): 1681 for row in range(m): 1682 try: 1683 body_processed += "f_[" + str(col) + "][" + str(row) \ 1684 + "] = " + specdict[specvars[row]][col] + ";\n" 1685 except KeyError: 1686 raise ValueError("Mass matrix should be %sx%s"%(m,n)) 1687 body_processed += "\n" 1688 auxspec_processedDict = {auxname: body_processed} 1689 else: 1690 ismat = False 1691 sig = "double " + auxname + "(" 1692 parlist = "" 1693 namemap = {} 1694 for parname in auxspec[0]: 1695 if parname == '': 1696 continue 1697 parlist += "double " + "__" + parname + "__, " 1698 namemap[parname] = '__'+parname+'__' 1699 sig += parlist + "double *p_, double *wk_, double *xv_)" 1700 auxstr = auxspec[1] 1701 if any([pt in auxspec[1] for pt in ('^', '**')]): 1702 auxstr = convertPowers(auxstr, 'pow') 1703 prep_auxstr = self._processSpecialC(auxstr) 1704 prep_auxstr_quant = QuantSpec('prep_q', 1705 prep_auxstr.replace(' ','').replace('\n',''), 1706 treatMultiRefs=False, preserveSpace=True) 1707 # have to do name map now in case function's formal arguments 1708 # coincide with state variable names, which may get tied up 1709 # in reused terms and not properly matched to the formal args. 1710 prep_auxstr_quant.mapNames(namemap) 1711 auxspec = (auxspec[0], prep_auxstr_quant()) 1712 reusestr, auxspec_processedDict = self._processReusedC([auxname], 1713 {auxname:auxspec[1]}) 1714 # addition of parameter done in Generator code 1715 # dummyQ = QuantSpec('dummy', auxspec_processedDict[auxname]) 1716 # auxspec_processed = "" 1717 # # add pars argument to inter-aux fn call 1718 # auxfn_found = False # then expect a left brace next 1719 # for tok in dummyQ: 1720 # if auxfn_found: 1721 # # expect left brace in this tok 1722 # if tok == '(': 1723 # auxspec_processed += tok + 'p_, ' 1724 # auxfn_found = False 1725 # else: 1726 # raise ValueError("Problem parsing inter-auxiliary" 1727 # " function call") 1728 # elif tok in self.auxfns and tok not in \ 1729 # ['Jacobian', 'Jacobian_pars']: 1730 # auxfn_found = True 1731 # auxspec_processed += tok 1732 # else: 1733 # auxspec_processed += tok 1734 # body_processed = "return "+auxspec_processed + ";\n\n" 1735 # add underscore to local names, to avoid clash with global 1736 # '#define' names 1737 dummyQ = QuantSpec('dummy', auxspec_processedDict[auxname], 1738 treatMultiRefs=False, preserveSpace=True) 1739 body_processed = "return "*(not ismat) + dummyQ() + ";\n\n" 1740 # auxspecstr = sig + " {\n\n" + pardefines + vardefines*ismat \ 1741 auxspecstr = sig + " {\n\n" \ 1742 + "\n" + (len(reusestr)>0)*"/* reused term definitions */\n" \ 1743 + reusestr + (len(reusestr)>0)*"\n" + body_processed \ 1744 + "}" 1745 # + parundefines + varundefines*ismat + "}" 1746 # sig as second entry, whereas Python-coded specifications 1747 # have the fn name there 1748 self.auxfns[auxname] = (auxspecstr, sig) 1749 # Don't apply #define's for built-in functions 1750 self.auxfns['heav'] = ("int heav(double x_, double *p_, double *wk_, double *xv_) {\n" \ 1751 + " if (x_>0.0) {return 1;} else {return 0;}\n}", 1752 "int heav(double x_, double *p_, double *wk_, double *xv_)") 1753 self.auxfns['__rhs_if'] = ("double __rhs_if(int cond_, double e1_, " \ 1754 + "double e2_, double *p_, double *wk_, double *xv_) {\n" \ 1755 + " if (cond_) {return e1_;} else {return e2_;};\n}", 1756 "double __rhs_if(int cond_, double e1_, double e2_, double *p_, double *wk_, double *xv_)") 1757 self.auxfns['__maxof2'] = ("double __maxof2(double e1_, double e2_, double *p_, double *wk_, double *xv_) {\n" \ 1758 + "if (e1_ > e2_) {return e1_;} else {return e2_;};\n}", 1759 "double __maxof2(double e1_, double e2_, double *p_, double *wk_, double *xv_)") 1760 self.auxfns['__minof2'] = ("double __minof2(double e1_, double e2_, double *p_, double *wk_, double *xv_) {\n" \ 1761 + "if (e1_ < e2_) {return e1_;} else {return e2_;};\n}", 1762 "double __minof2(double e1_, double e2_, double *p_, double *wk_, double *xv_)") 1763 self.auxfns['__maxof3'] = ("double __maxof3(double e1_, double e2_, double e3_, double *p_, double *wk_, double *xv_) {\n" \ 1764 + "double temp_;\nif (e1_ > e2_) {temp_ = e1_;} else {temp_ = e2_;};\n" \ 1765 + "if (e3_ > temp_) {return e3_;} else {return temp_;};\n}", 1766 "double __maxof3(double e1_, double e2_, double e3_, double *p_, double *wk_, double *xv_)") 1767 self.auxfns['__minof3'] = ("double __minof3(double e1_, double e2_, double e3_, double *p_, double *wk_, double *xv_) {\n" \ 1768 + "double temp_;\nif (e1_ < e2_) {temp_ = e1_;} else {temp_ = e2_;};\n" \ 1769 + "if (e3_ < temp_) {return e3_;} else {return temp_;};\n}", 1770 "double __minof3(double e1_, double e2_, double e3_, double *p_, double *wk_, double *xv_)") 1771 self.auxfns['__maxof4'] = ("double __maxof4(double e1_, double e2_, double e3_, double e4_, double *p_, double *wk_, double *xv_) {\n" \ 1772 + "double temp_;\nif (e1_ > e2_) {temp_ = e1_;} else {temp_ = e2_;};\n" \ 1773 + "if (e3_ > temp_) {temp_ = e3_;};\nif (e4_ > temp_) {return e4_;} else {return temp_;};\n}", 1774 "double __maxof4(double e1_, double e2_, double e3_, double e4_, double *p_, double *wk_, double *xv_)") 1775 self.auxfns['__minof4'] = ("double __minof4(double e1_, double e2_, double e3_, double e4_, double *p_, double *wk_, double *xv_) {\n" \ 1776 + "double temp_;\nif (e1_ < e2_) {temp_ = e1_;} else {temp_ = e2_;};\n" \ 1777 + "if (e3_ < temp_) {temp_ = e3_;};\nif (e4_ < temp_) {return e4_;} else {return temp_;};\n}", 1778 "double __minof4(double e1_, double e2_, double e3_, double e4_, double *p_, double *wk_, double *xv_)") 1779 # temporary placeholders for these built-ins... 1780 cases_ic = "" 1781 cases_index = "" 1782 for i in xrange(len(self.vars)): 1783 if i == 0: 1784 command = 'if' 1785 else: 1786 command = 'else if' 1787 vname = self.vars[i] 1788 cases_ic += " " + command + " (strcmp(varname, " + '"' + vname + '"'\ 1789 + ")==0)\n\treturn gICs[" + str(i) + "];\n" 1790 cases_index += " " + command + " (strcmp(name, " + '"' + vname + '"'\ 1791 + ")==0)\n\treturn " + str(i) + ";\n" 1792 # add remaining par names for getindex 1793 for i in xrange(len(self.pars)): 1794 pname = self.pars[i] 1795 cases_index += " else if" + " (strcmp(name, " + '"' + pname + '"'\ 1796 +")==0)\n\treturn " + str(i+len(self.vars)) + ";\n" 1797 cases_ic += """ else {\n\tfprintf(stderr, "Invalid variable name %s for """ \ 1798 + """initcond call\\n", varname);\n\treturn 0.0/0.0;\n\t}\n""" 1799 cases_index += """ else {\n\tfprintf(stderr, "Invalid name %s for """ \ 1800 + """getindex call\\n", name);\n\treturn 0.0/0.0;\n\t}\n""" 1801 self.auxfns['initcond'] = ("double initcond(char *varname, double *p_, double *wk_, double *xv_) {\n" \ 1802 + "\n" + cases_ic + "}", 1803 'double initcond(char *varname, double *p_, double *wk_, double *xv_)') 1804 self.auxfns['getindex'] = ("int getindex(char *name, double *p_, double *wk_, double *xv_) {\n" \ 1805 + "\n" + cases_index + "}", 1806 'int getindex(char *name, double *p_, double *wk_, double *xv_)') 1807 self.auxfns['globalindepvar'] = ("double globalindepvar(double t, double *p_, double *wk_, double *xv_)" \ 1808 + " {\n return globalt0+t;\n}", 1809 'double globalindepvar(double t, double *p_, double *wk_, double *xv_)') 1810 self.auxfns['getbound'] = \ 1811 ("double getbound(char *name, int which_bd, double *p_, double *wk_, double *xv_) {\n" \ 1812 + " return gBds[which_bd][getindex(name)];\n}", 1813 'double getbound(char *name, int which_bd, double *p_, double *wk_, double *xv_)')
1814 1815
1816 - def _genSpecC(self):
1817 assert self.targetlang == 'c', ('Wrong target language for this' 1818 ' call') 1819 assert self.varspecs != {}, 'varspecs attribute must be defined' 1820 specnames_unsorted = self.varspecs.keys() 1821 _vbfs_inv = invertMap(self._varsbyforspec) 1822 # Process state variable specifications 1823 if len(_vbfs_inv) > 0: 1824 specname_vars = [] 1825 specname_auxvars = [] 1826 for varname in self.vars: 1827 # check if varname belongs to a for macro grouping in self.varspecs 1828 specname = _vbfs_inv[varname] 1829 if varname not in specname_vars: 1830 specname_vars.append(varname) 1831 for varname in self.auxvars: 1832 # check if varname belongs to a for macro grouping in self.varspecs 1833 specname = _vbfs_inv[varname] 1834 if varname not in specname_auxvars: 1835 specname_auxvars.append(varname) 1836 else: 1837 specname_vars = intersect(self.vars, specnames_unsorted) 1838 specname_auxvars = intersect(self.auxvars, specnames_unsorted) 1839 specname_vars.sort() 1840 # sorted version of var and par names 1841 vnames = specname_vars 1842 pnames = self.pars 1843 inames = self.inputs 1844 pnames.sort() 1845 inames.sort() 1846 pardefines = "" 1847 vardefines = "" 1848 inpdefines = "" 1849 parundefines = "" 1850 varundefines = "" 1851 inpundefines = "" 1852 # produce vector field specification 1853 assert self.vars == specname_vars, ('Mismatch between declared ' 1854 ' variable names and varspecs keys') 1855 valid_depTargNames = self.inputs+self.vars+self.auxvars 1856 for specname, specstr in self.varspecs.iteritems(): 1857 assert type(specstr)==str, "Specification for %s was not a string"%specname 1858 if any([pt in specstr for pt in ('^', '**')]): 1859 specstr = convertPowers(specstr, 'pow') 1860 specQS = QuantSpec('__spectemp__', specstr) 1861 for s in specQS: 1862 if s in valid_depTargNames and (specname, s) not in \ 1863 self.dependencies: # and specname != s: 1864 self.dependencies.append((specname, s)) 1865 # pre-process reused sub-expression dictionary to adapt for 1866 # known calling sequence in C 1867 reusestr, specupdated = self._processReusedC(specname_vars, 1868 self.varspecs) 1869 self.varspecs.update(specupdated) 1870 specstr_C = self._genSpecFnC('vfieldfunc', reusestr, specname_vars, 1871 pardefines, vardefines, inpdefines, 1872 parundefines, varundefines, inpundefines, 1873 True) 1874 self.spec = specstr_C 1875 # produce auxiliary variables specification 1876 specname_auxvars.sort() 1877 assert self.auxvars == specname_auxvars, \ 1878 ('Mismatch between declared auxiliary' 1879 ' variable names and varspecs keys') 1880 if self.auxvars != []: 1881 reusestraux, specupdated = self._processReusedC(specname_auxvars, 1882 self.varspecs) 1883 self.varspecs.update(specupdated) 1884 if self.auxvars == []: 1885 auxspecstr_C = self._genSpecFnC('auxvars', '', 1886 specname_auxvars, 1887 '', '', '', 1888 '', '', '', False) 1889 else: 1890 auxspecstr_C = self._genSpecFnC('auxvars', reusestraux, 1891 specname_auxvars, pardefines, 1892 vardefines, inpdefines, parundefines, 1893 varundefines, inpundefines, 1894 False) 1895 self.auxspec = auxspecstr_C
1896 1897
1898 - def _genSpecFnC(self, funcname, reusestr, specnames, pardefines, 1899 vardefines, inpdefines, parundefines, varundefines, 1900 inpundefines, docodeinserts):
1901 sig = "void " + funcname + "(unsigned n_, unsigned np_, double t, double *Y_, " \ 1902 + "double *p_, double *f_, unsigned wkn_, double *wk_, unsigned xvn_, double *xv_)" 1903 # specstr = sig + "{\n\n" + pardefines + vardefines + "\n" 1904 specstr = sig + "{" + pardefines + vardefines + inpundefines + "\n" 1905 if docodeinserts and self.codeinserts['start'] != '': 1906 specstr += '/* Verbose code insert -- begin */\n' \ 1907 + self.codeinserts['start'] \ 1908 + '/* Verbose code insert -- end */\n\n' 1909 specstr += (len(reusestr)>0)*"/* reused term definitions */\n" \ 1910 + reusestr + "\n" 1911 auxdefs_parsed = {} 1912 # add function body 1913 for i in xrange(len(specnames)): 1914 xname = specnames[i] 1915 fbody = self.varspecs[xname] 1916 fbody_parsed = self._processSpecialC(fbody) 1917 if self.auxfns: 1918 fbody_parsed = addArgToCalls(fbody_parsed, 1919 self.auxfns.keys(), 1920 "p_, wk_, xv_") 1921 if 'initcond' in self.auxfns: 1922 # convert 'initcond(x)' to 'initcond("x")' for 1923 # compatibility with C syntax 1924 fbody_parsed = wrapArgInCall(fbody_parsed, 1925 'initcond', '"') 1926 specstr += "f_[" + str(i) + "] = " + fbody_parsed + ";\n" 1927 auxdefs_parsed[xname] = fbody_parsed 1928 if docodeinserts and self.codeinserts['end'] != '': 1929 specstr += '\n/* Verbose code insert -- begin */\n' \ 1930 + self.codeinserts['end'] \ 1931 + '/* Verbose code insert -- end */\n' 1932 specstr += "\n" + parundefines + varundefines + inpundefines + "}\n\n" 1933 self._auxdefs_parsed = auxdefs_parsed 1934 return (specstr, funcname)
1935 1936
1937 - def _doPreMacrosC(self):
1938 # Pre-processor macros are presently not available for C-code 1939 # specifications 1940 pass
1941 1942
1943 - def _processSpecialC(self, specStr):
1944 """Pre-process 'if' statements and names of 'abs' and 'sign' functions, 1945 as well as logical operators. 1946 """ 1947 qspec = QuantSpec('spec', specStr, treatMultiRefs=False) 1948 qspec.mapNames({'abs': 'fabs', 'sign': 'signum', 'mod': 'fmod', 1949 'and': '&&', 'or': '||', 'not': '!', 1950 'True': 1, 'False': 0, 1951 'max': '__maxof', 'min': '__minof'}) 1952 qtoks = qspec.parser.tokenized 1953 # default value 1954 new_specStr = str(qspec) 1955 if 'if' in qtoks: 1956 new_specStr = "" 1957 num_ifs = qtoks.count('if') 1958 if_ix = -1 1959 ix_continue = 0 1960 for ifstmt in range(num_ifs): 1961 if_ix = qtoks[if_ix+1:].index('if')+if_ix+1 1962 new_specStr += "".join(qtoks[ix_continue:if_ix]) + "__rhs_if(" 1963 rbrace_ix = findEndBrace(qtoks[if_ix+1:])+if_ix+1 1964 ix_continue = rbrace_ix+1 1965 new_specStr += "".join(qtoks[if_ix+2:ix_continue]) 1966 new_specStr += "".join(qtoks[ix_continue:]) 1967 qspec = QuantSpec('spec', new_specStr) 1968 qtoks = qspec.parser.tokenized 1969 if '__minof' in qtoks: 1970 new_specStr = "" 1971 num = qtoks.count('__minof') 1972 n_ix = -1 1973 ix_continue = 0 1974 for stmt in range(num): 1975 n_ix = qtoks[n_ix+1:].index('__minof')+n_ix+1 1976 new_specStr += "".join(qtoks[ix_continue:n_ix]) 1977 rbrace_ix = findEndBrace(qtoks[n_ix+1:])+n_ix+1 1978 ix_continue = rbrace_ix+1 1979 #assert qtoks[n_ix+2] == '[', "Error in min() syntax" 1980 #assert qtoks[rbrace_ix-1] == ']', "Error in min() syntax" 1981 #new_specStr += "".join(qtoks[n_ix+3:rbrace_ix-1]) + ")" 1982 num_args = qtoks[n_ix+2:ix_continue].count(',') + 1 1983 if num_args > 4: 1984 raise NotImplementedError("Max of more than 4 arguments not currently supported in C") 1985 new_specStr += '__minof%s(' % str(num_args) 1986 new_specStr += "".join([q for q in qtoks[n_ix+2:ix_continue] if q not in ('[',']')]) 1987 new_specStr += "".join(qtoks[ix_continue:]) 1988 qspec = QuantSpec('spec', new_specStr) 1989 qtoks = qspec.parser.tokenized 1990 if '__maxof' in qtoks: 1991 new_specStr = "" 1992 num = qtoks.count('__maxof') 1993 n_ix = -1 1994 ix_continue = 0 1995 for stmt in range(num): 1996 n_ix = qtoks[n_ix+1:].index('__maxof')+n_ix+1 1997 new_specStr += "".join(qtoks[ix_continue:n_ix]) 1998 rbrace_ix = findEndBrace(qtoks[n_ix+1:])+n_ix+1 1999 ix_continue = rbrace_ix+1 2000 #assert qtoks[n_ix+2] == '[', "Error in max() syntax" 2001 #assert qtoks[rbrace_ix-1] == ']', "Error in max() syntax" 2002 #new_specStr += "".join(qtoks[n_ix+3:rbrace_ix-1]) + ")" 2003 num_args = qtoks[n_ix+2:ix_continue].count(',') + 1 2004 if num_args > 4: 2005 raise NotImplementedError("Min of more than 4 arguments not currently supported in C") 2006 new_specStr += '__maxof%s(' % str(num_args) 2007 new_specStr += "".join([q for q in qtoks[n_ix+2:ix_continue] if q not in ('[',']')]) 2008 new_specStr += "".join(qtoks[ix_continue:]) 2009 qspec = QuantSpec('spec', new_specStr) 2010 qtoks = qspec.parser.tokenized 2011 return new_specStr
2012 2013 # ------------ Matlab code specifications ----------------------- 2014
2015 - def _genAuxFnMatlab(self):
2016 auxnames = self.auxfns.keys() 2017 # parameter and variable definitions 2018 2019 # sorted version of var and par names sorted version of par 2020 # names (vars not #define'd in aux functions unless Jacobian) 2021 vnames = self.vars 2022 pnames = self.pars 2023 vnames.sort() 2024 pnames.sort() 2025 2026 for auxname in auxnames: 2027 assert auxname not in ['auxvars', 'vfield'], \ 2028 ("auxiliary function name '" +auxname+ "' clashes with internal" 2029 " names") 2030 # must add parameter argument so that we can name 2031 # pars inside the functions! this would either 2032 # require all calls to include this argument (yuk!) or 2033 # else we add these extra pars automatically to 2034 # every call found in the .c code (as is done currently. 2035 # this is still an untidy solution, but there you go...) 2036 for auxname, auxspec in self._auxfnspecs.iteritems(): 2037 assert len(auxspec) == 2, 'auxspec tuple must be of length 2' 2038 if not isinstance(auxspec[0], list): 2039 print "Found type ", type(auxspec[0]) 2040 print "Containing: ", auxspec[0] 2041 raise TypeError('aux function arguments ' 2042 'must be given as a list') 2043 if not isinstance(auxspec[1], str): 2044 print "Found type ", type(auxspec[1]) 2045 print "Containing: ", auxspec[1] 2046 raise TypeError('aux function specification ' 2047 'must be a string of the function code') 2048 ## assert auxspec[1].find('^') == -1, ('carat character ^ is not ' 2049 ## 'permitted in function definitions' 2050 ## '-- use pow(x,p) syntax instead') 2051 # Process Jacobian functions specially, if present 2052 if auxname == 'Jacobian': 2053 raise NotImplementedError 2054 elif auxname == 'Jacobian_pars': 2055 raise NotImplementedError 2056 elif auxname == 'massMatrix': 2057 raise NotImplementedError 2058 else: 2059 ismat = False 2060 topstr = "function y_ = " + auxname + "(" 2061 commentstr = "% Auxilliary function " + auxname + " for model " + self.name + "\n% Generated by PyDSTool for ADMC++ target\n\n" 2062 parlist = "" 2063 namemap = {} 2064 for parname in auxspec[0]: 2065 parlist += parname + "__, " 2066 namemap[parname] = parname+'__' 2067 topstr += parlist + " p_)\n" 2068 sig = topstr + commentstr 2069 pardefines = self._prepareMatlabPDefines(pnames) 2070 auxstr = auxspec[1] 2071 if any([pt in auxstr for pt in ('pow', '**')]): 2072 auxstr = convertPowers(auxstr, '^') 2073 reusestr, auxspec_processedDict = self._processReusedMatlab([auxname], 2074 {auxname:auxstr.replace(' ','').replace('\n','')}) 2075 # addition of parameter done in Generator code 2076 2077 dummyQ = QuantSpec('dummy', auxspec_processedDict[auxname], 2078 treatMultiRefs=False, preserveSpace=True) 2079 if not ismat: 2080 dummyQ.mapNames(namemap) 2081 body_processed = "y_ = "*(not ismat) + dummyQ() + ";\n\n" 2082 # auxspecstr = sig + " {\n\n" + pardefines + vardefines*ismat \ 2083 auxspecstr = sig + pardefines + " \n\n" \ 2084 + "\n" + (len(reusestr)>0)*"% reused term definitions \n" \ 2085 + reusestr + (len(reusestr)>0)*"\n" + body_processed 2086 # sig as second entry, whereas Python-coded specifications 2087 # have the fn name there 2088 self.auxfns[auxname] = (auxspecstr, sig) 2089 self._protected_auxnames.extend(auxnames)
2090 # Don't apply #define's for built-in functions 2091 2092
2093 - def _genSpecMatlab(self):
2094 assert self.targetlang == 'matlab', ('Wrong target language for this' 2095 ' call') 2096 assert self.varspecs != {}, 'varspecs attribute must be defined' 2097 specnames_unsorted = self.varspecs.keys() 2098 specname_vars = intersect(self.vars, specnames_unsorted) 2099 specname_vars.sort() 2100 # parameter and variable definitions 2101 # sorted version of var and par names 2102 vnames = specname_vars 2103 pnames = self.pars 2104 pnames.sort() 2105 pardefines = self._prepareMatlabPDefines(pnames) 2106 vardefines = self._prepareMatlabVDefines(vnames) 2107 # produce vector field specification 2108 assert self.vars == specname_vars, ('Mismatch between declared ' 2109 ' variable names and varspecs keys') 2110 valid_depTargNames = self.inputs+self.vars+self.auxvars 2111 for specname, specstr in self.varspecs.iteritems(): 2112 assert type(specstr)==str, "Specification for %s was not a string"%specname 2113 if any([pt in specstr for pt in ('pow', '**')]): 2114 specstr = convertPowers(specstr, '^') 2115 specQS = QuantSpec('__spectemp__', specstr) 2116 for s in specQS: 2117 if s in valid_depTargNames and (specname, s) not in \ 2118 self.dependencies: # and specname != s: 2119 self.dependencies.append((specname, s)) 2120 # pre-process reused sub-expression dictionary to adapt for 2121 # known calling sequence in Matlab 2122 reusestr, specupdated = self._processReusedMatlab(specname_vars, 2123 self.varspecs) 2124 self.varspecs.update(specupdated) 2125 specstr_Matlab = self._genSpecFnMatlab('vfield', reusestr, specname_vars, 2126 pardefines, vardefines, True) 2127 self.spec = specstr_Matlab
2128 # do not produce auxiliary variables specification 2129 2130
2131 - def _genSpecFnMatlab(self, funcname, reusestr, specnames, pardefines, 2132 vardefines, docodeinserts):
2133 topstr = "function [vf_, y_] = " + funcname + "(vf_, t_, x_, p_)\n" 2134 commentstr = "% Vector field definition for model " + self.name + "\n% Generated by PyDSTool for ADMC++ target\n\n" 2135 2136 specstr = topstr + commentstr + pardefines + vardefines + "\n" 2137 if docodeinserts and self.codeinserts['start'] != '': 2138 specstr += '% Verbose code insert -- begin \n' \ 2139 + self.codeinserts['start'] \ 2140 + '% Verbose code insert -- end \n\n' 2141 specstr += (len(reusestr)>0)*"% reused term definitions \n" \ 2142 + reusestr + "\n" 2143 # add function body 2144 for i in xrange(len(specnames)): 2145 xname = specnames[i] 2146 fbody = self.varspecs[xname] 2147 fbody_parsed = self._processIfMatlab(fbody) 2148 if self.auxfns: 2149 fbody_parsed = addArgToCalls(fbody_parsed, 2150 self.auxfns.keys(), 2151 "p_") 2152 # if 'initcond' in self.auxfns: 2153 # convert 'initcond(x)' to 'initcond("x")' for 2154 # compatibility with C syntax 2155 # fbody_parsed = wrapArgInCall(fbody_parsed, 2156 # 'initcond', '"') 2157 specstr += "y_(" + str(i+1) + ") = " + fbody_parsed + ";\n" 2158 if docodeinserts and self.codeinserts['end'] != '': 2159 specstr += '\n% Verbose code insert -- begin \n' \ 2160 + self.codeinserts['end'] \ 2161 + '% Verbose code insert -- end \n' 2162 specstr += "\n\n" 2163 return (specstr, funcname)
2164 2165
2166 - def _processReusedMatlab(self, specnames, specdict):
2167 """Process reused subexpression terms for Matlab code.""" 2168 2169 if self.auxfns: 2170 def addParToCall(s): 2171 return addArgToCalls(s, self.auxfns.keys(), "p_")
2172 parseFunc = addParToCall 2173 else: 2174 parseFunc = idfn 2175 reused, specupdated, new_protected, order = _processReused(specnames, 2176 specdict, 2177 self.reuseterms, 2178 '', '', ';', 2179 parseFunc) 2180 self._protected_reusenames = new_protected 2181 reusedefs = {}.fromkeys(new_protected) 2182 for vname, deflist in reused.iteritems(): 2183 for d in deflist: 2184 reusedefs[d[2]] = d 2185 return (concatStrDict(reusedefs, intersect(order, reusedefs.keys())), 2186 specupdated) 2187 # NEED TO CHECK WHETHER THIS IS NECESSARY AND WORKS 2188 # IF STATEMENTS LOOK DIFFERENT IN MATLAB
2189 - def _processIfMatlab(self, specStr):
2190 qspec = QuantSpec('spec', specStr) 2191 qtoks = qspec[:] 2192 if 'if' in qtoks: 2193 raise NotImplementedError 2194 else: 2195 new_specStr = specStr 2196 return new_specStr
2197 2198
2199 - def _prepareMatlabPDefines(self, pnames):
2200 pardefines = "" 2201 for i in xrange(len(pnames)): 2202 p = pnames[i] 2203 pardefines += "\t" + p + " = p_(" + str(i+1) + ");\n" 2204 2205 alldefines = "\n% Parameter definitions\n\n" + pardefines 2206 return alldefines
2207 2208
2209 - def _prepareMatlabVDefines(self, vnames):
2210 vardefines = "" 2211 for i in xrange(len(vnames)): 2212 v = vnames[i] 2213 vardefines += "\t" + v + " = x_(" + str(i+1) + ");\n" 2214 alldefines = "\n% Variable definitions\n\n" + vardefines 2215 return alldefines
2216 2217 2218 # ------------ Other utilities ----------------------- 2219
2220 - def _infostr(self, verbose=1):
2221 if verbose == 0: 2222 outputStr = "FuncSpec " + self.name 2223 else: 2224 outputStr = '*********** FuncSpec: '+self.name + ' ***********' 2225 outputStr += '\nTarget lang: '+ self.targetlang 2226 outputStr += '\nVariables: ' 2227 for v in self.vars: 2228 outputStr += v+' ' 2229 outputStr += '\nParameters: ' 2230 if len(self.pars): 2231 for p in self.pars: 2232 outputStr += p+' ' 2233 else: 2234 outputStr += '[]' 2235 outputStr += '\nExternal inputs: ' 2236 if len(self.inputs): 2237 for i in self.inputs: 2238 outputStr += i+' ' 2239 else: 2240 outputStr += '[]' 2241 if verbose == 2: 2242 outputStr += "\nSpecification functions (in target language):" 2243 outputStr += "\n (ignore any arguments `ds` and `parsinps`," \ 2244 + "\n which are for internal use only)\n" 2245 if self.spec == {}: 2246 outputStr += "\n None\n" 2247 else: 2248 outputStr += "\n "+self.spec[0]+"\n" 2249 if len(self.auxvars) and self.auxspec != {}: 2250 outputStr += " "+self.auxspec[0] 2251 if self._protected_auxnames != []: 2252 outputStr += '\n\nUser-defined auxiliary variables: ' 2253 for v in self.auxvars: 2254 outputStr += v+' ' 2255 outputStr += '\n\nUser-defined auxiliary functions (in target ' + \ 2256 'language):' 2257 for auxname in self.auxfns: 2258 # verbose option shows up builtin auxiliary func definitions 2259 if auxname not in self._builtin_auxnames or verbose>0: 2260 outputStr += '\n '+self.auxfns[auxname][0]+'\n' 2261 outputStr += "\n\nDependencies in specification functions - pair (i, o)"\ 2262 " means i depends on o:\n " + str(self.dependencies) 2263 return outputStr
2264
2265 - def info(self, verbose=0):
2266 print self._infostr(verbose)
2267
2268 - def __repr__(self):
2269 return self._infostr(verbose=0)
2270 2271 __str__ = __repr__ 2272 2273 2274 2275 # ----------------------------------- 2276 2277 # Sub-classes of FuncSpec 2278
2279 -class RHSfuncSpec(FuncSpec):
2280 """Right-hand side definition for vars defined.""" 2281
2282 - def __init__(self, kw):
2283 FuncSpec.__init__(self, kw)
2284 2285 2286
2287 -class ExpFuncSpec(FuncSpec):
2288 """Explicit definition of vars defined.""" 2289
2290 - def __init__(self, kw):
2291 assert 'codeinsert_start' not in kw, ('code inserts invalid for ' 2292 'explicit function specification') 2293 assert 'codeinsert_end' not in kw, ('code inserts invalid for ' 2294 'explicit function specification') 2295 FuncSpec.__init__(self, kw)
2296 2297 2298
2299 -class ImpFuncSpec(FuncSpec):
2300 """Assumes this will be set to equal zero when solving for vars defined.""" 2301 2302 # funcspec will possibly be the same for several variables 2303 # so it's repeated, but must be checked so that only solved 2304 # once for all relevant variables
2305 - def __init__(self, kw):
2306 assert 'codeinsert_start' not in kw, ('code inserts invalid for ' 2307 'implicit function specification') 2308 assert 'codeinsert_end' not in kw, ('code inserts invalid for ' 2309 'implicit function specification') 2310 FuncSpec.__init__(self, kw)
2311 2312 2313
2314 -def _processReused(specnames, specdict, reuseterms, indentstr='', 2315 typestr='', endstatementchar='', parseFunc=idfn):
2316 """Process substitutions of reused terms.""" 2317 2318 seenrepterms = [] # for new protected names (global to all spec names) 2319 reused = {}.fromkeys(specnames) 2320 reuseterms_inv = invertMap(reuseterms) 2321 # establish order for reusable terms, in case of inter-dependencies 2322 are_dependent = [] 2323 deps = {} 2324 for origterm, rterm in reuseterms.iteritems(): 2325 for ot, rt in reuseterms.iteritems(): 2326 if proper_match(origterm, rt): 2327 if rterm not in are_dependent: 2328 are_dependent.append(rterm) 2329 try: 2330 deps[rterm].append(rt) 2331 except KeyError: 2332 # new list 2333 deps[rterm] = [rt] 2334 order = remain(reuseterms.values(), are_dependent) + are_dependent 2335 for specname in specnames: 2336 reused[specname] = [] 2337 specstr = specdict[specname] 2338 repeatkeys = [] 2339 for origterm, repterm in reuseterms.iteritems(): 2340 # only add definitions if string found 2341 if proper_match(specstr, origterm): 2342 specstr = specstr.replace(origterm, repterm) 2343 if repterm not in seenrepterms: 2344 reused[specname].append([indentstr, 2345 typestr+' '*(len(typestr)>0), 2346 repterm, " = ", 2347 parseFunc(origterm), 2348 endstatementchar, "\n"]) 2349 seenrepterms.append(repterm) 2350 else: 2351 # look for this term on second pass 2352 repeatkeys.append(origterm) 2353 if len(seenrepterms) > 0: 2354 # don't bother with a second pass if specstr has not changed 2355 for origterm in repeatkeys: 2356 # second pass 2357 repterm = reuseterms[origterm] 2358 if proper_match(specstr, origterm): 2359 specstr = specstr.replace(origterm, repterm) 2360 if repterm not in seenrepterms: 2361 seenrepterms.append(repterm) 2362 reused[specname].append([indentstr, 2363 typestr+' '*(len(typestr)>0), 2364 repterm, " = ", 2365 parseFunc(origterm), 2366 endstatementchar, "\n"]) 2367 # if replacement terms have already been used in the specifications 2368 # and there are no occurrences of the terms meant to be replaced then 2369 # just log the definitions that will be needed without replacing 2370 # any strings. 2371 if reused[specname] == [] and len(reuseterms) > 0: 2372 for origterm, repterm in reuseterms.iteritems(): 2373 # add definition if *replacement* string found in specs 2374 if proper_match(specstr, repterm) and repterm not in seenrepterms: 2375 reused[specname].append([indentstr, 2376 typestr+' '*(len(typestr)>0), 2377 repterm, " = ", 2378 parseFunc(origterm), 2379 endstatementchar, "\n"]) 2380 seenrepterms.append(repterm) 2381 specdict[specname] = specstr 2382 # add any dependencies for repeated terms to those that will get 2383 # defined when functions are instantiated 2384 add_reps = [] 2385 for r in seenrepterms: 2386 if r in are_dependent: 2387 for repterm in deps[r]: 2388 if repterm not in seenrepterms: 2389 reused[specname].append([indentstr, 2390 typestr+' '*(len(typestr)>0), 2391 repterm, " = ", 2392 parseFunc(reuseterms_inv[repterm]), 2393 endstatementchar, "\n"]) 2394 seenrepterms.append(repterm) 2395 # reuseterms may be automatically provided for a range of definitions 2396 # that may or may not contain instances, and it's too inefficient to 2397 # check in advance, so we'll not cause an error here if none show up. 2398 ## if len(seenrepterms) == 0 and len(reuseterms) > 0: 2399 ## print "Reuse terms expected:", reuseterms 2400 ## info(specdict) 2401 ## raise RuntimeError("Declared reusable term definitions did not match" 2402 ## " any occurrences in the specifications") 2403 return (reused, specdict, seenrepterms, order)
2404 2405 2406 2407 2408 # ---------------------------------------------- 2409 ## Public exported functions 2410 # ---------------------------------------------- 2411
2412 -def makePartialJac(spec_pair, varnames, select=None):
2413 """Use this when parameters have been added to a modified Generator which 2414 might clash with aux fn argument names. (E.g., used by find_nullclines). 2415 2416 'select' option (list of varnames) selects those entries from the Jac of the varnames, 2417 e.g. for constructing Jacobian w.r.t. 'parameters' using a parameter formerly 2418 a variable (e.g. for find_nullclines). 2419 """ 2420 fargs, fspec = spec_pair 2421 J = QuantSpec('J', fspec) 2422 # find positions of actual varnames in f argument list 2423 # then extract terms from the Jacobian matrix, simplifying to a scalar if 1D 2424 dim = len(varnames) 2425 if J.dim == dim: 2426 # nothing to do 2427 return (fargs, fspec) 2428 assert J.dim > dim, "Cannot add variable names to system while using its old Jacobian aux function" 2429 assert remain(varnames, fargs) == [], "Invalid variable names to resolve Jacobian aux function" 2430 assert fargs[0] == 't' 2431 # -1 adjusts for 't' being the first argument 2432 vixs = [fargs.index(v)-1 for v in varnames] 2433 vixs.sort() 2434 if select is None: 2435 select = varnames 2436 sixs = vixs 2437 else: 2438 sixs = [fargs.index(v)-1 for v in select] 2439 if dim == 1: 2440 fspec = str(J.fromvector(vixs[0]).fromvector(sixs[0])) 2441 else: 2442 terms = [] 2443 for i in vixs: 2444 Ji = J.fromvector(i) 2445 subterms = [] 2446 for j in sixs: 2447 subterms.append( str(Ji.fromvector(j)) ) 2448 terms.append( "[" + ",".join(subterms) + "]" ) 2449 fspec = "[" + ",".join(terms) + "]" 2450 # retain order of arguments 2451 fargs_new = ['t'] + [fargs[ix+1] for ix in vixs] 2452 return (fargs_new, fspec)
2453 2454
2455 -def resolveClashingAuxFnPars(fnspecs, varspecs, parnames):
2456 """Use this when parameters have been added to a modified Generator which 2457 might clash with aux fn argument names. (E.g., used by find_nullclines). 2458 Will remove arguments that are now considered parameters by the system, 2459 in both the function definitions and their use in specs for the variables. 2460 """ 2461 changed_fns = [] 2462 new_fnspecs = {} 2463 for fname, (fargs, fspec) in fnspecs.iteritems(): 2464 common_names = intersect(fargs, parnames) 2465 if fname in parnames: 2466 print "Problem with function definition", fname 2467 raise ValueError("Unrecoverable clash between parameter names and aux fn name") 2468 if common_names == []: 2469 new_fnspecs[fname] = (fargs, fspec) 2470 else: 2471 changed_fns.append(fname) 2472 new_fnspecs[fname] = (remain(fargs, parnames), fspec) 2473 2474 new_varspecs = {} 2475 for vname, vspec in varspecs.iteritems(): 2476 q = QuantSpec('__temp__', vspec) 2477 # only update use of functions both changed and used in the varspecs 2478 used_fns = intersect(q.parser.tokenized, changed_fns) 2479 for f in used_fns: 2480 ix = q.parser.tokenized.index(f) 2481 # identify arg list for this fn call 2482 rest = ''.join(q.parser.tokenized[ix+1:]) 2483 end_ix = findEndBrace(rest) 2484 # get string of this arg list 2485 argstr = rest[:end_ix+1] 2486 # split 2487 success, args_list, arglen = readArgs(argstr) 2488 assert success, "Parsing arguments failed" 2489 new_args_list = [] 2490 # remove parnames 2491 for arg in args_list: 2492 qarg = QuantSpec('a', arg) 2493 # if parameter appears in a compound expression in the argument, 2494 # then we don't know how to process it, so issue warning [was: raise exception] 2495 if len(qarg.parser.tokenized) > 1: 2496 if any([p in qarg for p in parnames]): 2497 # do not put raw parameter name arguments into new arg list 2498 #raise ValueError("Cannot process argument to aux fn %s"%f) 2499 print "Warning: some auxiliary function parameters clash in function %s" %f 2500 new_args_list.append(arg) 2501 elif arg not in parnames: 2502 # do not put raw parameter name arguments into new arg list 2503 new_args_list.append(arg) 2504 new_argstr = ','.join(new_args_list) 2505 # update vspec and q for next f 2506 vspec = ''.join(q[:ix+1]) + '(' + new_argstr + ')' + rest[end_ix+1:] 2507 q = QuantSpec('__temp__', vspec) 2508 new_varspecs[vname] = vspec 2509 return new_fnspecs, new_varspecs
2510 2511 2512
2513 -def getSpecFromFile(specfilename):
2514 """Read text specs from a file""" 2515 try: 2516 f = open(specfilename, 'r') 2517 s = f.read() 2518 except IOError, e: 2519 print 'File error:', str(e) 2520 raise 2521 f.close() 2522 return s
2523