use crate::symbolic::symbolic_engine::Expr;
use enum_dispatch::enum_dispatch;
use std::any::Any;
use std::collections::HashMap;
use std::f64;
#[enum_dispatch]
pub trait SymbolicType: Any {
fn as_any(&self) -> &dyn Any;
fn as_symbolic_type(&self) -> Box<dyn SymbolicType>;
fn var_expr(&self, var: &str) -> Box<dyn SymbolicType>;
fn const_expr(&self, val: f64) -> Box<dyn SymbolicType>;
fn exp(&self) -> Box<dyn SymbolicType>;
fn ln(&self) -> Box<dyn SymbolicType>;
fn pow(&self, rhs: Box<dyn SymbolicType>) -> Box<dyn SymbolicType>;
fn is_zero(&self) -> bool;
fn indexed_matrix(
self,
num_vars: usize,
values: Vec<String>,
) -> (Vec<Vec<Box<dyn SymbolicType>>>, Vec<Vec<String>>);
fn rename_variables(&self, variables: &HashMap<String, String>) -> Box<dyn SymbolicType>;
fn set_variable(&self, variable: &str, value: f64) -> Box<dyn SymbolicType>;
fn set_variable_from_map(&self, variables: &HashMap<String, f64>) -> Box<dyn SymbolicType>;
fn simplify(&self) -> Box<dyn SymbolicType>;
fn diff(&self, str_variable: &str) -> Box<dyn SymbolicType>;
fn lambdify(&self, variable_str: Vec<String>) -> Box<dyn Fn(&[f64]) -> f64 + Send + Sync>;
fn convert_to_string(&self) -> String;
fn to_native(&self) -> Expr;
fn clone_box(&self) -> Box<dyn SymbolicType>;
fn get_type(&self) -> String;
}
impl SymbolicType for Expr {
fn as_any(&self) -> &dyn Any {
self
}
fn as_symbolic_type(&self) -> Box<dyn SymbolicType> {
Box::new(self.clone())
}
fn var_expr(&self, var: &str) -> Box<dyn SymbolicType> {
Box::new(Expr::Var(var.to_string()))
}
fn const_expr(&self, val: f64) -> Box<dyn SymbolicType> {
Box::new(Expr::Const(val))
}
fn exp(&self) -> Box<dyn SymbolicType> {
let exp = Expr::Exp(self.clone().boxed());
Box::new(exp)
}
fn ln(&self) -> Box<dyn SymbolicType> {
let exp = Expr::Ln(self.clone().boxed());
Box::new(exp)
}
fn pow(&self, rhs: Box<dyn SymbolicType>) -> Box<dyn SymbolicType> {
let rhs = rhs.to_native();
let exp = Expr::Pow(self.clone().boxed(), Box::new(rhs));
Box::new(exp)
}
fn is_zero(&self) -> bool {
self.is_zero()
}
fn diff(&self, str_variable: &str) -> Box<dyn SymbolicType> {
let partial = Expr::diff(&self.clone(), str_variable);
Box::new(partial)
}
fn simplify(&self) -> Box<dyn SymbolicType> {
let simplified = self.simplify();
Box::new(simplified)
}
fn lambdify(&self, variable_str: Vec<String>) -> Box<dyn Fn(&[f64]) -> f64 + Send + Sync> {
let lambdified = Expr::lambdify_borrowed_thread_safe(
&self,
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
lambdified
}
fn rename_variables(&self, variables: &HashMap<String, String>) -> Box<dyn SymbolicType> {
let renamed = self.rename_variables(variables);
Box::new(renamed)
}
fn set_variable(&self, variable: &str, value: f64) -> Box<dyn SymbolicType> {
let res = self.set_variable(variable, value);
Box::new(res)
}
fn set_variable_from_map(&self, variables: &HashMap<String, f64>) -> Box<dyn SymbolicType> {
let res: Expr = self.set_variable_from_map(variables);
Box::new(res)
}
fn convert_to_string(&self) -> String {
let to_string = self.to_string();
to_string
}
fn indexed_matrix(
self,
num_vars: usize,
values: Vec<String>,
) -> (Vec<Vec<Box<dyn SymbolicType>>>, Vec<Vec<String>>) {
let (indexed_matrix, matrix_of_names) = Expr::IndexedVarsMatrix(num_vars, values.clone());
let indexed_matrix: Vec<Vec<Box<dyn SymbolicType>>> = indexed_matrix
.into_iter()
.map(|vec_x| {
vec_x
.into_iter()
.map(|x| Box::new(x) as Box<dyn SymbolicType>)
.collect()
})
.collect();
(indexed_matrix, matrix_of_names)
}
fn to_native(&self) -> Expr {
if let Some(native) = self.as_any().downcast_ref::<Expr>() {
native.to_owned()
} else {
panic!("Type mismatch: expected Expr, got {}", self.get_type());
}
}
fn clone_box(&self) -> Box<dyn SymbolicType> {
Box::new(self.clone())
}
fn get_type(&self) -> String {
"native".to_string()
}
}
pub fn SymbolicType_casting<T: SymbolicType>(
expr: T,
desired_type: String,
) -> Box<dyn SymbolicType> {
let res = if desired_type == "native" {
Box::new(expr) as Box<dyn SymbolicType>
} else {
panic!("Unknown constant type")
};
res
}
pub enum SymbolicEngineType {
Native,
}
pub trait SymbolicFactory: Send + Sync {
fn create_constant(&self, value: f64) -> Box<dyn SymbolicType>;
fn create_variable(&self, name: String) -> Box<dyn SymbolicType>;
fn create_variables(&self, name: String) -> Vec<Box<dyn SymbolicType>>;
fn parse_expression(&self, expr_str: &str) -> Box<dyn SymbolicType>;
}
pub struct NativeSymbolicFactory;
impl SymbolicFactory for NativeSymbolicFactory {
fn create_constant(&self, value: f64) -> Box<dyn SymbolicType> {
Box::new(Expr::Const(value))
}
fn create_variable(&self, name: String) -> Box<dyn SymbolicType> {
Box::new(Expr::Var(name))
}
fn create_variables(&self, vars: String) -> Vec<Box<dyn SymbolicType>> {
let vec_trimmed: Vec<String> = vars.split(',').map(|s| s.trim().to_string()).collect();
let mut vector_of_symbolic_vars: Vec<Box<dyn SymbolicType>> = Vec::new();
for var in vec_trimmed {
if var.is_empty() {
continue;
}
vector_of_symbolic_vars.push(Box::new(Expr::Var(var)));
}
vector_of_symbolic_vars
}
fn parse_expression(&self, expr_str: &str) -> Box<dyn SymbolicType> {
Box::new(Expr::parse_expression(expr_str))
}
}
pub fn get_symbolic_factory(engine_type: SymbolicEngineType) -> &'static dyn SymbolicFactory {
match engine_type {
SymbolicEngineType::Native => &NativeSymbolicFactory,
}
}
pub fn symbolic_backend_from_string(engine_type: String) -> &'static dyn SymbolicFactory {
match engine_type.as_str() {
"native" => &NativeSymbolicFactory,
_ => panic!("Unknown symbolic engine type"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diff_generic() {
let x = Expr::Var("x".to_string());
let f = Expr::Pow(Box::new(x.clone()), Box::new(Expr::Const(2.0)));
let f_cast = Box::new(f) as Box<dyn SymbolicType>;
let df_dx = &f_cast.diff("x");
let df_dx_native = df_dx.to_native().simplify();
let _degree = Box::new(Expr::Const(1.0));
let C = Expr::Const(2.0);
let expected_result = C.clone() * x;
println!("df_dx {:?} ", df_dx_native);
println!("expected_result {:?} ", expected_result);
assert_eq!(df_dx_native, expected_result);
}
#[test] fn test_get_symbolic_factory() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let constant = factory.create_constant(42.0);
let variable = factory.create_variable("x".to_string());
match constant.as_any().downcast_ref::<Expr>() {
Some(expr) => {
assert_eq!(*expr, Expr::Const(42.0));
}
_ => {
panic!("Expected Expr type for constant");
}
}
match variable.as_any().downcast_ref::<Expr>() {
Some(expr) => {
assert_eq!(*expr, Expr::Var("x".to_string()));
}
_ => {
panic!("Expected Expr type for variable");
}
}
}
#[test]
fn test_parse_expression() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let expr = factory.parse_expression("x + 1");
let result = expr.convert_to_string();
assert_eq!(result, "x + 1");
let expr = factory.parse_expression("x * exp(y)");
let result = expr.convert_to_string();
assert_eq!(result, "x * exp(y)");
}
#[test]
fn test_symbolic_operations() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let x = factory.create_variable("x".to_string());
let derivative = x.diff("x");
let result = derivative.convert_to_string();
assert_eq!(result, "1");
let expr = factory.parse_expression("x");
let evaluated = expr.set_variable("x", 2.0).simplify();
if let Some(native_expr) = evaluated.as_any().downcast_ref::<Expr>() {
println!("\n \n evaluated {:?} ", native_expr.to_string());
match native_expr {
Expr::Const(value) => assert_eq!(*value, 2.0),
_ => panic!("Expected constant value after evaluation"),
}
}
}
#[test]
#[should_panic(expected = "Invalid expression")]
fn test_invalid_expression_parsing() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
factory.parse_expression("@invalid@");
}
#[test]
fn test_variable_substitution() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let expr = factory.parse_expression("x + y");
let mut substitutions = HashMap::new();
substitutions.insert("x".to_string(), "a".to_string());
substitutions.insert("y".to_string(), "b".to_string());
let renamed = expr.rename_variables(&substitutions);
let result = renamed.convert_to_string();
assert_eq!(result, "a + b");
}
#[test]
fn test_numerical_evaluation() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let expr = factory.parse_expression("x^2");
let func = expr.lambdify(vec!["x".to_string()]);
assert_eq!(func(&[2.0]), 4.0);
assert_eq!(func(&[3.0]), 9.0);
assert_eq!(func(&[-2.0]), 4.0);
}
#[test]
fn test_expression_composition() {
let factory = get_symbolic_factory(SymbolicEngineType::Native);
let x = factory.create_variable("x".to_string());
let _y = factory.create_variable("y".to_string());
let product = x.clone_box(); let expr = factory.parse_expression(&format!("exp({})", product.convert_to_string()));
let result = expr.convert_to_string();
assert_eq!(result, "exp(x)");
}
}