cyqlone develop
Fast, parallel and vectorized solver for linear systems with optimal control structure.
Loading...
Searching...
No Matches
solve-ocp.cpp
Example demonstrating how to solve an optimal control problem using Cyqlone.

This example generates a random optimal control problem (without inequality constraints), and solves the resulting KKT system using Cyqlone.

2#include <cyqlone/cyqlone.hpp>
3#include <cyqlone/linalg.hpp>
4#include <cyqlone/ocp.hpp>
5#include <batmat/dtypes.hpp>
6#include <iostream>
7#include <print>
8#include <random>
9#include <string>
10
11#if CYQLONE_WITH_MATIO
12#include <cyqlone/matio.hpp>
13#endif
14
15using cyqlone::index_t; // Integer type for indices
16using cyqlone::real_t; // Floating-point type for scalars
17constexpr index_t v = // Vector length to use
19
20// Generate a random linear-quadratic OCP.
21cyqlone::LinearOCPStorage init_ocp(index_t N, index_t nx, index_t nu) {
24
25 std::mt19937 rng{12345};
26 std::uniform_real_distribution<real_t> uni_dist{-1.2, 1.2};
27 auto uni_gen = [&] { return uni_dist(rng); };
28
29 cyqlone::LinearOCPStorage ocp{.dim{.N_horiz = N, .nx = nx, .nu = nu, .ny = 0}};
30 auto diag = 2 * static_cast<real_t>(nx + nu);
31 ocp.b(0).generate(uni_gen); // initial state x(0)
32 for (index_t j = 0; j < N; ++j) {
33 ocp.A(j).generate(uni_gen); // Dynamics Jacobian df/dx
34 ocp.B(j).generate(uni_gen); // Dynamics Jacobian df/du
35 ocp.b(j + 1).generate(uni_gen); // Dynamics constant term f(j)
36 ocp.Q(j).generate(uni_gen); // Cost Hessian d²ℓ/dx²
37 ocp.R(j).generate(uni_gen); // Cost Hessian d²ℓ/du²
38 ocp.S(j).generate(uni_gen); // Cost Hessian d²ℓ/dxdu
39 ocp.q(j).generate(uni_gen); // Cost gradient dℓ/dx
40 ocp.r(j).generate(uni_gen); // Cost gradient dℓ/du
41 ocp.Q(j).add_to_diagonal(diag); // ensure positive definiteness
42 ocp.R(j).add_to_diagonal(diag);
43 copy(tril(ocp.H(j)).transposed(), triu(ocp.H(j))); // ensure symmetry of cost Hessian
44 }
45 ocp.Q(N).generate(uni_gen); // Terminal cost Hessian d²ℓ_N(x)/dx²
46 ocp.q(N).generate(uni_gen);
47 ocp.Q(N).add_to_diagonal(diag);
48 copy(tril(ocp.Q(N)).transposed(), triu(ocp.Q(N)));
49 return ocp;
50}
51
52// Example of solving a linear-quadratic OCP using the CyqloneSolver.
53int main(int argc, char *argv[]) try {
54 // Problem dimensions and parameters
55 const index_t p = argc > 1 ? std::stoi(argv[1]) : 8; // Number of processors/threads
56 const index_t N = argc > 2 ? std::stoi(argv[2]) : 128; // Horizon length
57 const index_t nx = argc > 3 ? std::stoi(argv[3]) : 10; // State dimension
58 const index_t nu = argc > 4 ? std::stoi(argv[4]) : 5; // Control dimension
60
61 // Initialize a random linear-quadratic OCP
62 auto ocp = init_ocp(N, nx, nu);
63 // Eliminate the initial state x0 and merge the first and last stages for Cyqlone
65
66 // Create a Cyqlone solver for the OCP
68 // Optionally configure the solver parameters
69 auto params = solver.get_tricyqle_params();
70 params.solve_method = cyqlone::SolveMethod::PCR; // Use the PCR solver instead of PCG
71 solver.update_tricyqle_params(params);
72 std::println("{}", solver.get_params_string());
73
74 // Initialize copies of the OCP data vectors in compact storage format
75 const auto rq = solver.initialize_gradient(cocp); // gradient vectors r and q
76 const auto b = solver.initialize_rhs(cocp); // right-hand side vector b of dynamics constraints
77 // Initialize the right-hand side of the KKT system (will be overwritten by the solution)
78 auto ux = solver.initialize_variables(), λ = solver.initialize_dynamics_constraints();
79 cyqlone::linalg::negate(rq, ux); // right-hand side of the KKT system is negative gradient
81 const real_t γ = 1e99; // Primal regularization
82
83 // Call the solver in parallel, passing a lambda that is executed by each thread
84 auto pctx = solver.create_parallel_context();
85 auto t0 = std::chrono::high_resolution_clock::now();
87 solver.factor_solve(ctx, γ, {}, ux, λ); // Cholesky factorization and forward substitution
88 solver.solve_reverse(ctx, ux, λ); // Backward substitution
89 }); // blocks until all threads have joined
90 auto t1 = std::chrono::high_resolution_clock::now();
91 std::println("Time: {:.3f} ms", std::chrono::duration<double, std::milli>(t1 - t0).count());
92
93 // Compute the residuals (in compact storage and without x0, also in parallel)
94 auto Mxb = solver.initialize_dynamics_constraints(); // primal equality constraint residual
95 auto grad = solver.initialize_variables(); // gradient of the Lagrangian
97 solver.residual_dynamics_constr(ctx, ux, b, Mxb); // Mx + b
98 solver.transposed_dynamics_constr(ctx, λ, grad); // Mᵀλ
99 solver.cost_gradient(ctx, ux, 1, rq, 1, grad); // + Qx + q
100 });
101
102 // Print the residuals
104 std::println("Eq. residual (compact): {:.17e}", norm_inf(Mxb));
105 std::println("Lagr. stationarity (compact): {:.17e}", norm_inf(grad));
106
107 // Reconstruct the full solution (as a flat vector, including x0)
108 std::vector ux_unpacked = solver.unpack_variables(ux); // convert compact to linear storage
109 std::vector λ_unpacked = solver.unpack_dynamics(λ);
110 auto sol = cocp.reconstruct_solution(ocp, ux_unpacked, {}, λ_unpacked);
111
112 // Check the residuals of the original OCP
113 auto resid = ocp.compute_kkt_error(sol);
114 std::println("Eq. residual (full): {:.17e}", resid.equality_residual);
115 std::println("Lagr. stationarity (full): {:.17e}", resid.stationarity);
116
117#if CYQLONE_WITH_MATIO
118 // Export the original OCP and the solution as a .mat file
119 std::filesystem::path filename = "ocp_solution.mat";
120 auto matfile = cyqlone::create_mat(filename);
121 cyqlone::add_to_mat(matfile.get(), "ocp", ocp);
122 cyqlone::add_to_mat(matfile.get(), "sol_x", sol.solution);
123 cyqlone::add_to_mat(matfile.get(), "sol_λ", sol.equality_multipliers);
124 std::cout << "Saved OCP and solution to " << filename << "\n";
125#endif
126} catch (std::exception &e) {
127 std::cerr << "Error: " << e.what() << "\n";
128 return 1;
129}
#define BATMAT_ASSERT(x)
Data structure for optimal control problems where the initial states are eliminated.
The main header for the Cyqlone and Tricyqle linear solvers.
int main()
SolveMethod solve_method
Algorithm to use for solving the final reduced block tridiagonal system.
@ PCR
Parallel Cyclic Reduction (direct).
simdified_value_t< Vx > norm_inf(Vx &&x)
Compute the infinity norm of a vector.
Definition linalg.hpp:260
void negate(VA &&A, VB &&B, with_rotate_t< Rotate >={})
Negate a matrix or vector B = -A.
Definition linalg.hpp:386
void copy(VA &&A, VB &&B, Opts... opts)
constexpr auto triu(M &&m)
constexpr auto tril(M &&m)
MatFilePtr create_mat(const std::filesystem::path &filename)
Create and open a new .mat file for writing.
Definition matio.cpp:97
void add_to_mat(mat_t *mat, const std::string &varname, float value)
Add a value to an open .mat file.
Definition matio.cpp:122
Functions for exporting and loading matrices and OCP data to and from .mat files.
constexpr index_t vl_at_most
constexpr bool is_pow_2(index_t n)
Definition cyqlone.hpp:32
Data structure for optimal control problems.
constexpr index_t v
Linear solver for systems with optimal control structure.
Definition cyqlone.hpp:561
matrix initialize_variables() const
Get a zero-initialized matrix for the primal variables u and x.
Definition data.tpp:501
void factor_solve(Context &ctx, value_type γ, view<> Σ, mut_view<> ux, mut_view<> λ)
Compute the Cyqlone factorization of the KKT matrix of the OCP and perform a forward solve (fused for...
Definition factor.tpp:132
void initialize_gradient(const CyqloneStorage< value_type > &ocp, mut_view<> grad) const
Initialize the gradient vector for the OCP cost function, using the custom Cyqlone storage format.
Definition data.tpp:170
void unpack_variables(view<> ux, std::span< value_type > ux_lin) const
Definition data.tpp:281
void transposed_dynamics_constr(Context &ctx, view<> λ, mut_view<> Mᵀλ, bool accum=false) const
Compute Mᵀλ, where M is the dynamics constraint Jacobian matrix of the OCP.
Definition mat-vec.tpp:52
void residual_dynamics_constr(Context &ctx, view<> x, view<> b, mut_view<> Mxb) const
Compute Mx + b, where M is the dynamics constraint Jacobian matrix of the OCP.
Definition mat-vec.tpp:17
TricyqleParams< value_type > get_tricyqle_params() const
Get the current Tricyqle solver parameters.
Definition cyqlone.hpp:720
std::unique_ptr< SharedContext > create_parallel_context() const
Definition cyqlone.hpp:616
std::string get_params_string() const
Get a string representation of the main solver parameters. Used mainly for file names.
Definition cyqlone.hpp:730
tricyqle_t::Context Context
Definition cyqlone.hpp:596
static CyqloneSolver build(const CyqloneStorage< value_type > &ocp, index_t p)
Initialize a Cyqlone solver for the given OCP.
Definition data.tpp:41
void cost_gradient(Context &ctx, view<> ux, value_type α, view<> q, value_type β, mut_view<> grad_f) const
Compute the cost gradient, with optional scaling factors.
Definition mat-vec.tpp:115
matrix initialize_dynamics_constraints() const
Get a zero-initialized matrix for the dynamics constraints (or their multipliers).
Definition data.tpp:506
void initialize_rhs(const CyqloneStorage< value_type > &ocp, mut_view<> rhs) const
Initialize the right-hand side vector for the dynamics constraints of the OCP, using the custom Cyqlo...
Definition data.tpp:147
void solve_reverse(Context &ctx, mut_view<> ux, mut_view<> λ)
Perform a reverse solve with the Cyqlone factorization.
Definition factor.tpp:255
void update_tricyqle_params(const TricyqleParams< value_type > &new_params)
Update the Tricyqle solver parameters.
Definition cyqlone.hpp:725
void unpack_dynamics(view<> λ, std::span< value_type > λ_lin) const
Definition data.tpp:342
static CyqloneStorage build(const LinearOCPStorage &ocp, index_t ny_0=-1)
Storage for a linear-quadratic OCP of the form.
Definition ocp.hpp:37
guanaqo::MatrixView< real_t, index_t > r(index_t i)
Definition ocp.hpp:245
guanaqo::MatrixView< real_t, index_t > q(index_t i)
Definition ocp.hpp:240
guanaqo::MatrixView< real_t, index_t > B(index_t i)
Definition ocp.hpp:132
guanaqo::MatrixView< real_t, index_t > b()
Definition ocp.hpp:251
guanaqo::MatrixView< real_t, index_t > R(index_t i)
Definition ocp.hpp:80
guanaqo::MatrixView< real_t, index_t > H(index_t i)
Definition ocp.hpp:64
guanaqo::MatrixView< real_t, index_t > S(index_t i)
Definition ocp.hpp:85
guanaqo::MatrixView< real_t, index_t > Q(index_t i)
Definition ocp.hpp:75
guanaqo::MatrixView< real_t, index_t > A(index_t i)
Definition ocp.hpp:127