casadi_finite_diff.hpp
1 //
2 // MIT No Attribution
3 //
4 // Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl, KU Leuven.
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy of this
7 // software and associated documentation files (the "Software"), to deal in the Software
8 // without restriction, including without limitation the rights to use, copy, modify,
9 // merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13 // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
14 // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
15 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
16 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
17 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 //
19 
20 // C-REPLACE "std::numeric_limits<T1>::quiet_NaN()" "casadi_nan"
21 // C-REPLACE "fmax" "casadi_fmax"
22 // C-REPLACE "fabs" "casadi_fabs"
23 // C-REPLACE "isfinite" "casadi_isfinite"
24 // C-REPLACE "static_cast<T1*>(0)" "0"
25 
26 // SYMBOL "forward_diff"
27 template<typename T1>
28 void casadi_forward_diff(const T1** yk, T1* J, T1 h, casadi_int n_y) {
29  // Local variables
30  casadi_int i;
31  const double *yf, *yc;
32  T1 hinv;
33  // Inverse of step size
34  hinv = 1. / h;
35  // Get stencil
36  yc = yk[0];
37  yf = yk[1];
38  // Calculate FD approximation
39  for (i = 0; i < n_y; ++i) J[i] = hinv * (yf[i] - yc[i]);
40 }
41 
42 // SYMBOL "central_diff"
43 template<typename T1>
44 void casadi_central_diff(const T1** yk, T1* J, T1 h, casadi_int n_y) {
45  // Local variables
46  casadi_int i;
47  const T1 *yf, *yc, *yb;
48  T1 hinv;
49  // Inverse of step size
50  hinv = 1. / h;
51  // Get stencil
52  yb = yk[0];
53  yc = yk[1];
54  yf = yk[2];
55  // Set u and stencils to zero (also supresses warnings)
56  for (i = 0; i < n_y; ++i) {
57  if (isfinite(yb[i])) {
58  if (isfinite(yf[i])) {
59  // Both forward and backward allowed
60  J[i] = 0.5 * hinv * (yf[i] - yb[i]);
61  } else {
62  // Backward but not forward allowed
63  J[i] = hinv * (yc[i] - yb[i]);
64  }
65  } else {
66  if (isfinite(yf[i])) {
67  // Forward but not backward allowed
68  J[i] = hinv * (yf[i] - yc[i]);
69  } else {
70  // Neither forward nor backward possible
71  J[i] = std::numeric_limits<T1>::quiet_NaN();
72  }
73  }
74  }
75 }
76 
77 // SYMBOL "central_diff_err"
78 template<typename T1>
79 T1 casadi_central_diff_err(const T1** yk, T1 h, casadi_int n_y, casadi_int i,
80  T1 abstol, T1 reltol) {
81  // Local variables
82  const T1 *yf, *yc, *yb;
83  T1 err_trunc, err_round;
84  // Get stencil
85  yb = yk[0];
86  yc = yk[1];
87  yf = yk[2];
88  // Only consider points where both forward and backward allowed
89  if (isfinite(yb[i]) && isfinite(yf[i])) {
90  // Truncation error
91  err_trunc = yf[i] - 2*yc[i] + yb[i];
92  // Roundoff error
93  err_round = reltol / h * fmax(fabs(yf[i] - yc[i]), fabs(yc[i] - yb[i])) + abstol;
94  // Error quotient estimate
95  return err_trunc / err_round;
96  } else {
97  // Cannot be calculated
98  return std::numeric_limits<T1>::quiet_NaN();;
99  }
100 }
101 
102 // SYMBOL "smoothing_diff_weights"
103 template<typename T1>
104 T1 casadi_smoothing_diff_weights(casadi_int k, T1 yb, T1 yc, T1 yf, T1 *J) {
105  // Calculate shifted finite difference approximation, weights
106  if (k == 0) {
107  // Backward shifted
108  // 7.10 in Conte & Carl de Boor: Elementary Numerical Analysis (1972)
109  // and 25.3.4 in Abramowitz and Stegun, Handbook of Mathematical Functions (1964)
110  if (J) *J = 3 * yf - 4 * yc + yb;
111  // Relative weight is 1
112  return 1;
113  } else if (k == 1) {
114  // Central
115  // We give this the relative weight 4 since if all weights are equal,
116  // this would amount to a five point formula for the derivative
117  // (yb2 - 8*yb + 8*yf - y_f2)/(12*h)
118  // cf. 25.3.6 in Abramowitz and Stegun, Handbook of Mathematical Functions (1964)
119  if (J) *J = yf - yb;
120  // Relative weight is 4
121  return 4;
122  } else {
123  // Forward shifted, cf. backward shifted above
124  if (J) *J = -3 * yb + 4 * yc - yf;
125  // Relative weight is 1
126  return 1;
127  }
128 }
129 
130 // SYMBOL "smoothing_diff"
131 template<typename T1>
132 void casadi_smoothing_diff(const T1** yk, T1* J, T1 h, casadi_int n_y, T1 smoothing) {
133  // Stencil
134  T1 yb, yc, yf;
135  // Local variables
136  T1 Jk, wk, sw, ui, sm;
137  casadi_int i, k;
138  // Set stencils to zero (also supresses warnings)
139  yf = yc = yb = 0;
140  for (i = 0; i < n_y; ++i) {
141  // Reset derivative estimate, sum of weights, error estimate
142  J[i] = sw = ui = 0;
143  // For backward shifted, central and forward shifted
144  for (k = 0; k < 3; ++k) {
145  // Get stencil
146  yb = yk[k][i];
147  yc = yk[k + 1][i];
148  yf = yk[k + 2][i];
149  // No contribuation if any value is infinite
150  if (!isfinite(yb) || !isfinite(yc) || !isfinite(yf)) continue;
151  // Calculate weights
152  wk = casadi_smoothing_diff_weights(k, yb, yc, yf, &Jk);
153  // Smoothness measure (second order derivative)
154  sm = yf - 2*yc + yb;
155  sm /= h*h;
156  // Modify the weight according to smoothness
157  wk /= sm*sm + smoothing;
158  sw += wk;
159  // Added weighted contribution to weight and error
160  J[i] += wk * Jk;
161  }
162  // If sw is 0, no stencil worked
163  if (sw == 0) {
164  // Set component to 0, return -1
165  J[i] = std::numeric_limits<T1>::quiet_NaN();
166  } else {
167  // Finalize estimate using the sum of weights and the step length
168  J[i] /= 2*h*sw;
169  }
170  }
171 }
172 
173 // SYMBOL "smoothing_diff_err"
174 template<typename T1>
175 T1 casadi_smoothing_diff_err(const T1** yk, T1 h, casadi_int n_y, casadi_int i,
176  T1 abstol, T1 reltol, T1 smoothing) {
177  // Stencil
178  T1 yb, yc, yf;
179  // Local variables
180  T1 wk, sw, ui, err_trunc, err_round, sm;
181  casadi_int k;
182  // Set stencils to zero (also supresses warnings)
183  yf = yc = yb = 0;
184  // Reset derivative estimate, sum of weights, error estimate
185  sw = ui = 0;
186  // For backward shifted, central and forward shifted
187  for (k = 0; k < 3; ++k) {
188  // Get stencil
189  yb = yk[k][i];
190  yc = yk[k + 1][i];
191  yf = yk[k + 2][i];
192  // No contribuation if any value is infinite
193  if (!isfinite(yb) || !isfinite(yc) || !isfinite(yf)) continue;
194  // Calculate weights
195  wk = casadi_smoothing_diff_weights(k, yb, yc, yf, static_cast<T1*>(0));
196  // Truncation error
197  err_trunc = yf - 2*yc + yb;
198  // Roundoff error
199  err_round = reltol/h*fmax(fabs(yf - yc), fabs(yc - yb)) + abstol;
200  // We use the second order derivative as a smoothness measure
201  sm = err_trunc/(h*h);
202  // Modify the weight according to smoothness
203  wk /= sm*sm + smoothing;
204  sw += wk;
205  // Added weighted contribution to weight and error
206  ui += wk * fabs(err_trunc / err_round);
207  }
208  // If sw is 0, no stencil worked
209  if (sw == 0) {
210  // Cannot be calculated
211  return std::numeric_limits<T1>::quiet_NaN();
212  } else {
213  // Finalize estimate using the sum of weights and the step length
214  return ui / sw;
215  }
216 }
217 
218 // SYMBOL "finite_diff_mem"
219 template<typename T1>
221  // Input precision
222  T1 reltol;
223  // Output precision
224  T1 abstol;
225  // Smoothness parameter
226  // Smaller epsilon: More discontinuity rejecting
227  // Larger epsilon: More accurate (higher order) if smooth
229 };
230 
231 // C-REPLACE "casadi_finite_diff_mem<T1>" "struct casadi_finite_diff_mem"
232 
233 // SYMBOL "forward_diff_old"
234 template<typename T1>
235 T1 casadi_forward_diff_old(T1** yk, T1* y0, T1* J,
236  T1 h, casadi_int n_y, const casadi_finite_diff_mem<T1>* m) {
237  casadi_int i;
238  for (i=0; i<n_y; ++i) {
239  J[i] = (yk[0][i]-y0[i])/h;
240  }
241  return -1;
242 }
243 
244 // SYMBOL "central_diff_old"
245 template<typename T1>
246 T1 casadi_central_diff_old(T1** yk, T1* y0, T1* J,
247  T1 h, casadi_int n_y, const casadi_finite_diff_mem<T1>* m) {
248  // Return value
249  T1 u;
250  // Stencil
251  T1 yf, yc, yb;
252  // Local variables
253  T1 err_trunc, err_round;
254  casadi_int i;
255  // Set u and stencils to zero (also supresses warnings)
256  yf = yc = yb = u = 0;
257  for (i=0; i<n_y; ++i) {
258  // Copy to local variables, return -1 if invalid entry
259  if (!isfinite((yf=yk[1][i])) || !isfinite((yc=y0[i])) || !isfinite((yb=yk[0][i]))) {
260  J[i] = std::numeric_limits<T1>::quiet_NaN();
261  u = -1;
262  continue;
263  }
264  // Central difference approximation
265  J[i] = (yf - yb)/(2*h);
266  // Truncation error
267  err_trunc = yf - 2*yc + yb;
268  // Roundoff error
269  err_round = m->reltol/h*fmax(fabs(yf-yc), fabs(yc-yb)) + m->abstol;
270  // Update error estimate
271  if (u>=0) u = fmax(u, fabs(err_trunc/err_round));
272  }
273  return u;
274 }
275 
276 // SYMBOL "smoothing_diff_old"
277 template<typename T1>
278 T1 casadi_smoothing_diff_old(T1** yk, T1* y0, T1* J,
279  T1 h, casadi_int n_y, const casadi_finite_diff_mem<T1>* m) {
280  // Return value
281  T1 u;
282  // Stencil
283  T1 yb, yc, yf;
284  // Local variables
285  T1 Jk, wk, sw, ui, err_trunc, err_round, sm;
286  casadi_int i, k;
287  // Set u and stencils to zero (also supresses warnings)
288  yf = yc = yb = u = 0;
289  for (i=0; i<n_y; ++i) {
290  // Reset derivative estimate, sum of weights, error estimate
291  J[i] = sw = ui = 0;
292  // For backward shifted, central and forward shifted
293  for (k=0; k<3; ++k) {
294  // Calculate shifted finite difference approximation
295  if (k==0) {
296  // Backward shifted
297  // 7.10 in Conte & Carl de Boor: Elementary Numerical Analysis (1972)
298  // and 25.3.4 in Abramowitz and Stegun, Handbook of Mathematical Functions (1964)
299  if (!isfinite((yc=yk[0][i]))) continue;
300  if (!isfinite((yb=yk[1][i]))) continue;
301  yf = y0[i];
302  Jk = 3*yf - 4*yc + yb;
303  wk = 1;
304  } else if (k==1) {
305  // Central
306  // We give this the "nominal weight" 4 since if all weights are equal,
307  // this would amount to a five point formula for the derivative
308  // (yb2 - 8*yb + 8*yf - y_f2)/(12*h)
309  // cf. 25.3.6 in Abramowitz and Stegun, Handbook of Mathematical Functions (1964)
310  if (!isfinite((yf=yk[2][i]))) continue;
311  if (!isfinite((yb=yc))) continue;
312  yc = y0[i];
313  Jk = yf-yb;
314  wk = 4;
315  } else {
316  // Forward shifted
317  if (!isfinite((yc=yf))) continue;
318  if (!isfinite((yf=yk[3][i]))) continue;
319  yb = y0[i];
320  Jk = -3*yb + 4*yc - yf;
321  wk = 1;
322  }
323  // Truncation error
324  err_trunc = yf - 2*yc + yb;
325  // Roundoff error
326  err_round = m->reltol/h*fmax(fabs(yf-yc), fabs(yc-yb)) + m->abstol;
327  // We use the second order derivative as a smoothness measure
328  sm = err_trunc/(h*h);
329  // Modify the weight according to smoothness
330  wk /= sm*sm + m->smoothing;
331  sw += wk;
332  // Added weighted contribution to weight and error
333  J[i] += wk * Jk;
334  ui += wk * fabs(err_trunc/err_round);
335  }
336  // If sw is 0, no stencil worked
337  if (sw==0) {
338  // Set component to 0, return -1
339  J[i] = std::numeric_limits<T1>::quiet_NaN();
340  u = -1;
341  } else {
342  // Finalize estimate using the sum of weights and the step length
343  J[i] /= 2*h*sw;
344  if (u>=0) u = fmax(u, ui/sw);
345  }
346  }
347  return u;
348 }