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

Source Code for Module PyDSTool.ModelSpec'

   1  """Structured model specification classes, and associated utilities.
 
   2  
 
   3  Robert Clewley, October 2005.
 
   4  
 
   5  
 
   6   Technical notes for ModelSpec attributes:
 
   7   _componentNameMap: object -> full (hierarchical) objname
 
   8   _registry: objname -> regObject:
 
   9                        obj, objLocalName, objClassName, objParentName
 
  10   _componentTypeMap: classname -> [objlist]
 
  11  """ 
  12  
 
  13  # ----------------------------------------------------------------------------
 
  14  
 
  15  from __future__ import division 
  16  from common import * 
  17  from utils import info as utils_info 
  18  from parseUtils import * 
  19  from errors import * 
  20  from Symbolic import * 
  21  
 
  22  from math import * 
  23  from utils import * 
  24  from numpy import Inf, NaN, isfinite,  mod, sum 
  25  from numpy import sometrue, alltrue 
  26  from copy import copy, deepcopy 
  27  import math, random 
  28  
 
  29  
 
  30  # ---------------------------------------------------------------------------
 
  31  ### Exports
 
  32  
 
  33  _classes = ['ModelSpec', 'Component', 'LeafComponent',
 
  34              'nameResolverClass'] 
  35  
 
  36  _functions = ['searchModelSpec', 'matchSubName', 'processMacro',
 
  37                'resolveMacroTargets'] 
  38  
 
  39  _objects = ['nameResolver'] 
  40  
 
  41  
 
  42  __all__ = _classes + _functions + _objects 
  43  
 
  44  # -----------------------------------------------------------------------------
 
  45  
 
