finite_differences.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 "finite_differences.hpp"
27 
28 namespace casadi {
29 
30 std::string to_string(FdMode v) {
31  switch (v) {
32  case FdMode::FORWARD: return "forward";
33  case FdMode::BACKWARD: return "backward";
34  case FdMode::CENTRAL: return "central";
35  case FdMode::SMOOTHING: return "smoothing";
36  default: break;
37  }
38  return "";
39 }
40 
41 casadi_int n_fd_points(FdMode v) {
42  switch (v) {
43  case FdMode::FORWARD:
44  case FdMode::BACKWARD: return 2;
45  case FdMode::CENTRAL: return 3;
46  case FdMode::SMOOTHING: return 5;
47  default: break;
48  }
49  return -1;
50 }
51 
52 casadi_int fd_offset(FdMode v) {
53  switch (v) {
54  case FdMode::FORWARD: return 0;
55  case FdMode::BACKWARD:
56  case FdMode::CENTRAL: return 1;
57  case FdMode::SMOOTHING: return 2;
58  default: break;
59  }
60  return -1;
61 }
62 
63 FiniteDiff::FiniteDiff(const std::string& name, casadi_int n)
64  : FunctionInternal(name), n_(n) {
65 }
66 
68  clear_mem();
69 }
70 
73  {{"second_order_stepsize",
74  {OT_DOUBLE,
75  "Second order perturbation size [default: 1e-3]"}},
76  {"h",
77  {OT_DOUBLE,
78  "Step size [default: computed from abstol]"}},
79  {"h_max",
80  {OT_DOUBLE,
81  "Maximum step size [default 0]"}},
82  {"h_min",
83  {OT_DOUBLE,
84  "Minimum step size [default inf]"}},
85  {"smoothing",
86  {OT_DOUBLE,
87  "Smoothing regularization [default: machine precision]"}},
88  {"reltol",
89  {OT_DOUBLE,
90  "Accuracy of function inputs [default: query object]"}},
91  {"abstol",
92  {OT_DOUBLE,
93  "Accuracy of function outputs [default: query object]"}},
94  {"u_aim",
95  {OT_DOUBLE,
96  "Target ratio of roundoff error to truncation error [default: 100.]"}},
97  {"h_iter",
98  {OT_INT,
99  "Number of iterations to improve on the step-size "
100  "[default: 1 if error estimate available, otherwise 0]"}},
101  }
102 };
103 
104 void FiniteDiff::init(const Dict& opts) {
105  // Call the initialization method of the base class
107 
108  // Default options
109  h_min_ = 0;
110  h_max_ = inf;
111  m_.smoothing = eps;
115  u_aim_ = 100;
116  h_iter_ = has_err() ? 1 : 0;
117 
118  // Read options
119  for (auto&& op : opts) {
120  if (op.first=="h") {
121  h_ = op.second;
122  } else if (op.first=="h_min") {
123  h_min_ = op.second;
124  } else if (op.first=="h_max") {
125  h_max_ = op.second;
126  } else if (op.first=="reltol") {
127  m_.reltol = op.second;
128  } else if (op.first=="abstol") {
129  m_.abstol = op.second;
130  } else if (op.first=="smoothing") {
131  m_.smoothing = op.second;
132  } else if (op.first=="u_aim") {
133  u_aim_ = op.second;
134  } else if (op.first=="h_iter") {
135  h_iter_ = op.second;
136  }
137  }
138 
139  // Check h_iter for consistency
140  if (h_iter_!=0 && !has_err()) {
141  casadi_error("Perturbation size refinement requires an error estimate, "
142  "which is not available for the class '" + class_name() + "'. "
143  "Choose a different differencing scheme.");
144  }
145 
146  // Allocate work vector for (perturbed) inputs and outputs
149  alloc_res(n_pert(), true); // yk
150  alloc_w((n_pert() + 3) * n_y_, true); // yk[:], y0, y, J
151  alloc_w(n_z_, true); // z
152 
153  // Dimensions
154  if (verbose_) {
155  casadi_message("Finite differences (" + class_name() + ") with "
156  + str(n_z_) + " inputs, " + str(n_y_)
157  + " outputs and " + str(n_) + " directional derivatives.");
158  }
159 
160  // Allocate sufficient temporary memory for function evaluation
162 }
163 
165  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
166  if (i<n_in) {
167  // Non-differentiated input
168  return derivative_of_.sparsity_in(i);
169  } else if (i<n_in+n_out) {
170  // Non-differentiated output
171  return derivative_of_.sparsity_out(i-n_in);
172  } else {
173  // Seeds
174  casadi_int ii = i - n_in - n_out;
175  if (is_diff_in_[i]) {
176  return repmat(derivative_of_.sparsity_in(ii), 1, n_);
177  } else {
178  return Sparsity(derivative_of_.size1_in(ii),
180  }
181  }
182 }
183 
185  return repmat(derivative_of_.sparsity_out(i), 1, n_);
186 }
187 
188 double FiniteDiff::get_default_in(casadi_int ind) const {
189  if (ind<derivative_of_.n_in()) {
190  return derivative_of_.default_in(ind);
191  } else {
192  return 0;
193  }
194 }
195 
198 }
199 
201  return derivative_of_.n_out();
202 }
203 
204 std::string FiniteDiff::get_name_in(casadi_int i) {
205  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
206  if (i<n_in) {
207  return derivative_of_.name_in(i);
208  } else if (i<n_in+n_out) {
209  return "out_" + derivative_of_.name_out(i-n_in);
210  } else {
211  return "fwd_" + derivative_of_.name_in(i-n_in-n_out);
212  }
213 }
214 
215 std::string FiniteDiff::get_name_out(casadi_int i) {
216  return "fwd_" + derivative_of_.name_out(i);
217 }
218 
219 Function CentralDiff::get_forward(casadi_int nfwd, const std::string& name,
220  const std::vector<std::string>& inames,
221  const std::vector<std::string>& onames,
222  const Dict& opts) const {
223  // Commented out, does not work well
224 #if 0
225  // The second order derivative is calculated as the backwards derivative
226  // of the forward derivative, which is equivalent to central differences
227  // of second order
228  std::string f_name = "fd_" + name;
229  Dict f_opts = {{"derivative_of", derivative_of_}};
230  Function f = Function::create(new ForwardDiff(f_name, n_, h_), f_opts);
231  // Calculate backwards derivative of f
232  f_opts["derivative_of"] = f;
233  return Function::create(new ForwardDiff(name, nfwd, -h_), f_opts);
234 #endif
235  return Function::create(new CentralDiff(name, nfwd), opts);
236 }
237 
238 int FiniteDiff::eval(const double** arg, double** res,
239  casadi_int* iw, double* w, void* mem) const {
240  setup(mem, arg, res, iw, w);
241  // Shorthands
242  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
243  casadi_int n_pert = this->n_pert();
244 
245  // Non-differentiated input
246  const double** x0 = arg;
247  arg += n_in;
248 
249  // Non-differentiated output
250  double* y0 = w;
251  for (casadi_int j=0; j<n_out; ++j) {
252  const casadi_int nnz = derivative_of_.nnz_out(j);
253  casadi_copy(*arg++, nnz, w);
254  w += nnz;
255  }
256 
257  // Forward seeds
258  const double** seed = arg;
259  arg += n_in;
260 
261  // Forward sensitivities
262  double** sens = res;
263  res += n_out;
264 
265  // Finite difference approximation
266  double* J = w;
267  w += n_y_;
268 
269  // Perturbed function values
270  double** yk = res;
271  res += n_pert;
272  for (casadi_int j=0; j<n_pert; ++j) {
273  yk[j] = w, w += n_y_;
274  }
275 
276  // Setup arg and z for evaluation
277  double *z = w;
278  for (casadi_int j=0; j<n_in; ++j) {
279  arg[j] = w;
280  w += derivative_of_.nnz_in(j);
281  }
282 
283  // Setup res and y for evaluation
284  double *y = w;
285  for (casadi_int j=0; j<n_out; ++j) {
286  res[j] = w;
287  w += derivative_of_.nnz_out(j);
288  }
289 
290  // For all sensitivity directions
291  for (casadi_int i=0; i<n_; ++i) {
292  // Initial stepsize
293  double h = h_;
294  // Perform finite difference algorithm with different step sizes
295  for (casadi_int iter=0; iter<1+h_iter_; ++iter) {
296  // Calculate perturbed function values
297  for (casadi_int k=0; k<n_pert; ++k) {
298  // Perturb inputs
299  casadi_int off = 0;
300  for (casadi_int j=0; j<n_in; ++j) {
301  casadi_int nnz = derivative_of_.nnz_in(j);
302  casadi_copy(x0[j], nnz, z + off);
303  if (seed[j] && is_diff_in_[j]) casadi_axpy(nnz, pert(k, h), seed[j] + i*nnz, z + off);
304  off += nnz;
305  }
306  // Evaluate
307  if (derivative_of_(arg, res, iw, w)) return 1;
308  // Save outputs
309  casadi_copy(y, n_y_, yk[k]);
310  }
311  // Finite difference calculation with error estimate
312  double u = calc_fd(yk, y0, J, h);
313  if (iter==h_iter_) break;
314 
315  // Update step size
316  if (u < 0) {
317  // Perturbation failed, try a smaller step size
318  h /= u_aim_;
319  } else {
320  // Update h to get u near the target ratio
321  h *= sqrt(u_aim_ / fmax(1., u));
322  }
323  // Make sure h stays in the range [h_min_,h_max_]
324  h = fmin(fmax(h, h_min_), h_max_);
325  }
326 
327  // Gather sensitivities
328  casadi_int off = 0;
329  for (casadi_int j=0; j<n_out; ++j) {
330  casadi_int nnz = derivative_of_.nnz_out(j);
331  if (sens[j]) casadi_copy(J + off, nnz, sens[j] + i*nnz);
332  off += nnz;
333  }
334  }
335  return 0;
336 }
337 
338 double ForwardDiff::calc_fd(double** yk, double* y0, double* J, double h) const {
339  return casadi_forward_diff_old(yk, y0, J, h, n_y_, &m_);
340 }
341 
342 double CentralDiff::calc_fd(double** yk, double* y0, double* J, double h) const {
343  return casadi_central_diff_old(yk, y0, J, h, n_y_, &m_);
344 }
345 
349 }
350 
352  // Shorthands
353  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
354  casadi_int n_pert = this->n_pert();
355 
356  g.comment("Non-differentiated input");
357  g.local("x0", "const casadi_real", "**");
358  g << "x0 = arg, arg += " << n_in << ";\n";
359 
360  g.comment("Non-differentiated output");
361  g.local("y0", "casadi_real", "*");
362  g << "y0 = w;\n";
363  for (casadi_int j=0; j<n_out; ++j) {
364  const casadi_int nnz = derivative_of_.nnz_out(j);
365  g << g.copy("*arg++", nnz, "w") << " w += " << nnz << ";\n";
366  }
367 
368  g.comment("Forward seeds");
369  g.local("seed", "const casadi_real", "**");
370  g << "seed = arg, arg += " << n_in << ";\n";
371 
372  g.comment("Forward sensitivities");
373  g.local("sens", "casadi_real", "**");
374  g << "sens = res, res += " << n_out << ";\n";
375 
376  g.comment("Finite difference approximation");
377  g.local("J", "casadi_real", "*");
378  g << "J = w, w += " << n_y_ << ";\n";
379 
380  g.comment("Perturbed function value");
381  g.local("yk", "casadi_real", "**");
382  g << "yk = res, res += " << n_pert << ";\n";
383  g.local("j", "casadi_int");
384  g << "for (j=0; j<" << n_pert << "; ++j) yk[j] = w, w += " << n_y_ << ";\n";
385 
386  g.comment("Setup arg and z for evaluation");
387  g.local("z", "casadi_real", "*");
388  g << "z = w;\n";
389  for (casadi_int j=0; j<n_in; ++j) {
390  g << g.arg(j) << " = w, w += " << derivative_of_.nnz_in(j) << ";\n";
391  }
392 
393  g.comment("Setup res and y for evaluation");
394  g.local("y", "casadi_real", "*");
395  g << "y = w;\n";
396  for (casadi_int j=0; j<n_out; ++j) {
397  g << g.res(j) << " = w, w += " << derivative_of_.nnz_out(j) << ";\n";
398  }
399 
400  g.comment("For all sensitivity directions");
401  g.local("i", "casadi_int");
402  g << "for (i=0; i<" << n_ << "; ++i) {\n";
403 
404  g.comment("Initial stepsize");
405  g.local("h", "casadi_real");
406  g << "h = " << g.constant(h_) << ";\n";
407 
408  g.comment("Perform finite difference algorithm with different step sizes");
409  g.local("iter", "casadi_int");
410  g << "for (iter=0; iter<" << 1+h_iter_ << "; ++iter) {\n";
411 
412  g.comment("Calculate perturbed function values");
413  g.local("k", "casadi_int");
414  g << "for (k=0; k<" << n_pert << "; ++k) {\n";
415 
416  g.comment("Perturb inputs");
417  casadi_int off=0;
418  for (casadi_int j=0; j<n_in; ++j) {
419  casadi_int nnz = derivative_of_.nnz_in(j);
420  std::string s = "seed[" + str(j) + "]";
421  g << g.copy("x0[" + str(j) + "]", nnz, "z+" + str(off)) << "\n";
422  if (is_diff_in_[j]) {
423  g << "if ("+s+") " << g.axpy(nnz, pert("k", "h"),
424  s+"+i*"+str(nnz), "z+" + str(off)) << "\n";
425  }
426  off += nnz;
427  }
428 
429  g.comment("Evaluate");
430  g << "if (" << g(derivative_of_, "arg", "res", "iw", "w") << ") return 1;\n";
431 
432  g.comment("Save outputs");
433  g << g.copy("y", n_y_, "yk[k]") << "\n";
434 
435  g << "}\n"; // for (k=0, ...)
436 
437  g.comment("Finite difference calculation with error estimate");
438  g.local("u", "casadi_real");
439  g.local("m", "const struct casadi_finite_diff_mem");
440  g.init_local("m", "{" + g.constant(m_.reltol) + ", "
441  + g.constant(m_.abstol) + ", "
442  + g.constant(m_.smoothing) + "}");
443  g << "u = " << calc_fd() << "(yk, y0, J, h, " << n_y_ << ", &m);\n";
444  g << "if (iter==" << h_iter_ << ") break;\n";
445 
446  g.comment("Update step size");
447  g << "if (u < 0) {\n";
448  // Perturbation failed, try a smaller step size
449  g << "h /= " << u_aim_ << ";\n";
450  g << "} else {\n";
451  // Update h to get u near the target ratio
452  g << "h *= sqrt(" << u_aim_ << " / fmax(1., u));\n";
453  g << "}\n";
454  // Make sure h stays in the range [h_min_,h_max_]
455  if (h_min_>0 || isfinite(h_max_)) {
456  std::string h = "h";
457  if (h_min_>0) h = "fmax(" + h + ", " + g.constant(h_min_) + ")";
458  if (isfinite(h_max_)) h = "fmin(" + h + ", " + g.constant(h_max_) + ")";
459  g << "h = " << h << ";\n";
460  }
461 
462  g << "}\n"; // for (iter=0, ...)
463 
464  g.comment("Gather sensitivities");
465  off = 0;
466  for (casadi_int j=0; j<n_out; ++j) {
467  casadi_int nnz = derivative_of_.nnz_out(j);
468  std::string s = "sens[" + str(j) + "]";
469  g << "if (" << s << ") " << g.copy("J+" + str(off), nnz, s + "+i*" + str(nnz)) << "\n";
470  off += nnz;
471  }
472  g << "}\n"; // for (i=0, ...)
473 }
474 
475 std::string Smoothing::pert(const std::string& k, const std::string& h) const {
476  std::string sign = "(2*(" + k + "/2)-1)";
477  std::string len = "(" + k + "%2+1)";
478  return len + "*" + sign + "*" + h;
479 }
480 
481 double Smoothing::pert(casadi_int k, double h) const {
482  casadi_int sign = 2*(k/2)-1;
483  casadi_int len = k%2+1;
484  return static_cast<double>(len*sign)*h;
485 }
486 
487 double Smoothing::calc_fd(double** yk, double* y0, double* J, double h) const {
488  return casadi_smoothing_diff_old(yk, y0, J, h, n_y_, &m_);
489 }
490 
491 Function ForwardDiff::get_forward(casadi_int nfwd, const std::string& name,
492  const std::vector<std::string>& inames,
493  const std::vector<std::string>& onames,
494  const Dict& opts) const {
495  return Function::create(new ForwardDiff(name, nfwd), opts);
496 }
497 
498 Function Smoothing::get_forward(casadi_int nfwd, const std::string& name,
499  const std::vector<std::string>& inames,
500  const std::vector<std::string>& onames,
501  const Dict& opts) const {
502  return Function::create(new Smoothing(name, nfwd), opts);
503 }
504 
505 } // namespace casadi
CentralDiff(const std::string &name, casadi_int n)
std::string calc_fd() const override
Function get_forward(casadi_int nfwd, const std::string &name, const std::vector< std::string > &inames, const std::vector< std::string > &onames, const Dict &opts) const override
Second order derivatives.
Helper class for C code generation.
std::string axpy(casadi_int n, const std::string &a, const std::string &x, const std::string &y)
Codegen axpy: y += a*x.
std::string add_dependency(const Function &f)
Add a function dependency.
std::string arg(casadi_int i) const
Refer to argument.
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 res(casadi_int i) const
Refer to resuly.
void init_local(const std::string &name, const std::string &def)
Specify the default value for a local variable.
void add_auxiliary(Auxiliary f, const std::vector< std::string > &inst={"casadi_real"})
Add a built-in auxiliary function.
size_t get_n_in() override
Number of function inputs and outputs.
int eval(const double **arg, double **res, casadi_int *iw, double *w, void *mem) const override
Evaluate numerically.
virtual casadi_int n_pert() const =0
void codegen_declarations(CodeGenerator &g) const override
Generate code for the declarations of the C function.
virtual double calc_stepsize(double abstol) const =0
void init(const Dict &opts) override
Initialize.
void codegen_body(CodeGenerator &g) const override
Generate code for the body of the C function.
Sparsity get_sparsity_in(casadi_int i) override
Sparsities of function inputs and outputs.
virtual std::string pert(const std::string &k, const std::string &h) const =0
std::string get_name_in(casadi_int i) override
Names of function input and outputs.
double get_default_in(casadi_int ind) const override
Get default input value.
~FiniteDiff() override
Destructor.
virtual casadi_int has_err() const =0
std::string get_name_out(casadi_int i) override
Names of function input and outputs.
virtual std::string calc_fd() const =0
static const Options options_
Options.
size_t get_n_out() override
Number of function inputs and outputs.
Sparsity get_sparsity_out(casadi_int i) override
Sparsities of function inputs and outputs.
casadi_finite_diff_mem< double > m_
FiniteDiff(const std::string &name, casadi_int n)
Function get_forward(casadi_int nfwd, const std::string &name, const std::vector< std::string > &inames, const std::vector< std::string > &onames, const Dict &opts) const override
Second order derivatives.
std::string calc_fd() const override
ForwardDiff(const std::string &name, casadi_int n)
Internal class for Function.
void init(const Dict &opts) override
Initialize.
void alloc_res(size_t sz_res, bool persistent=false)
Ensure required length of res field.
static const Options options_
Options.
void alloc_w(size_t sz_w, bool persistent=false)
Ensure required length of w field.
void setup(void *mem, const double **arg, double **res, casadi_int *iw, double *w) const
Set the (persistent and temporary) work vectors.
virtual double get_abstol() const
Get absolute tolerance.
void alloc(const Function &f, bool persistent=false, int num_threads=1)
Ensure work vectors long enough to evaluate function.
virtual double get_reltol() const
Get relative tolerance.
std::vector< bool > is_diff_in_
Are inputs and outputs differentiable?
Function derivative_of_
If the function is the derivative of another function.
Function object.
Definition: function.hpp:60
casadi_int nnz_out() const
Get number of output nonzeros.
Definition: function.cpp:1007
const Sparsity & sparsity_out(casadi_int ind) const
Get sparsity of a given output.
Definition: function.cpp:1183
casadi_int size1_in(casadi_int ind) const
Get input dimension.
Definition: function.cpp:979
const std::vector< std::string > & name_in() const
Get input scheme.
Definition: function.cpp:1113
static Function create(FunctionInternal *node)
Create from node.
Definition: function.cpp:488
const Sparsity & sparsity_in(casadi_int ind) const
Get sparsity of a given input.
Definition: function.cpp:1167
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
casadi_int nnz_in() const
Get number of input nonzeros.
Definition: function.cpp:1003
casadi_int size2_in(casadi_int ind) const
Get input dimension.
Definition: function.cpp:983
const std::vector< std::string > & name_out() const
Get output scheme.
Definition: function.cpp:1117
double default_in(casadi_int ind) const
Get default input value.
Definition: function.cpp:1677
bool verbose_
Verbose printout.
void clear_mem()
Clear all memory (called from destructor)
virtual std::string class_name() const =0
Readable name of the internal class.
Function get_forward(casadi_int nfwd, const std::string &name, const std::vector< std::string > &inames, const std::vector< std::string > &onames, const Dict &opts) const override
Second order derivatives.
std::string calc_fd() const override
std::string pert(const std::string &k, const std::string &h) const override
Smoothing(const std::string &name, casadi_int n)
General sparsity class.
Definition: sparsity.hpp:106
The casadi namespace.
Definition: archiver.cpp:28
FdMode
Variable type.
const double eps
Machine epsilon.
Definition: calculus.hpp:56
casadi_int n_fd_points(FdMode v)
Length of FD stencil, including unperturbed input.
casadi_int fd_offset(FdMode v)
Offset for FD stencil, i.e. index of unperturbed input.
double sign(double x)
Sign function, note that sign(nan) == nan.
Definition: calculus.hpp:270
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.
const double inf
infinity
Definition: calculus.hpp:50
std::string to_string(TypeFmi2 v)
void casadi_axpy(casadi_int n, T1 alpha, const T1 *x, T1 *y)
AXPY: y <- a*x + y.
Options metadata for a class.
Definition: options.hpp:40