simple_shunting/
rpneval.rs

1use crate::parser::RPNExpr;
2use lexers::MathToken;
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::cell::RefCell;
6
7
8pub trait RandomVariable {
9    fn eval(&self) -> f64;
10}
11
12#[derive(Clone)]
13pub enum MathOp {
14    Number(f64),
15    Dynamic(Rc<dyn Fn() -> Result<f64, String>>),
16}
17
18impl RandomVariable for MathOp {
19    fn eval(&self) -> f64 {
20        match self {
21            MathOp::Number(n) => *n,
22            MathOp::Dynamic(f) => f().unwrap(),
23        }
24    }
25}
26
27#[derive(Debug)]
28pub struct Histogram<const BUCKETS: usize> {
29    pub buckets: [u32; BUCKETS],
30    pub min: f64,
31    pub max: f64,
32}
33
34impl MathOp {
35    pub fn histogram<const BUCKETS: usize>(&self, samples: usize) -> Histogram<BUCKETS> {
36        // collect samples from random variable
37        let data: Vec<_> = (0..samples).map(|_| self.eval()).collect();
38        // extract info from data to build histogram
39        let (min, max) = data.iter().fold((f64::MAX, f64::MIN), |(min, max), &x| {
40            (min.min(x), max.max(x))
41        });
42        let bucket_size = (max + 1.0e-10 - min) / BUCKETS as f64;
43        // map samples to histogram buckets
44        let mut histogram = Histogram{buckets: [0; BUCKETS], min, max};
45        for bucket in data.into_iter().map(|x| (x - min) / bucket_size) {
46            histogram.buckets[bucket as usize] += 1;
47        }
48        histogram
49    }
50}
51
52pub struct MathContext(Rc<RefCell<HashMap<String, MathOp>>>);
53
54impl MathContext {
55    pub fn new() -> MathContext {
56        use std::f64::consts;
57        let mut cx = HashMap::new();
58        cx.insert("pi".to_string(), MathOp::Number(consts::PI));
59        cx.insert("e".to_string(), MathOp::Number(consts::E));
60        MathContext(Rc::new(RefCell::new(cx)))
61    }
62
63    pub fn setvar(&self, name: &str, value: MathOp) {
64        self.0.borrow_mut().insert(name.to_string(), value);
65    }
66
67    pub fn eval(&self, rpn: &RPNExpr) -> Result<f64, String> {
68        let mut operands = Vec::new();
69
70        for token in &rpn.0 {
71            match token {
72                MathToken::Number(num) => operands.push(*num),
73                MathToken::Variable(ref v) => operands.push(
74                    match self.0.borrow().get(v) {
75                        Some(mathop) => mathop.eval(),
76                        None => return Err(format!("Unknown Variable: {}", v)),
77                    }
78                ),
79                MathToken::BOp(op) => {
80                    let rhs = operands.pop().ok_or("Missing operands")?;
81                    let lhs = operands.pop().ok_or("Missing operands")?;
82                    operands.push(match &op[..] {
83                        "+" => lhs + rhs,
84                        "-" => lhs - rhs,
85                        "*" => lhs * rhs,
86                        "/" => lhs / rhs,
87                        "%" => lhs % rhs,
88                        "^" | "**" => lhs.powf(rhs),
89                        _ => return Err(format!("Unknown BOp: {}", op)),
90                    });
91                }
92                MathToken::UOp(op) => {
93                    let arg = operands.pop().ok_or("Missing operands")?;
94                    operands.push(match &op[..] {
95                        "-" => -arg,
96                        "!" => libm::tgamma(arg + 1.0),
97                        _ => return Err(format!("Unknown UOp: {}", op)),
98                    });
99                }
100                MathToken::Function(fname, arity) => {
101                    if *arity > operands.len() {
102                        return Err(format!("Missing args for function {}", fname));
103                    }
104                    let args: Vec<_> = operands.split_off(operands.len() - arity);
105                    operands.push(
106                        eval_fn(fname, &args)?
107                    );
108                }
109                _ => return Err(format!("Unexpected token for RPN eval: {:?}", token)),
110            }
111        }
112        operands.pop().ok_or(format!("Failed to eval RPN: {:?}", rpn))
113    }
114
115    pub fn compile(&self, rpn: &RPNExpr) -> Result<MathOp, String> {
116        let mut stack = Vec::new();
117        for token in &rpn.0 {
118            match token {
119                MathToken::Number(n) => stack.push(MathOp::Number(*n)),
120                MathToken::Variable(v) => stack.push(
121                    self.0.borrow().get(v).ok_or(format!("Unknown variable: {}", v))?.clone()),
122                MathToken::BOp(op) => {
123                    let rhs = stack.pop().ok_or(format!("Missing operands for {}", op))?;
124                    let lhs = stack.pop().ok_or(format!("Missing operands for {}", op))?;
125                    let dynamic = !(
126                        matches!(rhs, MathOp::Number(_)) && matches!(lhs, MathOp::Number(_)));
127                    let op = op.clone();
128                    let eval = move || {
129                        Ok(match op.as_str() {
130                            "+" => lhs.eval() + rhs.eval(),
131                            "-" => lhs.eval() - rhs.eval(),
132                            "*" => lhs.eval() * rhs.eval(),
133                            "/" => lhs.eval() / rhs.eval(),
134                            "%" => lhs.eval() % rhs.eval(),
135                            "^" | "**" => lhs.eval().powf(rhs.eval()),
136                            _ => return Err(format!("Unknown BOp: {}", op)),
137                        })
138                    };
139                    stack.push(if dynamic {
140                        MathOp::Dynamic(Rc::new(eval))
141                    } else {
142                        MathOp::Number(eval()?)
143                    });
144                }
145                MathToken::UOp(op) => {
146                    let arg = stack.pop().ok_or(format!("Missing operands for {}", op))?;
147                    let dynamic = !matches!(arg, MathOp::Number(_));
148                    let op = op.clone();
149                    let eval = move || {
150                        Ok(match op.as_str() {
151                            "-" => -arg.eval(),
152                            "!" => libm::tgamma(arg.eval() + 1.0),
153                            _ => return Err(format!("Unknown UOp: {}", op)),
154                        })
155                    };
156                    stack.push(if dynamic {
157                        MathOp::Dynamic(Rc::new(eval))
158                    } else {
159                        MathOp::Number(eval()?)
160                    });
161                }
162                MathToken::Function(fname, arity) => {
163                    if *arity > stack.len() {
164                        return Err(format!("Missing args for {}", fname));
165                    }
166                    let args: Vec<_> = stack.split_off(stack.len() - arity);
167                    let dynamic = !args.iter().all(|arg| matches!(arg, MathOp::Number(_)));
168                    let fname = fname.clone();
169                    let eval = move || -> Result<MathOp, String> {
170                        let args: Vec<_> = args.iter().map(|v| v.eval()).collect();
171                        Ok(
172                            MathOp::Number(eval_fn(&fname, &args)?)
173                        )
174                    };
175                    stack.push(if dynamic {
176                        MathOp::Dynamic(Rc::new(move || eval().map(|v| v.eval())))
177                    } else {
178                        eval()?
179                    });
180                }
181                _ => return Err(format!("Unexpected token for RPN compile: {:?}", token)),
182            }
183        }
184        assert_eq!(stack.len(), 1);
185        Ok(stack.pop().ok_or("Failed to compile RPNExpr")?)
186    }
187}
188
189fn eval_fn(fname: &str, args: &[f64]) -> Result<f64, String> {
190    Ok(match fname {
191        "abs" if args.len() == 1 => args[0].abs(),
192        "atan2" if args.len() == 2 => args[0].atan2(args[1]),
193        "cos" if args.len() == 1 => args[0].cos(),
194        "log" if args.len() == 1 => args[0].log10(),
195        "max" if !args.is_empty() => args.iter().fold(args[0], |a, &b| a.max(b)),
196        "min" if !args.is_empty() => args.iter().fold(args[0], |a, &b| a.min(b)),
197        // Order not important
198        "nCr" if args.len() == 2 => funcs::combinations(args[0], args[1])?,
199        "nMCr" if args.len() == 2 => funcs::multicombinations(args[0], args[1])?,
200        // Order is important
201        "nMPr" if args.len() == 2 => args[0].powf(args[1]),
202        "nPr" if args.len() == 2 => funcs::permutations(args[0], args[1])?,
203        "sin" if args.len() == 1 => args[0].sin(),
204        _ => return Err(format!("Unknown Function: {} with {} args", fname, args.len()))
205    })
206}
207
208mod funcs {
209    pub fn combinations(n: f64, r: f64) -> Result<f64, String> {
210        use libm::tgamma;
211        Ok(tgamma(n + 1.0) / tgamma(r + 1.0) / tgamma(n - r + 1.0))
212    }
213
214    pub fn multicombinations(n: f64, r: f64) -> Result<f64, String> {
215        use libm::tgamma;
216        Ok(tgamma(n + r) / tgamma(r + 1.0) / tgamma(n))
217    }
218
219    pub fn permutations(n: f64, r: f64) -> Result<f64, String> {
220        use libm::tgamma;
221        Ok(tgamma(n + 1.0) / tgamma(n - r + 1.0))
222    }
223}