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"))

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}")