def next(self, ignore_last_step=False):
        if len(self.gammas) == 0:
            raise ValueError(
                'No boundary value problem associated with this object')

        if not ignore_last_step and len(
                self.gammas) != 1 and not self.gammas[-1].converged:
            logging.error('The last step did not converge!')
            raise RuntimeError('Solution diverged! Stopping.')

        if self.ctr >= self.num_cases():
            raise StopIteration

        # Update auxiliary variables using previously calculated step sizes
        total_change = 0.0
        const0 = copy.deepcopy(self.gammas[-1].const)
        for var_name in self.vars:
            jj = self.constant_names.index(var_name)
            const0[jj] = self.vars[var_name].steps[self.ctr]
            total_change += abs(self.vars[var_name].steps[self.ctr])

        i = int(self.get_closest_gamma(const0))
        gamma_guess = copy.deepcopy(self.gammas[i])
        gamma_guess.const = const0
        self.ctr += 1
        logger.debug('CHOOSE\ttraj #' + str(i) + ' as guess.')
        return gamma_guess
    def next(self, ignore_last_step=False):
        """Generator class to create BVPs for the continuation step iterations

        last_converged: Specfies if the previous continuation step converged
        """
        # If it is the first step or if previous step converged
        # continue with usual behavior
        if self.ctr == 0 or self.gammas[-1].converged:
            # Reset division counter
            self.division_ctr = 0
            return super(BisectionStrategy, self).next()

        # If first step didn't converge, the process fails
        if self.ctr == 1:
            logging.error(
                'Initial guess should converge for automated continuation to work.'
            )
            raise RuntimeError('Initial guess does not converge.')
            # return self.gammas[-1]

        if self.division_ctr > self.max_divisions:
            logging.error(
                'Solution does not without exceeding max_divisions : ' +
                str(self.max_divisions))
            raise RuntimeError('Exceeded max_divisions')

        # If previous step did not converge, move back a half step
        for var_name in self.var_iterator():
            old_steps = self.vars[var_name].steps
            if self._spacing == 'linear':
                new_steps = np.linspace(old_steps[self.ctr - 2],
                                        old_steps[self.ctr - 1],
                                        self.num_divisions + 1)
            elif self._spacing == 'log':
                new_steps = np.logspace(np.log10(old_steps[self.ctr - 2]),
                                        np.log10(old_steps[self.ctr - 1]),
                                        self.num_divisions + 1)
            else:
                raise ValueError('Invalid spacing type')

            # Insert new steps
            self.vars[var_name].steps = np.insert(
                self.vars[var_name].steps,
                self.ctr - 1,
                new_steps[1:-1]  # Ignore first element as it is repeated
            )
        # Move the counter back
        self.ctr = self.ctr - 1
        # Increment total number of steps
        self._num_cases = self._num_cases + self.num_divisions - 1

        logger.debug('Increasing number of cases to ' + str(self._num_cases) +
                     '\n')
        self.division_ctr = self.division_ctr + 1
        return super(BisectionStrategy, self).next(True)
