def inv_map(self, sol: Solution) -> Solution:
        qinv = np.ones_like(sol.t)
        pinv = np.ones_like(sol.t)
        for ii, t in enumerate(sol.t):
            qinv[ii] = self.fn_q_inv(sol.y[ii], sol.dual[ii], sol.q[ii],
                                     sol.dynamical_parameters, sol.const)
            pinv[ii] = self.fn_p_inv(sol.y[ii], sol.dual[ii],
                                     sol.dynamical_parameters, sol.const)

        state = pinv
        qval = qinv
        if self.remove_parameter_dict['location'] == 'states':
            sol.y = np.column_stack(
                (sol.y[:, :self.remove_parameter_dict['index']], state,
                 sol.y[:, self.remove_parameter_dict['index']:]))
        elif self.remove_parameter_dict['location'] == 'costates':
            sol.dual = np.column_stack(
                (sol.dual[:, :self.remove_parameter_dict['index']], state,
                 sol.dual[:, self.remove_parameter_dict['index']:]))

        if self.remove_symmetry_dict['location'] == 'states':
            sol.y = np.column_stack(
                (sol.y[:, :self.remove_symmetry_dict['index']], qval,
                 sol.y[:, self.remove_symmetry_dict['index']:]))
        elif self.remove_symmetry_dict['location'] == 'costates':
            sol.dual = np.column_stack(
                (sol.dual[:, :self.remove_symmetry_dict['index']], qval,
                 sol.dual[:, self.remove_symmetry_dict['index']:]))

        sol.q = np.delete(sol.q, np.s_[-1], axis=1)
        sol.dynamical_parameters = sol.dynamical_parameters[:-1]
        return sol
    def map(self, sol: Solution) -> Solution:
        cval = self.fn_p(sol.y[0], sol.dual[0], sol.dynamical_parameters,
                         sol.const)
        qval = np.ones_like(sol.t)

        sol.dynamical_parameters = np.hstack((sol.dynamical_parameters, cval))
        for ii, t in enumerate(sol.t):
            qval[ii] = self.fn_q(sol.y[ii], sol.dual[ii],
                                 sol.dynamical_parameters, sol.const)

        if self.remove_parameter_dict['location'] == 'states':
            sol.y = np.delete(sol.y,
                              np.s_[self.remove_parameter_dict['index']],
                              axis=1)
        elif self.remove_parameter_dict['location'] == 'costates':
            sol.dual = np.delete(sol.dual,
                                 np.s_[self.remove_parameter_dict['index']],
                                 axis=1)

        if self.remove_symmetry_dict['location'] == 'states':
            sol.y = np.delete(sol.y,
                              np.s_[self.remove_symmetry_dict['index']],
                              axis=1)
        elif self.remove_symmetry_dict['location'] == 'costates':
            sol.dual = np.delete(sol.dual,
                                 np.s_[self.remove_symmetry_dict['index']],
                                 axis=1)

        sol.q = np.column_stack((sol.q, qval))

        return sol
    def inv_map(self, sol: Solution) -> Solution:
        sol.dual = sol.y[:, self.costate_idxs]
        sol.y = np.delete(sol.y, self.costate_idxs, axis=1)
        sol.nondynamical_parameters = np.delete(sol.nondynamical_parameters,
                                                self.constraint_adjoints_idxs)

        return sol
    def map(self,
            sol: Solution,
            control_costate: Union[float, np.ndarray] = 0.) -> Solution:
        idx_u_list = []

        for idx_u, (idx_y,
                    u) in enumerate(sorted(zip(self.control_idxs, sol.u.T))):
            sol.y = np.insert(sol.y, idx_y, u, axis=1)

            if isinstance(control_costate, Iterable):
                if not isinstance(control_costate, np.ndarray):
                    control_costate = np.array(control_costate)
                costate_insert = control_costate[idx_u] * np.ones_like(sol.t)
            else:
                costate_insert = control_costate * np.ones_like(sol.t)

            sol.dual = np.insert(sol.dual, -1, costate_insert, axis=1)
            if len(sol.dual_u) == 0:
                sol.dual_u = np.array([costate_insert])
            else:
                sol.dual_u = np.insert(sol.dual_u, -1, costate_insert, axis=1)

            idx_u_list.append(idx_u)

        sol.u = np.delete(sol.u, idx_u_list, axis=1)
        return sol
    def inv_map(self, sol: Solution) -> Solution:
        if self.ind_state_idx is None:
            self.ind_state_idx = np.shape(sol.y)[1] - 1

        sol.t = sol.y[:, self.ind_state_idx]
        sol.y = np.delete(sol.y, self.ind_state_idx, axis=1)

        sol.dual_t = sol.dual[:, self.ind_state_idx]
        sol.dual = np.delete(sol.dual, self.ind_state_idx, axis=1)

        return sol
    def map(self, sol: Solution) -> Solution:
        if self.ind_state_idx is None:
            self.ind_state_idx = np.shape(sol.y)[1]

        if len(sol.dual_t) == 0:
            sol.dual_t = np.zeros_like(sol.t)

        sol.y = np.insert(sol.y, self.ind_state_idx, sol.t, axis=1)
        sol.dual = np.insert(sol.dual, self.ind_state_idx, sol.dual_t, axis=1)

        return sol
    def inv_map(self, sol: Solution, retain_dual=True) -> Solution:

        if not retain_dual:
            sol.dual = empty_array

        sol.nondynamical_parameters = empty_array

        sol.cost = self.compute_cost(sol.t, sol.y, sol.q, sol.u,
                                     sol.dynamical_parameters, sol.const)

        return sol
 def inv_map(self, sol: Solution) -> Solution:
     sol.u = sol.y[:, self.control_idxs]
     sol.y = np.delete(sol.y, self.control_idxs, axis=1)
     sol.dual = np.delete(sol.dual, self.control_idxs, axis=1)
     return sol
    def solve(self, solinit, **kwargs):

        solinit = copy.deepcopy(solinit)
        nstates = solinit.y.shape[1]

        nquads = 0

        def return_nil(*_, **__):
            return np.array([])

        if solinit.q.size > 0:
            nquads = solinit.q.shape[1]
        else:
            nquads = 0
            self.quadrature_function = return_nil

        ndyn = solinit.dynamical_parameters.size
        nnondyn = solinit.nondynamical_parameters.size

        empty_array = np.array([])

        if nquads == 0:
            # TODO: Try to vectorize
            def _fun(t, y, params=empty_array, const=solinit.const):
                return np.vstack([self.derivative_function(yi[:nstates], params[:ndyn], const) for yi in y.T]).T

            def _bc(ya, yb, params=empty_array, const=solinit.const):
                return self.boundarycondition_function(ya, yb, params[:ndyn], params[ndyn:ndyn + nnondyn], const)
        else:
            def _fun(t, y, params=empty_array, const=solinit.const):
                y = y.T
                o1 = np.vstack([self.derivative_function(yi[:nstates], params[:ndyn], const) for yi in y])
                o2 = np.vstack([self.quadrature_function(yi[:nstates], params[:ndyn], const) for yi in y])
                return np.hstack((o1, o2)).T

            def _bc(ya, yb, params=np.array([]), const=solinit.const):
                return self.boundarycondition_function(ya[:nstates], ya[nstates:nstates+nquads], yb[:nstates],
                                                       yb[nstates:nstates+nquads], params[:ndyn],
                                                       params[ndyn:ndyn+nnondyn], const)

        if self.derivative_function_jac is not None:
            def _fun_jac(t, y, params=np.array([]), const=solinit.const):
                y = y.T
                df_dy = np.zeros((y[0].size, y[0].size, t.size))
                df_dp = np.zeros((y[0].size, ndyn+nnondyn, t.size))

                for ii, yi in enumerate(y):
                    df_dy[:, :, ii], _df_dp = self.derivative_function_jac(yi, params[:ndyn], const)
                    if nstates > 1 and len(_df_dp.shape) == 1:
                        _df_dp = np.array([_df_dp]).T

                    df_dp[:, :, ii] = np.hstack((_df_dp, np.zeros((nstates, nnondyn))))

                if ndyn + nnondyn == 0:
                    return df_dy
                else:
                    return df_dy, df_dp
        else:
            _fun_jac = None

        if self.boundarycondition_function_jac is not None:
            if nquads > 0:
                def _bc_jac(ya, yb, params=np.array([]), const=solinit.const):
                    dbc_dya, dbc_dyb, dbc_dp = \
                        self.boundarycondition_function_jac(ya[:nstates], ya[nstates:nstates+nquads], yb[:nstates],
                                                            yb[nstates:nstates+nquads], params[:ndyn],
                                                            params[ndyn:ndyn+nnondyn], const)
                    return dbc_dya, dbc_dyb, dbc_dp
            else:
                def _bc_jac(ya, yb, params=np.array([]), const=solinit.const):
                    dbc_dya, dbc_dyb, dbc_dp = \
                        self.boundarycondition_function_jac(ya, yb, params[:ndyn], params[ndyn:ndyn+nnondyn], const)
                    return dbc_dya, dbc_dyb, dbc_dp
        else:
            _bc_jac = None

        if nquads > 0:
            opt = solve_bvp(_fun, _bc, solinit.t, np.hstack((solinit.y, solinit.q)).T,
                            np.hstack((solinit.dynamical_parameters, solinit.nondynamical_parameters)),
                            max_nodes=self.max_nodes, fun_jac=_fun_jac, bc_jac=_bc_jac)
        else:
            opt = solve_bvp(_fun, _bc, solinit.t, solinit.y.T,
                            np.hstack((solinit.dynamical_parameters, solinit.nondynamical_parameters)),
                            max_nodes=self.max_nodes, fun_jac=_fun_jac, bc_jac=_bc_jac)

        sol = Trajectory(solinit)
        sol.t = opt['x']
        sol.y = opt['y'].T[:, :nstates]
        sol.q = opt['y'].T[:, nstates:nstates+nquads]
        sol.dual = np.zeros_like(sol.y)
        if opt['p'] is not None:
            sol.dynamical_parameters = opt['p'][:ndyn]
            sol.nondynamical_parameters = opt['p'][ndyn:ndyn+nnondyn]
        else:
            sol.dynamical_parameters = np.array([])
            sol.nondynamical_parameters = np.array([])

        sol.converged = opt['success']
        out = BVPResult(sol=sol, success=opt['success'], message=opt['message'], rms_residuals=opt['rms_residuals'],
                        niter=opt['niter'])

        return out