symbolic_qr.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "symbolic_qr.hpp"
27 
28 #ifdef WITH_DL
29 #include <cstdlib>
30 #endif // WITH_DL
31 
32 namespace casadi {
33 
34  extern "C"
35  int CASADI_LINSOL_SYMBOLICQR_EXPORT
36  casadi_register_linsol_symbolicqr(LinsolInternal::Plugin* plugin) {
37  plugin->creator = SymbolicQr::creator;
38  plugin->name = "symbolicqr";
39  plugin->doc = SymbolicQr::meta_doc.c_str();
40  plugin->version = CASADI_VERSION;
41  plugin->options = &SymbolicQr::options_;
42  plugin->deserialize = &SymbolicQr::deserialize;
43  plugin->exposed.det = &SymbolicQr::det;
44  return 0;
45  }
46 
47  // Sign (+1/-1) of a permutation: (-1)^(m - number of cycles)
48  static double permutation_sign(const std::vector<casadi_int>& p) {
49  std::vector<bool> seen(p.size(), false);
50  casadi_int ncycle = 0;
51  for (casadi_int i=0; i<static_cast<casadi_int>(p.size()); ++i) {
52  if (!seen[i]) {
53  ncycle++;
54  for (casadi_int j=i; !seen[j]; j=p[j]) seen[j] = true;
55  }
56  }
57  return (static_cast<casadi_int>(p.size())-ncycle) % 2 ? -1.0 : 1.0;
58  }
59 
60  // Determinant of a single (irreducible) block via sparse Householder QR:
61  // B = PR' Q R PC => det(B) = sign(PR) sign(PC) det(Q) det(R)
62  static SX qr_block_det(const SX& B, bool amd) {
63  if (B.is_scalar()) return B;
64  SX V, R, beta;
65  std::vector<casadi_int> prinv, pc;
66  SX::qr_sparse(B, V, R, beta, prinv, pc, amd);
67  SXElem d = casadi_det(V.sparsity(), get_ptr(V.nonzeros()),
68  R.sparsity(), get_ptr(R.nonzeros()), get_ptr(beta.nonzeros()));
69  return permutation_sign(prinv) * permutation_sign(pc) * d;
70  }
71 
72  SX SymbolicQr::det(const SX& A, const Dict& opts) {
73  casadi_assert(A.is_square(), "det: matrix must be square, got " + A.dim() + ".");
74  // Fill-reducing ordering is on by default; controllable via the 'amd' option
75  bool amd = true;
76  for (auto&& op : opts) {
77  if (op.first=="amd") amd = op.second;
78  }
79 
80  // Exploit sparsity: a block-triangular permutation turns the determinant
81  // into a product of (smaller) diagonal-block determinants.
82  // det(A) = sign(rowperm) sign(colperm) prod_b det(block_b)
83  std::vector<casadi_int> rowperm, colperm, rowblock, colblock;
84  std::vector<casadi_int> coarse_rowblock, coarse_colblock;
85  casadi_int nb = A.sparsity().btf(rowperm, colperm, rowblock, colblock,
86  coarse_rowblock, coarse_colblock);
87  SX Aperm = A(rowperm, colperm);
88  SX ret = permutation_sign(rowperm) * permutation_sign(colperm);
89  for (casadi_int b=0; b<nb; ++b) {
90  std::vector<casadi_int> rr = range(rowblock[b], rowblock[b+1]);
91  std::vector<casadi_int> cc = range(colblock[b], colblock[b+1]);
92  // A non-square diagonal block means the matrix is structurally singular
93  if (rr.size()!=cc.size()) return 0;
94  ret = ret * qr_block_det(Aperm(rr, cc), amd);
95  }
96  return ret;
97  }
98 
99  extern "C"
100  void CASADI_LINSOL_SYMBOLICQR_EXPORT casadi_load_linsol_symbolicqr() {
102  }
103 
104  SymbolicQr::SymbolicQr(const std::string& name, const Sparsity& sp) :
105  LinsolInternal(name, sp) {
106  }
107 
109  clear_mem();
110  }
111 
114  {{"fopts",
115  {OT_DICT,
116  "Options to be passed to generated function objects"}}
117  }
118  };
119 
120  void SymbolicQr::init(const Dict& opts) {
121  // Call the base class initializer
122  LinsolInternal::init(opts);
123 
124  // Read options
125  for (auto&& op : opts) {
126  if (op.first=="fopts") {
127  fopts_ = op.second;
128  }
129  }
130 
131  // Symbolic expression for A
132  SX A = SX::sym("A", sp_);
133 
134  // BTF factorization
135  std::vector<casadi_int> rowperm, colperm, rowblock, colblock, coarse_rowblock, coarse_colblock;
136  sp_.btf(rowperm, colperm, rowblock, colblock, coarse_rowblock, coarse_colblock);
137 
138  // Get the inverted column permutation
139  std::vector<casadi_int> inv_colperm(colperm.size());
140  for (casadi_int k=0; k<colperm.size(); ++k)
141  inv_colperm[colperm[k]] = k;
142 
143  // Get the inverted row permutation
144  std::vector<casadi_int> inv_rowperm(rowperm.size());
145  for (casadi_int k=0; k<rowperm.size(); ++k)
146  inv_rowperm[rowperm[k]] = k;
147 
148  // Permute the linear system
149  SX Aperm = A(rowperm, colperm); // NOLINT(cppcoreguidelines-slicing)
150 
151  // Generate the QR factorization function
152  SX Q1, R1;
153  qr(Aperm, Q1, R1);
154  factorize_ = Function("QR_fact", {A}, {Q1, R1}, fopts_);
155 
156  // Symbolic expressions for solve function
157  SX Q = SX::sym("Q", Q1.sparsity());
158  SX R = SX::sym("R", R1.sparsity());
159  SX b = SX::sym("b", sp_.size2(), 1);
160 
161  // Solve non-transposed
162  // We have Pb' * Q * R * Px * x = b <=> x = Px' * inv(R) * Q' * Pb * b
163 
164  // Permute the right hand sides
165  SX bperm = b(rowperm, Slice()); // NOLINT(cppcoreguidelines-slicing)
166 
167  // Solve the factorized system
168  SX xperm = SX::solve(R, mtimes(Q.T(), bperm));
169 
170  // Permute back the solution
171  SX x = xperm(inv_colperm, Slice()); // NOLINT(cppcoreguidelines-slicing)
172 
173  // Generate the QR solve function
174  std::vector<SX> solv_in = {Q, R, b};
175  solve_ = Function("QR_solv", solv_in, {x}, fopts_);
176 
177  // Solve transposed
178  // We have (Pb' * Q * R * Px)' * x = b
179  // <=> Px' * R' * Q' * Pb * x = b
180  // <=> x = Pb' * Q * inv(R') * Px * b
181 
182  // Permute the right hand side
183  bperm = b(colperm, Slice()); // NOLINT(cppcoreguidelines-slicing)
184 
185  // Solve the factorized system
186  xperm = mtimes(Q, SX::solve(R.T(), bperm));
187 
188  // Permute back the solution
189  x = xperm(inv_rowperm, Slice()); // NOLINT(cppcoreguidelines-slicing)
190 
191  // Mofify the QR solve function
192  solveT_ = Function("QR_solv_T", solv_in, {x}, fopts_);
193  }
194 
195  int SymbolicQr::init_mem(void* mem) const {
196  if (LinsolInternal::init_mem(mem)) return 1;
197  auto m = static_cast<SymbolicQrMemory*>(mem);
198 
199  m->alloc(solveT_);
200  m->alloc(solve_);
201  m->alloc(factorize_);
202 
203  // Temporary storage
204  m->w.resize(m->w.size() + sp_.size1());
205 
206  // Allocate storage for QR factorization
207  m->q.resize(factorize_.nnz_out(0));
208  m->r.resize(factorize_.nnz_out(1));
209  return 0;
210  }
211 
212  int SymbolicQr::nfact(void* mem, const double* A) const {
213  auto m = static_cast<SymbolicQrMemory*>(mem);
214 
215  // Factorize
216  std::fill_n(get_ptr(m->arg), factorize_.n_in(), nullptr);
217  m->arg[0] = A;
218  std::fill_n(get_ptr(m->res), factorize_.n_out(), nullptr);
219  m->res[0] = get_ptr(m->q);
220  m->res[1] = get_ptr(m->r);
221  if (factorize_(get_ptr(m->arg), get_ptr(m->res), get_ptr(m->iw), get_ptr(m->w))) return 1;
222  return 0;
223  }
224 
225  int SymbolicQr::solve(void* mem, const double* A, double* x, casadi_int nrhs, bool tr) const {
226  auto m = static_cast<SymbolicQrMemory*>(mem);
227 
228  // Select solve function
229  const Function& solv = tr ? solveT_ : solve_;
230 
231  // Solve for all right hand sides
232  std::fill_n(get_ptr(m->arg), solv.n_in(), nullptr);
233  m->arg[0] = get_ptr(m->q);
234  m->arg[1] = get_ptr(m->r);
235  std::fill_n(get_ptr(m->res), solv.n_out(), nullptr);
236  for (casadi_int i=0; i<nrhs; ++i) {
237  std::copy_n(x, nrow(), get_ptr(m->w)); // Copy x to a temporary
238  m->arg[2] = get_ptr(m->w);
239  m->res[0] = x;
240  if (solv(get_ptr(m->arg), get_ptr(m->res),
241  get_ptr(m->iw), get_ptr(m->w)+nrow(), 0)) return 1;
242  x += nrow();
243  }
244  return 0;
245  }
246 
247  void SymbolicQr::linsol_eval_sx(const SXElem** arg, SXElem** res,
248  casadi_int* iw, SXElem* w, void* mem,
249  bool tr, casadi_int nrhs) const {
250  //auto m = static_cast<SymbolicQrMemory*>(mem);
251  casadi_assert_dev(arg[0]!=nullptr);
252  casadi_assert_dev(arg[1]!=nullptr);
253  casadi_assert_dev(res[0]!=nullptr);
254 
255  // Get A and factorize it
256  SX A = SX::zeros(sp_);
257  std::copy(arg[1], arg[1]+A.nnz(), A->begin());
258  std::vector<SX> v = factorize_(A);
259 
260  // Select solve function
261  const Function& solv = tr ? solveT_ : solve_;
262 
263  // Solve for every right hand side
264  v.push_back(SX::zeros(A.size1()));
265  const SXElem* a=arg[0];
266  SXElem* r=res[0];
267  for (casadi_int i=0; i<nrhs; ++i) {
268  std::copy(a, a+v[2].nnz(), v[2]->begin());
269  SX rr = solv(v).at(0);
270  std::copy(rr->begin(), rr->end(), r);
271  r += rr.nnz();
272  a += v[2].nnz();
273  }
274  }
275 
277  arg.resize(std::max(arg.size(), f.sz_arg()));
278  res.resize(std::max(res.size(), f.sz_res()));
279  iw.resize(std::max(iw.size(), f.sz_iw()));
280  w.resize(std::max(w.size(), f.sz_w()));
281  }
282 
284  s.version("SymbolicQr", 1);
285  s.unpack("SymbolicQr::factorize", factorize_);
286  s.unpack("SymbolicQr::solve", solve_);
287  s.unpack("SymbolicQr::solveT", solveT_);
288  s.unpack("SymbolicQr::fopts", fopts_);
289  }
290 
293  s.version("SymbolicQr", 1);
294  s.pack("SymbolicQr::factorize", factorize_);
295  s.pack("SymbolicQr::solve", solve_);
296  s.pack("SymbolicQr::solveT", solveT_);
297  s.pack("SymbolicQr::fopts", fopts_);
298  }
299 
300 } // namespace casadi
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
static const Options options_
Options.
Function object.
Definition: function.hpp:60
casadi_int nnz_out() const
Get number of output nonzeros.
Definition: function.cpp:1007
size_t sz_res() const
Get required length of res field.
Definition: function.cpp:1237
size_t sz_iw() const
Get required length of iw field.
Definition: function.cpp:1239
casadi_int n_out() const
Get the number of function outputs.
Definition: function.cpp:975
casadi_int n_in() const
Get the number of function inputs.
Definition: function.cpp:971
size_t sz_w() const
Get required length of w field.
Definition: function.cpp:1241
size_t sz_arg() const
Get required length of arg field.
Definition: function.cpp:1235
casadi_int nnz() const
Get the number of (structural) non-zero elements.
casadi_int size1() const
Get the first dimension (i.e. number of rows)
std::string dim(bool with_nz=false) const
Get string representation of dimensions.
static Matrix< Scalar > sym(const std::string &name, casadi_int nrow=1, casadi_int ncol=1)
Create an nrow-by-ncol symbolic primitive.
bool is_square() const
Check if the matrix expression is square.
static Matrix< Scalar > zeros(casadi_int nrow=1, casadi_int ncol=1)
Create a dense matrix or a matrix with specified sparsity with all entries zero.
bool is_scalar(bool scalar_and_dense=false) const
Check if the matrix expression is scalar.
void init(const Dict &opts) override
Initialize.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
casadi_int nnz() const
casadi_int nrow() const
Get sparsity pattern.
int init_mem(void *mem) const override
Initalize memory block.
Sparse matrix class. SX and DM are specializations.
Definition: matrix_decl.hpp:99
std::vector< Scalar > & nonzeros()
Matrix< Scalar > T() const
Transpose the matrix.
const Sparsity & sparsity() const
Const access the sparsity - reference to data member.
static Matrix< Scalar > solve(const Matrix< Scalar > &A, const Matrix< Scalar > &b)
static void qr_sparse(const Matrix< Scalar > &A, Matrix< Scalar > &V, Matrix< Scalar > &R, Matrix< Scalar > &beta, std::vector< casadi_int > &prinv, std::vector< casadi_int > &pc, bool amd=true)
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
void clear_mem()
Clear all memory (called from destructor)
The basic scalar symbolic class of CasADi.
Definition: sx_elem.hpp:75
Helper class for Serialization.
void version(const std::string &name, int v)
void pack(const Sparsity &e)
Serializes an object to the output stream.
Class representing a Slice.
Definition: slice.hpp:48
General sparsity class.
Definition: sparsity.hpp:106
casadi_int size1() const
Get the number of rows.
Definition: sparsity.cpp:124
casadi_int size2() const
Get the number of columns.
Definition: sparsity.cpp:128
casadi_int btf(std::vector< casadi_int > &rowperm, std::vector< casadi_int > &colperm, std::vector< casadi_int > &rowblock, std::vector< casadi_int > &colblock, std::vector< casadi_int > &coarse_rowblock, std::vector< casadi_int > &coarse_colblock) const
Calculate the block triangular form (BTF)
Definition: sparsity.cpp:713
int init_mem(void *mem) const override
Initalize memory block.
static const Options options_
Options.
Definition: symbolic_qr.hpp:99
SymbolicQr(const std::string &name, const Sparsity &sp)
void init(const Dict &opts) override
Initialize.
static LinsolInternal * creator(const std::string &name, const Sparsity &sp)
Create a new Linsol.
Definition: symbolic_qr.hpp:85
virtual double det(void *mem, const double *A) const
Symbolic determinant via sparse QR factorization.
static const std::string meta_doc
A documentation string.
~SymbolicQr() override
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
int solve(void *mem, const double *A, double *x, casadi_int nrhs, bool tr) const override
void linsol_eval_sx(const SXElem **arg, SXElem **res, casadi_int *iw, SXElem *w, void *mem, bool tr, casadi_int nrhs) const override
Evaluate symbolically (SX)
int nfact(void *mem, const double *A) const override
Numeric factorization.
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
The casadi namespace.
Definition: archiver.cpp:28
std::vector< casadi_int > range(casadi_int start, casadi_int stop, casadi_int step, casadi_int len)
Range function.
static double permutation_sign(const std::vector< casadi_int > &p)
Definition: linsol_qr.cpp:87
int CASADI_LINSOL_SYMBOLICQR_EXPORT casadi_register_linsol_symbolicqr(LinsolInternal::Plugin *plugin)
Definition: symbolic_qr.cpp:36
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
static SX qr_block_det(const SX &B, bool amd)
Definition: symbolic_qr.cpp:62
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
void CASADI_LINSOL_SYMBOLICQR_EXPORT casadi_load_linsol_symbolicqr()
Options metadata for a class.
Definition: options.hpp:40
Memory for SymbolicQR
Definition: symbolic_qr.hpp:49
void alloc(const Function &f)
std::vector< casadi_int > iw
Definition: symbolic_qr.hpp:53
std::vector< double * > res
Definition: symbolic_qr.hpp:52
std::vector< const double * > arg
Definition: symbolic_qr.hpp:51
std::vector< double > w
Definition: symbolic_qr.hpp:54