Drake
Drake C++ Documentation

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, 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:

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:

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 way to update state through events is to declare an update handler in your LeafSystem-derived-class.

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 { ... }
};

Trigger data carried with Events

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 via multiple Event objects that specify the same handler. Then the handler may need a way to determine which condition triggered it.

For this purpose, every Event stores the type of trigger associated with it and, if relevant, some extra data that provides greater insight into why the event handler was invoked. The latter is stored in an std::variant field that is currently defined for periodic-triggered timing information and for witness-triggered localization information. 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: "
<< e.template get_event_data<PeriodicEventData>()->period_sec()
<< std::endl;
}
}
};
See also
PeriodicEventData, WitnessTriggeredEventData

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