1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/// A module for linear algebra-related code. Contains the `NxN` 
/// struct and a few functions for matrix/vector math. 
pub mod linalg;

/// A module for multivariate calculus-related code. Contains functions
/// relevant to finding (approximate) derivatives and jacobian matrices, 
/// as well as single-variable and multivariate rust implementations of 
/// Newton's Method.
pub mod mvcalc;

#[cfg(test)]
mod tests;

/// This module exposes parts of this crate in a Python-compatible fashion. While
/// not very useful for Rust development, this allows the crate to be `pip-installed` as a 
/// Python package for use elsewhere.
/// 
/// Documentation on how these work Python-wise can be found on the 
/// PyO3 user guide here: https://pyo3.rs/v0.8.1/print.html
#[cfg(feature = "python_ffi")]
pub mod pyffi;

use mvcalc::*;
use meval::{ Context, eval_str_with_context };
use std::{ collections::HashMap };

/// Returns a `HashMap` indicating where a given HashMap's keys will be placed in a Vec.
#[allow(dead_code)]
fn index_map<'a, V>(hm: &HashMap<&'a str, V>) -> HashMap<&'a str, usize> {
    let mut i: usize = 0;
    let mut res = HashMap::new();
    for k in hm.keys() {
        res.insert(*k, i);
        i += 1;
    }
    res
}

/// Returns a tuple of `Vec`s that contain the keys and values of the original HashMap. 
/// The index of the key will be the same as its corresponding value's index.
fn split_hm<K, V>(hm: HashMap<K, V>) -> (Vec<K>, Vec<V>) {
    let mut keys = Vec::new();
    let mut vals = Vec::new();

    for i in hm {
        keys.push(i.0);
        vals.push(i.1);
    }

    (keys, vals)
}

/// Reverses the operation performed by `split_hm`.
fn stitch_hm<K: std::hash::Hash + std::cmp::Eq, V>(mut keys: Vec<K>, mut vals: Vec<V>) -> HashMap<K, V> {
    let mut res = HashMap::new();
    for _ in 0..keys.len() {
        res.insert(
            keys.pop().unwrap(), 
            vals.pop().unwrap()
        );
    }
    res
}

/// Takes a mathematical expression given as a `&str` and returns a function that takes a `HashMap<&str, Variable>` and returns an `f64`.
fn functionify<'a>(text: &'a str) -> impl Fn(&HashMap<&str, Variable>) -> f64 + 'a {
    let func = move |v:&HashMap<&str, Variable>| -> f64 {
        let mut ctx = Context::new();
        
        for k in v {
            ctx.var(*k.0, k.1.as_f64());
        }

        eval_str_with_context(text, ctx)
            .expect(&format!("ERR: Failed to evaluate expression: {}", text))
    };
    func
}