Drake

This page describes how Drake Systems can respond (through an Event) to changes ("triggers") in time, state, and inputs.

The state of simple dynamical systems, like the ODE ẋ = x, can be propagated through time using straightforward numerical integration. More sophisticated systems (e.g., systems modeled using piecewise differential algebraic equations, systems dependent upon mouse button clicks, and Discrete Systems) require more sophisticated state updating mechanisms. We call those state updates "events", the conditions that cause these events "triggers", and the mechanisms that compute the updates "handlers". We discuss these concepts in detail on this page. The Simulator class documentation describes the technical process underlying how events are handled in great detail.

### Exposition

Events occur between discrete, finite advancements of systems' time and state. In the absence of events, a simulator would happily advance time and any continuous state without stopping. The occurrence of an event pauses the time and state evolution in order to allow a system to change its state discontinuously, to communicate with the "outside world", and even to just count the number of event occurrences.

### Types of events and triggers

The diagram below distinguishes between the condition that is responsible for detecting the event (the "trigger") and the action that is taken when the event is dispatched (the "event handler").

                           -- > handler1
triggers  -- >  dispatcher -- > handler2
Events             -- > etc.


Handler actions fall into several categories based on event type, as described next.

#### Event types

Events are grouped by the component(s) of a system's State that can be altered:

• "publish" events can modify no state: they are useful for broadcasting data outside of the novel system and any containing diagrams, for terminating a simulation, for detecting errors, and for forcing boundaries between integration steps.
• "discrete update" events can alter the discrete state of a system.
• "unrestricted update" events can alter every component of state but time: continuous state, discrete state, and abstract state.

Note that continuous state is nominally updated through the process of solving an ODE initial value problem (i.e., "integrating") rather than through a specialized event.

Updates are performed in a particular sequence. For example, unrestricted updates are performed before discrete updates. The Simulator documentation describes the precise update ordering.

#### Event triggers

Events can be triggered in various ways including:

• upon initialization
• as a certain time is crossed (whether once or repeatedly, i.e., periodically)
• per simulation step
• as a WitnessFunction crosses zero
• "by force", e.g., the system's CalcUnrestrictedUpdate() function is invoked by some user code

### How events are handled

State advances with time in dynamical systems. If the dynamical system is simulated, then Drake's Simulator, or another solver (see glossary) is responsible for detecting when events trigger, dispatching the appropriate handler function, and updating the state as time advances. The update functions modify only copies of state so that every update function in a class (e.g., all unrestricted updates) sees the same pre-update state regardless of the sequence of event updates).

Events can also be dispatched manually ("by force"), i.e., outside of a solver. As noted above, one could call CalcUnrestrictedUpdate() to determine how a system's state would change and, optionally, update that state manually. Here is a simple example illustrating a forced publish:

SystemX y;
std::unique_ptr<Context<T>> context = y.CreateDefaultContext();
y.Publish(*context);

### Information for leaf system authors

#### Declaring update functions

The preferred way to update state through events is to declare an update handler in your LeafSystem-derived-class. Some older Drake code computes state updates by overriding event dispatchers (e.g., LeafSystem::DoCalcUnrestrictedUpdate()), though that practice is discouraged and will soon be deprecated.

A number of convenience functions are available in LeafSystem for declaring various trigger and event update combinations; see, e.g., LeafSystem::DeclarePeriodicPublishEvent(), LeafSystem::DeclarePerStepDiscreteUpdateEvent(), and LeafSystem::DeclareInitializationUnrestrictedUpdateEvent().

The following sample code shows how to declare a publish event that is triggered at regular time intervals:

template <typename T>
class MySystem : public LeafSystem<T> {
MySystem() {
const double period = 1.0;
const double offset = 0.0;
this->DeclarePeriodicPublishEvent(period, offset, &MySystem::MyPublish);
}
// Called once per second when MySystem is simulated.
EventStatus MyPublish(const Context<T>&) const { ... }
};

#### EventData

It can be impractical or infeasible to create a different handler function for every possible trigger that a leaf system might need to consider. The alternative is to create a single event handler and map multiple triggers to it. The problem then becomes how an event handler should determine which condition triggered it.

The EventData structure was created for exactly this purpose, at the price of some verbosity when declaring the events. Every Event stores the type of trigger associated with it and, if relevant, some EventData that provides greater insight into why the event handler was invoked. For example:

template <typename T>
class MySystem : public LeafSystem<T> {
MySystem() {
const double period1 = 1.0;
const double period2 = 2.0;
const double offset = 0.0;
// Declare a publish event with one period.
this->DeclarePeriodicEvent(period1, offset, PublishEvent<T>(
[this](const Context<T>& context, const PublishEvent<T>& event) ->
EventStatus {
return MyPublish(context, event);
}));
// Declare a second publish event with another period.
this->DeclarePeriodicEvent(period2, offset, PublishEvent<T>(
[this](const Context<T>& context, const PublishEvent<T>& event) ->
EventStatus {
return MyPublish(context, event);
}));
}
// A single update handler for all triggered events.
EventStatus MyPublish(const Context<T>&, const PublishEvent<T>& e) const {
if (e.get_trigger_type() == TriggerType::kPeriodic) {
std::cout << "Event period: " <<
static_cast<PeriodicEventData<T>*>(
e.get_event_data()).period_sec() << std::endl;
}
}
};

#### Event status

Event handlers can return an EventStatus type to modulate the behavior of a solver. Returning EventStatus::kFailed from the event handler indicates that the event handler was unable to update the state (because, e.g., the simulation step was too big) and thus the solver should take corrective action. Or an event handler can return EventStatus::kReachedTermination to indicate that the solver should stop computing; this is useful if the event handler detects that a simulated walking robot has fallen over and that the end of a reinforcement learning episode has been observed, for example.

### Glossary

• dispatch: the process of the solver collecting events that trigger simultaneously and then distributing those events to their handlers.
• forced event: when an event is triggered manually through user code rather than by a solver.
• handle/handler: an event is "handled" when the triggering condition has been identified and the "handler" function is called.
• solver: a process that controls or tracks the time and state evolution of a System.
• trigger: the condition responsible for causing an event.