def __init__(self): super().__init__() self.num_ee = 2 self.length = ca.DM([0.5, 0.5, 0.5, 0.5, 0.5]) self.mass = ca.DM([0.25, 0.25, 0.25, 0.25, 0.25]) self.inertia = self.mass * (self.length**2) / 12 self.gravity = -10 self.gravity_vector = ca.MX.zeros(2) self.gravity_vector[1] = self.gravity self.b = ca.mmax(self.length * 2) self.a = ca.mmax(self.length)
def __init__(self): super().__init__() self.num_ee = 1 self.length = np.array([0.5, 0.5, 0.5]) self.mass = np.array([0.25, 0.25, 0.25]) self.initial_q = np.zeros((3, 1)) self.final_q = np.zeros((3, 1)) self.inertia = self.mass * (self.length**2) / 12 self.gravity = -10 self.gravity_vector = ca.MX.zeros(2) self.gravity_vector[1] = self.gravity self.b = ca.mmax(self.length * 3) self.a = ca.mmax(self.length)
def make_phase_interpolation_matrix(n_intervals, tau_evaluation_points): if isinstance(tau_evaluation_points, list): tau_evaluation_points = casadi.DM(tau_evaluation_points) assert isinstance(tau_evaluation_points, casadi.DM) assert tau_evaluation_points.numel() == tau_evaluation_points.size1() assert float(casadi.mmin( casadi.diff(tau_evaluation_points) > 0)) == 1.0 # Must be ascending assert float(casadi.mmin(tau_evaluation_points)) >= 0.0 assert float(casadi.mmax(tau_evaluation_points)) <= 1.0 tau_evaluation_points_scaled = [ float(f) * n_intervals for f in casadi.vertsplit(tau_evaluation_points) ] interval_indices = [int(f) for f in tau_evaluation_points_scaled] interval_values = [f - int(f) for f in tau_evaluation_points_scaled] if interval_indices[-1] == n_intervals: interval_indices[-1] = n_intervals - 1 interval_values[-1] = 1.0 interval_interpolation_matrix = make_interval_interpolation_matrix( interval_values) phase_interpolation_matrix = casadi.DM(tau_evaluation_points.numel(), (n_intervals * (collocation.n_nodes - 1) + 1)) for i in range(len(interval_values)): column_idx = interval_indices[i] * (collocation.n_nodes - 1) phase_interpolation_matrix[i, (column_idx):( column_idx + collocation.n_nodes)] = casadi.DM( interval_interpolation_matrix[i]).T return phase_interpolation_matrix
def max(a): """ Returns the maximum value of an array """ try: return _onp.max(a) except TypeError: return _cas.mmax(a)
def _compute_new_nu_and_error(self, p=None, theta=None, raw_solution_dict=None): if raw_solution_dict is None: raw_solution_dict = {} if theta is None: theta = {} if p is None: p = [] if self.new_nu_func is None: self._create_nu_update_func() if not self._debug_skip_compute_nu_and_error: raw_decision_variables = raw_solution_dict['x'] theta_vector = vertcat( *[theta[i] for i in range(self.finite_elements)]) output = self.new_nu_func(raw_decision_variables, p, theta_vector) # get from update new_nu = output[:self.finite_elements] rel_alg = output[self.finite_elements:self.finite_elements * 2] rel_eq = output[self.finite_elements * 2:self.finite_elements * 3] if not self._debug_skip_update_nu: self.nu_tilde = dict([(i, new_nu[i]) for i in range(self.finite_elements)]) self.alg_violation = dict([(i, rel_alg[i]) for i in range(self.finite_elements)]) self.eq_violation = dict([(i, rel_eq[i]) for i in range(self.finite_elements)]) error = max([ mmax(fabs(vertcat(rel_alg[i], rel_eq[i]))) for i in range(self.finite_elements) ]) else: error = 0 return error
def _convert(self, symbol, t, y, y_dot, inputs): """ See :meth:`CasadiConverter.convert()`. """ if isinstance( symbol, ( pybamm.Scalar, pybamm.Array, pybamm.Time, pybamm.InputParameter, pybamm.ExternalVariable, ), ): return casadi.MX(symbol.evaluate(t, y, y_dot, inputs)) elif isinstance(symbol, pybamm.StateVector): if y is None: raise ValueError("Must provide a 'y' for converting state vectors") return casadi.vertcat(*[y[y_slice] for y_slice in symbol.y_slices]) elif isinstance(symbol, pybamm.StateVectorDot): if y_dot is None: raise ValueError("Must provide a 'y_dot' for converting state vectors") return casadi.vertcat(*[y_dot[y_slice] for y_slice in symbol.y_slices]) elif isinstance(symbol, pybamm.BinaryOperator): left, right = symbol.children # process children converted_left = self.convert(left, t, y, y_dot, inputs) converted_right = self.convert(right, t, y, y_dot, inputs) if isinstance(symbol, pybamm.Minimum): return casadi.fmin(converted_left, converted_right) if isinstance(symbol, pybamm.Maximum): return casadi.fmax(converted_left, converted_right) # _binary_evaluate defined in derived classes for specific rules return symbol._binary_evaluate(converted_left, converted_right) elif isinstance(symbol, pybamm.UnaryOperator): converted_child = self.convert(symbol.child, t, y, y_dot, inputs) if isinstance(symbol, pybamm.AbsoluteValue): return casadi.fabs(converted_child) return symbol._unary_evaluate(converted_child) elif isinstance(symbol, pybamm.Function): converted_children = [ self.convert(child, t, y, y_dot, inputs) for child in symbol.children ] # Special functions if symbol.function == np.min: return casadi.mmin(*converted_children) elif symbol.function == np.max: return casadi.mmax(*converted_children) elif symbol.function == np.abs: return casadi.fabs(*converted_children) elif symbol.function == np.sqrt: return casadi.sqrt(*converted_children) elif symbol.function == np.sin: return casadi.sin(*converted_children) elif symbol.function == np.arcsinh: return casadi.arcsinh(*converted_children) elif symbol.function == np.arccosh: return casadi.arccosh(*converted_children) elif symbol.function == np.tanh: return casadi.tanh(*converted_children) elif symbol.function == np.cosh: return casadi.cosh(*converted_children) elif symbol.function == np.sinh: return casadi.sinh(*converted_children) elif symbol.function == np.cos: return casadi.cos(*converted_children) elif symbol.function == np.exp: return casadi.exp(*converted_children) elif symbol.function == np.log: return casadi.log(*converted_children) elif symbol.function == np.sign: return casadi.sign(*converted_children) elif isinstance(symbol.function, (PchipInterpolator, CubicSpline)): return casadi.interpolant("LUT", "bspline", [symbol.x], symbol.y)( *converted_children ) elif symbol.function.__name__.startswith("elementwise_grad_of_"): differentiating_child_idx = int(symbol.function.__name__[-1]) # Create dummy symbolic variables in order to differentiate using CasADi dummy_vars = [ casadi.MX.sym("y_" + str(i)) for i in range(len(converted_children)) ] func_diff = casadi.gradient( symbol.differentiated_function(*dummy_vars), dummy_vars[differentiating_child_idx], ) # Create function and evaluate it using the children casadi_func_diff = casadi.Function("func_diff", dummy_vars, [func_diff]) return casadi_func_diff(*converted_children) # Other functions else: return symbol._function_evaluate(converted_children) elif isinstance(symbol, pybamm.Concatenation): converted_children = [ self.convert(child, t, y, y_dot, inputs) for child in symbol.children ] if isinstance(symbol, (pybamm.NumpyConcatenation, pybamm.SparseStack)): return casadi.vertcat(*converted_children) # DomainConcatenation specifies a particular ordering for the concatenation, # which we must follow elif isinstance(symbol, pybamm.DomainConcatenation): slice_starts = [] all_child_vectors = [] for i in range(symbol.secondary_dimensions_npts): child_vectors = [] for child_var, slices in zip( converted_children, symbol._children_slices ): for child_dom, child_slice in slices.items(): slice_starts.append(symbol._slices[child_dom][i].start) child_vectors.append( child_var[child_slice[i].start : child_slice[i].stop] ) all_child_vectors.extend( [v for _, v in sorted(zip(slice_starts, child_vectors))] ) return casadi.vertcat(*all_child_vectors) else: raise TypeError( """ Cannot convert symbol of type '{}' to CasADi. Symbols must all be 'linear algebra' at this stage. """.format( type(symbol) ) )
def _integrate(self, model, t_eval, inputs_dict=None): """ Calculate the solution of the algebraic equations through root-finding Parameters ---------- model : :class:`pybamm.BaseModel` The model whose solution to calculate. t_eval : :class:`numpy.array`, size (k,) The times at which to compute the solution inputs_dict : dict, optional Any input parameters to pass to the model when solving. If any input parameters that are present in the model are missing from "inputs", then the solution will consist of `ProcessedSymbolicVariable` objects, which must be provided with inputs to obtain their value. """ # Record whether there are any symbolic inputs inputs_dict = inputs_dict or {} has_symbolic_inputs = any( isinstance(v, casadi.MX) for v in inputs_dict.values() ) symbolic_inputs = casadi.vertcat( *[v for v in inputs_dict.values() if isinstance(v, casadi.MX)] ) # Create casadi objects for the root-finder inputs = casadi.vertcat(*[v for v in inputs_dict.values()]) y0 = model.y0 # If y0 already satisfies the tolerance for all t then keep it if has_symbolic_inputs is False and all( np.all(abs(model.casadi_algebraic(t, y0, inputs).full()) < self.tol) for t in t_eval ): pybamm.logger.debug("Keeping same solution at all times") return pybamm.Solution( t_eval, y0, model, inputs_dict, termination="success" ) # The casadi algebraic solver can read rhs equations, but leaves them unchanged # i.e. the part of the solution vector that corresponds to the differential # equations will be equal to the initial condition provided. This allows this # solver to be used for initialising the DAE solvers if model.rhs == {}: len_rhs = 0 y0_diff = casadi.DM() y0_alg = y0 else: len_rhs = model.concatenated_rhs.size y0_diff = y0[:len_rhs] y0_alg = y0[len_rhs:] y_alg = None # Set up t_sym = casadi.MX.sym("t") y_alg_sym = casadi.MX.sym("y_alg", y0_alg.shape[0]) y_sym = casadi.vertcat(y0_diff, y_alg_sym) t_and_inputs_sym = casadi.vertcat(t_sym, symbolic_inputs) alg = model.casadi_algebraic(t_sym, y_sym, inputs) # Check interpolant extrapolation if model.interpolant_extrapolation_events_eval: extrap_event = [ event(0, y0, inputs) for event in model.interpolant_extrapolation_events_eval ] if extrap_event: if (np.concatenate(extrap_event) < self.extrap_tol).any(): extrap_event_names = [] for event in model.events: if ( event.event_type == pybamm.EventType.INTERPOLANT_EXTRAPOLATION and ( event.expression.evaluate( 0, y0.full(), inputs=inputs_dict ) < self.extrap_tol ) ): extrap_event_names.append(event.name[12:]) raise pybamm.SolverError( "CasADi solver failed because the following interpolation " "bounds were exceeded at the initial conditions: {}. " "You may need to provide additional interpolation points " "outside these bounds.".format(extrap_event_names) ) # Set constraints vector in the casadi format # Constrain the unknowns. 0 (default): no constraint on ui, 1: ui >= 0.0, # -1: ui <= 0.0, 2: ui > 0.0, -2: ui < 0.0. constraints = np.zeros_like(model.bounds[0], dtype=int) # If the lower bound is positive then the variable must always be positive constraints[model.bounds[0] >= 0] = 1 # If the upper bound is negative then the variable must always be negative constraints[model.bounds[1] <= 0] = -1 # Set up rootfinder roots = casadi.rootfinder( "roots", "newton", dict(x=y_alg_sym, p=t_and_inputs_sym, g=alg), { **self.extra_options, "abstol": self.tol, "constraints": list(constraints[len_rhs:]), }, ) timer = pybamm.Timer() integration_time = 0 for idx, t in enumerate(t_eval): # Evaluate algebraic with new t and previous y0, if it's already close # enough then keep it # We can't do this if there are symbolic inputs if has_symbolic_inputs is False and np.all( abs(model.casadi_algebraic(t, y0, inputs).full()) < self.tol ): pybamm.logger.debug( "Keeping same solution at t={}".format(t * model.timescale_eval) ) if y_alg is None: y_alg = y0_alg else: y_alg = casadi.horzcat(y_alg, y0_alg) # Otherwise calculate new y_sol else: t_eval_inputs_sym = casadi.vertcat(t, symbolic_inputs) # Solve try: timer.reset() y_alg_sol = roots(y0_alg, t_eval_inputs_sym) integration_time += timer.time() success = True message = None # Check final output y_sol = casadi.vertcat(y0_diff, y_alg_sol) fun = model.casadi_algebraic(t, y_sol, inputs) except RuntimeError as err: success = False message = err.args[0] fun = None # If there are no symbolic inputs, check the function is below the tol # Skip this check if there are symbolic inputs if success and ( has_symbolic_inputs is True or (not any(np.isnan(fun)) and np.all(casadi.fabs(fun) < self.tol)) ): # update initial guess for the next iteration y0_alg = y_alg_sol y0 = casadi.vertcat(y0_diff, y0_alg) # update solution array if y_alg is None: y_alg = y_alg_sol else: y_alg = casadi.horzcat(y_alg, y_alg_sol) elif not success: raise pybamm.SolverError( "Could not find acceptable solution: {}".format(message) ) elif any(np.isnan(fun)): raise pybamm.SolverError( "Could not find acceptable solution: solver returned NaNs" ) else: raise pybamm.SolverError( """ Could not find acceptable solution: solver terminated successfully, but maximum solution error ({}) above tolerance ({}) """.format( casadi.mmax(casadi.fabs(fun)), self.tol ) ) # Concatenate differential part y_diff = casadi.horzcat(*[y0_diff] * len(t_eval)) y_sol = casadi.vertcat(y_diff, y_alg) # Return solution object (no events, so pass None to t_event, y_event) sol = pybamm.Solution( [t_eval], y_sol, model, inputs_dict, termination="success" ) sol.integration_time = integration_time return sol
def _integrate(self, model, t_eval, inputs=None): """ Calculate the solution of the algebraic equations through root-finding Parameters ---------- model : :class:`pybamm.BaseModel` The model whose solution to calculate. t_eval : :class:`numpy.array`, size (k,) The times at which to compute the solution inputs : dict, optional Any input parameters to pass to the model when solving. If any input parameters that are present in the model are missing from "inputs", then the solution will consist of `ProcessedSymbolicVariable` objects, which must be provided with inputs to obtain their value. """ # Record whether there are any symbolic inputs inputs = inputs or {} has_symbolic_inputs = any( isinstance(v, casadi.MX) for v in inputs.values()) # Create casadi objects for the root-finder inputs = casadi.vertcat(*[x for x in inputs.values()]) y0 = model.y0 # The casadi algebraic solver can read rhs equations, but leaves them unchanged # i.e. the part of the solution vector that corresponds to the differential # equations will be equal to the initial condition provided. This allows this # solver to be used for initialising the DAE solvers if model.rhs == {}: y0_diff = casadi.DM() y0_alg = y0 else: len_rhs = model.concatenated_rhs.size y0_diff = y0[:len_rhs] y0_alg = y0[len_rhs:] y_alg = None # Set up t_sym = casadi.MX.sym("t") y_alg_sym = casadi.MX.sym("y_alg", y0_alg.shape[0]) y_sym = casadi.vertcat(y0_diff, y_alg_sym) p_sym = casadi.MX.sym("p", inputs.shape[0]) t_p_sym = casadi.vertcat(t_sym, p_sym) alg = model.casadi_algebraic(t_sym, y_sym, p_sym) # Set up rootfinder roots = casadi.rootfinder( "roots", "newton", dict(x=y_alg_sym, p=t_p_sym, g=alg), { **self.extra_options, "abstol": self.tol }, ) for idx, t in enumerate(t_eval): # Evaluate algebraic with new t and previous y0, if it's already close # enough then keep it # We can't do this if there are symbolic inputs if has_symbolic_inputs is False and np.all( abs(model.casadi_algebraic(t, y0, inputs).full()) < self.tol): pybamm.logger.debug("Keeping same solution at t={}".format( t * model.timescale_eval)) if y_alg is None: y_alg = y0_alg else: y_alg = casadi.horzcat(y_alg, y0_alg) # Otherwise calculate new y_sol else: t_inputs = casadi.vertcat(t, inputs) # Solve try: y_alg_sol = roots(y0_alg, t_inputs) success = True message = None # Check final output y_sol = casadi.vertcat(y0_diff, y_alg_sol) fun = model.casadi_algebraic(t, y_sol, inputs) except RuntimeError as err: success = False message = err.args[0] fun = None # If there are no symbolic inputs, check the function is below the tol # Skip this check if there are symbolic inputs if success and (has_symbolic_inputs is True or np.all(casadi.fabs(fun) < self.tol)): # update initial guess for the next iteration y0_alg = y_alg_sol # update solution array if y_alg is None: y_alg = y_alg_sol else: y_alg = casadi.horzcat(y_alg, y_alg_sol) elif not success: raise pybamm.SolverError( "Could not find acceptable solution: {}".format( message)) else: raise pybamm.SolverError(""" Could not find acceptable solution: solver terminated successfully, but maximum solution error ({}) above tolerance ({}) """.format(casadi.mmax(fun), self.tol)) # Concatenate differential part y_diff = casadi.horzcat(*[y0_diff] * len(t_eval)) y_sol = casadi.vertcat(y_diff, y_alg) # Return solution object (no events, so pass None to t_event, y_event) return pybamm.Solution(t_eval, y_sol, termination="success")
# Add mechanical energy output, to demonstrate output evaluation mocp.add_path_output('energy', y + 0.5 * speed**2) ### Problem done, solve it mocp.solve() mocp.phases[phase_name].change_time_resolution( 8) # Refine mesh and solve again mocp.solve() # Interpolate result and compare with analytic solution tau_grid = linspace(0.0, 1.0, 801) t_grid = tau_grid * mocp.get_value(duration) result_interpolated = mocp.phases[phase_name].interpolate(tau_grid) analytic_solution = dict() analytic_solution['x'] = t_grid - sin(t_grid) analytic_solution['y'] = cos(t_grid) analytic_solution['speed'] = sqrt(2.0 - 2.0 * cos(t_grid)) analytic_solution['path_angle'] = (t_grid - pi) / 2 analytic_solution_duration = 1.5 * pi print('error duration: ', mmax(fabs(mocp.get_value(duration) - analytic_solution_duration))) print('error energy: ', mmax(fabs(DM(result_interpolated['outputs']['energy']) - 1.0))) for trajectory_name in analytic_solution: errors = result_interpolated['trajectories'][ trajectory_name] - analytic_solution[trajectory_name] print(('error ' + trajectory_name + ': ')[:20], mmax(fabs(errors)))
def _integrate(self, model, t_eval, inputs=None): """ Calculate the solution of the algebraic equations through root-finding Parameters ---------- model : :class:`pybamm.BaseModel` The model whose solution to calculate. t_eval : :class:`numpy.array`, size (k,) The times at which to compute the solution inputs : dict, optional Any input parameters to pass to the model when solving """ y0 = model.y0 y = np.empty((len(y0), len(t_eval))) # Set up inputs = casadi.vertcat(*[x for x in inputs.values()]) t_sym = casadi.MX.sym("t") y_sym = casadi.MX.sym("y_alg", y0.shape[0]) p_sym = casadi.MX.sym("p", inputs.shape[0]) t_p_sym = casadi.vertcat(t_sym, p_sym) alg = model.casadi_algebraic(t_sym, y_sym, p_sym) # Set up rootfinder roots = casadi.rootfinder( "roots", "newton", dict(x=y_sym, p=t_p_sym, g=alg), { **self.extra_options, "abstol": self.tol }, ) for idx, t in enumerate(t_eval): # Evaluate algebraic with new t and previous y0, if it's already close # enough then keep it if np.all(abs(model.algebraic_eval(t, y0, inputs)) < self.tol): pybamm.logger.debug("Keeping same solution at t={}".format( t * model.timescale_eval)) y[:, idx] = y0 # Otherwise calculate new y0 else: t_inputs = casadi.vertcat(t, inputs) # Solve try: y_sol = roots(y0, t_inputs).full().flatten() success = True message = None # Check final output fun = model.casadi_algebraic(t, y_sol, inputs) except RuntimeError as err: success = False message = err.args[0] fun = None if success and np.all(casadi.fabs(fun) < self.tol): # update initial guess for the next iteration y0 = y_sol # update solution array y[:, idx] = y_sol elif not success: raise pybamm.SolverError( "Could not find acceptable solution: {}".format( message)) else: raise pybamm.SolverError(""" Could not find acceptable solution: solver terminated successfully, but maximum solution error ({}) above tolerance ({}) """.format(casadi.mmax(fun), self.tol)) # Return solution object (no events, so pass None to t_event, y_event) return pybamm.Solution(t_eval, y, termination="success")
def calculate_consistent_state(self, model, time=0, y0_guess=None, inputs=None): """ Calculate consistent state for the algebraic equations through root-finding Parameters ---------- model : :class:`pybamm.BaseModel` The model for which to calculate initial conditions. time : float The time at which to calculate the states y0_guess : :class:`np.array` Guess for the rootfinding inputs : dict, optional Any input parameters to pass to the model when solving Returns ------- y0_consistent : array-like, same shape as y0_guess Initial conditions that are consistent with the algebraic equations (roots of the algebraic equations) """ pybamm.logger.info("Start calculating consistent states") if y0_guess is None: y0_guess = model.concatenated_initial_conditions.flatten() inputs = inputs or {} if model.convert_to_format == "casadi": inputs = casadi.vertcat(*[x for x in inputs.values()]) # Split y0_guess into differential and algebraic len_rhs = model.rhs_eval(time, y0_guess, inputs).shape[0] y0_diff, y0_alg_guess = np.split(y0_guess, [len_rhs]) # Solve using casadi or scipy if self.root_method == "casadi": # Set up p = casadi.MX.sym("p", inputs.shape[0]) y_alg = casadi.MX.sym("y_alg", y0_alg_guess.shape[0]) y = casadi.vertcat(y0_diff, y_alg) alg_root = model.casadi_algebraic(time, y, p) # Solve roots = casadi.rootfinder( "roots", "newton", dict(x=y_alg, p=p, g=alg_root), {"abstol": self.root_tol}, ) try: y0_alg = roots(y0_alg_guess, inputs).full().flatten() success = True message = None # Check final output fun = model.casadi_algebraic(time, casadi.vertcat(y0_diff, y0_alg), inputs) abs_fun = casadi.fabs(fun) max_fun = casadi.mmax(fun) except RuntimeError as err: success = False message = err.args[0] abs_fun = None max_fun = None else: algebraic = model.algebraic_eval jac = model.jac_algebraic_eval def root_fun(y0_alg): "Evaluates algebraic using y0_diff (fixed) and y0_alg (changed by algo)" y0 = np.concatenate([y0_diff, y0_alg]) out = algebraic(time, y0, inputs) pybamm.logger.debug( "Evaluating algebraic equations at t={}, L2-norm is {}". format(time * model.timescale, np.linalg.norm(out))) return out if jac: if issparse(jac(0, y0_guess, inputs)): def jac_fn(y0_alg): """ Evaluates jacobian using y0_diff (fixed) and y0_alg (varying) """ y0 = np.concatenate([y0_diff, y0_alg]) return jac(0, y0, inputs)[:, len_rhs:].toarray() else: def jac_fn(y0_alg): """ Evaluates jacobian using y0_diff (fixed) and y0_alg (varying) """ y0 = np.concatenate([y0_diff, y0_alg]) return jac(0, y0, inputs)[:, len_rhs:] else: jac_fn = None # Find the values of y0_alg that are roots of the algebraic equations sol = optimize.root( root_fun, y0_alg_guess, jac=jac_fn, method=self.root_method, tol=self.root_tol, ) pybamm.citations.register("virtanen2020scipy") # Set outputs y0_alg = sol.x success = sol.success fun = sol.fun abs_fun = np.abs(fun) max_fun = np.max(fun) message = sol.message if success and np.all(abs_fun < self.root_tol): # Return full set of consistent initial conditions (y0_diff unchanged) y0_consistent = np.concatenate([y0_diff, y0_alg]) pybamm.logger.info( "Finish calculating consistent initial conditions") return y0_consistent elif not success: raise pybamm.SolverError( "Could not find consistent initial conditions: {}".format( message)) else: raise pybamm.SolverError(""" Could not find consistent initial conditions: solver terminated successfully, but maximum solution error ({}) above tolerance ({}) """.format(max_fun, self.root_tol))