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
3import pathlib
4
5import plotly.graph_objects as go
6
7import staliro
8import staliro.models as models
9import staliro.optimizers as optimizers
10import staliro.specifications.rtamt as rtamt
11
12
13@staliro.ode(method="RK45")
14def nonlinear_model(inputs: models.Ode.Inputs) -> dict[str, float]:
15 x1 = inputs.state["x1"]
16 x2 = inputs.state["x2"]
17
18 return {
19 "x1": x1 - x2 + 0.1 * inputs.time, # x1_dot
20 "x2": x2 * math.cos(2 * math.pi * x1) + 0.1 * inputs.time, # x2_dot
21 }
22
23
24phi = r"always !(a >= -1.6 and a <= -1.4 and b >= -1.1 and b <= -0.9)"
25specification = rtamt.parse_dense(phi, {"a": 0, "b": 1})
26optimizer = optimizers.UniformRandom[float]()
27options = staliro.TestOptions(
28 runs=1,
29 iterations=100,
30 tspan=(0, 2),
31 static_inputs={
32 "x1": (-1, 1),
33 "x2": (-1, 1),
34 },
35)
36
37if __name__ == "__main__":
38 logging.basicConfig(level=logging.DEBUG)
39
40 runs = staliro.test(nonlinear_model, specification, optimizer, options)
41 run = runs[0]
42 min_eval = min(run.evaluations, key=lambda e: e.cost)
43
44 figure = go.Figure()
45 figure.add_trace(
46 go.Scatter(
47 name="Falsification area",
48 x=[-1.6, -1.4, -1.4, -1.6],
49 y=[-1.1, -1.1, -0.9, -0.9],
50 fill="toself",
51 fillcolor="red",
52 line_color="red",
53 mode="lines+markers",
54 )
55 )
56
57 figure.add_trace(
58 go.Scatter(
59 name="Initial condition region",
60 x=[-1, 1, 1, -1],
61 y=[-1, -1, 1, 1],
62 fill="toself",
63 fillcolor="green",
64 line_color="green",
65 mode="lines+markers",
66 )
67 )
68
69 figure.add_trace(
70 go.Scatter(
71 name="Samples",
72 x=[evaluation.sample.static["x1"] for evaluation in run.evaluations],
73 y=[evaluation.sample.static["x2"] for evaluation in run.evaluations],
74 mode="markers",
75 marker=go.scatter.Marker(color="lightblue", symbol="circle"),
76 )
77 )
78
79 figure.add_trace(
80 go.Scatter(
81 name="Best evaluation trajectory",
82 x=[state[0] for state in min_eval.extra.trace.states],
83 y=[state[1] for state in min_eval.extra.trace.states],
84 mode="lines+markers",
85 line=go.scatter.Line(color="blue", shape="spline"),
86 )
87 )
88
89 figure.write_image(pathlib.Path("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
3import pathlib
4from typing import Final
5
6import numpy as np
7import plotly.graph_objects as go
8from aerobench.examples.gcas.gcas_autopilot import GcasAutopilot
9from aerobench.run_f16_sim import run_f16_sim
10
11from staliro import TestOptions, Trace, blackbox, staliro
12from staliro.models import Blackbox
13from staliro.optimizers import DualAnnealing
14from staliro.specifications import rtamt
15
16TSPAN: Final[tuple[float, float]] = (0, 15)
17
18
19@blackbox(step_size=0.1)
20def f16_model(inputs: Blackbox.Inputs) -> Trace[list[float]]:
21 power = 9
22 alpha = np.deg2rad(2.1215)
23 beta = 0
24 alt = 2330
25 vel = 540
26 phi = inputs.static["phi"]
27 theta = inputs.static["theta"]
28 psi = inputs.static["psi"]
29
30 initial_state = [vel, alpha, beta, phi, theta, psi, 0, 0, 0, 0, 0, 0, alt, power]
31 step = 1.0 / 30.0
32 autopilot = GcasAutopilot(init_mode="roll", stdout=False)
33 result = run_f16_sim(initial_state, TSPAN[1], autopilot, step, extended_states=True)
34 states = np.vstack(
35 (
36 np.array([0 if x == "standby" else 1 for x in result["modes"]]),
37 result["states"][:, 4], # roll
38 result["states"][:, 5], # pitch
39 result["states"][:, 6], # yaw
40 result["states"][:, 12], # altitude
41 )
42 )
43
44 return Trace(times=result["times"], states=np.transpose(states).tolist())
45
46
47spec = rtamt.parse_dense("always (alt > 0)", {"alt": 4})
48optimizer = DualAnnealing()
49options = TestOptions(
50 runs=1,
51 iterations=10,
52 tspan=TSPAN,
53 static_inputs={
54 "phi": math.pi / 4 + np.array([-math.pi / 20, math.pi / 30]),
55 "theta": -math.pi / 2 * 0.8 + np.array([0, math.pi / 20]),
56 "psi": -math.pi / 4 + np.array([-math.pi / 8, math.pi / 8]),
57 },
58)
59
60if __name__ == "__main__":
61 logging.basicConfig(level=logging.DEBUG)
62
63 runs = staliro(f16_model, spec, optimizer, options)
64 run = runs[0]
65 min_cost_eval = min(run.evaluations, key=lambda e: e.cost)
66 min_cost_trace = min_cost_eval.extra.trace
67
68 figure = go.Figure()
69 figure.update_layout(xaxis_title="time (s)", yaxis_title="alt (m)")
70 figure.add_hline(y=0, line_color="red")
71 figure.add_trace(
72 go.Scatter(
73 x=list(min_cost_trace.times),
74 y=[state[4] for state in min_cost_trace.states],
75 mode="lines",
76 line_color="green",
77 name="altitude",
78 )
79 )
80
81 figure.write_image(pathlib.Path("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(pathlib.Path("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.rtamt as rtamt
11
12TSPAN = (0, 15)
13
14
15@staliro.costfunc
16def outer(sample: staliro.Sample) -> float:
17 @staliro.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 = 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}")