banquo_core/
lib.rs

1#![deny(clippy::all)]
2
3use std::borrow::Borrow;
4use std::sync::Arc;
5use std::rc::Rc;
6
7use thiserror::Error;
8
9pub mod metrics;
10pub mod operators;
11pub mod predicate;
12pub mod trace;
13
14pub use crate::trace::Trace;
15pub use crate::metrics::{Top, Bottom, Meet, Join};
16
17/// Trait representing a temporal logic formula that can evaluate a timed set of system states
18/// called a [`Trace`].
19///
20/// In general, temporal logic formulas are used to represent requirements over the behaviors of a
21/// system, and are evaluated to quantify the degree to which the system satisfies or violates the
22/// requirement. There are many types of metrics used for this analysis, but this library defaults
23/// to [robustness] which is simply a measure of distance representing how "close" the system
24/// came to violation.
25///
26/// Implementations of this trait are expected to maintain the length of the trace, and should be
27/// able to handle input traces with no elements.
28///
29/// # Examples
30///
31/// For additional examples, please refer to the [`Predicate`](crate::predicate::Predicate) type and the
32/// [`operators`](crate::operators) module.
33///
34/// [robustness]: https://link.springer.com/chapter/10.1007/11940197_12
35///
36/// ```rust
37/// # use banquo::{Formula, Trace};
38/// struct UnitFormula;
39///
40/// impl<T> Formula<T> for UnitFormula {
41///     type Metric = ();
42///     type Error = ();
43///
44///     fn evaluate(&self, trace: &Trace<T>) -> Result<Trace<Self::Metric>, Self::Error> {
45///         let result = trace
46///             .iter()
47///             .map_states(|_| ())
48///             .collect();
49///
50///         Ok(result)
51///     }
52/// }
53/// ```
54pub trait Formula<State> {
55    /// The output metric from this formula
56    type Metric;
57
58    /// The type of error that may be produced during evaluation
59    type Error;
60
61    /// Evaluate a given trace of system states into either a trace of metric values or an error.
62    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error>;
63}
64
65impl<State, T> Formula<State> for &T
66where
67    T: Formula<State> + ?Sized,
68{
69    type Metric = T::Metric;
70    type Error = T::Error;
71
72    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error> {
73        (**self).evaluate(trace)
74    }
75}
76
77impl<State, T> Formula<State> for Box<T>
78where
79    T: Formula<State> + ?Sized,
80{
81    type Metric = T::Metric;
82    type Error = T::Error;
83
84    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error> {
85        (**self).evaluate(trace)
86    }
87}
88
89impl<State, T> Formula<State> for Arc<T>
90where
91    T: Formula<State> + ?Sized,
92{
93    type Metric = T::Metric;
94    type Error = T::Error;
95
96    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error> {
97        (**self).evaluate(trace)
98    }
99}
100
101impl<State, T> Formula<State> for Rc<T>
102where
103    T: Formula<State> + ?Sized,
104{
105    type Metric = T::Metric;
106    type Error = T::Error;
107
108    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error> {
109        (**self).evaluate(trace)
110    }
111}
112
113#[derive(Debug, Error)]
114enum ErrorKind {
115    #[error("Error evaluating formula: {source}")]
116    FormulaError {
117        source: Box<dyn std::error::Error>,
118    },
119
120    #[error("Empty trace")]
121    EmptyTraceError,
122}
123
124/// Error produced while evaluating a [`Trace`] using the [`evaluate`] function.
125///
126/// Two conditions are represented using this error type:
127///   1. An error is encountered during evaluation of the formula
128///   2. The trace does not contain any elements.
129#[derive(Debug, Error)]
130#[error(transparent)]
131pub struct EvaluationError(ErrorKind);
132
133impl EvaluationError {
134    /// Create a new evaluation error for an arbitrary inner error.
135    ///
136    /// The error provided this function is expected to be generated from a [`Formula`] evaluation.
137    /// This function allocates on the heap.
138    pub fn wrap<E>(source: E) -> Self
139    where
140        E: std::error::Error + 'static,
141    {
142        Self(ErrorKind::FormulaError { source: source.into() })
143    }
144
145    /// Create a new evaluation error when a trace does not contain any elements.
146    pub fn empty() -> Self {
147        Self(ErrorKind::EmptyTraceError)
148    }
149}
150
151/// Evaluate a [`Trace`] using a given [`Formula`] and return the metric value for the earliest
152/// time.
153///
154/// This function accepts either a borrowed or owned trace value, and any [`Formula`] type. The
155/// trace is evaluated using the formula, which may produce an error, and then the first
156/// metric is extracted from the trace , which will also produce an error if the trace is empty.
157///
158/// # Examples
159///
160/// ```rust
161/// use std::collections::HashMap;
162///
163/// use banquo::{EvaluationError, Trace, evaluate, predicate};
164///
165/// let phi = predicate!{ x <= 10.0 };
166///
167/// let t1 = Trace::from([
168///     (0.0, HashMap::from([("x", 8.0)])),
169///     (1.0, HashMap::from([("x", 4.0)])),
170/// ]);
171///
172/// let t2 = Trace::from([
173///     (0.0, HashMap::from([("y", 8.0)])),
174/// ]);
175///
176/// let t3: Trace<HashMap<&'static str, f64>> = Trace::new();
177///
178/// evaluate(&t1, &phi); // Ok -> 2.0
179/// evaluate(&t2, &phi); // Error -> predicate evaluation error
180/// evaluate(t3, &phi);  // Error -> empty trace
181/// ```
182pub fn evaluate<T, F, State, M, E>(trace: T, formula: F) -> Result<M, EvaluationError>
183where
184    T: Borrow<Trace<State>>,
185    F: Formula<State, Metric = M, Error = E>,
186    E: std::error::Error + 'static,
187{
188    formula
189        .evaluate(trace.borrow())
190        .map_err(EvaluationError::wrap)
191        .and_then(|trace| {
192            trace
193                .into_iter()
194                .next()
195                .map(|(_, metric)| metric)
196                .ok_or_else(EvaluationError::empty)
197        })
198}