def run_continuation_set(bvp_algorithm_, steps, sol_guess, bvp: Problem, pool,
                         autoscale):
    """
    Runs a continuation set for the BVP problem.

    :param bvp_algorithm_: BVP algorithm to be used.
    :param steps: The steps in a continuation set.
    :param sol_guess: Initial guess for the first problem in steps.
    :param bvp: The compiled boundary-value problem to solve.
    :param pool: A processing pool, if available.
    :param autoscale: Whether or not scaling is used.
    :return: A set of solutions for the steps.
    """
    # Loop through all the continuation steps
    solution_set = []

    functional_problem = bvp.functional_problem

    # Initialize scaling
    scale = functional_problem.scale_sol
    compute_factors = functional_problem.compute_scale_factors

    # Load the derivative function into the prob algorithm
    bvp_algorithm_.set_derivative_function(functional_problem.deriv_func)
    bvp_algorithm_.set_derivative_jacobian(functional_problem.deriv_func_jac)
    bvp_algorithm_.set_quadrature_function(functional_problem.quad_func)
    bvp_algorithm_.set_boundarycondition_function(functional_problem.bc_func)
    bvp_algorithm_.set_boundarycondition_jacobian(
        functional_problem.bc_func_jac)
    bvp_algorithm_.set_inequality_constraint_function(
        functional_problem.ineq_constraints)

    if steps is None:
        logger.info('Solving OCP...')
        time0 = time.time()
        if autoscale:
            scale_factors = compute_factors(sol_guess)
            sol_guess = scale(sol_guess, scale_factors)
        else:
            scale_factors = None

        opt = bvp_algorithm_.solve(sol_guess, pool=pool)
        sol = opt['sol']

        if autoscale:
            sol = scale(sol, scale_factors, inv=True)

        solution_set = [[sol]]
        if sol.converged:
            elapsed_time = time.time() - time0
            logger.debug('Problem converged in %0.4f seconds\n' % elapsed_time)
        else:
            logger.debug('Problem failed to converge!\n')
    else:
        for step_idx, step in enumerate(steps):
            logger.debug('\nRunning Continuation Step #{} ({})'.format(
                step_idx + 1, step) + ' : ')
            # logger.debug('Number of Iterations\t\tMax BC Residual\t\tTime to Solution')
            solution_set.append([])
            # Assign solution from last continuation set
            step.reset()
            step.init(sol_guess, bvp)
            try:
                log_level = logger.display_level_
            except AttributeError:
                log_level = logger.getEffectiveLevel()

            step_len = len(step)
            continuation_progress = tqdm(
                step,
                disable=(not (logging.INFO <= log_level < logging.WARN)),
                file=sys.stdout,
                desc='Cont. #{} ({})'.format(step_idx + 1,
                                             fit_string(str(step), 30)),
                ascii=True,
                unit='trajectories')
            for sol_guess in continuation_progress:
                continuation_progress.total = len(step)
                if step_len != continuation_progress.total:
                    step_len = continuation_progress.total
                    continuation_progress.refresh()

                logger.debug('START \tIter {:d}/{:d}'.format(
                    step.ctr, step.num_cases()))
                time0 = time.time()
                if autoscale:
                    scale_factors = compute_factors(sol_guess)
                    sol_guess = scale(sol_guess, scale_factors)
                else:
                    scale_factors = None

                opt = bvp_algorithm_.solve(sol_guess, pool=pool)
                sol = opt['sol']

                if autoscale:
                    sol = scale(sol, scale_factors, inv=True)

                ya = sol.y[0, :]
                yb = sol.y[-1, :]

                dp = sol.dynamical_parameters
                ndp = sol.nondynamical_parameters
                k = sol.const

                if sol.q.size > 0:
                    qa = sol.q[0, :]
                    qb = sol.q[-1, :]

                    bc_residuals_unscaled = bvp_algorithm_.boundarycondition_function(
                        ya, qa, yb, qb, dp, ndp, k)

                else:
                    bc_residuals_unscaled = bvp_algorithm_.boundarycondition_function(
                        ya, yb, dp, ndp, k)

                step.add_gamma(sol)
                """
                The following line is overwritten by the looping variable UNLESS it is the final iteration. It is
                required when chaining continuation strategies together. DO NOT DELETE!
                """
                sol_guess = copy.deepcopy(sol)
                elapsed_time = time.time() - time0
                logger.debug(
                    'STOP  \tIter {:d}/{:d}\tBVP Iters {:d}\tBC Res {:13.8E}\tTime {:13.8f}'
                    .format(step.ctr, step.num_cases(), opt['niter'],
                            max(bc_residuals_unscaled), elapsed_time))
                solution_set[step_idx].append(copy.deepcopy(sol))
                if not sol.converged:
                    logger.debug('Iteration %d/%d failed to converge!\n' %
                                 (step.ctr, step.num_cases()))

    return solution_set
