use crate::math::pow;
use core::{fmt, num::ParseFloatError, str::FromStr};
pub type Node = f64;
pub type Weight = f64;
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FiniteAboveNegOneF64(f64);
impl fmt::Display for FiniteAboveNegOneF64 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::LowerExp for FiniteAboveNegOneF64 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:e}", self.0)
}
}
impl fmt::UpperExp for FiniteAboveNegOneF64 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:E}", self.0)
}
}
impl FiniteAboveNegOneF64 {
#[inline]
pub const fn new(value: f64) -> Option<Self> {
if value > -1.0 && value.is_finite() {
Some(Self(value))
} else {
None
}
}
#[inline]
pub const fn get(&self) -> f64 {
self.0
}
#[inline]
pub const fn checked_add(self, other: f64) -> Option<Self> {
Self::new(self.get() + other)
}
#[inline]
pub const fn checked_sub(self, other: f64) -> Option<Self> {
Self::new(self.get() - other)
}
#[inline]
pub const fn checked_mul(self, other: f64) -> Option<Self> {
Self::new(self.get() * other)
}
#[inline]
pub const fn checked_div(self, denominator: f64) -> Option<Self> {
Self::new(self.get() / denominator)
}
#[inline]
pub fn checked_pow(self, exp: f64) -> Option<Self> {
Self::new(pow(self.get(), exp))
}
}
impl Default for FiniteAboveNegOneF64 {
#[inline]
fn default() -> Self {
Self(0.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct InfNanNegOneOrLessError;
impl fmt::Display for InfNanNegOneOrLessError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"attempted to convert a value that is infinite, NAN, or less than or equal to -1.0 to a `FiniteAboveNegOneF64`"
)
}
}
impl core::error::Error for InfNanNegOneOrLessError {}
impl TryFrom<f64> for FiniteAboveNegOneF64 {
type Error = InfNanNegOneOrLessError;
#[inline]
fn try_from(value: f64) -> Result<Self, Self::Error> {
FiniteAboveNegOneF64::new(value).ok_or(InfNanNegOneOrLessError)
}
}
impl From<FiniteAboveNegOneF64> for f64 {
#[inline]
fn from(value: FiniteAboveNegOneF64) -> Self {
value.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseFiniteAboveNegOneF64Error {
ParseError(ParseFloatError),
InvalidValue(InfNanNegOneOrLessError),
}
impl fmt::Display for ParseFiniteAboveNegOneF64Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ParseFiniteAboveNegOneF64Error::ParseError(e) => write!(f, "{e}"),
ParseFiniteAboveNegOneF64Error::InvalidValue(e) => write!(f, "{e}"),
}
}
}
impl core::error::Error for ParseFiniteAboveNegOneF64Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
ParseFiniteAboveNegOneF64Error::ParseError(e) => Some(e),
ParseFiniteAboveNegOneF64Error::InvalidValue(e) => Some(e),
}
}
}
impl FromStr for FiniteAboveNegOneF64 {
type Err = ParseFiniteAboveNegOneF64Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<f64>() {
Ok(value) => value
.try_into()
.map_err(ParseFiniteAboveNegOneF64Error::InvalidValue),
Err(e) => Err(ParseFiniteAboveNegOneF64Error::ParseError(e)),
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_node_weight_rule {
(
// The name of the quadrature rule struct, e.g. GaussLegendre.
$quadrature_rule:ident,
// The name that the iterator over the nodes should have, e.g. GaussLegendreNodes.
$quadrature_rule_nodes:ident,
// The name that the iterator over the weights should have, e.g. GaussLegendreWeights.
$quadrature_rule_weights:ident,
// The name that the iterator returned when calling the `iter` function should have,
// e.g. GaussLegendreIter.
$quadrature_rule_iter:ident,
// The name of the iterator returned by the by the IntoIterator trait.
$quadrature_rule_into_iter:ident
) => {
impl ::core::iter::IntoIterator for $quadrature_rule {
type IntoIter = $quadrature_rule_into_iter;
type Item = ($crate::Node, $crate::Weight);
#[inline]
fn into_iter(self) -> Self::IntoIter {
$quadrature_rule_into_iter::new(self.node_weight_pairs.into_iter())
}
}
impl<'a> ::core::iter::IntoIterator for &'a $quadrature_rule {
type IntoIter = $quadrature_rule_iter<'a>;
type Item = &'a ($crate::Node, $crate::Weight);
#[inline]
fn into_iter(self) -> Self::IntoIter {
$quadrature_rule_iter::new(self.node_weight_pairs.iter())
}
}
impl $quadrature_rule {
#[inline]
pub fn nodes(&self) -> $quadrature_rule_nodes<'_> {
$quadrature_rule_nodes::new(self.node_weight_pairs.iter().map(|p| &p.0))
}
#[inline]
pub fn weights(&self) -> $quadrature_rule_weights<'_> {
$quadrature_rule_weights::new(self.node_weight_pairs.iter().map(|p| &p.1))
}
#[inline]
pub fn iter(&self) -> $quadrature_rule_iter<'_> {
$quadrature_rule_iter::new(self.node_weight_pairs.iter())
}
#[inline]
pub fn as_node_weight_pairs(&self) -> &[($crate::Node, $crate::Weight)] {
&self.node_weight_pairs
}
#[inline]
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_node_weight_pairs(self) -> ::alloc::boxed::Box<[($crate::Node, $crate::Weight)]> {
self.node_weight_pairs
}
#[inline]
pub fn degree(&self) -> ::core::primitive::usize {
self.node_weight_pairs.len()
}
}
#[derive(::core::fmt::Debug, ::core::clone::Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct $quadrature_rule_nodes<'a>(
).map(|(x, _)| x)`.
::core::iter::Map<
::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Node,
>,
);
impl<'a> $quadrature_rule_nodes<'a> {
#[inline]
const fn new(
iter_map: ::core::iter::Map<
::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Node,
>,
) -> Self {
Self(iter_map)
}
}
$crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_nodes<'a>, &'a $crate::Node}
#[derive(::core::fmt::Debug, ::core::clone::Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct $quadrature_rule_weights<'a>(
::core::iter::Map<
::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Weight,
>,
);
impl<'a> $quadrature_rule_weights<'a> {
#[inline]
const fn new(
iter_map: ::core::iter::Map<
::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
fn(&'a ($crate::Node, $crate::Weight)) -> &'a $crate::Weight,
>,
) -> Self {
Self(iter_map)
}
}
$crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_weights<'a>, &'a $crate::Weight}
#[derive(::core::fmt::Debug, ::core::clone::Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct $quadrature_rule_iter<'a>(
::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
);
impl<'a> $quadrature_rule_iter<'a> {
#[inline]
const fn new(
node_weight_pairs: ::core::slice::Iter<'a, ($crate::Node, $crate::Weight)>,
) -> Self {
Self(node_weight_pairs)
}
#[inline]
pub fn as_slice(&self) -> &'a [($crate::Node, $crate::Weight)] {
self.0.as_slice()
}
}
impl<'a> ::core::convert::AsRef<[($crate::Node, $crate::Weight)]>
for $quadrature_rule_iter<'a>
{
#[inline]
fn as_ref(&self) -> &[($crate::Node, $crate::Weight)] {
self.0.as_ref()
}
}
$crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_iter<'a>, &'a ($crate::Node, $crate::Weight)}
#[derive(::core::fmt::Debug, ::core::clone::Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct $quadrature_rule_into_iter(::alloc::vec::IntoIter<($crate::Node, $crate::Weight)>);
impl $quadrature_rule_into_iter {
#[inline]
const fn new(
node_weight_pairs: ::alloc::vec::IntoIter<($crate::Node, $crate::Weight)>,
) -> Self {
Self(node_weight_pairs)
}
#[inline]
pub fn as_slice(&self) -> &[($crate::Node, $crate::Weight)] {
self.0.as_slice()
}
}
impl ::core::convert::AsRef<[($crate::Node, $crate::Weight)]>
for $quadrature_rule_into_iter
{
#[inline]
fn as_ref(&self) -> &[($crate::Node, $crate::Weight)] {
self.0.as_ref()
}
}
$crate::__impl_slice_iterator_newtype_traits!{$quadrature_rule_into_iter, ($crate::Node, $crate::Weight)}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_slice_iterator_newtype_traits {
($iterator:ident$(<$a:lifetime>)?, $item:ty) => {
impl$(<$a>)? ::core::iter::Iterator for $iterator$(<$a>)? {
type Item = $item;
#[inline]
fn next(&mut self) -> ::core::option::Option<Self::Item> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (::core::primitive::usize, ::core::option::Option<::core::primitive::usize>) {
self.0.size_hint()
}
#[inline]
fn nth(&mut self, index: ::core::primitive::usize) -> ::core::option::Option<Self::Item> {
self.0.nth(index)
}
#[inline]
fn count(self) -> ::core::primitive::usize {
self.0.count()
}
#[inline]
fn last(mut self) -> ::core::option::Option<Self::Item> {
self.0.next_back()
}
}
impl$(<$a>)? ::core::iter::DoubleEndedIterator for $iterator$(<$a>)? {
#[inline]
fn next_back(&mut self) -> ::core::option::Option<Self::Item> {
self.0.next_back()
}
#[inline]
fn nth_back(&mut self, n: ::core::primitive::usize) -> ::core::option::Option<Self::Item> {
self.0.nth_back(n)
}
}
impl$(<$a>)? ::core::iter::ExactSizeIterator for $iterator$(<$a>)? {
#[inline]
fn len(&self) -> ::core::primitive::usize {
self.0.len()
}
}
impl$(<$a>)? ::core::iter::FusedIterator for $iterator$(<$a>)? {}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_node_rule {
($quadrature_rule:ident, $quadrature_rule_iter:ident) => {
impl ::core::iter::IntoIterator for $quadrature_rule {
type Item = $crate::Node;
type IntoIter = $quadrature_rule_iter;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> ::core::iter::IntoIterator for &'a $quadrature_rule {
type IntoIter = $quadrature_rule_iter;
type Item = $crate::Node;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl $quadrature_rule {
#[inline]
pub fn iter(&self) -> $quadrature_rule_iter {
$quadrature_rule_iter::new((0..self.degree.get()).map(|d| d as $crate::Node))
}
#[inline]
pub fn degree(&self) -> ::core::num::NonZeroU32 {
self.degree
}
}
#[derive(Debug, Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct $quadrature_rule_iter(
::core::iter::Map<core::ops::Range<u32>, fn(u32) -> $crate::Node>,
);
impl $quadrature_rule_iter {
#[inline]
const fn new(
iter: ::core::iter::Map<core::ops::Range<u32>, fn(u32) -> $crate::Node>,
) -> Self {
Self(iter)
}
}
$crate::__impl_slice_iterator_newtype_traits! {$quadrature_rule_iter, $crate::Node}
};
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{boxed::Box, vec::Vec};
use core::f64;
use core::num::NonZeroUsize;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MockQuadrature {
node_weight_pairs: Box<[(Node, Weight)]>,
}
impl MockQuadrature {
pub fn new(deg: NonZeroUsize) -> Self {
Self {
node_weight_pairs: (0..deg.get()).map(|d| (d as f64, 1.0)).collect(),
}
}
pub fn integrate<F>(&self, a: f64, b: f64, integrand: F) -> f64
where
F: Fn(f64) -> f64,
{
let rect_width = (b - a) / self.node_weight_pairs.len() as f64;
let result: f64 = self
.node_weight_pairs
.iter()
.map(|(x_val, w_val)| integrand(a + rect_width * (0.5 + x_val)) * w_val)
.sum();
result * rect_width
}
}
__impl_node_weight_rule! {MockQuadrature, MockQuadratureNodes, MockQuadratureWeights, MockQuadratureIter, MockQuadratureIntoIter}
#[test]
fn test_macro_implementation() {
let quad = MockQuadrature::new(5.try_into().unwrap());
assert_eq!(quad.integrate(0.0, 1.0, |x| x), 0.5);
assert_eq!(quad.nodes().count(), 5);
assert_eq!(quad.weights().count(), 5);
assert_eq!(quad.iter().count(), 5);
assert_eq!(quad.as_node_weight_pairs().len(), 5);
let vec: Vec<(Node, Weight)> = quad.clone().into_iter().collect();
assert_eq!(vec.len(), 5);
let pairs = quad.clone().into_node_weight_pairs();
assert_eq!(pairs.len(), 5);
assert_eq!(quad.degree(), 5);
let mut quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.next().unwrap().0, 0.0);
assert_eq!(quad_iter.next().unwrap().0, 1.0);
let quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.size_hint(), (5, Some(5)));
let mut quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.nth(2).unwrap().0, 2.0);
let quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.count(), 5);
let quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.last().unwrap().0, 4.0);
let mut quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.next_back().unwrap().0, 4.0);
let mut quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.nth_back(1).unwrap().0, 3.0);
let quad_iter = (&quad).into_iter();
assert_eq!(quad_iter.len(), 5);
let quad_slice = (&quad).into_iter().as_slice();
assert_eq!(quad_slice.len(), 5);
assert_eq!(quad_slice[2].0, 2.0);
let quad_iter = (&quad).into_iter();
let quad_ref = quad_iter.as_ref();
assert_eq!(quad_ref.len(), 5);
assert_eq!(quad_ref[2].0, 2.0);
}
#[test]
fn test_new_finite_above_neg_one_f64() {
assert!(FiniteAboveNegOneF64::new(0.0).is_some());
assert!(FiniteAboveNegOneF64::new(-0.5).is_some());
assert!(FiniteAboveNegOneF64::new(-1.0).is_none());
assert!(FiniteAboveNegOneF64::new(f64::NAN).is_none());
assert!(FiniteAboveNegOneF64::new(f64::INFINITY).is_none());
assert!(FiniteAboveNegOneF64::new(f64::NEG_INFINITY).is_none());
}
#[test]
fn test_try_from_f64() {
assert!(FiniteAboveNegOneF64::try_from(0.0).is_ok());
assert!(FiniteAboveNegOneF64::try_from(-0.5).is_ok());
assert!(FiniteAboveNegOneF64::try_from(-1.0).is_err());
assert!(FiniteAboveNegOneF64::try_from(f64::NAN).is_err());
assert!(FiniteAboveNegOneF64::try_from(f64::INFINITY).is_err());
assert!(FiniteAboveNegOneF64::try_from(f64::NEG_INFINITY).is_err());
}
#[test]
fn test_from_str() {
assert_eq!(FiniteAboveNegOneF64::from_str("0.0").unwrap().get(), 0.0);
assert_eq!(FiniteAboveNegOneF64::from_str("-0.5").unwrap().get(), -0.5);
assert_eq!(
FiniteAboveNegOneF64::from_str("-1.0"),
Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
InfNanNegOneOrLessError
))
);
assert_eq!(
FiniteAboveNegOneF64::from_str("NAN"),
Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
InfNanNegOneOrLessError
))
);
assert_eq!(
FiniteAboveNegOneF64::from_str("INF"),
Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
InfNanNegOneOrLessError
))
);
assert_eq!(
FiniteAboveNegOneF64::from_str("-INF"),
Err(ParseFiniteAboveNegOneF64Error::InvalidValue(
InfNanNegOneOrLessError
))
);
}
#[test]
fn test_finite_above_neg_one_f64_arithmetic() {
let value = FiniteAboveNegOneF64::new(0.5).unwrap();
assert_eq!(value.checked_add(0.5).unwrap().get(), 1.0);
assert!(value.checked_add(f64::INFINITY).is_none());
assert!(value.checked_sub(2.0).is_none());
assert!(value.checked_add(f64::NAN).is_none());
assert_eq!(value.checked_mul(2.0).unwrap().get(), 1.0);
assert!(value.checked_div(0.0).is_none());
assert!(value.checked_div(0.0).is_none());
assert!(value.checked_div(f64::NAN).is_none());
assert_eq!(value.checked_pow(2.0).unwrap().get(), 0.25);
assert!(
FiniteAboveNegOneF64::new(2.0)
.unwrap()
.checked_pow(f64::INFINITY)
.is_none()
);
}
#[test]
fn test_from_impl() {
let value = FiniteAboveNegOneF64::new(0.5).unwrap();
let f64_value: f64 = value.into();
assert_eq!(f64_value, 0.5);
let value_from_f64: Result<FiniteAboveNegOneF64, _> = 0.5f64.try_into();
assert!(value_from_f64.is_ok());
assert_eq!(value_from_f64.unwrap().get(), 0.5);
let value_from_invalid: Result<FiniteAboveNegOneF64, _> = (-1.0f64).try_into();
assert!(value_from_invalid.is_err());
}
#[test]
fn test_default() {
let value = FiniteAboveNegOneF64::default();
assert_eq!(value.get(), 0.0);
}
}