use crate::Number;
use rand::Rng;
pub mod constantterm;
pub mod variableterm;
pub mod sequenceterm;
pub mod scalarterm;
pub mod randomterm;
pub mod piecewiseterm;
pub mod fractionterm;
pub mod functionterm;
use super::ParametrizerError;
use super::ParametrizerFunction;
const DYNAMIC_RANDOM_IDENTIFIER : &str = "rd(";
const COMPUTED_RANDOM_IDENTIFIER : &str = "rc(";
const PIECEWISE_IDENTIFIER : &str = "p";
pub trait Term<T: Number>
{
fn evaluate(&self, t: T) -> T;
}
pub fn create_parametrization<T: Number>(text: &str, functions: &[ParametrizerFunction]) -> Result<Box<dyn Term<T> + Send + Sync>, ParametrizerError>
{
let mut lower = text.to_lowercase();
lower.retain(|c| { return !c.is_whitespace(); }); lower = lower.replace("\\", "/"); lower = lower.replace("-", "+-");
let param = &(lower[0..]);
return quick_parametrization(param, functions);
}
pub fn quick_parametrization<T: Number>(param: &str, functions: &[ParametrizerFunction]) ->Result<Box<dyn Term<T> + Send + Sync>, ParametrizerError>
{
for function in functions
{
if param.starts_with(function.shorthand())
{
return parametrize_string(param, functions);
}
}
if param.starts_with(PIECEWISE_IDENTIFIER) {
let simplified_string = &(param[PIECEWISE_IDENTIFIER.len()..]);
let mut parts_string = &(param[PIECEWISE_IDENTIFIER.len()..]);
let mut looping = false;
let mut loop_value = T::zero();
if simplified_string.starts_with("[")
{
let closing_index = match simplified_string.find("]")
{
Some(i) => i,
None => return Err(ParametrizerError { param: simplified_string.to_string(), reason: "Unable to find closing bracket for looping piecewise term." })
};
let loop_string = &(simplified_string[1..closing_index]);
loop_value = match loop_string.parse()
{
Ok(l) => l,
Err(_e) => return Err(ParametrizerError { param: loop_string.to_string(), reason: "Could not parse the loop value for looping piecewise term." })
};
parts_string = &(param[closing_index+2..]);
looping = true;
}
let parts : Vec<&str> = parts_string.split("|").collect();
let mut piecewise = if looping { piecewiseterm::PiecewiseTerm::looping(loop_value) } else { piecewiseterm::PiecewiseTerm::new() };
for part in parts
{
let part_info : Vec<&str> = part.split(">").collect();
if part_info.len() != 2
{
return Err(ParametrizerError { param: part.to_string(), reason: "Unexpected number of splits for piecewise part. Each part should be separated by an = sign and contain a term and a number separated by a >" });
}
let term = parametrize_string(part_info[0], functions)?;
let time = match part_info[1].parse()
{
Ok(t) => t,
Err(_e) => return Err(ParametrizerError { param: part.to_string(), reason: "Could not parse the time value for piecewise part."})
};
piecewise.add_part(term, time);
}
return Ok(Box::new(piecewise));
}
return parametrize_string(param, functions);
}
pub fn parametrize_string<T: Number>(param: &str, functions: &[ParametrizerFunction]) -> Result<Box<dyn Term<T> + Send + Sync>, ParametrizerError>
{
if param.eq("t")
{
return Ok(Box::new(variableterm::VariableTerm::new()));
}
let c = param.parse();
match c
{
Ok(c) => return Ok(Box::new(constantterm::ConstantTerm::new(c))),
Err(_e) => ()
};
let length = param.len();
if param.starts_with("(") && param.ends_with(")")
{
return parametrize_string::<T>(&(param[1..length - 1]), functions);
}
if param.starts_with("+")
{
return parametrize_string::<T>(&(param[1..]), functions);
}
if param.contains('+')
{
let terms = respectful_symbol_split(param, '+', '(', ')')?;
if terms.len() > 1 {
let mut sum_terms = Vec::new();
for term in terms
{
let new_term = parametrize_string(term, functions)?;
sum_terms.push(new_term);
}
return Ok(Box::new(sequenceterm::SequenceTerm::new(sum_terms, sequenceterm::SequenceOperations::Addition)));
}
}
if param.contains('*')
{
let terms = respectful_symbol_split(param, '*', '(', ')')?;
if terms.len() > 1 {
let mut product_terms = Vec::new();
for term in terms
{
let new_term = parametrize_string(term, functions)?;
product_terms.push(new_term);
}
return Ok(Box::new(sequenceterm::SequenceTerm::new(product_terms, sequenceterm::SequenceOperations::Multiplication)));
}
}
if param.contains('/')
{
let terms = respectful_symbol_split(param, '/', '(', ')')?;
if terms.len() > 1
{
if terms.len() > 2
{
return Err(ParametrizerError { param: param.to_string(), reason: "More than one division symbol in a term." });
}
let numerator = parametrize_string(terms[0], functions)?;
let denominator = parametrize_string(terms[1], functions)?;
return Ok(Box::new(fractionterm::FractionTerm::new(numerator, denominator)));
}
}
if param.starts_with("-")
{
let term = parametrize_string(&(param[1..]), functions)?;
return Ok(Box::new(scalarterm::ScalarTerm::new(term, T::zero() - T::one())));
}
if param.starts_with(DYNAMIC_RANDOM_IDENTIFIER) && param.ends_with(")")
{
let simplified_param = &(param[DYNAMIC_RANDOM_IDENTIFIER.len()..param.len() - 1]);
let splits : Vec<&str> = simplified_param.split("<").collect();
if splits.len() != 2
{
return Err(ParametrizerError { param: param.to_string(), reason: "Random parametrization did not split into exactly two terms." });
}
let min = parametrize_string(splits[0], functions)?;
let max = parametrize_string(splits[1], functions)?;
return Ok(Box::new(randomterm::RandomTerm::new(min, max)));
}
for function in functions
{
let shorthand = function.shorthand();
if param.starts_with(shorthand) && param.ends_with(")")
{
let simplified_param = &(param[shorthand.len()..param.len() - 1]);
let term = parametrize_string(simplified_param, functions)?;
return Ok(Box::new(functionterm::FunctionTerm::new(term, function.function())));
}
}
if param.starts_with(COMPUTED_RANDOM_IDENTIFIER) && param.ends_with(")")
{
let simplified_param = &(param[COMPUTED_RANDOM_IDENTIFIER.len()..param.len() - 1]);
let splits : Vec<&str> = simplified_param.split("<").collect();
if splits.len() != 2
{
return Err(ParametrizerError { param: param.to_string(), reason: "Random parametrization did not split into exactly two terms." });
}
let min = splits[0].parse();
let max = splits[1].parse();
let min = match min
{
Ok(m) => m,
Err(_e) => return Err(ParametrizerError { param: param.to_string(), reason: "Could not parse the minimum value as a number for computed random generation."})
};
let max = match max
{
Ok(m) => m,
Err(_e) => return Err(ParametrizerError { param: param.to_string(), reason: "Could not parse the maximum value as a umber for computed random generation."})
};
let constant = T::from_f64(rand::thread_rng().gen_range(min..max));
let constant = match constant
{
Some(c) => c,
None => return Err(ParametrizerError {param: param.to_string(), reason: "Could not convert to the generic type T from f64 for computed random generation."})
};
return Ok(Box::new(constantterm::ConstantTerm::new(constant)));
}
return Err(ParametrizerError { param: param.to_string(), reason: "Did not match any cases. Do not forget to write multiplication explicitly, i.e. 'n*t' as opposed to 'nt'." });
}
fn respectful_symbol_split<'a>(param: &'a str, splitter: char, left: char, right: char) -> Result<Vec<&'a str>, ParametrizerError>
{
let mut balance = 0;
let mut last_split = 0;
let symbols = |s: char| -> bool { return s == splitter || s == left || s == right; };
let iter = param.match_indices(symbols);
let mut splits = Vec::new();
for symbol in iter
{
if symbol.1.contains(left)
{
balance += 1;
}
else if symbol.1.contains(right)
{
balance -= 1;
if balance < 0 {
return Err(ParametrizerError { param: param.to_string(), reason: "Malformed split, right exceeded left." });
}
}
else {
if balance == 0
{
splits.push(&(param[last_split..symbol.0]));
last_split = symbol.0 + 1;
}
}
}
if balance > 0 {
return Err(ParametrizerError { param: param.to_string(), reason: "Malformed split, left exceeded right." });
}
else
{
splits.push(&(param[last_split..]));
return Ok(splits);
}
}
#[cfg(test)]
mod term_tests
{
use super::*;
#[test]
fn test_splitting ()
{
let no_split = respectful_symbol_split("15*t", '+', '(', ')').expect("Splitting failed when there was nothing to split.");
let ignore_split = respectful_symbol_split("(15*t)", '*', '(', ')').expect("Splitting failed when the splitter was in parentheses.");
let easy_split = respectful_symbol_split("9+3*t+6", '+', '(', ')').expect("Splitting failed with no parentheses.");
let hard_split = respectful_symbol_split("1+(6+9*t)+(6+(5+t))", '+', '(', ')').expect("Splitting failed with parentheses.");
let right_split = respectful_symbol_split("(t+1))*5", '+', '(', ')');
let left_split = respectful_symbol_split("((t+1)*5", '+', '(', ')');
assert_eq!(no_split, ["15*t"]);
assert_eq!(ignore_split, ["(15*t)"]);
assert_eq!(easy_split, ["9", "3*t", "6"]);
assert_eq!(hard_split, ["1", "(6+9*t)", "(6+(5+t))"]);
match right_split
{
Ok(_) => panic!("Expected too many right parentheses error."),
Err(e) => assert_eq!(e.reason, "Malformed split, right exceeded left.")
}
match left_split
{
Ok(_) => panic!("Expected too many left parentheses error."),
Err(e) => assert_eq!(e.reason, "Malformed split, left exceeded right.")
}
}
#[test]
fn test_division ()
{
let division = parametrize_string::<f32>("6/(t+1)/2", &[]);
match division
{
Ok(_) => panic!("Expected too many division terms error."),
Err(e) => assert_eq!(e.reason, "More than one division symbol in a term.")
}
}
#[test]
fn test_identifiers ()
{
fn polynomial(t: f64) -> f64
{
return t*t+2.0*t+1.0;
}
let succeed = quick_parametrization::<f32>("poly(t)", &vec![super::ParametrizerFunction::new("poly".to_string(), polynomial)]);
let fail = quick_parametrization::<f32>("poly(t)", &[]);
match succeed
{
Ok(t) => assert_eq!(16.0, t.evaluate(3.0)),
Err(_) => panic!("Expected successful parsing of added function.")
};
match fail
{
Ok(_) => panic!("Expected unable to parse error"),
Err(e) => assert_eq!(e.reason, "Unexpected number of splits for piecewise part. Each part should be separated by an = sign and contain a term and a number separated by a >")
};
}
}