hecate-symrs 0.2.4

A pure rust symbolic computation library! Inspired by sympy.
Documentation
use indexmap::IndexMap;
use itertools::Itertools;

use super::*;
#[derive(Clone)]
pub struct Diff {
    pub f: Box<dyn Expr>,
    pub vars: IndexMap<Symbol, usize>,
}

pub trait IntoVarOrder {
    fn into_var_order(self) -> (Symbol, usize);
}

impl Diff {
    pub fn new<'a, It: IntoIterator<Item = &'a Box<dyn Expr>>>(
        f: &Box<dyn Expr>,
        vars: It,
    ) -> Box<dyn Expr> {
        Box::new(Diff::new_move(
            f.clone(),
            vars.into_iter().map(|v| v.as_symbol().unwrap()),
        ))
    }

    pub fn new_v2(f: Box<dyn Expr>, vars: IndexMap<Symbol, usize>) -> Diff {
        Diff { f, vars }
    }

    pub fn idiff(f: Box<dyn Expr>, var: Symbol, order: usize) -> Self {
        let mut vars = IndexMap::new();
        vars.insert(var, order);
        Self { f, vars }
    }

    pub fn new_move<I, T>(f: Box<dyn Expr>, vars: I) -> Diff
    where
        I: IntoIterator<Item = T>,
        T: IntoVarOrder,
    {
        let mut vars_orders = IndexMap::new();
        for var in vars {
            let (var, order) = var.into_var_order();
            let entry = vars_orders.entry(var).or_insert(0);
            *entry += order;
        }
        Diff {
            f,
            vars: vars_orders,
        }
    }
}

impl IntoVarOrder for (Symbol, usize) {
    fn into_var_order(self) -> (Symbol, usize) {
        self
    }
}

impl IntoVarOrder for Symbol {
    fn into_var_order(self) -> (Symbol, usize) {
        (self, 1)
    }
}

impl IntoVarOrder for &str {
    fn into_var_order(self) -> (Symbol, usize) {
        (Symbol::new(self), 1)
    }
}
impl IntoVarOrder for char {
    fn into_var_order(self) -> (Symbol, usize) {
        (Symbol::new(&self.to_string()), 1)
    }
}

impl<T: ToString> IntoVarOrder for &(T, usize) {
    fn into_var_order(self) -> (Symbol, usize) {
        (Symbol::new(&self.0.to_string()), self.1)
    }
}

impl Expr for Diff {
    fn get_ref<'a>(&'a self) -> &'a dyn Expr {
        self as &dyn Expr
    }
    fn for_each_arg(&self, f: &mut dyn FnMut(&dyn Arg) -> ()) {
        f(&*self.f);
        f(&self
            .vars
            .iter()
            .map(|(var, order)| (var.clone(), *order))
            .collect::<Vec<(Symbol, usize)>>());
    }

    fn known_expr(&self) -> KnownExpr {
        KnownExpr::Diff(self)
    }

    fn from_args(&self, args: Vec<Box<dyn Arg>>) -> Box<dyn Expr> {
        let vars = &*args[1];
        let vars = vars as &dyn Any;
        let vars = vars.downcast_ref::<Vec<(Symbol, usize)>>().unwrap();
        Box::new(Diff::new_v2(
            args[0].clone().into(),
            IndexMap::from_iter(vars.clone()),
        ))
    }

    fn clone_box(&self) -> Box<dyn Expr> {
        Box::new(self.clone())
    }

    fn str(&self) -> String {
        let order = self.vars.values().sum::<usize>();
        let exponent = if order > 1 {
            format!("^{}", order)
        } else {
            "".to_string()
        };
        let mut f = self.f.str();
        if f.len() > 1 {
            f = format!("({})", f);
        }

        let denom = self
            .vars
            .iter()
            .map(|(var, order)| {
                if *order == 1 {
                    format!("{var}")
                } else {
                    format!("{}^{}", var.str(), order)
                }
            })
            .join(".");

        format!("{}{f} / {denom}", exponent,)
    }
}

#[cfg(test)]
mod tests {
    use crate::symbols;

    use super::*;

    #[test]
    fn test_str() {
        let [u] = symbols!("u");
        let t = Symbol::new("t");
        let expr = Diff::new_move(u.ipow(2), vec![t; 2]);
        assert_eq!(expr.str(), "∂^2(u^2) / ∂t^2")
    }

    #[test]
    fn test_str_first_order() {
        let [u] = symbols!("u");
        let t = Symbol::new("t");
        let expr = Diff::new_move(u.ipow(2), vec![t; 1]);
        assert_eq!(expr.str(), "∂(u^2) / ∂t")
    }

    #[test]
    fn test_str_symbol_first_order() {
        let [u] = symbols!("u");
        let t = Symbol::new("t");
        let expr = Diff::new_move(u.clone_box(), vec![t; 1]);
        assert_eq!(expr.str(), "∂u / ∂t")
    }

    #[test]
    fn test_args() {
        let [u] = symbols!("u");
        let t = Symbol::new("t");
        let expr = Diff::new_move(u.ipow(2), vec![t; 2]);

        let args = expr.args();
        assert_eq!(expr.from_args(args).srepr(), expr.srepr());
    }
}