Package PyDSTool :: Package Generator :: Module baseclasses
[hide private]
[frames] | no frames]

Source Code for Module PyDSTool.Generator.baseclasses

   1  # Generator base classes: Generator, ctsGen, discGen
 
   2  from __future__ import division 
   3  
 
   4  from allimports import * 
   5  from PyDSTool.utils import * 
   6  from PyDSTool.common import * 
   7  from PyDSTool.Symbolic import ensureStrArgDict, Quantity, QuantSpec 
   8  from PyDSTool.Trajectory import Trajectory 
   9  from PyDSTool.parseUtils import symbolMapClass, readArgs 
  10  from PyDSTool.Variable import Variable, iscontinuous 
  11  from PyDSTool.Points import Pointset 
  12  import PyDSTool.Events as Events 
  13  
 
  14  # Other imports
 
  15  from numpy import Inf, NaN, isfinite, sometrue, alltrue 
  16  import math, random 
  17  import os 
  18  from copy import copy, deepcopy 
  19  try: 
  20      # use pscyo JIT byte-compiler optimization, if available
 
  21      import psyco 
  22      HAVE_PSYCO = True 
  23  except ImportError: 
  24      HAVE_PSYCO = False 
  25  
 
  26  # -----------------------------------------------------------------------------
 
  27  
 
  28  __all__ = ['ctsGen', 'discGen', 'theGenSpecHelper', 'Generator',
 
  29             'genDB', 'auxfn_container', '_pollInputs'] 
  30  
 
  31  # -----------------------------------------------------------------------------
 
  32  
 
