Specification language
Robustness semantics
The definition of quantitative robustness for every SENTIL operator.
Robustness maps a formula and a trace to a signed real number. The sign is the positive for satisfied and negative for violated, and the magnitude is how far the signal can move before the verdict flips. The definition is recursive meaning that predicates give a signed distance, boolean connectives combine robustnessmargins, and temporal operators quantify a margin over a window of time. Write for the robustness of at time over the trace. The grammar page covers how to write each form and the operators page works through every temporal operator on a concrete trace.
Predicates
A predicate compares two arithmetic terms. Its robustness is the signed distance to the comparison boundary, with and the values of the left and right terms at time .
Both sides are arithmetic terms, so speed > 0.5 * wind is one predicate whose margin is speed - 0.5 * wind. A strict comparison and its non-strict partner share the same margin. They differ only on the boundary, where the margin is zero and the predicate counts as satisfied.
Terms may call the twelve built-in functions listed on the grammar page, and functions nest:
import sentil
from sentil import Formula
trace = sentil.Trace([0.0], {"x": [1.5], "target": [2.0], "y": [2.4]})
phi = Formula.parse("max(abs(x - target), abs(y - target)) < 1")
phi.robustness(trace) # 0.5The inner distances are abs(1.5 - 2.0) = 0.5 and abs(2.4 - 2.0) = 0.4; their maximum is 0.5; the predicate < 1 scores 1 - 0.5 = 0.5, a positive margin with half a unit to spare.
Arithmetic edge cases
Division and modulo by zero stop evaluation with an error, for instance division by zero while evaluating `y / x`. Other functions follow IEEE arithmetic, like ln(0) is , and sqrt or ln of a negative value is NaN. A NaN margin propagates as the robustness value when it stands alone, but min and max, including the folds behind & and |, drop a NaN operand and return the other, so one NaN predicate does not poison a conjunction: with x = -1 and y = 1, (sqrt(x) > 0) & (y > 0) scores 1.0.
Boolean connectives
The connectives combine margins with negation, minimum, and maximum.
The spelled-out forms not, and, or, and implies lex to the same four connectives as !, & or &&, | or ||, and ->. A property that holds unconditionally has robustness , and one that fails unconditionally has .
! (speed > 5) with speed = 12 scores -7, the predicate margin negated. (speed > 5) & (speed < 30) scores min(7, 18) = 7: the weaker side, the lower limit, decides. (mode == 1) | (mode == 3) with mode = 1 scores max(0, -2) = 0, the satisfied disjunct on its boundary. And implication:
trace = sentil.Trace([0.0], {"temp": [90.0], "fan": [1.0]})
Formula.parse("(temp > 80) -> (fan == 1)").robustness(trace) # 0.0The antecedent scores 10 and the consequent 0, so the implication scores max(-10, 0) = 0.
Future temporal operators
The G and F operators, read always and eventually, take the infimum and supremum of the inner robustness over a forward-shifted window ; when the interval is unbounded above, .
The U operator, read until, asks to hold until becomes true somewhere in the window, and its robustness is a supremum over witness times of the smaller of the margin there and the running infimum of up to it.
The guard infimum stops short of itself: the interval is half-open, so the guard need not hold at the instant the release fires.
The X operator, read next, shifts evaluation one sample forward. At the final sample it has no successor, so it returns .
Past temporal operators
The past operators mirror the future ones over a backward window . H, read historically, is the past dual of G, O, read once, the dual of F, and S, read since, the dual of U.
The guard interval is again half-open, this time at : the guard applies strictly after the trigger fires, never at the instant itself.
The probabilistic operator
The PrSTL operator does not report a margin of its own. It compares the estimated satisfaction probability , produced by the statistical layer over an ensemble of sampled trajectories, against its threshold , and collapses to a hard bound. For the at-least form:
The >, <=, and < forms compare against with the corresponding relation. P is a top-level operator in practice: a nested P parses, but every evaluation path rejects it with a typed error rather than treating the verdict as a margin. The operators page shows the error text.
Discrete and dense time
In discrete time the window infima and suprema range over the trace's sample points that fall inside the interval, and an empty window reduces to the operator's identity, for an infimum and for a supremum. The outermost reduction of U and S is a supremum, so their empty windows likewise yield . In dense time the trace is treated as a continuous signal between samples under one of three interpolation modes: linear, zero-order hold, or cubic spline. The window extrema then range over the interpolated signal, which can place the decisive value strictly between two samples. The one exception is X, which is tied to the sample index and has no dense reading; dense evaluation rejects it with a typed error. Both modes otherwise share this recursive definition and differ only in what set of times the window quantifies over.
The bounded G, F, H, and O windows are evaluated with a monotonic-deque sliding extremum: O(1) amortized work per sample and memory proportional to the window rather than the trace, with the equivalence to the naive window scan proven in Lean under proofs/. Why the deque is O(1) amortized shows it to you.