blazing_spline.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 "blazing_spline_impl.hpp"
27 #include "interpolant_impl.hpp"
28 #include "bspline_impl.hpp"
29 #include "casadi_misc.hpp"
30 #include "serializer.hpp"
31 
32 #include <fstream>
33 #include <iostream>
34 #include <sstream>
35 
36 namespace casadi {
37 
38  Function blazing_spline(const std::string& name,
39  const std::vector< std::vector<double> >& knots,
40  const Dict& opts) {
41  return Function::create(new BlazingSplineFunction(name, knots, 0), opts);
42  }
43 
45  return 2+diff_order_;
46  }
48  return diff_order_+1;
49  }
50 
52  return i==0;
53  }
54 
56  if (i==0) {
57  return Sparsity::dense(knots_.size());
58  } else if (i==1) {
59  return Sparsity::dense(nc_);
60  } else if (i==2) {
61  return Sparsity::dense(ndc_);
62  } else if (i==3) {
63  return Sparsity::dense(nddc_);
64  } else {
65  casadi_assert_dev(false);
66  return Sparsity();
67  }
68  }
70  if (i==0) {
71  return Sparsity::dense(1, 1);
72  } else if (i==1) {
73  return Sparsity::dense(1, knots_.size());
74  } else if (i==2) {
75  return Sparsity::dense(knots_.size(), knots_.size());
76  } else {
77  casadi_assert_dev(false);
78  return Sparsity();
79  }
80  }
81 
82  std::string BlazingSplineFunction::get_name_in(casadi_int i) {
83  if (i==0) {
84  return "x";
85  } else if (i==1) {
86  return "C";
87  } else if (i==2) {
88  return "dC";
89  } else if (i==3) {
90  return "ddC";
91  } else {
92  casadi_assert_dev(false);
93  return "";
94  }
95  }
96  std::string BlazingSplineFunction::get_name_out(casadi_int i) {
97  if (i==0) {
98  return "f";
99  } else if (i==1) {
100  return "g";
101  } else if (i==2) {
102  return "h";
103  } else {
104  casadi_assert_dev(false);
105  return "";
106  }
107  }
108 
110  const std::vector< std::vector<double> >& knots,
111  casadi_int diff_order) : FunctionInternal(name), diff_order_(diff_order), knots_(knots) {
112 
114 
115  casadi_assert(knots.size()>=1, "blazing_spline only defined for 1D/2D/3D");
116  casadi_assert(knots.size()<=3, "blazing_spline only defined for 1D/2D/3D");
117  }
118 
120  // Compute coefficient tensor size
121  nc_ = 1;
122  for (const auto& e : knots_) {
123  nc_ *= e.size()-4;
124  }
125 
126  // Compute coefficient tensor size
127  ndc_ = 0;
128  for (casadi_int k=0;k<knots_.size();++k) {
129  casadi_int ndc = 1;
130  for (casadi_int i=0;i<knots_.size();++i) {
131  ndc *= knots_[i].size() - 4 - (i==k);
132  }
133  ndc_+= ndc;
134  }
135 
136  nddc_ = 0;
137  for (casadi_int k=0;k<knots_.size();++k) {
138  for (casadi_int kk=0;kk<knots_.size();++kk) {
139  casadi_int ndc = 1;
140  for (casadi_int i=0;i<knots_.size();++i) {
141  ndc *= knots_[i].size() - 4 - (i==k)-(i==kk);
142  }
143  // We only need the triangular part
144  if (kk>=k) {
145  nddc_+= ndc;
146  }
147  }
148  }
149 
150 
151  std::vector<casadi_int> offset;
152  std::vector<double> stacked;
154  }
155 
158  {
159  }
160  };
161 
162  void BlazingSplineFunction::init(const Dict& opts) {
163  // Call the initialization method of the base class
165 
166  casadi_int n_dims = knots_.size();
167 
168  // Arrays for holding inputs and outputs
169  alloc_iw(4*n_dims+2);
170  alloc_w(n_dims+1);
171  }
172 
174  clear_mem();
175  }
176 
178  if (knots_.size() == 3) {
180  }
181  if (knots_.size() == 2) {
183  }
184  if (knots_.size() == 1) {
186  }
187  g.add_include("simde/x86/avx2.h");
188  g.add_include("simde/x86/fma.h");
189 
190  std::string knots_offset = g.constant(knots_offset_);
191  std::string knots_stacked = g.constant(knots_stacked_);
192 
193  std::vector<std::string> lookup_mode;
194  std::vector<casadi_int> degree(3, knots_.size());
195  std::vector<casadi_int> mode =
197  lookup_mode, knots_stacked_, knots_offset_, degree, degree);
198 
199  std::string fun_name = "casadi_blazing_" + str(knots_.size()) + "d_boor_eval";
200 
201  if (diff_order_==0) {
202  // Codegen function body
203  g << fun_name + "(res[0], 0, 0, " +
204  knots_stacked + ", " +
205  knots_offset + ", " +
206  "arg[1], 0, 0, " +
207  "arg[0], " +
208  g.constant(mode) + ", " +
209  "iw, w);\n";
210  } else if (diff_order_==1) {
211  // Codegen function body
212  g << fun_name + "(res[0], res[1], 0, " +
213  knots_stacked + ", " +
214  knots_offset + ", " +
215  "arg[1], arg[2], 0, " +
216  "arg[0], " +
217  g.constant(mode) + ", " +
218  "iw, w);\n";
219  } else if (diff_order_==2) {
220  // Codegen function body
221  g << fun_name + "(res[0], res[1], res[2], " +
222  knots_stacked + ", " +
223  knots_offset + ", " +
224  "arg[1], arg[2], arg[3], " +
225  "arg[0], " +
226  g.constant(mode) + ", " +
227  "iw, w);\n";
228  } else {
229  casadi_assert_dev(false);
230  }
231  }
232 
234  return diff_order_<2;
235  }
236 
238  const std::vector<std::string>& inames,
239  const std::vector<std::string>& onames,
240  const Dict& opts) const {
241  size_t N = knots_.size();
242  MX C = MX::sym("C", nc_);
243  MX x = MX::sym("x", N);
244 
245  std::vector<casadi_int> coeffs_dims(N+1);
246  coeffs_dims[0] = 1;
247  for (casadi_int i=0; i<N; ++i) {
248  coeffs_dims[i+1] = knots_[i].size()-4;
249  }
250 
251  std::vector<casadi_int> degree(N, 3);
252  std::vector< std::vector< std::vector<double> > > knots_d(N);
253  std::vector< std::vector< casadi_int> > degree_d(N);
254 
255  std::vector<MX> dCv;
256  for (size_t i=0;i<N;++i) {
257  dCv.push_back(
259  i, knots_stacked_, knots_offset_, degree, coeffs_dims, C, knots_d[i], degree_d[i]));
260  }
261 
262  MX dC = vertcat(dCv);
263 
265  Jopts = combine(opts, Jopts);
266  Jopts = combine(Jopts, generate_options("jacobian"));
267  Jopts["derivative_of"] = self();
268 
269  std::string fJname = name_ + "_der";
270 
271  Function fJ;
272  if (!incache(fJname, fJ)) {
273  fJ = Function::create(new BlazingSplineFunction(fJname, knots_, diff_order_+1), Jopts);
274  // Save in cache
275  tocache(fJ);
276  }
277 
278  if (diff_order_==0) {
279  std::vector<MX> ret = fJ(std::vector<MX>{x, C, dC});
280 
281  Function jac(name,
282  {x, C, MX(1, 1)},
283  {ret[1], MX(1, nc_)},
284  inames,
285  onames,
286  {{"always_inline", true}});
287 
288  return jac;
289  } else {
290  MX ddC;
291  if (N==3) {
292  int size0 = knots_[0].size();
293  int size1 = knots_[1].size();
294  int size2 = knots_[2].size();
295  std::vector<casadi_int> offset;
296  std::vector<double> stacked;
297  std::vector< std::vector<double> > knots_dummy;
298  std::vector< casadi_int> degree_dummy;
299  Interpolant::stack_grid(knots_d[0], offset, stacked);
300  MX ddC00 = BSplineCommon::derivative_coeff(0, stacked, offset, degree_d[0],
301  {1, size0-5, size1-4, size2-4}, dCv[0], knots_dummy, degree_dummy);
302  Interpolant::stack_grid(knots_d[1], offset, stacked);
303  MX ddC11 = BSplineCommon::derivative_coeff(1, stacked, offset, degree_d[1],
304  {1, size0-4, size1-5, size2-4}, dCv[1], knots_dummy, degree_dummy);
305  Interpolant::stack_grid(knots_d[2], offset, stacked);
306  MX ddC22 = BSplineCommon::derivative_coeff(2, stacked, offset, degree_d[2],
307  {1, size0-4, size1-4, size2-5}, dCv[2], knots_dummy, degree_dummy);
308  Interpolant::stack_grid(knots_d[0], offset, stacked);
309  MX ddC01 = BSplineCommon::derivative_coeff(1, stacked, offset, degree_d[0],
310  {1, size0-5, size1-4, size2-4}, dCv[0], knots_dummy, degree_dummy);
311  Interpolant::stack_grid(knots_d[1], offset, stacked);
312  MX ddC12 = BSplineCommon::derivative_coeff(2, stacked, offset, degree_d[1],
313  {1, size0-4, size1-5, size2-4}, dCv[1], knots_dummy, degree_dummy);
314  Interpolant::stack_grid(knots_d[2], offset, stacked);
315  MX ddC20 = BSplineCommon::derivative_coeff(0, stacked, offset, degree_d[2],
316  {1, size0-4, size1-4, size2-5}, dCv[2], knots_dummy, degree_dummy);
317  ddC = vertcat(ddC00, ddC11, ddC22, ddC01, ddC12, ddC20);
318  } else if (N==2) {
319  int size0 = knots_[0].size();
320  int size1 = knots_[1].size();
321  std::vector<casadi_int> offset;
322  std::vector<double> stacked;
323  std::vector< std::vector<double> > knots_dummy;
324  std::vector< casadi_int> degree_dummy;
325  Interpolant::stack_grid(knots_d[0], offset, stacked);
326  MX ddC00 = BSplineCommon::derivative_coeff(0, stacked, offset, degree_d[0],
327  {1, size0-5, size1-4}, dCv[0], knots_dummy, degree_dummy);
328  Interpolant::stack_grid(knots_d[1], offset, stacked);
329  MX ddC11 = BSplineCommon::derivative_coeff(1, stacked, offset, degree_d[1],
330  {1, size0-4, size1-5}, dCv[1], knots_dummy, degree_dummy);
331  Interpolant::stack_grid(knots_d[0], offset, stacked);
332  MX ddC01 = BSplineCommon::derivative_coeff(1, stacked, offset, degree_d[0],
333  {1, size0-5, size1-4}, dCv[0], knots_dummy, degree_dummy);
334  ddC = vertcat(ddC00, ddC11, ddC01);
335  } else if (N==1) {
336  int size0 = knots_[0].size();
337  std::vector<casadi_int> offset;
338  std::vector<double> stacked;
339  std::vector< std::vector<double> > knots_dummy;
340  std::vector< casadi_int> degree_dummy;
341  Interpolant::stack_grid(knots_d[0], offset, stacked);
342  ddC = BSplineCommon::derivative_coeff(0, stacked, offset, degree_d[0],
343  {1, size0-5}, dCv[0], knots_dummy, degree_dummy);
344  }
345 
346  std::vector<MX> ret = fJ(std::vector<MX>{x, C, dC, ddC});
347 
348  Function jac(name,
349  {x, C, MX(1, ndc_), MX(1, 1), MX(1, N)},
350  {ret[1], MX(1, nc_), MX(1, ndc_), ret[2], MX(N, nc_), MX(N, ndc_)},
351  inames,
352  onames,
353  {{"always_inline", true}});
354 
355  return jac;
356  }
357  }
358 
361 
362  s.version("BlazingSplineFunction", 1);
363  s.pack("BlazingSplineFunction::diff_order", diff_order_);
364  s.pack("BlazingSplineFunction::knots", knots_);
365  }
366 
368  s.version("BlazingSplineFunction", 1);
369  s.unpack("BlazingSplineFunction::diff_order", diff_order_);
370  s.unpack("BlazingSplineFunction::knots", knots_);
372  }
373 
375  return new BlazingSplineFunction(s);
376  }
377 
378  class BlazingSplineIncrementalSerializer {
379  public:
380 
381  BlazingSplineIncrementalSerializer() : serializer(ss) {
382  }
383 
384  std::string generate_id(const std::vector<MX>& a) {
385  ref.insert(ref.end(), a.begin(), a.end());
386  if (a.empty()) return "";
387 
388  std::vector<MX> ordered = Function::order(a);
389  // First serialize may introduce unknown dependencies (e.g. sparsity)
390  // and hence definitions
391  // Subsequent serialization will have references instead.
392  // In order to still get a match with a later common subexpression,
393  // make sure that all dependencies are already defined.
394  serializer.pack(ordered);
395  ss.str("");
396  ss.clear();
397  serializer.pack(ordered);
398  std::string ret = ss.str();
399  ss.str("");
400  ss.clear();
401  return ret;
402  }
403 
404  private:
405  std::stringstream ss;
406  // List of references to keep alive
407  std::vector<MX> ref;
408  SerializingStream serializer;
409  };
410 
411  void BlazingSplineFunction::merge(const std::vector<MX>& arg,
412  std::vector<MX>& subs_from,
413  std::vector<MX>& subs_to) const {
414 
415  Function base = self();
416  for (casadi_int i=0;i<diff_order_;++i) {
417  base = base->derivative_of_;
418  }
419 
420  // Sort graph
421  Function f("f", {}, arg, {{"allow_free", true}, {"max_io", 0}});
422 
423 
424  std::unordered_map<std::string, std::vector<MX> > targets0;
425  std::unordered_map<std::string, std::vector<MX> > targets1;
426  std::vector<MX> targets2;
427 
428  BlazingSplineIncrementalSerializer ss;
429  std::string key;
430 
431  // Loop over instructions
432  for (int k=0; k<f.n_instructions(); ++k) {
433  MX e = f.instruction_MX(k);
434  if (e.is_call()) {
435  Function fun = e.which_function();
436 
437  // Check if the function is a BlazingSplineFunction
438  if (fun.class_name()=="BlazingSplineFunction") {
439  key = ss.generate_id(e->dep_);
440  // Which derivative level?
441  if (fun==base) {
442  targets0[key].push_back(e);
443  } else if (!fun->derivative_of_.is_null() &&
444  fun->derivative_of_==base) {
445  targets1[key].push_back(e);
446  } else if (!fun->derivative_of_.is_null() &&
448  fun->derivative_of_->derivative_of_==base) {
449  targets2.push_back(e);
450  }
451  }
452  }
453  }
454 
455  // Loop over second order targets, targets2
456  for (const auto& e : targets2) {
457 
458  // Compute key of all but last args
459  key = ss.generate_id(vector_init(e->dep_));
460 
461  // Loop over all matching target1 entries
462  for (const auto& ee : targets1[key]) {
463  // Mark all matches for substitution
464  subs_from.push_back(ee);
465  // Substitute with self
466  subs_to.push_back(e);
467  }
468 
469  // Compute key of all but last args
470  key = ss.generate_id(vector_init(vector_init(e->dep_)));
471 
472  // Loop over all matching target1 entries
473  for (const auto& ee : targets0[key]) {
474  // Mark all matches for substitution
475  subs_from.push_back(ee);
476  // Substitute with self
477  subs_to.push_back(e);
478  }
479  }
480 
481  // Loop over first order targets, targets1
482  for (const auto& ee : targets1) {
483  for (const auto& e : ee.second) {
484  // Compute key of all but last args
485  key = ss.generate_id(vector_init(e->dep_));
486 
487  // Loop over all matching target1 entries
488  for (const auto& ee : targets0[key]) {
489  // Mark all matches for substitution
490  subs_from.push_back(ee);
491  // Substitute with self
492  subs_to.push_back(e);
493  }
494  }
495  }
496 
497  }
498 
499 
500 } // namespace casadi
static M derivative_coeff(casadi_int i, const std::vector< double > &knots, const std::vector< casadi_int > &offset, const std::vector< casadi_int > &degree, const std::vector< casadi_int > &coeffs_dims, const M &coeffs, std::vector< std::vector< double > > &new_knots, std::vector< casadi_int > &new_degree)
static ProtoFunction * deserialize(DeserializingStream &s)
Deserialize into MX.
std::string get_name_out(casadi_int i) override
Names of function input and outputs.
std::vector< casadi_int > knots_offset_
std::vector< double > knots_stacked_
~BlazingSplineFunction() override
Destructor.
Sparsity get_sparsity_in(casadi_int i) override
Sparsities of function inputs and outputs.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
bool get_diff_in(casadi_int i) override
Which inputs are differentiable?
std::vector< std::vector< double > > knots_
bool has_jacobian() const override
Jacobian of all outputs with respect to all inputs.
size_t get_n_out() override
Number of function inputs and outputs.
BlazingSplineFunction(const std::string &name, const std::vector< std::vector< double > > &knots, casadi_int diff_order)
Constructor.
Sparsity get_sparsity_out(casadi_int i) override
Sparsities of function inputs and outputs.
Function get_jacobian(const std::string &name, const std::vector< std::string > &inames, const std::vector< std::string > &onames, const Dict &opts) const override
Jacobian of all outputs with respect to all inputs.
static const Options options_
Options.
void init(const Dict &opts) override
Initialize.
void codegen_body(CodeGenerator &g) const override
Generate code for the function body.
size_t get_n_in() override
Number of function inputs and outputs.
std::string get_name_in(casadi_int i) override
Names of function input and outputs.
void merge(const std::vector< MX > &arg, std::vector< MX > &subs_from, std::vector< MX > &subs_to) const override
List merge opportunitities.
Helper class for C code generation.
std::string constant(const std::vector< casadi_int > &v)
Represent an array constant; adding it when new.
void add_include(const std::string &new_include, bool relative_path=false, const std::string &use_ifdef=std::string())
Add an include file optionally using a relative path "..." instead of an absolute path <....
void add_auxiliary(Auxiliary f, const std::vector< std::string > &inst={"casadi_real"})
Add a built-in auxiliary function.
Helper class for Serialization.
void unpack(Sparsity &e)
Reconstruct an object from the input stream.
void version(const std::string &name, int v)
Internal class for Function.
void alloc_iw(size_t sz_iw, bool persistent=false)
Ensure required length of iw field.
void init(const Dict &opts) override
Initialize.
void serialize_body(SerializingStream &s) const override
Serialize an object without type information.
bool incache(const std::string &fname, Function &f, const std::string &suffix="") const
Get function in cache.
static const Options options_
Options.
void alloc_w(size_t sz_w, bool persistent=false)
Ensure required length of w field.
void tocache(const Function &f, const std::string &suffix="") const
Save function to cache.
Function derivative_of_
If the function is the derivative of another function.
Dict generate_options(const std::string &target) const override
Reconstruct options dict.
Function object.
Definition: function.hpp:60
static Function create(FunctionInternal *node)
Create from node.
Definition: function.cpp:336
static std::vector< SX > order(const std::vector< SX > &expr)
Definition: function.cpp:1938
static MX sym(const std::string &name, casadi_int nrow=1, casadi_int ncol=1)
Create an nrow-by-ncol symbolic primitive.
bool is_null() const
Is a null pointer?
static void stack_grid(const std::vector< std::vector< double > > &grid, std::vector< casadi_int > &offset, std::vector< double > &stacked)
Definition: interpolant.cpp:46
static std::vector< casadi_int > interpret_lookup_mode(const std::vector< std::string > &modes, const std::vector< double > &grid, const std::vector< casadi_int > &offset, const std::vector< casadi_int > &margin_left=std::vector< casadi_int >(), const std::vector< casadi_int > &margin_right=std::vector< casadi_int >())
Convert from (optional) lookup modes labels to enum.
std::vector< MX > dep_
dependencies - functions that have to be evaluated before this one
Definition: mx_node.hpp:762
MX - Matrix expression.
Definition: mx.hpp:92
bool is_call() const
Check if evaluation.
Definition: mx.cpp:774
Function which_function() const
Get function - only valid when is_call() is true.
Definition: mx.cpp:778
Base class for FunctionInternal and LinsolInternal.
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.
std::string class_name() const
Get class name.
General sparsity class.
Definition: sparsity.hpp:106
static Sparsity dense(casadi_int nrow, casadi_int ncol=1)
Create a dense rectangular sparsity pattern *.
Definition: sparsity.cpp:1012
The casadi namespace.
Definition: archiver.cpp:28
Function blazing_spline(const std::string &name, const std::vector< std::vector< double > > &knots, const Dict &opts)
Construct a specialized parametric BSpline.
Dict combine(const Dict &first, const Dict &second, bool recurse)
Combine two dicts. First has priority.
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
std::vector< T > vector_init(const std::vector< T > &v)
Return all but the last element of a vector.
Options metadata for a class.
Definition: options.hpp:40