How to
Lift a trace into an ensemble
Make and register a fitted noise model against a trace.
A PrSTL formula asks whether a property holds once noise is accounted for. This is done by drawing a distribution of traces from a single trace. Lifting is how you get the distribution.
Register the noise
A LiftingRegistry maps each signal to a noise model and an interaction mode.
import sentil
from sentil import Formula, LiftingRegistry, NoiseInteraction, NoiseModel, SmcConfig
trace = sentil.Trace(list(range(20)), {"x": [0.4 + 0.05 * i for i in range(20)]})
lifting = LiftingRegistry()
lifting.register("x", NoiseModel.gaussian(0.0, 0.3), NoiseInteraction.Additive)
phi = Formula.parse("P>=0.9 (G (x > 0))")
result = phi.check(trace, lifting, SmcConfig(samples=5000))
print(f"p = {result.probability:.3f}, holds = {result.holds}")use sentil::{Formula, LiftingRegistry, NoiseInteraction, NoiseModel, SmcConfig, Trace};
let times: Vec<f64> = (0..20).map(f64::from).collect();
let x: Vec<f64> = (0..20).map(|i| 0.4 + 0.05 * f64::from(i)).collect();
let mut trace = Trace::new(times)?;
trace.add_signal("x", x)?;
let mut lifting = LiftingRegistry::new();
lifting.register("x", NoiseModel::gaussian(0.0, 0.3)?, NoiseInteraction::Additive);
let phi = Formula::parse("P>=0.9 (G (x > 0))")?;
let result = phi.check(&trace, &lifting, &SmcConfig { samples: 5000, ..SmcConfig::default() })?;
println!("p = {:.3}, holds = {}", result.probability, result.holds);Only registered signals are perturbed. A registry with no entry for a signal leaves it exactly as recorded, so a multi-signal trace can mix noisy sensors and known-exact channels in one check. An empty registry makes lifting the identity.
Read the estimate
The check lifts the trace samples times and scores the inner formula on each realization. Each of the realizations is one draw from the lifted ensemble, a realization counts as a satisfaction when its robustness is positive, and the empirical satisfaction probability is the mean of the indicator:
That is the Monte Carlo estimate. result.probability is , result.satisfactions is the numerator, and result.samples is . As grows, converges to the true satisfaction probability, and the interval around it narrows. The verdict result.holds compares against the threshold in the P>=0.9 operator.
Look at one realization
Sometimes you want the noisy trace itself, not the aggregate, to see what a single draw looks like or to feed it into a deterministic monitor. lift produces one realization.
noisy = lifting.lift(trace, seed=7)
print(noisy["x"])let noisy = lifting.lift(&trace, 7)?;
println!("{:?}", noisy.signal("x"));The Monte Carlo driver does the same draw internally, once per sample, from a base seed offset by the sample index. Because of the deterministic nature of the seed, a run reproduces exactly.
Set the ensemble size
SmcConfig.samples is , the number of realizations drawn. It defaults to 10000. A larger narrows the interval at a cost linear in . This means that a smaller is faster but reports a wider interval. Rather than guess, size the budget from the tolerance you need with the Chernoff-Hoeffding bound. The remaining fields set the confidence level, the base seed, and which interval to report.
Before you lift, you need a fitted model. Fit each noise model covers building one from paired calibration data. For the theory behind the ensemble and the P~p operator, read what PrSTL is.
Rare-event estimation
Adaptive multilevel splitting allows you to estimate rare events more efficiently than Monte Carlo.
Fit a noise model
Turn paired ground-truth and sensor data into residuals, choose additive or multiplicative interaction, and fit a distribution, a bootstrap, or a Gaussian mixture.