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: return 2;
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: return 1;
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  return repmat(derivative_of_.sparsity_in(i-n_in-n_out), 1, n_);
175  }
176 }
177 
179  return repmat(derivative_of_.sparsity_out(i), 1, n_);
180 }
181 
182 double FiniteDiff::get_default_in(casadi_int ind) const {
183  if (ind<derivative_of_.n_in()) {
184  return derivative_of_.default_in(ind);
185  } else {
186  return 0;
187  }
188 }
189 
192 }
193 
195  return derivative_of_.n_out();
196 }
197 
198 std::string FiniteDiff::get_name_in(casadi_int i) {
199  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
200  if (i<n_in) {
201  return derivative_of_.name_in(i);
202  } else if (i<n_in+n_out) {
203  return "out_" + derivative_of_.name_out(i-n_in);
204  } else {
205  return "fwd_" + derivative_of_.name_in(i-n_in-n_out);
206  }
207 }
208 
209 std::string FiniteDiff::get_name_out(casadi_int i) {
210  return "fwd_" + derivative_of_.name_out(i);
211 }
212 
213 Function CentralDiff::get_forward(casadi_int nfwd, const std::string& name,
214  const std::vector<std::string>& inames,
215  const std::vector<std::string>& onames,
216  const Dict& opts) const {
217  // Commented out, does not work well
218 #if 0
219  // The second order derivative is calculated as the backwards derivative
220  // of the forward derivative, which is equivalent to central differences
221  // of second order
222  std::string f_name = "fd_" + name;
223  Dict f_opts = {{"derivative_of", derivative_of_}};
224  Function f = Function::create(new ForwardDiff(f_name, n_, h_), f_opts);
225  // Calculate backwards derivative of f
226  f_opts["derivative_of"] = f;
227  return Function::create(new ForwardDiff(name, nfwd, -h_), f_opts);
228 #endif
229  return Function::create(new CentralDiff(name, nfwd), opts);
230 }
231 
232 int FiniteDiff::eval(const double** arg, double** res,
233  casadi_int* iw, double* w, void* mem) const {
234  setup(mem, arg, res, iw, w);
235  // Shorthands
236  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
237  casadi_int n_pert = this->n_pert();
238 
239  // Non-differentiated input
240  const double** x0 = arg;
241  arg += n_in;
242 
243  // Non-differentiated output
244  double* y0 = w;
245  for (casadi_int j=0; j<n_out; ++j) {
246  const casadi_int nnz = derivative_of_.nnz_out(j);
247  casadi_copy(*arg++, nnz, w);
248  w += nnz;
249  }
250 
251  // Forward seeds
252  const double** seed = arg;
253  arg += n_in;
254 
255  // Forward sensitivities
256  double** sens = res;
257  res += n_out;
258 
259  // Finite difference approximation
260  double* J = w;
261  w += n_y_;
262 
263  // Perturbed function values
264  double** yk = res;
265  res += n_pert;
266  for (casadi_int j=0; j<n_pert; ++j) {
267  yk[j] = w, w += n_y_;
268  }
269 
270  // Setup arg and z for evaluation
271  double *z = w;
272  for (casadi_int j=0; j<n_in; ++j) {
273  arg[j] = w;
274  w += derivative_of_.nnz_in(j);
275  }
276 
277  // Setup res and y for evaluation
278  double *y = w;
279  for (casadi_int j=0; j<n_out; ++j) {
280  res[j] = w;
281  w += derivative_of_.nnz_out(j);
282  }
283 
284  // For all sensitivity directions
285  for (casadi_int i=0; i<n_; ++i) {
286  // Initial stepsize
287  double h = h_;
288  // Perform finite difference algorithm with different step sizes
289  for (casadi_int iter=0; iter<1+h_iter_; ++iter) {
290  // Calculate perturbed function values
291  for (casadi_int k=0; k<n_pert; ++k) {
292  // Perturb inputs
293  casadi_int off = 0;
294  for (casadi_int j=0; j<n_in; ++j) {
295  casadi_int nnz = derivative_of_.nnz_in(j);
296  casadi_copy(x0[j], nnz, z + off);
297  if (seed[j]) casadi_axpy(nnz, pert(k, h), seed[j] + i*nnz, z + off);
298  off += nnz;
299  }
300  // Evaluate
301  if (derivative_of_(arg, res, iw, w)) return 1;
302  // Save outputs
303  casadi_copy(y, n_y_, yk[k]);
304  }
305  // Finite difference calculation with error estimate
306  double u = calc_fd(yk, y0, J, h);
307  if (iter==h_iter_) break;
308 
309  // Update step size
310  if (u < 0) {
311  // Perturbation failed, try a smaller step size
312  h /= u_aim_;
313  } else {
314  // Update h to get u near the target ratio
315  h *= sqrt(u_aim_ / fmax(1., u));
316  }
317  // Make sure h stays in the range [h_min_,h_max_]
318  h = fmin(fmax(h, h_min_), h_max_);
319  }
320 
321  // Gather sensitivities
322  casadi_int off = 0;
323  for (casadi_int j=0; j<n_out; ++j) {
324  casadi_int nnz = derivative_of_.nnz_out(j);
325  if (sens[j]) casadi_copy(J + off, nnz, sens[j] + i*nnz);
326  off += nnz;
327  }
328  }
329  return 0;
330 }
331 
332 double ForwardDiff::calc_fd(double** yk, double* y0, double* J, double h) const {
333  return casadi_forward_diff_old(yk, y0, J, h, n_y_, &m_);
334 }
335 
336 double CentralDiff::calc_fd(double** yk, double* y0, double* J, double h) const {
337  return casadi_central_diff_old(yk, y0, J, h, n_y_, &m_);
338 }
339 
343 }
344 
346  // Shorthands
347  casadi_int n_in = derivative_of_.n_in(), n_out = derivative_of_.n_out();
348  casadi_int n_pert = this->n_pert();
349 
350  g.comment("Non-differentiated input");
351  g.local("x0", "const casadi_real", "**");
352  g << "x0 = arg, arg += " << n_in << ";\n";
353 
354  g.comment("Non-differentiated output");
355  g.local("y0", "casadi_real", "*");
356  g << "y0 = w;\n";
357  for (casadi_int j=0; j<n_out; ++j) {
358  const casadi_int nnz = derivative_of_.nnz_out(j);
359  g << g.copy("*arg++", nnz, "w") << " w += " << nnz << ";\n";
360  }
361 
362  g.comment("Forward seeds");
363  g.local("seed", "const casadi_real", "**");
364  g << "seed = arg, arg += " << n_in << ";\n";
365 
366  g.comment("Forward sensitivities");
367  g.local("sens", "casadi_real", "**");
368  g << "sens = res, res += " << n_out << ";\n";
369 
370  g.comment("Finite difference approximation");
371  g.local("J", "casadi_real", "*");
372  g << "J = w, w += " << n_y_ << ";\n";
373 
374  g.comment("Perturbed function value");
375  g.local("yk", "casadi_real", "**");
376  g << "yk = res, res += " << n_pert << ";\n";
377  g.local("j", "casadi_int");
378  g << "for (j=0; j<" << n_pert << "; ++j) yk[j] = w, w += " << n_y_ << ";\n";
379 
380  g.comment("Setup arg and z for evaluation");
381  g.local("z", "casadi_real", "*");
382  g << "z = w;\n";
383  for (casadi_int j=0; j<n_in; ++j) {
384  g << g.arg(j) << " = w, w += " << derivative_of_.nnz_in(j) << ";\n";
385  }
386 
387  g.comment("Setup res and y for evaluation");
388  g.local("y", "casadi_real", "*");
389  g << "y = w;\n";
390  for (casadi_int j=0; j<n_out; ++j) {
391  g << g.res(j) << " = w, w += " << derivative_of_.nnz_out(j) << ";\n";
392  }
393 
394  g.comment("For all sensitivity directions");
395  g.local("i", "casadi_int");
396  g << "for (i=0; i<" << n_ << "; ++i) {\n";
397 
398  g.comment("Initial stepsize");
399  g.local("h", "casadi_real");
400  g << "h = " << h_ << ";\n";
401 
402  g.comment("Perform finite difference algorithm with different step sizes");
403  g.local("iter", "casadi_int");
404  g << "for (iter=0; iter<" << 1+h_iter_ << "; ++iter) {\n";
405 
406  g.comment("Calculate perturbed function values");
407  g.local("k", "casadi_int");
408  g << "for (k=0; k<" << n_pert << "; ++k) {\n";
409 
410  g.comment("Perturb inputs");
411  casadi_int off=0;
412  for (casadi_int j=0; j<n_in; ++j) {
413  casadi_int nnz = derivative_of_.nnz_in(j);
414  std::string s = "seed[" + str(j) + "]";
415  g << g.copy("x0[" + str(j) + "]", nnz, "z+" + str(off)) << "\n"
416  << "if ("+s+") " << g.axpy(nnz, pert("k"),
417  s+"+i*"+str(nnz), "z+" + str(off)) << "\n";
418  off += nnz;
419  }
420 
421  g.comment("Evaluate");
422  g << "if (" << g(derivative_of_, "arg", "res", "iw", "w") << ") return 1;\n";
423 
424  g.comment("Save outputs");
425  g << g.copy("y", n_y_, "yk[k]") << "\n";
426 
427  g << "}\n"; // for (k=0, ...)
428 
429  g.comment("Finite difference calculation with error estimate");
430  g.local("u", "casadi_real");
431  g.local("m", "const struct casadi_finite_diff_mem");
432  g.init_local("m", "{" + str(m_.reltol) + ", "
433  + str(m_.abstol) + ", "
434  + str(m_.smoothing) + "}");
435  g << "u = " << calc_fd() << "(yk, y0, J, h, " << n_y_ << ", &m);\n";
436  g << "if (iter==" << h_iter_ << ") break;\n";
437 
438  g.comment("Update step size");
439  g << "if (u < 0) {\n";
440  // Perturbation failed, try a smaller step size
441  g << "h /= " << u_aim_ << ";\n";
442  g << "} else {\n";
443  // Update h to get u near the target ratio
444  g << "h *= sqrt(" << u_aim_ << " / fmax(1., u));\n";
445  g << "}\n";
446  // Make sure h stays in the range [h_min_,h_max_]
447  if (h_min_>0 || isfinite(h_max_)) {
448  std::string h = "h";
449  if (h_min_>0) h = "fmax(" + h + ", " + str(h_min_) + ")";
450  if (isfinite(h_max_)) h = "fmin(" + h + ", " + str(h_max_) + ")";
451  g << "h = " << h << ";\n";
452  }
453 
454  g << "}\n"; // for (iter=0, ...)
455 
456  g.comment("Gather sensitivities");
457  off = 0;
458  for (casadi_int j=0; j<n_out; ++j) {
459  casadi_int nnz = derivative_of_.nnz_out(j);
460  std::string s = "sens[" + str(j) + "]";
461  g << "if (" << s << ") " << g.copy("J+" + str(off), nnz, s + "+i*" + str(nnz)) << "\n";
462  off += nnz;
463  }
464  g << "}\n"; // for (i=0, ...)
465 }
466 
467 std::string Smoothing::pert(const std::string& k) const {
468  std::string sign = "(2*(" + k + "/2)-1)";
469  std::string len = "(" + k + "%%2+1)";
470  return len + "*" + sign + "*" + str(h_);
471 }
472 
473 double Smoothing::pert(casadi_int k, double h) const {
474  casadi_int sign = 2*(k/2)-1;
475  casadi_int len = k%2+1;
476  return static_cast<double>(len*sign)*h;
477 }
478 
479 double Smoothing::calc_fd(double** yk, double* y0, double* J, double h) const {
480  return casadi_smoothing_diff_old(yk, y0, J, h, n_y_, &m_);
481 }
482 
483 Function ForwardDiff::get_forward(casadi_int nfwd, const std::string& name,
484  const std::vector<std::string>& inames,
485  const std::vector<std::string>& onames,
486  const Dict& opts) const {
487  return Function::create(new ForwardDiff(name, nfwd), opts);
488 }
489 
490 Function Smoothing::get_forward(casadi_int nfwd, const std::string& name,
491  const std::vector<std::string>& inames,
492  const std::vector<std::string>& onames,
493  const Dict& opts) const {
494  return Function::create(new Smoothing(name, nfwd), opts);
495 }
496 
497 } // 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)
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 =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.
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:855
const Sparsity & sparsity_out(casadi_int ind) const
Get sparsity of a given output.
Definition: function.cpp:1031
const std::vector< std::string > & name_in() const
Get input scheme.
Definition: function.cpp:961
static Function create(FunctionInternal *node)
Create from node.
Definition: function.cpp:336
const Sparsity & sparsity_in(casadi_int ind) const
Get sparsity of a given input.
Definition: function.cpp:1015
casadi_int n_out() const
Get the number of function outputs.
Definition: function.cpp:823
casadi_int n_in() const
Get the number of function inputs.
Definition: function.cpp:819
casadi_int nnz_in() const
Get number of input nonzeros.
Definition: function.cpp:851
const std::vector< std::string > & name_out() const
Get output scheme.
Definition: function.cpp:965
double default_in(casadi_int ind) const
Get default input value.
Definition: function.cpp:1480
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 pert(const std::string &k) const override
std::string calc_fd() 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:264
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