33 -class genDBClass(object):
34 """This class keeps a record of which non-Python Generators have been 35 created in a session. A single global instance of this class is created, 36 and prevents the user from re-using the name of a low-level 37 DLL (such as that for a Dopri_ODEsystem vector field) unless the 38 system is identical (according to its FuncSpec).""" 39
40 - def __init__(self):
41 self.database = {}
42
43 - def check(self, gen):
44 """Look up generator instance and return Boolean of whether 45 it is already registered. Only non-python based generators 46 are registered, so all others will return False. 47 """ 48 if gen.funcspec.targetlang == 'python': 49 # these do not need to be checked 50 return False 51 try: 52 if gen._solver.rhs in self.database: 53 entry = self.database[gen._solver.rhs] 54 return className(gen) == entry['class'] \ 55 and hash(gen.funcspec) == entry['hash'] 56 else: 57 return False 58 except AttributeError: 59 # these do not need to be checked 60 return False
61
62 - def unregister(self, gen):
63 try: 64 del self.database[gen._solver.rhs] 65 except (AttributeError, KeyError): 66 # invalid type of generator or not present 67 # so do nothing 68 return
69
70 - def register(self, gen):
71 if gen.funcspec.targetlang == 'python': 72 # these do not need to be checked 73 return 74 # verify name of vector field in database 75 try: 76 if gen._solver.rhs not in self.database: 77 self.database[gen._solver.rhs] = {'name': gen.name, 78 'class': className(gen), 79 'hash': hash(gen.funcspec)} 80 else: 81 if hash(gen.funcspec) != self.database[gen._solver.rhs]['hash']: 82 raise PyDSTool_KeyError("Generator named %s already exists"%gen.name) 83 # otherwise it's identical and can ignore 84 except AttributeError: 85 # these do not need to be checked 86 return
87
88 - def __repr__(self):
89 s = "Generator internal database class: " 90 s += str(self.database.keys()) 91 return s
92 93 __str__ = __repr__ 94 95
96 - def clearall(self):
97 self.database = {}
98 99 100 # use single instance of nameResolver per session 101 global genDB 102 genDB = genDBClass() 103 104 105 # ---------------------------------------------------------------------- 106
107 -class auxfn_container(object):
108 """ 109 Auxiliary function interface for python user 110 """
111 - def __init__(self, genref):
112 self.genref = genref 113 self.map_ixs = ixmap
114
115 - def __getitem__(self, k):
116 try: 117 return self.__dict__[k] 118 except KeyError: 119 raise KeyError("Function %s not present"%k)
120
121 - def keys(self):
122 return [k for k in self.__dict__.keys() \ 123 if k not in ['genref', 'map_ixs']]
124
125 - def __contains__(self, k):
126 return k in self.keys()
127
128 - def values(self):
129 return [self.__dict__[k] for k in self.__dict__.keys() \ 130 if k not in ['genref', 'map_ixs']]
131
132 - def items(self):
133 return zip(self.keys(), self.values())
134
135 - def __repr__(self):
136 return "Aux functions: " + ", ".join(self.keys())
137 138
139 -class ixmap(dict):
140 - def __init__(self, genref):
141 self.parixmap = {} 142 self.pars = genref.pars 143 i = genref.inputs.keys() 144 i.sort() 145 p = genref.pars.keys() 146 p.sort() 147 allnames = p + i 148 for pair in enumerate(allnames): 149 self.parixmap[pair[0]] = pair[1]
150
151 - def __getitem__(self, k):
152 try: 153 return self.pars[self.parixmap[k]] 154 except: 155 raise "Cannot access external input values using ixmap class"
156
157 - def __repr__(self):
158 return "Index mapping: " + str(self.parixmap)
159 160
161 -class Generator(object):
162 """ 163 Trajectory Generator abstract class. 164 """ 165 # query keys for 'query' method 166 _querykeys = ['pars', 'parameters', 'events', 'abseps', 167 'ics', 'initialconditions', 'vars', 'variables', 168 'auxvariables', 'auxvars', 'vardomains', 'pardomains'] 169 # initialization keyword keys 170 _needKeys = ['name'] 171 _optionalKeys = ['globalt0', 'checklevel', 'model', 'abseps', 172 'eventPars', 'FScompatibleNames', 173 'FScompatibleNamesInv'] 174
175 - def __init__(self, kw):
176 # _funcreg stores function instances (and base classes) that are 177 # passed from self.funcspec, as they are not defined in __main__. 178 # A Generator object cannot be deep copied or 179 # pickled without the additional __getstate__ and __setstate__ 180 # methods which know how to reconstruct these functions. 181 self._funcreg = {} 182 try: 183 # sometimes certain keys are checked prior to calling this base 184 # class so we don't want to tread on the feet of those __init__ 185 # methods 186 dummy = self.foundKeys 187 # if this works then we won't reset to zero 188 except: 189 self.foundKeys = 0 190 try: 191 self.name = kw['name'] 192 self.foundKeys += 1 193 except KeyError: 194 raise PyDSTool_KeyError("name must be supplied as a keyword in arguments") 195 # dimension value is set later 196 self.dimension = None 197 # Regular Variable objects, and auxiliary variables. 198 # These are callable and can even appear in 199 # inputs. They are defined internally by the FuncSpec object. 200 # These self.variables are generally placeholders containing domain 201 # information only, except for the special Generator types LookupTable 202 # and InterpTable. The actual variable contents from a computed 203 # trajectory are created locally during computeTraj and immediately 204 # exported to a Trajectory object, not affecting these variable objects. 205 self.variables = {} 206 # initial conditions for each regular and auxiliary variable 207 self.initialconditions = {} 208 # Generator's functional specification for variables, e.g. 209 # right-hand side of ODE, or data lists for look-up table 210 self.funcspec = None 211 # Generator's internal pars - for the functional specification 212 self.pars = {} 213 # Place-holder for event structure, if used 214 self.eventstruct = None 215 # Place-holder for latest trajectory events 216 self.trajevents = None 217 # Autonomous external inputs (dictionary of callable Variables) 218 self.inputs = {} 219 # Local independent variable interval and validity range (relative to global t0) 220 self.indepvariable = None 221 # Sorted list of callable names 222 self._callnames = [] 223 # Registry of object names and types 224 self._registry = {} 225 # Internal flag for whether the generator is being used as part of a hybrid DS 226 # (useful for control over high level event resetting between trajectory 227 # segments when generators are reused) -- HybridModel class must keep 228 # track of this and set using the _set_for_hybrid_DS method 229 self._for_hybrid_DS = False 230 # Absolute tolerance for Interval endpoints 231 if 'abseps' in kw: 232 self._abseps = kw['abseps'] 233 self.foundKeys += 1 234 else: 235 self._abseps = 1e-13 236 # `Checklevel` code determining response to uncertain float comparisons 237 if 'checklevel' in kw: 238 if kw['checklevel'] not in range(4): 239 raise ValueError('Invalid interval endpoint checking option') 240 self.checklevel = kw['checklevel'] 241 self.foundKeys += 1 242 else: 243 # default: no checking 244 self.checklevel = 0 245 246 self.diagnostics = Diagnostics(errmessages, errorfields, warnmessages, 247 warnfields, propagate_dict=self.inputs) 248 249 # global independent variable reference (for non-autonomous systems, 250 # especially when a Generator is embedded in a hybrid Model object) 251 if 'globalt0' in kw: 252 v = kw['globalt0'] 253 assert isinstance(v, _num_types), 'Incorrect type of globalt0' 254 self.globalt0 = v 255 self.foundKeys += 1 256 else: 257 self.globalt0 = 0 258 259 # If part of a Model, keep a reference to which one this 260 # Generator belongs to 261 if 'model' in kw: 262 assert isinstance(kw['model'], str), "model tag must be a string" 263 self._modeltag = kw['model'] 264 self.foundKeys += 1 265 else: 266 self._modeltag = None 267 if 'FScompatibleNames' in kw: 268 sm = kw['FScompatibleNames'] 269 if sm is None: 270 sm = symbolMapClass() 271 self._FScompatibleNames = sm 272 self.foundKeys += 1 273 else: 274 self._FScompatibleNames = symbolMapClass() 275 if 'FScompatibleNamesInv' in kw: 276 sm = kw['FScompatibleNamesInv'] 277 if sm is None: 278 sm = symbolMapClass() 279 self._FScompatibleNamesInv = sm 280 self.foundKeys += 1 281 else: 282 self._FScompatibleNamesInv = symbolMapClass() 283 284 # If there are eventPars, keep track of them (name list) 285 self._eventPars = [] 286 if 'eventPars' in kw: 287 if isinstance(kw['eventPars'], list): 288 self._eventPars = kw['eventPars'] 289 elif isinstance(kw['eventPars'], str): 290 self._eventPars.append(kw['eventPars']) 291 self.foundKeys += 1 292 self._eventPars = self._FScompatibleNames(self._eventPars) 293 # Indicator of whether a trajectory has been successfully computed 294 self.defined = False
295 296
297 - def addEvtPars(self, eventPars):
298 """Register parameter names as event specific parameters.""" 299 if isinstance(eventPars, list): 300 self._eventPars.extend(self._FScompatibleNames(eventPars)) 301 elif isinstance(eventPars, str): 302 self._eventPars.append(self._FScompatibleNames(eventPars))
303
304 - def getEvents(self, evnames=None, asGlobalTime=True):
305 """Produce dictionary of pointsets of all flagged events' independent 306 and dependent variable values, for each event (whether terminal or not). 307 Times will be globalized if optional asGlobalTime argument is True 308 (default behavior). If a single event name is passed, only the pointset 309 is returned (not a dictionary). 310 311 evnames may be a singleton string or list of strings, or left blank to 312 return data for all events. 313 314 The events are not guaranteed to be ordered by the value of the 315 independent variable. 316 """ 317 compat_evnames = self._FScompatibleNamesInv(self.trajevents.keys()) 318 if evnames is None: 319 evnames = compat_evnames 320 if asGlobalTime: 321 t_offset = self.globalt0 322 else: 323 t_offset = 0 324 if isinstance(evnames, str): 325 # singleton 326 assert evnames in compat_evnames, "Invalid event name provided: %s"%evnames 327 try: 328 result = self.trajevents[self._FScompatibleNames(evnames)] 329 except AttributeError: 330 # empty pointset 331 return None 332 else: 333 result.indepvararray += t_offset 334 return result 335 else: 336 # assume a sequence of strings 337 assert all([ev in compat_evnames for ev in evnames]), \ 338 "Invalid event name(s) provided: %s"%str(evnames) 339 result = {} 340 for (evname, evptset) in self.trajevents.iteritems(): 341 compat_evname = self._FScompatibleNamesInv(evname) 342 if compat_evname not in evnames: 343 continue 344 result[compat_evname] = copy(evptset) 345 try: 346 result[compat_evname].indepvararray += t_offset 347 except AttributeError: 348 # empty pointset 349 pass 350 return result
351
352 - def getEventTimes(self, evnames=None, asGlobalTime=True):
353 """Produce dictionary of lists of all flagged events' independent 354 variable values, for each event (whether terminal or not). 355 Times will be globalized if optional asGlobalTime argument is True 356 (default behavior). If a single event name is passed, only the pointset 357 is returned (not a dictionary). 358 359 evnames may be a singleton string or list of strings, or left blank to 360 return data for all events. 361 362 The events are guaranteed to be ordered by the value of the 363 independent variable. 364 """ 365 result = {} 366 if asGlobalTime: 367 t_offset = self.globalt0 368 else: 369 t_offset = 0 370 compat_evnames = self._FScompatibleNamesInv(self.trajevents.keys()) 371 if evnames is None: 372 evnames = compat_evnames 373 if isinstance(evnames, str): 374 # singleton 375 assert evnames in compat_evnames, "Invalid event name provided: %s"%evnames 376 try: 377 return self.trajevents[self._FScompatibleNames(evnames)].indepvararray \ 378 + t_offset 379 except AttributeError: 380 # empty pointset 381 return [] 382 else: 383 # assume a sequence of strings 384 assert all([ev in compat_evnames for ev in evnames]), \ 385 "Invalid event name(s) provided: %s"%str(evnames) 386 for (evname, evptset) in self.trajevents.iteritems(): 387 compat_evname = self._FScompatibleNamesInv(evname) 388 if compat_evname not in compat_evnames: 389 continue 390 try: 391 result[compat_evname] = evptset.indepvararray + t_offset 392 except AttributeError: 393 # empty pointset 394 result[compat_evname] = [] 395 return result
396 397
398 - def query(self, querykey=''):
399 """Return info about Generator set-up. 400 Valid query key: 'pars', 'parameters', 'pardomains', 'events', 401 'ics', 'initialconditions', 'vars', 'variables', 402 'auxvars', 'auxvariables', 'vardomains' 403 """ 404 assert isinstance(querykey, str), \ 405 ("Query argument must be a single string") 406 if querykey not in self._querykeys: 407 print 'Valid query keys are:', self._querykeys 408 print "('events' key only queries model-level events, not those" 409 print " inside sub-models)" 410 raise ValueError('Query key '+querykey+' is not valid') 411 if querykey in ['pars', 'parameters']: 412 result = self._FScompatibleNamesInv(self.pars) 413 elif querykey in ['ics', 'initialconditions']: 414 try: 415 result = self._FScompatibleNamesInv(self.initialconditions) 416 except AttributeError: 417 result = None 418 elif querykey == 'events': 419 result = self.eventstruct.events 420 elif querykey in ['vars', 'variables']: 421 result = self._FScompatibleNamesInv(self.funcspec.vars) 422 elif querykey in ['auxvars', 'auxvariables']: 423 result = self._FScompatibleNamesInv(self.funcspec.auxvars) 424 elif querykey == 'vardomains': 425 result = {} 426 for varname, var in self.variables.iteritems(): 427 result[self._FScompatibleNamesInv(varname)] = \ 428 var.depdomain 429 elif querykey == 'pardomains': 430 result = {} 431 for parname, pardom in self.parameterDomains.iteritems(): 432 result[self._FScompatibleNamesInv(parname)] = \ 433 pardom 434 elif querykey == 'abseps': 435 result = self._abseps 436 return result
437
438 - def get(self, key):
439 """For API compatibility with ModelInterface: get will make a copy of 440 the key and pass it through the inverse FuncSpec-compatible name map. 441 """ 442 return self._FScompatibleNamesInv(getattr(self, key))
443 444
445 - def haveJacobian(self):
446 """Default method. Can be overridden by subclasses.""" 447 return False
448 449
450 - def haveJacobian_pars(self):
451 """Default method. Can be overridden by subclasses.""" 452 return False
453 454
455 - def info(self, verbose=1):
456 print self._infostr(verbose)
457 458
459 - def _kw_process_dispatch(self, keys, kw):
460 # compile funcspec arguments by processing init keys 461 # make ignorespecial initially empty so that it can safely be 462 # extended by its own dispatch method and by process_system 463 fs_args = {'name': self.name, 464 'ignorespecial': []} 465 # make sure to do varspecs first in case of FOR macros 466 self._kw_process_varspecs(kw, fs_args) 467 for key in keys: 468 if key == 'varspecs': 469 # already did it 470 continue 471 f = getattr(self, '_kw_process_'+key) 472 # f can update fs_args in place 473 f(kw, fs_args) 474 return fs_args
475
476 - def _kw_process_varspecs(self, kw, fs_args):
477 if 'varspecs' in kw: 478 for varname, varspec in kw['varspecs'].items(): 479 assert isinstance(varname, (str, QuantSpec, Quantity)), "Invalid type for Variable name: %s"%str(varname) 480 assert isinstance(varspec, (str, QuantSpec, Quantity)), "Invalid type for Variable %s's specification"%varname 481 self.foundKeys += 1 482 fs_args['varspecs'] = \ 483 self._FScompatibleNames(ensureStrArgDict(kw['varspecs'])) 484 else: 485 raise PyDSTool_KeyError("Keyword 'varspecs' missing from " 486 "argument") 487 all_vars = [] 488 # record number of variables defined by macros for FuncSpec checks 489 fs_args['_for_macro_info'] = args(totforvars=0, numfors=0, varsbyforspec={}) 490 for specname, specstr in fs_args['varspecs'].items(): 491 if not '[' in specname: 492 all_vars.append(specname) 493 fs_args['_for_macro_info'].varsbyforspec[specname] = [specname] 494 continue 495 # assume this will be a FOR macro (FuncSpec will check properly later) 496 assert specstr[:4] == 'for(', ('Expected `for` macro when ' 497 'square brackets used in name definition') 498 # read contents of braces 499 ok, arglist, nargs = readArgs(specstr[3:]) 500 if not ok: 501 raise ValueError('Error finding ' 502 'arguments applicable to `for` ' 503 'macro') 504 rootstr = specname[:specname.find('[')] 505 assert len(arglist) == 4, ('Wrong number of arguments passed ' 506 'to `for` macro. Expected 4') 507 ilo = int(arglist[1]) 508 ihi = int(arglist[2]) 509 new_vars = [rootstr+str(i) for i in range(ilo,ihi+1)] 510 all_vars.extend(new_vars) 511 fs_args['_for_macro_info'].numfors += 1 512 fs_args['_for_macro_info'].totforvars += (ihi-ilo+1) 513 fs_args['_for_macro_info'].varsbyforspec[specname] = new_vars 514 # Temporary record of all var names, will be deleted before finalizing 515 # class initialization 516 self.__all_vars = all_vars
517
518 - def _kw_process_tdomain(self, kw, fs_args):
519 if 'tdomain' in kw: 520 self.tdomain = kw['tdomain'] 521 if self.tdomain[0] >= self.tdomain[1]: 522 print "Time domain specified: [%s, %s]"%(self.tdomain[0], 523 self.tdomain[1]) 524 raise PyDSTool_ValueError("tdomain values must be in order of " 525 "increasing size") 526 self.foundKeys += 1 527 else: 528 self.tdomain = [-Inf, Inf]
529
530 - def _kw_process_ttype(self, kw, fs_args):
531 # e.g. for map system 532 if 'ttype' in kw: 533 try: 534 self.indepvartype = _num_equivtype[kw['ttype']] 535 except KeyError: 536 raise TypeError('Invalid ttype: %s'%str(kw['ttype'])) 537 self.foundKeys += 1 538 else: 539 self.indepvartype = float
540
541 - def _kw_process_tdata(self, kw, fs_args):
542 # set tdomain first 543 if 'tdata' in kw: 544 self.tdata = kw['tdata'] 545 if self.tdata[0] >= self.tdata[1]: 546 raise PyDSTool_ValueError("tdata values must be in order of " 547 "increasing size") 548 # _tdata is made into list to be consistent with 549 # other uses of it in other Generators... 550 if self.tdomain[0] > self.tdata[0]: 551 raise ValueError('tdata cannot be specified below smallest '\ 552 'value in tdomain\n (possibly due to uncertain'\ 553 'bounding)') 554 if self.tdomain[1] < self.tdata[1]: 555 raise ValueError('tdata cannot be specified above largest '\ 556 'value in tdomain\n (possibly due to uncertain '\ 557 'bounding)') 558 self.foundKeys += 1 559 else: 560 self.tdata = self.tdomain # default needed
561
562 - def _kw_process_tstep(self, kw, fs_args):
563 # requires self.indepvartype (e.g. for map system) 564 if 'tstep' in kw: 565 self.tstep = kw['tstep'] 566 if self.tstep > self.tdata[1]-self.tdata[0]: 567 raise PyDSTool_ValueError('tstep too large') 568 if compareNumTypes(self.indepvartype, _all_int) and round(self.tstep) != self.tstep: 569 raise PyDSTool_ValueError('tstep must be an integer for integer ttype') 570 self.foundKeys += 1 571 else: 572 if compareNumTypes(self.indepvartype, _all_int): 573 # default to 1 for integer types 574 self.tstep = 1 575 else: 576 # no reasonable default - so raise error 577 raise PyDSTool_KeyError('tstep key needed for float ttype')
578
579 - def _kw_process_inputs(self, kw, fs_args):
580 if 'inputs' in kw: 581 inputs = copy(kw['inputs']) 582 if isinstance(inputs, Trajectory): 583 # extract the variables 584 self.inputs.update(self._FScompatibleNames(inputs.variables)) 585 elif isinstance(inputs, Variable): 586 self.inputs.update({self._FScompatibleNames(inputs.name): \ 587 inputs}) 588 elif isinstance(inputs, Pointset): 589 # turn into Variables with linear interpoolation between 590 # independent variable values 591 for n in inputs.coordnames: 592 x_array = inputs[n] 593 nFS = self._FScompatibleNames(n) 594 self.inputs[nFS] = \ 595 Variable(interp1d(inputs.indepvararray, 596 x_array), 't', 597 Interval(nFS, float, extent(x_array), 598 abseps=self._abseps), 599 name=n) # keep original name here 600 elif isinstance(inputs, dict): 601 self.inputs.update(self._FScompatibleNames(inputs)) 602 # ensure values are Variables or Pointsets 603 for k, v in self.inputs.iteritems(): 604 if not isinstance(v, Variable): 605 try: 606 self.inputs[k]=Variable(v) 607 except: 608 raise TypeError("Invalid specification of inputs") 609 else: 610 raise TypeError("Invalid specification of inputs") 611 self._register(self.inputs) 612 self.foundKeys += 1 613 # only signal that _extInputsChanged if there are actually some 614 # defined, e.g. inputs may be formally present in the keys but in 615 # fact unused 616 self._extInputsChanged = (self.inputs != {}) 617 fs_args['inputs'] = self.inputs.keys() 618 else: 619 self._extInputsChanged = False
620
621 - def _kw_process_ics(self, kw, fs_args):
622 if 'ics' in kw: 623 self._xdatadict = {} 624 for k, v in dict(kw['ics']).iteritems(): 625 self._xdatadict[self._FScompatibleNames(str(k))] = ensurefloat(v) 626 self.initialconditions = self._xdatadict.copy() 627 unspecd = remain(self._xdatadict.keys(), self.__all_vars) 628 if unspecd != []: 629 # ics were declared for variables not in varspecs 630 raise ValueError("Missing varspec entries for declared ICs: " + str(unspecd)) 631 for name in remain(self.__all_vars, 632 self._xdatadict.keys()): 633 self.initialconditions[name] = NaN 634 self.foundKeys += 1 635 else: 636 self._xdatadict = {} 637 for name in self.__all_vars: 638 self.initialconditions[name] = NaN
639
640 - def _kw_process_allvars(self, kw, fs_args):
641 if 'auxvars' in kw: 642 assert 'vars' not in kw, ("Cannot use both 'auxvars' and 'vars' " 643 "keywords") 644 if isinstance(kw['auxvars'], list): 645 auxvars = self._FScompatibleNames([str(v) for v in kw['auxvars']]) 646 else: 647 auxvars = self._FScompatibleNames([str(kw['auxvars'])]) 648 vars = remain(self.__all_vars, auxvars) 649 self.foundKeys += 1 650 elif 'vars' in kw: 651 assert 'auxvars' not in kw, \ 652 "Cannot use both 'auxvars' and 'vars' keywords" 653 if isinstance(kw['vars'], list): 654 vars = self._FScompatibleNames([str(v) for v in kw['vars']]) 655 else: 656 vars = self._FScompatibleNames([str(kw['vars'])]) 657 auxvars = remain(self.__all_vars, vars) 658 self.foundKeys += 1 659 else: 660 # default is that all are considered regular vars 661 auxvars = [] 662 vars = self.__all_vars 663 # vars will never have any macro spec names 664 fs_args['vars'] = vars 665 self.dimension = len(vars) 666 if auxvars != []: 667 fs_args['auxvars'] = auxvars
668
669 - def _kw_process_xtype(self, kw, fs_args):
670 # requires varspecs to have been set 671 # default types are float 672 self.xtype = {} 673 if 'xtype' in kw: 674 xts = kw['xtype'] 675 for name_temp, xt in dict(xts).iteritems(): 676 if compareNumTypes(xt, _all_int): 677 xt_actual = int 678 elif compareNumTypes(xt, _all_float): 679 xt_actual = float 680 else: 681 raise TypeError("Invalid variable type %s"%str(xt)) 682 name = self._FScompatibleNames(name_temp) 683 if name[-1] == ']': 684 # for macro -- FuncSpec.py will double check for correct syntax 685 base = name[:name.index('[')] 686 # pull out everything in parentheses 687 for_spec = fs_args['varspecs'][name][4:-1].replace(' ', '').split(',') 688 for name_i in range(int(for_spec[1]), int(for_spec[2])+1): 689 self.xtype[base+str(name_i)] = xt_actual 690 else: 691 self.xtype[name] = xt_actual 692 for name in remain(fs_args['varspecs'].keys(), self.xtype.keys()): 693 self.xtype[name] = float 694 self.foundKeys += 1 695 else: 696 for name in fs_args['varspecs']: 697 if name[-1] == ']': 698 # for macro -- FuncSpec.py will double check for correct syntax 699 base = name[:name.index('[')] 700 # pull out everything in parentheses 701 for_spec = fs_args['varspecs'][name][4:-1].replace(' ', '').split(',') 702 for name_i in range(int(for_spec[1]), int(for_spec[2])+1): 703 self.xtype[base+str(name_i)] = float 704 else: 705 self.xtype[name] = float
706
707 - def _kw_process_xdomain(self, kw, fs_args):
708 if 'xdomain' in kw: 709 self.xdomain = {} 710 for k, v in dict(kw['xdomain']).iteritems(): 711 name = self._FScompatibleNames(str(k)) 712 if isinstance(v, _seq_types): 713 assert len(v) == 2, \ 714 "Invalid size of domain specification for "+name 715 if v[0] >= v[1]: 716 raise PyDSTool_ValueError('xdomain values must be in' 717 'order of increasing size') 718 else: 719 self.xdomain[name] = copy(v) 720 elif isinstance(v, _num_types): 721 self.xdomain[name] = [v, v] 722 else: 723 raise PyDSTool_TypeError('Invalid type for xdomain spec' 724 ' '+name) 725 for name in remain(fs_args['varspecs'].keys(), self.xdomain.keys()): 726 if name[-1] == ']': 727 # for macro -- FuncSpec.py will double check for correct syntax 728 base = name[:name.index('[')] 729 # pull out everything in parentheses 730 for_spec = fs_args['varspecs'][name][4:-1].replace(' ', '').split(',') 731 for name_i in range(int(for_spec[1]), int(for_spec[2])+1): 732 self.xdomain[base+str(name_i)] = [-Inf, Inf] 733 else: 734 self.xdomain[name] = [-Inf, Inf] 735 self.foundKeys += 1 736 else: 737 self.xdomain = {} 738 for name in fs_args['varspecs']: 739 if name[-1] == ']': 740 # for macro -- FuncSpec.py will double check for correct syntax 741 base = name[:name.index('[')] 742 # pull out everything in parentheses 743 for_spec = fs_args['varspecs'][name][4:-1].replace(' ', '').split(',') 744 for name_i in range(int(for_spec[1]), int(for_spec[2])+1): 745 self.xdomain[base+str(name_i)] = [-Inf, Inf] 746 else: 747 self.xdomain[name] = [-Inf, Inf]
748
749 - def _kw_process_reuseterms(self, kw, fs_args):
750 if 'reuseterms' in kw: 751 self.foundKeys += 1 752 fs_args['reuseterms'] = kw['reuseterms']
753
754 - def _kw_process_ignorespecial(self, kw, fs_args):
755 if 'ignorespecial' in kw: 756 self.foundKeys += 1 757 fs_args['ignorespecial'].extend(kw['ignorespecial'])
758
759 - def _kw_process_algparams(self, kw, fs_args):
760 if 'algparams' in kw: 761 self.algparams = copy(kw['algparams']) 762 self.foundKeys += 1 763 else: 764 self.algparams = {}
765
766 - def _kw_process_pars(self, kw, fs_args):
767 if 'pars' in kw: 768 self.pars = {} 769 if isinstance(kw['pars'], list): 770 # may be a list of symbolic definitions 771 for p in kw['pars']: 772 try: 773 self.pars[self._FScompatibleNames(p.name)] = p.tonumeric() 774 except (AttributeError, TypeError): 775 raise TypeError("Invalid parameter symbolic definition") 776 else: 777 for k, v in dict(kw['pars']).iteritems(): 778 self.pars[self._FScompatibleNames(str(k))] = ensurefloat(v) 779 fs_args['pars'] = self.pars.keys() 780 self._register(self.pars) 781 self.foundKeys += 1 782 self.numpars = len(self.pars)
783
784 - def _kw_process_pdomain(self, kw, fs_args):
785 if 'pdomain' in kw: 786 if self.pars: 787 self.pdomain = {} 788 for k, v in dict(kw['pdomain']).iteritems(): 789 assert len(v) == 2, \ 790 "Invalid size of domain specification for "+k 791 self.pdomain[self._FScompatibleNames(str(k))] = v 792 for name in self.pdomain: 793 if self.pdomain[name][0] >= self.pdomain[name][1]: 794 raise PyDSTool_ValueError('pdomain values must be in order of increasing size') 795 for name in remain(self.pars.keys(), self.pdomain.keys()): 796 self.pdomain[name] = [-Inf, Inf] 797 self.foundKeys += 1 798 else: 799 raise ValueError('Cannot specify pdomain because no pars declared') 800 else: 801 if self.pars: 802 self.pdomain = {} 803 for pname in self.pars: 804 self.pdomain[pname] = [-Inf, Inf] 805 if self.pars: 806 self.parameterDomains = {} 807 for pname in self.pdomain: 808 self.parameterDomains[pname] = Interval(pname, float, 809 self.pdomain[pname], 810 self._abseps) 811 try: 812 cval = self.parameterDomains[pname].contains(self.pars[pname]) 813 except KeyError: 814 raise ValueError("Parameter %s is missing a value"%pname) 815 if self.checklevel < 3: 816 if cval is not notcontained: 817 if cval is uncertain and self.checklevel == 2: 818 print 'Warning: Parameter value at bound' 819 else: 820 print self.pars[pname], "not in", self.parameterDomains[pname].get() 821 raise PyDSTool_ValueError('Parameter %s: value out of bounds'%pname) 822 else: 823 if cval is uncertain: 824 raise PyDSTool_UncertainValueError('Parameter %s: value at bound'%pname) 825 elif cval is notcontained: 826 raise PyDSTool_ValueError('Parameter %s: value out of bounds'%pname)
827
828 - def _kw_process_fnspecs(self, kw, fs_args):
829 if 'fnspecs' in kw: 830 fs_args['fnspecs'] = ensureStrArgDict(kw['fnspecs']) 831 self.foundKeys += 1
832
833 - def _kw_process_target(self, kw, fs_args):
834 fs_args['targetlang'] = theGenSpecHelper(self).lang 835 if 'compiler' in kw: 836 if fs_args['targetlang'] == 'python': 837 print "Warning: redundant option 'compiler' for python target" 838 self._compiler = kw['compiler'] 839 self.foundKeys += 1 840 elif fs_args['targetlang'] != 'python': 841 osname = os.name 842 # os-specific defaults for C compiler 843 if osname == 'nt': 844 self._compiler = 'mingw32' 845 elif osname == 'mac': 846 self._compiler = 'mwerks' 847 elif osname == 'posix' or osname == 'unix': 848 self._compiler = 'unix' 849 elif osname == 'os2emx': 850 self._compiler = 'emx' 851 else: 852 self._compiler = '' 853 else: 854 self._compiler = ''
855
856 - def _kw_process_vfcodeinserts(self, kw, fs_args):
857 if 'vfcodeinsert_start' in kw: 858 fs_args['codeinsert_start'] = kw['vfcodeinsert_start'] 859 self.foundKeys += 1 860 if 'vfcodeinsert_end' in kw: 861 fs_args['codeinsert_end'] = kw['vfcodeinsert_end'] 862 self.foundKeys += 1
863
864 - def _kw_process_system(self, kw, fs_args):
865 # for python-based solvers, esp. map system 866 if 'system' in kw: 867 self._solver = kw['system'] 868 try: 869 fs_args['ignorespecial'].append(self._solver.name) 870 except: 871 raise TypeError("Invalid solver system provided") 872 self.foundKeys += 1 873 if self.pars: 874 # automatically pass par values on to embedded system 875 # when Rhs called 876 parlist = self.pars.keys() 877 parstr = "".join(["'%s': %s, "%(parname,parname) \ 878 for parname in parlist]) 879 if 'codeinsert_start' in fs_args: 880 fs_args['codeinsert_start'] = \ 881 ' %s.set(pars={%s})\n'%(self._solver.name, parstr) \ 882 + fs_args['codeinsert_start'] 883 else: 884 fs_args['codeinsert_start'] = \ 885 ' %s.set(pars={%s})\n'%(self._solver.name, parstr) 886 else: 887 self._solver = None
888 889
890 - def _infostr(self, verbose=1):
891 """Return detailed information about the Generator 892 specification.""" 893 894 if verbose == 0: 895 outputStr = "Generator "+self.name 896 else: 897 outputStr = '**************************************************' 898 outputStr += '\n Generator '+self.name 899 outputStr +='\n**************************************************' 900 outputStr +='\nType : ' + className(self) 901 outputStr +='\nIndependent variable interval: ' + str(self.indepvariable.depdomain) 902 outputStr +='\nGlobal t0 = ' + str(self.globalt0) 903 outputStr +='\nInterval endpoint check level = ' + str(self.checklevel) 904 outputStr +='\nDimension = ' + str(self.dimension) 905 outputStr +='\n' 906 if isinstance(self.funcspec, FuncSpec): 907 outputStr += self.funcspec._infostr(verbose) 908 if verbose == 2: 909 outputStr += '\nVariables` validity intervals:' 910 for v in self.variables.values(): 911 outputStr += '\n ' + str(v.depdomain) 912 if self.eventstruct is not None: 913 outputStr += '\nEvents defined:' 914 outputStr += '\n ' + str(self.eventstruct.events.keys()) 915 if self._modeltag is not None: 916 outputStr += '\nAssociated Model: ' + self._modeltag.name 917 if verbose > 0: 918 outputStr += '\n' 919 return outputStr
920 921
922 - def showEventSpec(self):
923 if self.eventstruct is not None: 924 for evname, ev in self.eventstruct.events.iteritems(): 925 print evname + ":\n" + ev._funcstr 926 print "\n"
927 928
929 - def showSpec(self):
930 print self.funcspec.spec[0]
931 932
933 - def showAuxSpec(self):
934 print self.funcspec.auxspec[0]
935 936
937 - def showAuxFnSpec(self, auxfnname=None):
938 if auxfnname is None: 939 retdict = {} 940 for aname, aspec in self.funcspec.auxfns.iteritems(): 941 retdict[aname] = aspec[0] 942 info(retdict) 943 else: 944 try: 945 print self.funcspec.auxfns[auxfnname][0] 946 except KeyError: 947 raise NameError("Aux function %s not found"%auxfnname)
948 949
950 - def __repr__(self):
951 return self._infostr(verbose=0)
952 953 954 __str__ = __repr__ 955 956
957 - def validateSpec(self):
958 try: 959 assert self.dimension > 0 960 assert len(self.variables) == self.dimension 961 # don't assert self.pars because not all systems need them 962 assert self.indepvariable.name == 't' 963 assert self.funcspec 964 assert self.checklevel in range(4) 965 # check that all names in individual dicts are all in _registry 966 if self.pars: 967 for name in self.pars: 968 assert isinstance(self.pars[name], _num_types) 969 assert type(self.pars[name]) == self._registry[name] 970 if self.inputs: 971 for subjectname, obj in self.inputs.iteritems(): 972 # test for containment of input's interval in independent 973 # variable interval 974 # (use checklevel = 1 for this o/w could get errors) 975 assert self.contains(obj.indepdomain, 976 self.indepvariable.indepdomain, 0) 977 # test that types entered in registry are still correct 978 assert type(obj) == self._registry[subjectname] 979 for name in self.variables: 980 assert self.variables[name].__class__ == self._registry[name] 981 dummy = self.indepvariable(self.tdata[0]) # exception if this call is ill-defined 982 # check consistency with FuncSpec type of self.funcspec 983 # (unnecessary for dictionary version of FuncSpec) 984 if isinstance(self.funcspec, FuncSpec): 985 varnames = self.variables.keys() 986 fsvars = self.funcspec.vars 987 if len(varnames) > 1: 988 varnames.sort() 989 fsvars.sort() 990 assert varnames == fsvars, ('Inconsistency with funcspec ' 991 'variable names') 992 else: 993 assert varnames == fsvars 994 parnames = self.pars.keys() 995 fspars = self.funcspec.pars 996 if len(parnames) > 1: 997 parnames.sort() 998 fspars.sort() 999 assert parnames == fspars, ('Inconsistency with funcspec ' 1000 'parameter names') 1001 else: 1002 assert parnames == fspars 1003 if self.inputs: 1004 inputnames = self.inputs.keys() 1005 fsinputs = self.funcspec.inputs 1006 if len(inputnames) > 1: 1007 inputnames.sort() 1008 fsinputs.sort() 1009 assert inputnames == fsinputs, ('Inconsistency with funcspec' 1010 ' input names') 1011 else: 1012 assert inputnames == fsinputs 1013 else: 1014 assert len(self.funcspec) == self.dimension 1015 except: 1016 print 'Invalid system specification' 1017 raise
1018 1019 1020 # call this after all expected keywords have been processed
1021 - def checkArgs(self, kw):
1022 if len(kw) == self.foundKeys: 1023 for name in self._needKeys: 1024 if name not in kw: 1025 raise PyDSTool_KeyError('Necessary key missing: ' + name) 1026 for name in kw: 1027 if name not in self._needKeys + self._optionalKeys: 1028 raise PyDSTool_KeyError('Key name ' + name + ' is invalid') 1029 else: 1030 print 'Keywords supplied:\n\t' + str(kw.keys()) 1031 print '# keywords found: ' + str(self.foundKeys) 1032 print 'Needed:\n\t' + str(self._needKeys) 1033 print 'Optional:\n\t' + str(self._optionalKeys) 1034 raise PyDSTool_KeyError('Invalid keyword arguments for this class') 1035 del self.foundKeys
1036 1037
1038 - def _set_for_hybrid_DS(self, state):
1039 """Internal method for indicating whether this Generator is currently 1040 being used as part of a hybrid dybnamical system calculation""" 1041 self._for_hybrid_DS = state
1042 1043
1044 - def _register(self, items):
1045 """_register names and types of sub-system variables (including 1046 Generator variables), pars and external inputs. 1047 1048 Names must be unique for the Generator. 1049 """ 1050 1051 if isinstance(items, dict): 1052 # for parameter and variable dictionaries 1053 for name, v in items.iteritems(): 1054 if isinstance(self.funcspec, FuncSpec): 1055 assert name in self.funcspec.vars \ 1056 or name in self.funcspec.auxvars \ 1057 or name in self.funcspec.pars \ 1058 or name in self.funcspec.inputs, \ 1059 ("Generator = '" 1060 +self.name+"': name "+name+" not found in " 1061 "functional specification declaration") 1062 if name not in self._registry: 1063 self._registry[name] = type(v) 1064 else: 1065 raise ValueError('The name `' + name + '` of type `' 1066 + type(v).__name__ + 1067 '` already exists in the registry') 1068 if isinstance(v, Variable) and \ 1069 name in self.variables or name in self.inputs: 1070 self._callnames.append(name) 1071 self._callnames.sort() 1072 elif isinstance(items, Variable) and items.name == 't': 1073 # for self.indepvariable 1074 if items.name not in self._registry: 1075 self._registry[items.name] = type(items) 1076 else: 1077 raise ValueError('The reserved name `t` has already' 1078 ' been declared to the registry') 1079 else: 1080 raise TypeError('Expected dictionary or independent variable in ' 1081 'argument to _register()')
1082
1083 - def _kw_process_events(self, kw):
1084 # Only call once funcspec built 1085 # 1086 # Holder and interface for events 1087 self.eventstruct = EventStruct() 1088 if 'enforcebounds' in kw: 1089 if 'activatedbounds' in kw: 1090 ab = kw['activatedbounds'] 1091 self.foundKeys += 1 1092 else: 1093 ab = None 1094 if kw['enforcebounds']: 1095 self._makeBoundsEvents(precise=True, activatedbounds=ab) 1096 self.foundKeys += 1 1097 if 'events' in kw: 1098 self._addEvents(kw['events']) 1099 self.foundKeys += 1
1100 1101
1102 - def _addEvents(self, evs):
1103 if isinstance(evs, list): 1104 map(self.eventstruct.add, copy(evs)) 1105 elif isinstance(evs, Event): 1106 # singleton 1107 self.eventstruct.add(evs) 1108 else: 1109 raise TypeError('Unsupported type of argument for event ' 1110 'structure')
1111 1112
1113 - def _makeBoundsEvents(self, precise=True, eventtol=1e-6, 1114 activatedbounds=None):
1115 events = [] 1116 # pars + vars (pars used only by PyCont during continuation) 1117 alldoms = copy(self.pdomain) 1118 alldoms.update(self.xdomain) 1119 if self._eventPars != []: 1120 # This exclusion doesn't work at the moment. 1121 # nonEvtPars = remain(self.funcspec.pars, self._eventPars) 1122 nonEvtPars = self.funcspec.pars 1123 else: 1124 nonEvtPars = self.funcspec.pars 1125 allnames = self.funcspec.vars + nonEvtPars 1126 if activatedbounds in (None,{}): 1127 activatedbounds = {}.fromkeys(allnames, (False,False)) 1128 for xname, xdom in alldoms.iteritems(): 1129 if xname not in allnames: 1130 # don't make bound constraints for non-state variables 1131 continue 1132 xdlo = xdom[0] 1133 xdhi = xdom[1] 1134 evname = xname+"_domlo" 1135 evargs = {'term': True, 1136 'precise': precise, 1137 'name': evname, 1138 'eventtol': eventtol, 1139 'eventdelay': eventtol*10, 1140 'eventinterval': eventtol*20, 1141 'xdomain': self.xdomain, 1142 'pdomain': self.pdomain} 1143 try: 1144 evargs['active'] = activatedbounds[xname][0] and isfinite(xdlo) 1145 except KeyError: 1146 evargs['active'] = False 1147 evstr = xname + '-' + 'getbound("%s", 0)'%xname 1148 ev = Events.makeZeroCrossEvent(evstr, -1, evargs, [xname], 1149 targetlang=self.funcspec.targetlang, 1150 reuseterms=self.funcspec.reuseterms) 1151 events.append(ev) 1152 evname = xname+"_domhi" 1153 evargs = {'term': True, 1154 'precise': precise, 1155 'name': evname, 1156 'eventtol': eventtol, 1157 'eventdelay': eventtol*10, 1158 'eventinterval': eventtol*20, 1159 'xdomain': self.xdomain, 1160 'pdomain': self.pdomain} 1161 try: 1162 evargs['active'] = activatedbounds[xname][1] and isfinite(xdhi) 1163 except KeyError: 1164 evargs['active'] = False 1165 evstr = xname + '-' + 'getbound("%s", 1)'%xname 1166 ev = Events.makeZeroCrossEvent(evstr, 1, evargs, [xname], 1167 targetlang=self.funcspec.targetlang, 1168 reuseterms=self.funcspec.reuseterms) 1169 events.append(ev) 1170 if events != []: 1171 self._addEvents(events)
1172 1173
1174 - def set(self, **kw):
1175 """Set generic parameters.""" 1176 # Note: globalt0 may be unused by many classes of Generator 1177 if len(kw) > 0: 1178 if 'globalt0' in kw: 1179 self.globalt0 = kw['globalt0'] 1180 try: 1181 self.eventstruct.setglobalt0(self.globalt0) 1182 except AttributeError: 1183 # no events present 1184 pass 1185 if 'checklevel' in kw: 1186 self.checklevel = kw['checklevel'] 1187 if hasattr(self, 'algparams'): 1188 self.algparams['checkBounds'] = kw['checklevel'] 1189 if 'abseps' in kw: 1190 self._abseps = kw['abseps'] 1191 if remain(kw.keys(), ['globalt0', 'checklevel', 'abseps']) != []: 1192 raise PyDSTool_KeyError('Invalid keywords passed')
1193 1194
1195 - def setEventICs(self, ics, gt0=0):
1196 """Set initialconditions attribute of all generator's events, in 1197 case event uses auxiliary functions that access this information.""" 1198 try: 1199 evs = self.eventstruct.events.values() 1200 except AttributeError: 1201 # no events present 1202 pass 1203 else: 1204 for ev in evs: 1205 ev.initialconditions = self._FScompatibleNames(ics) 1206 ev.globalt0 = gt0
1207 1208
1209 - def resetEventTimes(self):
1210 try: 1211 self.eventstruct.resetEvtimes() 1212 except AttributeError: 1213 # no events present 1214 pass
1215
1216 - def resetEvents(self, state=None):
1217 """Reset any high level (Python) events in Generator""" 1218 try: 1219 # time is OK to be 0 here, it will be overwritten before use anyway 1220 # e.g. by VODE or map system 1221 self.eventstruct.resetHighLevelEvents(0, state=state) 1222 except AttributeError: 1223 # no events present 1224 pass
1225 1226 # Auxiliary functions for user-defined code to call 1227
1228 - def _auxfn_globalindepvar(self, parsinps, t):
1229 return self.globalt0 + t
1230
1231 - def _auxfn_initcond(self, parsinps, varname):
1232 return self.initialconditions[varname]
1233
1234 - def _auxfn_heav(self, parsinps, x):
1235 if x>0: 1236 return 1 1237 else: 1238 return 0
1239
1240 - def _auxfn_if(self, parsinps, c, e1, e2):
1241 if c: 1242 return e1 1243 else: 1244 return e2
1245
1246 - def _auxfn_getindex(self, parsinps, varname):
1247 return self._var_namemap[varname]
1248 1249
1250 - def _generate_ixmaps(self, gentypes=None):
1251 """Generate indices mapping. 1252 1253 This creates a mapping from the names of variables, 1254 pars and inputs, to indices in the arrays used for 1255 refering to the internal (dynamic) call methods.""" 1256 1257 if gentypes is not None: 1258 if isinstance(gentypes, str): 1259 gentypes = [gentypes] 1260 for s in gentypes: 1261 assert s in ['variables', 'inputs', 'pars'], \ 1262 ('Incorrect type string for _generate_ixmaps') 1263 else: 1264 # default to all 1265 gentypes = ['variables', 'inputs', 'pars'] 1266 # ixmap (list) : int -> str 1267 # namemap (dict) : str -> int 1268 if 'variables' in gentypes: 1269 self._var_ixmap = sortedDictKeys(self.variables, 1270 self.funcspec.vars) 1271 self._var_namemap = invertMap(self._var_ixmap) 1272 if 'pars' in gentypes: 1273 if self.pars: 1274 self._parameter_ixmap = sortedDictKeys(self.pars) 1275 self._parameter_namemap = invertMap(self._parameter_ixmap) 1276 else: 1277 self._parameter_ixmap = [] 1278 self._parameter_namemap = {} 1279 if 'inputs' in gentypes: 1280 if self.inputs: 1281 self._inputs_ixmap = \ 1282 sortedDictKeys(self.inputs) 1283 self._inputs_namemap = invertMap(self._inputs_ixmap) 1284 else: 1285 self._inputs_ixmap = [] 1286 self._inputs_namemap = {}
1287 1288
1289 - def contains(self, interval, val, checklevel=2):
1290 """Interval containment test""" 1291 # NB. val may be another interval 1292 if checklevel == 0: 1293 # level 0 -- no bounds checking at all 1294 # code should avoid calling this function with checklevel = 0 1295 # if possible, but this case is left here for completeness and 1296 # consistency 1297 return True 1298 elif checklevel == 2: 1299 # level 2 -- warn on uncertain and continue 1300 testresult = interval.contains(val) 1301 if testresult is contained: 1302 return True 1303 elif testresult is uncertain: 1304 self.diagnostics.warnings.append((W_UNCERTVAL, (val,interval))) 1305 return True 1306 else: 1307 return False 1308 elif checklevel == 1: 1309 # level 1 -- ignore uncertain cases (treat as contained) 1310 if interval.contains(val) is not notcontained: 1311 return True 1312 else: 1313 return False 1314 else: 1315 # level 3 -- exception will be raised for uncertain case 1316 if val in interval: 1317 return True 1318 else: 1319 return False
1320 1321 1322 # Methods for pickling protocol
1323 - def __getstate__(self):
1324 d = copy(self.__dict__) 1325 for fname, finfo in self._funcreg.iteritems(): 1326 try: 1327 del d[fname] 1328 except KeyError: 1329 pass 1330 return d
1331 1332
1333 - def __setstate__(self, state):
1334 self.__dict__.update(state) 1335 if self._funcreg != {}: 1336 self.addMethods()
1337 1338
1339 - def __del__(self):
1340 # delete object-specific class methods etc. before deleting 1341 # to avoid crowding namespace 1342 try: 1343 for fname, finfo in self._funcreg.iteritems(): 1344 try: 1345 delattr(eval(finfo[0]), fname) 1346 except AttributeError: 1347 pass 1348 except NameError: 1349 # not sure what happens here, but some other names 1350 # may be deleted before all references to them have 1351 # been deleted, but it's very non-fatal to ignore. 1352 pass 1353 if hasattr(self, 'eventstruct'): 1354 if self.eventstruct is not None: 1355 self.eventstruct.__del__() 1356 if self.indepvariable is not None: 1357 del self.indepvariable 1358 for v in self.variables.values(): 1359 v.__del__() 1360 if hasattr(self, 'inputs'): 1361 for v in self.inputs.values(): 1362 v.__del__() 1363 except AttributeError: 1364 # self does not have _funcreg 1365 pass 1366 except NameError: 1367 # see above notes for NameError catch 1368 pass
1369 1370
1371 - def __copy__(self):
1372 pickledself = pickle.dumps(self) 1373 return pickle.loads(pickledself)
1374 1375
1376 - def __deepcopy__(self, memo=None, _nil=[]):
1377 pickledself = pickle.dumps(self) 1378 return pickle.loads(pickledself)
1379 1380 1381 #-------------------------------------------------------------------------- 1382 1383
1384 -class ctsGen(Generator):
1385 "Abstract class for continuously-parameterized trajectory generators." 1386
1387 - def validateSpec(self):
1388 # only check that domain is cts, range may be a finite subset of points 1389 assert isinputcts(self.indepvariable), ("self.indepvariable must be continuously-" 1390 "defined for this class")
1391
1392 - def __del__(self):
1393 Generator.__del__(self)
1394 1395 1396
1397 -class discGen(Generator):
1398 "Abstract class for discretely-parameterized trajectory generators." 1399
1400 - def validateSpec(self):
1401 assert isdiscrete(self.indepvariable), ("self.indepvariable must be discretely-" 1402 "defined for this class")
1403
1404 - def __del__(self):
1405 Generator.__del__(self)
1406 1407 1408 #-------------------------------------------------------------------------- 1409 1410
1411 -class GenSpecInfoObj(object):
1412 # empty class struct for GenSpecHelper 1413 pass
1414 1415
1416 -class GenSpecHelper(object):
1417 """Generator specification helper - abstract class. 1418 1419 Used to help ModelConstructor translate abstract model specifications 1420 into concrete specifications specific to individual Generators.""" 1421
1422 - def __init__(self):
1423 self.gshDB = {}
1424
1425 - def add(self, genClass, symbolMapDict, lang, specType='RHSfuncSpec'):
1426 genName = className(genClass) 1427 if genName in self.gshDB: 1428 raise ValueError("Generator %s has already been declared"%genName) 1429 else: 1430 infoObj = GenSpecInfoObj() 1431 infoObj.genClass = genClass 1432 infoObj.symbolMap = symbolMapClass(symbolMapDict) 1433 infoObj.lang = lang 1434 infoObj.specType = specType 1435 if issubclass(genClass, ctsGen): 1436 infoObj.domain = Continuous 1437 elif issubclass(genClass, discGen): 1438 infoObj.domain = Discrete 1439 else: 1440 raise TypeError("Invalid Generator class") 1441 self.gshDB[genName] = infoObj
1442
1443 - def __call__(self, subject):
1444 try: 1445 if isinstance(subject, str): 1446 return self.gshDB[subject] 1447 else: 1448 return self.gshDB[className(subject)] 1449 except KeyError: 1450 raise KeyError("Generator %s was not found in database"%str(subject))
1451
1452 - def __contains__(self, subject):
1453 return subject in self.gshDB or className(subject) in self.gshDB
1454 1455
1456 -def _pollInputs(inputVarList, t, checklevel):
1457 ilist = [] 1458 try: 1459 for f in inputVarList: 1460 f.diagnostics.clearWarnings() 1461 ilist.append(f(t, checklevel)) 1462 except AssertionError: 1463 print 'External input call has t out of range: t = ', t 1464 print 'Maybe checklevel is 3 and initial time is not', \ 1465 'completely inside valid time interval' 1466 raise 1467 except ValueError: 1468 print 'External input call has value out of range: t = ', t 1469 print 'Check beginning and end time of integration' 1470 for f in inputVarList: 1471 if f.diagnostics.hasWarnings(): 1472 print 'External input %s out of range:' % f.name 1473 print ' t = ', repr(f.diagnostics.warnings[-1][0]), ', ', \ 1474 f.name, ' = ', repr(f.diagnostics.warnings[-1][1]) 1475 raise 1476 return ilist
1477 1478 global theGenSpecHelper 1479 theGenSpecHelper = GenSpecHelper() 1480