resistor_calc/
expr_builder.rs

1extern crate meval;
2
3use std::{f64::EPSILON, str::FromStr};
4
5use RSet;
6
7lazy_static!(
8    static ref RNAMES: Vec<String> = (1..=100).map(|i| format!("R{}", i)).collect();
9);
10
11enum Bounds {
12    Cmp(Box<Fn(f64, f64) -> bool>, meval::Expr, f64),
13    Err(meval::Expr, f64),
14}
15
16fn split_expr(expr: &str, pat: &str) -> (meval::Expr, f64) {
17    let mut split = expr.split(pat);
18    (
19        split.next().unwrap().trim().parse::<meval::Expr>().unwrap(),
20        split.next().unwrap().trim().parse::<f64>().unwrap(),
21    )
22}
23
24impl FromStr for Bounds {
25    type Err = &'static str;
26
27    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
28        if s.contains("<=") {
29            let (ex, trg) = split_expr(s, "<=");
30            Ok(Bounds::Cmp(Box::new(|a, b| a <= b), ex, trg))
31        } else if s.contains('<') {
32            let (ex, trg) = split_expr(s, "<");
33            Ok(Bounds::Cmp(Box::new(|a, b| a < b), ex, trg))
34        } else if s.contains(">=") {
35            let (ex, trg) = split_expr(s, ">=");
36            Ok(Bounds::Cmp(Box::new(|a, b| a >= b), ex, trg))
37        } else if s.contains('>') {
38            let (ex, trg) = split_expr(s, ">");
39            Ok(Bounds::Cmp(Box::new(|a, b| a > b), ex, trg))
40        } else if s.contains("==") {
41            let (ex, trg) = split_expr(s, "==");
42            Ok(Bounds::Cmp(
43                Box::new(|a, b| (a - b).abs() < EPSILON),
44                ex,
45                trg,
46            ))
47        } else if s.contains("!=") {
48            let (ex, trg) = split_expr(s, "!=");
49            Ok(Bounds::Cmp(
50                Box::new(|a, b| (a - b).abs() > EPSILON),
51                ex,
52                trg,
53            ))
54        } else if s.contains('~') {
55            let (ex, trg) = split_expr(s, "~");
56            Ok(Bounds::Err(ex, trg))
57        } else {
58            Err("Err: Bound must contain either <, <=, >, >=, ==, != or ~")
59        }
60    }
61}
62
63/// Builder struct used to create `f` values for `RCalc::calc` from mathematical expressions.
64#[derive(Default)]
65pub struct ROpBuilder {
66    ops: Vec<Bounds>,
67}
68
69impl ROpBuilder {
70    /// Init a new builder.
71    pub fn new() -> Self {
72        ROpBuilder { ops: Vec::new() }
73    }
74
75    /// Add a new bound to the builder, this must be an expression of the form `expr op target`
76    /// where expr is a math expression using R1,...,Rn and [supported expressions](https://docs.rs/meval/#supported-expressions),
77    /// op is one of <, >, <=, >=, ==, != or ~ and target is an [f64 value](https://doc.rust-lang.org/std/primitive.f64.html#impl-FromStr).
78    /// For ~ the bound will calculate the difference between the value of expr and target and add
79    /// the abs error to the resulting error. For all other ops the bound will compare the value of
80    /// expr to target, and if the comparison fails, it will reject the set of proposed values.
81    pub fn bound(mut self, expr: &str) -> Self {
82        self.ops.push(expr.parse().unwrap());
83        self
84    }
85
86    fn cmp_bound_fn(&mut self) -> Box<Fn(&meval::Context) -> Option<f64>> {
87        match self.ops.pop() {
88            Some(b) => match b {
89                Bounds::Cmp(op, expr, target) => {
90                    let inner_bound = self.cmp_bound_fn();
91                    Box::new(move |ctx| {
92                        if op(expr.eval_with_context(ctx).unwrap(), target) {
93                            inner_bound(ctx)
94                        } else {
95                            None
96                        }
97                    })
98                }
99                Bounds::Err(expr, target) => {
100                    let inner_bound = self.cmp_bound_fn();
101                    Box::new(move |ctx| {
102                        let val = expr.eval_with_context(ctx).unwrap();
103                        inner_bound(ctx).map(|v| v + (target - val).abs())
104                    })
105                }
106            },
107            None => Box::new(|_| Some(0.0)),
108        }
109    }
110
111    /// Finishes the building and converts the struct into a function suitable to be passed to calc
112    pub fn finish(mut self) -> impl Fn(&RSet) -> Option<f64> {
113        let bound = self.cmp_bound_fn();
114        move |rs: &RSet| {
115            let mut ctx = meval::Context::new();
116            for (i, v) in rs.0.iter().enumerate() {
117                ctx.var(RNAMES[i].clone(), *v as f64);
118            }
119            bound(&ctx)
120        }
121    }
122}