use num_complex::Complex;
use std::collections::HashMap;
use crate::core::Real;
#[derive(Clone, Debug, PartialEq)]
pub struct Constants<T: Real>
{
map: HashMap<String, Complex<T>>,
}
impl<T: Real> Constants<T>
{
pub fn new() -> Self {
Self::default()
}
pub fn with_builtins() -> Self { Self::default() }
pub fn from<I, S, V>(items: I) -> Self
where
I: IntoIterator<Item = (S, V)>,
S: Into<String>,
Complex<T>: From<V>,
{
let mut consts = Self::new();
for (key, val) in items {
consts.insert(key, val);
}
consts
}
pub fn insert<S, V>(&mut self, key: S, value: V)
where
S: Into<String>,
Complex<T>: From<V>,
{
self.map.insert(key.into(), Complex::from(value));
}
pub fn contains<S>(&self, key: S) -> bool
where
S: AsRef<str>,
{
self.map.contains_key(key.as_ref())
}
pub fn get<S>(&self, key: S) -> Option<Complex<T>>
where
S: AsRef<str>,
{
if let Some(v) = Self::resolve_builtin(key.as_ref()) {
return Some(Complex::from(v))
}
self.map.get(key.as_ref()).cloned()
}
pub fn clear(&mut self) { self.map.clear(); }
pub fn len(&self) -> usize { self.map.len() }
pub fn is_empty(&self) -> bool { self.map.is_empty() }
pub fn keys(&self) -> impl Iterator<Item = &str>
{
self.map.keys().map(|s| s.as_str())
}
pub fn symbols() -> &'static [&'static str]
{
BUILTIN_CONSTANT_NAMES
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Complex<T>)>
{
self.map.iter()
}
}
impl<S, V, R: Real> FromIterator<(S, V)> for Constants<R>
where
S: Into<String>,
Complex<R>: From<V>,
{
fn from_iter<T: IntoIterator<Item = (S, V)>>(iter: T) -> Self
{
Self::from(iter)
}
}
macro_rules! define_builtin_constants {
($( $name:expr => $func:ident ),* $(,)? ) => {
pub const BUILTIN_CONSTANT_NAMES: &'static [&'static str] = &[
$( $name, )*
];
impl<T: Real> Constants<T> {
fn resolve_builtin(name: &str) -> Option<T>
{
match name {
$( $name => Some(T::$func()), )*
_ => None,
}
}
}
};
}
define_builtin_constants! {
"E" => e,
"FRAC_1_PI" => frac_1_pi,
"FRAC_1_SQRT_2" => frac_1_sqrt_2,
"FRAC_2_PI" => frac_2_pi,
"FRAC_2_SQRT_PI" => frac_2_sqrt_pi,
"FRAC_PI_2" => frac_pi_2,
"FRAC_PI_3" => frac_pi_3,
"FRAC_PI_4" => frac_pi_4,
"FRAC_PI_6" => frac_pi_6,
"FRAC_PI_8" => frac_pi_8,
"LN_2" => ln_2,
"LN_10" => ln_10,
"LOG2_10" => log2_10,
"LOG2_E" => log2_e,
"LOG10_2" => log10_2,
"LOG10_E" => log10_e,
"PI" => pi,
"SQRT_2" => sqrt_2,
"TAU" => tau,
}
impl<T: Real> Default for Constants<T>
{
fn default() -> Self
{
Self { map: HashMap::new() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use num_complex::Complex;
#[test]
fn test_new_insert_contains_get() {
let mut c = Constants::new();
assert!(c.is_empty());
c.insert("PI", std::f64::consts::PI);
assert!(c.contains("PI"));
let pi = c.get("PI").expect("PI should be present");
assert_eq!(pi.re, std::f64::consts::PI);
assert_eq!(pi.im, 0.0);
assert_eq!(c.len(), 1);
}
#[test]
fn test_from_and_from_iter() {
let arr = [("PI", std::f64::consts::PI), ("E", std::f64::consts::E)];
let c = Constants::from(arr);
assert!(c.contains("PI"));
assert!(c.contains("E"));
let vec = vec![
("A", Complex::new(1.0, 0.5)),
("B", Complex::from(2.0f64)),
];
let c2: Constants<f64> = vec.into_iter().collect();
assert!(c2.contains("A"));
assert!(c2.contains("B"));
}
#[test]
fn test_keys_iter_and_names_len() {
let consts = Constants::<f64>::default();
let keys: Vec<&str> = consts.keys().collect();
assert_eq!(keys.len(), 0);
}
#[test]
fn test_iter_len_clear_is_empty() {
let mut c = Constants::new();
c.insert("X", 3.14);
c.insert("Y", 2.71);
assert_eq!(c.len(), 2);
let mut found = vec![];
for (k, v) in c.iter() {
found.push((k.clone(), v.clone()));
}
assert!(found.iter().any(|(k, _)| k == "X"));
assert!(found.iter().any(|(k, _)| k == "Y"));
c.clear();
assert!(c.is_empty());
}
}