@@ -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,44 @@ 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
+ // Currently untested - in the case where we have two roots
363
+ // that would have been crossed in this time window, the one
364
+ // we are currently considering happens first so pre-empts the
365
+ // previously found events.
366
+ if (found_any) {
367
+ std::fill (found.begin (), found.end (), false );
368
+ }
369
+ } else if (!(f_t1 == 0 && f_t0 != 0 )) {
370
+ // Consider the case where jump to a root *exactly* at t1;
371
+ // this happens in coincident roots and with roots that are
372
+ // based in time, and which we arrange for the solver to stop
373
+ // at (e.g., while using simulate()).
374
+ //
375
+ // This test is the inverse of this though, because in the
376
+ // case where we *don't* get an exact root we should skip the
377
+ // bookkeeping below and try the next event.
378
+ continue ;
357
379
}
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});
380
+ sign[idx_event] = f_t0 < 0 ? 1 : -1 ;
381
+ found[idx_event] = true ;
382
+ found_any = true ;
383
+ }
384
+
385
+ // If we found at least one event, then reset the solver state
386
+ // back to the point of the event and apply all the events in
387
+ // turn.
388
+ if (found_any) {
389
+ internals.last .interpolate (t1, y_next_.data ());
390
+ for (size_t idx_event = 0 ; idx_event < events.size (); ++idx_event) {
391
+ if (found[idx_event]) {
392
+ events[idx_event].action (t1, sign[idx_event], y_next_.data ());
393
+ internals.events .push_back ({t1, idx_event, sign[idx_event]});
394
+ }
365
395
}
396
+ internals.last .t1 = t1;
366
397
}
398
+
367
399
return t1;
368
400
}
369
401
0 commit comments