use approx::AbsDiffEq;
use serde::{Deserialize, Serialize};
use struct_iterable::Iterable;
use crate::{
composition::ScaleComponents,
constants,
util::{iter_all_abs_diff_eq, iter_fields_as},
};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[doc = include_str!("../../docs/bibs/27.md")]
#[doc = include_str!("../../docs/bibs/34.md")]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Iterable, PartialEq, Serialize, Deserialize, Copy, Clone, Debug)]
#[serde(default, deny_unknown_fields)]
pub struct Fibers {
#[doc = include_str!("../../docs/bibs/24.md")]
#[doc = include_str!("../../docs/bibs/27.md")]
pub inulin: f64,
#[doc = include_str!("../../docs/bibs/24.md")]
#[doc = include_str!("../../docs/bibs/27.md")]
pub oligofructose: f64,
pub other: f64,
}
impl Fibers {
#[must_use]
pub const fn empty() -> Self {
Self {
inulin: 0.0,
oligofructose: 0.0,
other: 0.0,
}
}
#[must_use]
pub const fn new() -> Self {
Self::empty()
}
#[must_use]
pub const fn inulin(self, inulin: f64) -> Self {
Self { inulin, ..self }
}
#[must_use]
pub const fn oligofructose(self, oligofructose: f64) -> Self {
Self { oligofructose, ..self }
}
#[must_use]
pub const fn other(self, other: f64) -> Self {
Self { other, ..self }
}
#[must_use]
pub fn total(&self) -> f64 {
iter_fields_as::<f64, _>(self).sum()
}
#[must_use]
pub fn energy(&self) -> f64 {
(self.inulin + self.oligofructose) * constants::energy::INULIN_AND_OLIGOFRUCTOSE
}
#[must_use]
pub fn to_pod(&self) -> f64 {
(self.inulin * constants::pod::INULIN + self.oligofructose * constants::pod::OLIGOFRUCTOSE) / 100.0
}
}
#[cfg_attr(coverage, coverage(off))]
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl Fibers {
#[allow(clippy::missing_const_for_fn)] #[wasm_bindgen(constructor)]
#[must_use]
pub fn new_wasm() -> Self {
Self::new()
}
}
impl ScaleComponents for Fibers {
fn scale(&self, factor: f64) -> Self {
Self {
inulin: self.inulin * factor,
oligofructose: self.oligofructose * factor,
other: self.other * factor,
}
}
fn add(&self, other: &Self) -> Self {
Self {
inulin: self.inulin + other.inulin,
oligofructose: self.oligofructose + other.oligofructose,
other: self.other + other.other,
}
}
}
impl AbsDiffEq for Fibers {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
iter_all_abs_diff_eq::<f64, f64, Self>(self, other, epsilon)
}
}
impl Default for Fibers {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
#[allow(clippy::unwrap_used, clippy::float_cmp)]
mod tests {
use crate::tests::asserts::shadow_asserts::assert_eq;
#[expect(unused_imports)]
use crate::tests::asserts::*;
use super::*;
#[test]
fn fibers_total() {
assert_eq!(Fibers::new().inulin(5.0).oligofructose(3.0).other(2.0).total(), 10.0);
}
#[test]
fn fibers_energy() {
assert_eq!(
Fibers::new().inulin(10.0).oligofructose(5.0).energy(),
(10.0 + 5.0) * constants::energy::INULIN_AND_OLIGOFRUCTOSE
);
}
#[test]
fn fibers_to_pod() {
assert_eq!(Fibers::new().inulin(10.0).to_pod(), 0.0);
assert_eq!(Fibers::new().oligofructose(10.0).to_pod(), 4.0);
}
}