Ejemplo n.º 4
0
def solve(autoscale=True, bvp=None, bvp_algorithm=None, guess_generator=None, initial_helper=False,
          method='traditional', n_cpus=1, ocp=None, ocp_transform=None, ocp_inv_transform=None,
          ocp_inv_transform_many=None, optim_options=None, steps=None, save_sols=True):

    if optim_options is None:
        optim_options = {}

    if logger.level <= logging.DEBUG:
        logger.debug(make_a_splash())

    """
    Error checking
    """
    if n_cpus < 1:
        raise ValueError('Number of cpus must be greater than 1.')
    if n_cpus > 1:
        pool = pathos.multiprocessing.Pool(processes=n_cpus)
    else:
        pool = None

    if ocp is None:
        raise NotImplementedError('\"ocp\" must be defined.')

    """
    Main code
    """

    # f_ocp = compile_direct(ocp)

    logger.debug('Using ' + str(n_cpus) + '/' + str(pathos.multiprocessing.cpu_count()) + ' CPUs. ')

    if bvp is None:
        preprocessor = make_preprocessor()
        processed_ocp = preprocessor(copy.deepcopy(ocp))

        if method.lower() in ['indirect', 'traditional', 'brysonho']:
            method = 'traditional'
        
        if method.lower() in ['traditional', 'diffyg']:
            RFfunctor = make_indirect_method(copy.deepcopy(processed_ocp), method=method, **optim_options)
            prob = RFfunctor(copy.deepcopy(processed_ocp))
        elif method == 'direct':
            functor = make_direct_method(copy.deepcopy(processed_ocp), **optim_options)
            prob = functor(copy.deepcopy(processed_ocp))
        else:
            raise NotImplementedError
        
        postprocessor = make_postprocessor()
        bvp = postprocessor(copy.deepcopy(prob))

        logger.debug('Resulting BVP problem:')
        logger.debug(bvp.__repr__())

        ocp_transform = bvp.map_sol
        ocp_inv_transform = bvp.inv_map_sol

    else:
        if ocp_transform is None or ocp_inv_transform is None or ocp_inv_transform_many is None:
            raise ValueError('BVP problem must have an associated \'ocp_map\' and \'ocp_map_inverse\'')

    solinit = Trajectory()
    solinit.k = np.array(getattr_from_list(bvp.constants, 'default_val'))
    solinit = guess_generator.generate(bvp.functional_problem, solinit, ocp_transform, ocp_inv_transform)

    if initial_helper:
        sol_ocp = copy.deepcopy(solinit)
        sol_ocp = match_constants_to_states(ocp, ocp_inv_transform(sol_ocp))
        solinit.k = sol_ocp.k

    if bvp.functional_problem.compute_u is not None:
        u = np.array([bvp.functional_problem.compute_u(solinit.y[0], solinit.p, solinit.k)])
        for ii in range(len(solinit.t) - 1):
            u = np.vstack(
                (u, bvp.functional_problem.compute_u(solinit.y[ii + 1], solinit.p, solinit.k)))
        solinit.u = u

    """
    Main continuation process
    """
    time0 = time.time()
    continuation_set = run_continuation_set(bvp_algorithm, steps, solinit, bvp, pool, autoscale)
    total_time = time.time() - time0
    logger.info('Continuation process completed in %0.4f seconds.\n' % total_time)
    bvp_algorithm.close()

    """
    Post processing and output
    """
    out = postprocess_continuations(continuation_set, ocp_inv_transform)

    if pool is not None:
        pool.close()

    if save_sols or (isinstance(save_sols, str)):
        if isinstance(save_sols, str):
            filename = save_sols
        else:
            filename = 'data.json'

        save(out, filename=filename)

    return out
