def addMethods(self): # override to add _solver function ODEsystem.addMethods(self) # Jacobian ignored self._solver = euler_solver(getattr(self,self.funcspec.spec[1])) self._funcreg['_solver'] = ('self', 'euler_solver(getattr(self,' \ + 'self.funcspec.spec[1]))')
def __init__(self, kw): ODEsystem.__init__(self, kw) self.diagnostics._errorcodes = \ {0: 'Unrecognized error code returned (see stderr output)', -1: 'Excess work done on this call. (Perhaps wrong method MF.)', -2: 'Excess accuracy requested. (Tolerances too small.)', -3: 'Illegal input detected. (See printed message.)', -4: 'Repeated error test failures. (Check all input.)', -5: 'Repeated convergence failures. (Perhaps bad' ' Jacobian supplied or wrong choice of method MF or tolerances.)', -6: 'Error weight became zero during problem. (Solution' ' component i vanished, and ATOL or ATOL(i) = 0.)' } self.diagnostics.outputStatsInfo = {'errorStatus': 'Error status on completion.'} # note: VODE only supports array atol, not rtol. algparams_def = {'poly_interp': False, 'rtol': 1e-9, 'atol': [1e-12 for dimix in range(self.dimension)], 'stiff': False, 'max_step': 0.0, 'min_step': 0.0, 'init_step': 0.01, 'max_pts': 1000000, 'strictdt': False, 'use_special': False, 'specialtimes': [] } for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v
def __init__(self, kw): if 'user_func_beforestep' in kw: self.ufunc_before = kw['user_func_beforestep'] # delete because not covered in ODEsystem del kw['user_func_beforestep'] else: self.ufunc_before = _dummy_userfunc if 'user_func_afterstep' in kw: self.ufunc_after = kw['user_func_afterstep'] # delete because not covered in ODEsystem del kw['user_func_afterstep'] else: self.ufunc_after = _dummy_userfunc ODEsystem.__init__(self, kw) self._paraminfo = {'init_step': 'Fixed step size for time mesh.'} self.diagnostics._errorcodes = {1: 'Step OK'} self.diagnostics.outputStatsInfo = { 'errorStatus': 'Error status on completion.' } algparams_def = { 'poly_interp': False, 'init_step': 0.01, 'max_pts': 100000 } for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v
def __init__(self, kw): ODEsystem.__init__(self, kw) self.diagnostics._errorcodes = \ {0: 'Unrecognized error code returned (see stderr output)', -1: 'Excess work done on this call. (Perhaps wrong method MF.)', -2: 'Excess accuracy requested. (Tolerances too small.)', -3: 'Illegal input detected. (See printed message.)', -4: 'Repeated error test failures. (Check all input.)', -5: 'Repeated convergence failures. (Perhaps bad' ' Jacobian supplied or wrong choice of method MF or tolerances.)', -6: 'Error weight became zero during problem. (Solution' ' component i vanished, and ATOL or ATOL(i) = 0.)' } self.diagnostics.outputStatsInfo = { 'errorStatus': 'Error status on completion.' } # note: VODE only supports array atol, not rtol. algparams_def = { 'poly_interp': False, 'rtol': 1e-9, 'atol': [1e-12 for dimix in range(self.dimension)], 'stiff': False, 'max_step': 0.0, 'min_step': 0.0, 'init_step': 0.01, 'max_pts': 1000000, 'strictdt': False, 'use_special': False, 'specialtimes': [] } for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v
def __init__(self, kw): ODEsystem.__init__(self, kw) assert self.funcspec.targetlang == 'python', \ ('Wrong target language for functional specification. ' 'Python needed for this class') assert isinstance(self.funcspec, RHSfuncSpec), ('Vode ' 'requires RHSfuncSpec type to proceed') self._paraminfo = {'init_step': 'Fixed step size for time mesh.', 'strictdt': 'Boolean determining whether to evenly space time mesh (default=False), or to use exactly dt spacing.', 'stiff': 'Boolean to activate the BDF method, otherwise Adams method used. Default False.'} self._errorcodes = {0: 'Unrecognized error code returned (see stderr output)', -1: 'Excess work done on this call. (Perhaps wrong method MF.)', -2: 'Excess accuracy requested. (Tolerances too small.)', -3: 'Illegal input detected. (See printed message.)', -4: 'Repeated error test failures. (Check all input.)', -5: 'Repeated convergence failures. (Perhaps bad' ' Jacobian supplied or wrong choice of method MF or tolerances.)', -6: 'Error weight became zero during problem. (Solution' ' component i vanished, and ATOL or ATOL(i) = 0.)' } self._outputinfo = {'errorStatus': 'Error status on completion.'} # note: VODE only supports array atol, not rtol. algparams_def = {'rtol': 1e-9, 'atol': [1e-12 for dimix in xrange(self.dimension)], 'stiff': False, 'max_step': 0.0, 'min_step': 0.0, 'init_step': 0.01, 'max_pts': 1000000, 'strictdt': False } for k, v in algparams_def.iteritems(): if k not in self._algparams: self._algparams[k] = v self.outputstats = {}
def addMethods(self): # override to add _solver function ODEsystem.addMethods(self, usePsyco=False) if self.haveJacobian(): self._solver = ode(getattr(self,self.funcspec.spec[1]), getattr(self,self.funcspec.auxfns["Jacobian"][1])) self._funcreg['_solver'] = ('self', 'ode(getattr(self,self.funcspec.spec[1]),' \ + 'getattr(self,self.funcspec.auxfns["Jacobian"][1]))') else: self._solver = ode(getattr(self,self.funcspec.spec[1])) self._funcreg['_solver'] = ('self', 'ode(getattr(self,' \ + 'self.funcspec.spec[1]))')
def __init__(self, kw): ODEsystem.__init__(self, kw) assert self.funcspec.targetlang == 'python', \ ('Wrong target language for functional specification. ' 'Python needed for this class') assert isinstance( self.funcspec, RHSfuncSpec), ('Vode ' 'requires RHSfuncSpec type to proceed') self._paraminfo = { 'init_step': 'Fixed step size for time mesh.', 'strictdt': 'Boolean determining whether to evenly space time mesh (default=False), or to use exactly dt spacing.', 'stiff': 'Boolean to activate the BDF method, otherwise Adams method used. Default False.' } self._errorcodes = { 0: 'Unrecognized error code returned (see stderr output)', -1: 'Excess work done on this call. (Perhaps wrong method MF.)', -2: 'Excess accuracy requested. (Tolerances too small.)', -3: 'Illegal input detected. (See printed message.)', -4: 'Repeated error test failures. (Check all input.)', -5: 'Repeated convergence failures. (Perhaps bad' ' Jacobian supplied or wrong choice of method MF or tolerances.)', -6: 'Error weight became zero during problem. (Solution' ' component i vanished, and ATOL or ATOL(i) = 0.)' } self._outputinfo = {'errorStatus': 'Error status on completion.'} # note: VODE only supports array atol, not rtol. algparams_def = { 'rtol': 1e-9, 'atol': [1e-12 for dimix in xrange(self.dimension)], 'stiff': False, 'max_step': 0.0, 'min_step': 0.0, 'init_step': 0.01, 'max_pts': 1000000, 'strictdt': False } for k, v in algparams_def.iteritems(): if k not in self._algparams: self._algparams[k] = v self.outputstats = {}
def __init__(self, kw): if 'user_func_beforestep' in kw: self.ufunc_before = kw['user_func_beforestep'] # delete because not covered in ODEsystem del kw['user_func_beforestep'] else: self.ufunc_before = _dummy_userfunc if 'user_func_afterstep' in kw: self.ufunc_after = kw['user_func_afterstep'] # delete because not covered in ODEsystem del kw['user_func_afterstep'] else: self.ufunc_after = _dummy_userfunc ODEsystem.__init__(self, kw) self._paraminfo = {'init_step': 'Fixed step size for time mesh.'} self.diagnostics._errorcodes = {1: 'Step OK'} self.diagnostics.outputStatsInfo = {'errorStatus': 'Error status on completion.'} algparams_def = {'poly_interp': False, 'init_step': 0.01, 'max_pts': 100000 } for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v
def __init__(self, kw): """Use the nobuild key to postpone building of the library, e.g. in order to provide additional build options to makeLibSource and compileLib methods or to make changes to the C code by hand. No build options can be specified otherwise.""" # Building is just doing make if 'nobuild' in kw: nobuild = kw['nobuild'] del kw['nobuild'] else: nobuild = False ODEsystem.__init__(self, kw) self._solver = None assert self.funcspec.targetlang == 'matlab', \ ('Wrong target language for functional specification. ' 'matlab needed for this class') assert isinstance(self.funcspec, RHSfuncSpec), ('ADMC++ ' 'requires RHSfuncSpec type to proceed') assert not self.inputs, \ 'ADMC++ does not support external inputs feature' self._errorcodes = {} self._paraminfo = {} self.vftype = 'vfieldts' # currently the final four of these params are for event handling # NEED TO CHECK WHICH ONES ARE SUPPORTED BY ADMC -- LOOKS LIKE EVTOLS ONLY FOR NOW # HACK: vftype is alg param for now, tells us whether parent class is hybridvf, vfieldts, etc. algparams_def = {'evtols' : 0.0001, 'vftype' : 'vfieldts'} # Remove this later for k, v in algparams_def.iteritems(): if k not in self.algparams: self.algparams[k] = v # verify that no additional keys are present in algparams, after # defaults are added above if len(self.algparams) != len(algparams_def): raise ValueError("Invalid keys present in algparams argument: " \ + str(remain(self.algparams.keys(),algparams_def.keys()))) thisplatform = platform.system() self._compilation_tempdir = os.path.join(os.getcwd(), "admcpp_temp") if not os.path.isdir(self._compilation_tempdir): try: assert not os.path.isfile(self._compilation_tempdir), \ "A file already exists with the same name" os.mkdir(self._compilation_tempdir) except: print "Could not create compilation temp directory " + \ self._compilation_tempdir raise # ADMC targets must go in their own directories with appropriate names self._model_dir = "@"+self.name self._target_dir = os.path.join(self._compilation_tempdir,self._model_dir) # Make the target directory if not os.path.isdir(self._target_dir): try: assert not os.path.isfile(self._target_dir), \ "A file already exists with the same name" os.mkdir(self._target_dir) except: print "Could not creat target ADMC model directory " + \ self._target_dir raise """ An ADMC model has the following files: vfield.m -- contains code for the RHS of the vector field set.m -- a generic method that overload matlab's set method; only need to insert vfield name get.m -- a generic method that overloads matlab's get method; only need to insert appropriate parent name """ # model.m, get.m, set.m, vfield.m are minimal files required. TO DO: EVENTS self._model_file = self.name+".m" self._ic_file = self.name+"_ics.m" self._param_file = self.name+"_params.m" self._set_file = "set.m" self._get_file = "get.m" self._vfield_file = "vfield.m" self._events_file = "events.m" self._vf_filename_ext = "_"+self._model_file[:-2] if not nobuild: self.makeLibSource() else: print "Build the library using the makeLib method, or in " print "stages using the makeLibSource and compileLib methods."
def __del__(self): ODEsystem.__del__(self)
def compute(self, trajname, dirn='f', ics=None): continue_integ = ODEsystem.prepDirection(self, dirn) if self._dircode == -1: raise NotImplementedError( 'Backwards integration is not implemented') if ics is not None: self.set(ics=ics) self.validateICs() self.diagnostics.clearWarnings() self.diagnostics.clearErrors() pnames = sortedDictKeys(self.pars) xnames = self._var_ixmap # ensures correct order # Check i.c.'s are well defined (finite) self.checkInitialConditions() if self.algparams['stiff']: methstr = 'bdf' methcode = 2 else: methstr = 'adams' methcode = 1 haveJac = int(self.haveJacobian()) if isinstance(self.algparams['atol'], list): if len(self.algparams['atol']) != self.dimension: raise ValueError('atol list must have same length as phase ' 'dimension') else: atol = self.algparams['atol'] self.algparams['atol'] = [atol for dimix in range(self.dimension)] indepdom0, indepdom1 = self.indepvariable.depdomain.get() if continue_integ: if indepdom0 > self._solver.t: print("Previous end time is %f" % self._solver.t) raise ValueError("Start time not correctly updated for " "continuing orbit") x0 = self._solver.y indepdom0 = self._solver.t self.indepvariable.depdomain.set((indepdom0, indepdom1)) else: x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) t0 = indepdom0 if self.algparams['use_special']: tmesh_special = list(self.algparams['specialtimes']) if continue_integ: if self._solver.t not in tmesh_special: raise ValueError("Invalid time to continue integration:" "it is not in 'special times'") tmesh_special = tmesh_special[tmesh_special.index(self._solver. t):] try: dt = min([tmesh_special[1] - t0, self.algparams['init_step']]) except: raise ValueError("Invalid list of special times") if not isincreasing(tmesh_special): raise ValueError("special times must be given in increasing " "order") if self.indepvariable.depdomain.contains(t0) is notcontained or \ self.indepvariable.depdomain.contains(tmesh_special[-1]) is notcontained: raise PyDSTool_BoundsError( "special times were outside of independent " "variable bounds") else: tmesh_special = [] dt = self.algparams['init_step'] # speed up repeated access to solver by making a temp name for it solver = self._solver if solver._integrator is None: # Banded Jacobians not yet supported # # start a new integrator, because method may have been # switched solver.set_integrator('vode', method=methstr, rtol=self.algparams['rtol'], atol=self.algparams['atol'], nsteps=self.algparams['max_pts'], max_step=self.algparams['max_step'], min_step=self.algparams['min_step'], first_step=dt / 2., with_jacobian=haveJac) else: solver.with_jacobian = haveJac # self.mu = lband # self.ml = uband solver.rtol = self.algparams['rtol'] solver.atol = self.algparams['atol'] solver.method = methcode # self.order = order solver.nsteps = self.algparams['max_pts'] solver.max_step = self.algparams['max_step'] solver.min_step = self.algparams['min_step'] solver.first_step = dt / 2. solver.set_initial_value(x0, t0) ## Broken code for going backwards (doesn't respect 'continue' option ## either) ## if self._dircode == 1: ## solver.set_initial_value(x0, t0) ## else: ## solver.set_initial_value(x0, indepdom1) # wrap up each dictionary initial value as a singleton list alltData = [t0] allxDataDict = dict(zip(xnames, map(listid, x0))) plist = sortedDictValues(self.pars) extralist = copy(plist) if self.inputs: # inputVarList is a list of Variables inames = sortedDictKeys(self.inputs) listend = self.numpars + len(self.inputs) inputVarList = sortedDictValues(self.inputs) ilist = _pollInputs(inputVarList, alltData[0] + self.globalt0, self.checklevel) else: ilist = [] inames = [] listend = self.numpars inputVarList = [] extralist.extend(ilist) solver.set_f_params(extralist) if haveJac: solver.set_jac_params(extralist) do_poly = self.algparams['poly_interp'] if do_poly: rhsfn = getattr(self, self.funcspec.spec[1]) dx0 = rhsfn(t0, x0, extralist) alldxDataDict = dict(zip(xnames, map(listid, dx0))) auxvarsfn = getattr(self, self.funcspec.auxspec[1]) strict = self.algparams['strictdt'] # Make t mesh if it wasn't given as 'specialtimes' if not all(isfinite(self.indepvariable.depdomain.get())): print("Time domain was: %f" % self.indepvariable.depdomain.get()) raise ValueError("Ensure time domain is finite") if dt == indepdom1 - indepdom0: # single-step integration required # special times will not have been set (unless trivially # they are [indepdom0, indepdom1]) tmesh = [indepdom0, indepdom1] else: notDone = True repeatTol = 10 count = 0 while notDone and count <= repeatTol: try: tmesh = self.indepvariable.depdomain.sample( dt, strict=strict, avoidendpoints=self.checklevel > 2) notDone = False except AssertionError: count += 1 dt = dt / 3.0 if count == repeatTol: raise AssertionError("Supplied time step is too large for " "selected time interval") # incorporate tmesh_special, if used, ensuring uniqueness if tmesh_special != []: tmesh.extend(tmesh_special) tmesh = list(unique(tmesh)) tmesh.sort() if len(tmesh) <= 2: # safety net, in case too few points in mesh # too few points unless we can add endpoint if tmesh[-1] != indepdom1: # dt too large for tmesh to have more than one point tmesh.append(indepdom1) if not strict: # get actual time step used # don't use [0] in case avoided end points try: dt = tmesh[2] - tmesh[1] except IndexError: # can't avoid end points for such a small mesh dt = tmesh[1] - tmesh[0] #if self.eventstruct.query(['lowlevel']) != []: # raise ValueError("Only high level events can be passed to VODE") eventslist = self.eventstruct.query(['active', 'notvarlinked']) termevents = self.eventstruct.query(['term'], eventslist) # reverse time by reversing mesh doesn't work ## if self._dircode == -1: ## tmesh.reverse() tmesh.pop(0) # get rid of first entry for initial condition xnames = self.funcspec.vars # storage of all auxiliary variable data allaDataDict = {} anames = self.funcspec.auxvars avals = auxvarsfn(t0, x0, extralist) for aix in range(len(anames)): aname = anames[aix] try: allaDataDict[aname] = [avals[aix]] except IndexError: print("\nVODE generator: There was a problem evaluating " \ + "an auxiliary variable") print("Debug info: avals (length %d) was %r" % (len(avals), avals)) print("Index out of range was %d" % aix) print(self.funcspec.auxspec[1]) print(hasattr(self, self.funcspec.auxspec[1])) print("Args were:%r" % [t0, x0, extralist]) raise # Initialize signs of event detection objects at IC self.setEventICs(self.initialconditions, self.globalt0) if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames, ilist))) else: parsinps = self.pars if eventslist != []: dataDict = copy(self.initialconditions) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = t0 ## Removed this "belt and braces" pre-check because it messes ## up some events in the middle of hybrid regime switches. ## Not sure yet what the problem is. (Oct 2013) #evsflagged = self.eventstruct.pollHighLevelEvents(None, # dataDict, # parsinps, # eventslist) #if len(evsflagged) > 0: # raise RuntimeError("Some events flagged at initial condition") if continue_integ: # revert to prevprevsign, since prevsign changed after call self.eventstruct.resetHighLevelEvents(t0, eventslist, 'prev') elif self._for_hybrid_DS: # self._for_hybrid_DS is set internally by HybridModel class # to ensure not to reset events, because they may be about to # flag on first step if previous hybrid state was the same # generator and, for example, two variables are synchronizing # so that their events get very close together. # Just reset the starttimes of these events for evname, ev in eventslist: ev.starttime = t0 else: # default state is a one-off call to this generator self.eventstruct.resetHighLevelEvents(t0, eventslist, None) self.eventstruct.validateEvents(self.funcspec.vars + \ self.funcspec.auxvars + \ self.funcspec.inputs + \ ['t'], eventslist) # temp storage of first time at which terminal events found # (this is used for keeping the correct end point of new mesh) first_found_t = None # list of precise non-terminal events to be resolved after integration nontermprecevs = [] evnames = [ev[0] for ev in eventslist] lastevtime = {}.fromkeys(evnames, None) # initialize new event info dictionaries Evtimes = {} Evpoints = {} if continue_integ: for evname in evnames: try: # these are in global time, so convert to local time lastevtime[evname] = self.eventstruct.Evtimes[evname][-1] \ - self.globalt0 except (IndexError, KeyError): # IndexError: Evtimes[evname] was None # KeyError: Evtimes does not have key evname pass for evname in evnames: Evtimes[evname] = [] Evpoints[evname] = [] # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = dict( zip(range(self.dimension), [self.variables[xn].depdomain for xn in xnames])) # Main integration loop num_points = 0 breakwhile = False while not breakwhile: try: new_t = tmesh.pop(0) # this destroys tmesh for future use except IndexError: # empty break last_t = solver.t # a record of previous time for use by event detector try: errcode = solver.integrate(new_t) except: print("Error calling right hand side function:") self.showSpec() print("Numerical traceback information (current state, " \ + "parameters, etc.)") print("in generator dictionary 'traceback'") self.traceback = { 'vars': dict(zip(xnames, solver.y)), 'pars': dict(zip(pnames, plist)), 'inputs': dict(zip(inames, ilist)), self.indepvariable.name: new_t } raise avals = auxvarsfn(new_t, solver.y, extralist) # Uncomment the following assertion for debugging # assert all([isfinite(a) for a in avals]), \ # "Some auxiliary variable values not finite" if eventslist != []: dataDict = dict(zip(xnames, solver.y)) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = new_t if self.inputs: parsinps = copy(self.pars) parsinps.update( dict( zip( inames, _pollInputs(inputVarList, new_t + self.globalt0, self.checklevel)))) else: parsinps = self.pars evsflagged = self.eventstruct.pollHighLevelEvents( None, dataDict, parsinps, eventslist) ## print new_t, evsflagged ## evsflagged = [ev for ev in evsflagged if solver.t-indepdom0 > ev[1].eventinterval] termevsflagged = [e for e in termevents if e in evsflagged] nontermevsflagged = [ e for e in evsflagged if e not in termevsflagged ] # register any non-terminating events in the warnings # list, unless they are 'precise' in which case flag # them to be resolved after integration completes if len(nontermevsflagged) > 0: evnames = [ev[0] for ev in nontermevsflagged] precEvts = self.eventstruct.query(['precise'], nontermevsflagged) prec_evnames = [e[0] for e in precEvts] # first register non-precise events nonprecEvts = self.eventstruct.query(['notprecise'], nontermevsflagged) nonprec_evnames = [e[0] for e in nonprecEvts] # only record events if they have not been previously # flagged within their event interval if nonprec_evnames != []: temp_names = [] for evname, ev in nonprecEvts: prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if solver.t - prevevt_time < ev.eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: temp_names.append(evname) lastevtime[evname] = solver.t self.diagnostics.warnings.append( (W_NONTERMEVENT, (solver.t, temp_names))) for evname in temp_names: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) for evname, ev in precEvts: # only record events if they have not been previously # flagged within their event interval prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if last_t - prevevt_time < ev.eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: nontermprecevs.append( (last_t, solver.t, (evname, ev))) # be conservative as to where the event is, so # that don't miss any events. lastevtime[evname] = last_t # solver.t-dt ev.reset() #ev.prevsign = None # do_termevs = [] if termevsflagged != []: # only record events if they have not been previously # flagged within their event interval for e in termevsflagged: prevevt_time = lastevtime[e[0]] ## print "Event %s flagged."%e[0] ## print " ... last time was ", prevevt_time ## print " ... event interval = ", e[1].eventinterval ## print " ... t = %f, dt = %f"%(solver.t, dt) if prevevt_time is None: ignore_ev = False else: ## print " ... comparison = %f < %f"%(solver.t-dt-prevevt_time, e[1].eventinterval) if last_t - prevevt_time < e[1].eventinterval: ignore_ev = True ## print "VODE ignore ev" else: ignore_ev = False if not ignore_ev: do_termevs.append(e) if len(do_termevs) > 0: # >= 1 active terminal event flagged at this time point if all([not ev[1].preciseFlag for ev in do_termevs]): # then none of the events specify greater accuracy # register the event in the warnings evnames = [ev[0] for ev in do_termevs] self.diagnostics.warnings.append((W_TERMEVENT, \ (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) # break while loop after appending t, x breakwhile = True else: # find which are the 'precise' events that flagged precEvts = self.eventstruct.query(['precise'], do_termevs) # these events have flagged once so eventdelay has # been used. now switch it off while finding event # precisely (should be redundant after change to # eventinterval and eventdelay parameters) evnames = [ev[0] for ev in precEvts] if first_found_t is None: ## print "first time round at", solver.t numtries = 0 first_found_t = solver.t restore_evts = deepcopy(precEvts) minbisectlimit = min( [ev[1].bisectlimit for ev in precEvts]) for ev in precEvts: ev[1].eventdelay = 0. else: numtries += 1 ## print "time round: ", numtries if numtries > minbisectlimit: self.diagnostics.warnings.append( (W_BISECTLIMIT, (solver.t, evnames))) breakwhile = True # get previous time point if len(alltData) >= 1: # take one step back -> told, which will # get dt added back to first new meshpoint # (solver.t is the step *after* the event was # detected) told = last_t # solver.t-dt without the loss of precision from subtraction else: raise ValueError("Event %s found too "%evnames[0]+\ "close to local start time: try decreasing " "initial step size (current size is " "%f @ t=%f)"%(dt,solver.t+self.globalt0)) # absolute tolerance check on event function values (not t) in_tols = [ abs(e[1].fval) < e[1].eventtol for e in precEvts ] if all(in_tols): ## print "Registering event:", dt_min, dt # register the event in the warnings self.diagnostics.warnings.append( (W_TERMEVENT, (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) # Cannot continue -- dt_min no smaller than # previous dt. If this is more than the first time # in this code then have found the event to within # the minimum 'precise' event's eventtol, o/w need # to set eventtol smaller. # Before exiting event-finding loop, reset prevsign # of flagged events self.eventstruct.resetHighLevelEvents(0, precEvts) breakwhile = True # while loop, but append point first if not breakwhile: dt_new = dt / 5.0 # calc new tmesh trangewidth = 2 * dt #first_found_t - told numpoints = int(math.ceil(trangewidth / dt_new)) # choose slightly smaller dt to fit trange exactly dt = trangewidth / numpoints tmesh = [ told + i * dt for i in range(1, numpoints + 1) ] # linspace version is *much* slower for numpoints ~ 10 and 100 #tmesh = list(told+linspace(dt, numpoints*dt, numpoints)) # reset events according to new time mesh, # setting known previous event state to be their # "no event found" state self.eventstruct.resetHighLevelEvents(told, precEvts, state='off') # build new ic with last good values (at t=told) if len(alltData) > 1: new_ic = [allxDataDict[xname][-1] \ for xname in xnames] else: new_ic = x0 # reset integrator solver.set_initial_value(new_ic, told) extralist[self.numpars:listend] = _pollInputs( inputVarList, told + self.globalt0, self.checklevel) solver.set_f_params(extralist) # continue integrating over new mesh continue # while # after events have had a chance to be detected at a state boundary # we check for any that have not been caught by an event (will be # much less accurately determined) if not breakwhile: # only here if a terminal event hasn't just flagged for xi in range(self.dimension): if not self.contains(depdomains[xi], solver.y[xi], self.checklevel): self.diagnostics.warnings.append( (W_TERMSTATEBD, (solver.t, xnames[xi], solver.y[xi], depdomains[xi].get()))) breakwhile = True break # for loop if breakwhile: break # while loop alltData.append(solver.t) if do_poly: dxvals = rhsfn(solver.t, solver.y, extralist) for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) alldxDataDict[xname].append(dxvals[xi]) else: for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) for aix, aname in enumerate(anames): allaDataDict[aname].append(avals[aix]) num_points += 1 if not breakwhile: try: extralist[self.numpars:listend] = [f(solver.t+self.globalt0, self.checklevel) \ for f in inputVarList] except ValueError: print('External input call caused value out of range error:',\ 't = %f' % solver.t) for f in inputVarList: if f.diagnostics.hasWarnings(): print('External input variable %s out of range:' % f.name) print(' t = %r, %s, %r' % (repr(f.diagnostics.warnings[-1][0]), f.name, repr(f.diagnostics.warnings[-1][1]))) raise except AssertionError: print( 'External input call caused t out of range error: t = %f' % solver.t) raise solver.set_f_params(extralist) breakwhile = not solver.successful() # Check that any terminal events found terminated the code correctly if first_found_t is not None: # ... then terminal events were found. Those that were 'precise' had # their 'eventdelay' attribute temporarily set to 0. It now should # be restored. for evname1, ev1 in termevents: # restore_evts are copies of the originally flagged 'precise' # events for evname2, ev2 in restore_evts: if evname2 == evname1: ev1.eventdelay = ev2.eventdelay try: if self.diagnostics.warnings[-1][0] not in [ W_TERMEVENT, W_TERMSTATEBD ]: print("t =%f" % solver.t) print("state =%r" % dict(zip(xnames, solver.y))) raise RuntimeError("Event finding code for terminal event " "failed in Generator " + self.name + \ ": try decreasing eventdelay or " "eventinterval below eventtol, or the " "atol and rtol parameters") except IndexError: info(self.diagnostics.outputStats, "Output statistics") print("t =%f" % solver.t) print("x =%f" % solver.y) raise RuntimeError("Event finding failed in Generator " + \ self.name + ": try decreasing eventdelay " "or eventinterval below eventtol, or the " "atol and rtol parameters") # Package up computed trajectory in Variable variables # Add external inputs warnings to self.diagnostics.warnings, if any for f in inputVarList: for winfo in f.diagnostics.warnings: self.diagnostics.warnings.append( (W_NONTERMSTATEBD, (winfo[0], f.name, winfo[1], f.depdomain.get()))) # check for non-unique terminal event termcount = 0 for (w, i) in self.diagnostics.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: termcount += 1 if termcount > 1: self.diagnostics.errors.append( (E_NONUNIQUETERM, (alltData[-1], i[1]))) # uncomment the following lines for debugging # assert len(alltData) == len(allxDataDict.values()[0]) \ # == len(allaDataDict.values()[0]), "Output data size mismatch" # for val_list in allaDataDict.values(): # assert all([isfinite(x) for x in val_list]) # Create variables (self.variables contains no actual data) # These versions of the variables are only final if no non-terminal # events need to be inserted. variables = copyVarDict(self.variables) for x in xnames: if len(alltData) > 1: if do_poly: xvals = array([allxDataDict[x], alldxDataDict[x]]).T interp = PiecewisePolynomial(alltData, xvals, 2) else: xvals = allxDataDict[x] interp = interp1d(alltData, xvals) variables[x] = Variable(interp, 't', x, x) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") # Resolve non-terminal 'precise' events that were flagged, using the # variables created. Then, add them to a new version of the variables. ntpe_tdict = {} for (et0, et1, e) in nontermprecevs: lost_evt = False # problem if eventinterval > et1-et0 !! # was: search_dt = max((et1-et0)/5,e[1].eventinterval) search_dt = (et1 - et0) / 5 try: et_precise_list = e[1].searchForEvents( trange=[et0, et1], dt=search_dt, checklevel=self.checklevel, parDict=self.pars, vars=variables, inputs=self.inputs, abseps=self._abseps, eventdelay=False, globalt0=self.globalt0) except (ValueError, PyDSTool_BoundsError): # dt too large for trange, e.g. if event tol is smaller than time mesh et_precise_list = [(et0, (et0, et1))] if et_precise_list == []: lost_evt = True for et_precise in et_precise_list: if et_precise[0] is not None: if et_precise[0] in ntpe_tdict: # add event name at this time (that already exists in the dict) ntpe_tdict[et_precise[0]].append(e[0]) else: # add event name at this time (when time is not already in dict) ntpe_tdict[et_precise[0]] = [e[0]] else: lost_evt = True if lost_evt: print( "Error: A non-terminal, 'precise' event was lost -- did you reset", end=' ') print("events prior to integration?") raise PyDSTool_ExistError( "Internal error: A non-terminal, " "'precise' event '%s' was lost after integration!" % e[0]) # add non-terminal event points to variables if ntpe_tdict != {}: # find indices of times at which event times will be inserted tix = 0 evts = list(ntpe_tdict.keys()) evts.sort() for evix in range(len(evts)): evt = evts[evix] evnames = ntpe_tdict[evt] self.diagnostics.warnings.append( (W_NONTERMEVENT, (evt, evnames))) xval = [variables[x](evt) for x in xnames] ilist = _pollInputs(inputVarList, evt + self.globalt0, self.checklevel) # find accurate dx and aux vars value at this time if do_poly: dxval = rhsfn(evt, xval, plist + ilist) aval = list(auxvarsfn(evt, xval, plist + ilist)) for evname in evnames: Evtimes[evname].append(evt) Evpoints[evname].append(array(xval + aval)) tcond = less_equal(alltData[tix:], evt).tolist() try: tix = tcond.index(0) + tix # lowest index for t > evt do_insert = (alltData[tix - 1] != evt) except ValueError: # evt = last t value so no need to add it do_insert = False if do_insert: alltData.insert(tix, evt) for ai, a in enumerate(anames): allaDataDict[a].insert(tix, aval[ai]) if do_poly: for xi, x in enumerate(xnames): allxDataDict[x].insert(tix, xval[xi]) alldxDataDict[x].insert(tix, dxval[xi]) else: for xi, x in enumerate(xnames): allxDataDict[x].insert(tix, xval[xi]) for x in xnames: if do_poly: # use asarray in case points added to sequences as a list xvals = array( [asarray(allxDataDict[x]), asarray(alldxDataDict[x])]).T interp = PiecewisePolynomial(alltData, xvals, 2) else: xvals = allxDataDict[x] interp = interp1d(alltData, xvals) variables[x] = Variable(interp, 't', x, x) for a in anames: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) self.diagnostics.outputStats = { 'last_step': dt, 'num_fcns': num_points, 'num_steps': num_points, 'errorStatus': errcode } if solver.successful(): #self.validateSpec() for evname, evtlist in Evtimes.items(): try: self.eventstruct.Evtimes[evname].extend([et+self.globalt0 \ for et in evtlist]) except KeyError: self.eventstruct.Evtimes[evname] = [et+self.globalt0 \ for et in evtlist] # build event pointset information (reset previous trajectory's) self.trajevents = {} for (evname, ev) in eventslist: evpt = Evpoints[evname] if evpt == []: self.trajevents[evname] = None else: evpt = transpose(array(evpt)) self.trajevents[evname] = Pointset({ 'coordnames': xnames + anames, 'indepvarname': 't', 'coordarray': evpt, 'indepvararray': Evtimes[evname], 'indepvartype': float }) self.defined = True return Trajectory(trajname, list(variables.values()), abseps=self._abseps, globalt0=self.globalt0, checklevel=self.checklevel, FScompatibleNames=self._FScompatibleNames, FScompatibleNamesInv=self._FScompatibleNamesInv, events=self.trajevents, modelNames=self.name, modelEventStructs=self.eventstruct) else: try: self.diagnostics.errors.append( (E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[errcode]))) except TypeError: # e.g. when errcode has been used to return info list print("Error information: %d" % errcode) self.diagnostics.errors.append( (E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[0]))) self.defined = False
def __del__(self): genDB.unregister(self) ODEsystem.__del__(self)
def compute(self, trajname, dirn='f', ics=None): continue_integ = ODEsystem.prepDirection(self, dirn) if ics is not None: self.set(ics=ics) self.validateICs() self.diagnostics.clearWarnings() self.diagnostics.clearErrors() if isinstance(self.algparams['rtol'], list): if len(self.algparams['rtol']) != self.dimension: raise ValueError('rtol list must have same length as phase dimension') else: rtol = self.algparams['rtol'] self.algparams['rtol'] = [rtol for dimix in range(self.dimension)] if isinstance(self.algparams['atol'], list): if len(self.algparams['atol']) != self.dimension: raise ValueError('atol list must have same length as phase dimension') else: atol = self.algparams['atol'] self.algparams['atol'] = [atol for dimix in range(self.dimension)] anames = self.funcspec.auxvars # Check i.c.'s are well defined (finite) self.checkInitialConditions() self.setEventICs(self.initialconditions, self.globalt0) # update event params in case changed since last run self._prepareEventSpecs() # Main integration t0 = self.indepvariable.depdomain[0] t1 = self.indepvariable.depdomain[1] plist = sortedDictValues(self.pars) self.algparams['hasJac'] = self.haveJacobian() self.algparams['hasJacP'] = self.haveJacobian_pars() if self._solver is None: self._solver = dopri(self.modname, rhs=self.name, phaseDim=self.dimension, paramDim=len(plist), nAux=len(anames), nEvents=len(self._eventNames), nExtInputs=len(self.inputs), hasJac=self.algparams['hasJac'], hasJacP=self.algparams['hasJacP'], hasMass=self.haveMass(), extraSpace=self.algparams['extraspace'], ) try: genDB.register(self) except PyDSTool_KeyError: errstr = "Generator " + self.name + ": this vector field's " +\ "DLL is already in use" raise RuntimeError(errstr) if self._dircode == 1: tbegin = t0 tend = t1 elif self._dircode == -1: # dopri does reverse time integration simply by switching t0 and t1 # and using negative steps tbegin = t1 tend = t0 if len(self.algparams['specialtimes'])>0: use_special = self.algparams['use_special'] else: use_special = 0 bounds = [[],[]] # lower, then upper for v in self.funcspec.vars: bds = self.xdomain[v] bounds[0].append(bds[0]) bounds[1].append(bds[1]) for p in self.funcspec.pars: bds = self.pdomain[p] bounds[0].append(bds[0]) bounds[1].append(bds[1]) if continue_integ: x0 = self._solver.lastPoint # overwrite t0 from self.indepvariable.domain, but use its t1 tbegin = self._solver.lastTime if abs(self._solver.lastStep) < abs(self.algparams['init_step']): self.algparams['init_step'] = self._solver.lastStep if abs(t1-tbegin) < abs(self.algparams['init_step']): raise ValueError("Integration end point too close to initial " "point") # if self.inputs and self._extInputsChanged: # self._extInputsChanged = False # self._solver.setContParams(tend, plist, # use_special, # self.algparams['verbose'], # True, deepcopy(self._inputVarList), # deepcopy(self._inputTimeList)) else: if self._solver.numRuns > 0: self._solver.clearAll() x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) self._solver.setInteg(maxpts=self.algparams['max_pts'], rtol=self.algparams['rtol'], atol=self.algparams['atol']) self._solver.setRunParams(ic=x0, params=plist, t0=tbegin, tend=tend, gt0=self.globalt0, refine=self.algparams['refine'], specTimes=self.algparams['specialtimes'], bounds=bounds) if self.inputs: # self._extInputsChanged if global t0 changed so that can # adjust times given to the integrator (it is blind to global t0 # when accesses input variable times) self._ensure_inputs(self._extInputsChanged) # hinit only set if not continue_integ if len(anames)>0: check_aux = self.algparams['check_aux'] else: check_aux = 0 if self.algparams['max_step'] == 0: max_step = tend-tbegin else: max_step = self.algparams['max_step'] init_step = self.algparams['init_step'] if self._dircode == 1: if init_step < 0: init_step = -init_step if max_step < 0: max_step = -max_step else: if init_step > 0: init_step = -init_step if max_step > 0: max_step = -max_step if continue_integ: # record needed for bounds checking and truncation old_highest_ix = self._solver.points.shape[1] alltData, X, A, Stats, H, Err, Evtimes, \ Evpoints = self._solver.Continue(tend, plist, use_special, self.algparams['verbose'], self._extInputsChanged, deepcopy(self._inputVarList), deepcopy(self._inputTimeList), bounds) else: old_highest_ix = 0 self._solver.setEvents(eventActive=self.algparams['eventActive'], eventTerm=self.algparams['eventTerm'], eventDir=self.algparams['eventDir'], eventDelay=self.algparams['eventDelay'], eventInt=self.algparams['eventInt'], eventTol=self.algparams['eventTol'], maxevtpts=self.algparams['maxevtpts'], maxbisect=self.algparams['maxbisect']) alltData, X, A, Stats, H, Err, Evtimes, \ Evpoints = self._solver.Run(init_step, max_step, check_aux, use_special, self.algparams['verbose'], self.algparams['fac1'], self.algparams['fac2'], self.algparams['safety'], self.algparams['beta'], self.algparams['checkBounds'], self.algparams['boundsCheckMaxSteps'], self.algparams['magBound']) self._extInputsChanged = False # reset this now self.diagnostics.outputStats = {'last_step': H, 'last_time': self._solver.lastTime, 'last_point': self._solver.lastPoint, 'num_fcns': Stats[0], 'num_steps': Stats[1], 'num_accept': Stats[2], 'num_reject': Stats[3], 'errorStatus': Err } if self._dircode == -1: # reverse the array object (no reverse method!) alltData = alltData[::-1] X = X[:,::-1] if anames != []: A = A[:,::-1] xnames = self._var_ixmap # Package up computed trajectory in Variable variables # Add external inputs warnings to self.diagnostics.warnings, if any # (not presently supported) ## for f in inputVarList: ## for winfo in f.diagnostics.warnings: ## self.diagnostics.warnings.append((W_NONTERMSTATEBD, ## (winfo[0], f.name, winfo[1], ## f.depdomain))) eventslist = self.eventstruct.query(['lowlevel', 'active']) termevents = self.eventstruct.query(['term'], eventslist) if self._eventNames != []: # build self.diagnostics.warnings because events happened -- # and keep a record of which times terminal events happened because # Model.py's event handling procedure assumes multiple events # happening at one time are listed in one warning termevtimes = {} nontermevtimes = {} try: for evix in range(len(self._eventNames)): if Evpoints[evix] is None: continue evname = self._eventNames[evix] numevs = len(Evtimes[evix]) if self.algparams['eventTerm'][evix]: if numevs > 1: print("Event info:%r, %r" % (Evpoints, Evtimes)) assert numevs <= 1, ("Internal error: more than one " "terminal event of same type found") # For safety, we should assert that this event # also appears in termevents, but we don't if Evtimes[evix][0] in termevtimes.keys(): # append event name to this warning warning_ix = termevtimes[Evtimes[evix][0]] self.diagnostics.warnings[warning_ix][1][1].append(evname) else: # make new termevtime entry for the new warning termevtimes[Evtimes[evix][0]] = len(self.diagnostics.warnings) self.diagnostics.warnings.append((W_TERMEVENT, (Evtimes[evix][0], [self._eventNames[evix]]))) else: for ev in range(numevs): if Evtimes[evix][ev] in nontermevtimes.keys(): # append event name to this warning warning_ix = nontermevtimes[Evtimes[evix][ev]] self.diagnostics.warnings[warning_ix][1][1].append(evname) else: # make new nontermevtime entry for the new warning nontermevtimes[Evtimes[evix][ev]] = \ len(self.diagnostics.warnings) self.diagnostics.warnings.append((W_NONTERMEVENT, (Evtimes[evix][ev], [evname]))) except IndexError: print("Events returned from integrator are the wrong size.") print(" Did you change the system and not refresh the C " \ + "library using the forcelibrefresh() method?") raise termcount = 0 for (w,i) in self.diagnostics.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: if termcount > 0: raise ValueError("Internal error: more than one terminal " "event found") termcount += 1 # post-process check of variable bounds (if defined and algparams['checkBounds'] True) if self._dircode > 0: compare = operator.lt last_ix = Inf else: compare = operator.gt last_ix = -Inf highest_ix = X.shape[1]-1 last_t = Inf if self.algparams['checkBounds'] > 0: # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = dict(zip(range(self.dimension), [self.variables[xn].depdomain for xn in xnames])) offender_ix = None for xi in range(self.dimension): if not any(depdomains[xi].isfinite()): # no point in checking when the bounds are +/- infinity continue next_last_ix = array_bounds_check(X[xi][old_highest_ix:], depdomains[xi], self._dircode) + old_highest_ix if compare(next_last_ix, last_ix): # won't count as truncating unless the following checks # hold last_ix = next_last_ix offender_ix = xi if not isfinite(last_ix) and last_ix < 0: # only use +Inf hereon to flag no truncation needed last_ix = Inf elif last_ix >= 0 and last_ix < highest_ix: # truncate data last_t = alltData[last_ix] print("Warning; domain bound reached (because algparams['checkBounds'] > 0)") self.diagnostics.warnings.append((W_TERMSTATEBD, (last_t, xnames[offender_ix], X[offender_ix, last_ix], depdomains[offender_ix].get()))) # Create variables (self.variables contains no actual data) variables = copyVarDict(self.variables) # build event pointset information (reset previous trajectory's) # don't include events after any truncation due to state bound violation self.trajevents = {} for evix in range(len(self._eventNames)): evname = self._eventNames[evix] if Evpoints[evix] is None: self.trajevents[evname] = None else: try: ev_a_list = [] for t in Evtimes[evix]: tix = find(alltData, t) ev_a_list.append(A[:,tix]) ev_array = concatenate((Evpoints[evix], transpose(array(ev_a_list, 'd')))) del ev_a_list, tix except TypeError: # A is empty ev_array = Evpoints[evix] if last_ix >= 0 and last_ix < highest_ix: # don't count last_ix = -1 which is the same as highest_ix last_ev_tix = npy.argmax(Evtimes[evix] >= alltData[last_ix]) if last_ev_tix == 0 and Evtimes[evix][0] >= last_t: # checks that there was actually a violation # - so no events to record self.trajevents[evname] = None else: # truncation needed ev_array = ev_array[:, :last_ev_tix+1] ev_times = Evtimes[evix][:last_ev_tix+1] self.trajevents[evname] = Pointset({'coordnames': xnames+anames, 'indepvarname': 't', 'coordarray': ev_array, 'indepvararray': ev_times}) else: # no truncation needed self.trajevents[evname] = Pointset({'coordnames': xnames+anames, 'indepvarname': 't', 'coordarray': ev_array, 'indepvararray': Evtimes[evix]}) if last_ix >= 0 and last_ix < highest_ix: # truncate X = X[:, :last_ix] alltData = alltData[:last_ix] try: allxDataDict = dict(zip(xnames,X)) except IndexError: print("Integration returned variable values of unexpected dimensions.") print(" Did you change the system and not refresh the C library" \ + " using the forcelibrefresh() method?") raise # storage of all auxiliary variable data try: if anames != []: if last_ix < highest_ix: A = A[:, :last_ix] try: allaDataDict = dict(zip(anames,A)) except TypeError: print("Internal error! Type of A: %s" % type(A)) raise except IndexError: print("Integration returned auxiliary values of unexpected dimensions.") print(" Did you change the system and not refresh the C library" \ + " using the forcelibrefresh() method?") raise if int(Err) == 1 or (int(Err) == 2 and termcount == 1): # output OK if self.algparams['poly_interp']: rhsfn = self._solver.Rhs # when Dopri can output the Rhs values alongside variable # values then this won't be necessary dxvals = zeros((len(alltData),self.dimension),float) for tix, tval in enumerate(alltData): # solver's Rhs function already contains the inputs so no # need to recompute and provide here. #i = _pollInputs(sortedDictValues(self.inputs), tval, # self.checklevel) # X is the output variable array, but rhsfn demands a list dxvals[tix] = rhsfn(tval, list(X[:,tix]), plist)[0] for xi, x in enumerate(xnames): if len(alltData) > 1: if self.algparams['poly_interp']: interp = PiecewisePolynomial(alltData, array([allxDataDict[x], dxvals[:,xi]]).T, 2) else: interp = interp1d(alltData, allxDataDict[x]) variables[x] = Variable(interp, 't', x, x) else: raise PyDSTool_ValueError("Fewer than 2 data points " "computed") for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData,allaDataDict[a]), 't', a, a) else: raise PyDSTool_ValueError("Fewer than 2 data points " "computed") # final checks #self.validateSpec() self.defined = True return Trajectory(trajname, list(variables.values()), abseps=self._abseps, globalt0=self.globalt0, checklevel=self.checklevel, FScompatibleNames=self._FScompatibleNames, FScompatibleNamesInv=self._FScompatibleNamesInv, modelNames=self.name, events=self.trajevents, modelEventStructs=self.eventstruct) else: try: diagnost_info = self.diagnostics._errorcodes[int(Err)] except TypeError: # errcode messed up from Dopri print("Error code: %d" % Err) diagnost_info = self.diagnostics._errorcodes[0] if self._solver.verbose: info(self.diagnostics.outputStats, "Output statistics") self.defined = False # Did the solver run out of memory? if (len(alltData) == self.algparams['max_pts'] or \ self.diagnostics.outputStats['num_steps'] >= self.algparams['max_pts']) \ and alltData[-1] < tend: print("max_pts algorithmic parameter too small: current " + \ "value is %i"%self.algparams['max_pts']) # avstep = (self.algparams['init_step']+self.diagnostics.outputStats['last_step'])/2. if self.diagnostics.outputStats['last_time']-tbegin > 0: ms = str(int(round(self.algparams['max_pts'] / \ (self.diagnostics.outputStats['last_time'] - \ tbegin)*(tend-tbegin)))) else: ms = 'Inf' print("(recommended value for this trajectory segment is " + \ "estimated to be %s (saved in diagnostics.errors attribute))"%str(ms)) diagnost_info += " -- recommended value is " + ms self.diagnostics.errors.append((E_COMPUTFAIL, (self._solver.lastTime, diagnost_info))) raise PyDSTool_ExistError("No trajectory created")
def compute(self, trajname, dirn='f', ics=None): continue_integ = ODEsystem.prepDirection(self, dirn) if self._dircode == -1: raise NotImplementedError('Backwards integration is not implemented') if ics is not None: self.set(ics=ics) self.validateICs() self.diagnostics.clearWarnings() self.diagnostics.clearErrors() pnames = sortedDictKeys(self.pars) xnames = self._var_ixmap # ensures correct order # Check i.c.'s are well defined (finite) self.checkInitialConditions() haveJac = int(self.haveJacobian()) indepdom0, indepdom1 = self.indepvariable.depdomain.get() if continue_integ: if indepdom0 > self._solver.t: print("Previous end time is %f"%self._solver.t) raise ValueError("Start time not correctly updated for " "continuing orbit") x0 = self._solver.y indepdom0 = self._solver.t self.indepvariable.depdomain.set((indepdom0,indepdom1)) else: x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) t0 = indepdom0 dt = self.algparams['init_step'] # speed up repeated access to solver by making a temp name for it solver = self._solver solver.set_initial_value(x0, t0) solver.dt = dt # wrap up each dictionary initial value as a singleton list alltData = [t0] allxDataDict = dict(zip(xnames, map(listid, x0))) plist = sortedDictValues(self.pars) extralist = copy(plist) if self.inputs: # inputVarList is a list of Variables inames = sortedDictKeys(self.inputs) listend = self.numpars + len(self.inputs) inputVarList = sortedDictValues(self.inputs) ilist = _pollInputs(inputVarList, alltData[0]+self.globalt0, self.checklevel) else: ilist = [] inames = [] listend = self.numpars inputVarList = [] extralist.extend(ilist) solver.set_f_params(extralist) if haveJac: solver.set_jac_params(extralist) do_poly = self.algparams['poly_interp'] if do_poly: rhsfn = getattr(self, self.funcspec.spec[1]) dx0 = rhsfn(t0, x0, extralist) alldxDataDict = dict(zip(xnames, map(listid, dx0))) auxvarsfn = getattr(self,self.funcspec.auxspec[1]) # Make t mesh if it wasn't given as 'specialtimes' if not all(isfinite(self.indepvariable.depdomain.get())): print("Time domain was: %r" % self.indepvariable.depdomain.get()) raise ValueError("Ensure time domain is finite") if dt == indepdom1 - indepdom0: # single-step integration required # special times will not have been set (unless trivially # they are [indepdom0, indepdom1]) tmesh = [indepdom0, indepdom1] else: notDone = True while notDone: tmesh = self.indepvariable.depdomain.sample(dt, strict=True, avoidendpoints=True) notDone = False first_found_t = None eventslist = self.eventstruct.query(['active', 'notvarlinked']) termevents = self.eventstruct.query(['term'], eventslist) tmesh.pop(0) # get rid of first entry for initial condition xnames = self.funcspec.vars # storage of all auxiliary variable data allaDataDict = {} anames = self.funcspec.auxvars avals = auxvarsfn(t0, x0, extralist) for aix in range(len(anames)): aname = anames[aix] try: allaDataDict[aname] = [avals[aix]] except IndexError: print("\nEuler generator: There was a problem evaluating " \ + "an auxiliary variable") print("Debug info: avals (length %d) was %r" % (len(avals), avals)) print("Index out of range was %d" % aix) print(self.funcspec.auxspec[1]) print(hasattr(self, self.funcspec.auxspec[1])) print("Args were: %r" % [t0, x0, extralist]) raise # Initialize signs of event detection objects at IC self.setEventICs(self.initialconditions, self.globalt0) if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames,ilist))) else: parsinps = self.pars if eventslist != []: dataDict = copy(self.initialconditions) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = t0 evsflagged = self.eventstruct.pollHighLevelEvents(None, dataDict, parsinps, eventslist) if len(evsflagged) > 0: raise RuntimeError("Some events flagged at initial condition") if continue_integ: # revert to prevprevsign, since prevsign changed after call self.eventstruct.resetHighLevelEvents(t0, eventslist, 'prev') elif self._for_hybrid_DS: # self._for_hybrid_DS is set internally by HybridModel class # to ensure not to reset events, because they may be about to # flag on first step if previous hybrid state was the same # generator and, for example, two variables are synchronizing # so that their events get very close together. # Just reset the starttimes of these events for evname, ev in eventslist: ev.starttime = t0 else: # default state is a one-off call to this generator self.eventstruct.resetHighLevelEvents(t0, eventslist, None) self.eventstruct.validateEvents(self.funcspec.vars + \ self.funcspec.auxvars + \ self.funcspec.inputs + \ ['t'], eventslist) evnames = [ev[0] for ev in eventslist] lastevtime = {}.fromkeys(evnames, None) # initialize new event info dictionaries Evtimes = {} Evpoints = {} if continue_integ: for evname in evnames: try: # these are in global time, so convert to local time lastevtime[evname] = self.eventstruct.Evtimes[evname][-1] \ - self.globalt0 except (IndexError, KeyError): # IndexError: Evtimes[evname] was None # KeyError: Evtimes does not have key evname pass for evname in evnames: Evtimes[evname] = [] Evpoints[evname] = [] # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = dict(zip(range(self.dimension), [self.variables[xn].depdomain for xn in xnames])) # Main integration loop num_points = 0 breakwhile = False while not breakwhile: try: new_t = tmesh.pop(0) # this destroys tmesh for future use except IndexError: # empty break # optional user function (not a method) self.ufunc_before(self) try: errcode = solver.integrate(dt) except: print("Error calling right hand side function:") self.showSpec() print("Numerical traceback information (current state, " \ + "parameters, etc.)") print("in generator dictionary 'traceback'") self.traceback = {'vars': dict(zip(xnames,solver.y)), 'pars': dict(zip(pnames,plist)), 'inputs': dict(zip(inames,ilist)), self.indepvariable.name: new_t} raise avals = auxvarsfn(new_t, solver.y, extralist) # Uncomment the following assertion for debugging # assert all([isfinite(a) for a in avals]), \ # "Some auxiliary variable values not finite" if eventslist != []: dataDict = dict(zip(xnames,solver.y)) dataDict.update(dict(zip(anames,avals))) dataDict['t'] = new_t if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames, _pollInputs(inputVarList, new_t+self.globalt0, self.checklevel)))) else: parsinps = self.pars evsflagged = self.eventstruct.pollHighLevelEvents(None, dataDict, parsinps, eventslist) ## print new_t, evsflagged ## evsflagged = [ev for ev in evsflagged if solver.t-indepdom0 > ev[1].eventinterval] termevsflagged = [e for e in termevents if e in evsflagged] nontermevsflagged = [e for e in evsflagged if e not in termevsflagged] # register any non-terminating events in the warnings # list, unless they are 'precise' in which case flag # them to be resolved after integration completes if len(nontermevsflagged) > 0: evnames = [ev[0] for ev in nontermevsflagged] precEvts = self.eventstruct.query(['precise'], nontermevsflagged) # register both precise and non-precise events the same # (Euler currently ignores precise events with its fixed time step) nonprecEvts = self.eventstruct.query(['notprecise'], nontermevsflagged) + precEvts nonprec_evnames = [e[0] for e in nonprecEvts] + [e[0] for e in precEvts] # only record events if they have not been previously # flagged within their event interval if nonprec_evnames != []: temp_names = [] for evname, ev in nonprecEvts: prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if solver.t-prevevt_time < ev.eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: temp_names.append(evname) lastevtime[evname] = solver.t self.diagnostics.warnings.append((W_NONTERMEVENT, (solver.t, temp_names))) for evname in temp_names: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) do_termevs = [] if termevsflagged != []: # only record events if they have not been previously # flagged within their event interval for e in termevsflagged: prevevt_time = lastevtime[e[0]] ## print "Event %s flagged."%e[0] ## print " ... last time was ", prevevt_time ## print " ... event interval = ", e[1].eventinterval ## print " ... t = %f, dt = %f"%(solver.t, dt) if prevevt_time is None: ignore_ev = False else: ## print " ... comparison = %f < %f"%(solver.t-dt-prevevt_time, e[1].eventinterval) if solver.t-dt-prevevt_time < e[1].eventinterval: ignore_ev = True ## print "Euler ignore ev" else: ignore_ev = False if not ignore_ev: do_termevs.append(e) if len(do_termevs) > 0: # >= 1 active terminal event flagged at this time point evnames = [ev[0] for ev in do_termevs] self.diagnostics.warnings.append((W_TERMEVENT, \ (solver.t, evnames))) first_found_t = solver.t for evname in evnames: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) # break while loop after appending t, x breakwhile = True # after events have had a chance to be detected at a state boundary # we check for any that have not been caught by an event (will be # much less accurately determined) if not breakwhile: # only here if a terminal event hasn't just flagged for xi in range(self.dimension): if not self.contains(depdomains[xi], solver.y[xi], self.checklevel): self.diagnostics.warnings.append((W_TERMSTATEBD, (solver.t, xnames[xi],solver.y[xi], depdomains[xi].get()))) breakwhile = True break # for loop if breakwhile: break # while loop alltData.append(solver.t) if do_poly: dxvals = rhsfn(solver.t, solver.y, extralist) for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) alldxDataDict[xname].append(dxvals[xi]) else: for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) for aix, aname in enumerate(anames): allaDataDict[aname].append(avals[aix]) num_points += 1 if not breakwhile: try: extralist[self.numpars:listend] = [f(solver.t+self.globalt0, self.checklevel) \ for f in inputVarList] except ValueError: print('External input call caused value out of range error:',\ 't = %f' % solver.t) for f in inputVarList: if f.diagnostics.hasWarnings(): print('External input variable %s out of range:'%f.name) print(' t = %r, %s = %r' % ( repr(f.diagnostics.warnings[-1][0]), f.name, repr(f.diagnostics.warnings[-1][1]))) raise except AssertionError: print('External input call caused t out of range error: t = %f' % solver.t) raise solver.set_f_params(extralist) breakwhile = not solver.successful() # optional user function (not a method) self.ufunc_after(self) # Check that any terminal events found terminated the code correctly if first_found_t is not None: # ... then terminal events were found. try: if self.diagnostics.warnings[-1][0] not in [W_TERMEVENT, W_TERMSTATEBD]: print("t =%f" % solver.t) print("state =%r" % dict(zip(xnames,solver.y))) raise RuntimeError("Event finding code for terminal event " "failed in Generator " + self.name + \ ": try decreasing eventdelay or " "eventinterval below eventtol, or the " "atol and rtol parameters") except IndexError: info(self.diagnostics.outputStats, "Output statistics") print("t =%f" % solver.t) print("x =%f" % solver.y) raise RuntimeError("Event finding failed in Generator " + \ self.name + ": try decreasing eventdelay " "or eventinterval below eventtol") # Package up computed trajectory in Variable variables # Add external inputs warnings to self.diagnostics.warnings, if any for f in inputVarList: for winfo in f.diagnostics.warnings: self.diagnostics.warnings.append((W_NONTERMSTATEBD, (winfo[0], f.name, winfo[1], f.depdomain.get()))) # check for non-unique terminal event termcount = 0 for (w,i) in self.diagnostics.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: termcount += 1 if termcount > 1: self.diagnostics.errors.append((E_NONUNIQUETERM, (alltData[-1], i[1]))) # uncomment the following lines for debugging # assert len(alltData) == len(allxDataDict.values()[0]) \ # == len(allaDataDict.values()[0]), "Output data size mismatch" # for val_list in allaDataDict.values(): # assert all([isfinite(x) for x in val_list]) # Create variables (self.variables contains no actual data) # These versions of the variables are only final if no non-terminal # events need to be inserted. variables = copyVarDict(self.variables) for x in xnames: if len(alltData) > 1: if do_poly: xvals = array([allxDataDict[x], alldxDataDict[x]]).T interp = PiecewisePolynomial(alltData, xvals, 2) else: xvals = allxDataDict[x] interp = interp1d(alltData, xvals) variables[x] = Variable(interp, 't', x, x) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") self.diagnostics.outputStats = {'last_step': dt, 'num_fcns': num_points, 'num_steps': num_points, 'errorStatus': errcode } if solver.successful(): #self.validateSpec() for evname, evtlist in Evtimes.items(): try: self.eventstruct.Evtimes[evname].extend([et+self.globalt0 \ for et in evtlist]) except KeyError: self.eventstruct.Evtimes[evname] = [et+self.globalt0 \ for et in evtlist] # build event pointset information (reset previous trajectory's) self.trajevents = {} for (evname, ev) in eventslist: evpt = Evpoints[evname] if evpt == []: self.trajevents[evname] = None else: evpt = transpose(array(evpt)) self.trajevents[evname] = Pointset({'coordnames': xnames+anames, 'indepvarname': 't', 'coordarray': evpt, 'indepvararray': Evtimes[evname], 'indepvartype': float}) self.defined = True return Trajectory(trajname, list(variables.values()), abseps=self._abseps, globalt0=self.globalt0, checklevel=self.checklevel, FScompatibleNames=self._FScompatibleNames, FScompatibleNamesInv=self._FScompatibleNamesInv, events=self.trajevents, modelNames=self.name, modelEventStructs=self.eventstruct) else: try: self.diagnostics.errors.append((E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[errcode]))) except TypeError: # e.g. when errcode has been used to return info list print("Error information: %d" % errcode) self.diagnostics.errors.append((E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[0]))) self.defined = False
def __init__(self, kw): """Use the nobuild key to postpone building of the library, e.g. in order to provide additional build options to makeLibSource and compileLib methods or to make changes to the C code by hand. No build options can be specified otherwise.""" # Building is just doing make if 'nobuild' in kw: nobuild = kw['nobuild'] del kw['nobuild'] else: nobuild = False ODEsystem.__init__(self, kw) self._solver = None assert self.funcspec.targetlang == 'matlab', \ ('Wrong target language for functional specification. ' 'matlab needed for this class') assert isinstance( self.funcspec, RHSfuncSpec), ('ADMC++ ' 'requires RHSfuncSpec type to proceed') assert not self.inputs, \ 'ADMC++ does not support external inputs feature' self._errorcodes = {} self._paraminfo = {} self.vftype = 'vfieldts' # currently the final four of these params are for event handling # NEED TO CHECK WHICH ONES ARE SUPPORTED BY ADMC -- LOOKS LIKE EVTOLS ONLY FOR NOW # HACK: vftype is alg param for now, tells us whether parent class is hybridvf, vfieldts, etc. algparams_def = {'evtols': 0.0001, 'vftype': 'vfieldts'} # Remove this later for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v # verify that no additional keys are present in algparams, after # defaults are added above if len(self.algparams) != len(algparams_def): raise ValueError("Invalid keys present in algparams argument: " \ + str(remain(self.algparams.keys(),algparams_def.keys()))) thisplatform = platform.system() self._compilation_tempdir = os.path.join(os.getcwd(), "admcpp_temp") if not os.path.isdir(self._compilation_tempdir): try: assert not os.path.isfile(self._compilation_tempdir), \ "A file already exists with the same name" os.mkdir(self._compilation_tempdir) except: print("Could not create compilation temp directory " + \ self._compilation_tempdir) raise # ADMC targets must go in their own directories with appropriate names self._model_dir = "@" + self.name self._target_dir = os.path.join(self._compilation_tempdir, self._model_dir) # Make the target directory if not os.path.isdir(self._target_dir): try: assert not os.path.isfile(self._target_dir), \ "A file already exists with the same name" os.mkdir(self._target_dir) except: print("Could not creat target ADMC model directory " + \ self._target_dir) raise """ An ADMC model has the following files: vfield.m -- contains code for the RHS of the vector field set.m -- a generic method that overload matlab's set method; only need to insert vfield name get.m -- a generic method that overloads matlab's get method; only need to insert appropriate parent name """ # model.m, get.m, set.m, vfield.m are minimal files required. TO DO: EVENTS self._model_file = self.name + ".m" self._ic_file = self.name + "_ics.m" self._param_file = self.name + "_params.m" self._set_file = "set.m" self._get_file = "get.m" self._vfield_file = "vfield.m" self._events_file = "events.m" self._vf_filename_ext = "_" + self._model_file[:-2] if not nobuild: self.makeLibSource() else: print("Build the library using the makeLib method, or in ") print("stages using the makeLibSource and compileLib methods.")
def compute(self, trajname, dirn='f', ics=None): continue_integ = ODEsystem.prepDirection(self, dirn) if self._dircode == -1: raise NotImplementedError('Backwards integration is not implemented') if ics is not None: self.set(ics=ics) self.validateICs() self.diagnostics.clearWarnings() self.diagnostics.clearErrors() pnames = sortedDictKeys(self.pars) xnames = self._var_ixmap # ensures correct order # Check i.c.'s are well defined (finite) self.checkInitialConditions() if self.algparams['stiff']: methstr = 'bdf' methcode = 2 else: methstr = 'adams' methcode = 1 haveJac = int(self.haveJacobian()) if isinstance(self.algparams['atol'], list): if len(self.algparams['atol']) != self.dimension: raise ValueError('atol list must have same length as phase ' 'dimension') else: atol = self.algparams['atol'] self.algparams['atol'] = [atol for dimix in range(self.dimension)] indepdom0, indepdom1 = self.indepvariable.depdomain.get() if continue_integ: if indepdom0 > self._solver.t: print("Previous end time is %f"%self._solver.t) raise ValueError("Start time not correctly updated for " "continuing orbit") x0 = self._solver.y indepdom0 = self._solver.t self.indepvariable.depdomain.set((indepdom0,indepdom1)) else: x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) t0 = indepdom0 if self.algparams['use_special']: tmesh_special = list(self.algparams['specialtimes']) if continue_integ: if self._solver.t not in tmesh_special: raise ValueError("Invalid time to continue integration:" "it is not in 'special times'") tmesh_special = tmesh_special[tmesh_special.index(self._solver.t):] try: dt = min([tmesh_special[1]-t0, self.algparams['init_step']]) except: raise ValueError("Invalid list of special times") if not isincreasing(tmesh_special): raise ValueError("special times must be given in increasing " "order") if self.indepvariable.depdomain.contains(t0) is notcontained or \ self.indepvariable.depdomain.contains(tmesh_special[-1]) is notcontained: raise PyDSTool_BoundsError("special times were outside of independent " "variable bounds") else: tmesh_special = [] dt = self.algparams['init_step'] # speed up repeated access to solver by making a temp name for it solver = self._solver if solver._integrator is None: # Banded Jacobians not yet supported # # start a new integrator, because method may have been # switched solver.set_integrator('vode', method=methstr, rtol=self.algparams['rtol'], atol=self.algparams['atol'], nsteps=self.algparams['max_pts'], max_step=self.algparams['max_step'], min_step=self.algparams['min_step'], first_step=dt/2., with_jacobian=haveJac) else: solver.with_jacobian = haveJac # self.mu = lband # self.ml = uband solver.rtol = self.algparams['rtol'] solver.atol = self.algparams['atol'] solver.method = methcode # self.order = order solver.nsteps = self.algparams['max_pts'] solver.max_step = self.algparams['max_step'] solver.min_step = self.algparams['min_step'] solver.first_step = dt/2. solver.set_initial_value(x0, t0) ## Broken code for going backwards (doesn't respect 'continue' option ## either) ## if self._dircode == 1: ## solver.set_initial_value(x0, t0) ## else: ## solver.set_initial_value(x0, indepdom1) # wrap up each dictionary initial value as a singleton list alltData = [t0] allxDataDict = dict(zip(xnames, map(listid, x0))) plist = sortedDictValues(self.pars) extralist = copy(plist) if self.inputs: # inputVarList is a list of Variables inames = sortedDictKeys(self.inputs) listend = self.numpars + len(self.inputs) inputVarList = sortedDictValues(self.inputs) ilist = _pollInputs(inputVarList, alltData[0]+self.globalt0, self.checklevel) else: ilist = [] inames = [] listend = self.numpars inputVarList = [] extralist.extend(ilist) solver.set_f_params(extralist) if haveJac: solver.set_jac_params(extralist) do_poly = self.algparams['poly_interp'] if do_poly: rhsfn = getattr(self, self.funcspec.spec[1]) dx0 = rhsfn(t0, x0, extralist) alldxDataDict = dict(zip(xnames, map(listid, dx0))) auxvarsfn = getattr(self,self.funcspec.auxspec[1]) strict = self.algparams['strictdt'] # Make t mesh if it wasn't given as 'specialtimes' if not all(isfinite(self.indepvariable.depdomain.get())): print("Time domain was: %f" % self.indepvariable.depdomain.get()) raise ValueError("Ensure time domain is finite") if dt == indepdom1 - indepdom0: # single-step integration required # special times will not have been set (unless trivially # they are [indepdom0, indepdom1]) tmesh = [indepdom0, indepdom1] else: notDone = True repeatTol = 10 count = 0 while notDone and count <= repeatTol: try: tmesh = self.indepvariable.depdomain.sample(dt, strict=strict, avoidendpoints=self.checklevel>2) notDone = False except AssertionError: count += 1 dt = dt/3.0 if count == repeatTol: raise AssertionError("Supplied time step is too large for " "selected time interval") # incorporate tmesh_special, if used, ensuring uniqueness if tmesh_special != []: tmesh.extend(tmesh_special) tmesh = list(unique(tmesh)) tmesh.sort() if len(tmesh)<=2: # safety net, in case too few points in mesh # too few points unless we can add endpoint if tmesh[-1] != indepdom1: # dt too large for tmesh to have more than one point tmesh.append(indepdom1) if not strict: # get actual time step used # don't use [0] in case avoided end points try: dt = tmesh[2]-tmesh[1] except IndexError: # can't avoid end points for such a small mesh dt = tmesh[1]-tmesh[0] #if self.eventstruct.query(['lowlevel']) != []: # raise ValueError("Only high level events can be passed to VODE") eventslist = self.eventstruct.query(['active', 'notvarlinked']) termevents = self.eventstruct.query(['term'], eventslist) # reverse time by reversing mesh doesn't work ## if self._dircode == -1: ## tmesh.reverse() tmesh.pop(0) # get rid of first entry for initial condition xnames = self.funcspec.vars # storage of all auxiliary variable data allaDataDict = {} anames = self.funcspec.auxvars avals = auxvarsfn(t0, x0, extralist) for aix in range(len(anames)): aname = anames[aix] try: allaDataDict[aname] = [avals[aix]] except IndexError: print("\nVODE generator: There was a problem evaluating " \ + "an auxiliary variable") print("Debug info: avals (length %d) was %r" % (len(avals), avals)) print("Index out of range was %d" % aix) print(self.funcspec.auxspec[1]) print(hasattr(self, self.funcspec.auxspec[1])) print("Args were:%r" % [t0, x0, extralist]) raise # Initialize signs of event detection objects at IC self.setEventICs(self.initialconditions, self.globalt0) if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames,ilist))) else: parsinps = self.pars if eventslist != []: dataDict = copy(self.initialconditions) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = t0 ## Removed this "belt and braces" pre-check because it messes ## up some events in the middle of hybrid regime switches. ## Not sure yet what the problem is. (Oct 2013) #evsflagged = self.eventstruct.pollHighLevelEvents(None, # dataDict, # parsinps, # eventslist) #if len(evsflagged) > 0: # raise RuntimeError("Some events flagged at initial condition") if continue_integ: # revert to prevprevsign, since prevsign changed after call self.eventstruct.resetHighLevelEvents(t0, eventslist, 'prev') elif self._for_hybrid_DS: # self._for_hybrid_DS is set internally by HybridModel class # to ensure not to reset events, because they may be about to # flag on first step if previous hybrid state was the same # generator and, for example, two variables are synchronizing # so that their events get very close together. # Just reset the starttimes of these events for evname, ev in eventslist: ev.starttime = t0 else: # default state is a one-off call to this generator self.eventstruct.resetHighLevelEvents(t0, eventslist, None) self.eventstruct.validateEvents(self.funcspec.vars + \ self.funcspec.auxvars + \ self.funcspec.inputs + \ ['t'], eventslist) # temp storage of first time at which terminal events found # (this is used for keeping the correct end point of new mesh) first_found_t = None # list of precise non-terminal events to be resolved after integration nontermprecevs = [] evnames = [ev[0] for ev in eventslist] lastevtime = {}.fromkeys(evnames, None) # initialize new event info dictionaries Evtimes = {} Evpoints = {} if continue_integ: for evname in evnames: try: # these are in global time, so convert to local time lastevtime[evname] = self.eventstruct.Evtimes[evname][-1] \ - self.globalt0 except (IndexError, KeyError): # IndexError: Evtimes[evname] was None # KeyError: Evtimes does not have key evname pass for evname in evnames: Evtimes[evname] = [] Evpoints[evname] = [] # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = dict(zip(range(self.dimension), [self.variables[xn].depdomain for xn in xnames])) # Main integration loop num_points = 0 breakwhile = False while not breakwhile: try: new_t = tmesh.pop(0) # this destroys tmesh for future use except IndexError: # empty break last_t = solver.t # a record of previous time for use by event detector try: errcode = solver.integrate(new_t) except: print("Error calling right hand side function:") self.showSpec() print("Numerical traceback information (current state, " \ + "parameters, etc.)") print("in generator dictionary 'traceback'") self.traceback = {'vars': dict(zip(xnames,solver.y)), 'pars': dict(zip(pnames,plist)), 'inputs': dict(zip(inames,ilist)), self.indepvariable.name: new_t} raise avals = auxvarsfn(new_t, solver.y, extralist) # Uncomment the following assertion for debugging # assert all([isfinite(a) for a in avals]), \ # "Some auxiliary variable values not finite" if eventslist != []: dataDict = dict(zip(xnames,solver.y)) dataDict.update(dict(zip(anames,avals))) dataDict['t'] = new_t if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames, _pollInputs(inputVarList, new_t+self.globalt0, self.checklevel)))) else: parsinps = self.pars evsflagged = self.eventstruct.pollHighLevelEvents(None, dataDict, parsinps, eventslist) ## print new_t, evsflagged ## evsflagged = [ev for ev in evsflagged if solver.t-indepdom0 > ev[1].eventinterval] termevsflagged = [e for e in termevents if e in evsflagged] nontermevsflagged = [e for e in evsflagged if e not in termevsflagged] # register any non-terminating events in the warnings # list, unless they are 'precise' in which case flag # them to be resolved after integration completes if len(nontermevsflagged) > 0: evnames = [ev[0] for ev in nontermevsflagged] precEvts = self.eventstruct.query(['precise'], nontermevsflagged) prec_evnames = [e[0] for e in precEvts] # first register non-precise events nonprecEvts = self.eventstruct.query(['notprecise'], nontermevsflagged) nonprec_evnames = [e[0] for e in nonprecEvts] # only record events if they have not been previously # flagged within their event interval if nonprec_evnames != []: temp_names = [] for evname, ev in nonprecEvts: prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if solver.t-prevevt_time < ev.eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: temp_names.append(evname) lastevtime[evname] = solver.t self.diagnostics.warnings.append((W_NONTERMEVENT, (solver.t, temp_names))) for evname in temp_names: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) for evname, ev in precEvts: # only record events if they have not been previously # flagged within their event interval prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if last_t-prevevt_time < ev.eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: nontermprecevs.append((last_t, solver.t, (evname, ev))) # be conservative as to where the event is, so # that don't miss any events. lastevtime[evname] = last_t # solver.t-dt ev.reset() #ev.prevsign = None # do_termevs = [] if termevsflagged != []: # only record events if they have not been previously # flagged within their event interval for e in termevsflagged: prevevt_time = lastevtime[e[0]] ## print "Event %s flagged."%e[0] ## print " ... last time was ", prevevt_time ## print " ... event interval = ", e[1].eventinterval ## print " ... t = %f, dt = %f"%(solver.t, dt) if prevevt_time is None: ignore_ev = False else: ## print " ... comparison = %f < %f"%(solver.t-dt-prevevt_time, e[1].eventinterval) if last_t-prevevt_time < e[1].eventinterval: ignore_ev = True ## print "VODE ignore ev" else: ignore_ev = False if not ignore_ev: do_termevs.append(e) if len(do_termevs) > 0: # >= 1 active terminal event flagged at this time point if all([not ev[1].preciseFlag for ev in do_termevs]): # then none of the events specify greater accuracy # register the event in the warnings evnames = [ev[0] for ev in do_termevs] self.diagnostics.warnings.append((W_TERMEVENT, \ (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) # break while loop after appending t, x breakwhile = True else: # find which are the 'precise' events that flagged precEvts = self.eventstruct.query(['precise'], do_termevs) # these events have flagged once so eventdelay has # been used. now switch it off while finding event # precisely (should be redundant after change to # eventinterval and eventdelay parameters) evnames = [ev[0] for ev in precEvts] if first_found_t is None: ## print "first time round at", solver.t numtries = 0 first_found_t = solver.t restore_evts = deepcopy(precEvts) minbisectlimit = min([ev[1].bisectlimit for ev in precEvts]) for ev in precEvts: ev[1].eventdelay = 0. else: numtries += 1 ## print "time round: ", numtries if numtries > minbisectlimit: self.diagnostics.warnings.append((W_BISECTLIMIT, (solver.t, evnames))) breakwhile = True # get previous time point if len(alltData)>=1: # take one step back -> told, which will # get dt added back to first new meshpoint # (solver.t is the step *after* the event was # detected) told = last_t # solver.t-dt without the loss of precision from subtraction else: raise ValueError("Event %s found too "%evnames[0]+\ "close to local start time: try decreasing " "initial step size (current size is " "%f @ t=%f)"%(dt,solver.t+self.globalt0)) # absolute tolerance check on event function values (not t) in_tols = [abs(e[1].fval) < e[1].eventtol for e in precEvts] if all(in_tols): ## print "Registering event:", dt_min, dt # register the event in the warnings self.diagnostics.warnings.append((W_TERMEVENT, (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) xv = solver.y av = array(avals) Evpoints[evname].append(concatenate((xv, av))) # Cannot continue -- dt_min no smaller than # previous dt. If this is more than the first time # in this code then have found the event to within # the minimum 'precise' event's eventtol, o/w need # to set eventtol smaller. # Before exiting event-finding loop, reset prevsign # of flagged events self.eventstruct.resetHighLevelEvents(0, precEvts) breakwhile = True # while loop, but append point first if not breakwhile: dt_new = dt/5.0 # calc new tmesh trangewidth = 2*dt #first_found_t - told numpoints = int(math.ceil(trangewidth/dt_new)) # choose slightly smaller dt to fit trange exactly dt = trangewidth/numpoints tmesh = [told + i*dt for i in range(1, numpoints+1)] # linspace version is *much* slower for numpoints ~ 10 and 100 #tmesh = list(told+linspace(dt, numpoints*dt, numpoints)) # reset events according to new time mesh, # setting known previous event state to be their # "no event found" state self.eventstruct.resetHighLevelEvents(told, precEvts, state='off') # build new ic with last good values (at t=told) if len(alltData)>1: new_ic = [allxDataDict[xname][-1] \ for xname in xnames] else: new_ic = x0 # reset integrator solver.set_initial_value(new_ic, told) extralist[self.numpars:listend] = _pollInputs(inputVarList, told+self.globalt0, self.checklevel) solver.set_f_params(extralist) # continue integrating over new mesh continue # while # after events have had a chance to be detected at a state boundary # we check for any that have not been caught by an event (will be # much less accurately determined) if not breakwhile: # only here if a terminal event hasn't just flagged for xi in range(self.dimension): if not self.contains(depdomains[xi], solver.y[xi], self.checklevel): self.diagnostics.warnings.append((W_TERMSTATEBD, (solver.t, xnames[xi],solver.y[xi], depdomains[xi].get()))) breakwhile = True break # for loop if breakwhile: break # while loop alltData.append(solver.t) if do_poly: dxvals = rhsfn(solver.t, solver.y, extralist) for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) alldxDataDict[xname].append(dxvals[xi]) else: for xi, xname in enumerate(xnames): allxDataDict[xname].append(solver.y[xi]) for aix, aname in enumerate(anames): allaDataDict[aname].append(avals[aix]) num_points += 1 if not breakwhile: try: extralist[self.numpars:listend] = [f(solver.t+self.globalt0, self.checklevel) \ for f in inputVarList] except ValueError: print('External input call caused value out of range error:',\ 't = %f' % solver.t) for f in inputVarList: if f.diagnostics.hasWarnings(): print('External input variable %s out of range:'%f.name) print(' t = %r, %s, %r' % (repr(f.diagnostics.warnings[-1][0]), f.name, repr(f.diagnostics.warnings[-1][1]))) raise except AssertionError: print('External input call caused t out of range error: t = %f' % solver.t) raise solver.set_f_params(extralist) breakwhile = not solver.successful() # Check that any terminal events found terminated the code correctly if first_found_t is not None: # ... then terminal events were found. Those that were 'precise' had # their 'eventdelay' attribute temporarily set to 0. It now should # be restored. for evname1, ev1 in termevents: # restore_evts are copies of the originally flagged 'precise' # events for evname2, ev2 in restore_evts: if evname2 == evname1: ev1.eventdelay = ev2.eventdelay try: if self.diagnostics.warnings[-1][0] not in [W_TERMEVENT, W_TERMSTATEBD]: print("t =%f" % solver.t) print("state =%r" % dict(zip(xnames,solver.y))) raise RuntimeError("Event finding code for terminal event " "failed in Generator " + self.name + \ ": try decreasing eventdelay or " "eventinterval below eventtol, or the " "atol and rtol parameters") except IndexError: info(self.diagnostics.outputStats, "Output statistics") print("t =%f" % solver.t) print("x =%f" % solver.y) raise RuntimeError("Event finding failed in Generator " + \ self.name + ": try decreasing eventdelay " "or eventinterval below eventtol, or the " "atol and rtol parameters") # Package up computed trajectory in Variable variables # Add external inputs warnings to self.diagnostics.warnings, if any for f in inputVarList: for winfo in f.diagnostics.warnings: self.diagnostics.warnings.append((W_NONTERMSTATEBD, (winfo[0], f.name, winfo[1], f.depdomain.get()))) # check for non-unique terminal event termcount = 0 for (w,i) in self.diagnostics.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: termcount += 1 if termcount > 1: self.diagnostics.errors.append((E_NONUNIQUETERM, (alltData[-1], i[1]))) # uncomment the following lines for debugging # assert len(alltData) == len(allxDataDict.values()[0]) \ # == len(allaDataDict.values()[0]), "Output data size mismatch" # for val_list in allaDataDict.values(): # assert all([isfinite(x) for x in val_list]) # Create variables (self.variables contains no actual data) # These versions of the variables are only final if no non-terminal # events need to be inserted. variables = copyVarDict(self.variables) for x in xnames: if len(alltData) > 1: if do_poly: xvals = array([allxDataDict[x], alldxDataDict[x]]).T interp = PiecewisePolynomial(alltData, xvals, 2) else: xvals = allxDataDict[x] interp = interp1d(alltData, xvals) variables[x] = Variable(interp, 't', x, x) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) else: print("Error in Generator:%s" % self.name) print("t = %r" % alltData) print("x = %r" % allxDataDict) raise PyDSTool_ValueError("Fewer than 2 data points computed") # Resolve non-terminal 'precise' events that were flagged, using the # variables created. Then, add them to a new version of the variables. ntpe_tdict = {} for (et0,et1,e) in nontermprecevs: lost_evt = False # problem if eventinterval > et1-et0 !! # was: search_dt = max((et1-et0)/5,e[1].eventinterval) search_dt = (et1-et0)/5 try: et_precise_list = e[1].searchForEvents(trange=[et0,et1], dt=search_dt, checklevel=self.checklevel, parDict=self.pars, vars=variables, inputs=self.inputs, abseps=self._abseps, eventdelay=False, globalt0=self.globalt0) except (ValueError, PyDSTool_BoundsError): # dt too large for trange, e.g. if event tol is smaller than time mesh et_precise_list = [(et0, (et0, et1))] if et_precise_list == []: lost_evt = True for et_precise in et_precise_list: if et_precise[0] is not None: if et_precise[0] in ntpe_tdict: # add event name at this time (that already exists in the dict) ntpe_tdict[et_precise[0]].append(e[0]) else: # add event name at this time (when time is not already in dict) ntpe_tdict[et_precise[0]] = [e[0]] else: lost_evt = True if lost_evt: print("Error: A non-terminal, 'precise' event was lost -- did you reset", end=' ') print("events prior to integration?") raise PyDSTool_ExistError("Internal error: A non-terminal, " "'precise' event '%s' was lost after integration!"%e[0]) # add non-terminal event points to variables if ntpe_tdict != {}: # find indices of times at which event times will be inserted tix = 0 evts = list(ntpe_tdict.keys()) evts.sort() for evix in range(len(evts)): evt = evts[evix] evnames = ntpe_tdict[evt] self.diagnostics.warnings.append((W_NONTERMEVENT, (evt, evnames))) xval = [variables[x](evt) for x in xnames] ilist = _pollInputs(inputVarList, evt+self.globalt0, self.checklevel) # find accurate dx and aux vars value at this time if do_poly: dxval = rhsfn(evt, xval, plist+ilist) aval = list(auxvarsfn(evt, xval, plist+ilist)) for evname in evnames: Evtimes[evname].append(evt) Evpoints[evname].append(array(xval+aval)) tcond = less_equal(alltData[tix:], evt).tolist() try: tix = tcond.index(0) + tix # lowest index for t > evt do_insert = (alltData[tix-1] != evt) except ValueError: # evt = last t value so no need to add it do_insert = False if do_insert: alltData.insert(tix, evt) for ai, a in enumerate(anames): allaDataDict[a].insert(tix, aval[ai]) if do_poly: for xi, x in enumerate(xnames): allxDataDict[x].insert(tix, xval[xi]) alldxDataDict[x].insert(tix, dxval[xi]) else: for xi, x in enumerate(xnames): allxDataDict[x].insert(tix, xval[xi]) for x in xnames: if do_poly: # use asarray in case points added to sequences as a list xvals = array([asarray(allxDataDict[x]), asarray(alldxDataDict[x])]).T interp = PiecewisePolynomial(alltData, xvals, 2) else: xvals = allxDataDict[x] interp = interp1d(alltData, xvals) variables[x] = Variable(interp, 't', x, x) for a in anames: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) self.diagnostics.outputStats = {'last_step': dt, 'num_fcns': num_points, 'num_steps': num_points, 'errorStatus': errcode } if solver.successful(): #self.validateSpec() for evname, evtlist in Evtimes.items(): try: self.eventstruct.Evtimes[evname].extend([et+self.globalt0 \ for et in evtlist]) except KeyError: self.eventstruct.Evtimes[evname] = [et+self.globalt0 \ for et in evtlist] # build event pointset information (reset previous trajectory's) self.trajevents = {} for (evname, ev) in eventslist: evpt = Evpoints[evname] if evpt == []: self.trajevents[evname] = None else: evpt = transpose(array(evpt)) self.trajevents[evname] = Pointset({'coordnames': xnames+anames, 'indepvarname': 't', 'coordarray': evpt, 'indepvararray': Evtimes[evname], 'indepvartype': float}) self.defined = True return Trajectory(trajname, list(variables.values()), abseps=self._abseps, globalt0=self.globalt0, checklevel=self.checklevel, FScompatibleNames=self._FScompatibleNames, FScompatibleNamesInv=self._FScompatibleNamesInv, events=self.trajevents, modelNames=self.name, modelEventStructs=self.eventstruct) else: try: self.diagnostics.errors.append((E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[errcode]))) except TypeError: # e.g. when errcode has been used to return info list print("Error information: %d" % errcode) self.diagnostics.errors.append((E_COMPUTFAIL, (solver.t, self.diagnostics._errorcodes[0]))) self.defined = False
def __init__(self, kw): """Use the nobuild key to postpone building of the library, e.g. in order to provide additional build options to makeLibSource and compileLib methods or to make changes to the C code by hand. No build options can be specified otherwise.""" # delete because not covered in ODEsystem nobuild = kw.pop('nobuild', False) ODEsystem.__init__(self, kw) self.diagnostics.outputStatsInfo = { 'last_step': 'Predicted step size of the last accepted step (useful for a subsequent call to dop853).', 'num_steps': 'Number of used steps.', 'num_accept': 'Number of accepted steps.', 'num_reject': 'Number of rejected steps.', 'num_fcns': 'Number of function calls.', 'errorStatus': 'Error status on completion.' } self.diagnostics._errorcodes = { 0 : 'Unrecognized error code returned (see stderr output)', -1 : 'input is not consistent', -2 : 'larger nmax is needed', 2 : 'larger nmax or maxevtpts is probably needed (error raised by solout)', -3 : 'step size becomes too small', -4 : 'the problem is probably stiff (interrupted)', -8 : 'The solution exceeded a magbound (poor choice of initial step)'} self._solver = None algparams_def = {'poly_interp': False, 'init_step': 0, 'max_step': 0, 'rtol': [1e-9 for i in range(self.dimension)], 'atol': [1e-12 for i in range(self.dimension)], 'fac1': 0.2, 'fac2': 10.0, 'safety': 0.9, 'beta': 0.04, 'max_pts': 10000, 'refine': 0, 'maxbisect': [], # for events 'maxevtpts': 1000, # for events 'eventInt': [], # set using setEventInterval only 'eventDelay': [], # set using setEventDelay only 'eventTol': [], # set using setEventTol only 'use_special': 0, 'specialtimes': [], 'check_aux': 1, 'extraspace': 100, 'verbose': 0, 'hasJac': 0, 'hasJacP': 0, 'magBound': 1e7, 'boundsCheckMaxSteps': 1000, 'checkBounds': self.checklevel } for k, v in algparams_def.items(): if k not in self.algparams: self.algparams[k] = v # verify that no additional keys are present in algparams, after # defaults are added above if len(self.algparams) != len(algparams_def): raise ValueError("Invalid keys present in algparams argument: " \ + str(remain(self.algparams.keys(),algparams_def.keys()))) if self.haveMass(): raise ValueError("Mass matrix declaration is incompatible " "with Dopri853 integrator system specification") self._prepareEventSpecs() self._inputVarList = [] self._inputTimeList = [] if nobuild: print("Build the library using the makeLib method, or in ") print("stages using the makeLibSource and compileLib methods.") else: self.makeLib()
def compute(self, trajname, dirn='f'): continue_integ = ODEsystem.prepDirection(self, dirn) if self._dircode == -1: raise NotImplementedError, ( 'Backwards integration is not implemented') # validate spec if there exists a prior trajectory computation if self.defined: self.validateSpec() self.validateICs() self.clearWarnings() self.clearErrors() pnames = sortedDictKeys(self.pars) xnames = self._var_ixmap # ensures correct order # Check i.c.'s are well defined (finite) self.checkInitialConditions() if self._algparams['stiff']: methstr = 'bdf' methcode = 2 else: methstr = 'adams' methcode = 1 if self.haveJacobian(): haveJac = 1 else: haveJac = 0 if isinstance(self._algparams['atol'], list): if len(self._algparams['atol']) != self.dimension: raise ValueError, 'atol list must have same length as phase dimension' else: atol = self._algparams['atol'] self._algparams['atol'] = [ atol for dimix in xrange(self.dimension) ] indepdom0 = self.indepvariable.depdomain.get(0) indepdom1 = self.indepvariable.depdomain.get(1) if continue_integ: if self._tdata[0] != self._solver.t: print "Previous end time is %f" % self._solver.t raise ValueError, \ "Start time not correctly updated for continuing orbit" x0 = self._solver.y indepdom0 = self._solver.t else: x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) if self._solver._integrator is None: # Banded Jacobians not yet supported # # start a new integrator, because method may have been # switched self._solver.set_integrator( 'vode', method=methstr, rtol=self._algparams['rtol'], atol=self._algparams['atol'], nsteps=self._algparams['max_pts'], max_step=self._algparams['max_step'], min_step=self._algparams['min_step'], first_step=self._algparams['init_step'], with_jacobian=haveJac) # speed up repeated access to solver by making a temp name for it solver = self._solver else: # speed up repeated access to solver by making a temp name for it solver = self._solver solver.with_jacobian = haveJac # self.mu = lband # self.ml = uband solver.rtol = self._algparams['rtol'] solver.atol = self._algparams['atol'] solver.method = methcode # self.order = order solver.nsteps = self._algparams['max_pts'] solver.max_step = self._algparams['max_step'] solver.min_step = self._algparams['min_step'] solver.first_step = self._algparams['init_step'] solver.set_initial_value(x0, indepdom0) ## if self._dircode == 1: ## solver.set_initial_value(x0, indepdom0) ## else: ## solver.set_initial_value(x0, indepdom1) # wrap up each dictionary initial value as a singleton list alltData = [indepdom0] allxDataDict = dict(zip(xnames, map(listid, x0))) plist = sortedDictValues(self.pars) extralist = copy(plist) if self.inputs: # inputVarList is a list of Variables inames = sortedDictKeys(self.inputs) listend = self.numpars + len(self.inputs) inputVarList = sortedDictValues(self.inputs) ilist = _pollInputs(inputVarList, alltData[0] + self.globalt0, self.checklevel) else: ilist = [] inames = [] listend = self.numpars inputVarList = [] extralist.extend(ilist) solver.set_f_params(extralist) if haveJac: solver.set_jac_params(extralist) dt = self._algparams['init_step'] strict = self._algparams['strictdt'] # Make t mesh if not all(isfinite(self.indepvariable.depdomain.get())): print "Time domain was: ", self.indepvariable.depdomain.get() raise ValueError, "Ensure time domain is finite" if dt == indepdom1 - indepdom0: # single-step integration required tmesh = [indepdom0, indepdom1] else: notDone = True repeatTol = 10 count = 0 while notDone and count <= repeatTol: try: tmesh = self.indepvariable.depdomain.uniformSample( dt, strict=strict, avoidendpoints=self.checklevel > 2) notDone = False except AssertionError: count += 1 dt = dt / 3.0 if count == repeatTol: raise AssertionError, \ ("supplied time step is too large for selected time" " interval") if len(tmesh) <= 2: # safety net, in case too few points in mesh # too few points unless we can add endpoint if tmesh[-1] != indepdom1: # dt too large for tmesh to have more than one point tmesh.append(indepdom1) if not strict: # get actual time step used # don't use [0] in case avoided end points dt = tmesh[2] - tmesh[1] if self.eventstruct.query(['lowlevel']) != []: raise ValueError, "Only high level events can be passed to VODE" eventslist = self.eventstruct.query( ['highlevel', 'active', 'notvarlinked']) termevents = self.eventstruct.query(['term'], eventslist) # reverse time by reversing mesh doesn't work ## if self._dircode == -1: ## tmesh.reverse() tmesh.pop(0) # get rid of first entry for initial condition # per-iteration storage of variable data (initial values are irrelevant) xDataDict = {} xnames = self.funcspec.vars # storage of all auxiliary variable data allaDataDict = {} anames = self.funcspec.auxvars avals = apply(getattr(self, self.funcspec.auxspec[1]), [indepdom0, x0, extralist]) for aix in range(len(anames)): aname = anames[aix] try: allaDataDict[aname] = [avals[aix]] except IndexError: print "\nVODE generator: There was a problem evaluating " \ + "an auxiliary variable" print "Debug info: avals (length", len(avals), ") was ", avals print "Index out of range was ", aix print self.funcspec.auxspec[1] print hasattr(self, self.funcspec.auxspec[1]) print "Args were:", [indepdom0, x0, extralist] raise # Initialize signs of event detection objects at IC dataDict = copy(self.initialconditions) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = indepdom0 self.setEventICs(self.initialconditions, self.globalt0) if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames, ilist))) else: parsinps = self.pars if eventslist != []: evsflagged = self.eventstruct.pollHighLevelEvents( None, dataDict, parsinps, eventslist) if len(evsflagged) > 0: raise RuntimeError, "Some events flagged at initial condition" if continue_integ: # revert to prevprevsign, since prevsign changed after call self.eventstruct.resetHighLevelEvents(indepdom0, eventslist, 'prev') else: self.eventstruct.resetHighLevelEvents(indepdom0, eventslist) #, 'off') self.eventstruct.validateEvents(self.funcspec.vars + \ self.funcspec.auxvars + \ self.funcspec.inputs + \ ['t'], eventslist) # temp storage of first time at which terminal events found # (this is used for keeping the correct end point of new mesh) first_found_t = None # list of precise non-terminal events to be resolved after integration nontermprecevs = [] evnames = [ev[0] for ev in eventslist] lastevtime = {}.fromkeys(evnames, None) # initialize new event info dictionaries Evtimes = {} Evpoints = {} if continue_integ: for evname in evnames: try: # these are in global time, so convert to local time lastevtime[evname] = self.eventstruct.Evtimes[evname][ -1] - self.globalt0 except (IndexError, KeyError): # IndexError: Evtimes[evname] was None # KeyError: Evtimes does not have key evname pass for evname in evnames: Evtimes[evname] = [] Evpoints[evname] = [] # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = {} for xi in xrange(self.dimension): depdomains[xi] = self.variables[xnames[xi]].depdomain # Main integration loop num_points = 0 breakwhile = False while not breakwhile: try: new_t = tmesh.pop(0) # this destroys tmesh for future use except IndexError: break try: errcode = solver.integrate(new_t) except: print "Error calling right hand side function:" self.showSpec() print "Numerical traceback information (current state, " \ + "parameters, etc.)" print "in generator dictionary 'traceback'" self.traceback = { 'vars': dict(zip(xnames, solver.y)), 'pars': dict(zip(pnames, plist)), 'inputs': dict(zip(inames, ilist)), self.indepvariable.name: new_t } raise for xi in xrange(self.dimension): xDataDict[xnames[xi]] = solver.y[xi] if not self.contains(depdomains[xi], solver.y[xi], self.checklevel): self.warnings.append( (W_TERMSTATEBD, (solver.t, xnames[xi], solver.y[xi], depdomains[xi].get()))) breakwhile = True break # for loop if breakwhile: break avals = apply( getattr(self, self.funcspec.auxspec[1]), [new_t, sortedDictValues(xDataDict), extralist]) # Uncomment the following assertion for debugging # assert all([isfinite(a) for a in avals]), \ # "Some auxiliary variable values not finite" aDataDict = dict(zip(anames, avals)) if eventslist != []: dataDict = copy(xDataDict) dataDict.update(aDataDict) dataDict['t'] = new_t if self.inputs: parsinps = copy(self.pars) parsinps.update( dict( zip( inames, ## extralist[self.numpars:listend]))) _pollInputs(inputVarList, new_t + self.globalt0, self.checklevel)))) else: parsinps = self.pars evsflagged = self.eventstruct.pollHighLevelEvents( None, dataDict, parsinps, eventslist) ## print new_t, evsflagged # evsflagged = [ev for ev in evsflagged if solver.t-indepdom0 > ev[1].eventinterval] termevsflagged = filter(lambda e: e in evsflagged, termevents) nontermevsflagged = filter(lambda e: e not in termevsflagged, evsflagged) # register any non-terminating events in the warnings # list, unless they are 'precise' in which case flag # them to be resolved after integration completes if len(nontermevsflagged) > 0: evnames = [ev[0] for ev in nontermevsflagged] precEvts = self.eventstruct.query(['precise'], nontermevsflagged) prec_evnames = [e[0] for e in precEvts] # first register non-precise events nonprec_evnames = remain(evnames, prec_evnames) # only record events if they have not been previously # flagged within their event interval if nonprec_evnames != []: temp_names = [] for evname in nonprec_evnames: prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if solver.t - prevevt_time < e[1].eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: temp_names.append(evname) lastevtime[evname] = solver.t self.warnings.append( (W_NONTERMEVENT, (solver.t, temp_names))) for evname in temp_names: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) for e in precEvts: # only record events if they have not been previously # flagged within their event interval prevevt_time = lastevtime[e[0]] if prevevt_time is None: ignore_ev = False else: if solver.t - dt - prevevt_time < e[ 1].eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: nontermprecevs.append((solver.t - dt, solver.t, e)) # be conservative as to where the event is, so # that don't miss any events. lastevtime[e[0]] = solver.t - dt e[1].reset() #e[1].prevsign = None # do_termevs = [] if termevsflagged != []: # only record events if they have not been previously # flagged within their event interval for e in termevsflagged: prevevt_time = lastevtime[e[0]] ## print "Event %s flagged."%e[0] ## print " ... last time was ", prevevt_time ## print " ... event interval = ", e[1].eventinterval ## print " ... t = %f, dt = %f"%(solver.t, dt) if prevevt_time is None: ignore_ev = False else: ## print " ... comparison = %f < %f"%(solver.t-dt-prevevt_time, e[1].eventinterval) if solver.t - dt - prevevt_time < e[ 1].eventinterval: ignore_ev = True ## print "VODE ignore ev" else: ignore_ev = False if not ignore_ev: do_termevs.append(e) if len(do_termevs) > 0: # >= 1 active terminal event flagged at this time point if all([not ev[1].preciseFlag for ev in do_termevs]): # then none of the events specify greater accuracy # register the event in the warnings evnames = [ev[0] for ev in do_termevs] self.warnings.append((W_TERMEVENT, \ (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) breakwhile = True # break while loop after appending t, x else: # find which are the 'precise' events that flagged precEvts = self.eventstruct.query(['precise'], do_termevs) # these events have flagged once so eventdelay has # been used. now switch it off while finding event # precisely (should be redundant after change to # eventinterval and eventdelay parameters) evnames = [ev[0] for ev in precEvts] if first_found_t is None: ## print "first time round at", solver.t numtries = 0 first_found_t = solver.t restore_evts = deepcopy(precEvts) minbisectlimit = min( [ev[1].bisectlimit for ev in precEvts]) for ev in precEvts: ev[1].eventdelay = 0. else: numtries += 1 ## print "time round: ", numtries if numtries > minbisectlimit: self.warnings.append( (W_BISECTLIMIT, (solver.t, evnames))) breakwhile = True # find minimum eventtol in precEvts dt_min = min([e[1].eventtol for e in precEvts]) # get previous time point if len(alltData) >= 1: # take one step back -> told, which will # get dt added back to first new meshpoint # (solver.t is the step *after* the event was # detected) told = solver.t - dt else: raise ValueError("Event %s found too "%evnames[0]+\ "close to local start time: try decreasing " "initial step size (current size is " "%f @ t=%f)"%(dt,solver.t+self.globalt0)) if dt_min >= dt: ## print "Registering event:", dt_min, dt # register the event in the warnings self.warnings.append( (W_TERMEVENT, (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) # Cannot continue -- dt_min no smaller than # previous dt. If this is more than the first time # in this code then have found the event to within # the minimum 'precise' event's eventtol, o/w need # to set eventtol smaller. breakwhile = True # while loop if not breakwhile: dt_new = dt / 5.0 # calc new tmesh trangewidth = 2 * dt #first_found_t - told numpoints = int(math.ceil(trangewidth / dt_new)) # choose slightly smaller dt to fit trange exactly dt = trangewidth / numpoints tmesh = [ told + i * dt for i in xrange(1, numpoints + 1) ] # reset events according to new time mesh, # setting known previous event state to be their # "no event found" state self.eventstruct.resetHighLevelEvents(told, precEvts, state='off') # build new ic with last good values (at t=told) if len(alltData) > 1: new_ic = [allxDataDict[xname][-1] \ for xname in xnames] else: new_ic = x0 # reset integrator solver.set_initial_value(new_ic, told) extralist[self.numpars:listend] = \ [apply(f, [told+self.globalt0, self.checklevel]) \ for f in inputVarList] solver.set_f_params(extralist) # continue integrating over new mesh continue # while alltData.append(solver.t) for xi in xrange(self.dimension): allxDataDict[xnames[xi]].append(solver.y[xi]) for aix in xrange(len(anames)): aname = anames[aix] allaDataDict[aname].append(avals[aix]) num_points += 1 if not breakwhile: try: extralist[self.numpars:listend] = [apply(f, \ [solver.t+self.globalt0, self.checklevel]) \ for f in inputVarList] except ValueError: print 'External input call caused value out of range error:',\ 't = ', solver.t for f in inputVarList: if len(f.warnings): print 'External input variable %s out of range:' % f.name print ' t = ', repr(f.warnings[-1][0]), ', ', \ f.name, ' = ', repr(f.warnings[-1][1]) raise except AssertionError: print 'External input call caused t out of range error: t = ', \ solver.t raise solver.set_f_params(extralist) breakwhile = not solver.successful() # Check that any terminal events found terminated the code correctly if first_found_t is not None: # ... then terminal events were found. Those that were 'precise' had # their 'eventdelay' attribute temporarily set to 0. It now should # be restored. for evname1, ev1 in termevents: # restore_evts are copies of the originally flagged 'precise' # events for evname2, ev2 in restore_evts: if evname2 == evname1: ev1.eventdelay = ev2.eventdelay try: if self.warnings[-1][0] not in [W_TERMEVENT, W_TERMSTATEBD]: raise RuntimeError("Event finding code for terminal event " "failed in Generator " + self.name + \ ": try decreasing eventdelay or " "eventinterval below eventtol") except IndexError: print "Output stats: ", self.outputstats raise RuntimeError("Event finding failed in Generator " + \ self.name + ": try decreasing eventdelay " "or eventinterval below eventtol") # Package up computed trajectory in Variable variables # Add external inputs warnings to self.warnings, if any for f in inputVarList: for winfo in f.warnings: self.warnings.append( (W_NONTERMSTATEBD, (winfo[0], f.name, winfo[1], f.depdomain.get()))) # check for non-unique terminal event termcount = 0 for (w, i) in self.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: termcount += 1 if termcount > 1: self.errors.append((E_NONUNIQUETERM, (alltData[-1], i[1]))) # uncomment the following lines for debugging # assert len(alltData) == len(allxDataDict.values()[0]) \ # == len(allaDataDict.values()[0]), "Output data size mismatch" # for val_list in allaDataDict.values(): # assert all([isfinite(x) for x in val_list]) # Create variables (self.variables contains no actual data) # These versions of the variables are only final if no non-terminal # events need to be inserted. variables = copyVarDict(self.variables) for x in xnames: if len(alltData) > 1: variables[x] = Variable(interp1d(alltData, allxDataDict[x]), 't', x, x) else: print "Error in Generator:", self.name print "t = ", alltData print "x = ", allxDataDict raise PyDSTool_ValueError, "Fewer than 2 data points computed" for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) else: print "Error in Generator:", self.name print "t = ", alltData print "x = ", allxDataDict raise PyDSTool_ValueError, "Fewer than 2 data points computed" # Resolve non-terminal 'precise' events that were flagged, using the # variables created. Then, add them to a new version of the variables. ntpe_tdict = {} for (et0, et1, e) in nontermprecevs: lost_evt = False search_dt = max((et1 - et0) / 5, e[1].eventinterval) et_precise_list = e[1].searchForEvents(trange=[et0, et1], dt=search_dt, checklevel=self.checklevel, parDict=self.pars, vars=variables, inputs=self.inputs, abseps=self._abseps, eventdelay=False, globalt0=self.globalt0) if et_precise_list == []: lost_evt = True for et_precise in et_precise_list: if et_precise[0] is not None: if et_precise[0] in ntpe_tdict: # add event name at this time (that already exists in the dict) ntpe_tdict[et_precise[0]].append(e[0]) else: # add event name at this time (when time is not already in dict) ntpe_tdict[et_precise[0]] = [e[0]] else: lost_evt = True if lost_evt: raise PyDSTool_ExistError, \ ("Internal error: A non-terminal, 'precise' event '" +e[0]+"' was lost after integration!") # add non-terminal event points to variables if ntpe_tdict != {}: # find indices of times at which event times will be inserted tix = 0 evts = ntpe_tdict.keys() evts.sort() for evix in xrange(len(evts)): evt = evts[evix] evnames = ntpe_tdict[evt] self.warnings.append((W_NONTERMEVENT, (evt, evnames))) xval = [variables[x](evt) for x in xnames] for evname in evnames: Evtimes[evname].append(evt) Evpoints[evname].append(array(xval)) tcond = less_equal(alltData[tix:], evt).tolist() try: tix = tcond.index(0) + tix # lowest index for t > evt do_insert = (alltData[tix - 1] != evt) except ValueError: # evt = last t value so no need to add it do_insert = False if do_insert: alltData.insert(tix, evt) xvaldict = dict(zip(xnames, xval)) for x in xnames: allxDataDict[x].insert(tix, xvaldict[x]) for a in anames: allaDataDict[a].insert(tix, variables[a](evt)) for x in xnames: variables[x] = Variable(interp1d(alltData, allxDataDict[x]), 't', x, x) for a in anames: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) self.outputstats = { 'last_step': dt, 'num_fcns': num_points, 'num_steps': num_points, 'errorStatus': errcode } if solver.successful(): self.validateSpec() for evname, evtlist in Evtimes.iteritems(): try: self.eventstruct.Evtimes[evname].extend([et+self.globalt0 \ for et in evtlist]) except KeyError: self.eventstruct.Evtimes[evname] = [et+self.globalt0 \ for et in evtlist] # build event pointset information (reset previous trajectory's) self.trajevents = {} for (evname, ev) in eventslist: evpt = Evpoints[evname] if evpt == []: self.trajevents[evname] = None else: evpt = transpose(array(evpt)) self.trajevents[evname] = Pointset({ 'coordnames': xnames, 'indepvarname': 't', 'coordarray': evpt, 'indepvararray': Evtimes[evname], 'indepvartype': Float }) self.defined = True return Trajectory(trajname, variables.values(), self.globalt0, self.checklevel) else: try: self.errors.append( (E_COMPUTFAIL, (solver.t, self._errorcodes[errcode]))) except TypeError: # e.g. when errcode has been used to return info list print "Error information: ", errcode self.errors.append( (E_COMPUTFAIL, (solver.t, self._errorcodes[0]))) self.defined = False
def compute(self, trajname, dirn='f'): continue_integ = ODEsystem.prepDirection(self, dirn) if self._dircode == -1: raise NotImplementedError, ('Backwards integration is not implemented') # validate spec if there exists a prior trajectory computation if self.defined: self.validateSpec() self.validateICs() self.clearWarnings() self.clearErrors() pnames = sortedDictKeys(self.pars) xnames = self._var_ixmap # ensures correct order # Check i.c.'s are well defined (finite) self.checkInitialConditions() if self._algparams['stiff']: methstr = 'bdf' methcode = 2 else: methstr = 'adams' methcode = 1 if self.haveJacobian(): haveJac = 1 else: haveJac = 0 if isinstance(self._algparams['atol'], list): if len(self._algparams['atol']) != self.dimension: raise ValueError, 'atol list must have same length as phase dimension' else: atol = self._algparams['atol'] self._algparams['atol'] = [atol for dimix in xrange(self.dimension)] indepdom0 = self.indepvariable.depdomain.get(0) indepdom1 = self.indepvariable.depdomain.get(1) if continue_integ: if self._tdata[0] != self._solver.t: print "Previous end time is %f"%self._solver.t raise ValueError, \ "Start time not correctly updated for continuing orbit" x0 = self._solver.y indepdom0 = self._solver.t else: x0 = sortedDictValues(self.initialconditions, self.funcspec.vars) if self._solver._integrator is None: # Banded Jacobians not yet supported # # start a new integrator, because method may have been # switched self._solver.set_integrator('vode', method=methstr, rtol=self._algparams['rtol'], atol=self._algparams['atol'], nsteps=self._algparams['max_pts'], max_step=self._algparams['max_step'], min_step=self._algparams['min_step'], first_step=self._algparams['init_step'], with_jacobian=haveJac) # speed up repeated access to solver by making a temp name for it solver = self._solver else: # speed up repeated access to solver by making a temp name for it solver = self._solver solver.with_jacobian = haveJac # self.mu = lband # self.ml = uband solver.rtol = self._algparams['rtol'] solver.atol = self._algparams['atol'] solver.method = methcode # self.order = order solver.nsteps = self._algparams['max_pts'] solver.max_step = self._algparams['max_step'] solver.min_step = self._algparams['min_step'] solver.first_step = self._algparams['init_step'] solver.set_initial_value(x0, indepdom0) ## if self._dircode == 1: ## solver.set_initial_value(x0, indepdom0) ## else: ## solver.set_initial_value(x0, indepdom1) # wrap up each dictionary initial value as a singleton list alltData = [indepdom0] allxDataDict = dict(zip(xnames, map(listid, x0))) plist = sortedDictValues(self.pars) extralist = copy(plist) if self.inputs: # inputVarList is a list of Variables inames = sortedDictKeys(self.inputs) listend = self.numpars + len(self.inputs) inputVarList = sortedDictValues(self.inputs) ilist = _pollInputs(inputVarList, alltData[0]+self.globalt0, self.checklevel) else: ilist = [] inames = [] listend = self.numpars inputVarList = [] extralist.extend(ilist) solver.set_f_params(extralist) if haveJac: solver.set_jac_params(extralist) dt = self._algparams['init_step'] strict = self._algparams['strictdt'] # Make t mesh if not all(isfinite(self.indepvariable.depdomain.get())): print "Time domain was: ", self.indepvariable.depdomain.get() raise ValueError, "Ensure time domain is finite" if dt == indepdom1 - indepdom0: # single-step integration required tmesh = [indepdom0, indepdom1] else: notDone = True repeatTol = 10 count = 0 while notDone and count <= repeatTol: try: tmesh = self.indepvariable.depdomain.uniformSample(dt, strict=strict, avoidendpoints=self.checklevel>2) notDone = False except AssertionError: count += 1 dt = dt/3.0 if count == repeatTol: raise AssertionError, \ ("supplied time step is too large for selected time" " interval") if len(tmesh)<=2: # safety net, in case too few points in mesh # too few points unless we can add endpoint if tmesh[-1] != indepdom1: # dt too large for tmesh to have more than one point tmesh.append(indepdom1) if not strict: # get actual time step used # don't use [0] in case avoided end points dt = tmesh[2]-tmesh[1] if self.eventstruct.query(['lowlevel']) != []: raise ValueError, "Only high level events can be passed to VODE" eventslist = self.eventstruct.query(['highlevel', 'active', 'notvarlinked']) termevents = self.eventstruct.query(['term'], eventslist) # reverse time by reversing mesh doesn't work ## if self._dircode == -1: ## tmesh.reverse() tmesh.pop(0) # get rid of first entry for initial condition # per-iteration storage of variable data (initial values are irrelevant) xDataDict = {} xnames = self.funcspec.vars # storage of all auxiliary variable data allaDataDict = {} anames = self.funcspec.auxvars avals = apply(getattr(self,self.funcspec.auxspec[1]), [indepdom0, x0, extralist]) for aix in range(len(anames)): aname = anames[aix] try: allaDataDict[aname] = [avals[aix]] except IndexError: print "\nVODE generator: There was a problem evaluating " \ + "an auxiliary variable" print "Debug info: avals (length", len(avals), ") was ", avals print "Index out of range was ", aix print self.funcspec.auxspec[1] print hasattr(self, self.funcspec.auxspec[1]) print "Args were:", [indepdom0, x0, extralist] raise # Initialize signs of event detection objects at IC dataDict = copy(self.initialconditions) dataDict.update(dict(zip(anames, avals))) dataDict['t'] = indepdom0 self.setEventICs(self.initialconditions, self.globalt0) if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames,ilist))) else: parsinps = self.pars if eventslist != []: evsflagged = self.eventstruct.pollHighLevelEvents(None, dataDict, parsinps, eventslist) if len(evsflagged) > 0: raise RuntimeError, "Some events flagged at initial condition" if continue_integ: # revert to prevprevsign, since prevsign changed after call self.eventstruct.resetHighLevelEvents(indepdom0, eventslist, 'prev') else: self.eventstruct.resetHighLevelEvents(indepdom0, eventslist) #, 'off') self.eventstruct.validateEvents(self.funcspec.vars + \ self.funcspec.auxvars + \ self.funcspec.inputs + \ ['t'], eventslist) # temp storage of first time at which terminal events found # (this is used for keeping the correct end point of new mesh) first_found_t = None # list of precise non-terminal events to be resolved after integration nontermprecevs = [] evnames = [ev[0] for ev in eventslist] lastevtime = {}.fromkeys(evnames, None) # initialize new event info dictionaries Evtimes = {} Evpoints = {} if continue_integ: for evname in evnames: try: # these are in global time, so convert to local time lastevtime[evname] = self.eventstruct.Evtimes[evname][-1]-self.globalt0 except (IndexError, KeyError): # IndexError: Evtimes[evname] was None # KeyError: Evtimes does not have key evname pass for evname in evnames: Evtimes[evname] = [] Evpoints[evname] = [] # temp storage for repeatedly used object attributes (for lookup efficiency) depdomains = {} for xi in xrange(self.dimension): depdomains[xi] = self.variables[xnames[xi]].depdomain # Main integration loop num_points = 0 breakwhile = False while not breakwhile: try: new_t = tmesh.pop(0) # this destroys tmesh for future use except IndexError: break try: errcode = solver.integrate(new_t) except: print "Error calling right hand side function:" self.showSpec() print "Numerical traceback information (current state, " \ + "parameters, etc.)" print "in generator dictionary 'traceback'" self.traceback = {'vars': dict(zip(xnames,solver.y)), 'pars': dict(zip(pnames,plist)), 'inputs': dict(zip(inames,ilist)), self.indepvariable.name: new_t} raise for xi in xrange(self.dimension): xDataDict[xnames[xi]] = solver.y[xi] if not self.contains(depdomains[xi], solver.y[xi], self.checklevel): self.warnings.append((W_TERMSTATEBD, (solver.t, xnames[xi],solver.y[xi], depdomains[xi].get()))) breakwhile = True break # for loop if breakwhile: break avals = apply(getattr(self,self.funcspec.auxspec[1]), [new_t, sortedDictValues(xDataDict), extralist]) # Uncomment the following assertion for debugging # assert all([isfinite(a) for a in avals]), \ # "Some auxiliary variable values not finite" aDataDict = dict(zip(anames,avals)) if eventslist != []: dataDict = copy(xDataDict) dataDict.update(aDataDict) dataDict['t'] = new_t if self.inputs: parsinps = copy(self.pars) parsinps.update(dict(zip(inames, ## extralist[self.numpars:listend]))) _pollInputs(inputVarList, new_t+self.globalt0, self.checklevel)))) else: parsinps = self.pars evsflagged = self.eventstruct.pollHighLevelEvents(None, dataDict, parsinps, eventslist) ## print new_t, evsflagged # evsflagged = [ev for ev in evsflagged if solver.t-indepdom0 > ev[1].eventinterval] termevsflagged = filter(lambda e: e in evsflagged, termevents) nontermevsflagged = filter(lambda e: e not in termevsflagged, evsflagged) # register any non-terminating events in the warnings # list, unless they are 'precise' in which case flag # them to be resolved after integration completes if len(nontermevsflagged) > 0: evnames = [ev[0] for ev in nontermevsflagged] precEvts = self.eventstruct.query(['precise'], nontermevsflagged) prec_evnames = [e[0] for e in precEvts] # first register non-precise events nonprec_evnames = remain(evnames, prec_evnames) # only record events if they have not been previously # flagged within their event interval if nonprec_evnames != []: temp_names = [] for evname in nonprec_evnames: prevevt_time = lastevtime[evname] if prevevt_time is None: ignore_ev = False else: if solver.t-prevevt_time < e[1].eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: temp_names.append(evname) lastevtime[evname] = solver.t self.warnings.append((W_NONTERMEVENT, (solver.t, temp_names))) for evname in temp_names: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) for e in precEvts: # only record events if they have not been previously # flagged within their event interval prevevt_time = lastevtime[e[0]] if prevevt_time is None: ignore_ev = False else: if solver.t-dt-prevevt_time < e[1].eventinterval: ignore_ev = True else: ignore_ev = False if not ignore_ev: nontermprecevs.append((solver.t-dt, solver.t,e)) # be conservative as to where the event is, so # that don't miss any events. lastevtime[e[0]] = solver.t-dt e[1].reset() #e[1].prevsign = None # do_termevs = [] if termevsflagged != []: # only record events if they have not been previously # flagged within their event interval for e in termevsflagged: prevevt_time = lastevtime[e[0]] ## print "Event %s flagged."%e[0] ## print " ... last time was ", prevevt_time ## print " ... event interval = ", e[1].eventinterval ## print " ... t = %f, dt = %f"%(solver.t, dt) if prevevt_time is None: ignore_ev = False else: ## print " ... comparison = %f < %f"%(solver.t-dt-prevevt_time, e[1].eventinterval) if solver.t-dt-prevevt_time < e[1].eventinterval: ignore_ev = True ## print "VODE ignore ev" else: ignore_ev = False if not ignore_ev: do_termevs.append(e) if len(do_termevs) > 0: # >= 1 active terminal event flagged at this time point if all([not ev[1].preciseFlag for ev in do_termevs]): # then none of the events specify greater accuracy # register the event in the warnings evnames = [ev[0] for ev in do_termevs] self.warnings.append((W_TERMEVENT, \ (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) breakwhile = True # break while loop after appending t, x else: # find which are the 'precise' events that flagged precEvts = self.eventstruct.query(['precise'], do_termevs) # these events have flagged once so eventdelay has # been used. now switch it off while finding event # precisely (should be redundant after change to # eventinterval and eventdelay parameters) evnames = [ev[0] for ev in precEvts] if first_found_t is None: ## print "first time round at", solver.t numtries = 0 first_found_t = solver.t restore_evts = deepcopy(precEvts) minbisectlimit = min([ev[1].bisectlimit for ev in precEvts]) for ev in precEvts: ev[1].eventdelay = 0. else: numtries += 1 ## print "time round: ", numtries if numtries > minbisectlimit: self.warnings.append((W_BISECTLIMIT, (solver.t, evnames))) breakwhile = True # find minimum eventtol in precEvts dt_min = min([e[1].eventtol for e in precEvts]) # get previous time point if len(alltData)>=1: # take one step back -> told, which will # get dt added back to first new meshpoint # (solver.t is the step *after* the event was # detected) told = solver.t-dt else: raise ValueError("Event %s found too "%evnames[0]+\ "close to local start time: try decreasing " "initial step size (current size is " "%f @ t=%f)"%(dt,solver.t+self.globalt0)) if dt_min >= dt: ## print "Registering event:", dt_min, dt # register the event in the warnings self.warnings.append((W_TERMEVENT, (solver.t, evnames))) for evname in evnames: Evtimes[evname].append(solver.t) Evpoints[evname].append(solver.y) # Cannot continue -- dt_min no smaller than # previous dt. If this is more than the first time # in this code then have found the event to within # the minimum 'precise' event's eventtol, o/w need # to set eventtol smaller. breakwhile = True # while loop if not breakwhile: dt_new = dt/5.0 # calc new tmesh trangewidth = 2*dt #first_found_t - told numpoints = int(math.ceil(trangewidth/dt_new)) # choose slightly smaller dt to fit trange exactly dt = trangewidth/numpoints tmesh = [told + i*dt for i in xrange(1, numpoints+1)] # reset events according to new time mesh, # setting known previous event state to be their # "no event found" state self.eventstruct.resetHighLevelEvents(told, precEvts, state='off') # build new ic with last good values (at t=told) if len(alltData)>1: new_ic = [allxDataDict[xname][-1] \ for xname in xnames] else: new_ic = x0 # reset integrator solver.set_initial_value(new_ic, told) extralist[self.numpars:listend] = \ [apply(f, [told+self.globalt0, self.checklevel]) \ for f in inputVarList] solver.set_f_params(extralist) # continue integrating over new mesh continue # while alltData.append(solver.t) for xi in xrange(self.dimension): allxDataDict[xnames[xi]].append(solver.y[xi]) for aix in xrange(len(anames)): aname = anames[aix] allaDataDict[aname].append(avals[aix]) num_points += 1 if not breakwhile: try: extralist[self.numpars:listend] = [apply(f, \ [solver.t+self.globalt0, self.checklevel]) \ for f in inputVarList] except ValueError: print 'External input call caused value out of range error:',\ 't = ', solver.t for f in inputVarList: if len(f.warnings): print 'External input variable %s out of range:' % f.name print ' t = ', repr(f.warnings[-1][0]), ', ', \ f.name, ' = ', repr(f.warnings[-1][1]) raise except AssertionError: print 'External input call caused t out of range error: t = ', \ solver.t raise solver.set_f_params(extralist) breakwhile = not solver.successful() # Check that any terminal events found terminated the code correctly if first_found_t is not None: # ... then terminal events were found. Those that were 'precise' had # their 'eventdelay' attribute temporarily set to 0. It now should # be restored. for evname1, ev1 in termevents: # restore_evts are copies of the originally flagged 'precise' # events for evname2, ev2 in restore_evts: if evname2 == evname1: ev1.eventdelay = ev2.eventdelay try: if self.warnings[-1][0] not in [W_TERMEVENT, W_TERMSTATEBD]: raise RuntimeError("Event finding code for terminal event " "failed in Generator " + self.name + \ ": try decreasing eventdelay or " "eventinterval below eventtol") except IndexError: print "Output stats: ", self.outputstats raise RuntimeError("Event finding failed in Generator " + \ self.name + ": try decreasing eventdelay " "or eventinterval below eventtol") # Package up computed trajectory in Variable variables # Add external inputs warnings to self.warnings, if any for f in inputVarList: for winfo in f.warnings: self.warnings.append((W_NONTERMSTATEBD, (winfo[0], f.name, winfo[1], f.depdomain.get()))) # check for non-unique terminal event termcount = 0 for (w,i) in self.warnings: if w == W_TERMEVENT or w == W_TERMSTATEBD: termcount += 1 if termcount > 1: self.errors.append((E_NONUNIQUETERM, (alltData[-1], i[1]))) # uncomment the following lines for debugging # assert len(alltData) == len(allxDataDict.values()[0]) \ # == len(allaDataDict.values()[0]), "Output data size mismatch" # for val_list in allaDataDict.values(): # assert all([isfinite(x) for x in val_list]) # Create variables (self.variables contains no actual data) # These versions of the variables are only final if no non-terminal # events need to be inserted. variables = copyVarDict(self.variables) for x in xnames: if len(alltData) > 1: variables[x] = Variable(interp1d(alltData, allxDataDict[x]), 't', x, x) else: print "Error in Generator:", self.name print "t = ", alltData print "x = ", allxDataDict raise PyDSTool_ValueError, "Fewer than 2 data points computed" for a in anames: if len(alltData) > 1: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) else: print "Error in Generator:", self.name print "t = ", alltData print "x = ", allxDataDict raise PyDSTool_ValueError, "Fewer than 2 data points computed" # Resolve non-terminal 'precise' events that were flagged, using the # variables created. Then, add them to a new version of the variables. ntpe_tdict = {} for (et0,et1,e) in nontermprecevs: lost_evt = False search_dt = max((et1-et0)/5,e[1].eventinterval) et_precise_list = e[1].searchForEvents(trange=[et0,et1], dt=search_dt, checklevel=self.checklevel, parDict=self.pars, vars=variables, inputs=self.inputs, abseps=self._abseps, eventdelay=False, globalt0=self.globalt0) if et_precise_list == []: lost_evt = True for et_precise in et_precise_list: if et_precise[0] is not None: if et_precise[0] in ntpe_tdict: # add event name at this time (that already exists in the dict) ntpe_tdict[et_precise[0]].append(e[0]) else: # add event name at this time (when time is not already in dict) ntpe_tdict[et_precise[0]] = [e[0]] else: lost_evt = True if lost_evt: raise PyDSTool_ExistError, \ ("Internal error: A non-terminal, 'precise' event '" +e[0]+"' was lost after integration!") # add non-terminal event points to variables if ntpe_tdict != {}: # find indices of times at which event times will be inserted tix = 0 evts = ntpe_tdict.keys() evts.sort() for evix in xrange(len(evts)): evt = evts[evix] evnames = ntpe_tdict[evt] self.warnings.append((W_NONTERMEVENT, (evt, evnames))) xval = [variables[x](evt) for x in xnames] for evname in evnames: Evtimes[evname].append(evt) Evpoints[evname].append(array(xval)) tcond = less_equal(alltData[tix:], evt).tolist() try: tix = tcond.index(0) + tix # lowest index for t > evt do_insert = (alltData[tix-1] != evt) except ValueError: # evt = last t value so no need to add it do_insert = False if do_insert: alltData.insert(tix, evt) xvaldict = dict(zip(xnames, xval)) for x in xnames: allxDataDict[x].insert(tix, xvaldict[x]) for a in anames: allaDataDict[a].insert(tix, variables[a](evt)) for x in xnames: variables[x] = Variable(interp1d(alltData, allxDataDict[x]), 't', x, x) for a in anames: variables[a] = Variable(interp1d(alltData, allaDataDict[a]), 't', a, a) self.outputstats = {'last_step': dt, 'num_fcns': num_points, 'num_steps': num_points, 'errorStatus': errcode } if solver.successful(): self.validateSpec() for evname, evtlist in Evtimes.iteritems(): try: self.eventstruct.Evtimes[evname].extend([et+self.globalt0 \ for et in evtlist]) except KeyError: self.eventstruct.Evtimes[evname] = [et+self.globalt0 \ for et in evtlist] # build event pointset information (reset previous trajectory's) self.trajevents = {} for (evname, ev) in eventslist: evpt = Evpoints[evname] if evpt == []: self.trajevents[evname] = None else: evpt = transpose(array(evpt)) self.trajevents[evname] = Pointset({'coordnames': xnames, 'indepvarname': 't', 'coordarray': evpt, 'indepvararray': Evtimes[evname], 'indepvartype': Float}) self.defined = True return Trajectory(trajname, variables.values(), self.globalt0, self.checklevel) else: try: self.errors.append((E_COMPUTFAIL, (solver.t, self._errorcodes[errcode]))) except TypeError: # e.g. when errcode has been used to return info list print "Error information: ", errcode self.errors.append((E_COMPUTFAIL, (solver.t, self._errorcodes[0]))) self.defined = False