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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
def max(a):
    """
    Returns the maximum value of an array
    """

    try:
        return _onp.max(a)
    except TypeError:
        return _cas.mmax(a)
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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)
                )
            )
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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")
Ejemplo n.º 9
0
    # 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)))
Ejemplo n.º 10
0
    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")
Ejemplo n.º 11
0
    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))