Ejemplo n.º 5
0
    def solve(self, solinit, **kwargs):
        """
        Solve a two-point boundary value problem using the shooting method.

        :param solinit: An initial guess for a solution to the BVP.
        :return: A solution to the BVP.
        """

        # Make a copy of sol and format inputs
        sol = copy.deepcopy(solinit)
        sol.t = np.array(sol.t, dtype=beluga.DTYPE)
        sol.y = np.array(sol.y, dtype=beluga.DTYPE)
        if np.issubdtype(sol.y.dtype, np.complexfloating):
            dtype = complex
        else:
            dtype = float
        sol.q = np.array(sol.q, dtype=beluga.DTYPE)
        sol.p = np.array(sol.p, dtype=beluga.DTYPE)
        sol.nu = np.array(sol.nu, dtype=beluga.DTYPE)

        # n = traj.y[0].shape[0]
        k = sol.p.shape[0]
        # traj.p = np.hstack((traj.p, traj.nu))
        # traj.nu = np.empty((0,))

        fun_wrapped, bc_wrapped, fun_jac_wrapped, bc_jac_wrapped = wrap_functions(
            self.derivative_function, self.boundarycondition_function, None,
            None, sol.k, k, dtype)

        pool = kwargs.get('pool', None)

        # Extract some info from the guess structure
        y0g = sol.y[0, :]
        if self.quadrature_function is None or np.isnan(sol.q).all():
            q0g = np.array([])
        else:
            q0g = sol.q[0, :]

        parameter_guess = sol.p
        nondynamical_parameter_guess = sol.nu

        # Get some info on the size of the problem
        n_odes = y0g.shape[0]
        n_quads = q0g.shape[0]
        n_dynparams = sol.p.shape[0]
        # n_nondynparams = nondynamical_parameter_guess.shape[0]

        # Make the state-transition ode matrix
        if self.stm_ode_func is None:
            self.stm_ode_func = self.make_stmode(self.derivative_function,
                                                 y0g.shape[0])

        # Set up the boundary condition function
        if self.bc_func_ms is None:
            self.bc_func_ms = self._bc_func_multiple_shooting(
                bc_func=self.boundarycondition_function)

        # Build each of the separate arcs for multiple shooting. Uses sol's interpolation style
        gamma_set = []
        t0 = sol.t[0]
        tf = sol.t[-1]
        tn = np.linspace(t0, tf, self.num_arcs + 1)
        node_data = sol.interpolate(tn)
        for trajectory_number in range(self.num_arcs):
            y0t, q0t, u0t = tuple(item[trajectory_number]
                                  for item in node_data)
            yft, qft, uft = tuple(item[trajectory_number + 1]
                                  for item in node_data)
            t_set = np.hstack(
                (tn[trajectory_number], tn[trajectory_number + 1]))
            y_set = np.vstack((y0t, yft))
            q_set = np.vstack((q0t, qft))
            u_set = np.vstack((u0t, uft))
            gamma_set.append(Trajectory(t_set, y_set, q_set, u_set))

        # Initial state of STM is an identity matrix with an additional column of zeros per parameter
        # stm0 = np.hstack((np.eye(n_odes), np.zeros((n_odes, n_dynparams)))).reshape(n_odes*(n_odes + n_dynparams))
        # y0stm = np.zeros((len(stm0) + n_odes))

        prop = Propagator(**self.ivp_args)

        converged = False  # Convergence flag
        n_iter = 0  # Initialize iteration counter
        err = -1

        # Set up the initial guess vector
        x_init = self._wrap_y0(gamma_set, parameter_guess,
                               nondynamical_parameter_guess)

        def quad_wrap(t, xx, p, const):
            return self.quadrature_function(t, xx[:n_odes], p, const)

        # Pickle the functions for faster execution
        if pool is not None:
            pick_deriv = pickle.dumps(self.derivative_function)
            pick_quad = pickle.dumps(self.quadrature_function)
            pick_stm = pickle.dumps(self.stm_ode_func)
            pick_quad_stm = pickle.dumps(quad_wrap)
            _gamma_maker = self._make_gammas_parallel
        else:
            pick_deriv = self.derivative_function
            pick_quad = self.quadrature_function
            pick_stm = self.stm_ode_func
            pick_quad_stm = quad_wrap
            _gamma_maker = self._make_gammas

        # Set up the constraint function
        def _constraint_function(xx, deriv_func, quad_func, n_odes, n_quads,
                                 n_dynparams, n_arcs, const):
            g = copy.deepcopy(gamma_set)
            _y, _q, _params, _nonparams = self._unwrap_y0(
                xx, n_odes, n_quads, n_dynparams, n_arcs)
            for ii in range(n_arcs):
                g[ii].y[0] = _y[ii]
                if n_quads > 0:
                    g[ii].q[0] = _q
            g = _gamma_maker(deriv_func, quad_func, g, _params, sol, prop,
                             pool, n_quads)
            return self.bc_func_ms(g, _params, _nonparams, k, const)

        def _constraint_function_wrapper(X):
            return _constraint_function(X, pick_deriv, pick_quad, n_odes,
                                        n_quads, n_dynparams, self.num_arcs,
                                        sol.k)

        # Set up the jacobian of the constraint function
        def _jacobian_function(xx, deriv_func, quad_func, n_odes, n_quads,
                               n_dynparams, n_arcs):
            g = copy.deepcopy(gamma_set)
            _y, _q, _params, _nonparams = self._unwrap_y0(
                xx, n_odes, n_quads, n_dynparams, n_arcs)
            n_nondyn = _nonparams.shape[0]
            for ii in range(n_arcs):
                g[ii].y[0] = _y[ii]
                if n_quads > 0:
                    g[ii].q[0] = _q

            phi_full_list = []
            for ii in range(n_arcs):
                t0 = g[ii].t[0]
                _y0g, _q0g, _u0g = g[ii](t0)
                tf = g[ii].t[-1]
                _yfg, _qfg, _ufg = g[ii](tf)
                stm0 = np.hstack(
                    (np.eye(n_odes), np.zeros((n_odes, n_dynparams)))).reshape(
                        n_odes * (n_odes + n_dynparams))
                y0stm = np.zeros((len(stm0) + n_odes))
                stmf = np.hstack(
                    (np.eye(n_odes), np.zeros((n_odes, n_dynparams)))).reshape(
                        n_odes * (n_odes + n_dynparams))
                yfstm = np.zeros((len(stmf) + n_odes))
                y0stm[:n_odes] = _y0g
                y0stm[n_odes:] = stm0[:]
                yfstm[:n_odes] = _yfg
                yfstm[n_odes:] = stmf[:]
                g[ii].t = np.hstack((t0, tf))
                g[ii].y = np.vstack((y0stm, yfstm))
                g[ii].q = np.vstack((_q0g, _qfg))
                g[ii].u = np.vstack((_u0g, _ufg))

            gamma_set_new = _gamma_maker(deriv_func, quad_func, g,
                                         _params[:n_dynparams], sol, prop,
                                         pool, n_quads)
            for ii in range(len(gamma_set_new)):
                t_set = gamma_set_new[ii].t
                temp = gamma_set_new[ii].y
                y_set = temp[:, :n_odes]
                q_set = gamma_set_new[ii].q
                u_set = gamma_set_new[ii].u
                gamma_set_new[ii] = Trajectory(t_set, y_set, q_set, u_set)
                phi_temp = np.reshape(
                    temp[:, n_odes:],
                    (len(gamma_set_new[ii].t), n_odes, n_odes + n_dynparams))
                phi_full_list.append(np.copy(phi_temp))

            dbc_dya, dbc_dyb, dbc_dp = estimate_bc_jac(bc_wrapped,
                                                       gamma_set_new[0].y[0],
                                                       [],
                                                       gamma_set_new[-1].y[-1],
                                                       [], _params, _nonparams)

            if dbc_dp is None:
                dbc_dp = np.empty((dbc_dya.shape[0], 0))

            values = np.empty((0, ))
            i_jac = np.empty((0, ), dtype=int)
            j_jac = np.empty((0, ), dtype=int)

            if n_arcs == 1:
                jac = np.hstack((dbc_dya, dbc_dp))
                _phi = np.vstack((phi_full_list[-1][-1],
                                  np.zeros(
                                      (n_dynparams, n_odes + n_dynparams))))
                jac += np.dot(np.hstack((dbc_dyb, dbc_dp)), _phi)

                i_bc = np.repeat(np.arange(0, n_odes + n_dynparams),
                                 n_odes + n_dynparams)
                j_bc = np.tile(np.arange(n_odes + n_dynparams),
                               n_odes + n_dynparams)
                values = np.hstack((values, jac.ravel()))
                i_jac = np.hstack((i_jac, i_bc))
                j_jac = np.hstack((j_jac, j_bc))
            else:
                p_jac = np.empty((0, n_dynparams))
                for ii in range(n_arcs - 1):
                    jac = np.dot(np.eye(n_odes), phi_full_list[ii][-1])
                    i_bc = np.repeat(np.arange(n_odes * ii, n_odes * (ii + 1)),
                                     n_odes)
                    j_bc = np.tile(np.arange(0, n_odes), n_odes) + n_odes * ii
                    values = np.hstack((values, jac[:, :n_odes].ravel()))
                    i_jac = np.hstack((i_jac, i_bc))
                    j_jac = np.hstack((j_jac, j_bc))

                    if n_dynparams > 0:
                        p_jac = np.vstack((p_jac, jac[:, n_odes:]))

                    jac = -np.eye(n_odes)
                    i_bc = np.repeat(np.arange(n_odes * ii, n_odes * (ii + 1)),
                                     n_odes)
                    j_bc = np.tile(np.arange(0, n_odes),
                                   n_odes) + n_odes * (ii + 1)
                    values = np.hstack((values, jac.ravel()))
                    i_jac = np.hstack((i_jac, i_bc))
                    j_jac = np.hstack((j_jac, j_bc))

                if n_dynparams > 0:
                    values = np.hstack((values, p_jac.ravel()))
                    i_p = np.repeat(np.arange(0, n_odes * (n_arcs - 1)),
                                    n_dynparams)
                    j_p = np.tile(np.arange(0, n_dynparams),
                                  n_odes * (n_arcs - 1)) + n_odes * n_arcs
                    i_jac = np.hstack((i_jac, i_p))
                    j_jac = np.hstack((j_jac, j_p))

                jac = dbc_dya
                i_bc = np.repeat(np.arange(0, n_odes + n_dynparams),
                                 n_odes) + n_odes * (n_arcs - 1)
                j_bc = np.tile(np.arange(n_odes), n_odes + n_dynparams)
                values = np.hstack((values, jac.ravel()))
                i_jac = np.hstack((i_jac, i_bc))
                j_jac = np.hstack((j_jac, j_bc))

                _phi = np.vstack((phi_full_list[-1][-1],
                                  np.zeros(
                                      (n_dynparams, n_odes + n_dynparams))))
                jac = np.dot(np.hstack((dbc_dyb, dbc_dp)), _phi)
                jac[:, n_odes:] += dbc_dp
                i_bc = np.repeat(np.arange(0, n_odes + n_dynparams),
                                 n_odes + n_dynparams) + n_odes * (n_arcs - 1)
                j_bc = np.tile(np.arange(n_odes + n_dynparams),
                               n_odes + n_dynparams) + n_odes * (n_arcs - 1)
                values = np.hstack((values, jac.ravel()))
                i_jac = np.hstack((i_jac, i_bc))
                j_jac = np.hstack((j_jac, j_bc))

            J = csc_matrix(coo_matrix((values, (i_jac, j_jac))))
            return J

        def _jacobian_function_wrapper(X):
            return approx_jacobian(X, _constraint_function_wrapper, 1e-6)

        is_sparse = False

        # is_sparse = False
        # if n_quads == 0 and self.algorithm.lower() == 'armijo':
        #     is_sparse = True
        #     def _jacobian_function_wrapper(X):
        #         return _jacobian_function(X, pick_stm, pick_quad_stm, n_odes, n_quads, n_dynparams, self.num_arcs)
        # elif n_quads == 0:
        #     def _jacobian_function_wrapper(X):
        #         return _jacobian_function(X, pick_stm, pick_quad_stm, n_odes, n_quads, n_dynparams, self.num_arcs).toarray()
        # else:
        #     def _jacobian_function_wrapper(X):
        #         return approx_jacobian(X, _constraint_function_wrapper, 1e-6)

        constraint = {
            'type': 'eq',
            'fun': _constraint_function_wrapper,
            'jac': _jacobian_function_wrapper
        }

        # Set up the cost function. This should just return 0 unless the specified method cannot handle constraints
        def cost(_):
            return 0

        """
        Run the root-solving process
        """
        if self.algorithm in scipy_minimize_algorithms:
            if not (self.algorithm == 'COBYLA' or self.algorithm == 'SLSQP'
                    or self.algorithm == 'trust-constr'):

                def cost(x):
                    return np.linalg.norm(_constraint_function_wrapper(x))**2

            opt = minimize(cost,
                           x_init,
                           method=self.algorithm,
                           tol=self.tolerance,
                           constraints=constraint,
                           options={'maxiter': self.max_iterations})

            err = opt.fun
            x_init = opt.x
            n_iter = opt.nit
            converged = opt.success and isclose(err, 0, abs_tol=self.tolerance)

        elif self.algorithm in scipy_root_algorithms:
            opt = root(_constraint_function_wrapper,
                       x_init,
                       jac=_jacobian_function_wrapper,
                       method=self.algorithm,
                       tol=self.tolerance,
                       options={'maxiter': self.max_iterations})
            err = opt.fun
            x_init = opt.x
            n_iter = -1
            converged = opt.success

        elif self.algorithm.lower() == 'fsolve':
            x = fsolve(_constraint_function_wrapper,
                       x_init,
                       fprime=_jacobian_function_wrapper,
                       xtol=self.tolerance)
            err = np.linalg.norm(_constraint_function_wrapper(x_init))**2
            x_init = x
            n_iter = -1
            converged = isclose(err, 0, abs_tol=self.tolerance)

        elif self.algorithm.lower() == 'armijo':

            while not converged and n_iter <= self.max_iterations and err < self.max_error:
                residual = _constraint_function_wrapper(x_init)

                if any(np.isnan(residual)):
                    raise RuntimeError("Nan in residual")

                err = np.linalg.norm(residual)
                jac = _jacobian_function_wrapper(x_init)
                try:
                    if is_sparse:
                        LU = splu(jac)
                        dy0 = LU.solve(-residual)
                    else:
                        dy0 = np.linalg.solve(jac, -residual)
                except np.linalg.LinAlgError as error:
                    logging.warning(error)
                    dy0, *_ = np.linalg.lstsq(jac, -residual)

                a = 1e-4
                reduct = 0.5
                ll = 1
                r_try = float('Inf')
                step = None

                while (r_try >=
                       (1 - a * ll) * err) and (r_try >
                                                self.tolerance) and ll > 0.05:
                    step = ll * dy0
                    res_try = _constraint_function_wrapper(x_init + step)
                    r_try = np.linalg.norm(res_try)
                    ll *= reduct

                x_init += step
                err = r_try
                n_iter += 1

                if err <= self.tolerance:
                    converged = True
                if is_sparse:
                    logger.debug(
                        'BVP Iter {}\tResidual {:13.8E}\tJacobian condition {:13.8E}'
                        .format(n_iter, err, np.linalg.cond(jac.toarray())))
                else:
                    logger.debug(
                        'BVP Iter {}\tResidual {:13.8E}\tJacobian condition {:13.8E}'
                        .format(n_iter, err, np.linalg.cond(jac)))

        else:
            raise NotImplementedError('Method \'' + self.algorithm +
                                      '\' is not implemented.')
        """
        Post symbolic checks and formatting
        """

        # Unwrap the solution from the solver to put in a readable format
        y, q, parameter_guess, nondynamical_parameter_guess = self._unwrap_y0(
            x_init, n_odes, n_quads, n_dynparams, self.num_arcs)
        for ii in range(self.num_arcs):
            gamma_set[ii].y[0] = y[ii]
            if n_quads > 0:
                gamma_set[ii].q[0] = q
        gamma_set = _gamma_maker(pick_deriv, pick_quad, gamma_set,
                                 parameter_guess, sol, prop, pool, n_quads)

        if err < self.tolerance and converged:
            if n_iter == -1:
                message = "Converged in an unknown number of iterations."
            else:
                message = "Converged in " + str(n_iter) + " iterations."
        elif n_iter > self.max_iterations:
            message = 'Max iterations exceeded.'
        elif err > self.max_error:
            message = 'Error exceeded max_error.'
        else:
            message = 'Solver stopped for unspecified reason'

        # Stitch the arcs together to make a single trajectory, removing the boundary points inbetween each arc
        t_out = gamma_set[0].t
        y_out = gamma_set[0].y
        q_out = gamma_set[0].q
        u_out = gamma_set[0].u

        for ii in range(self.num_arcs - 1):
            t_out = np.hstack((t_out, gamma_set[ii + 1].t[1:]))
            y_out = np.vstack((y_out, gamma_set[ii + 1].y[1:]))
            q_out = np.vstack((q_out, gamma_set[ii + 1].q[1:]))
            u_out = np.vstack((u_out, gamma_set[ii + 1].u[1:]))

        sol.t = t_out
        sol.y = y_out
        sol.q = q_out
        sol.u = u_out

        sol.p = parameter_guess
        sol.nu = nondynamical_parameter_guess
        sol.converged = converged

        out = BVPResult(sol=sol,
                        success=converged,
                        message=message,
                        niter=n_iter)
        return out
