@@ -329,9 +329,17 @@ class solver {
329
329
real_type apply_events (real_type t0, real_type h, const real_type* y,
330
330
const events_type<real_type>& events,
331
331
ode::internals<real_type>& internals) {
332
- size_t idx_first = events.size ();
333
332
real_type t1 = t0 + h;
334
- real_type sign = 0 ;
333
+
334
+ // It might be worth saving this storage space in the solver, but
335
+ // I doubt it matters in pratice. We need to save all the found
336
+ // events and their signs, even though probably only one will be
337
+ // found, because of the possibility that multiple events could
338
+ // happen at the same time (e.g., two time-scheduled events or two
339
+ // functions that happen to hit their roots at the same time).
340
+ std::vector<bool > found (events.size ());
341
+ std::vector<real_type> sign (events.size ());
342
+ bool found_any = false ;
335
343
336
344
for (size_t idx_event = 0 ; idx_event < events.size (); ++idx_event) {
337
345
const auto & e = events[idx_event];
@@ -350,20 +358,38 @@ class solver {
350
358
// interpolation is expected to be quite fast and accurate.
351
359
constexpr real_type eps = 1e-6 ;
352
360
constexpr size_t steps = 100 ;
353
- auto root = lostturnip::find_result<real_type>(fn, t0, t1, eps, steps);
354
- idx_first = idx_event;
355
- t1 = root.x ;
356
- sign = f_t0 < 0 ? 1 : -1 ;
361
+ t1 = lostturnip::find_result<real_type>(fn, t0, t1, eps, steps).x ;
362
+ } else if (!(f_t1 == 0 && f_t0 != 0 )) {
363
+ // Consider the case where jump to a root *exactly* at t1;
364
+ // this happens in coincident roots and with roots that are
365
+ // based in time, and which we arrange for the solver to stop
366
+ // at (e.g., while using simulate()).
367
+ //
368
+ // This test is the inverse of this though, because in the
369
+ // case where we *don't* get an exact root we should skip the
370
+ // bookkeeping below and try the next event.
371
+ continue ;
357
372
}
358
- if (idx_first < events.size ()) {
359
- internals.last .interpolate (t1, y_next_.data ());
360
- events[idx_first].action (t1, sign, y_next_.data ());
361
- // We need to modify the history here so that search will find
362
- // the right point.
363
- internals.last .t1 = t1;
364
- internals.events .push_back ({t1, idx_first, sign});
373
+
374
+ sign[idx_event] = f_t0 < 0 ? 1 : -1 ;
375
+ found[idx_event] = true ;
376
+ found_any = true ;
377
+ }
378
+
379
+ // If we found at least one event, then reset the solver state
380
+ // back to the point of the event and apply all the events in
381
+ // turn.
382
+ if (found_any) {
383
+ internals.last .interpolate (t1, y_next_.data ());
384
+ for (size_t idx_event = 0 ; idx_event < events.size (); ++idx_event) {
385
+ if (found[idx_event]) {
386
+ events[idx_event].action (t1, sign[idx_event], y_next_.data ());
387
+ internals.events .push_back ({t1, idx_event, sign[idx_event]});
388
+ }
365
389
}
390
+ internals.last .t1 = t1;
366
391
}
392
+
367
393
return t1;
368
394
}
369
395
0 commit comments