linsol_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 "linsol_qr.hpp"
27 #include "casadi/core/global_options.hpp"
28 
29 namespace casadi {
30 
31  extern "C"
32  int CASADI_LINSOL_QR_EXPORT
33  casadi_register_linsol_qr(LinsolInternal::Plugin* plugin) {
34  plugin->creator = LinsolQr::creator;
35  plugin->name = "qr";
36  plugin->doc = LinsolQr::meta_doc.c_str();
37  plugin->version = CASADI_VERSION;
38  plugin->options = &LinsolQr::options_;
39  plugin->deserialize = &LinsolQr::deserialize;
40  return 0;
41  }
42 
43  extern "C"
44  void CASADI_LINSOL_QR_EXPORT casadi_load_linsol_qr() {
46  }
47 
48  LinsolQr::LinsolQr(const std::string& name, const Sparsity& sp)
49  : LinsolInternal(name, sp) {
50  }
51 
53  clear_mem();
54  }
55 
58  {{"eps",
59  {OT_DOUBLE,
60  "Minimum R entry before singularity is declared [1e-12]"}},
61  {"cache",
62  {OT_DOUBLE,
63  "Amount of factorisations to remember (thread-local) [0]"}}
64  }
65  };
66 
67  void LinsolQr::init(const Dict& opts) {
68  // Call the init method of the base class
70 
71  // Read options
72  eps_ = 1e-12;
73  n_cache_ = 0;
74  for (auto&& op : opts) {
75  if (op.first=="eps") {
76  eps_ = op.second;
77  } else if (op.first=="cache") {
78  n_cache_ = op.second;
79  }
80  }
81 
82  // Symbolic factorization
84  }
85 
86  // Sign (+1/-1) of a permutation: (-1)^(n - number of cycles)
87  static double permutation_sign(const std::vector<casadi_int>& p) {
88  std::vector<bool> seen(p.size(), false);
89  casadi_int ncycle = 0;
90  for (casadi_int i=0; i<static_cast<casadi_int>(p.size()); ++i) {
91  if (!seen[i]) {
92  ncycle++;
93  for (casadi_int j=i; !seen[j]; j=p[j]) seen[j] = true;
94  }
95  }
96  return (static_cast<casadi_int>(p.size())-ncycle) % 2 ? -1 : 1;
97  }
98 
101  // Determinant of the (data-independent) row and column pivoting
104  }
105 
106  int LinsolQr::init_mem(void* mem) const {
107  if (LinsolInternal::init_mem(mem)) return 1;
108  auto m = static_cast<LinsolQrMemory*>(mem);
109 
110  // Memory for numerical solution
111  m->v.resize(sp_v_.nnz());
112  m->r.resize(sp_r_.nnz());
113  m->beta.resize(ncol());
114  m->w.resize(nrow() + ncol());
115 
116  m->cache.resize(cache_stride_*n_cache_);
117  m->cache_loc.resize(n_cache_, -1);
118 
119  return 0;
120  }
121 
122  int LinsolQr::sfact(void* mem, const double* A) const {
123  return 0;
124  }
125 
126  int LinsolQr::nfact(void* mem, const double* A) const {
127  auto m = static_cast<LinsolQrMemory*>(mem);
128 
129  // Check for a cache hit
130  double* cache = nullptr;
131  bool cache_hit = casadi_cache_check(A, get_ptr(m->cache), get_ptr(m->cache_loc),
132  cache_stride_, n_cache_, sp_.nnz(), &cache);
133 
134  if (cache && cache_hit) {
135  cache += sp_.nnz();
136  // Retrieve from cache and return early
137  casadi_copy(cache, sp_v_.nnz(), get_ptr(m->v)); cache+=sp_v_.nnz();
138  casadi_copy(cache, sp_r_.nnz(), get_ptr(m->r)); cache+=sp_r_.nnz();
139  casadi_copy(cache, ncol(), get_ptr(m->beta)); cache+=ncol();
140  return 0;
141  }
142 
143  // Cache miss -> compute result
144  casadi_qr(sp_, A, get_ptr(m->w),
145  sp_v_, get_ptr(m->v), sp_r_, get_ptr(m->r),
146  get_ptr(m->beta), get_ptr(prinv_), get_ptr(pc_));
147  // Check singularity
148  double rmin;
149  casadi_int irmin, nullity;
150  nullity = casadi_qr_singular(&rmin, &irmin, get_ptr(m->r), sp_r_, get_ptr(pc_), eps_);
151  if (nullity) {
152  if (verbose_) {
153  print("Singularity detected: Rank %lld<%lld\n", ncol()-nullity, ncol());
154  print("First singular R entry: %g<%g, corresponding to row %lld\n", rmin, eps_, irmin);
155  casadi_qr_colcomb(get_ptr(m->w), get_ptr(m->r), sp_r_, get_ptr(pc_), eps_, 0);
156  print("Linear combination of columns:\n[");
157  for (casadi_int k=0; k<ncol(); ++k) print(k==0 ? "%g" : ", %g", m->w[k]);
158  print("]\n");
159  }
160  return 1;
161  }
162 
163  if (cache) { // Store result in cache
164  casadi_copy(A, sp_.nnz(), cache); cache+=sp_.nnz();
165  casadi_copy(get_ptr(m->v), sp_v_.nnz(), cache); cache+=sp_v_.nnz();
166  casadi_copy(get_ptr(m->r), sp_r_.nnz(), cache); cache+=sp_r_.nnz();
167  casadi_copy(get_ptr(m->beta), ncol(), cache); cache+=ncol();
168  }
169  return 0;
170  }
171 
172  int LinsolQr::solve(void* mem, const double* A, double* x, casadi_int nrhs, bool tr) const {
173  auto m = static_cast<LinsolQrMemory*>(mem);
174  casadi_qr_solve(x, nrhs, tr,
175  sp_v_, get_ptr(m->v), sp_r_, get_ptr(m->r),
176  get_ptr(m->beta), get_ptr(prinv_), get_ptr(pc_), get_ptr(m->w));
177  return 0;
178  }
179 
180  double LinsolQr::det(void* mem, const double* A) const {
181  auto m = static_cast<LinsolQrMemory*>(mem);
182  return pivot_sign_ * casadi_det(sp_v_, get_ptr(m->v), sp_r_, get_ptr(m->r),
183  get_ptr(m->beta));
184  }
185 
186  void LinsolQr::generate_factorize(CodeGenerator& g, const std::string& A) const {
187  // Codegen the integer vectors
188  std::string prinv = g.constant(prinv_);
189  std::string pc = g.constant(pc_);
190  std::string sp = g.sparsity(sp_);
191  std::string sp_v = g.sparsity(sp_v_);
192  std::string sp_r = g.sparsity(sp_r_);
193 
194  // Carve the factorization workspace from w (reserved by sz_w_fact())
195  g.local("qr_v", "casadi_real", "*");
196  g << "qr_v = w;\n";
197  g.local("qr_r", "casadi_real", "*");
198  g << "qr_r = w+" << sp_v_.nnz() << ";\n";
199  g.local("qr_beta", "casadi_real", "*");
200  g << "qr_beta = w+" << sp_v_.nnz() + sp_r_.nnz() << ";\n";
201  g.local("qr_w", "casadi_real", "*");
202  g << "qr_w = w+" << sp_v_.nnz() + sp_r_.nnz() + ncol() << ";\n";
203 
204  if (n_cache_) {
205  // Place the cache in a block to scope its (untouched) local arrays
206  g << "{\n";
207  g << "casadi_real *c;\n";
208  g << "casadi_real cache[" << cache_stride_*n_cache_ << "];\n";
209  g << "int cache_loc[" << n_cache_ << "] = {";
210  for (casadi_int i=0;i<n_cache_;++i) {
211  g << "-1,";
212  }
213  g << "};\n";
214  g << "if (" << g.cache_check(A, "cache", "cache_loc",
215  cache_stride_, n_cache_, sp_.nnz(), "&c") << ") {\n";
216  casadi_int offset = sp_.nnz();
217  g.comment("Retrieve from cache");
218  g << g.copy("c+" + str(offset), sp_v_.nnz(), "qr_v") << "\n"; offset+=sp_v_.nnz();
219  g << g.copy("c+" + str(offset), sp_r_.nnz(), "qr_r") << "\n"; offset+=sp_r_.nnz();
220  g << g.copy("c+" + str(offset), ncol(), "qr_beta") << "\n"; offset+=ncol();
221  g << "} else {\n";
222  }
223 
224  // Factorize
225  g << g.qr(sp, A, "qr_w", sp_v, "qr_v", sp_r, "qr_r", "qr_beta", prinv, pc) << "\n";
226 
227  if (n_cache_) {
228  casadi_int offset = 0;
229  g.comment("Store in cache");
230  g << g.copy(A, sp_.nnz(), "c") << "\n";; offset+=sp_.nnz();
231  g << g.copy("qr_v", sp_v_.nnz(), "c+"+str(offset)) << "\n"; offset+=sp_v_.nnz();
232  g << g.copy("qr_r", sp_r_.nnz(), "c+"+str(offset)) << "\n"; offset+=sp_r_.nnz();
233  g << g.copy("qr_beta", ncol(), "c+"+str(offset)) << "\n"; offset+=ncol();
234  g << "}\n";
235  // End of cache block
236  g << "}\n";
237  }
238  }
239 
240  void LinsolQr::generate(CodeGenerator& g, const std::string& A, const std::string& x,
241  casadi_int nrhs, bool tr) const {
242  generate_factorize(g, A);
243 
244  // Solve
245  g << g.qr_solve(x, nrhs, tr, g.sparsity(sp_v_), "qr_v", g.sparsity(sp_r_), "qr_r",
246  "qr_beta", g.constant(prinv_), g.constant(pc_), "qr_w") << "\n";
247  }
248 
249  void LinsolQr::generate_det(CodeGenerator& g, const std::string& A,
250  const std::string& d) const {
251  generate_factorize(g, A);
252 
253  // Determinant from the factors, corrected by the pivoting sign
254  g << d << " = " << (pivot_sign_ < 0 ? "-" : "")
255  << g.det(g.sparsity(sp_v_), "qr_v", g.sparsity(sp_r_), "qr_r", "qr_beta") << ";\n";
256  }
257 
259  int version = s.version("LinsolQr", 1, 2);
260  s.unpack("LinsolQr::prinv", prinv_);
261  s.unpack("LinsolQr::pc", pc_);
262  s.unpack("LinsolQr::sp_v", sp_v_);
263  s.unpack("LinsolQr::sp_r", sp_r_);
264  s.unpack("LinsolQr::eps", eps_);
265  if (version>1) {
266  s.unpack("LinsolQr::n_cache", n_cache_);
267  } else {
268  n_cache_ = 1;
269  }
270  }
271 
274  s.version("LinsolQr", 2);
275  s.pack("LinsolQr::prinv", prinv_);
276  s.pack("LinsolQr::pc", pc_);
277  s.pack("LinsolQr::sp_v", sp_v_);
278  s.pack("LinsolQr::sp_r", sp_r_);
279  s.pack("LinsolQr::eps", eps_);
280  s.pack("LinsolQr::n_cache", n_cache_);
281  }
282 
283 } // namespace casadi
Helper class for C code generation.
std::string copy(const std::string &arg, std::size_t n, const std::string &res)
Create a copy operation.
void comment(const std::string &s)
Write a comment line (ignored if not verbose)
std::string constant(const std::vector< casadi_int > &v)
Represent an array constant; adding it when new.
void local(const std::string &name, const std::string &type, const std::string &ref="")
Declare a local variable.
std::string cache_check(const std::string &key, const std::string &cache, const std::string &loc, casadi_int stride, casadi_int sz, casadi_int key_sz, const std::string &val)
cache check
std::string qr_solve(const std::string &x, casadi_int nrhs, bool tr, const std::string &sp_v, const std::string &v, const std::string &sp_r, const std::string &r, const std::string &beta, const std::string &prinv, const std::string &pc, const std::string &w)
QR solve.
std::string det(const std::string &sp_v, const std::string &v, const std::string &sp_r, const std::string &r, const std::string &beta)
Determinant from sparse QR factors.
std::string sparsity(const Sparsity &sp, bool canonical=true)
std::string qr(const std::string &sp, const std::string &A, const std::string &w, const std::string &sp_v, const std::string &v, const std::string &sp_r, const std::string &r, const std::string &beta, const std::string &prinv, const std::string &pc)
QR factorization.
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
void init(const Dict &opts) override
Initialize.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
casadi_int nrow() const
Get sparsity pattern.
casadi_int ncol() const
int init_mem(void *mem) const override
Initalize memory block.
static const std::string meta_doc
A documentation string.
Definition: linsol_qr.hpp:126
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize with type disambiguation.
Definition: linsol_qr.hpp:144
int sfact(void *mem, const double *A) const override
Definition: linsol_qr.cpp:122
~LinsolQr() override
Definition: linsol_qr.cpp:52
int solve(void *mem, const double *A, double *x, casadi_int nrhs, bool tr) const override
Definition: linsol_qr.cpp:172
int nfact(void *mem, const double *A) const override
Numeric factorization.
Definition: linsol_qr.cpp:126
std::vector< casadi_int > pc_
Definition: linsol_qr.hpp:129
void generate_det(CodeGenerator &g, const std::string &A, const std::string &d) const override
Generate C code for the determinant.
Definition: linsol_qr.cpp:249
std::vector< casadi_int > prinv_
Symbolic factorization.
Definition: linsol_qr.hpp:129
casadi_int cache_stride_
Definition: linsol_qr.hpp:138
static LinsolInternal * creator(const std::string &name, const Sparsity &sp)
Create a new LinsolInternal.
Definition: linsol_qr.hpp:62
double pivot_sign_
Sign of the row/column pivoting permutations (data-independent)
Definition: linsol_qr.hpp:134
void generate(CodeGenerator &g, const std::string &A, const std::string &x, casadi_int nrhs, bool tr) const override
Generate C code.
Definition: linsol_qr.cpp:240
int init_mem(void *mem) const override
Initalize memory block.
Definition: linsol_qr.cpp:106
void generate_factorize(CodeGenerator &g, const std::string &A) const
Definition: linsol_qr.cpp:186
casadi_int n_cache_
Cache size.
Definition: linsol_qr.hpp:137
double det(void *mem, const double *A) const override
Determinant.
Definition: linsol_qr.cpp:180
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
Definition: linsol_qr.cpp:272
static const Options options_
Options.
Definition: linsol_qr.hpp:77
void finalize() override
Finalize the object creation.
Definition: linsol_qr.cpp:99
LinsolQr(const std::string &name, const Sparsity &sp)
Definition: linsol_qr.cpp:48
void init(const Dict &opts) override
Initialize.
Definition: linsol_qr.cpp:67
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
void print(const char *fmt,...) const
C-style formatted printing during evaluation.
bool verbose_
Verbose printout.
virtual void finalize()
Finalize the object creation.
static const Options options_
Options.
void clear_mem()
Clear all memory (called from destructor)
Helper class for Serialization.
void version(const std::string &name, int v)
void pack(const Sparsity &e)
Serializes an object to the output stream.
General sparsity class.
Definition: sparsity.hpp:106
casadi_int nnz() const
Get the number of (structural) non-zeros.
Definition: sparsity.cpp:148
void qr_sparse(Sparsity &V, Sparsity &R, std::vector< casadi_int > &prinv, std::vector< casadi_int > &pc, bool amd=true) const
Symbolic QR factorization.
Definition: sparsity.cpp:655
The casadi namespace.
Definition: archiver.cpp:28
static double permutation_sign(const std::vector< casadi_int > &p)
Definition: linsol_qr.cpp:87
void casadi_copy(const T1 *x, casadi_int n, T1 *y)
COPY: y <-x.
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
void CASADI_LINSOL_QR_EXPORT casadi_load_linsol_qr()
Definition: linsol_qr.cpp:44
T * get_ptr(std::vector< T > &v)
Get a pointer to the data contained in the vector.
int CASADI_LINSOL_QR_EXPORT casadi_register_linsol_qr(LinsolInternal::Plugin *plugin)
Definition: linsol_qr.cpp:33
std::vector< double > v
Definition: linsol_qr.hpp:44
Options metadata for a class.
Definition: options.hpp:40