@@ -240,7 +240,7 @@ them; one that declares none does not.
240240function build_fmu_me_callbacks (sys, wrapper_param)
241241 meta = SymbolicUtils. getmetadata (
242242 SymbolicUtils. unwrap (wrapper_param), FMUEventMetadata
243- )
243+ ):: FMUEventMetadata
244244 get_wrapper = SII. getp (sys, wrapper_param)
245245 # `nothing` if the integrator can exchange state and inputs with the FMU, else the reason
246246 # it cannot. An event needs both: states to write the post-event values back into, and
@@ -290,139 +290,146 @@ function build_fmu_me_callbacks(sys, wrapper_param)
290290 state_buffer = zeros (Float64, length (state_idxs))
291291 input_buffer = zeros (Float64, length (input_names))
292292
293- function push_fmu_event_state! (wrapper, u, p, t)
294- for (i, idx) in enumerate (state_idxs)
295- state_buffer[i] = u[idx]
296- end
297- if get_inputs != = nothing
298- copyto! (input_buffer, get_inputs (u, p, t))
293+ # the closures below are the callbacks. They capture with an explicit `let` because
294+ # `no_event_access` and `get_inputs` are assigned above after their initialization, which
295+ # would otherwise box them in the capture.
296+ return let get_wrapper = get_wrapper, wrapper_param = wrapper_param,
297+ no_event_access = no_event_access, get_inputs = get_inputs,
298+ state_idxs = state_idxs, state_buffer = state_buffer, input_buffer = input_buffer
299+ function push_fmu_event_state! (wrapper, u, p, t)
300+ for (i, idx) in enumerate (state_idxs)
301+ state_buffer[i] = u[idx]
302+ end
303+ if get_inputs != = nothing
304+ copyto! (input_buffer, get_inputs (u, p, t))
305+ end
306+ return force_set_fmu_state! (wrapper, state_buffer, input_buffer, t)
299307 end
300- return force_set_fmu_state! (wrapper, state_buffer, input_buffer, t)
301- end
302308
303- function fmu_event_condition! (out, u, t, integrator)
304- wrapper = get_wrapper (integrator)
305- # the instance is created by the first evaluation of the FMU, and unlike a completed
306- # step an event indicator has no value without one
307- if wrapper. instance === nothing
308- error (
309- " The event indicators of the FMU wrapped by $(getname (wrapper_param)) cannot \
310- be evaluated before the FMU is instantiated, which the first evaluation of \
311- its dynamics does."
312- )
309+ function fmu_event_condition! (out, u, t, integrator)
310+ wrapper = get_wrapper (integrator)
311+ # the instance is created by the first evaluation of the FMU, and unlike a completed
312+ # step an event indicator has no value without one
313+ if wrapper. instance === nothing
314+ error (
315+ " The event indicators of the FMU wrapped by $(getname (wrapper_param)) cannot \
316+ be evaluated before the FMU is instantiated, which the first evaluation of \
317+ its dynamics does."
318+ )
319+ end
320+ push_fmu_event_state! (wrapper, u, integrator. p, t)
321+ return get_fmu_event_indicators! (wrapper, out)
313322 end
314- push_fmu_event_state! (wrapper, u, integrator. p, t)
315- return get_fmu_event_indicators! (wrapper, out)
316- end
317323
318- function fmu_event_affect! (integrator, idx)
319- wrapper = get_wrapper (integrator)
320- push_fmu_event_state! (wrapper, integrator. u, integrator. p, integrator. t)
321- enter_fmu_event_mode! (wrapper, idx)
322- event_result = do_fmu_event_iteration! (wrapper)
323- if event_result. terminate
324- SciMLBase. terminate! (integrator)
325- # we suppress the discontinuity because a terminating FMU stays in Event Mode,
326- # where the calls the interpolant rebuild makes are illegal. Nothing is stepped
327- # after `terminate!`, so nothing needs that rebuild.
328- SciMLBase. derivative_discontinuity! (integrator, false )
329- return nothing
330- end
331- states = leave_fmu_event_mode! (wrapper, event_result. values_changed)
332- if states != = nothing
333- for (i, state_idx) in enumerate (state_idxs)
334- integrator. u[state_idx] = states[i]
324+ function fmu_event_affect! (integrator, idx)
325+ wrapper = get_wrapper (integrator)
326+ push_fmu_event_state! (wrapper, integrator. u, integrator. p, integrator. t)
327+ enter_fmu_event_mode! (wrapper, idx)
328+ event_result = do_fmu_event_iteration! (wrapper)
329+ if event_result. terminate
330+ SciMLBase. terminate! (integrator)
331+ # we suppress the discontinuity because a terminating FMU stays in Event Mode,
332+ # where the calls the interpolant rebuild makes are illegal. Nothing is stepped
333+ # after `terminate!`, so nothing needs that rebuild.
334+ SciMLBase. derivative_discontinuity! (integrator, false )
335+ return nothing
335336 end
337+ states = leave_fmu_event_mode! (wrapper, event_result. values_changed)
338+ if states != = nothing
339+ for (i, state_idx) in enumerate (state_idxs)
340+ integrator. u[state_idx] = states[i]
341+ end
342+ end
343+ wrapper. next_event_time = event_result. next_event_time
344+ handle_fmu_time_event! (wrapper, event_result. next_event_time)
345+ return nothing
336346 end
337- wrapper. next_event_time = event_result. next_event_time
338- handle_fmu_time_event! (wrapper, event_result. next_event_time)
339- return nothing
340- end
341347
342- # notify the FMU of the step and return whether it asks for Event Mode, which is the only
343- # outcome its caller has to treat as a discontinuity
344- function fmu_step_event_occurred! (integrator)
345- # we bail unless the solve is in flight, because SciML still applies discrete callbacks
346- # after a continuous one terminated, and a terminated FMU is in Event Mode where none
347- # of the calls below are legal.
348- retcode = integrator. sol. retcode
349- # `check_error!` stores `Success` mid-solve, so these are the two in-flight codes
350- in_flight = retcode === SciMLBase. ReturnCode. Default ||
351- retcode === SciMLBase. ReturnCode. Success
352- in_flight || return false
353- wrapper = get_wrapper (integrator)
354- # the instance is created by the first evaluation of the FMU, which an FMU that only
355- # feeds observed equations may not have seen by the time this initializes
356- wrapper. instance === nothing && return false
357- # we push the accepted state because error control and (for an implicit solver) the
358- # Jacobian leave the FMU at other points. Without exchangeable states there is nothing
359- # to push, which is what FMI.jl's `stepCompleted` does for every FMU.
360- no_event_access === nothing &&
361- push_fmu_event_state! (wrapper, integrator. u, integrator. p, integrator. t)
362- step_result = partiallyCompleteIntegratorStep (wrapper)
363- if step_result. terminate
364- SciMLBase. terminate! (integrator)
365- return false
348+ # notify the FMU of the step and return whether it asks for Event Mode, which is the only
349+ # outcome its caller has to treat as a discontinuity
350+ function fmu_step_event_occurred! (integrator)
351+ # we bail unless the solve is in flight, because SciML still applies discrete callbacks
352+ # after a continuous one terminated, and a terminated FMU is in Event Mode where none
353+ # of the calls below are legal.
354+ retcode = integrator. sol. retcode
355+ # `check_error!` stores `Success` mid-solve, so these are the two in-flight codes
356+ in_flight = retcode === SciMLBase. ReturnCode. Default ||
357+ retcode === SciMLBase. ReturnCode. Success
358+ in_flight || return false
359+ wrapper = get_wrapper (integrator)
360+ # the instance is created by the first evaluation of the FMU, which an FMU that only
361+ # feeds observed equations may not have seen by the time this initializes
362+ wrapper. instance === nothing && return false
363+ # we push the accepted state because error control and (for an implicit solver) the
364+ # Jacobian leave the FMU at other points. Without exchangeable states there is nothing
365+ # to push, which is what FMI.jl's `stepCompleted` does for every FMU.
366+ no_event_access === nothing &&
367+ push_fmu_event_state! (wrapper, integrator. u, integrator. p, integrator. t)
368+ step_result = partiallyCompleteIntegratorStep (wrapper)
369+ if step_result. terminate
370+ SciMLBase. terminate! (integrator)
371+ return false
372+ end
373+ return step_result. enter_event_mode
366374 end
367- return step_result. enter_event_mode
368- end
369375
370- function fmu_step_completed! (integrator)
371- if fmu_step_event_occurred! (integrator)
372- if no_event_access != = nothing
373- error (
374- " The FMU declares no event indicators but requested Event Mode from a \
375- completed integrator step, which needs the same access to its states as \
376- a state event. $no_event_access "
377- )
376+ function fmu_step_completed! (integrator)
377+ if fmu_step_event_occurred! (integrator)
378+ if no_event_access != = nothing
379+ error (
380+ " The FMU declares no event indicators but requested Event Mode from a \
381+ completed integrator step, which needs the same access to its states as \
382+ a state event. $no_event_access "
383+ )
384+ end
385+ # we set the flag ourselves because every callback ahead of this one clears it
386+ # during the callback-initialize phase (`SciMLBase.INITIALIZE_DEFAULT`). On the
387+ # `apply_discrete_callback!` path it is already true.
388+ SciMLBase. derivative_discontinuity! (integrator, true )
389+ # a step event has no triggered event indicator
390+ return fmu_event_affect! (integrator, nothing )
378391 end
379- # we set the flag ourselves because every callback ahead of this one clears it
380- # during the callback-initialize phase (`SciMLBase.INITIALIZE_DEFAULT`). On the
381- # `apply_discrete_callback!` path it is already true.
382- SciMLBase. derivative_discontinuity! (integrator, true )
383- # a step event has no triggered event indicator
384- return fmu_event_affect! (integrator, nothing )
392+ # we clear the flag on every non-event path because the assumed discontinuity
393+ # reinitializes a singular-mass-matrix system and overwrites `u`, while a step the FMU
394+ # accepts as-is changed nothing.
395+ SciMLBase. derivative_discontinuity! (integrator, false )
396+ return nothing
385397 end
386- # we clear the flag on every non-event path because the assumed discontinuity
387- # reinitializes a singular-mass-matrix system and overwrites `u`, while a step the FMU
388- # accepts as-is changed nothing.
389- SciMLBase. derivative_discontinuity! (integrator, false )
390- return nothing
391- end
392398
393- function fmu_step_initialize (cb, u, t, integrator)
394- return fmu_step_completed! (integrator)
395- end
399+ function fmu_step_initialize (cb, u, t, integrator)
400+ return fmu_step_completed! (integrator)
401+ end
396402
397- # we name an algorithm on both callbacks because a standing discontinuity reinitializes the
398- # integrator, and the problem's own `OverrideInit` would reset `u` to `u0` and throw away
399- # the post-event states the affect just wrote. This one keeps the differential states and
400- # re-solves only the algebraic variables.
401- event_initializealg = DiffEqBase. BrownFullBasicInit ()
403+ # we name an algorithm on both callbacks because a standing discontinuity reinitializes the
404+ # integrator, and the problem's own `OverrideInit` would reset `u` to `u0` and throw away
405+ # the post-event states the affect just wrote. This one keeps the differential states and
406+ # re-solves only the algebraic variables.
407+ event_initializealg = DiffEqBase. BrownFullBasicInit ()
402408
403- callbacks = SciMLBase. DECallback[]
404- if meta. n_event_indicators > 0
409+ callbacks = SciMLBase. DECallback[]
410+ if meta. n_event_indicators > 0
411+ push! (
412+ callbacks,
413+ SciMLBase. VectorContinuousCallback (
414+ fmu_event_condition!, fmu_event_affect!, meta. n_event_indicators;
415+ # we need RightRootFind because the FMU has to be past the switch when it
416+ # enters Event Mode: https://fmi-standard.org/docs/3.0.2/#state-event
417+ rootfind = SciMLBase. RightRootFind, interp_points = 10 ,
418+ save_positions = (true , true ), initializealg = event_initializealg
419+ )
420+ )
421+ end
405422 push! (
406423 callbacks,
407- SciMLBase. VectorContinuousCallback (
408- fmu_event_condition!, fmu_event_affect!, meta. n_event_indicators;
409- # we need RightRootFind because the FMU has to be past the switch when it
410- # enters Event Mode: https://fmi-standard.org/docs/3.0.2/#state-event
411- rootfind = SciMLBase. RightRootFind, interp_points = 10 ,
412- save_positions = (true , true ), initializealg = event_initializealg
424+ SciMLBase. DiscreteCallback (
425+ Returns (true ), fmu_step_completed!;
426+ # `initialize` gives the FMU the `t0` step FMI.jl's `func_start` does
427+ initialize = fmu_step_initialize, save_positions = (false , false ),
428+ initializealg = event_initializealg
413429 )
414430 )
431+ callbacks
415432 end
416- push! (
417- callbacks,
418- SciMLBase. DiscreteCallback (
419- Returns (true ), fmu_step_completed!;
420- # `initialize` gives the FMU the `t0` step FMI.jl's `func_start` does
421- initialize = fmu_step_initialize, save_positions = (false , false ),
422- initializealg = event_initializealg
423- )
424- )
425- return callbacks
426433end
427434
428435"""
0 commit comments