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"
39 const std::string& opt_name,
40 const std::string& msg) {
41 if (mode ==
"ignore")
return;
42 std::string full = msg +
"\n(Controlled by option '" + opt_name +
43 "'; set to 'ignore' to silence, 'warn' to demote, 'error' to escalate.)";
46 }
else if (mode ==
"error") {
49 casadi_error(
"Option '" + opt_name +
"' must be one of "
50 "'ignore', 'warn', 'error'; got '" + mode +
"'.");
55 return n >= 8 && (n & (n - 1)) == 0;
62 const std::vector<casadi_int>& ext,
63 const std::string& tag) {
65 for (
size_t i = 0; i < ext.size(); ++i) {
68 offenders.push_back(
"prefix product over dims 0.." +
str(i)
69 +
" = " +
str(p) + tag);
74 static std::vector<casadi_int>
knot_offsets(
const std::vector<casadi_int>& knot_dims) {
75 std::vector<casadi_int> offsets(knot_dims.size() + 1);
77 for (
size_t i = 0; i < knot_dims.size(); ++i)
78 offsets[i + 1] = offsets[i] + knot_dims[i];
93 casadi_int nd = offsets.size() - 1;
94 casadi_int degree = 3;
96 for (casadi_int d = 0; d < nd; ++d) {
97 casadi_int off = offsets[d];
98 casadi_int n_k = offsets[d + 1] - off;
99 M t = K(
Slice(off, off + n_k));
103 casadi_int ng = n_k - 2*degree;
107 M dg = t(n_k - degree - 1) - g0;
109 slope =
if_else_zero(dg,
static_cast<double>(ng-1) / (dg + 1e-100));
110 intercept = -g0 * slope;
112 slope = M::zeros(1, 1);
113 intercept = M::zeros(1, 1);
115 parts.push_back(intercept);
116 parts.push_back(slope);
119 for (casadi_int span = 1; span <= 3; ++span) {
123 diff = vertcat(
diff, M::zeros(span, 1));
126 inv_span = M::zeros(n_k, 1);
128 parts.push_back(inv_span);
131 return vertcat(parts);
138 const std::vector<MX>& knots_per_dim,
139 const std::vector<casadi_int>& degree,
140 const std::vector<casadi_int>& coeffs_dims,
142 casadi_int n_k = knots_per_dim[i].
size1();
143 casadi_int n = n_k - degree[i] - 1;
145 MX K_i = knots_per_dim[i];
146 MX delta_knots = K_i(
range(1+degree[i], n_k-1))
147 - K_i(
range(1, n_k-degree[i]-1));
148 MX d =
static_cast<double>(degree[i]) / delta_knots;
152 std::vector<casadi_int> coeffs_dims_new = coeffs_dims;
153 coeffs_dims_new[i+1] = n - 1;
155 casadi_int L = 1, R = 1;
156 for (casadi_int k=0; k<=i; ++k) L *= coeffs_dims[k];
157 for (casadi_int k=i+2; k<(casadi_int)coeffs_dims.size(); ++k) R *= coeffs_dims[k];
158 casadi_int K = coeffs_dims[i+1];
159 casadi_int Kp = n - 1;
161 MX M_coeffs = reshape(coeffs, L*K, R);
164 MX diffed = top - bot;
166 std::vector<casadi_int> dims{L, Kp, R};
167 std::vector<casadi_int> a{-1, -2, -3};
168 std::vector<casadi_int> b{-2};
169 std::vector<casadi_int> c{-1, -2, -3};
171 dims, std::vector<casadi_int>{Kp}, dims,
176 const std::vector< std::vector<double> >& knots,
182 const std::vector<casadi_int>& knot_dims,
184 bool precompute_coeff =
false, precompute_grid =
false;
185 auto it = opts.find(
"precompute_coeff");
186 if (it != opts.end()) precompute_coeff = it->second;
187 it = opts.
find(
"precompute_grid");
188 if (it != opts.end()) precompute_grid = it->second;
189 bool use_inv = precompute_grid;
192 precompute_coeff, precompute_grid, use_inv), opts);
193 if (!use_inv)
return F_inner;
196 std::vector<casadi_int> offsets =
knot_offsets(knot_dims);
197 casadi_int nd = knot_dims.size();
198 casadi_int nk = offsets.back();
203 std::vector<MX> ret = F_inner(std::vector<MX>{x,
C, knots, inv});
204 return Function(name, {x,
C, knots}, ret,
205 {
"x",
"C",
"knots"}, F_inner.
name_out(),
206 {{
"always_inline",
true}});
240 casadi_assert_dev(
false);
252 casadi_assert_dev(
false);
271 casadi_assert_dev(
false);
283 casadi_assert_dev(
false);
289 const std::vector< std::vector<double> >& knots,
290 casadi_int diff_order,
291 bool precompute_coeff,
293 precompute_coeff_(precompute_coeff), precompute_grid_(precompute_grid),
298 casadi_assert(knots.size()>=1,
"blazing_spline only defined for 1D-5D");
299 casadi_assert(knots.size()<=5,
"blazing_spline only defined for 1D-5D");
303 const std::vector<casadi_int>& knot_dims,
304 casadi_int diff_order,
305 bool precompute_coeff,
306 bool precompute_grid,
308 precompute_coeff_(precompute_coeff), precompute_grid_(precompute_grid),
309 inv_input_(inv_input) {
314 for (
size_t i=0; i<knot_dims.size(); ++i) {
320 casadi_assert(knot_dims.size()>=1,
"blazing_spline only defined for 1D-5D");
321 casadi_assert(knot_dims.size()<=5,
"blazing_spline only defined for 1D-5D");
330 casadi_int nd =
ndim();
334 for (casadi_int i=0; i<nd; ++i) {
340 for (casadi_int k=0;k<nd;++k) {
342 for (casadi_int i=0;i<nd;++i) {
349 for (casadi_int k=0;k<nd;++k) {
350 for (casadi_int kk=0;kk<nd;++kk) {
352 for (casadi_int i=0;i<nd;++i) {
371 {{
"precompute_coeff",
373 "If true, derivative evaluation requires precomputed derivative "
374 "coefficient tensors (dC, ddC) as function inputs. Only supported "
375 "up to 3D. Default: true for fixed knots, false for parametric knots."}},
378 "If true, precompute reciprocal knot spans to replace runtime "
379 "divisions with multiplications. For parametric knots, inv is "
380 "computed symbolically from the knots input. Default: false."}},
383 "Specifies, for each grid dimension, the lookup algorithm used to find the "
384 "correct index. 'linear' uses a forward linear search. 'exact' uses "
385 "a comparator function optimized for uniformly distributed data "
386 "(requires equally spaced knots). 'binary' uses a binary search. "
387 "'auto' (default) uses 'linear' for small grids and 'binary' for large."}},
388 {
"pedantic_mode_order",
390 "How to react when per-dimension knot counts are increasing "
391 "in dimension index. Deviating from this sorting may cost "
392 "up to ~30% speedup but may also be harmless of even slightly beneficial. "
393 "One of 'ignore', 'warn' (default), 'error'."}},
394 {
"pedantic_mode_size",
396 "How to react when an internal coefficient-tensor extent or "
397 "cumulative product is a power of 2 (8, 16, 32, ...). Such extents "
398 "cause cache-set aliasing on power-of-2 strides / cache eviction and "
399 "will incur costs. These costs can vary from 30% to 400% runtime. "
400 "One of 'ignore', 'warn', 'error' (default)."}}
409 for (
auto&& op : opts) {
410 if (op.first==
"precompute_coeff") {
412 }
else if (op.first==
"precompute_grid") {
414 }
else if (op.first==
"lookup_mode") {
416 }
else if (op.first==
"pedantic_mode_order") {
418 }
else if (op.first==
"pedantic_mode_size") {
423 casadi_int n_dims =
ndim();
426 casadi_assert(n_dims<=3,
427 "blazing_spline with precompute_coeff=true only supports up to 3D. "
428 "Use precompute_coeff=false for 4D/5D.");
432 std::vector<casadi_int> dim_sizes(n_dims);
433 for (casadi_int i = 0; i < n_dims; ++i) {
438 "blazing_spline '" +
name_ +
"': per-dimension knot counts " +
439 str(dim_sizes) +
" are not increasing in dimension index. "
440 "Deviating from this sorting may cost up to ~30% speedup but may "
441 "also be harmless of even slightly beneficial.");
449 std::vector<std::string> offenders;
450 std::vector<casadi_int> ext_nc(n_dims);
451 for (casadi_int i = 0; i < n_dims; ++i) {
455 offenders.push_back(
"dim " +
str(i) +
" (zero-based): "
456 "(n_knots - 4) = " +
str(ext_nc[i]));
462 for (casadi_int k = 0; k < n_dims; ++k) {
464 casadi_int f5 = n_k - 5;
466 offenders.push_back(
"dim " +
str(k) +
" (zero-based): "
467 "(n_knots - 5) = " +
str(f5) +
" (diff order 1)");
469 std::vector<casadi_int> ext = ext_nc;
472 " (diff order 1, d/dx_" +
str(k) +
")");
476 for (casadi_int k = 0; k < n_dims; ++k) {
477 for (casadi_int kk = k; kk < n_dims; ++kk) {
480 casadi_int f6 = n_k - 6;
482 offenders.push_back(
"dim " +
str(k) +
" (zero-based): "
483 "(n_knots - 6) = " +
str(f6) +
" (diff order 2)");
486 std::vector<casadi_int> ext = ext_nc;
490 " (diff order 2, d2/dx_" +
str(k) +
"dx_" +
str(kk) +
")");
494 if (!offenders.empty()) {
495 std::string msg =
"blazing_spline '" +
name_ +
"': internal "
496 "coefficient-tensor extents or cumulative products are powers of 2 "
497 "(8, 16, 32, ...). Such extents cause cache-set aliasing on "
498 "power-of-2 strides / cache eviction and will incur costs. These "
499 "costs can vary from 30% to 400% runtime. Adjust the number of "
500 "knots in the affected dimension(s). Offending:";
501 for (
const auto& s : offenders) msg +=
"\n - " + s;
515 casadi_int nd =
ndim();
522 default: casadi_assert_dev(
false);
530 std::string knots_inv;
539 std::vector<casadi_int> degree(nd, 3);
540 std::vector<casadi_int> mode =
544 std::string fun_name =
"casadi_blazing_" +
str(nd) +
"d_boor_eval";
545 std::string f_ptr =
"res[0]";
546 std::string J_ptr = (
diff_order_>=1) ?
"res[1]" :
"0";
547 std::string H_ptr = (
diff_order_>=2) ?
"res[2]" :
"0";
549 std::string dc_ptr =
"0", ddc_ptr =
"0";
556 g << fun_name +
"(" + f_ptr +
", " + J_ptr +
", " + H_ptr +
", " +
557 knots_stacked +
", " +
559 knots_offset +
", " +
560 "arg[1], " + dc_ptr +
", " + ddc_ptr +
", " +
571 const std::vector<std::string>& inames,
572 const std::vector<std::string>& onames,
573 const Dict& opts)
const {
574 casadi_int N =
ndim();
578 casadi_int n_inv = 2 * N + 3 * nk;
583 if (parametric) knots_sym =
MX::sym(
"knots", nk);
590 Jopts[
"derivative_of"] =
self();
595 Dict pedantic_defaults;
598 Jopts =
combine(Jopts, pedantic_defaults);
600 std::string fJname =
name_ +
"_der";
603 std::vector<casadi_int> coeffs_dims(N+1);
605 for (casadi_int i=0; i<N; ++i) {
608 std::vector<casadi_int> degree(N, 3);
613 std::vector< std::vector<casadi_int> > degree_d(N);
615 std::vector< std::vector< std::vector<double> > > knots_d_num(N);
617 std::vector<MX> K_per_dim;
622 for (casadi_int i=0; i<N; ++i) {
627 for (casadi_int i=0; i<N; ++i) {
632 i,
knots_, degree, coeffs_dims,
C, knots_d_num[i], degree_d[i]));
634 degree_d[i].assign(N, 3);
643 std::vector<std::pair<casadi_int, casadi_int>> dd_pairs;
644 for (casadi_int i=0; i<N; ++i) dd_pairs.emplace_back(i, i);
646 dd_pairs.emplace_back(0, 1);
648 dd_pairs.emplace_back(0, 1);
649 dd_pairs.emplace_back(1, 2);
650 dd_pairs.emplace_back(2, 0);
653 std::vector<MX> parts;
654 parts.reserve(dd_pairs.size());
655 std::vector< std::vector<double> > knots_dummy;
656 std::vector<casadi_int> degree_dummy;
657 for (
auto& p : dd_pairs) {
658 casadi_int di = p.first, dj = p.second;
659 std::vector<casadi_int> cd = coeffs_dims;
662 std::vector<MX> Kd(N);
663 for (casadi_int k=0; k<N; ++k) {
665 Kd[k] = (k==di) ? K_per_dim[k](
Slice(1, n_ki-1)) : K_per_dim[k];
670 dj, knots_d_num[di], degree_d[di], cd, dCv[di],
671 knots_dummy, degree_dummy));
674 ddC = vertcat(parts);
682 std::vector<casadi_int> kdims(N);
683 for (casadi_int i=0; i<N; ++i)
697 std::vector<MX> in_child = {x,
C};
698 if (parametric) in_child.push_back(knots_sym);
701 in_child.push_back(inv_mx);
704 in_child.push_back(dC);
708 std::vector<MX> ret = fJ(in_child);
711 std::vector<MX> jac_in = {x,
C};
712 std::vector<casadi_int> in_sizes = {N,
nc_};
713 if (parametric) { jac_in.push_back(knots_sym); in_sizes.push_back(nk); }
714 if (
inv_input_) { jac_in.push_back(inv_sym); in_sizes.push_back(n_inv); }
716 jac_in.push_back(
MX(1,
ndc_)); in_sizes.push_back(
ndc_);
720 std::vector<MX> jac_out;
722 casadi_int nrows = 1;
723 for (casadi_int j=0; j<k; ++j) nrows *= N;
724 for (
size_t j=0; j<jac_in.size(); ++j) {
725 jac_out.push_back(j==0 ? ret[k+1] :
MX(nrows, in_sizes[j]));
731 if (k==0) jac_in.push_back(
MX(1, 1));
732 else if (k==1) jac_in.push_back(
MX(1, N));
733 else if (k==2) jac_in.push_back(
MX(N, N));
736 return Function(name, jac_in, jac_out, inames, onames, {{
"always_inline",
true}});
742 s.
version(
"BlazingSplineFunction", 2);
746 s.
pack(
"BlazingSplineFunction::knots",
knots_);
758 int v = s.
version(
"BlazingSplineFunction", 1, 2);
771 s.
unpack(
"BlazingSplineFunction::parametric_knots", parametric);
786 class BlazingSplineIncrementalSerializer {
789 BlazingSplineIncrementalSerializer() : serializer(ss) {
792 std::string generate_id(
const std::vector<MX>& a) {
793 ref.insert(ref.end(), a.begin(), a.end());
794 if (a.empty())
return "";
802 serializer.pack(ordered);
805 serializer.pack(ordered);
806 std::string ret = ss.str();
813 std::stringstream ss;
816 SerializingStream serializer;
820 std::vector<MX>& subs_from,
821 std::vector<MX>& subs_to)
const {
829 Function f(
"f", {}, arg, {{
"allow_free",
true}, {
"max_io", 0}});
832 std::unordered_map<std::string, std::vector<MX> > targets0;
833 std::unordered_map<std::string, std::vector<MX> > targets1;
834 std::vector<MX> targets2;
836 BlazingSplineIncrementalSerializer ss;
840 for (
int k=0; k<f.n_instructions(); ++k) {
841 MX e = f.instruction_MX(k);
846 if (fun.
class_name()==
"BlazingSplineFunction") {
847 key = ss.generate_id(e->
dep_);
850 targets0[key].push_back(e);
853 targets1[key].push_back(e);
857 targets2.push_back(e);
864 for (
const auto& e : targets2) {
871 ss.generate_id(e->dep_);
874 for (
const auto& ee : targets1[key]) {
876 subs_from.push_back(ee);
878 subs_to.push_back(e);
890 for (
const auto& ee : targets0[key]) {
892 subs_from.push_back(ee);
894 subs_to.push_back(e);
899 for (
const auto& ee : targets1) {
900 for (
const auto& e : ee.second) {
906 ss.generate_id(e->dep_);
909 for (
const auto& ee : targets0[key]) {
911 subs_from.push_back(ee);
913 subs_to.push_back(e);
static M derivative_coeff(casadi_int i, const std::vector< std::vector< double > > &knots, const std::vector< casadi_int > °ree, 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< std::string > lookup_modes_
std::vector< casadi_int > knots_offset_
std::vector< double > knots_stacked_
std::vector< double > knots_inv_
~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.
BlazingSplineFunction(const std::string &name, const std::vector< std::vector< double > > &knots, casadi_int diff_order, bool precompute_coeff=true, bool precompute_grid=false)
Constructor (fixed knots)
bool get_diff_in(casadi_int i) override
Which inputs are differentiable?
std::vector< std::vector< double > > knots_
bool has_parametric_knots() const
Are knots parametric (provided at runtime)?
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.
std::string pedantic_mode_size_
std::string pedantic_mode_order_
Sparsity get_sparsity_out(casadi_int i) override
Sparsities of function inputs and outputs.
casadi_int arg_knots() const
Index of the knots input (only valid when parametric)
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.
casadi_int arg_inv() const
Index of the inv input (only valid when inv_input_)
void codegen_body(CodeGenerator &g) const override
Generate code for the function body.
casadi_int ndim() const
Number of dimensions.
size_t get_n_in() override
Number of function inputs and outputs.
void init_derived_members()
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 arg(casadi_int i) const
Refer to argument.
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 <....
@ AUX_BLAZING_5D_BOOR_EVAL
@ AUX_BLAZING_2D_BOOR_EVAL
@ AUX_BLAZING_4D_BOOR_EVAL
@ AUX_BLAZING_1D_BOOR_EVAL
@ AUX_BLAZING_3D_BOOR_EVAL
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.
virtual void find(std::map< FunctionInternal *, std::pair< Function, size_t > > &all_fun, casadi_int max_depth) const
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.
static Function create(FunctionInternal *node)
Create from node.
static std::vector< SX > order(const std::vector< SX > &expr)
std::pair< casadi_int, casadi_int > size_in(casadi_int ind) const
Get input dimension.
const std::vector< std::string > & name_out() const
Get output scheme.
casadi_int size1() const
Get the first dimension (i.e. number of rows)
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)
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
static MX einstein(const MX &A, const MX &B, const MX &C, const std::vector< casadi_int > &dim_a, const std::vector< casadi_int > &dim_b, const std::vector< casadi_int > &dim_c, const std::vector< casadi_int > &a, const std::vector< casadi_int > &b, const std::vector< casadi_int > &c)
Computes an einstein dense tensor contraction.
bool is_call() const
Check if evaluation.
Function which_function() const
Get function - only valid when is_call() is true.
std::vector< Scalar > & nonzeros()
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.
Class representing a Slice.
static Sparsity dense(casadi_int nrow, casadi_int ncol=1)
Create a dense rectangular sparsity pattern *.
std::vector< casadi_int > range(casadi_int start, casadi_int stop, casadi_int step, casadi_int len)
Range function.
Function blazing_spline(const std::string &name, const std::vector< std::vector< double > > &knots, const Dict &opts)
Construct a specialized parametric BSpline.
static std::vector< casadi_int > knot_offsets(const std::vector< casadi_int > &knot_dims)
double if_else_zero(double x, double y)
Conditional assignment.
static M compute_knots_cache(const M &K, const std::vector< casadi_int > &offsets)
static bool is_pow2_ge8(casadi_int n)
Dict combine(const Dict &first, const Dict &second, bool recurse)
Combine two dicts. First has priority.
static void scan_prefix_pow2(std::vector< std::string > &offenders, const std::vector< casadi_int > &ext, const std::string &tag)
std::string str(const T &v)
String representation, any type.
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
static MX derivative_coeff_mx(casadi_int i, const std::vector< MX > &knots_per_dim, const std::vector< casadi_int > °ree, const std::vector< casadi_int > &coeffs_dims, const MX &coeffs)
std::vector< T > diff(const std::vector< T > &values)
diff
static void handle_pedantic(const std::string &mode, const std::string &opt_name, const std::string &msg)
std::vector< T > vector_init(const std::vector< T > &v)
Return all but the last element of a vector.
bool is_nondecreasing(const std::vector< T > &v)
Check if the vector is non-decreasing.
Options metadata for a class.