banquo_core/
predicate.rs

1//! System requirements expressed as the inequality **`ax`**`≤ b`.
2//!
3//! In this library a [`Predicate`] is used to represent some constraint on a system state. As an
4//! example, consider a requirement on a automotive system such as "The RPM of transmission should
5//! always be greater than or equal to 10,000". In this situation, we would use a `Predicate` to
6//! represent the `rpm <= 10_000` part of the requirement.
7//!
8//! In this inequality, both **a** and **x** are both maps of names to [`f64`] variables, while b
9//! is a constant value. The **a** vector contains the variable coefficients and is created while
10//! constructing the predicate. Conversely, the **x** vector contains the variable values and is
11//! provided by the system trace. The [robustness] value of the inequality is computed using the
12//! equation `b - `**`a`**`·`**`x`**.
13//!
14//! [robustness]: https://link.springer.com/chapter/10.1007/11940197_12
15//!
16//! # Examples
17//!
18//! You can explicitly create a new empty `Predicate` using the [`Predicate::new`] function:
19//!
20//! ```rust
21//! use banquo::Predicate;
22//! let p = Predicate::new();
23//! ```
24//!
25//! You can add new terms to a predicate using the [`AddAssign`](std::ops::AddAssign) and
26//! [`SubAssign`](std::ops::SubAssign) operators like so:
27//!
28//! ```rust
29//! use banquo::Predicate;
30//!
31//! let mut p = Predicate::new();
32//!
33//! p += ("x", 1.5);  // Add a term x to the _a_ vector with coefficient 1.0
34//! p += (2.0, "y");  // Add a term y to the _a_ vector with coefficient 2.0
35//! p += "z";         // Add a term z to the _a_ vector with coefficient 1.0
36//! p += 1.3;         // Add constant to _b_ value;
37//!
38//! p -= ("x", 0.6);  // Subtract a term x from _a_ vector with coefficient 0.5
39//! p -= (2.0, "y");  // Subtract a term y from _a_ vector with coefficient 2.0
40//! p -= 0.6;         // Subtract constant from _b_ value;
41//! ```
42//!
43//! Any value that can be converted into a [`Term`] is supported as an `AddAssign` operand. If a
44//! variable already contains a coefficient then the two values will be added together. Therefore,
45//! the final result of the example above would be the predicate `0.9 * x + 1.0 * z ≤ 0.7`.
46//!
47//! For a predicate of known length, you can create a `Predicate` from an array:
48//!
49//! ```rust
50//! use banquo::predicate::{Predicate, Term};
51//!
52//! let p = Predicate::from([
53//!     Term::from(("x", 1.0)),
54//!     Term::from((1.0, "y")),
55//!     Term::from("z"),
56//!     Term::from(1.0),
57//! ]);
58//! ```
59//!
60//! You can also use the [`predicate!`](crate::predicate!) macro for short-hand initialization:
61//!
62//! ```rust
63//! use banquo::predicate;
64//! let p = predicate!{ x + y * 1.1 + 1.2 * z + 1.3 <= 2.0 * a + 2.1 };
65//! ```
66//!
67//! Once a `Predicate` has been constructed, you can use it to evaluate a system state using the
68//! [`Predicate::evaluate_state`] method. Any type that implements the [`VariableSet`] trait can be
69//! evaluated.
70//!
71//! ```rust
72//! use std::collections::{HashMap, BTreeMap};
73//!
74//! use banquo::predicate;
75//!
76//! let p = predicate!{ x + y * 1.1 + 1.2 * z + 1.3 <= 2.0 * a + 2.1 };
77//! let s1 = HashMap::from([
78//!     ("x", 1.0),
79//!     ("y", 2.0),
80//!     ("z", 3.0),
81//!     ("a", 0.5),
82//! ]);
83//! 
84//! let s2 = BTreeMap::from([
85//!     ("x", 1.5),
86//!     ("y", 0.8),
87//!     ("z", 4.9),
88//!     ("a", 0.1),
89//! ]);
90//!
91//! let _ = p.evaluate_state(&s1);
92//! let _ = p.evaluate_state(&s2);
93//! ```
94//!
95//! A `Predicate` also implements the [`Formula`] trait which allows evaluating a set of timed
96//! system states.
97//!
98//! ```rust
99//! use std::collections::HashMap;
100//!
101//! use banquo::predicate;
102//! use banquo::{Trace, Formula};
103//!
104//! let p = predicate!{ x + y * 1.1 + 1.2 * z + 1.3 <= 2.0 * a + 2.1 };
105//! let trace = Trace::from([
106//!     (1.0, HashMap::from([
107//!         ("x", 1.0),
108//!         ("y", 2.0),
109//!         ("z", 3.0),
110//!         ("a", 0.5),
111//!     ])),
112//!     (2.0, HashMap::from([
113//!         ("x", 1.5),
114//!         ("y", 0.8),
115//!         ("z", 4.9),
116//!         ("a", 0.1),
117//!     ])),
118//! ]);
119//!
120//! let _ = p.evaluate(&trace);
121//! ```
122
123use std::borrow::Borrow;
124use std::collections::{BTreeMap, HashMap};
125use std::hash::Hash;
126use std::ops::{AddAssign, Index, Neg, SubAssign};
127
128use thiserror::Error;
129
130use crate::Formula;
131use crate::trace::Trace;
132
133/// System requirements expressed as the inequality **`ax`**`≤ b`.
134///
135/// See the [`predicate`](predicate) module for more information on the semantics of this data type.
136#[derive(Debug, Clone, Default, PartialEq)]
137pub struct Predicate {
138    coefficients: HashMap<String, f64>,
139    constant: f64,
140}
141
142impl Predicate {
143    /// Create a new empty predicate equivalent to `0 ≤ 0`.
144    ///
145    /// # Examples
146    ///
147    /// ```rust
148    /// use banquo::Predicate;
149    /// let mut p = Predicate::new();
150    /// ```
151    pub fn new() -> Self {
152        Self::default()
153    }
154
155    /// Return a variable coefficient from the left-hand side of the inequality if it has been set,
156    /// otherwise return [`None`].
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use banquo::Predicate;
162    ///
163    /// let p = Predicate::from([
164    ///     ("x", 1.0), ("y", 3.0), ("z", 2.0),
165    /// ]);
166    ///
167    /// p.get("x");  // Some(3.0)
168    /// p.get("a");  // None
169    /// ```
170    pub fn get(&self, name: &str) -> Option<f64> {
171        self.coefficients.get(name).copied()
172    }
173
174    /// Return the constant from the right-hand side of the inequality.
175    ///
176    /// # Examples
177    ///
178    /// ```rust
179    /// use banquo::Predicate;
180    ///
181    /// let p = Predicate::from([
182    ///     (None, 5.0), (Some("x"), 1.0), (Some("y"), 2.0), (None, 10.0),
183    /// ]);
184    ///
185    /// p.constant();  // 15
186    /// ```
187    pub fn constant(&self) -> f64 {
188        self.constant
189    }
190}
191
192/// Iterator over the coefficient terms of a predicate.
193///
194/// The coefficient terms are the terms with a variable name associated with them. This iterator
195/// does not guarantee any order of the terms.
196#[derive(Debug)]
197pub struct Coefficients<'a>(std::collections::hash_map::Iter<'a, String, f64>);
198
199impl<'a> Iterator for Coefficients<'a> {
200    type Item = (&'a str, f64);
201
202    fn next(&mut self) -> Option<Self::Item> {
203        self.0
204            .next()
205            .map(|(name, value)| (name.as_str(), *value))
206    }
207}
208
209impl Predicate {
210    /// Create an iterator over the coefficient terms of the `Predicate`.
211    ///
212    /// A coefficient term is a term that has a variable. This function does not make any guarantees
213    /// about the order of iteration of the terms.
214    ///
215    /// # Example
216    ///
217    /// ```rust
218    /// let p = banquo::predicate!{ x + 2.0 * y + 3.3 * z <= 4.2 };
219    ///
220    /// for (name, coefficient) in p.coefficients() {
221    ///     // ...
222    /// }
223    /// ````
224    pub fn coefficients(&self) -> Coefficients<'_> {
225        Coefficients(self.coefficients.iter())
226    }
227}
228
229impl<T> FromIterator<T> for Predicate
230where
231    T: Into<Term>,
232{
233    fn from_iter<I>(terms: I) -> Self 
234    where
235        I: IntoIterator<Item = T>
236    {
237        let mut p = Predicate::new();
238
239        for term in terms {
240            p += term.into();
241        }
242
243        p
244    }
245}
246
247impl<T, const N: usize> From<[T; N]> for Predicate
248where
249    T: Into<Term>,
250{
251    fn from(terms: [T; N]) -> Self {
252        Predicate::from_iter(terms)
253    }
254}
255
256impl Index<&str> for Predicate {
257    type Output = f64;
258    
259    /// Returns a reference to a coefficient in the predicate
260    ///
261    /// # Panics
262    ///
263    /// Panics if the variable does not have a coefficient set
264    fn index(&self, index: &str) -> &Self::Output {
265        &self.coefficients[index]
266    }
267}
268
269impl Neg for Predicate {
270    type Output = Self;
271
272    fn neg(self) -> Self::Output {
273        Self {
274            constant: -self.constant,
275            coefficients: self.coefficients
276                .into_iter()
277                .map(|(name, coeff)| (name, -coeff))
278                .collect(),
279        }
280    }
281}
282
283/// A variable or constant term in a predicate.
284///
285/// A `Variable` term contains a variable name and the associated coefficient, while a `Constant`
286/// term contains only a constant value. The purpose of this type is to provide a conversion target
287/// that supports multiple different types as [`AddAssign`] operands for a [`Predicate`].
288///
289/// # Examples
290///
291/// ```rust
292/// use banquo::predicate::Term;
293///
294/// let terms = [
295///     Term::from(1.0),
296///     Term::from("x"),
297///     Term::from(("x", 1.0)),
298///     Term::from((1.0, "x")),
299///     Term::from((Some("x"), 1.0)),
300///     Term::from((1.0, Some("x"))),
301/// ];
302///
303/// ```
304pub enum Term {
305    Variable(String, f64),
306    Constant(f64),
307}
308
309impl Neg for Term {
310    type Output = Term;
311
312    fn neg(self) -> Self::Output {
313        match self {
314            Self::Variable(name, value) => Self::Variable(name, -value),
315            Self::Constant(value) => Self::Constant(-value),
316        }
317    }
318}
319
320impl From<&str> for Term {
321    fn from(name: &str) -> Self {
322        Term::Variable(name.into(), 1.0)
323    }
324}
325
326impl From<String> for Term {
327    fn from(name: String) -> Self {
328        Term::Variable(name, 1.0)
329    }
330}
331
332impl From<(&str, f64)> for Term {
333    fn from((name, value): (&str, f64)) -> Self {
334        Term::Variable(name.into(), value)
335    }
336}
337
338impl From<(String, f64)> for Term {
339    fn from((name, value): (String, f64)) -> Self {
340        Term::Variable(name, value)
341    }
342}
343
344impl From<(f64, &str)> for Term {
345    fn from((value, name): (f64, &str)) -> Self {
346        Term::Variable(name.into(), value)
347    }
348}
349
350impl From<(f64, String)> for Term {
351    fn from((value, name): (f64, String)) -> Self {
352        Term::Variable(name, value)
353    }
354}
355
356impl From<f64> for Term {
357    fn from(value: f64) -> Self {
358        Term::Constant(value)
359    }
360}
361
362impl From<(Option<String>, f64)> for Term {
363    fn from((name, value): (Option<String>, f64)) -> Self {
364        match name {
365            Some(n) => Term::from((n, value)),
366            None => Term::from(value),
367        }
368    }
369}
370
371impl From<(Option<&str>, f64)> for Term {
372    fn from((name, value): (Option<&str>, f64)) -> Self {
373        match name {
374            Some(n) => Term::from((n, value)),
375            None => Term::from(value),
376        }
377    }
378}
379
380impl From<(f64, Option<String>)> for Term {
381    fn from((value, name): (f64, Option<String>)) -> Self {
382        match name {
383            Some(n) => Term::from((n, value)),
384            None => Term::from(value),
385        }
386    }
387}
388
389impl From<(f64, Option<&str>)> for Term {
390    fn from((value, name): (f64, Option<&str>)) -> Self {
391        match name {
392            Some(n) => Term::from((n, value)),
393            None => Term::from(value),
394        }
395    }
396}
397
398impl<T> AddAssign<T> for Predicate
399where
400    T: Into<Term>,
401{
402    fn add_assign(&mut self, rhs: T) {
403        match rhs.into() {
404            Term::Variable(name, value) => {
405                let coeff = self.coefficients
406                    .entry(name)
407                    .or_insert(0.0);
408
409                *coeff += value;
410            },
411            Term::Constant(value) => {
412                self.constant += value;
413            },
414        }
415    }
416}
417
418impl<T> SubAssign<T> for Predicate
419where
420    T: Into<Term>,
421{
422    fn sub_assign(&mut self, rhs: T) {
423        *self += rhs.into().neg();
424    }
425}
426
427/// Trait representing a set of named variables.
428pub trait VariableSet {
429    /// Return the value for a name in the set if it exists.
430    fn value_for(&self, name: &str) -> Option<f64>;
431}
432
433impl<K> VariableSet for HashMap<K, f64>
434where
435    K: Borrow<str> + Eq + Hash,
436{
437    fn value_for(&self, name: &str) -> Option<f64> {
438        self.get(name).copied()
439    }
440}
441
442impl<K> VariableSet for BTreeMap<K, f64>
443where
444    K: Borrow<str> + Ord + Hash,
445{
446    fn value_for(&self, name: &str) -> Option<f64> {
447        self.get(name).copied()
448    }
449}
450
451/// Error categories that can be produced from [`Predicate::evaluate_state`]
452///
453/// This enum is marked as `non_exhaustive` so it is best practice to match against the `ErrorKind`
454/// variants you are expecting, and use `_` for all the rest.
455#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
456#[non_exhaustive]
457pub enum ErrorKind {
458    #[error("Missing variable")]
459    Missing,
460
461    #[error("NaN value for variable")]
462    NanValue,
463
464    #[error("NaN coefficient for variable")]
465    NanCoefficient,
466}
467
468/// The error type for evaluating of a state using a predicate.
469///
470/// The primary source of this error is the [`Predicate::evaluate_state`] method. This error can
471/// represent one of three following occurences:
472///   1. A variable has a coefficient but not a value in the variable set
473///   2. A variable has a value in the variable set that is NaN
474///   3. A variable has a coefficient that is NaN
475///
476/// Values of this type can be queried for its cause, as well as the variable name causing the
477/// error. 
478#[derive(Debug, Clone, Error, PartialEq, Eq)]
479#[error("Error evaluating predicate: {kind} \"{name}\"")]
480pub struct EvaluationError {
481    kind: ErrorKind,
482    name: String,
483}
484
485impl EvaluationError {
486    /// Create an error for a missing variable
487    ///
488    /// This method creates a clone of the name argument.
489    ///
490    /// # Example
491    ///
492    /// ```rust
493    /// # use banquo::predicate::EvaluationError;
494    /// let err = EvaluationError::missing("foo");
495    /// ```
496    pub fn missing(name: &str) -> Self {
497        Self {
498            name: name.to_owned(),
499            kind: ErrorKind::Missing,
500        }
501    }
502
503    /// Create an error for a variable with a NaN value
504    ///
505    /// This method creates a clone of the name argument.
506    ///
507    /// # Example
508    ///
509    /// ```rust
510    /// # use banquo::predicate::EvaluationError;
511    /// let err = EvaluationError::nan_value("foo");
512    /// ```
513    pub fn nan_value(name: &str) -> Self {
514        Self {
515            name: name.to_owned(),
516            kind: ErrorKind::NanValue,
517        }
518    }
519
520    /// Create an error for a variable with a NaN coefficient
521    ///
522    /// This method creates a clone of the name argument.
523    ///
524    /// # Example
525    ///
526    /// ```rust
527    /// # use banquo::predicate::EvaluationError;
528    /// let err = EvaluationError::nan_coefficient("foo");
529    /// ```
530    pub fn nan_coefficient(name: &str) -> Self {
531        Self {
532            name: name.to_owned(),
533            kind: ErrorKind::NanCoefficient,
534        }
535    }
536
537    /// Return the name of the variable that produced the error
538    pub fn name(&self) -> &str {
539        &self.name
540    }
541
542    /// Return the [`ErrorKind`] for this error
543    pub fn kind(&self) -> ErrorKind {
544        self.kind
545    }
546}
547
548impl Predicate {
549    /// Evaluate a system state into a robustness value.
550    ///
551    /// The successful output of this function is a `f64` value representing the distance of the
552    /// state from making the inequality represented by the `Predicate` false. A positive output
553    /// value indicates that the inequality was not violated, while a negative value indicates the
554    /// inequality was violated. The unsuccessful output of this function is a [`PredicateError`]
555    /// which indicates the nature of the evaluation error.
556    ///
557    /// Any value that implements the [`VariableSet`] trait can be evaluated with this method.
558    ///
559    /// # Examples
560    ///
561    /// ```rust
562    /// use std::collections::HashMap;
563    ///
564    /// use banquo::predicate::{Predicate, Term};
565    ///
566    /// let state = HashMap::from([
567    ///      ("x", 1.0), ("y", 3.0), ("z", f64::NAN),
568    /// ]);
569    ///
570    /// // BTreeMap also implements the VariableSet trait
571    /// // let state = BTreeMap::from([
572    /// //     ("x", 1.0), ("y", 3.0), ("z", f64::NAN),
573    /// // ]);
574    ///
575    /// let p = Predicate::from([
576    ///     Term::from(("x", 1.0)), Term::from(("y", 2.0)), Term::from(10.0)
577    /// ]);
578    ///
579    /// p.evaluate_state(&state);  // Ok -> 3.0
580    ///
581    /// let p = Predicate::from([
582    ///     Term::from(("x", 1.0)), Term::from(("a", 2.0)), Term::from(10.0)
583    /// ]);
584    ///
585    /// p.evaluate_state(&state);  // Error -> Missing variable "a"
586    ///
587    /// let p = Predicate::from([
588    ///     Term::from(("x", 1.0)), Term::from(("z", 2.0)), Term::from(10.0)
589    /// ]);
590    ///
591    /// p.evaluate_state(&state);  // Error -> Variable "z" has NaN value
592    ///
593    /// let p = Predicate::from([
594    ///     Term::from(("x", 1.0)), Term::from(("y", f64::NAN)), Term::from(10.0)
595    /// ]);
596    ///
597    /// p.evaluate_state(&state);  // Error -> Variable "y" has NaN coefficient
598    /// ```
599    pub fn evaluate_state<State>(&self, state: &State) -> Result<f64, EvaluationError>
600    where
601        State: VariableSet,
602    {
603        let mut sum = 0.0;
604
605        for (name, coeff) in &self.coefficients {
606            if coeff.is_nan() {
607                return Err(EvaluationError::nan_coefficient(name));
608            }
609
610            let value = state
611                .value_for(name.as_str())
612                .ok_or_else(|| EvaluationError::missing(name))?;
613
614            if value.is_nan() {
615                return Err(EvaluationError::nan_value(name));
616            }
617
618            sum += coeff * value;
619        }
620
621        Ok(self.constant - sum)
622    }
623}
624
625/// The error type when evaluating a state trace using a [`Predicate`].
626#[derive(Debug, Clone, Error)]
627#[error("At time {time} encountered error: {error}")]
628pub struct FormulaError {
629    time: f64,
630
631    #[source]
632    error: EvaluationError,
633}
634
635impl FormulaError {
636    /// Create a new evaluation error for a given time.
637    ///
638    /// # Example
639    ///
640    /// ```rust
641    /// use banquo::predicate::{EvaluationError, FormulaError};
642    ///
643    /// let err = FormulaError::new(1.0, EvaluationError::missing("x"));
644    /// ```
645    pub fn new(time: f64, error: EvaluationError) -> Self {
646        Self { time, error }
647    }
648
649    /// Returns the time of the state that produced the [`EvaluationError`].
650    pub fn time(&self) -> f64 {
651        self.time
652    }
653
654    /// Returns a reference to the [`EvaluationError`] that was generated.
655    pub fn error(&self) -> &EvaluationError {
656        &self.error
657    }
658}
659
660/// Evaluate a [`Trace`] of state values by evaluating each state individually.
661impl<State> Formula<State> for Predicate
662where
663    State: VariableSet,
664{
665    type Metric = f64;
666    type Error = FormulaError;
667
668    fn evaluate(&self, trace: &Trace<State>) -> Result<Trace<Self::Metric>, Self::Error> {
669        trace
670            .iter()
671            .map(|(time, state)| {
672                self.evaluate_state(state)
673                    .map(|rho| (time, rho))
674                    .map_err(|error| FormulaError{ time, error })
675            })
676            .collect()
677    }
678}
679
680/// Create a [`Predicate`] from a given algebraic expression.
681///
682/// This macro parses an algebraic expression into a predicate from right to left. Terms can be
683/// specified as either a `constant`, `variable`, `coefficient * variable`, or
684/// `variable * coefficient`. Malformed expressions will result in a compile-time error. An empty
685/// expression will also result in compile-time error. 
686///
687/// # Example
688///
689/// ```rust
690/// banquo::predicate!{ 1.0 * x + y + -2.0 * z <= 10.0 + a };
691/// ```
692#[macro_export]
693macro_rules! predicate {
694    (@rhs $(+)? $coeff:literal * $name:ident $($rest:tt)*) => {
695        {
696            let mut pred = $crate::predicate!(@rhs $($rest)*);
697            pred += (stringify!($name), $coeff);
698            pred
699        }
700    };
701    (@rhs $(+)? $name:ident * $coeff:literal $($rest:tt)*) => {
702        {
703            let mut pred = $crate::predicate!(@rhs $($rest)*);
704            pred += (stringify!($name), $coeff);
705            pred
706        }
707    };
708    (@rhs $(+)? $name:ident $($rest:tt)*) => {
709        {
710            let mut pred = $crate::predicate!(@rhs $($rest)*);
711            pred += (stringify!($name), 1.0);
712            pred
713        }
714    };
715    (@rhs $(+)? $const:literal $($rest:tt)*) => {
716        {
717            let mut pred = $crate::predicate!(@rhs $($rest)*);
718            pred -= $const;
719            pred
720        }
721    };
722    (@rhs) => {
723        $crate::predicate::Predicate::new()
724    };
725    ($(+)? $coeff:literal * $name:ident $($rest:tt)*) => {
726        {
727            let mut pred = $crate::predicate!($($rest)*);
728            pred += (stringify!($name), $coeff);
729            pred
730        }
731    };
732    ($(+)? $name:ident * $coeff:literal $($rest:tt)*) => {
733        {
734            let mut pred = $crate::predicate!($($rest)*);
735            pred += (stringify!($name), $coeff);
736            pred
737        }
738    };
739    ($(+)? $name:ident $($rest:tt)*) => {
740        {
741            let mut pred = $crate::predicate!($($rest)*);
742            pred += (stringify!($name), 1.0);
743            pred
744        }
745    };
746    ($(+)? $const:literal $($rest:tt)*) => {
747        {
748            let mut pred = $crate::predicate!($($rest)*);
749            pred -= $const;
750            pred
751        }
752    };
753    (<= $($rest:tt)+) => {
754        {
755            let pred = $crate::predicate!(@rhs $($rest)*);
756            std::ops::Neg::neg(pred)
757        }
758    };
759    (<=) => {
760        compile_error!("Missing right hand side of predicate")
761    };
762    () => {
763        compile_error!("Missing inequality (<=) sign in predicate")
764    };
765}
766
767#[cfg(test)]
768mod tests {
769    use std::collections::{BTreeMap, HashMap};
770
771    use crate::predicate;
772    use super::{Predicate, EvaluationError};
773
774    #[test]
775    fn macro_parsing() {
776        // Equivalent to 3.0 * x - 4.1 * y - 1.0 * z <= 2.2
777        let p = predicate!{ x * 3.0 + 1.0 <= y * 4.1 + 3.2 + z };
778
779        assert_eq!(p.get("x"), Some(3.0));
780        assert_eq!(p.get("y"), Some(-4.1));
781        assert_eq!(p.get("z"), Some(-1.0));
782        assert_eq!(p.constant(), 2.2);
783    }
784
785    #[test]
786    fn robustness() {
787        // 1.0 * x + 2.0 * y <= 10
788        let mut p = Predicate::new();
789        p += ("x", 1.0);
790        p += ("y", 2.0);
791        p += 10.0;
792
793        let hash_map = HashMap::from([("x", 2.0), ("y", 1.0)]);
794        let btree = BTreeMap::from([("x", 5.0), ("y", 5.0)]);
795        let missing = HashMap::from([("y", 2.0)]);
796        let nan_value = HashMap::from([("x", 2.0), ("y", f64::NAN)]);
797
798        assert_eq!(p.evaluate_state(&hash_map), Ok(6.0));
799        assert_eq!(p.evaluate_state(&btree), Ok(-5.0));
800        assert_eq!(p.evaluate_state(&missing), Err(EvaluationError::missing("x")));
801        assert_eq!(p.evaluate_state(&nan_value), Err(EvaluationError::nan_value("y")));
802
803        p += ("z", f64::NAN);
804
805        assert_eq!(p.evaluate_state(&hash_map), Err(EvaluationError::nan_coefficient("z")));
806    }
807}