46 -class ModelSpec(object):
47 """Model specification abstract class. 48 Sub-classes can override targetLangs, compatibleSubcomponents, 49 compatibleContainers, and compatibleGens.""" 50 51 targetLangs = () 52 compatibleSubcomponents = () 53 compatibleContainers = () 54 compatibleGens = () 55
56 - def __init__(self, name):
57 """Called by all sub-classes - do not override""" 58 self._registry = {} 59 self._componentNameMap = {} 60 self._componentTypeMap = {} 61 self.multiDefRefs = {} 62 self.funcSpecDict = {} 63 self.flatSpec = {'FScompatibleNames': symbolMapClass(), 64 'FScompatibleNamesInv': symbolMapClass()} 65 self.variables = {} 66 self.pars = {} 67 self.inputs = {} 68 self.auxfns = {} 69 self.components = {} 70 self.name = name 71 self.eventPars = [] 72 # Find unique list of all compatible subcomponent types inherited 73 # from ModelSpec and its sub-classes. Include only the most specialized 74 # sub-class of each ancestry. 75 self._allSubcomponentTypes = allCompTypes(accrueCompTypes(self.__class__)) 76 # optional store for names of components that depend on this component's 77 # variables -- needed for supporting network 'graph' abstractions 78 # (in particular, to support edges) 79 self.connxnTargets = [] 80 # list of unbound symbols 81 self.freeSymbols = [] 82 # for use by ModelLibrary classes to identify interchangeable types of spec 83 self.library_tag = '' 84 # in case a previous instance of this model spec existed in this 85 # session, we need to clear out its associated names in 86 # the nameResolverClass instance. 87 nameResolver.clear(self) 88 self.validate()
89 90
91 - def rename(self, newName):
92 oldName = self.name 93 for robj in self._registry.values(): 94 if robj.objParentName == oldName: 95 robj.objParentName = newName 96 self.name = newName
97 98
99 - def compileFuncSpec(self):
100 raise NotImplementedError("Only call this method on a concrete " 101 "sub-class")
102 103
104 - def flattenSpec(self, multiDefUnravel=True, globalRefs=None, 105 ignoreInputs=False, force=False):
106 """Flatten structured model specification to dictionary compatible with 107 FuncSpec instantiation. 108 109 Use globalRefs option to declare global variables used in the 110 definitions that are not defined in them (e.g. time as 't', although 111 this one is included as globalRef by default). 112 113 Default for multiple quantity definitions is to unravel them. 114 Use multiDefUnravel=False to override this. 115 116 Use force option to rebuild flat spec if an existing one is 117 out of date. 118 """ 119 ## Use the optional multiDefUnravel dictionary to selectively retain 120 ## compact specification or only partially unravel the definitions 121 ## during the flattening process. Use {} to force all definitions to 122 ## remain compact, otherwise the syntax is: 123 ## 124 ## key = a rootname of multiple quantity names 125 ## --> value = either (a): pair (i1,i2) to unravel all indices of the 126 ## rootname from i1 to i2 inclusive, or 127 ## (b): a list of such pairs (the union of the intervals will 128 ## be used if they overlap), or 129 ## (c): an empty list, so that the definition will remain 130 ## compact.""" 131 #self.validate() 132 if not self.isDefined(True, ignoreInputs): 133 raise AssertionError("Model spec not fully defined") 134 if globalRefs is None: 135 globalRefs = ['t'] 136 elif 't' not in globalRefs: 137 globalRefs.append('t') 138 if not self.isComplete(globalRefs): 139 ## if globalRefs is None: 140 ## print "Unresolved symbols:", self.freeSymbols 141 ## else: 142 print "Unresolved symbols:", remain(self.freeSymbols, globalRefs) 143 raise ValueError("Cannot flatten incomplete functional " 144 "specification") 145 if self.funcSpecDict == {} or force: 146 try: 147 self.compileFuncSpec(ignoreInputs=ignoreInputs) 148 except KeyboardInterrupt: 149 raise 150 except: 151 print "compileFuncSpec() failed" 152 raise 153 fs = self.funcSpecDict 154 # name mappings to be compatible with FuncSpec.py and python variable 155 # naming rules: i.e. "a_name.b.x3" (ModelSpec) <-> "a_name_b_x3" (FuncSpec/python) 156 FScompatibleNames = {} 157 FScompatibleNamesInv = {} 158 for name in self._registry.keys(): 159 FScompatibleNames[name] = replaceSep(name) 160 FScompatibleNamesInv[replaceSep(name)] = name 161 # store for later use 162 self._FScompatibleNames = FScompatibleNames 163 self._FScompatibleNamesInv = FScompatibleNamesInv 164 # process multi-quantity definitions, if used 165 # TEMP -- multiDefUnravel can only be True or False 166 if self.multiDefRefs != {}: 167 unravelDict = {}.fromkeys(self.multiDefRefs) 168 if multiDefUnravel: 169 for k in unravelDict: 170 # default is to entirely unravel all definitions 171 infotuple = self.multiDefRefs[k] 172 unravelDict[k] = [(infotuple[1], infotuple[2])] 173 ## if multiDefUnravel is None: 174 ## for k in unravelDict: 175 ## # default is to entirely unravel all definitions 176 ## infotuple = self.multiDefRefs[k] 177 ## unravelDict[k] = [(infotuple[1], infotuple[2])] 178 ## else: 179 ## for k in unravelDict: 180 ## unravelDict[k] = [] 181 ## # if multiDefUnravel == {} then the following will not change 182 ## # unravelDict from being all [] 183 ## for k, v in multiDefUnravel.iteritems(): 184 ## if isinstance(v, list): 185 ## unravelDict[k] = [] 186 ## if v != []: 187 ## # list of pairs 188 ## for p in v: 189 ## assert p[0] < p[1], "Pair must be ordered" 190 ## unravelDict[k].append(p) 191 ## else: 192 ## # single pair 193 ## assert len(p) == 2 194 ## assert p[0] < p[1], "Pair must be ordered" 195 ## unravelDict[k] = [p] 196 # Any unravelDict entries with [], and those remaining after a 197 # partial unravelling, should still have their funcSpecDict 198 # entry show a valid multiDef name, e.g. z[i,1,5] including i1 199 # and i2 200 outfs = {} # output dictionary for funcspec initialization 201 quants = ['vars', 'pars', 'inputs'] 202 outfs['domains'] = {} 203 outfs['spectypes'] = {} 204 # mathNameMap maps title case math function names to all lower-case 205 mathNameMap = dict(zip(allmathnames_symbolic,allmathnames)) 206 # ----- start of add_to_fs function ----- 207 def add_to_fs(k, name, carg, i=None): 208 # locally-defined because refers to outfs from scope of method 209 # 'flattenSpec' 210 c = copy(carg) 211 if 'for' in c.usedSymbols: 212 # Activate macro for variable definition. 213 # Correct syntax already checked in Quantity __init__ 214 # 'for' may have been used more than once 215 c, dummy1, dummy2 = processMacro(c, self) #._registry) 216 # dummy1 and 2 are unused here 217 c.mapNames(mathNameMap) 218 fsname = FScompatibleNames[name] 219 if i is None: 220 try: 221 outfs[k][fsname] = replaceSep(c.spec) 222 except KeyError: 223 outfs[k] = {fsname: replaceSep(c.spec)} 224 else: 225 # c is a multi-quantity def and needs evaluating at i 226 try: 227 outfs[k][fsname] = replaceSep(c[i]) 228 except KeyError: 229 outfs[k] = {fsname: replaceSep(c[i])} 230 outfs['domains'][fsname] = c.domain 231 if k not in ['pars', 'inputs']: 232 # not necessary for pars and inputs 233 # as they must be explicitly defined 234 outfs['spectypes'][fsname] = c.spec.specType
235 # ----- end of add_to_fs function ----- 236 full_mref_names = {} 237 if multiDefUnravel: 238 # reconstruct list of full names for mrefs, e.g. 'z[i]' from 'z' 239 # otherwise just leave the dict empty 240 for k, v in self.multiDefRefs.iteritems(): 241 try: 242 ksubs = FScompatibleNames[k] 243 except KeyError: 244 ksubs = replaceSep(k) 245 FScompatibleNames[k] = ksubs 246 FScompatibleNamesInv[ksubs] = k 247 # also add all instances of the multiref to 248 # FScompatibleNames, so add z0 ... z10 for 249 # z[i,0,10]. 250 for ix in range(v[1], v[2]+1): 251 FScompatibleNames[k+str(ix)] = ksubs+str(ix) 252 FScompatibleNamesInv[ksubs+str(ix)] = k+str(ix) 253 full_mref_names[ksubs+"["+v[0]+","+str(v[1])+","+str(v[2])+"]"]\ 254 = ksubs 255 for k, v in fs.iteritems(): 256 if k in quants: 257 for c in v: 258 if c.name in full_mref_names.keys(): 259 # unravel defs given by pairs p in unravelDict 260 root = full_mref_names[c.name] 261 for p in unravelDict[root]: 262 for i in xrange(p[0],p[1]+1): 263 name = root + str(i) 264 try: 265 if name not in outfs[k]: 266 add_to_fs(k, name, c, i) 267 except KeyError: 268 add_to_fs(k, name, c, i) 269 else: 270 add_to_fs(k, c.name, c) 271 elif k == 'auxfns': 272 for a in v: 273 afsname = FScompatibleNames[a.name] 274 acopy = copy(a) 275 acopy.mapNames(mathNameMap) 276 aspec = acopy.spec 277 try: 278 outfs[k][afsname] = (a.signature, replaceSep(aspec)) 279 except KeyError: 280 outfs[k] = {afsname: (a.signature, replaceSep(aspec))} 281 elif k == 'complete': 282 # ignore this -- it's not needed in the flattened specification 283 pass 284 else: 285 outfs[k] = v 286 outfs['FScompatibleNames'] = symbolMapClass(FScompatibleNames) 287 outfs['FScompatibleNamesInv'] = symbolMapClass(FScompatibleNamesInv) 288 self.flatSpec = outfs # for future reference 289 return outfs
290
291 - def isDefined(self, verbose=False, ignoreInputs=False):
292 defined = True # initial value 293 if len(self.compatibleGens) == 0: 294 if verbose: 295 print "'%s' ill defined: empty compatibleGens"%self.name 296 return False 297 if len(self.targetLangs) == 0: 298 if verbose: 299 print "'%s' ill defined: empty targetLangs"%self.name 300 return False 301 if self.isEmpty(): 302 if verbose: 303 print "'%s' ill defined: empty contents"%self.name 304 return False 305 for v in self.variables.values(): 306 if not v.isDefined(verbose): 307 if verbose: 308 print "... in '%s' (type %s)"%(self.name,str(self.__class__)) 309 return False 310 for p in self.pars.values(): 311 if not p.isDefined(verbose): 312 if verbose: 313 print "... in '%s' (type %s)"%(self.name,str(self.__class__)) 314 return False 315 if not ignoreInputs: 316 for i in self.inputs.values(): 317 if not i.isDefined(verbose): 318 if verbose: 319 print "... in '%s' (type %s)"%(self.name,str(self.__class__)) 320 return False 321 for a in self.auxfns.values(): 322 if not a.isDefined(verbose): 323 if verbose: 324 print "... in '%s' (type %s)"%(self.name,str(self.__class__)) 325 return False 326 return defined
327 328
329 - def addConnxnTarget(self, targ):
330 if isinstance(targ, str): 331 if targ not in self.connxnTargets: 332 self.connxnTargets.append(targ) 333 elif isinstance(targ, list): 334 for t in targ: 335 if t not in self.connxnTargets: 336 self.connxnTargets.append(t) 337 else: 338 raise TypeError("Invalid ModelSpec type for targ argument")
339 340
341 - def delConnxnTarget(self, targ):
342 if isinstance(targ, str): 343 try: 344 self.connxnTargets.remove(targ) 345 except ValueError: 346 raise ValueError("Connection target %s not found"%targ) 347 elif isinstance(targ, list): 348 for t in targ: 349 try: 350 self.connxnTargets.remove(t) 351 except ValueError: 352 raise ValueError("Connection target %s not found"%t)
353 354
355 - def __contains__(self, obj):
356 try: 357 if obj.name in self._registry: 358 return obj == self._registry[obj.name].obj 359 elif self.multiDefInfo[0]: 360 # there are multiply-defined variables here 361 # if subject is a single Quantity that is defined here 362 # in multiple definition form. 363 return isMultiDefClash(obj, self.multiDefRefs) 364 else: 365 return False 366 except AttributeError: 367 return False
368 369
370 - def __eq__(self, other, diff=False):
371 results = [] 372 try: 373 results.append(type(self) == type(other)) 374 results.append(self.name == other.name) 375 results.append(self._registry == other._registry) 376 except AttributeError, e: 377 if diff: 378 print "Type:", className(self), results 379 print " " + e 380 return False 381 if diff: 382 print "Type:", className(self), results 383 return alltrue(results)
384 385
386 - def __ne__(self, other):
387 return not self.__eq__(other)
388 389
390 - def difference(self, other):
391 """Print the difference between two ModelSpecs to screen.""" 392 self.__eq__(other, diff=True)
393 394
395 - def validate(self):
396 assert isinstance(self.name, str), "ModelSpec name must be a string" 397 assert len(remain(self.targetLangs, targetLangs)) == 0, \ 398 "Invalid target language for '" + self.name + "'" 399 if hasattr(self, 'components'): 400 # leaf nodes do not have this 401 for c in self.components.values(): 402 assert c in self._componentNameMap, "Component name map incomplete" 403 # assert remain(self._componentNameMap[c], 404 # self._registry.keys()) == [] 405 for v in self.flatSpec['FScompatibleNamesInv'](self.variables.values()): 406 assert v in self._componentNameMap, "Variable name map incomplete" 407 for p in self.flatSpec['FScompatibleNamesInv'](self.pars.values()): 408 if p not in self._componentNameMap and p.name not in self.eventPars: 409 print "\n", self.name, self 410 print "Pars:", p 411 print p.name in self.flatSpec['FScompatibleNames'] 412 print p.name in self.flatSpec['FScompatibleNamesInv'] 413 print self.eventPars 414 print self.flatSpec['FScompatibleNamesInv'](p) 415 print "Component name map:", self._componentNameMap 416 raise AssertionError("Parameter name map incomplete") 417 i = None 418 for i in self.flatSpec['FScompatibleNamesInv'](self.inputs.values()): 419 assert i in self._componentNameMap, "External input name map incomplete" 420 for a in self.flatSpec['FScompatibleNamesInv'](self.auxfns.values()): 421 assert a in self._componentNameMap, "Auxiliary function name map incomplete"
422 # !! check that compatibleGens point to known Generator types !! 423 # if get this far without raising exception, then valid 424 425
426 - def add(self, arg, tosubcomponent=None):
427 """Add object to registry, into a specified sub component if 428 provided. 429 """ 430 if isinstance(arg, list): 431 for obj in arg: 432 self.add(obj, tosubcomponent) 433 elif tosubcomponent is not None: 434 if tosubcomponent in self.components: 435 c = deepcopy(self[tosubcomponent]) 436 self.remove(tosubcomponent) 437 c.add(arg) 438 self.add(c) 439 elif tosubcomponent == '': 440 # no subcomponent, add to top level 441 self.add(arg) 442 else: 443 raise ValueError("Unknown sub-component %s"%tosubcomponent) 444 elif isHierarchicalName(arg.name): 445 # add the object to the appropriate sub-component 446 obj = deepcopy(arg) 447 hier_name_split = obj.name.split(NAMESEP) 448 comp_name = hier_name_split[0] 449 rest_name = ".".join(hier_name_split[1:]) 450 obj.rename(rest_name) 451 self.add(obj, tosubcomponent=comp_name) 452 else: 453 obj = deepcopy(arg) 454 objname = self._register(obj) 455 if isinstance(obj, Var): 456 self.variables[objname] = obj 457 elif isinstance(obj, Par): 458 self.pars[objname] = obj 459 elif isinstance(obj, Input): 460 self.inputs[objname] = obj 461 elif isinstance(obj, Fun): 462 self.auxfns[objname] = obj 463 elif isinstance(obj, ModelSpec): 464 if not issubclass(self.__class__, obj.compatibleContainers): 465 print "Component " + self.name + ": " + self.__class__.__name__ 466 print "Invalid sub-component " + objname \ 467 + " (type " + className(obj) + ") to add," 468 print " with compatible container types:", obj.compatibleContainers 469 print "Compatible sub-component types: ", self._allSubcomponentTypes 470 raise ValueError("Incompatible sub-component type" 471 " for object '" + objname + "'") 472 else: 473 self.components[objname] = obj 474 else: 475 raise TypeError("Invalid Quantity object to add")
476 477
478 - def isEmpty(self):
479 raise NotImplementedError("Only call this method on a concrete " 480 "sub-class")
481 482
483 - def _register(self, obj, depth=0, parent_obj=None):
484 # depth = recursion depth, when this component registers its 485 # subcomponents' definitions (from their _registry attributes) 486 if len(obj.compatibleGens) == 0: 487 # object inherits self's 488 obj.compatibleGens = self.compatibleGens 489 elif len(remain(obj.compatibleGens, self.compatibleGens)) > 0: 490 print remain(obj.compatibleGens, self.compatibleGens) 491 raise ValueError("Incompatible generators found in component" 492 " '" + obj.name + "'") 493 if len(obj.targetLangs) == 0: 494 # object inherits self's 495 obj.targetLangs = self.targetLangs 496 elif len(remain(obj.targetLangs, self.targetLangs)) > 0: 497 print remain(obj.targetLangs, self.targetLangs) 498 raise ValueError("Incompatible target language in component" 499 " '" + obj.name + "'") 500 if obj.name in protected_allnames: 501 raise ValueError("Name '" + obj.name + "' is a protected name") 502 if not compareClassAndBases(obj, self._allSubcomponentTypes): 503 print "Valid sub-component types that have been declared:", \ 504 self._allSubcomponentTypes 505 raise TypeError("Invalid type for object '" + obj.name + \ 506 "' in component '" + self.name + "'") 507 if parent_obj is None: 508 parentname = None 509 else: 510 parentname = parent_obj.name 511 if isMultiDefClash(obj, self.multiDefRefs, parentname): 512 raise ValueError("Object %s defines names that already exist in" 513 " registry"%obj.name) 514 # # TEMP 515 # print "\n------------------------" 516 # print depth, self.name, self.freeSymbols 517 if hasattr(obj, '_registry'): 518 # if obj is a Component, then process its registry into ours 519 # but register the object itself 520 do_obj_register = False 521 # globalized name means we must create a local namemap 522 # for obj and its local free symbols. 523 # namemap for a registry object maps original (local) object name 524 # to globalized name at this level. 525 # tracebacknames are the names of all sub-component registry 526 # names that are to be added to the registry at this level. 527 namemap = {} 528 tracebacknames = [] 529 for sub_regObj in obj._registry.values(): 530 subobj = sub_regObj.obj 531 subobjname = self._register(subobj, depth+1, obj) 532 # _register won't have created an entry yet 533 namemap[subobj.name] = subobjname 534 if isMultiRef(subobjname): 535 # assumes isMultiRef(objname) == sub_regObj.obj.multiDefInfo[0] 536 # Add non-definition version of name to dictionary, 537 # i.e. add z to mapping dictionary containing z[i,0,4] 538 rootname = subobj.multiDefInfo[1] 539 ixname = subobj.multiDefInfo[2] 540 namemap[rootname] = obj.name+NAMESEP+rootname 541 tracebacknames.append(subobjname) 542 # for all newly registered names, add their associated 543 # namemap to the regObject, and map all occurrences of the 544 # local names from the sub-component to their new, globalized 545 # names at this level, now that we have collected up the namemap. 546 # 547 # First, take out symbols defined by obj (i.e. those appearing in 548 # its registry) from self.freeSymbols 549 mapper = symbolMapClass(namemap) 550 globalized_newdefs = mapper(obj._registry.keys()) 551 self.freeSymbols = remain(self.freeSymbols, 552 globalized_newdefs) 553 obj_freeSymbols = mapper(obj.freeSymbols) 554 # # TEMP 555 # print "New self.freeSymbols:", self.freeSymbols 556 # print "Processing ", obj.name 557 # print "Namemap" 558 # info(namemap) 559 # print "Obj free (orig):", obj.freeSymbols, "(mapped)", obj_freeSymbols 560 # the following never executes! 561 if obj_freeSymbols != obj.freeSymbols: 562 print "mapped object free symbols not same as originals" 563 # Test code introspection for HH_spectest.py 564 # if self.name == 'cell1' and obj.name == 'chan_s21': 565 # # this is None, so comment out namemap[s] = snew below! 566 # print "cell1: parentname =", parentname 567 for s in obj.freeSymbols: 568 if s not in namemap: 569 # then it didn't get mapped, so add to namemap 570 # that will be embedded in reg object 571 if parentname is None: 572 snew = self.name+NAMESEP+s 573 else: 574 snew = parentname+NAMESEP+s 575 # namemap[s] = snew 576 # # TEMP 577 # print self.name, obj.name, "namemap[%s] = %s"%(s,snew) 578 for sub_regObj in obj._registry.values(): 579 subobjname = namemap[sub_regObj.obj.name] 580 subsplit = subobjname.split(NAMESEP) 581 # resolve the first parent of objects brought up from deep 582 # in the sub-component tree 583 if len(subsplit) > 1: 584 actual_parentname = subsplit[-2] 585 if actual_parentname in obj.components: 586 actual_parent = obj.components[actual_parentname] 587 # print "Old parent %s"%actual_parentname 588 # print "parent is obj '%s': self=%s, subobj=%s"%(obj.name, self.name, subobjname) 589 # if obj.name == 'cell1': 590 # 1/0 591 else: 592 # TEMP 593 # print "Changing parent from %s to %s"%(actual_parentname, self.name) 594 # print "for obj %s, sub-obj %s"%(obj.name, subobjname) 595 # print obj._registry.keys() 596 # print obj.components.keys() 597 # 1/0 598 actual_parent = obj 599 # actual_parentname = self.name 600 # actual_parent = self 601 # print "parent is self '%s' (1): obj=%s, subobj=%s"%(self.name, obj.name, subobjname) 602 else: 603 actual_parent = obj 604 new_regObj = deepcopy(sub_regObj) 605 if isinstance(new_regObj.obj, Quantity): 606 if new_regObj.obj.typestr == 'auxfn': 607 # do not map the signature variables 608 func_namemap = copy(namemap) 609 for s in new_regObj.obj.signature: 610 try: 611 del func_namemap[s] 612 except KeyError: 613 pass 614 new_regObj.obj.mapNames(func_namemap) 615 else: 616 new_regObj.obj.mapNames(namemap) 617 self._registry[subobjname] = regObject(new_regObj.obj, 618 actual_parent) 619 self._registry[subobjname].namemap = namemap 620 # fix new registry object's local name to not include the 621 # highest level name prefix 622 localName = self._registry[subobjname].objLocalName 623 temp = localName.split(NAMESEP) 624 if len(temp) > 1: 625 localName = NAMESEP.join(temp[1:]) 626 self._registry[subobjname].objLocalName = localName 627 objname = obj.name 628 self._componentNameMap[obj] = objname #tracebacknames 629 oclasses = getSuperClasses(obj, ModelSpec) 630 for oclass in oclasses: 631 try: 632 if obj not in self._componentTypeMap[oclass]: 633 self._componentTypeMap[oclass].append(obj) 634 except KeyError: 635 self._componentTypeMap[oclass] = [obj] 636 do_free_symbs = True 637 else: 638 # obj is a Quantity object 639 do_obj_register = (depth == 0) 640 do_free_symbs = do_obj_register 641 try: 642 if depth > 0: # and obj.scope == localscope: 643 objname = nameResolver(obj, self, parentname) 644 else: 645 objname = obj.name 646 except AttributeError: 647 raise TypeError("Invalid object type in _register(): %s (type" 648 " %s)"%(obj.name, className(obj))) 649 # # TEMP 650 # print "Processing Quant:", obj.name, "(%s)"%objname 651 if hasattr(obj, 'multiDefInfo'): 652 # then Quantity spec may be defining multiple quantities, 653 # so check it 654 if obj.multiDefInfo[0]: 655 # no longer need first entry of multiDefInfo. 656 # store the info for purposes of flattening, using 657 # the *rootname* as key (e.g. 'z' and not 'z[i]'). 658 # Note that the rootname has the parentname appended, 659 # if it exists 660 if parentname is None: 661 rootname = obj.multiDefInfo[1] 662 else: 663 rootname = parentname+NAMESEP+obj.multiDefInfo[1] 664 self.multiDefRefs[rootname] = obj.multiDefInfo[2:] 665 self._componentNameMap[obj] = objname 666 oclasses = getSuperClasses(obj) 667 for oclass in oclasses: 668 try: 669 if obj not in self._componentTypeMap[oclass]: 670 self._componentTypeMap[oclass].append(obj) 671 except KeyError: 672 self._componentTypeMap[oclass] = [obj] 673 if objname in self._registry: 674 raise ValueError("Name '%s' already exists in "%objname \ 675 + "registry of object '%s'"%self.name) 676 # remove free symbols from self's sub-components if 677 # they are being defined here 678 for fs in copy(self.freeSymbols): 679 fs_split = fs.split(NAMESEP) 680 if len(fs_split) > 1: 681 checkname = "".join(fs_split[1:]) 682 else: 683 checkname = fs 684 if objname == checkname: 685 self.freeSymbols.remove(fs) 686 obj_freeSymbols = obj.freeSymbols 687 if do_obj_register: 688 self._registry[objname] = regObject(deepcopy(obj), 689 parent_obj or self) 690 # localname = objname so no need for the next line 691 # self._registry[objname].objLocalName = obj.name.split(NAMESEP)[-1] 692 if do_free_symbs: 693 # # TEMP 694 # print "new free symbols from obj:", obj_freeSymbs 695 # print "defined:", self._registry.keys() 696 self.freeSymbols.extend(remain(obj_freeSymbols, 697 self._registry.keys()+self.freeSymbols)) 698 # print "resulting self.free:", self.freeSymbols 699 # return objname in case _register is being called recursively 700 return objname
701 702
703 - def __delitem__(self, name):
704 """Delete named object (Var, Par, Fun, Input, or sub-component) 705 """ 706 self.remove(name)
707
708 - def __getitem__(self, name):
709 """Return object named using the hierarchical naming format.""" 710 try: 711 return self._registry[name].obj 712 except KeyError: 713 # full hierarchical name not found in registry, so 714 # check for components (not kept in registry) 715 name_list = name.split('.') 716 try: 717 c = self.components[name_list[0]] 718 except KeyError: 719 if name == '': 720 # for compatibility with empty string index 721 return self 722 else: 723 raise ValueError("Object %s not found in registry"%name) 724 else: 725 rest = name_list[1:] 726 if len(rest) > 0: 727 return c['.'.join(rest)] 728 else: 729 return c
730 731
732 - def remove(self, target):
733 """Remove target component from specification. 734 735 Use global hierarchical names for components if specifying a string.""" 736 737 if hasattr(target, 'name'): 738 objname = target.name 739 elif isinstance(target, str): 740 objname = target 741 elif isinstance(target, list): 742 for t in target: self.remove(t) 743 return 744 else: 745 raise TypeError("Invalid type to remove from ModelSpec " 746 "'%s'"%self.name) 747 try: 748 del self._registry[objname] 749 except KeyError: 750 # objname may refer to a component, which is not stored in registry 751 pass 752 if isHierarchicalName(objname): 753 tempname = objname.split(NAMESEP) 754 parentname = tempname[0] 755 try: 756 self.components[parentname].remove("".join(tempname[1:])) 757 except ValueError, e: 758 print e 759 raise ValueError("Error recognizing parent object '%s' in " 760 "sub-component '%s'"%(parentname,objname)) 761 else: 762 if objname in self.variables: 763 del self.variables[objname] 764 elif objname in self.pars: 765 del self.pars[objname] 766 elif objname in self.inputs: 767 del self.inputs[objname] 768 elif objname in self.components: 769 del self.components[objname] 770 elif objname in self.auxfns: 771 del self.auxfns[objname] 772 protected_auxnamesDB.removeAuxFn(objname) 773 else: 774 raise ValueError("%s: Error recognizing object '%s'"%(self.name,objname)) 775 # This is inefficient, but simply reregister everything to 776 # work out the change in free symbols! 777 self.freeSymbols = [] 778 self.multiDefRefs = {} 779 self._registry = {} 780 self._componentNameMap = {} 781 self._componentTypeMap = {} 782 nameResolver.clear(self) 783 for v in self.variables.values(): 784 self._register(v) 785 for p in self.pars.values(): 786 self._register(p) 787 for i in self.inputs.values(): 788 self._register(i) 789 for a in self.auxfns.values(): 790 self._register(a) 791 # do components, if any, last, so that self._componentNameMap is 792 # built up properly (otherwise get validation error) 793 if hasattr(self, 'components'): 794 # leaf nodes do not have this 795 for c in self.components.values(): 796 self._register(c) 797 try: 798 self.validate() 799 except AssertionError, e: 800 raise RuntimeError("Model spec structure inconsistent: "+str(e))
801 802
803 - def isComplete(self, globalRefs=None):
804 if globalRefs is None: 805 globalRefs = allmathnames_symbolic + \ 806 ['True', 'False', 'and', 'or', 'not'] 807 else: 808 globalRefs.extend(allmathnames_symbolic + \ 809 ['True', 'False', 'and', 'or', 'not']) 810 return remain(self.freeSymbols, globalRefs) == []
811 812
813 - def __call__(self):
814 # info is defined in PyDSTool.utils 815 utils_info(self.__dict__, "ModelSpec " + self.name)
816 817
818 - def info(self, verboselevel=1):
819 if verboselevel > 0: 820 # info is defined in PyDSTool.utils 821 utils_info(self.__dict__, "ModelSpec " + self.name, 822 recurseDepthLimit=1+verboselevel) 823 else: 824 print self.__repr__()
825 826
827 - def __repr__(self):
828 return self._infostr(1)
829
830 - def _infostr(self, verbose=1):
831 base_str = "Component " + self.name 832 if verbose > 0: 833 if len(self.components) > 0: 834 base_str += "\n Components: ( " 835 base_str += ", ".join([c._infostr(verbose-1) for c in self.components.values()]) + " )" 836 if len(self.variables) > 0: 837 base_str += "\n Variables: ( " 838 base_str += ", ".join([str(c) for c in self.variables.values()]) + " )" 839 if len(self.pars) > 0: 840 base_str += "\n Parameters: ( " 841 base_str += ", ".join([str(c) for c in self.pars.values()]) + " )" 842 if len(self.inputs) > 0: 843 base_str += "\n Inputs: ( " 844 base_str += ", ".join([str(c) for c in self.inputs.values()]) + " )" 845 if len(self.auxfns) > 0: 846 base_str += "\n Functions: ( " 847 base_str += ", ".join([str(c) for c in self.auxfns.values()]) + " )" 848 return base_str
849 850
851 - def search(self, name, component_type_order=None):
852 """Find Quantity objects containing a component named <name>, 853 of type given by the hierarchical name <comptype1.comptype2. ... .name>, 854 where component_type_order = [<comptype1>, <comptype2>, ... ], 855 and <comptypeN> may be a component type (as a wildcard) or a specific 856 component name. 857 """ 858 return searchModelSpec(self, name, component_type_order)
859 860 861 __str__ = __repr__ 862 863
864 - def __copy__(self):
865 pickledself = pickle.dumps(self) 866 return pickle.loads(pickledself)
867 868
869 - def __deepcopy__(self, memo=None, _nil=[]):
870 pickledself = pickle.dumps(self) 871 return pickle.loads(pickledself)
872 873 874 # ---------------------------------------------------------------------------- 875
876 -class Component(ModelSpec):
877 """Non-leaf node sub-class of ModelSpec abstract class.""" 878
879 - def compileFuncSpec(self, ignoreInputs=False):
880 self.validate() 881 assert self.isDefined(True, ignoreInputs=ignoreInputs), \ 882 "Node '" + self.name + "' is not completely defined" 883 fsdict = {} 884 fsdict['inputs'] = [] 885 fsdict['vars'] = [] 886 fsdict['pars'] = [] 887 fsdict['auxfns'] = [] 888 for objname, regObj in self._registry.iteritems(): 889 objtype = regObj.obj.typestr+'s' 890 add_obj = regObj.obj.renderForCode() 891 if regObj.namemap != {}: 892 if isinstance(add_obj, Fun): 893 # AuxFns may only reference pars, so raise 894 # error if the auxfn's namemap refers (i.e. is bound) 895 # to a non-parameter 896 for name in regObj.namemap: 897 mappedName = regObj.namemap[name] 898 if mappedName in add_obj.freeSymbols and not \ 899 isinstance(self._registry[mappedName].obj, (Par, Fun)): 900 print "Problem with registry object:", \ 901 self._registry[mappedName].obj 902 print "in auxiliary function:", add_obj.name 903 print " that has free symbols:", \ 904 add_obj.freeSymbols 905 raise TypeError("Fun '" + add_obj.name + "' " 906 "cannot bind symbols to non-" 907 "pars (info printed above)") 908 # redundant mapping (already has been done) 909 # add_obj.spec.mapNames(regObj.namemap) 910 fsdict[objtype].append(add_obj) 911 fsdict['complete'] = self.isComplete() 912 self.funcSpecDict = fsdict
913 914
915 - def isEmpty(self):
916 return self.components == {}
917 918
919 - def isDefined(self, verbose=False, ignoreInputs=False):
920 # Quantity is defined if it has a specified definition 921 # (even with unbound references) 922 defined = ModelSpec.isDefined(self, verbose, ignoreInputs) 923 for c in self.components.values(): 924 if not c.isDefined(verbose, ignoreInputs): 925 return False 926 return defined
927 928 # ---------------------------------------------------------------------------- 929
930 -class LeafComponent(ModelSpec):
931 """Leaf node sub-class of ModelSpec abstract class.""" 932
933 - def _register(self, obj, depth=0, parent_obj=None):
934 if not compareBaseClass(obj, Quantity): 935 print "Bad argument %s (type %s) to register"%(str(obj), type(obj)) 936 raise TypeError("Not a valid Quantity type") 937 return ModelSpec._register(self, obj, 0, parent_obj)
938 939
940 - def isEmpty(self):
941 return self.variables == {} and self.pars == {} \ 942 and self.inputs == {}
943 944
945 - def compileFuncSpec(self, ignoreInputs=False):
946 self.validate() 947 assert self.isDefined(ignoreInputs=ignoreInputs), \ 948 "Node '" + self.name + "' is not completely defined" 949 fsdict = {} 950 fsdict['vars'] = [deepcopy(v) for v in self.variables.values()] 951 fsdict['pars'] = [deepcopy(p) for p in self.pars.values()] 952 fsdict['inputs'] = [deepcopy(i) for i in self.inputs.values()] 953 fsdict['auxfns'] = [deepcopy(a) for a in self.auxfns.values()] 954 self.funcSpecDict = fsdict
955 956
957 - def add(self, arg):
958 if isinstance(arg, list): 959 for obj in arg: 960 self.add(obj) 961 else: 962 if isinstance(arg, ModelSpec): 963 raise TypeError("Cannot add sub-components to a leaf node") 964 else: 965 ModelSpec.add(self, arg)
966 967 # add the valid sub-component types now that all classes have been declared 968 Component.compatibleSubcomponents=(Par, Var, Input, Fun, 969 Component, LeafComponent) 970 Component.compatibleContainers=(Component,) 971 972 LeafComponent.compatibleSubcomponents=(Par, Var, Input, Fun) 973 LeafComponent.compatibleContainers=(Component,LeafComponent) 974 975 # ---------------------------------------------------------------------------- 976 ### Search utilities 977
978 -def allCompTypes(class_list):
979 """Filter full list of all super-classes' compatible sub-component 980 types to remove any classes that are not over-ridden by sub-classes. 981 982 e.g. If Par was allowed for Component type, but a sub-class of 983 Component specifies only a sub-class of Par may be used, then 984 Par is removed from the original list of valid component types. 985 """ 986 filter_list = [] 987 for c in class_list: 988 filter_list.extend([d for d in class_list if issubclass(c, d) and c!=d]) 989 return [c for c in class_list if c not in filter_list]
990 991 992
993 -def accrueCompTypes(c):
994 """Collect all super-class component types (string names) and accumulate 995 recursively. 996 """ 997 cs = list(c.compatibleSubcomponents) 998 for cb in c.__bases__: 999 if cb is ModelSpec: 1000 # don't go any deeper as there won't be any more 1001 # componentTypes -- this is the top of this class tree 1002 break 1003 else: 1004 try: 1005 cs.extend(accrueCompTypes(cb)) 1006 except AttributeError: 1007 # cover the event of a user-defined multiple 1008 # inheritance with non ModelSpec classes 1009 pass 1010 return makeSeqUnique(cs)
1011 1012
1013 -def matchSubName(nameList, subName, position, level=0, invert=False):
1014 """Return a list of names from nameList matching subName at the given 1015 position (at the hierarchical name level given, if the name is 1016 hierarchical). 1017 1018 e.g. For a hierarchical variable name such as 'sRM.s_cell1_cell2', 1019 level=0 will cause a search for a match with subName in the top-level name, 1020 i.e. 'sRM', at the given position. Whereas, level=1 will search for a match 1021 in 's_cell1_cell2' at the given position. 1022 1023 invert=True will return names that did not match.""" 1024 matchList = [] 1025 for name in nameList: 1026 if isHierarchicalName(name): 1027 hier_parts = name.split('.') 1028 searchName = hier_parts[level] 1029 else: 1030 searchName = name 1031 subName_parts = searchName.split('_') 1032 if invert: 1033 if subName != subName_parts[position]: 1034 matchList.append(name) 1035 else: 1036 if subName == subName_parts[position]: 1037 matchList.append(name) 1038 return matchList
1039 1040
1041 -def searchModelSpec(mspec, name, component_type_order=None):
1042 """Find Quantity or sub-component objects containing named <name> 1043 of type given by the hierarchical name <comptype1.comptype2. ... .name>, 1044 where component_type_order = [<comptype1>, <comptype2>, ... ], 1045 and <comptypeN> may be a component type or a specific component name.""" 1046 if isHierarchicalName(name): 1047 # transfer root names to component_type_order 1048 if component_type_order is None or component_type_order == []: 1049 # OK to continue 1050 parts = name.split(NAMESEP) 1051 return searchModelSpec(mspec, parts[-1], parts[:-1]) 1052 else: 1053 raise ValueError("Cannot pass both a hierarchical name to search" 1054 " and a non-empty component_type_order argument") 1055 if component_type_order is None or component_type_order == []: 1056 # we have gone as far as intended 1057 if name in mspec._registry: 1058 return [name] 1059 elif name in mspec.components: 1060 return [name] 1061 elif name in mspec._componentTypeMap: 1062 return [mspec._componentNameMap[o] for o in \ 1063 mspec._componentTypeMap[name]] 1064 else: 1065 return [] 1066 else: 1067 ct = component_type_order[0] 1068 try: 1069 complist = mspec._componentTypeMap[ct] 1070 except KeyError: 1071 # ct not present in this generator as a type 1072 # see if it's an actual component 1073 try: 1074 complist = [mspec.components[ct]] 1075 except KeyError: 1076 return [] 1077 result = [] 1078 # special case for distinguishing RHSfuncSpec vs ExpFuncSpec vars, which 1079 # have to be the last component type specified 1080 if name in ['RHSfuncSpec', 'ExpFuncSpec'] and component_type_order[1:] == []: 1081 for comp in complist: 1082 compname = mspec._componentNameMap[comp] 1083 try: 1084 test = mspec._registry[compname].obj.specType == name 1085 except (KeyError, AttributeError): 1086 # invalid kind of object to have a specType (e.g. a Component) 1087 test = False 1088 if test: 1089 result.append(compname) 1090 else: 1091 for comp in complist: 1092 comp_result = searchModelSpec(comp, name, component_type_order[1:]) 1093 result.extend([comp.name + NAMESEP + r for r in comp_result]) 1094 return result
1095 1096 # ------------------------------------------------------------------------- 1097 ### Quantity 'for' macro utilities for ModelSpecs 1098
1099 -def processMacro(c, mspec, forSubs=False):
1100 """Process 'for' macro occurrences in a Quantity specification. 1101 1102 forSubs = True causes any target names to be resolved to their 1103 global names and returned to caller so that they can be substituted 1104 textually into the Quantity's specification.""" 1105 1106 ctypestrlist = [] # component type names to remove 1107 targList = [] 1108 toks = c.spec.parser.tokenized 1109 num_fors = toks.count('for') 1110 specStr = "" 1111 forpos = 0 1112 for for_ix in range(num_fors): 1113 old_forpos = forpos 1114 forpos += toks[forpos:].index('for') 1115 if old_forpos == 0: 1116 specStr += "".join(toks[:forpos]) 1117 else: 1118 specStr += "".join(toks[old_forpos+1:forpos]) 1119 qtemp = QuantSpec('macrospec', toks[forpos+1]) 1120 # targList_new, ctStr, opStr = resolveMacroTargets(qtemp.parser.tokenized, 1121 # registry, c.name) 1122 parentName = ".".join(c.name.split('.')[:-1]) 1123 sourceList = searchModelSpec(mspec, parentName) 1124 if len(sourceList) == 1: 1125 sourceName = sourceList[0] 1126 elif len(sourceList) == 0: 1127 sourceName = "" 1128 else: 1129 print "Found: ", sourceList 1130 raise ValueError("source list for macro resolution should have no " 1131 "more than one entry") 1132 targList_new, ctStr, opStr = resolveMacroTargets(qtemp.parser.tokenized, 1133 mspec, sourceName) 1134 ctypestrlist.append(ctStr) 1135 if forSubs: 1136 for t in targList_new: 1137 if t not in targList: targList.append(t) 1138 if targList_new == []: 1139 specStr += "0" 1140 else: 1141 specStr += "(" + opStr.join(targList_new) + ")" 1142 forpos += 1 1143 # add rest of original spec string 1144 specStr += "".join(toks[forpos+1:]) 1145 c.spec = QuantSpec(replaceSep(c.spec.subjectToken), specStr.strip(), 1146 c.spec.specType) 1147 # remove componentTypeStr from c.freeSymbols and 1148 # c.usedSymbols 1149 c.freeSymbols = remain(c.freeSymbols, ctypestrlist) 1150 c.usedSymbols = remain(c.usedSymbols, ctypestrlist+['for']) 1151 return (c, targList, ctypestrlist)
1152 1153
1154 -def resolveMacroTargets(toks, mspec, targParent):
1155 # opStr already checked to be either + or * 1156 opStr = toks[5] 1157 localTarget = toks[3] 1158 componentTypeStr = toks[1] 1159 if componentTypeStr in ['Var', 'Par', 'Quantity']: 1160 raise ValueError("Invalid component type for macro in" 1161 " %s"%targParent) 1162 if targParent == "": 1163 target = componentTypeStr+'.'+localTarget 1164 else: 1165 target = targParent+'.'+componentTypeStr+'.'+localTarget 1166 targList = searchModelSpec(mspec, target) 1167 return (targList, componentTypeStr, opStr)
1168 1169 # --------------------------------------------------------------------------- 1170 ### Private classes 1171
1172 -class regObject(object):
1173 """Registry object container.""" 1174
1175 - def __init__(self, obj, parent, namemap={}):
1176 self.obj = obj 1177 self.objClass = obj.__class__ 1178 self.objParentClass = parent.__class__ 1179 self.objParentName = parent.name 1180 self.objLocalName = obj.name 1181 # maps local variable name to globalized name (if global, o/w identical) 1182 self.namemap = copy(namemap)
1183
1184 - def __eq__(self, other):
1185 tests = [] 1186 try: 1187 tests.append(self.objClass == other.objClass) 1188 tests.append(self.objParentClass == other.objParentClass) 1189 tests.append(self.objParentName == other.objParentName) 1190 tests.append(self.objLocalName == other.objLocalName) 1191 tests.append(self.obj == other.obj) 1192 tests.append(self.namemap == other.namemap) 1193 except AttributeError: 1194 return False 1195 return alltrue(tests)
1196
1197 - def __repr__(self):
1198 return "regObject %s (%s)"%(self.objLocalName, className(self.obj))
1199
1200 - def __str__(self):
1201 return self.obj.spec()
1202
1203 - def __call__(self):
1204 return (self.objLocalName, self.objClass, self.namemap, self.obj)
1205
1206 - def getGlobalName(self):
1207 if self.namemap == {}: 1208 return self.objLocalName 1209 else: 1210 try: 1211 return self.namemap[self.objLocalName] 1212 except KeyError: 1213 print "Namemap for '%s' is: "%self.__repr__(), self.namemap 1214 raise ValueError("Namemap for registry object didn't " 1215 "contain object's name " 1216 "'%s'"%self.objLocalName)
1217 1218
1219 -class typeCounter(object):
1220 - def __init__(self):
1221 self.varcount = self.parcount = self.funcount = self.inpcount = 0
1222
1223 - def __getitem__(self, typelabel):
1224 if typelabel == 'var': 1225 return self.varcount 1226 elif typelabel == 'par': 1227 return self.parcount 1228 elif typelabel == 'input': 1229 return self.inpcount 1230 elif typelabel == 'auxfn': 1231 return self.funcount 1232 else: 1233 raise KeyError("Invalid key")
1234
1235 - def __setitem__(self, typelabel, value):
1236 if typelabel == 'var': 1237 self.varcount = value 1238 elif typelabel == 'par': 1239 self.parcount = value 1240 elif typelabel == 'input': 1241 self.inpcount = value 1242 elif typelabel == 'auxfn': 1243 self.funcount = value 1244 else: 1245 raise KeyError("Invalid key")
1246 1247 1248
1249 -class nameResolverClass(object):
1250 """This class keeps a tab of how many times a local name has been 1251 used for a given specfication type ('var', 'par', 'input' or 1252 'auxfn'), and renames it with an appropriate globalized name 1253 (hierarchical according to declared parent object, with possible 1254 numbered suffix for multiple name declarations). 1255 1256 Only one instance of this class is needed for a session.""" 1257
1258 - def __init__(self):
1259 self.database = {}
1260
1261 - def __call__(self, obj, fspec, parentname=None):
1262 typelabel = className(obj).lower() 1263 if parentname is None: 1264 fullname = obj.name 1265 else: 1266 # create hierarchical name 1267 fullname = parentname + NAMESEP + obj.name 1268 if fspec.name not in self.database: 1269 self.database[fspec.name] = {} 1270 if obj.name in self.database[fspec.name]: 1271 self.database[fspec.name][fullname][typelabel] += 1 1272 namecount = self.database[fspec.name][fullname][typelabel] 1273 else: 1274 self.database[fullname] = typeCounter() 1275 namecount = 0 1276 if namecount == 0: 1277 globalname = fullname 1278 else: 1279 # add number to end of name if multiple instances of the 1280 # same hierarchical name declared 1281 globalname = fullname + NAMESEP + str(namecount) 1282 return globalname
1283
1284 - def __repr__(self):
1285 return "ModelSpec internal helper class: nameResolver object"
1286 1287 __str__ = __repr__ 1288
1289 - def clear(self, fspec):
1290 if fspec.name in self.database: 1291 del self.database[fspec.name]
1292
1293 - def clearall(self):
1294 self.database = {}
1295 1296 1297 # use single instance of nameResolver per session 1298 global nameResolver 1299 nameResolver = nameResolverClass() 1300