casadi_ivp_event.hpp
1 //
2 // MIT No Attribution
3 //
4 // Copyright (C) 2010-2026 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 // Zero-crossing events of the one-step integrators, see casadi_ivp.hpp: the kernel requests
21 // task IVP_EVENT after each accepted step, so only integrators with events need this file
22 // Event i occurs where e_i crosses from positive to negative; it is located on the dense output
23 // of the step, and the integration restarts from the state after the transition
24 // The jump (task IVP_EVAL_JUMP) is a function of the state before the event whose derivatives
25 // are those of the event, including the dependence of its time: forward, adjoint (task
26 // IVP_EVENT_ADJ for event records of the tape) and forward-over-adjoint sensitivities
27 
28 // C-REPLACE "static_cast<T1>" "(casadi_real) "
29 // C-REPLACE "static_cast<casadi_int>" "(casadi_int) "
30 // C-REPLACE "fabs" "casadi_fabs"
31 // C-REPLACE "casadi_ivp_prob<T1>" "struct casadi_ivp_prob"
32 // C-REPLACE "casadi_ivp_data<T1>" "struct casadi_ivp_data"
33 
34 // SYMBOL "ivp_event_phase_t"
35 typedef enum {
36  // Phases of casadi_ivp_event: each one consumes the result of the previously requested task
37  IVP_EV_BEGIN,
38  IVP_EV_START,
39  IVP_EV_END,
40  IVP_EV_TRIAL,
41  IVP_EV_POST,
42  IVP_EV_BACK_SEED
43 } casadi_ivp_event_phase_t;
44 
45 // SYMBOL "ivp_event_request"
46 template<typename T1>
47 int casadi_ivp_event_request(casadi_ivp_data<T1>* d, casadi_ivp_task_t task, int phase) {
48  // Hand a task to the caller, resume at phase
49  d->task = task;
50  d->ev_phase = phase;
51  d->fail = 0;
52  return 1;
53 }
54 
55 // SYMBOL "ivp_event_done"
56 template<typename T1>
57 int casadi_ivp_event_done(casadi_ivp_data<T1>* d, casadi_ivp_flag_t status) {
58  d->ev_phase = IVP_EV_BEGIN;
59  if (status != IVP_SUCCESS) d->status = status;
60  return 0;
61 }
62 
63 // SYMBOL "ivp_dense_derivative"
64 template<typename T1>
65 void casadi_ivp_dense_derivative(casadi_ivp_data<T1>* d, T1 t, T1* ydot) {
66  // Time derivative of the dense output of the current step at t
67  const casadi_ivp_prob<T1>* p = d->prob;
68  casadi_int q;
69  T1 theta = (t - d->t0) / d->h, th = 1. / d->h;
70  casadi_clear(d->w_dense, p->n_stages);
71  for (q = 0; q < p->n_dense; ++q) {
72  casadi_axpy(p->n_stages, static_cast<T1>(q + 1) * th, p->m_dense + q * p->n_stages,
73  d->w_dense);
74  th *= theta;
75  }
76  casadi_clear(ydot, p->ny);
77  casadi_mv_dense(d->U, p->ny, p->n_stages, d->w_dense, ydot, 0);
78 }
79 
80 // SYMBOL "ivp_event_hermite"
81 template<typename T1>
82 casadi_int casadi_ivp_event_hermite(casadi_ivp_data<T1>* d, T1* t) {
83  // First crossing in the bracket of the cubic Hermite interpolants of the candidates, from
84  // values and derivatives at its ends; its time in t; -1 if none
85  const casadi_ivp_prob<T1>* p = d->prob;
86  casadi_int i, k, ind = -1;
87  T1 H = d->ev_tb - d->ev_ta, c[4], sa, sb, s, v, t_i;
88  for (i = 0; i < p->ne; ++i) {
89  if (d->e_mask[i] == 0 || !(d->e1[i] < 0)) continue;
90  // p(s) = c0 + c1 s + c2 s^2 + c3 s^3, p(0) >= 0 > p(1)
91  c[0] = d->e0[i];
92  c[1] = H * d->edot0[i];
93  c[2] = 3 * (d->e1[i] - d->e0[i]) - H * (2 * d->edot0[i] + d->edot1[i]);
94  c[3] = 2 * (d->e0[i] - d->e1[i]) + H * (d->edot0[i] + d->edot1[i]);
95  // First sign change on a uniform grid, then bisection
96  sa = 0;
97  sb = 1;
98  for (k = 1; k <= 8; ++k) {
99  s = static_cast<T1>(k) / 8;
100  if (c[0] + s * (c[1] + s * (c[2] + s * c[3])) < 0) {
101  sb = s;
102  break;
103  }
104  sa = s;
105  }
106  for (k = 0; k < 60 && sb - sa > 1e-15; ++k) {
107  s = 0.5 * (sa + sb);
108  v = c[0] + s * (c[1] + s * (c[2] + s * c[3]));
109  if (v < 0) {
110  sb = s;
111  } else {
112  sa = s;
113  }
114  }
115  t_i = d->ev_ta + sb * H;
116  if (ind < 0 || t_i < *t) {
117  *t = t_i;
118  ind = i;
119  }
120  }
121  return ind;
122 }
123 
124 // SYMBOL "ivp_event_eval"
125 template<typename T1>
126 int casadi_ivp_event_eval(casadi_ivp_data<T1>* d, T1 t, int phase) {
127  // Zero-crossing functions and their time derivatives at t on the dense output of the step
128  const casadi_ivp_prob<T1>* p = d->prob;
129  d->ev_tm = t;
130  if (t == d->t) {
131  casadi_copy(d->y, p->ny, d->y_out);
132  } else {
133  casadi_ivp_interpolate(d, t, d->y_out);
134  }
135  casadi_ivp_dense_derivative(d, t, d->ydot);
136  d->t_arg = t;
137  d->y_arg = d->y_out;
138  d->res = d->em;
139  return casadi_ivp_event_request(d, IVP_EVAL_ZERO, phase);
140 }
141 
142 // SYMBOL "ivp_event_restart"
143 template<typename T1>
144 int casadi_ivp_event_restart(casadi_ivp_data<T1>* d) {
145  // Continue from the state after the event, adaptive steps from a new initial step size; the
146  // method restarts once the outputs before the event, on the step that contains it, are done
147  d->t = d->t_ev;
148  d->e_valid = 0;
149  d->ev_restart = 1;
150  d->jump = 1;
151  d->h_next = 0;
152  d->err_prev = 1;
153  d->last_rejected = 0;
154  return casadi_ivp_event_done(d, IVP_SUCCESS);
155 }
156 
157 // SYMBOL "ivp_event_select"
158 template<typename T1>
159 void casadi_ivp_event_select(casadi_ivp_data<T1>* d, casadi_int ind) {
160  // Argument of the jump selecting event ind
161  casadi_clear(d->ev_w, d->prob->ne);
162  d->ev_w[ind] = 1;
163 }
164 
165 // SYMBOL "ivp_event_post"
166 template<typename T1>
167 int casadi_ivp_event_post(casadi_ivp_data<T1>* d) {
168  // State after the event in y: event record (room reserved with the step), restart
169  if (d->taping) {
170  // State before the event, index in place of the step size
171  d->record = IVP_RECORD_EVENT;
172  casadi_ivp_tape_push(d, d->y_out, static_cast<T1>(d->ev_fired), d->t_ev, d->t_ev);
173  }
174  return casadi_ivp_event_restart(d);
175 }
176 
177 // SYMBOL "ivp_event_fire"
178 template<typename T1>
179 int casadi_ivp_event_fire(casadi_ivp_data<T1>* d) {
180  // Event ev_fired at t_ev: state before it in y_out, then the jump
181  const casadi_ivp_prob<T1>* p = d->prob;
182  if (!d->nom && ++d->n_events > p->max_events) {
183  return casadi_ivp_event_done(d, IVP_TOO_MANY_EVENTS);
184  }
185  if (!d->nom && d->t_ev == d->t) {
186  casadi_copy(d->y, p->ny, d->y_out);
187  } else {
188  casadi_ivp_interpolate(d, d->t_ev, d->y_out);
189  }
190  casadi_copy(d->y_out, p->ny, d->y);
191  if (!p->has_transition) return casadi_ivp_event_post(d);
192  casadi_ivp_event_select(d, d->ev_fired);
193  d->t_arg = d->t_ev;
194  d->y_arg = d->y_out;
195  d->res = d->y;
196  return casadi_ivp_event_request(d, IVP_EVAL_JUMP, IVP_EV_POST);
197 }
198 
199 // SYMBOL "ivp_event"
200 template<typename T1>
201 int casadi_ivp_event(casadi_ivp_data<T1>* d) {
202  // Events in the accepted step from t0 to t (task IVP_EVENT): returns 1 while a task in
203  // d->task is to be carried out; afterwards ev_fired >= 0 and jump set if one occurred
204  const casadi_ivp_prob<T1>* p = d->prob;
205  casadi_int i, ind;
206  int any;
207  T1 tn = 0, dt;
208  switch (d->ev_phase) {
209  case IVP_EV_BEGIN:
210  d->ev_fired = -1;
211  if (d->nom) {
212  // Replayed step: the event of the nominal integration, if any
213  if (d->nom->ev_fired < 0) return casadi_ivp_event_done(d, IVP_SUCCESS);
214  d->ev_fired = d->nom->ev_fired;
215  d->t_ev = d->nom->t_ev;
216  return casadi_ivp_event_fire(d);
217  }
218  casadi_clear(d->e_mask, p->ne);
219  if (!d->e_valid) return casadi_ivp_event_eval(d, d->t0, IVP_EV_START);
220  return casadi_ivp_event_eval(d, d->t, IVP_EV_END);
221  case IVP_EV_START:
222  if (d->fail) return casadi_ivp_event_done(d, IVP_EVAL_FAILED);
223  casadi_copy(d->em, p->ne, d->e0);
224  casadi_copy(d->edot, p->ne, d->edot0);
225  if (d->ev_restart) {
226  // On the zero surface after an event, up to the accuracy of the event time: positive if
227  // rising, so that an event cannot repeat until its function has been positive
228  d->ev_restart = 0;
229  for (i = 0; i < p->ne; ++i) {
230  if (fabs(d->e0[i]) > 10 * fabs(d->edot0[i]) * d->ev_dt) continue;
231  d->e_mask[i] = d->edot0[i] > 0;
232  d->e0[i] = 0;
233  }
234  }
235  return casadi_ivp_event_eval(d, d->t, IVP_EV_END);
236  case IVP_EV_END:
237  if (d->fail) return casadi_ivp_event_done(d, IVP_EVAL_FAILED);
238  casadi_copy(d->em, p->ne, d->e1);
239  casadi_copy(d->edot, p->ne, d->edot1);
240  // Candidates: positive at the start of the step, negative at its end
241  any = 0;
242  for (i = 0; i < p->ne; ++i) {
243  d->e_mask[i] = (d->e0[i] > 0 || d->e_mask[i] != 0) && d->e1[i] < 0;
244  if (d->e_mask[i] != 0) any = 1;
245  }
246  if (!any) {
247  casadi_copy(d->e1, p->ne, d->e0);
248  casadi_copy(d->edot1, p->ne, d->edot0);
249  d->e_valid = 1;
250  return casadi_ivp_event_done(d, IVP_SUCCESS);
251  }
252  d->ev_ta = d->t0;
253  d->ev_tb = d->t;
254  d->ev_iter = 0;
255  casadi_ivp_event_hermite(d, &tn);
256  return casadi_ivp_event_eval(d, tn, IVP_EV_TRIAL);
257  case IVP_EV_TRIAL:
258  if (d->fail) return casadi_ivp_event_done(d, IVP_EVAL_FAILED);
259  // Shrink the bracket: a candidate crossed before the trial time or not
260  any = 0;
261  for (i = 0; i < p->ne; ++i) if (d->e_mask[i] != 0 && d->em[i] < 0) any = 1;
262  if (any) {
263  d->ev_tb = d->ev_tm;
264  casadi_copy(d->em, p->ne, d->e1);
265  casadi_copy(d->edot, p->ne, d->edot1);
266  } else {
267  d->ev_ta = d->ev_tm;
268  casadi_copy(d->em, p->ne, d->e0);
269  casadi_copy(d->edot, p->ne, d->edot0);
270  }
271  // Next estimate; its distance to the trial time measures the error
272  ind = casadi_ivp_event_hermite(d, &tn);
273  dt = fabs(tn - d->ev_tm);
274  d->ev_iter++;
275  if (dt <= p->event_tol || d->ev_tb - d->ev_ta <= p->event_tol
276  || d->ev_iter >= p->max_event_iter) {
277  if (p->event_acceptable_tol >= 0 && dt >= p->event_acceptable_tol) {
278  return casadi_ivp_event_done(d, IVP_EVENT_FAILED);
279  }
280  d->ev_fired = ind;
281  d->t_ev = tn;
282  d->ev_dt = dt > p->event_tol ? dt : p->event_tol;
283  return casadi_ivp_event_fire(d);
284  }
285  return casadi_ivp_event_eval(d, tn, IVP_EV_TRIAL);
286  case IVP_EV_POST:
287  if (d->fail) return casadi_ivp_event_done(d, IVP_EVAL_FAILED);
288  return casadi_ivp_event_post(d);
289  default:
290  return casadi_ivp_event_done(d, IVP_EVENT_FAILED);
291  }
292 }
293 
294 // SYMBOL "ivp_event_adj"
295 template<typename T1>
296 int casadi_ivp_event_adj(casadi_ivp_data<T1>* d) {
297  // Adjoint of the popped event record (task IVP_EVENT_ADJ), then the stages of the step it
298  // cut short are seeded through the dense output; returns 1 while a task in d->task is to be
299  // carried out
300  const casadi_ivp_prob<T1>* p = d->prob;
301  switch (d->ev_phase) {
302  case IVP_EV_BEGIN:
303  d->t_ev = d->t0;
304  if (p->has_transition) {
305  casadi_ivp_event_select(d, static_cast<casadi_int>(d->h));
306  casadi_ivp_clear_vjp(d);
307  d->t_arg = d->t0;
308  d->y_arg = d->y0;
309  d->w = d->ry;
310  return casadi_ivp_event_request(d, IVP_EVAL_JUMP_ADJ, IVP_EV_BACK_SEED);
311  }
312  d->ev_phase = IVP_EV_BACK_SEED;
313  return casadi_ivp_event_adj(d);
314  case IVP_EV_BACK_SEED:
315  if (d->fail) return casadi_ivp_event_done(d, IVP_EVAL_FAILED);
316  if (p->has_transition) {
317  // Adjoint of the state before the event; quadratures pass through
318  casadi_clear(d->ry, p->nrx + p->nrz);
319  casadi_ivp_add_vjp(d, d->ry);
320  }
321  // The step that contains the event, ending there
322  if (casadi_ivp_tape_pop(d) || d->record != IVP_RECORD_STEP) {
323  return casadi_ivp_event_done(d, IVP_EVENT_FAILED);
324  }
325  d->record_pending = 1;
326  if (d->t_ev != d->t1) {
327  casadi_copy(d->ry, p->nr, d->seed);
328  casadi_clear(d->ry, p->nr);
329  casadi_ivp_interpolate(d, d->t_ev, d->y_out);
330  casadi_ivp_seed_stages(d);
331  }
332  d->t = d->t_ev;
333  return casadi_ivp_event_done(d, IVP_SUCCESS);
334  default:
335  return casadi_ivp_event_done(d, IVP_EVENT_FAILED);
336  }
337 }
bool any(const std::vector< bool > &v)
Check if any arguments are true.
Definition: casadi_misc.cpp:88
const T1 * w
Definition: casadi_ivp.hpp:206
casadi_int ev_iter
Definition: casadi_ivp.hpp:265
casadi_ivp_task_t task
Definition: casadi_ivp.hpp:196
casadi_ivp_flag_t status
Definition: casadi_ivp.hpp:201
casadi_int n_events
Definition: casadi_ivp.hpp:265
casadi_ivp_record_t record
Definition: casadi_ivp.hpp:245
const casadi_ivp_prob< T1 > * prob
Definition: casadi_ivp.hpp:189
const T1 * y_arg
Definition: casadi_ivp.hpp:206
casadi_ivp_data< T1 > * nom
Definition: casadi_ivp.hpp:193
casadi_int ev_fired
Definition: casadi_ivp.hpp:265
casadi_int n_dense
Definition: casadi_ivp.hpp:120
casadi_int ny
Definition: casadi_ivp.hpp:145
const T1 * m_dense
Definition: casadi_ivp.hpp:121
casadi_int nr
Definition: casadi_ivp.hpp:145
casadi_int nrz
Definition: casadi_ivp.hpp:145
casadi_int max_event_iter
Definition: casadi_ivp.hpp:143
casadi_int max_events
Definition: casadi_ivp.hpp:143
casadi_int nrx
Definition: casadi_ivp.hpp:145
casadi_int ne
Definition: casadi_ivp.hpp:140
casadi_int n_stages
Definition: casadi_ivp.hpp:117