nexsys_math/
mvcalc.rs

1use super::{NxN, Variable};
2use meval::{Context, eval_str_with_context};
3use std::{collections::HashMap, ops::{Add, Sub, Div}, fmt::Display, hash::Hash};
4
5/// Takes a mathematical expression given as a string and returns a function.
6pub fn functionify<S>(text: S) -> impl Fn(&HashMap<S, Variable>) -> f64
7where
8    S: Copy + AsRef<str> + Display + Into<String>
9{
10    let func = move |v:&HashMap<S, Variable>| -> f64 {
11        let mut ctx = Context::new();
12        
13        for k in v {
14            ctx.var(*k.0, k.1.as_f64());
15        }
16
17        eval_str_with_context(text, ctx).expect(&format!("failed to evaluate expression: {}", text))
18    };
19    func
20}
21
22/// Returns the derivative of a function at a point.
23pub fn d_dx<T>(mut func: impl FnMut(T) -> T, x: T) -> T 
24where
25    T: Copy + Add<T, Output = T> + Add<f64, Output = T> + Sub<T, Output = T> + Div<f64, Output = T>
26{
27    let dx = 1e-7;
28    ( func(x + dx) - func(x) ) / dx
29}
30
31/// Returns the partial derivative of a function w.r.t. the `target` variable.
32/// # Example
33/// ```
34/// use nexsys_math::partial_d_dx;
35/// use nexsys_math::Variable;
36/// use std::collections::HashMap;
37/// let expr = "x^2 + y - z";
38/// 
39/// let X = HashMap::from([
40///     ("x", Variable::new(1_f64, None)),
41///     ("y", Variable::new(1_f64, None)),
42///     ("z", Variable::new(1_f64, None))
43/// ]);
44/// 
45/// let dFdx = partial_d_dx(expr, &X, "x");
46/// assert_eq!(dFdx.round(), 2_f64);
47/// ```
48pub fn partial_d_dx<S>(expr: S, guess: &HashMap<S, Variable>, target: S) -> f64 
49where 
50    S: Copy + AsRef<str> + Display + Into<String> + Eq + Hash
51{
52    // copy the guess vector
53    let mut temp = guess.clone();
54
55    // create an actual function from the given expression
56    let func = functionify(expr);
57
58    // create a partial function of the target variable
59    let partial = move |x:f64| -> f64 {
60        if let Some(v) = temp.get_mut(&target) {
61            v.change(x);
62        }
63        func(&temp)
64    };
65
66    // take the derivative of the partial function
67    d_dx(partial, guess[&target].as_f64())
68}
69
70/// Returns a tuple of `Vec`s that contain the keys and values of the original HashMap. 
71/// The index of the key will be the same as its corresponding value's index.
72/// 
73/// This function only exists for use in `pub fn jacobian()`.
74fn split_hm<K, V>(hm: HashMap<K, V>) -> (Vec<K>, Vec<V>) {
75    let mut keys = Vec::new();
76    let mut vals = Vec::new();
77
78    for i in hm {
79        keys.push(i.0);
80        vals.push(i.1);
81    }
82
83    (keys, vals)
84}
85
86/// Returns the (numerical) `NxN` Jacobian matrix of a given system of equations at the vector given by `guess`.
87/// 
88/// Note that the resulting matrix's columns will be in a random order, so extra care is needed to identify which
89/// variable occupies which column by checking the ordering of `self.vars`.
90/// # Example
91/// ```
92/// use nexsys_math::jacobian;
93/// use nexsys_math::Variable;
94/// use std::collections::HashMap;
95/// 
96/// let my_sys = vec![
97///     "x^2 + y",
98///     "y   - x"
99/// ];
100/// let guess = HashMap::from([
101///     ("x", Variable::new(1.0, None)),
102///     ("y", Variable::new(1.0, None))
103/// ]);
104/// 
105/// let j = jacobian(&my_sys, &guess);
106/// 
107/// // j.to_vec() will return roughly:
108/// // vec![
109/// //      vec![2.0, -1.0],
110/// //      vec![1.0, 1.0]
111/// // ];
112/// ```
113pub fn jacobian(system: &Vec<&str>, guess: &HashMap<&str, Variable>) -> Result<NxN, &'static str> {
114    if system.len() != guess.keys().len() { 
115        panic!("ERR: System is not properly constrained!") // guard clause against invalid problems
116    } 
117
118    let size = system.len();
119    let mut mat = Vec::new();
120    let vec = split_hm(guess.clone());
121
122    for c in 0..size {
123        let col = Vec::from_iter(
124            system.iter().map(
125                |&i| partial_d_dx(i, guess, vec.0[c])
126            )
127        );
128        mat.push(col);
129    };
130
131    NxN::from_cols( mat, Some(vec.0) )
132}