var_quantity 0.3.3

Provides an interface for defining variable quantities whose value depends on that of other quantities.
Documentation
use dyn_quantity::uom::si::magnetic_flux_density::millitesla;
use dyn_quantity::uom::si::{
    electric_current::ampere, electrical_resistance::ohm, electrical_resistivity::ohm_meter,
    f64::*, magnetic_flux_density::tesla, power::watt, thermodynamic_temperature::kelvin,
};
use dyn_quantity::{DynQuantity, PredefUnit, Unit, deserialize_quantity};
use indoc::indoc;
use serde::{Deserialize, Serialize};
use var_quantity::{unary::FirstOrderTaylor, *};

#[test]
fn test_deserialize_constant_success() {
    {
        // No units
        let q: VarQuantity<MagneticFluxDensity> = serde_yaml::from_str("0.001").unwrap();
        assert_eq!(q.get(&[]).get::<tesla>(), 0.001)
    }
    {
        // With units
        let q: VarQuantity<MagneticFluxDensity> = serde_yaml::from_str("1 mT").unwrap();
        assert_eq!(q.get(&[]).get::<tesla>(), 0.001)
    }
    {
        // With units
        let q: VarQuantity<MagneticFluxDensity> = serde_yaml::from_str("1e-3 T").unwrap();
        assert_eq!(q.get(&[]).get::<tesla>(), 0.001)
    }
    {
        // With units
        let q: VarQuantity<ElectricalResistivity> =
            serde_yaml::from_str("1/(2.0e6) Ohm*m").unwrap();
        assert_eq!(q.get(&[]).get::<ohm_meter>(), 0.5e-6)
    }
}

#[test]
fn test_deserialize_constant_fail() {
    {
        // Unit mismatch
        assert!(serde_yaml::from_str::<VarQuantity<Power>>("1 mT").is_err());
    }
}

#[test]
fn test_serialize_and_deserialize() {
    {
        // Constant
        let q = VarQuantity::Constant(MagneticFluxDensity::new::<tesla>(1.0));
        let string = serde_yaml::to_string(&q).expect("serializable");
        let q_serde: VarQuantity<MagneticFluxDensity> =
            serde_yaml::from_str(&string).expect("deserializable");
        assert_eq!(q_serde.get(&[]).get::<tesla>(), 1.0);
    }
    {
        // Function
        let fun = FirstOrderTaylor::new(
            DynQuantity::new(2.5, PredefUnit::Power),
            DynQuantity::new(2.0, Unit::from(PredefUnit::ElectricCurrent).powi(-1)),
            DynQuantity::new(0.5, PredefUnit::ElectricCurrent),
        )
        .unwrap();
        let q: VarQuantity<Power> = VarQuantity::try_from_quantity_function(fun).unwrap();
        let string = serde_yaml::to_string(&q).expect("serializable");
        let q_serde: VarQuantity<Power> = serde_yaml::from_str(&string).expect("deserializable");
        assert_eq!(q_serde.get(&[]).get::<watt>(), 2.5);
    }
}

#[test]
fn test_serialize_with_units() {
    let q = VarQuantity::Constant(MagneticFluxDensity::new::<millitesla>(1.0));

    let expected = indoc::indoc! {"
        ---
        0.001
        "};
    let actual = serde_yaml::to_string(&q).expect("serialization succeeds");
    assert_eq!(expected, actual);

    let expected = indoc::indoc! {"
        ---
        0.001 s^-2 kg A^-1
        "};
    let actual =
        serialize_with_units(|| serde_yaml::to_string(&q)).expect("serialization succeeds");
    assert_eq!(expected, actual);

    let q_de = serde_yaml::from_str(&actual).expect("deserialization succeeds");
    assert_eq!(q, q_de);
}

// A simple function for a variable electric resistance. If one of the
// conditions is a temperature, divide it by 10 K and add it to the
// base value, otherwise just return the base value
#[derive(Serialize, Deserialize, Clone, PartialEq)]
struct VariableResistance {
    #[serde(deserialize_with = "deserialize_quantity")]
    base_value: ElectricalResistance,
}

#[typetag::serde]
impl IsQuantityFunction for VariableResistance {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let quantity = conditions
            .into_iter()
            .find_map(|iq| {
                if Unit::from(PredefUnit::Temperature) == iq.unit {
                    return Some(self.base_value * iq.value / 10.0);
                } else {
                    return None;
                }
            })
            .unwrap_or(self.base_value);
        return quantity.into();
    }

    fn dyn_eq(&self, other: &dyn IsQuantityFunction) -> bool {
        (other as &dyn std::any::Any).downcast_ref::<Self>() == Some(self)
    }
}

#[test]
fn test_deserialize_var_resistance() {
    let conditions = [
        ElectricCurrent::new::<ampere>(2.0).into(),
        ThermodynamicTemperature::new::<kelvin>(20.0).into(),
    ];

    {
        // No units
        let string = indoc! {"
        ---
        VariableResistance:
            base_value: 2.0
        "};

        let var_quantity: VarQuantity<ElectricalResistance> = serde_yaml::from_str(string).unwrap();

        assert_eq!(var_quantity.get(conditions.as_slice()).get::<ohm>(), 4.0);
    }
    {
        // With units
        let string = indoc! {"
        ---
        VariableResistance:
            base_value: 2.0 mOhm
        "};

        let var_quantity: VarQuantity<ElectricalResistance> = serde_yaml::from_str(string).unwrap();

        assert_eq!(var_quantity.get(conditions.as_slice()).get::<ohm>(), 0.004);
    }
}

#[test]
fn test_serialize_and_deserialize_var_resistance() {
    let conditions = [
        ElectricCurrent::new::<ampere>(2.0).into(),
        ThermodynamicTemperature::new::<kelvin>(20.0).into(),
    ];
    {
        let multiply_by_volt = VariableResistance {
            base_value: ElectricalResistance::new::<ohm>(2.0),
        };
        let boxed: Box<dyn IsQuantityFunction> = Box::new(multiply_by_volt);
        let string = serde_yaml::to_string(&boxed).unwrap();
        let var_quantity: VarQuantity<ElectricalResistance> =
            serde_yaml::from_str(&string).unwrap();

        assert_eq!(var_quantity.get(conditions.as_slice()).get::<ohm>(), 4.0);
    }
}