Ejemplo n.º 6
0
def solve(autoscale=True,
          bvp=None,
          bvp_algorithm=None,
          guess_generator=None,
          initial_helper=False,
          method='traditional',
          n_cpus=1,
          ocp=None,
          ocp_map=None,
          ocp_map_inverse=None,
          optim_options=None,
          steps=None,
          save_sols=True):
    """
    Solves the OCP using specified method

    +------------------------+-----------------+---------------------------------------+
    | Valid kwargs           | Default Value   | Valid Values                          |
    +========================+=================+=======================================+
    | autoscale              | True            | bool                                  |
    +------------------------+-----------------+---------------------------------------+
    | prob                   | None            | codegen'd BVPs                        |
    +------------------------+-----------------+---------------------------------------+
    | bvp_algorithm          | None            | prob algorithm                        |
    +------------------------+-----------------+---------------------------------------+
    | guess_generator        | None            | guess generator                       |
    +------------------------+-----------------+---------------------------------------+
    | initial_helper         | False           | bool                                  |
    +------------------------+-----------------+---------------------------------------+
    | method                 | 'traditional'   | string                                |
    +------------------------+-----------------+---------------------------------------+
    | n_cpus                 | 1               | integer                               |
    +------------------------+-----------------+---------------------------------------+
    | ocp                    | None            | :math:`\\Sigma`                       |
    +------------------------+-----------------+---------------------------------------+
    | ocp_map                | None            | :math:`\\gamma \rightarrow \\gamma`   |
    +------------------------+-----------------+---------------------------------------+
    | ocp_map_inverse        | None            | :math:`\\gamma \rightarrow \\gamma`   |
    +------------------------+-----------------+---------------------------------------+
    | optim_options          | None            | dict()                                |
    +------------------------+-----------------+---------------------------------------+
    | steps                  | None            | continuation_strategy                 |
    +------------------------+-----------------+---------------------------------------+
    | save                   | False           | bool, str                             |
    +------------------------+-----------------+---------------------------------------+

    """

    if optim_options is None:
        optim_options = {}

    # Display useful info about the environment to debug logger.
    logger.debug('\n' + __splash__ + '\n')
    from beluga import __version__ as beluga_version
    from llvmlite import __version__ as llvmlite_version
    from numba import __version__ as numba_version
    from numpy import __version__ as numpy_version
    from scipy import __version__ as scipy_version
    from sympy.release import __version__ as sympy_version

    logger.debug('beluga:\t\t' + str(beluga_version))
    logger.debug('llvmlite:\t' + str(llvmlite_version))
    logger.debug('numba:\t\t' + str(numba_version))
    logger.debug('numpy:\t\t' + str(numpy_version))
    logger.debug('python:\t\t' + str(sys.version_info[0]) + '.' +
                 str(sys.version_info[1]) + '.' + str(sys.version_info[2]))
    logger.debug('scipy:\t\t' + str(scipy_version))
    logger.debug('sympy:\t\t' + str(sympy_version) + '\n\n')
    """
    Error checking
    """
    if n_cpus < 1:
        raise ValueError('Number of cpus must be greater than 1.')
    if n_cpus > 1:
        pool = pathos.multiprocessing.Pool(processes=n_cpus)
    else:
        pool = None

    if ocp is None:
        raise NotImplementedError('\"ocp\" must be defined.')
    """
    Main code
    """

    # f_ocp = compile_direct(ocp)

    logger.debug('Using ' + str(n_cpus) + '/' +
                 str(pathos.multiprocessing.cpu_count()) + ' CPUs. ')

    if bvp is None:
        preprocessor = make_preprocessor()
        processed_ocp = preprocessor(copy.deepcopy(ocp))

        if method.lower() in ['indirect', 'traditional', 'brysonho']:
            method = 'traditional'

        if method.lower() in ['traditional', 'diffyg']:
            RFfunctor = make_indirect_method(copy.deepcopy(processed_ocp),
                                             method=method,
                                             **optim_options)
            prob = RFfunctor(copy.deepcopy(processed_ocp))
        elif method == 'direct':
            functor = make_direct_method(copy.deepcopy(processed_ocp),
                                         **optim_options)
            prob = functor(copy.deepcopy(processed_ocp))
        else:
            raise NotImplementedError

        postprocessor = make_postprocessor()
        bvp = postprocessor(copy.deepcopy(prob))

        logger.debug('Resulting BVP problem:')
        logger.debug(bvp.__repr__())

        ocp_map = bvp.map_sol
        ocp_map_inverse = bvp.inv_map_sol

    else:
        if ocp_map is None or ocp_map_inverse is None:
            raise ValueError(
                'BVP problem must have an associated \'ocp_map\' and \'ocp_map_inverse\''
            )

    solinit = Trajectory()
    solinit.const = np.array(getattr_from_list(bvp.constants, 'default_val'))
    solinit = guess_generator.generate(bvp.functional_problem, solinit,
                                       ocp_map, ocp_map_inverse)

    if initial_helper:
        sol_ocp = copy.deepcopy(solinit)
        sol_ocp = match_constants_to_states(ocp, ocp_map_inverse(sol_ocp))
        solinit.const = sol_ocp.const

    if bvp.functional_problem.compute_u is not None:
        u = np.array([
            bvp.functional_problem.compute_u(solinit.y[0],
                                             solinit.dynamical_parameters,
                                             solinit.const)
        ])
        for ii in range(len(solinit.t) - 1):
            u = np.vstack(
                (u,
                 bvp.functional_problem.compute_u(solinit.y[ii + 1],
                                                  solinit.dynamical_parameters,
                                                  solinit.const)))
        solinit.u = u
    """
    Main continuation process
    """
    time0 = time.time()
    continuation_set = run_continuation_set(bvp_algorithm, steps, solinit, bvp,
                                            pool, autoscale)
    total_time = time.time() - time0
    logger.info('Continuation process completed in %0.4f seconds.\n' %
                total_time)
    bvp_algorithm.close()
    """
    Post processing and output
    """
    out = postprocess_continuations(continuation_set, ocp_map_inverse)

    if pool is not None:
        pool.close()

    if save_sols or (isinstance(save_sols, str)):
        if isinstance(save_sols, str):
            filename = save_sols
        else:
            filename = 'data.beluga'

        save(out, ocp, bvp, filename=filename)

    return out