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

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