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}