def set_integrators(self): """ Generate continuous time high-precision integrators. """ # Set CasADi variables x = ca.MX.sym('x', 4) u = ca.MX.sym('u', 1) # Integration method - integrator options an be adjusted options = { "abstol": 1e-5, "reltol": 1e-9, "max_num_steps": 100, "tf": self.dt } # Create linear dynamics integrator dae = {'x': x, 'ode': self.model(x, u), 'p': ca.vertcat(u)} self.Integrator_lin = ca.integrator('integrator_lin', 'cvodes', dae, options) # Create nonlinear dynamics integrator dae_nl = {'x': x, 'ode': self.model_nl(x, u), 'p': ca.vertcat(u)} self.Integrator = ca.integrator('integrator', 'cvodes', dae_nl, options)
def __prepare_dynamics(self, nlp): """ Builds CasaDI dynamics function. :param dynamics_func: A selected method handler of the class dynamics.Dynamics. :param ode_solver: Name of chosen ode, available in OdeSolver enum class. """ dynamics = nlp["dynamics_func"] ode_opt = {"t0": 0, "tf": nlp["dt"]} if nlp["ode_solver"] == OdeSolver.RK or nlp[ "ode_solver"] == OdeSolver.COLLOCATION: ode_opt["number_of_finite_elements"] = 5 ode = { "x": nlp["x"], "p": nlp["u"], "ode": dynamics(nlp["x"], nlp["u"]) } nlp["dynamics"] = [] nlp["par_dynamics"] = {} if nlp["ode_solver"] == OdeSolver.RK: ode_opt["idx"] = 0 ode["ode"] = dynamics if "external_forces" in nlp: for idx in range(len(nlp["external_forces"])): ode_opt["idx"] = idx nlp["dynamics"].append(RK4(ode, ode_opt)) else: nlp["dynamics"].append(RK4(ode, ode_opt)) elif nlp["ode_solver"] == OdeSolver.COLLOCATION: if isinstance(nlp["tf"], casadi.MX): raise RuntimeError( "OdeSolver.COLLOCATION cannot be used while optimizing the time parameter" ) if "external_forces" in nlp: raise RuntimeError( "COLLOCATION cannot be used with external_forces") nlp["dynamics"].append( casadi.integrator("integrator", "collocation", ode, ode_opt)) elif nlp["ode_solver"] == OdeSolver.CVODES: if isinstance(nlp["tf"], casadi.MX): raise RuntimeError( "OdeSolver.CVODES cannot be used while optimizing the time parameter" ) if "external_forces" in nlp: raise RuntimeError( "CVODES cannot be used with external_forces") nlp["dynamics"].append( casadi.integrator("integrator", "cvodes", ode, ode_opt)) if len(nlp["dynamics"]) == 1: if self.nb_threads > 1: nlp["par_dynamics"] = nlp["dynamics"][0].map( nlp["ns"], "thread", self.nb_threads) nlp["dynamics"] = nlp["dynamics"] * nlp["ns"]
def simulate(x0: State, f_control, p: Parameters, t0: float, tf: float, dt: float): """ Simulate the aircraft for a given control function and initial state. Parameters: x0: initial state (see State) f_control: A function of the form f(t, x), which returns the control u p: Aircraft parameters t0: initial time tf: fintal time dt: The discrete sampling time of the controller. """ xs = ca.MX.sym('x', 16) x = State.from_casadi(xs) us = ca.MX.sym('u', 4) u = Control.from_casadi(us) dae = {'x': xs, 'p': us, 'ode': dynamics(x, u, p).to_casadi()} F = ca.integrator('F', 'idas', dae, {'t0': 0, 'tf': dt, 'jit': True}) x = np.array(x0.to_casadi()).reshape(-1) u0 = f_control(t0, x0) u = np.array(u0.to_casadi()).reshape(-1) data = {'t': [0], 'x': [x]} t_vect = np.arange(t0, tf, dt) for t in t_vect: u0 = f_control(t, x) u = np.array(u0.to_casadi()).reshape(-1) x = np.array(F(x0=x, p=u)['xf']).reshape(-1) data['t'].append(t) data['x'].append(x) for k in data.keys(): data[k] = np.array(data[k]) return data
def calc_xs_full_ode(Ex, Ey, e_hat, y_hat, state_compressor): t = cs.SX.sym('t', 1) x_sym = cs.SX.sym('x', sc.nm) y_sym = cs.SX.sym('y', ny) e_sym = cs.SX.sym('e', sc.nr) Ex_sym = cs.SX.sym('Ex', sc.nr, sc.nm) Ey_sym = cs.SX.sym('Ex', sc.nr, Ey.shape[1]) v = e_sym * v_star * (1 + Ex_sym @ cs.log(x_sym / sc.x_star) + Ey @ np.log(y_hat)) v_fun = cs.Function('v', [x_sym, e_sym, Ex_sym], [v]) ode = cs.mtimes(cs.DM(N), v_fun(x_sym, e_sym, Ex_sym)) integrator = cs.integrator( 'full', 'cvodes', { 't': t, 'x': x_sym, 'p': cs.vertcat(e_sym, Ex_sym.reshape((-1, 1))), 'ode': ode, }, { 'tf': 2000, 'regularity_check': True, }) p = np.hstack([e_hat, Ex.reshape((-1, 1), order='F').squeeze()]) xs = np.array(integrator(x0=sc.x_star, p=p)['xf']).flatten() return xs
def test_parallelization(): N = 300 x = cd.MX.sym('x'); z = cd.MX.sym('z'); p = cd.MX.sym('p') dae = {'x': x, 'z': z, 'p': p, 'ode': 0, 'alg': z} func = cd.integrator('func', 'idas', dae) F = func.map(N, 'openmp') F(x0=0, z0=0, p=0)
def solve_ode(self, ts=None): """ Solve the ODE using casadi's CVODES wrapper to ensure that the collocated dynamics match the error-controlled dynamics of the ODE """ if ts is None: ts = self.ts check_err = True else: check_err = False ts.sort() # Assert ts is increasing integrator = cs.integrator('int', 'cvodes', self._model_dict, { 'grid': ts, 'output_t0': True, }) x_sim = np.array(integrator(x0=self.sol[0], p=self.var.p_op)['xf']).T if check_err: err = ((self.sol - x_sim).mean(0) / (self.sol.mean(0))).mean() if err > 1E-3: warn('Collocation does not match ODE Solution: ' '{:.2%} Error'.format(err)) return x_sim
def _simulate_with_casadi_no_inputs(self, initcon, tsim, integrator, integrator_options): # Old way (10 times faster, but can't incorporate time # varying parameters/controls) xalltemp = [self._templatemap[i] for i in self._diffvars] xall = casadi.vertcat(*xalltemp) odealltemp = [convert_pyomo2casadi(self._rhsdict[i]) for i in self._derivlist] odeall = casadi.vertcat(*odealltemp) dae = {'x': xall, 'ode': odeall} if len(self._algvars) != 0: zalltemp = [self._templatemap[i] for i in self._simalgvars] zall = casadi.vertcat(*zalltemp) algalltemp = [convert_pyomo2casadi(i) for i in self._alglist] algall = casadi.vertcat(*algalltemp) dae['z'] = zall dae['alg'] = algall integrator_options['grid'] = tsim integrator_options['output_t0'] = True F = casadi.integrator('F', integrator, dae, integrator_options) sol = F(x0=initcon) profile = sol['xf'].full().T if len(self._algvars) != 0: algprofile = sol['zf'].full().T profile = np.concatenate((profile, algprofile), axis=1) return [tsim, profile]
def dynamics(x, u, data): """ System dynamics function (discrete time) """ # state derivative expression xdot = ca.vertcat( u['Vdot'] * (data['cA0'] - x['cA']) - arrhenius(x, u, data, 1) * x['cA'] - arrhenius(x, u, data, 3) * x['cA'] * x['cA'], -u['Vdot'] * x['cB'] + arrhenius(x, u, data, 1) * x['cA'] - arrhenius(x, u, data, 2) * x['cB'], u['Vdot'] * (data['theta0'] - x['theta']) - 1.0 / (data['rho'] * data['Cp']) * (arrhenius(x, u, data, 1) * x['cA'] * data['DH_AB'] + arrhenius(x, u, data, 2) * x['cB'] * data['DH_BC'] + arrhenius(x, u, data, 3) * x['cA'] * x['cA'] * data['DH_AD']) + data['kw'] * data['AR'] / (data['rho'] * data['Cp'] * data['VR']) * (x['thetaK'] - x['theta']), 1.0 / (data['mK'] * data['CPK']) * (u['QdotK'] + data['kw'] * data['AR'] * (x['theta'] - x['thetaK']))) # create ode for integrator ode = {'x': x, 'p': u, 'ode': xdot / 3600} return ca.integrator('F', data['integrator'], ode, { 'tf': data['ts'], 'number_of_finite_elements': data['num_steps'] })
def _create_integrator(self, options=None, integrator_type='implicit'): """ Create casadi.integrator for the DAESystem. :param dict options: casadi.integrator options :param str integrator_type: integrator type, options available are: 'cvodes', 'idas', 'implicit' (default, auto-select between 'idas' and 'cvodes'), 'rk', and 'collocation'. :return: """ if options is None: options = {} # Integrator Function name if 'name' in options: name = options.pop('name') else: name = 'integrator' # Load default options from the config file, if a 'options' dict was passed use that to override the default. for k in config.INTEGRATOR_OPTIONS: if k not in options: options[k] = config.INTEGRATOR_OPTIONS[k] # For backwards compatibility, previously option 'explicit' was a custom Runge-Kutta. CasADi now has it built-in if integrator_type == 'explicit': integrator_type = 'rk' # Return the specified integrator if integrator_type == 'implicit' and self.is_ode: return integrator(name, "cvodes", self.dae_system_dict, options) if integrator_type == 'implicit' and self.is_dae: return integrator(name, "idas", self.dae_system_dict, options) if integrator_type == 'rk4': if self.is_ode: return self._create_explicit_integrator( 'explicit_integrator', 'rk4', self.dae_system_dict, options) else: raise Exception( 'Explicit integrator not implemented for DAE systems') if integrator_type in ['rk', 'collocation', 'cvodes', 'idas']: return integrator(name, integrator_type, self.dae_system_dict, options) raise ValueError( "'integrator_type'={} not available. Options available are: 'cvodes', 'idas', implicit " "(default, auto-select between 'idas' and 'cvodes'), 'rk'," " and 'collocation'.".format(integrator_type))
def __init__(self, params): self.model = KinematicBicycleCar(N=params["N"], step=params["step"]) self.sys = self.model.getDae() self.cost = 0 self.Uk_prev = None self.indices_to_stop = None self.indices_to_start = None # hack simparams = { 'x': self.sys.x[0], 'p': self.sys.u[0], 'ode': self.sys.ode[0] } self.sim = ca.integrator('F', 'idas', simparams, { 't0': 0, 'tf': self.model.step }) # Set up collocation (from direct_collocation example) # Degree of interpolating polynomial self._d = 3 # Get collocation points tau_root = np.append(0, ca.collocation_points(self._d, 'legendre')) # Coefficients of the collocation equation self._C = np.zeros((self._d + 1, self._d + 1)) # Coefficients of the continuity equation self._D = np.zeros(self._d + 1) # Coefficients of the quadrature function self._B = np.zeros(self._d + 1) # Construct polynomial basis for j in range(self._d + 1): # Construct Lagrange polynomials to get the polynomial basis at the collocation point p = np.poly1d([1]) for r in range(self._d + 1): if r != j: p *= np.poly1d([1, -tau_root[r] ]) / (tau_root[j] - tau_root[r]) # Evaluate the polynomial at the final time to get the coefficients of the continuity equation self._D[j] = p(1.0) # Evaluate the time derivative of the polynomial at all collocation points to get the coefficients of the continuity equation pder = np.polyder(p) for r in range(self._d + 1): self._C[j, r] = pder(tau_root[r]) # Evaluate the integral of the polynomial to get the coefficients of the quadrature function pint = np.polyint(p) self._B[j] = pint(1.0)
def create_test_data(model, x_labels): ts = np.linspace(0, 100, 100) opts = {'grid': ts, 'output_t0': True} integrator = cs.integrator('integrator', 'cvodes', model, opts) xf = np.asarray(integrator(x0=[0., 0., 1.], p=[0.4, 0.05, 0.1])['xf']).T xf_noise = xf * (1 + .1 * np.random.randn(*xf.shape)) return pd.DataFrame(xf_noise, index=ts, columns=x_labels)
def _initialize_ekf_for_arrival_cost_update(self): self.hfcn = ca.Function("h", [self.x, self.c], [self.h]) self.H = self.hfcn.jac() ode = {"x": self.x, "p": ca.veccat(self.c, self.u, self.b, self.w), \ "ode": self.f} self.phi = ca.integrator("integrator", "cvodes", ode, \ {"t0": 0.0, "tf": self._timing.dt_day}) self.Phi = self.phi.jac()
def _initialize_polynomial_coefs(self): """ Setup radau polynomials and initialize the weight factor matricies """ self.col_vars['tau_root'] = [0] + cs.collocation_points( self.d, "radau") # Dimensionless time inside one control interval tau = cs.SX.sym("tau") # For all collocation points L = [[]] * (self.d + 1) for j in range(self.d + 1): # Construct Lagrange polynomials to get the polynomial basis at the # collocation point L[j] = 1 for r in range(self.d + 1): if r != j: L[j] *= ((tau - self.col_vars['tau_root'][r]) / (self.col_vars['tau_root'][j] - self.col_vars['tau_root'][r])) self.col_vars['lfcn'] = lfcn = cs.Function('lfcn', [tau], [cs.vertcat(*L)]) # Evaluate the polynomial at the final time to get the coefficients of # the continuity equation # Coefficients of the continuity equation self.col_vars['D'] = np.asarray(lfcn(1.0)).squeeze() # Evaluate the time derivative of the polynomial at all collocation # points to get the coefficients of the continuity equation tfcn = lfcn.tangent() # Coefficients of the collocation equation self.col_vars['C'] = np.zeros((self.d + 1, self.d + 1)) for r in range(self.d + 1): self.col_vars['C'][:, r] = np.asarray( tfcn(self.col_vars['tau_root'][r])[0]).squeeze() # Find weights for gaussian quadrature: approximate int_0^1 f(x) by # Sum( xtau = cs.SX.sym("xtau") Phi = [[]] * (self.d + 1) for j in range(self.d + 1): dae = dict(t=tau, x=xtau, ode=L[j]) tau_integrator = cs.integrator("integrator", "cvodes", dae, { 't0': 0., 'tf': 1 }) Phi[j] = np.asarray(tau_integrator(x0=0)['xf']) self.col_vars['Phi'] = np.array(Phi)
def createTimeDiscreteFun(ode, stepSize, NX, NU, integratorType='rk'): x = ca.SX.sym('x', NX) u = ca.SX.sym('u', NU) modelProps = {'x': x, 'p': u, 'ode': ode(x, u)} if integratorType == 'rk': return ca.Function('intg', [x, u], [rk4step(ode, x, u, stepSize)]) else: return lambda x0, u: ca.integrator('intg', integratorType, modelProps, {'tf': stepSize})( x0=x0, p=u)['xf']
def dynamics(x, u, data): """ System dynamics function (discrete time) """ # state derivative expression xdot = ca.vertcat( (data['F1'] * data['X1'] - data['F2'] * x['X2']) / data['M'], (data['F4'] - data['F5']) / data['C']) # create ode for integrator ode = {'x': x, 'p': u, 'ode': xdot} return [ca.integrator('F', 'collocation', ode, {'tf': 1}), ode]
def intg_builtin(self, f, X, U, P, Z): # A single CVODES step DT = MX.sym("DT") t = MX.sym("t") t0 = MX.sym("t0") res = f(x=X, u=U, p=P, t=t0+t*DT, z=Z) data = {'x': X, 'p': vertcat(U, DT, P, t0), 'z': Z, 't': t, 'ode': DT * res["ode"], 'quad': DT * res["quad"], 'alg': res["alg"]} options = dict(self.intg_options) if self.intg in ["collocation"]: options["number_of_finite_elements"] = 1 I = integrator('intg_cvodes', self.intg, data, options) res = I.call({'x0': X, 'p': vertcat(U, DT, P, t0)}) return Function('F', [X, U, t0, DT, P], [res["xf"], MX(), res["qf"]], ['x0', 'u', 't0', 'DT', 'p'], ['xf', 'poly_coeff','qf'])
def _setup_simulator(self): self._setup_model() dt = ca.MX.sym("dt") ode = {"x": self.x, "p": ca.veccat(dt, self.c, self.u, self.b, self.w), \ "ode": dt * self.f} self._integrator = ca.integrator("integrator", "cvodes", ode, \ {"t0": 0.0, "tf": 1.0}) self._remove_unserializable_attributes()
def _initialize_polynomial_coefs(self): """ Setup radau polynomials and initialize the weight factor matricies """ self.col_vars['tau_root'] = [0] + cs.collocation_points(self.d, "radau") # Dimensionless time inside one control interval tau = cs.SX.sym("tau") # For all collocation points L = [[]]*(self.d+1) for j in range(self.d+1): # Construct Lagrange polynomials to get the polynomial basis at the # collocation point L[j] = 1 for r in range(self.d+1): if r != j: L[j] *= ( (tau - self.col_vars['tau_root'][r]) / (self.col_vars['tau_root'][j] - self.col_vars['tau_root'][r])) self.col_vars['lfcn'] = lfcn = cs.Function( 'lfcn', [tau], [cs.vertcat(*L)]) # Evaluate the polynomial at the final time to get the coefficients of # the continuity equation # Coefficients of the continuity equation self.col_vars['D'] = np.asarray(lfcn(1.0)).squeeze() # Evaluate the time derivative of the polynomial at all collocation # points to get the coefficients of the continuity equation tfcn = lfcn.tangent() # Coefficients of the collocation equation self.col_vars['C'] = np.zeros((self.d+1, self.d+1)) for r in range(self.d+1): self.col_vars['C'][:,r] = np.asarray(tfcn(self.col_vars['tau_root'][r])[0]).squeeze() # Find weights for gaussian quadrature: approximate int_0^1 f(x) by # Sum( xtau = cs.SX.sym("xtau") Phi = [[]] * (self.d+1) for j in range(self.d+1): dae = dict(t=tau, x=xtau, ode=L[j]) tau_integrator = cs.integrator( "integrator", "cvodes", dae, {'t0':0., 'tf':1}) Phi[j] = np.asarray(tau_integrator(x0=0)['xf']) self.col_vars['Phi'] = np.array(Phi)
def get_integrator(self, model, t_eval, inputs): # Only set up problem once if model not in self.problems: y0 = model.y0 rhs = model.casadi_rhs algebraic = model.casadi_algebraic # When not in DEBUG mode (level=10), suppress warnings from CasADi if (pybamm.logger.getEffectiveLevel() == 10 or pybamm.settings.debug_mode is True): show_eval_warnings = True else: show_eval_warnings = False options = { **self.extra_options_setup, "grid": t_eval, "reltol": self.rtol, "abstol": self.atol, "output_t0": True, "show_eval_warnings": show_eval_warnings, } # set up and solve t = casadi.MX.sym("t") p = casadi.MX.sym("p", inputs.shape[0]) y_diff = casadi.MX.sym("y_diff", rhs(t_eval[0], y0, p).shape[0]) problem = {"t": t, "x": y_diff, "p": p} if algebraic(t_eval[0], y0, p).is_empty(): method = "cvodes" problem.update({"ode": rhs(t, y_diff, p)}) else: options["calc_ic"] = True method = "idas" y_alg = casadi.MX.sym("y_alg", algebraic(t_eval[0], y0, p).shape[0]) y_full = casadi.vertcat(y_diff, y_alg) problem.update({ "z": y_alg, "ode": rhs(t, y_full, p), "alg": algebraic(t, y_full, p), }) self.problems[model] = problem self.options[model] = options self.methods[model] = method else: # problem stays the same # just update options self.options[model]["grid"] = t_eval return casadi.integrator("F", self.methods[model], self.problems[model], self.options[model])
def __prepare_dynamics(self, nlp): """ Builds CasaDI dynamics function. :param dynamics_func: A selected method handler of the class dynamics.Dynamics. :param ode_solver: Name of chosen ode, available in OdeSolver enum class. """ dynamics = casadi.Function( "ForwardDyn", [self.symbolic_states, self.symbolic_controls], [ nlp["dynamics_func"](self.symbolic_states, self.symbolic_controls, nlp) ], ["x", "u"], ["xdot"], ).expand() # .map(nlp["ns"], "thread", 2) ode = { "x": nlp["x"], "p": nlp["u"], "ode": dynamics(nlp["x"], nlp["u"]) } ode_opt = {"t0": 0, "tf": nlp["dt"]} if nlp["ode_solver"] == OdeSolver.RK or nlp[ "ode_solver"] == OdeSolver.COLLOCATION: ode_opt["number_of_finite_elements"] = 5 if nlp["ode_solver"] == OdeSolver.RK: nlp["dynamics"] = casadi.integrator("integrator", "rk", ode, ode_opt) elif nlp["ode_solver"] == OdeSolver.COLLOCATION: nlp["dynamics"] = casadi.integrator("integrator", "collocation", ode, ode_opt) elif nlp["ode_solver"] == OdeSolver.CVODES: nlp["dynamics"] = casadi.integrator("integrator", "cvodes", ode, ode_opt)
def arclength(self, s): if s == 0: return 0.0 length = cas.integrator( 'integrator', 'rk', { 'x': cas.MX.sym('null'), 't': self._s, 'ode': 0, 'quad': cas.norm_2(cas.jacobian(self._expr, self._s)) }, { 't0': 0.0, 'tf': s }).call({})['qf'] return length
def set_integrators_augmented(self): """ Generate continuous time high-precision integrators. """ # Set CasADi variables u = ca.MX.sym('u', 4) options = {"abstol" : 1e-5, "reltol" : 1e-9, "max_num_steps": 100, "tf" : self.dt} # Create augmented system dynamics integrator x_ag = ca.MX.sym('x', 15) dae = {'x': x_ag, 'ode': self.model_nl_ag(x_ag,u), 'p':ca.vertcat(u)} self.Integrator_nl_ag = ca.integrator('integrator', 'cvodes', dae, options) print("Augmented integraters are set perfectly !")
def run_reaction(self,species_dict,reactions,t_i=0,t_f=20,t_step=0.1): # ODE and Bounds t= np.arange(t_i,t_f,t_step) ode = {'x':vertcat(*[v['sx'] for v in species_dict.values()]), 'ode':reactions} # Solving opts = {'grid':t, 'output_t0':True} F = cas.integrator('F','cvodes',ode,opts) results = F(x0=[v['Ci'] for v in species_dict.values()]) # Clean results dfresults = pd.DataFrame(np.array(results['xf']),index=species_dict.keys()).T.set_index(t) return dfresults, results
def dynamics(x, u, data, h=1.0): """ System dynamics formulation (discrete time) """ # state derivative xdot = ca.vertcat( data['v'] * x['ez'], data['v'] * x['ey'], -u['u'] * x['ey'] - data['rho'] * x['ez'] * (x['ez']**2 + x['ey']**2 - 1.0), u['u'] * x['ez'] - data['rho'] * x['ey'] * (x['ez']**2 + x['ey']**2 - 1.0)) # set-up ode system f = ca.Function('f', [x, u], [xdot]) ode = {'x': x, 'p': u, 'ode': f(x, u)} return ca.integrator('F', 'rk', ode, { 'tf': h, 'number_of_finite_elements': 50 })
def simulate(x0, u0, t0, tf, dt): xs = ca.MX.sym('x', 13) x = f16.State.from_casadi(xs) us = ca.MX.sym('u', 4) u = f16.Control.from_casadi(us) dae = {'x': xs, 'p': us, 'ode': f16.dynamics(x, u, p).to_casadi()} F = ca.integrator('F', 'idas', dae, {'t0': 0, 'tf': dt, 'jit': True}) x = np.array(x0.to_casadi()).reshape(-1) u = np.array(u0.to_casadi()).reshape(-1) data = {'t': [0], 'x': [x]} t_vect = np.arange(t0, tf, dt) for t in t_vect: x = np.array(F(x0=x, p=u)['xf']).reshape(-1) data['t'].append(t) data['x'].append(x) for k in data.keys(): data[k] = np.array(data[k]) return data
def integrator(self, ocp, nlp) -> list: """ The interface of the OdeSolver to the corresponding integrator Parameters ---------- ocp: OptimalControlProgram A reference to the ocp nlp: NonLinearProgram A reference to the nlp Returns ------- A list of integrators """ if not isinstance(ocp.cx(), casadi.MX): raise RuntimeError( "CVODES integrator can only be used with MX graphs") if len(ocp.v.params.size) != 0: raise RuntimeError( "CVODES cannot be used while optimizing parameters") if nlp.external_forces: raise RuntimeError( "CVODES cannot be used with external_forces") if nlp.control_type == ControlType.LINEAR_CONTINUOUS: raise RuntimeError( "CVODES cannot be used with piece-wise linear controls (only RK4)" ) if not isinstance(nlp.ode_solver, OdeSolver.RK4): raise RuntimeError("CVODES is only implemented with RK4") ode = { "x": nlp.x, "p": nlp.u, "ode": nlp.dynamics_func(nlp.x, nlp.u, nlp.p) } ode_opt = { "t0": 0, "tf": nlp.dt, "number_of_finite_elements": nlp.ode_solver.steps } return [casadi.integrator("integrator", "cvodes", ode, ode_opt)]
def get_integrator(self, model, t_eval, inputs): # Only set up problem once if model not in self.problems: y0 = model.y0 rhs = model.casadi_rhs algebraic = model.casadi_algebraic u_stacked = casadi.vertcat(*[x for x in inputs.values()]) options = { "grid": t_eval, "reltol": self.rtol, "abstol": self.atol, "output_t0": True, "max_num_steps": self.max_steps, } # set up and solve t = casadi.MX.sym("t") u = casadi.MX.sym("u", u_stacked.shape[0]) y_diff = casadi.MX.sym("y_diff", rhs(t_eval[0], y0, u).shape[0]) problem = {"t": t, "x": y_diff, "p": u} if algebraic(t_eval[0], y0, u).is_empty(): method = "cvodes" problem.update({"ode": rhs(t, y_diff, u)}) else: options["calc_ic"] = True method = "idas" y_alg = casadi.MX.sym("y_alg", algebraic(t_eval[0], y0, u).shape[0]) y_full = casadi.vertcat(y_diff, y_alg) problem.update({ "z": y_alg, "ode": rhs(t, y_full, u), "alg": algebraic(t, y_full, u), }) self.problems[model] = problem self.options[model] = options self.methods[model] = method else: # problem stays the same # just update options self.options[model]["grid"] = t_eval return casadi.integrator("F", self.methods[model], self.problems[model], self.options[model])
def setup_integrator(self, integration_method='cvodes', opts=None): """Setup casadi integrator.""" #: Generate Options for integration self.generate_opts(opts) #: Initialize states of the integrator self.fin['x0'] = self.dae.x self.fin['p'] = self.dae.u +\ self.dae.p + self.dae.c self.fin['z0'] = self.dae.z self.fin['rx0'] = cas.DM([]) self.fin['rp'] = cas.DM([]) self.fin['rz0'] = cas.DM([]) self.dae.print_dae() #: Set up the integrator self.integrator = cas.integrator('pendulum', integration_method, self.dae.generate_dae(), self.opts) return self.integrator
def integrate_casadi(self, problem, y0, t_eval, mass_matrix=None): """ Solve a DAE model defined by residuals with initial conditions y0. Parameters ---------- residuals : method A function that takes in t, y and ydot and returns the residuals of the equations y0 : numeric type The initial conditions t_eval : numeric type The times at which to compute the solution mass_matrix : array_like, optional The (sparse) mass matrix for the chosen spatial method. This is only passed to check that the mass matrix is diagonal with 1s for the odes and 0s for the algebraic equations, as CasADi does not allow to pass mass matrices. """ options = { "grid": t_eval, "reltol": self.rtol, "abstol": self.atol, "output_t0": True, } options.update(self.extra_options) if self.method == "idas": options["calc_ic"] = True # set up and solve integrator = casadi.integrator("F", self.method, problem, options) try: # Try solving len_rhs = problem["x"].size()[0] y0_diff, y0_alg = np.split(y0, [len_rhs]) sol = integrator(x0=y0_diff, z0=y0_alg) y_values = np.concatenate([sol["xf"].full(), sol["zf"].full()]) return pybamm.Solution(t_eval, y_values, None, None, "final time") except RuntimeError as e: # If it doesn't work raise error raise pybamm.SolverError(e.args[0])
def expm(a_matrix): """Since casadi does not have native support for matrix exponential, this is a trick to computing it. It can be quite expensive, specially for large matrices. THIS ONLY SUPPORT NUMERIC MATRICES AND MX VARIABLES, DOES NOT SUPPORT SX SYMBOLIC VARIABLES. :param DM a_matrix: matrix :return: """ dim = a_matrix.shape[1] # Create the integrator x_mx = MX.sym('x', a_matrix.shape[1]) a_mx = MX.sym('x', a_matrix.shape) ode = mtimes(a_mx, x_mx) dae_system_dict = {'x': x_mx, 'ode': ode, 'p': vec(a_mx)} integrator_ = integrator("integrator", "cvodes", dae_system_dict, {'tf': 1}) integrator_map = integrator_.map(a_matrix.shape[1], 'thread') res = integrator_map(x0=DM.eye(dim), p=repmat(vec(a_matrix), (1, a_matrix.shape[1])))['xf'] return res
def __prepare_dynamics(self, nlp): """ Builds CasaDI dynamics function. :param nlp: The nlp problem """ ode_opt = {"t0": 0, "tf": nlp["dt"]} if nlp["ode_solver"] == OdeSolver.COLLOCATION or nlp[ "ode_solver"] == OdeSolver.RK: ode_opt["number_of_finite_elements"] = nlp["nb_integration_steps"] dynamics = nlp["dynamics_func"] ode = { "x": nlp["x"], "p": nlp["u"], "ode": dynamics(nlp["x"], nlp["u"], nlp["p"]) } nlp["dynamics"] = [] nlp["par_dynamics"] = {} if nlp["ode_solver"] == OdeSolver.RK: ode_opt["model"] = nlp["model"] ode_opt["param"] = nlp["p"] ode_opt["CX"] = nlp["CX"] ode_opt["idx"] = 0 ode["ode"] = dynamics ode_opt["control_type"] = nlp["control_type"] if "external_forces" in nlp: for idx in range(len(nlp["external_forces"])): ode_opt["idx"] = idx nlp["dynamics"].append(RK4(ode, ode_opt)) else: if self.nb_threads > 1 and nlp[ "control_type"] == ControlType.LINEAR_CONTINUOUS: raise RuntimeError( "Piece-wise linear continuous controls cannot be used with multiple threads" ) nlp["dynamics"].append(RK4(ode, ode_opt)) elif nlp["ode_solver"] == OdeSolver.COLLOCATION: if not isinstance(self.CX(), MX): raise RuntimeError( "COLLOCATION integrator can only be used with MX graphs") if len(self.param_to_optimize) != 0: raise RuntimeError( "COLLOCATION cannot be used while optimizing parameters") if "external_forces" in nlp: raise RuntimeError( "COLLOCATION cannot be used with external_forces") if nlp["control_type"] == ControlType.LINEAR_CONTINUOUS: raise RuntimeError( "COLLOCATION cannot be used with piece-wise linear controls (only RK4)" ) nlp["dynamics"].append( casadi.integrator("integrator", "collocation", ode, ode_opt)) elif nlp["ode_solver"] == OdeSolver.CVODES: if not isinstance(self.CX(), MX): raise RuntimeError( "CVODES integrator can only be used with MX graphs") if len(self.param_to_optimize) != 0: raise RuntimeError( "CVODES cannot be used while optimizing parameters") if "external_forces" in nlp: raise RuntimeError( "CVODES cannot be used with external_forces") if nlp["control_type"] == ControlType.LINEAR_CONTINUOUS: raise RuntimeError( "CVODES cannot be used with piece-wise linear controls (only RK4)" ) nlp["dynamics"].append( casadi.integrator("integrator", "cvodes", ode, ode_opt)) if len(nlp["dynamics"]) == 1: if self.nb_threads > 1: nlp["par_dynamics"] = nlp["dynamics"][0].map( nlp["ns"], "thread", self.nb_threads) nlp["dynamics"] = nlp["dynamics"] * nlp["ns"]
def _simulate_with_casadi_with_inputs(self, initcon, tsim, varying_inputs, integrator, integrator_options): xalltemp = [self._templatemap[i] for i in self._diffvars] xall = casadi.vertcat(*xalltemp) time = casadi.SX.sym('time') odealltemp = [time * convert_pyomo2casadi(self._rhsdict[i]) for i in self._derivlist] odeall = casadi.vertcat(*odealltemp) # Time-varying inputs ptemp = [self._templatemap[i] for i in self._siminputvars.values()] pall = casadi.vertcat(time, *ptemp) dae = {'x': xall, 'p': pall, 'ode': odeall} if len(self._algvars) != 0: zalltemp = [self._templatemap[i] for i in self._simalgvars] zall = casadi.vertcat(*zalltemp) # Need to do anything special with time scaling?? algalltemp = [convert_pyomo2casadi(i) for i in self._alglist] algall = casadi.vertcat(*algalltemp) dae['z'] = zall dae['alg'] = algall integrator_options['tf'] = 1.0 F = casadi.integrator('F', integrator, dae, integrator_options) N = len(tsim) # This approach removes the time scaling from tsim so must # create an array with the time step between consecutive # time points tsimtemp = np.hstack([0, tsim[1:] - tsim[0:-1]]) tsimtemp.shape = (1, len(tsimtemp)) palltemp = [casadi.DM(tsimtemp)] # Need a similar np array for each time-varying input for p in self._siminputvars.keys(): profile = varying_inputs[p] tswitch = list(profile.keys()) tswitch.sort() tidx = [tsim.searchsorted(i) for i in tswitch] + \ [len(tsim) - 1] ptemp = [profile[0]] + \ [casadi.repmat(profile[tswitch[i]], 1, tidx[i + 1] - tidx[i]) for i in range(len(tswitch))] temp = casadi.horzcat(*ptemp) palltemp.append(temp) I = F.mapaccum('simulator', N) sol = I(x0=initcon, p=casadi.vertcat(*palltemp)) profile = sol['xf'].full().T if len(self._algvars) != 0: algprofile = sol['zf'].full().T profile = np.concatenate((profile, algprofile), axis=1) return [tsim, profile]