The RTAEval framework tool suite can be used for designing and evaluating different runtime assurance logics and is written in Python. The main components are:
- Scenario definition and execution
- Data collection, evaluation, and visualization
An example of the framework is shown below.
A further explanation of the framework is provided in the paper "RTAEval: A framework for evaluating runtime assurance logic".
This README is intended to be a quick start guide for the RTAEval tool suite.
We provide a simple python simulator that can be used to quickly evaluate RTA different RTA logics. In this section, we go over the different components needed to run the simulator. However, users need only provide the scenario execution trace to the data collection tool in order for our evaluation and visualization to work (discussed in the data collection, evaluation, and visualization section). Scenarios are defined by the:
- agents and low-level controllers,
- unsafe sets,
- safety and untrusted controllers, and
- RTA logic.
First, we discuss the simulation traces that result from the scenario execution.
The simulation traces that we collect are a dictionary of the following form
{"agents":{
"agent_id":{
"state_trace":[[t0,state0], ... ],
"mode_trace":[mode0, ...]},
...},
"unsafe":{
"unsafe_id":{
"set_type": setType,
"state_trace":[[t0,setDef0], ... ]},
...}
}
For each agent in the scenario, the state traces are a list of time-stamped states and the mode trace is a list of modes. For each unsafe set in the scenario, we need the set type (currently supports relative and fixed points, balls, rectangles, and polytopes), as well as the state trace of the unsafe set, which is a list of time-stamped set definitions. A visualization of this dictionary is given below.
The agents are the systems that are operating in the scenario.
These agents have a state which is updated via a dynamic function, as well as a mode which tells the dynamic function how to update the agent's state.
When creating an agent to use in our Python simulator, users must create an agent class which has a step
function to update the agent states.
The agents must also have an associated id
so they can be identified throughout the scenario execution.
The agents are updated via a step
function which takes in a mode, initial condition, the time difference between two time steps, and the current simulator state (in the same dictionary format as the simulation trace).
Its output is the agent state at the next time step.
An example of a step function (taken from the demos\acc\acc_utils\cc_agent.py
) is given by the following:
def step(self, mode, initialCondition, time_step, simulatorState):
if self.follower:
self.goal_state = self.desired_traj(simulatorState)
if mode[0].value == 1:
self.control = self.no_control
elif mode[0].value == 2:
self.control = self.BangBang1D
else:
self.control = self.P
self.simulate(mode, initialCondition, 2*time_step, time_step)
else:
self.simulate(mode, initialCondition, 2*time_step, time_step)
return self.state_hist[-1]
Here, the step
function takes in the mode, initial condition, time difference between two time steps, and the current simulator state.
It then uses the mode to decide which controller should be used and updates its state via the simulate
function.
Finally, it returns the state at the next time step.
The agent mode determines what the control input for the agent should be at any time step.
Our evaluation tool currently supports two modes: UNTRUSTED
and SAFETY
, however users can still simulate scenarios where other modes are used.
An example of the modes used in the acc
example can be found in acc\acc_utils\cc_controller.py
, and is copied below:
class ccMode(Enum):
'''Defines the discrete modes of a single agent'''
NORMAL = auto()
UNTRUSTED = auto()
SAFETY = auto()
A runtime assurance (RTA) decision module determines which mode should be used for the agent at any given time step.
We provide a basic RTA class in utils/baseRTA.py
called baseRTA
.
When creating an RTA, users should create a class which inherits the baseRTA
class.
The user should then provide the RTALogic
function which takes in the current simulation state (again in the form of the simulation trace dictionary)
and returns the mode in which the agent should operate over the current time step.
The switching is performed via the RTASwitch
function, which includes some data collection functionality (which will be discussed in Data collection, evaluation, and visualization section).
This is the function that the users must provide when inheriting the baseRTA
class.
It takes in the current simulation state in the form of the simulation trace dictionary and returns the mode that the agent should operate in over the current time step.
We provide a variety of unsafe sets that the users can add to a scenario.
These unsafe sets can be either fixed
(which means that the set definition does not change over time), or they can be relative
to another agent in the scenario.
hroughout the scenario execution.
Currently, our tool suite supports unsafe sets of the following types: point, ball, rectangle, and polytope.
When initializing the unsafe set, the user must provide the unsafe set id
, the initial set definition, and, if the set is relative
, the agent id
that the set is defined with respect to.
Below, we provide the specific definitions needed for each type of set.
- Point - a point is defined by its
(x,y,z)
center. - Ball - a ball is defined by its
(x,y,z)
center and its radiusr
. - Rectangle - a rectangle is defined by its
(x,y,z)
center anddx
,dy
, anddz
, which are the lengths in thex
-,y
-, andz
-directions respectively. - Polytope - a polytope is defined by an nx3 matrix
A
and an n vectorb
, where the set contains everyp = [x,y,z]
such thatA p <= b
.
We provide the class simpleSim
to quickly set up and execute scenarios.
When setting up a scenario, we use the following functionality.
This function adds the agents, RTAs, and initial state of the agents to the scenario.
agents
is a list of agents[agent1, agent2, ...]
.modes
is a list of initial modes used for the agents. It must be given in the same order as theagents
list, ie.[agent1Mode, agent2Mode, ...]
.RTAs
is a list of RTAs used for the agents. It must be given in the same order as theagents
list, ie.[agent1RTA, agent2RTA, ...]
. If the agent does not have an associated RTA, useNone
instead. For example, if agent 2 does not have an RTA, theRTAs
list would be[agent1RTA, None, ...]
.initStates
is a list of initial states for the agents. It must be given in the same order as theagents
list, ie.[agent1initState, agent2initState, ...]
.
This function adds the unsafe sets to the scenario.
Here, unsafeSets
is a list of unsafe sets.
This function defines the visualization parameters for the simulation.
vis
is a boolean variable that determines if the scenario execution should be visualized in real time. IfTrue
, then a visualization will be shown.plotType
can take on a string value"2D"
or"3D"
. This determines if the plot shown by the visualization is in 2- or 3-dimensions.simType
can take on a string value"1D"
,"2D"
, or"3D"
. It tells the scenario if the physical agent lives in 1-, 2-, or 3-dimensions. IfsimType
is set to"1D"
, then the first component of the state should be thex
-position. IfsimType
is set to"2D"
, then the first component of each agent's state should be thex
-position and the second component should be they
-position. IfsimType
is set to"3D"
, then the first component of each agent's state should be thex
-position, the second component should be they
-position, and the third component should be thez
-position.
This function sets the time parameters for the scenario execution.
Here, dt
is the time difference between two time steps and T
is the upper time bound on the scenario execution.
This function sets the axes limits for the scenario execution visualization.
axes_type
determines if axes limits should be set relative to an agent or the origin. If the axes should be set relative to an agent, choose"relative"
. If the axes should be set relative to the origin, choose"fixed"
.ref_agent_id
chooses which agent the axes should be set relative to ifrelative
is chosen.axes_xlim
,axes_ylim
, andaxes_zlim
determine the x-, y-, and z-limits for the axes, given as[xmin, xmax]
,[ymin, ymax]
, and[zmin, zmax]
respectively. Ifxcenter
is the x-value of the point that the axes are set relative to, then the resulting axes limits would be[xcenter + xmin, xcenter + xmax]
. The same is true for the y- and z-axes limits.
This function runs the simulator and returns a simulation trace of the scenario execution. Here, saveVid
is a boolean variable which decides if the scenario execution should be saved as a video, and
vidFileName
is a string that decides the file that the video should be saved to.
The videos should be saved as a .mp4
.
When a user creates an RTA that inherits baseRTA
, data collection can also be included.
When initializing the RTA, the user should use the function setupEval(self, egoID, workspaceDims)
,
where egoID
is the id
of the agent that the RTA is associated with, and workspaceDims
is the 1-, 2-, or 3-dimensional physical space that the agent lives in.
This creates an eval
object within the RTA.
Each time a decision is made by the RTA module, the eval
object collects the current simulation state (in the dictionary form of the simulation trace) and saves it to eval.simulation_trace
.
It also collects the time it takes for the RTA logic to make a decision and saves it to a list eval.computation_times
.
Then, we provide the following evaluation and visualization functionality:
running_times(self)
- this function returns the average, minimum, and maximum times it took for the RTA to make a decision over the scenario.trace_at_min_RT(self)
- this function returns the simulation state at the time step where the RTA made the quickest decision.trace_at_max_RT(self)
- this function returns the simulation state at the time step where the RTA took the longest to make a decision.controllerUsage(self, showPlot)
- this function returns the percentage that the untrusted controller was used, the percentage that the safety controller was used, and the number of times that the RTA module switched between the untrusted and safety controllers. IfshowPlot
is set to true, then a plot showing the controller usage over time is shown. An example of this plot is shown below.
compute_dists(self, showPlot)
- this function returns the minimum distance, maximum distance, and average distances of the ego agent from the unsafe sets. This function also creates a dictionaryeval.dists
of the form{unsafeID:dist, ...}
. IfshowPlot
is set to true, a plot showing the distance of the agent from the unsafe sets over time is shown. An example is shown below.
compute_agent_dists(self, showPlot = False)
- this function returns the minimum distance, maximum distance, and average distances of the ego agent from the other agents. This function also creates a dictionaryeval.agent_dists
of the form{agentID:dist, ...}
. IfshowPlot
is set to true, a plot showing the distance of the agent from the other agents over time is shown. An example is shown below.
compute_unsafe_TTC(self, vel_idx, heading_idx, pitch_idx)
- this function returns the minimum, maximum, and average times to collision of the ego agent with the unsafe sets over the scenario. It creates a dictionaryeval.unsafe_TTC
of the form{unsafeID:TTC, ...}
. Here,vel_idx
is the index of the state that is the velocity of the agent,heading_idx
is the index of the state that is the heading of the agent, andpitch_idx
is the index of the state that is the pitch of the agent. Note thatheading_idx
andpitch_idx
can beNone
in the case of scenarios where the agent does not have a heading or pitch angle.compute_agent_TTC(self, vel_idx, heading_idx, pitch_idx)
- this function returns the minimum, maximum, and average times to collision of the ego agent with the other agents sets over the scenario. It creates a dictionaryeval.agent_TTC
of the form{unsafeID:TTC, ...}
. Here,vel_idx
is the index of the state that is the velocity of the agent,heading_idx
is the index of the state that is the heading of the agent, andpitch_idx
is the index of the state that is the pitch of the agent. Note thatheading_idx
andpitch_idx
can beNone
in the case of scenarios where the agent does not have a heading or pitch angle. Additionally, note that the velocity, heading, and pitch indices must be the same between both agents.summary(self, showPlots)
- This function provides a summary of the RTA performance over the scenario. It prints the computation times, distances from the unsafe sets, distances from the other agents, and the controller usage over the scenario. Note that the times to collision are not included as the agents must have some physical aspect to compute this metric. IfshowPlots
is set toTrue
, then the controller usage and distance plots are shown.
In this repository, we provide a few examples in the demos
folder.
A cruise-control example is provided in acc
and an example with aircraft following Dubins dynamics are provided in dubins
.
We also provide two example RTA in demo_rta
: simRTA
which simulates the trajectory of the agent forward and checks for safety, and reachRTA
which uses the Verse library to compute the forward reachable sets of the agent forward and checks for safety.
A description of these scenarios is provided in the RTAEval Examples section of our paper.
We also provide a brief description here.
This is a scenario which has one physical dimension.
The goal of the agents is to track a point d
units behind a leader agent while never getting closer than c
to the leader.
- Agents - the agents are defined in the
acc_utils/cc_agent.py
file. The agent class is given byccAgent
, which contains the step function, safety, and untrusted controllers. The agent modes are given in theacc_utils/cc_controller.py
file. There are three modes:NORMAL
, which is used by agents that have no control input,SAFETY
which sets the controller to the PID control, andUNTRUSTED
which sets the controller to the bang-bang control. - Unsafe sets - the unsafe set is a ball centered on a "leader" agent that operates in the
NORMAL
mode - RTAs - we implement both
simRTA
andreachRTA
in theacc_utils/cc_controller.py
file. The single follower scenario is given inacc_sim.py
. Scenarios with two and five followers are given in thetwo_acc_sim.py
andfive_acc_sim.py
files respectively.
This is a scenario which has either two or three physical dimension.
The goal of the agents is to track a point d
units behind a leader agent while never getting closer than c
to the leader.
Additionally, in the three dimensional scenario, the follower agents must also avoid collision with the ground.
- Agents - the agents are defined in the
dubins_utils/agent_aircraft.py
file. The leader agent class is given byAircraftAgent
which provides a constant input to the leader, and the follower agent class is given byAircraftTrackingAgent
which tracks a given goal point. Both classes contain the step function, and theAircraftTrackingAgent
class contains the safety and untrusted controllers. The agent modes are given in thedubins_utils/controller_aircraft.py
file. There are three modes:NORMAL
, which is used by agents that have no control input,SAFETY
which sets the controller to track some safe point, andUNTRUSTED
which sets the controller to track the desired, but potentially unsafe point. - Unsafe sets -
for the two dimensional case, the unsafe set is a ball of radius
c
centered on a "leader" agent, and a building defined as a rectangle. In the three dimensional case, the unsafe set is a ball of radiusc
centered on a "leader" agent, and the ground which is defined as a polytope. - RTAs - we implement both
simRTA
andreachRTA
in theacc_utils/cc_controller.py
file.
The two dimensional scenarios
are given in simpleDubins.py
(1 follower),
three_simpleDubins.py
(3 followers), and
ten_simpleDubins.py
(10 followers).
The three dimensional scenarios are given in
dubins_gcas.py
(1 follower), and
dubins_three_gcas.py
(3 followers).