-
Notifications
You must be signed in to change notification settings - Fork 11
[feat] Implement PolynomialTraits for Polynomial #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 17 commits
52fa102
4efb87f
628c0fa
8a8cae5
30da628
26c5e89
da0286b
ad89c5c
7d88344
be760d9
2ffc4b6
141c784
0d56b05
890a7e0
0ca191d
bc10e6c
11ee031
0955182
a12f0f2
3c06902
2fe6299
ef5ae3c
89ed944
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| use crate::polynomials::PolynomialError; | ||
| use crate::polynomials::structs::advanced::{Polynomial, TokenStream}; | ||
| use std::collections::HashMap; | ||
| use std::collections::{BTreeSet, HashMap}; | ||
| use std::f64; | ||
| use std::str::FromStr; | ||
| use std::sync::LazyLock; | ||
|
|
||
|
|
@@ -106,7 +107,7 @@ macro_rules! token_from_char { | |
|
|
||
| // declaring `Operators` with `token_from_char` | ||
| token_from_char! { | ||
| #[derive(Debug, PartialEq, Hash, Eq,Copy,Clone)] | ||
| #[derive(Debug, PartialEq,Eq,Copy,Clone,Hash)] | ||
| pub Operators { | ||
| Add => '+', | ||
| Sub => '-', | ||
|
|
@@ -137,7 +138,7 @@ impl std::fmt::Display for Operators { | |
|
|
||
| // declaring `Functions` with `token_from_str` | ||
| token_from_str! { | ||
| #[derive(Debug, PartialEq, Eq,Clone)] | ||
| #[derive(Debug, PartialEq,Clone,Copy)] | ||
| pub Functions { | ||
| Sin => "sin", | ||
| Cos => "cos", | ||
|
|
@@ -164,7 +165,7 @@ impl std::fmt::Display for Functions { | |
|
|
||
| // declaring `Constants` with `token_from_str` | ||
| token_from_str! { | ||
| #[derive(Debug, PartialEq, Eq,Clone)] | ||
| #[derive(Debug, PartialEq,Clone,Copy,)] | ||
| pub Constants { | ||
| Pi => "pi", | ||
| E => "e", | ||
|
|
@@ -280,8 +281,7 @@ impl std::fmt::Display for Expr { | |
| } | ||
| } | ||
|
|
||
| #[allow(dead_code)] | ||
| fn lexer<S>(input: S) -> Result<Vec<Token>, PolynomialError> | ||
| pub fn lexer<S>(input: S) -> Result<Vec<Token>, PolynomialError> | ||
| where | ||
| S: AsRef<str>, | ||
| { | ||
|
|
@@ -422,7 +422,6 @@ fn implied_multiplication_pass(token_stream: &mut Vec<Token>) { | |
| } | ||
| } | ||
|
|
||
| #[allow(dead_code)] | ||
| fn parse_expr(token_stream: &mut TokenStream, min_bind_pow: f64) -> Result<Expr, PolynomialError> { | ||
| let mut left = match token_stream.next() { | ||
| Some(Token::Number(n)) => Ok(Expr::Number(n)), | ||
|
|
@@ -505,8 +504,7 @@ fn parse_expr(token_stream: &mut TokenStream, min_bind_pow: f64) -> Result<Expr, | |
| Ok(left) | ||
| } | ||
|
|
||
| #[allow(dead_code)] | ||
| fn parser(token_stream: Vec<Token>) -> Result<Polynomial, PolynomialError> { | ||
| pub fn parser(token_stream: Vec<Token>) -> Result<Polynomial, PolynomialError> { | ||
| let mut tokens = token_stream; | ||
| implied_multiplication_pass(&mut tokens); | ||
| let mut token_stream = tokens.into_iter().peekable(); | ||
|
|
@@ -581,6 +579,169 @@ impl From<&'static str> for Expr { | |
| } | ||
| } | ||
|
|
||
| pub fn eval_advanced_polynomial<V, S, F>( | ||
| poly: &Polynomial, | ||
| variables: &V, | ||
| ) -> Result<f64, PolynomialError> | ||
| where | ||
| V: IntoIterator<Item = (S, F)> + std::fmt::Debug + Clone, | ||
| S: AsRef<str>, | ||
| F: Into<f64>, | ||
| { | ||
| let vars_map: HashMap<String, f64> = variables | ||
| .clone() | ||
| .into_iter() | ||
| .map(|(k, v)| (k.as_ref().to_string(), v.into())) | ||
| .collect(); | ||
| let literal_expr = replace_variable_occurence(&poly.expr, &vars_map)?; | ||
| evaluate_numerical_expression(&literal_expr).ok_or(PolynomialError::MissingVariable) // TODO: Work on error messages | ||
| } | ||
|
|
||
| fn replace_variable_occurence( | ||
| expr: &Expr, | ||
| vars: &HashMap<String, f64>, | ||
| ) -> Result<Expr, PolynomialError> { | ||
| expr.clone().map(&mut |e| match e { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another clone that might be removed |
||
| Expr::Variable(v) => { | ||
| vars.get(&v) | ||
| .copied() | ||
| .map(Expr::Number) | ||
| .ok_or(PolynomialError::VariableNotFound { | ||
| variable: v.to_string(), | ||
| }) | ||
| } | ||
| x => Ok(x), | ||
| }) | ||
| } | ||
|
|
||
| impl Expr { | ||
| pub fn map( | ||
| self, | ||
| f: &mut impl FnMut(Expr) -> Result<Expr, PolynomialError>, | ||
| ) -> Result<Expr, PolynomialError> { | ||
| match self { | ||
| // recursively walks down the polynomial for operators | ||
| Expr::BinaryOp { | ||
| op, | ||
| lhs, | ||
| rhs, | ||
| paren, | ||
| } => Ok(Expr::BinaryOp { | ||
| op, | ||
| lhs: Box::new(lhs.map(f)?), | ||
| rhs: Box::new(rhs.map(f)?), | ||
| paren, | ||
| }), | ||
| Expr::UnaryOpPrefix { op, value } => Ok(Expr::UnaryOpPrefix { | ||
| op, | ||
| value: Box::new(value.map(f)?), | ||
| }), | ||
| Expr::UnaryOpPostfix { op, value } => Ok(Expr::UnaryOpPostfix { | ||
| op, | ||
| value: Box::new(value.map(f)?), | ||
| }), | ||
| Expr::Function { func, inner } => Ok(Expr::Function { | ||
| func, | ||
| inner: Box::new(inner.map(f)?), | ||
| }), | ||
| // This allows for extension of variants with f. | ||
| x => f(x), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn extract_univariate_variable(expr: &Expr) -> Result<String, PolynomialError> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also probably take a look at the scoping of these various functions. Like this function might be good as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, I mean, are we expecting on introducing the api seperately? If so, then this would be fine, else I'll have to scope it down for pub (crate).
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What specifically do you mean by introducing the API separately??
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm, I assumed if it we were to introduce an ability to provide function like 'extract_univariate_variable' as a part of the library. |
||
| let mut variables: BTreeSet<String> = Default::default(); | ||
| let _ = expr.clone().map(&mut |e| match &e { | ||
| Expr::Variable(v) => { | ||
| variables.insert(v.to_string()); | ||
| Ok(e) | ||
| } | ||
| expr => Ok(expr.clone()), | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of cloning the entire expression tree, is there another way to traverse the tree without needing to clone (like using the visitor pattern for example) |
||
| if variables.len() > 1 { | ||
| Err(PolynomialError::TooManyVariables { | ||
| variables: variables.into_iter().collect::<Vec<_>>(), | ||
| }) | ||
| } else { | ||
| variables | ||
| .clone() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way to remove this clone? |
||
| .into_iter() | ||
| .next() | ||
| .map_or_else(|| Err(PolynomialError::MissingVariable), Ok) | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn evaluate_numerical_expression(expr: &Expr) -> Option<f64> { | ||
| match expr { | ||
| Expr::Number(v) => Some(*v), | ||
| Expr::BinaryOp { op, lhs, rhs, .. } => handle_binary_operation(op, lhs, rhs), | ||
| Expr::UnaryOpPostfix { op, value } => handle_postfix_operation(op, value), | ||
| Expr::UnaryOpPrefix { op, value } => handle_prefix_operation(op, value), | ||
| Expr::Function { func, inner } => handle_function(func, inner), | ||
| Expr::Constant(v) => handle_constants(v), | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| fn handle_binary_operation(op: &Operators, lhs: &Expr, rhs: &Expr) -> Option<f64> { | ||
| let lhs = evaluate_numerical_expression(lhs)?; | ||
| let rhs = evaluate_numerical_expression(rhs)?; | ||
| match op { | ||
| Operators::Div => Some(lhs / rhs), | ||
| Operators::Mul | Operators::CDot => Some(lhs * rhs), | ||
| Operators::Add => Some(lhs + rhs), | ||
| Operators::Sub => Some(lhs - rhs), | ||
| Operators::Rem => Some(lhs % rhs), | ||
| Operators::Caret => Some(lhs.powf(rhs)), | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| fn factorial_f64(n: f64) -> f64 { | ||
| if n < 0.0 { | ||
| return f64::NAN; | ||
| } | ||
| let n_int = n.floor() as u64; | ||
| (1..=n_int).fold(1.0, |acc, x| acc * x as f64) | ||
| } | ||
|
|
||
| fn handle_postfix_operation(op: &Operators, value: &Expr) -> Option<f64> { | ||
| match op { | ||
| Operators::Fac => Some(factorial_f64(evaluate_numerical_expression(value)?)), | ||
| _ => None, | ||
| } | ||
| } | ||
|
|
||
| fn handle_prefix_operation(op: &Operators, value: &Expr) -> Option<f64> { | ||
| let value = evaluate_numerical_expression(value)?; | ||
| match op { | ||
| Operators::Add => Some(value), | ||
| Operators::Sub => Some(-value), | ||
| _ => None, | ||
| } | ||
| } | ||
| fn handle_function(func: &Functions, value: &Expr) -> Option<f64> { | ||
| let value = evaluate_numerical_expression(value)?; | ||
| Some(match func { | ||
| Functions::Sin => value.sin(), | ||
| Functions::Cos => value.cos(), | ||
| Functions::Tan => value.tan(), | ||
| Functions::Cot => 1.0 / value.tan(), | ||
| Functions::Ln => value.ln(), | ||
| Functions::Log => value.log10(), | ||
| }) | ||
| } | ||
|
|
||
| fn handle_constants(cnst: &Constants) -> Option<f64> { | ||
| Some(match cnst { | ||
| Constants::Pi => f64::consts::PI, | ||
| Constants::E => f64::consts::E, | ||
| Constants::Tau => f64::consts::TAU, | ||
| Constants::Phi => 1.618_033_988_749_895_f64, // f64::consts::PHI | ||
| }) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
@@ -1498,6 +1659,97 @@ mod tests { | |
| let expected = Polynomial::new(Expr::Number(0.)); | ||
| assert_eq!(result, expected); | ||
| } | ||
| #[test] | ||
| fn test_failing_substitution_expression() { | ||
| let expr = "x^3-3xy+5"; | ||
| let tok_str = lexer(expr).unwrap(); | ||
| let parsed_result = parser(tok_str).unwrap(); | ||
| let evaluated_result = eval_advanced_polynomial(&parsed_result, &[("x", 5)]); | ||
| assert!(evaluated_result.is_err()); | ||
| } | ||
| #[test] | ||
| fn test_eval_expression() { | ||
| let expr = "x^3-3xy+5!"; | ||
| let tok_str = lexer(expr).unwrap(); | ||
| let parsed_result = parser(tok_str).unwrap(); | ||
| let evaluated_result = | ||
| eval_advanced_polynomial(&parsed_result, &[("x", 5), ("y", 5)]).unwrap(); | ||
| println!("{}", parsed_result); | ||
| println!("{}", evaluated_result); | ||
| assert_eq!(evaluated_result, 170.0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_univariance_of_an_expression() { | ||
| let expr = "x^3-3x+5!"; | ||
| let tok_str = lexer(expr).unwrap(); | ||
| let parsed_result = parser(tok_str).unwrap(); | ||
| let evaluated_result = extract_univariate_variable(&parsed_result.expr).unwrap(); | ||
| println!("{}", parsed_result); | ||
| println!("{}", evaluated_result); | ||
| assert_eq!(evaluated_result, String::from("x")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_too_many_variables_expression() { | ||
| let expr = "x^3-3y+5!"; | ||
| let tok_str = lexer(expr).unwrap(); | ||
| let parsed_result = parser(tok_str).unwrap(); | ||
| let evaluated_result = extract_univariate_variable(&parsed_result.expr); | ||
| println!("{}", parsed_result); | ||
| println!("{:?}", evaluated_result); | ||
| assert!(evaluated_result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_univariant_evaluation() { | ||
| let expr = "z^3-3z+5!"; | ||
| let poly = Polynomial::parse(expr).unwrap(); | ||
| let evaluated_result = poly.eval_univariate(10).unwrap(); | ||
| println!("{}", poly); | ||
| println!("{:?}", evaluated_result); | ||
| assert_eq!(evaluated_result, 1090.); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_univariant_failure_evaluation() { | ||
| let expr = "-3x+5y!"; | ||
| let poly = Polynomial::parse(expr).unwrap(); | ||
| let evaluated_result = poly.eval_univariate(1); | ||
| println!("{}", poly); | ||
| println!("{:?}", evaluated_result); | ||
| assert!(evaluated_result.is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_univariant_no_variables_evaluation() { | ||
| let expr = "-3+5!"; | ||
| let poly = Polynomial::parse(expr).unwrap(); | ||
| let evaluated_result = poly.eval_univariate(1).unwrap(); | ||
| println!("{}", poly); | ||
| println!("{:?}", evaluated_result); | ||
| assert_eq!(evaluated_result, 117.0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_multivariant_expression() { | ||
| let expr = "2x+3y!"; | ||
| let poly = Polynomial::parse(expr).unwrap(); | ||
| let evaluated_result = poly.eval_multivariate(&[("x", 2), ("y", 1)]).unwrap(); | ||
| println!("{}", poly); | ||
| println!("{:?}", evaluated_result); | ||
| assert_eq!(evaluated_result, 7.0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_multivariant_missing_mapping_expression() { | ||
| let expr = "2x+3y!"; | ||
| let poly = Polynomial::parse(expr).unwrap(); | ||
| let evaluated_result = poly.eval_multivariate(&[("x", 2)]); | ||
| println!("{}", poly); | ||
| println!("{:?}", evaluated_result); | ||
| assert!(evaluated_result.is_err()); | ||
| } | ||
| } | ||
| // --------------------------- | ||
| // Test Display | ||
|
|
@@ -1651,7 +1903,7 @@ mod tests { | |
| use super::*; | ||
|
|
||
| token_from_str! { | ||
| #[derive(Debug, PartialEq, Eq)] | ||
| #[derive(Debug, PartialEq)] | ||
| pub TestEnum { | ||
| Alpha => "alpha", | ||
| Beta => "beta", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might be able to replace this clone too if we implement the visitor pattern mentioned in one of my other comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure of how to remove this clone and I'm not sure this is related to visitor pattern as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this shouldn't be much of a problem as considering there will be only a few variables, what do you think of it?