#![forbid(unsafe_code)]
#![warn(clippy::pedantic)]
use std::panic::{RefUnwindSafe, UnwindSafe};
use serde::{Deserialize, Serialize};
use static_assertions::assert_impl_all;
pub mod math_helpers;
#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, PartialOrd, Serialize)]
#[must_use]
pub struct Monomial {
pub c: f64, pub e: f64, }
assert_impl_all!(Monomial: RefUnwindSafe, Send, Sized, Sync, Unpin, UnwindSafe);
impl Monomial {
pub fn new(c: f64, e: f64) -> Self {
Self { c, e }
}
#[must_use]
pub fn value(&self, x: f64) -> f64 {
self.c * (x.powf(self.e))
}
pub fn add_monomial_of_same_power(&self, other: &Self) -> Result<Self, String> {
if !math_helpers::is_equal_within_tolerance_to(&self.e, &other.e) {
return Err("Cannot add monomials with different powers of x.".to_string());
}
Ok(Self {
c: self.c + other.c,
e: self.e,
})
}
pub fn multiply_monomial(&self, other: &Self) -> Self {
Self {
c: self.c * other.c,
e: self.e + other.e,
}
}
pub fn derivative(&self) -> Self {
Self {
c: self.c * self.e,
e: self.e - 1_f64,
}
}
pub fn nth_derivative(&self, n: u32) -> Self {
let mut new_monomial = *self;
for _ in 0..n {
new_monomial = new_monomial.derivative();
}
new_monomial
}
#[must_use]
pub fn is_equal_within_tolerance_to(&self, other: &Self) -> bool {
let c_equal = math_helpers::is_equal_within_tolerance_to(&self.c, &other.c);
let e_equal = math_helpers::is_equal_within_tolerance_to(&self.e, &other.e);
c_equal && e_equal
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, PartialOrd, Serialize)]
#[must_use]
pub struct Polynomial(pub Vec<Monomial>);
assert_impl_all!(Polynomial: RefUnwindSafe, Send, Sized, Sync, Unpin, UnwindSafe);
impl Polynomial {
pub fn new() -> Self {
Self(vec![])
}
#[must_use]
pub fn value(&self, x: f64) -> f64 {
let elements = &self.0;
let mut value = 0_f64;
for element in elements {
value += element.value(x);
}
value
}
pub fn simplified(&self) -> Result<Self, String> {
Ok(self
.simplify_by_combining_alike_powers()?
.eliminate_zero_coefficients()
.sort_by_exponent())
}
pub fn simplify_by_combining_alike_powers(&self) -> Result<Self, String> {
let elements = &self.0;
let mut simplified_elements = Self::new();
let mut first_element_transferred = false;
for element in elements {
if !first_element_transferred {
simplified_elements.0.push(*element);
first_element_transferred = true;
continue;
}
let mut found_match = false;
for simplified_element in &mut simplified_elements.0 {
if math_helpers::is_equal_within_tolerance_to(&element.e, &simplified_element.e) {
*simplified_element = simplified_element.add_monomial_of_same_power(element)?;
found_match = true;
break;
}
}
if !found_match {
simplified_elements.0.push(*element);
}
}
Ok(simplified_elements)
}
pub fn eliminate_zero_coefficients(&self) -> Self {
let elements = &self.0;
let mut new_elements = vec![];
for element in elements {
if element.c != 0_f64 {
new_elements.push(*element);
}
}
Self(new_elements)
}
pub fn sort_by_exponent(&self) -> Self {
let mut elements = self.0.clone();
elements.sort_by(|a, b| b.e.partial_cmp(&a.e).unwrap_or(std::cmp::Ordering::Equal));
Self(elements)
}
pub fn add_polynomial(&self, other: Self) -> Result<Self, String> {
let mut elements = self.0.clone();
elements.extend(other.0.clone());
let new_polynomial = Self(elements);
new_polynomial.simplified()
}
pub fn multiply_polynomial(&self, other: Self) -> Result<Self, String> {
let mut elements = vec![];
for element1 in &self.0 {
for element2 in &other.0 {
elements.push(element1.multiply_monomial(element2));
}
}
let new_polynomial = Self(elements);
new_polynomial.simplified()
}
pub fn derivative(&self) -> Result<Self, String> {
let mut elements = vec![];
for element in &self.0 {
elements.push(element.derivative());
}
Self(elements).simplified()
}
pub fn nth_derivative(&self, n: u32) -> Result<Self, String> {
let mut new_polynomial = self.clone();
for _ in 0..n {
new_polynomial = new_polynomial.derivative()?;
}
Ok(new_polynomial)
}
pub fn is_equal_within_tolerance_to(&self, other: Self) -> Result<bool, String> {
let simplified_self = self.simplified()?;
let simplified_other = other.simplified()?;
if simplified_self.0.len() != simplified_other.0.len() {
return Ok(false);
}
for (element1, element2) in simplified_self.0.iter().zip(simplified_other.0.iter()) {
if !math_helpers::is_equal_within_tolerance_to(&element1.c, &element2.c)
|| !math_helpers::is_equal_within_tolerance_to(&element1.e, &element2.e)
{
return Ok(false);
}
}
Ok(true)
}
pub fn trend_over_interval(&self, start: f64, end: f64) -> String {
let mut start_x = start;
let mut end_x = end;
if start_x > end_x {
std::mem::swap(&mut start_x, &mut end_x);
}
let start_value = self.value(start_x);
let end_value = self.value(end_x);
if start_value < end_value {
"increasing".to_string()
} else if start_value > end_value {
"decreasing".to_string()
} else if start_value == end_value {
"constant".to_string()
} else {
"undefined".to_string()
}
}
pub fn concavity_over_interval(&self, start: f64, end: f64) -> Result<String, String> {
let mut start_x = start;
let mut end_x = end;
if start_x > end_x {
std::mem::swap(&mut start_x, &mut end_x);
}
let second_derivative = self.nth_derivative(2)?;
let start_value = second_derivative.value(start_x);
let end_value = second_derivative.value(end_x);
if start_value > 0.0 && end_value > 0.0 {
Ok("concave up".to_string())
} else if start_value < 0.0 && end_value < 0.0 {
Ok("concave down".to_string())
} else {
Ok("undefined".to_string())
}
}
}
#[cfg(test)]
mod tests;