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
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
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
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