cyqlone develop
Fast, parallel and vectorized solver for linear systems with optimal control structure.
Loading...
Searching...
No Matches
conversion.cpp
Go to the documentation of this file.
2#include <batmat/assume.hpp>
3#include <guanaqo/blas/hl-blas-interface.hpp>
4#include <algorithm>
5#include <numeric>
6
7namespace cyqlone {
8
9void reference_to_gradient(const LinearOCPStorage &ocp, std::span<const real_t> ref,
10 std::span<real_t> qr) {
11 auto [N, nx, nu, ny, ny_N] = ocp.dim;
12 BATMAT_ASSERT(static_cast<index_t>(qr.size()) == N * (nx + nu) + nx);
13 auto n_ref = static_cast<index_t>(ref.size());
14 if (n_ref == N * (nx + nu) + nx) {
15 auto H0 = ocp.H(0), HN = ocp.H(N);
16 guanaqo::blas::xgemv_batch_strided(CblasColMajor, CblasNoTrans, nx + nu, nx + nu,
17 real_t{-1}, H0.data, H0.outer_stride,
18 H0.outer_stride * H0.cols, ref.data(), index_t{1},
19 nx + nu, real_t{0}, qr.data(), index_t{1}, nx + nu, N);
20 index_t off_N = N * (nx + nu);
21 guanaqo::blas::xgemv(CblasColMajor, CblasNoTrans, nx, nx, real_t{-1}, HN.data,
22 HN.outer_stride, &ref[off_N], index_t{1}, real_t{0}, &qr[off_N],
23 index_t{1});
24 } else if (n_ref == 2 * nx + nu) {
25 auto H0 = ocp.H(0), HN = ocp.H(N);
27 CblasColMajor, CblasNoTrans, nx + nu, nx + nu, real_t{-1}, H0.data, H0.outer_stride,
28 H0.outer_stride * H0.cols, ref.data(), index_t{1}, index_t{0}, real_t{0}, qr.data(),
29 index_t{1}, nx + nu, N);
30 index_t off_N = N * (nx + nu);
31 guanaqo::blas::xgemv(CblasColMajor, CblasNoTrans, nx, nx, real_t{-1}, HN.data,
32 HN.outer_stride, &ref[nx + nu], index_t{1}, real_t{0}, &qr[off_N],
33 index_t{1});
34 }
35}
36
37void reference_to_gradient(LinearOCPStorage &ocp, std::span<const real_t> ref) {
38 auto qr = ocp.qr();
39 BATMAT_ASSERT(qr.cols == 1);
40 static_assert(qr.storage_order == guanaqo::StorageOrder::ColMajor);
41 reference_to_gradient(ocp, ref, std::span{qr.data, static_cast<size_t>(qr.rows)});
42}
43
47 auto [N, nx, nu, ny, ny_N] = ocp.dim;
48 // Q matrix
49 qp.n = N * (nx + nu) + nx;
50 index_t nnz_Q = N * (nx + nu) * (nx + nu + 1) / 2 + nx * (nx + 1) / 2;
51 {
52 qp.Q_outer_ptr.reserve(qp.n + 1);
53 qp.Q_inner_idx.reserve(nnz_Q);
54 qp.Q_values.reserve(nnz_Q);
55 index_t r_off = 0;
56 for (index_t i = 0; i < N; ++i) {
57 auto Hi = ocp.H(i);
58 for (index_t c = 0; c < nx + nu; ++c) {
59 auto nnz = static_cast<index_t>(qp.Q_inner_idx.size());
60 qp.Q_outer_ptr.push_back(nnz);
61 for (index_t r = c; r < nx + nu; ++r) {
62 qp.Q_inner_idx.push_back(r_off + r);
63 qp.Q_values.push_back(Hi(r, c));
64 }
65 }
66 r_off += nx + nu;
67 }
68 auto HN = ocp.H(N);
69 for (index_t c = 0; c < nx; ++c) {
70 auto nnz = static_cast<index_t>(qp.Q_inner_idx.size());
71 qp.Q_outer_ptr.push_back(nnz);
72 for (index_t r = c; r < nx; ++r) {
73 qp.Q_inner_idx.push_back(r_off + r);
74 qp.Q_values.push_back(HN(r, c));
75 }
76 }
77 qp.Q_outer_ptr.push_back(nnz_Q);
78 assert(r_off + nx == qp.n);
79 assert(qp.Q_outer_ptr.size() == static_cast<size_t>(qp.n + 1));
80 assert(qp.Q_inner_idx.size() == static_cast<size_t>(nnz_Q));
81 assert(qp.Q_values.size() == static_cast<size_t>(nnz_Q));
82 qp.Q_sparsity = {
83 .rows = qp.n,
84 .cols = qp.n,
85 .symmetry = Symmetry::Lower,
86 .inner_idx = qp.Q_inner_idx,
87 .outer_ptr = qp.Q_outer_ptr,
88 .order = decltype(Q_sparsity)::SortedRows,
89 };
90 }
91 // A matrix
92 qp.m_eq = (N + 1) * nx;
93 qp.m_ineq = N * ny + ny_N;
94 index_t m = qp.m_eq + qp.m_ineq;
95 index_t nnz_A_eq = qp.m_eq + N * nx * (nx + nu);
96 index_t nnz_A_ineq = N * ny * (nx + nu) + ny_N * nx;
97 index_t nnz_A = nnz_A_eq + nnz_A_ineq;
98 {
99 qp.A_outer_ptr.reserve(qp.n + 1);
100 qp.A_inner_idx.reserve(nnz_A);
101 qp.A_values.reserve(nnz_A);
102 index_t r_off_eq = 0, r_off_ineq = qp.m_eq;
103 for (index_t i = 0; i < N; ++i) {
104 auto ABi = ocp.AB(i), CDi = ocp.CD(i);
105 for (index_t c = 0; c < nx + nu; ++c) {
106 auto nnz = static_cast<index_t>(qp.A_inner_idx.size());
107 qp.A_outer_ptr.push_back(nnz);
108 if (c < nx) {
109 qp.A_inner_idx.push_back(r_off_eq + c);
110 qp.A_values.push_back(-1);
111 }
112 for (index_t r = 0; r < nx; ++r) {
113 qp.A_inner_idx.push_back(r_off_eq + nx + r);
114 qp.A_values.push_back(ABi(r, c));
115 }
116 for (index_t r = 0; r < ny; ++r) {
117 qp.A_inner_idx.push_back(r_off_ineq + r);
118 qp.A_values.push_back(CDi(r, c));
119 }
120 }
121 r_off_eq += nx;
122 r_off_ineq += ny;
123 }
124 auto CDi = ocp.CD(N);
125 for (index_t c = 0; c < nx; ++c) {
126 auto nnz = static_cast<index_t>(qp.A_inner_idx.size());
127 qp.A_outer_ptr.push_back(nnz);
128 qp.A_inner_idx.push_back(r_off_eq + c);
129 qp.A_values.push_back(-1);
130 for (index_t r = 0; r < ny_N; ++r) {
131 qp.A_inner_idx.push_back(r_off_ineq + r);
132 qp.A_values.push_back(CDi(r, c));
133 }
134 }
135 qp.A_outer_ptr.push_back(nnz_A);
136 assert(r_off_eq + nx == qp.m_eq);
137 assert(r_off_ineq + ny_N == m);
138 assert(qp.A_outer_ptr.size() == static_cast<size_t>(qp.n + 1));
139 assert(qp.A_inner_idx.size() == static_cast<size_t>(nnz_A));
140 assert(qp.A_values.size() == static_cast<size_t>(nnz_A));
141 qp.A_sparsity = {.rows = m,
142 .cols = qp.n,
143 .symmetry = Symmetry::Unsymmetric,
144 .inner_idx = qp.A_inner_idx,
145 .outer_ptr = qp.A_outer_ptr,
146 .order = decltype(A_sparsity)::SortedRows};
147 }
148 return qp;
149}
150
151auto LinearOCPSparseQP::build_kkt(real_t S, std::span<const real_t> Σ,
152 std::span<const bool> J) const -> KKTMatrix {
153 KKTMatrix K;
154 K.outer_ptr.reserve(n + m_ineq + m_eq + 1);
155 const auto nnz_max = Q_sparsity.nnz() + A_sparsity.nnz() + m_ineq;
156 K.inner_idx.reserve(nnz_max);
157 K.values.reserve(nnz_max);
158 assert(static_cast<index_t>(J.size()) == m_ineq);
159 assert(static_cast<index_t>(Σ.size()) == m_ineq);
160 std::vector<index_t> constr_indices(m_ineq + 1);
161 std::inclusive_scan(J.begin(), J.end(), constr_indices.begin() + 1, std::plus{}, index_t{0});
162 const index_t m_ineq_active = constr_indices.back();
163 real_t inv_S = 1 / S;
164 for (index_t c = 0; c < n; ++c) {
165 K.outer_ptr.push_back(static_cast<index_t>(K.inner_idx.size()));
166 // Q
167 index_t Q_ptr = Q_outer_ptr[c], A_eq_ptr = A_outer_ptr[c];
168 const index_t Q_end = Q_outer_ptr[c + 1], A_end = A_outer_ptr[c + 1];
169 while (Q_ptr < Q_end) {
170 K.inner_idx.push_back(Q_inner_idx[Q_ptr]);
171 K.values.push_back(Q_values[Q_ptr]);
172 if (K.inner_idx.back() == c)
173 K.values.back() += inv_S;
174 ++Q_ptr;
175 }
176 // First A_ineq
177 auto A_ineq_it =
178 std::lower_bound(A_inner_idx.begin() + A_eq_ptr, A_inner_idx.begin() + A_end, m_eq);
179 auto A_ineq_ptr = static_cast<index_t>(A_ineq_it - A_inner_idx.begin());
180 const auto A_eq_end = A_ineq_ptr;
181 while (A_ineq_ptr < A_end) {
182 const index_t r = A_inner_idx[A_ineq_ptr] - m_eq;
183 if (J[r]) {
184 K.inner_idx.push_back(n + constr_indices[r]);
185 K.values.push_back(A_values[A_ineq_ptr]);
186 }
187 ++A_ineq_ptr;
188 }
189 // Then A_eq
190 while (A_eq_ptr < A_eq_end) {
191 K.inner_idx.push_back(n + m_ineq_active + A_inner_idx[A_eq_ptr]);
192 K.values.push_back(A_values[A_eq_ptr]);
193 ++A_eq_ptr;
194 }
195 }
196 // Diagonal block Σ⁻¹
197 for (index_t r = 0; r < m_ineq; ++r) {
198 if (J[r]) {
199 K.outer_ptr.push_back(static_cast<index_t>(K.inner_idx.size()));
200 K.inner_idx.push_back(n + constr_indices[r]);
201 K.values.push_back(-1 / Σ[r]);
202 }
203 }
204 std::fill_n(std::back_inserter(K.outer_ptr), m_eq + 1,
205 static_cast<index_t>(K.inner_idx.size()));
207 K.sparsity = {.rows = n + m_ineq_active + m_eq,
208 .cols = n + m_ineq_active + m_eq,
209 .symmetry = Symmetry::Lower,
210 .inner_idx = K.inner_idx,
211 .outer_ptr = K.outer_ptr,
212 .order = decltype(K.sparsity)::SortedRows};
213 return K;
214}
215
216} // namespace cyqlone
#define BATMAT_ASSERT(x)
Conversion utilities for optimal control problems.
void xgemv_batch_strided(CBLAS_LAYOUT layout, CBLAS_TRANSPOSE trans, I m, I n, std::type_identity_t< T > alpha, std::type_identity_t< const T * > a, I lda, I stridea, std::type_identity_t< const T * > x, I incx, I stridex, std::type_identity_t< T > beta, T *y, I incy, I stridey, I batch_size)
void xgemv(CBLAS_LAYOUT Layout, CBLAS_TRANSPOSE TransA, I M, I N, std::type_identity_t< T > alpha, std::type_identity_t< const T * > A, I lda, std::type_identity_t< const T * > X, I incX, std::type_identity_t< T > beta, T *Y, I incY)
void reference_to_gradient(const LinearOCPStorage &ocp, std::span< const real_t > ref, std::span< real_t > qr)
Simply computes the gradient of the quadratic cost , with , with the Hessian .
Definition conversion.cpp:9
Represents a sparse multiple shooting formulation of the standard optimal control problem.
SparseCSC< index_t, index_t > A_sparsity
std::vector< real_t > A_values
std::vector< index_t > A_outer_ptr
static LinearOCPSparseQP build(const LinearOCPStorage &ocp)
std::vector< index_t > A_inner_idx
SparseCSC< index_t, index_t > Q_sparsity
std::vector< index_t > Q_outer_ptr
std::vector< index_t > Q_inner_idx
SparseCSC< index_t, index_t > sparsity
KKTMatrix build_kkt(real_t S, std::span< const real_t > Σ, std::span< const bool > J) const
Returns the lower part of the symmetric indefinite KKT matrix for the active set J and penalty factor...
std::vector< real_t > Q_values
Storage for a linear-quadratic OCP of the form.
Definition ocp.hpp:37
guanaqo::MatrixView< real_t, index_t > CD(index_t i)
Definition ocp.hpp:95
guanaqo::MatrixView< real_t, index_t > H(index_t i)
Definition ocp.hpp:64
guanaqo::MatrixView< real_t, index_t > AB(index_t i)
Definition ocp.hpp:116
guanaqo::MatrixView< real_t, index_t > qr()
Definition ocp.hpp:225