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 let data: Vec<_> = (0..samples).map(|_| self.eval()).collect();
38 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 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 "nCr" if args.len() == 2 => funcs::combinations(args[0], args[1])?,
199 "nMCr" if args.len() == 2 => funcs::multicombinations(args[0], args[1])?,
200 "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}