Examples#
This page contains some examples of PSY-TaLiRo tests interfacing with different systems. These
examples are also available in the example directory of the project repository. Each example
will execute a test and create a graph to visualize the outputs.
ODE#
This example defines a model using a basic first-order ODE which represents an aircraft in flight.
1import logging
2import math
3
4import plotly.graph_objects as go
5
6import staliro
7import staliro.models as models
8import staliro.optimizers as optimizers
9import staliro.specifications as specifications
10
11
12@models.ode()
13def nonlinear_model(inputs: models.Ode.Inputs) -> dict[str, float]:
14 x1 = inputs.state["x1"]
15 x2 = inputs.state["x2"]
16
17 return {
18 "x1": x1 - x2 + 0.1 * inputs.time, # x1_dot
19 "x2": x2 * math.cos(2 * math.pi * x1) + 0.1 * inputs.time, # x2_dot
20 }
21
22
23phi = r"always !(a >= -1.6 and a <= -1.4 and b >= -1.1 and b <= -0.9)"
24specification = specifications.rtamt.parse_dense(phi, {"a": 0, "b": 1})
25optimizer = optimizers.UniformRandom[float]()
26options = staliro.TestOptions(
27 runs=1,
28 iterations=100,
29 tspan=(0, 2),
30 static_inputs={
31 "x1": (-1, 1),
32 "x2": (-1, 1),
33 },
34)
35
36if __name__ == "__main__":
37 logging.basicConfig(level=logging.DEBUG)
38
39 runs = staliro.test(nonlinear_model, specification, optimizer, options)
40 run = runs[0]
41 min_eval = min(run.evaluations, key=lambda e: e.cost)
42
43 figure = go.Figure()
44 figure.add_trace(
45 go.Scatter(
46 name="Falsification area",
47 x=[-1.6, -1.4, -1.4, -1.6],
48 y=[-1.1, -1.1, -0.9, -0.9],
49 fill="toself",
50 fillcolor="red",
51 line_color="red",
52 mode="lines+markers",
53 )
54 )
55
56 figure.add_trace(
57 go.Scatter(
58 name="Initial condition region",
59 x=[-1, 1, 1, -1],
60 y=[-1, -1, 1, 1],
61 fill="toself",
62 fillcolor="green",
63 line_color="green",
64 mode="lines+markers",
65 )
66 )
67
68 figure.add_trace(
69 go.Scatter(
70 name="Samples",
71 x=[evaluation.sample.static["x1"] for evaluation in run.evaluations],
72 y=[evaluation.sample.static["x2"] for evaluation in run.evaluations],
73 mode="markers",
74 marker=go.scatter.Marker(color="lightblue", symbol="circle"),
75 )
76 )
77
78 figure.add_trace(
79 go.Scatter(
80 name="Best evaluation trajectory",
81 x=[state[0] for state in min_eval.extra.trace.states],
82 y=[state[1] for state in min_eval.extra.trace.states],
83 mode="lines+markers",
84 line=go.scatter.Line(color="blue", shape="spline"),
85 )
86 )
87
88 figure.write_image("nonlinear.jpeg")
Blackbox#
This example defines a model of the F16 Ground Collision Avoidance System (GCAS) which has been used to save pilots who have become unresponsive from crashing into the ground. The implementation of the GCAS system used in this example can be found here.
1import logging
2import math
3from typing import Final
4
5import numpy as np
6import plotly.graph_objects as go
7from aerobench.examples.gcas.gcas_autopilot import GcasAutopilot
8from aerobench.run_f16_sim import run_f16_sim
9
10from staliro import TestOptions, Trace, staliro
11from staliro.models import Blackbox, blackbox
12from staliro.optimizers import DualAnnealing
13from staliro.specifications import rtamt
14
15TSPAN: Final[tuple[float, float]] = (0, 15)
16
17
18@blackbox()
19def f16_model(inputs: Blackbox.Inputs) -> Trace[list[float]]:
20 power = 9
21 alpha = np.deg2rad(2.1215)
22 beta = 0
23 alt = 2330
24 vel = 540
25 phi = inputs.static["phi"]
26 theta = inputs.static["theta"]
27 psi = inputs.static["psi"]
28
29 initial_state = [vel, alpha, beta, phi, theta, psi, 0, 0, 0, 0, 0, 0, alt, power]
30 step = 1.0 / 30.0
31 autopilot = GcasAutopilot(init_mode="roll", stdout=False)
32 result = run_f16_sim(initial_state, TSPAN[1], autopilot, step, extended_states=True)
33 states = np.vstack(
34 (
35 np.array([0 if x == "standby" else 1 for x in result["modes"]]),
36 result["states"][:, 4], # roll
37 result["states"][:, 5], # pitch
38 result["states"][:, 6], # yaw
39 result["states"][:, 12], # altitude
40 )
41 )
42
43 return Trace(times=result["times"], states=np.transpose(states).tolist())
44
45
46spec = rtamt.parse_dense("always (alt > 0)", {"alt": 4})
47optimizer = DualAnnealing()
48options = TestOptions(
49 runs=1,
50 iterations=10,
51 tspan=TSPAN,
52 static_inputs={
53 "phi": math.pi / 4 + np.array([-math.pi / 20, math.pi / 30]),
54 "theta": -math.pi / 2 * 0.8 + np.array([0, math.pi / 20]),
55 "psi": -math.pi / 4 + np.array([-math.pi / 8, math.pi / 8]),
56 },
57)
58
59if __name__ == "__main__":
60 logging.basicConfig(level=logging.DEBUG)
61
62 runs = staliro(f16_model, spec, optimizer, options)
63 run = runs[0]
64 min_cost_eval = min(run.evaluations, key=lambda e: e.cost)
65 min_cost_trace = min_cost_eval.extra.trace
66
67 figure = go.Figure()
68 figure.update_layout(xaxis_title="time (s)", yaxis_title="alt (m)")
69 figure.add_hline(y=0, line_color="red")
70 figure.add_trace(
71 go.Scatter(
72 x=list(min_cost_trace.times),
73 y=[state[4] for state in min_cost_trace.states],
74 mode="lines",
75 line_color="green",
76 name="altitude",
77 )
78 )
79
80 figure.write_image("f16.jpeg")
MATLAB/Simulink#
This example demonstrates executing a simulink model of an automatic transmission. In order to interface with MATLAB you will need to ensure you have the MATLAB python interface installed according to the MathWorks instructions.
1import logging
2import pathlib
3
4import matlab
5import matlab.engine
6import numpy as np
7import plotly.graph_objects as go
8import plotly.subplots as sp
9
10from staliro import Sample, SignalInput, TestOptions, staliro
11from staliro.models import Model, Result
12from staliro.optimizers import DualAnnealing
13from staliro.specifications import rtamt
14
15
16class AutotransModel(Model[list[float], None]):
17 MODEL_NAME = "Autotrans_shift"
18
19 def __init__(self) -> None:
20 script_path = pathlib.Path(__name__)
21
22 self.sampling_step = 0.2
23 self.engine = matlab.engine.start_matlab()
24 self.engine.addpath(str(script_path.parent))
25
26 model_opts = self.engine.simget(AutotransModel.MODEL_NAME)
27 self.model_opts = self.engine.simset(model_opts, "SaveFormat", "Array")
28
29 def simulate(self, sample: Sample) -> Result[list[float], None]:
30 assert sample.signals.tspan is not None
31
32 tstart, tend = sample.signals.tspan
33 duration = tend - tstart
34 sim_t = matlab.double([0, tend])
35 n_times = duration // self.sampling_step
36 signal_times = np.linspace(tstart, tend, num=int(n_times))
37 signal_values = np.array(
38 [[signal.at_time(t) for t in signal_times] for signal in sample.signals]
39 )
40
41 model_input = matlab.double(np.row_stack((signal_times, signal_values)).T.tolist())
42 timestamps, _, data = self.engine.sim(
43 self.MODEL_NAME, sim_t, self.model_opts, model_input, nargout=3
44 )
45
46 times: list[float] = np.array(timestamps).flatten().tolist()
47 states: list[list[float]] = list(data)
48
49 return Result(times=times, states=states, extra=None)
50
51
52model = AutotransModel()
53
54phi = "always[0,30] (rpm >= 3000) -> (always[0,4] speed >= 35)"
55specification = rtamt.parse_discrete(phi, {"rpm": 0, "speed": 1})
56
57optimizer = DualAnnealing()
58
59options = TestOptions(
60 runs=1,
61 iterations=100,
62 tspan=(0, 30),
63 signals={
64 "throttle": SignalInput(control_points=[(0, 100)] * 7),
65 "brake": SignalInput(control_points=[(0, 350)] * 3),
66 },
67)
68
69if __name__ == "__main__":
70 logging.basicConfig(level=logging.DEBUG)
71
72 runs = staliro(model, specification, optimizer, options)
73 run = runs[0]
74 min_eval = min(run.evaluations, key=lambda e: e.cost)
75
76 times = list(min_eval.extra.trace.times)
77 rpm = [state[0] for state in min_eval.extra.trace.states]
78 speed = [state[1] for state in min_eval.extra.trace.states]
79
80 figure = sp.make_subplots(rows=2, cols=1, shared_xaxes=True, x_title="Time (s)")
81 figure.add_trace(go.Scatter(x=times, y=rpm), row=1, col=1)
82 figure.add_trace(go.Scatter(x=times, y=speed), row=2, col=1)
83 figure.update_yaxes(title_text="RPM", row=1, col=1)
84 figure.update_yaxes(title_text="Speed", row=2, col=1)
85 figure.write_image("autotrans.jpeg")
Bi-Level#
This example demonstrates a bi-level test where the outer optimization attempt searches over the initial altitude of the aircraft and the inner optimization attempt searches over the initial attitude of the aircraft.
1import math
2
3import aerobench.examples.gcas.gcas_autopilot as autopilot
4import aerobench.run_f16_sim as f16_sim
5import numpy as np
6
7import staliro
8import staliro.models as models
9import staliro.optimizers as optimizers
10import staliro.specifications as specifications
11
12TSPAN = (0, 15)
13
14
15@staliro.costfunc()
16def outer(sample: staliro.Sample) -> float:
17 @models.blackbox(step_size=0.1)
18 def inner(inputs: models.Blackbox.Inputs) -> models.Trace[list[float]]:
19 power = 9
20 alpha = np.deg2rad(2.1215)
21 beta = 0
22 alt = sample.static["alt"]
23 vel = 540
24 phi = inputs.static["phi"]
25 theta = inputs.static["theta"]
26 psi = inputs.static["psi"]
27
28 initial_state = [vel, alpha, beta, phi, theta, psi, 0, 0, 0, 0, 0, 0, alt, power]
29 step = 1.0 / 30.0
30 system = autopilot.GcasAutopilot(init_mode="roll", stdout=False)
31 result = f16_sim.run_f16_sim(initial_state, TSPAN[1], system, step, extended_states=True)
32 states = np.vstack(
33 (
34 np.array([0 if x == "standby" else 1 for x in result["modes"]]),
35 result["states"][:, 4], # roll
36 result["states"][:, 5], # pitch
37 result["states"][:, 6], # yaw
38 result["states"][:, 12], # altitude
39 )
40 )
41
42 return models.Trace(times=result["times"], states=np.transpose(states).tolist())
43
44 spec = specifications.rtamt.parse_dense("always (alt >= 0)", {"alt": 0})
45 optimizer = optimizers.UniformRandom[float]()
46 options = staliro.TestOptions(
47 runs=1,
48 tspan=TSPAN,
49 iterations=500,
50 static_inputs={
51 "phi": math.pi / 4 + np.array([-math.pi / 20, math.pi / 30]),
52 "theta": -math.pi / 2 * 0.8 + np.array([0, math.pi / 20]),
53 "psi": -math.pi / 4 + np.array([-math.pi / 8, math.pi / 8]),
54 },
55 )
56
57 results = staliro.test(inner, spec, optimizer, options)
58 result = results[0]
59
60 return min(e.cost for e in result.evaluations)
61
62
63if __name__ == "__main__":
64 optimizer = optimizers.UniformRandom(min_cost=0.0)
65 options = staliro.TestOptions(
66 runs=1,
67 iterations=100,
68 static_inputs={"alt": (2200, 2500)},
69 )
70
71 results = staliro.test(outer, optimizer, options)
72 result = results[0]
73 min_cost = min(e.cost for e in result.evaluations)
74
75 print(f"Minimum cost found: {min_cost}")