Skip to main content

VarQuantity

Enum VarQuantity 

Source
pub enum VarQuantity<T: IsQuantity> {
    Constant(T),
    Function(FunctionWrapper<T>),
}
Expand description

A quantity whose value can either be constant or a function of one or more other quantities.

The value of (physical) quantities can depend on the values of other quantities. This is often the case for quantities representing physical properties such as e.g. the electric resistance of a conductor. This enum serves as a general container for such quantities with the variant VarQuantity::Constant being an optimization for the important case of a constant quantitity and with the variant VarQuantity::Function covering all other cases via a QuantityFunction trait object (wrapped in FunctionWrapper). Due to the generic design, it can also be used for dimensionless quantities which can be represented by a simple f64.

The value of the underlying quantity can be read out via the VarQuantity::get method. It takes a slice of DynQuantity representing influencing factors, for example the temperature in case of a resistance. If the enum variant is constant, the value field is simply cloned, otherwise the QuantityFunction::call function is called. This returns a DynQuantity<f64>, which must be convertable via TryFrom to T (enforced by trait bound). This dynamic approach is chosen to make this enum serializable / deserializable (see section Features).

Even though the conversion from DynQuantity<f64> to T is fallible from the perspective of the type system, in actual implementations it must be infallible (i.e. the conversion must always succeed). This is done so T can be a statically typed physical quantity (e.g. from the uom library), for which From<DynQuantity<f64>> can obviously not be implemented. The conversion is checked once when constructing a FunctionWrapper from a QuantityFunction trait object by calling QuantityFunction::call with influencing_factors = &[], but of course it is impossible to test all potential values for influencing_factors.

It is therefore up to the provider of the trait object to make sure that the DynQuantity<f64> returned by QuantityFunction::call always has the same Unit. If this is not the case, the trait object has a bug and the program has entered an invalid state, resulting in a panic!.

§Examples

§f64 and statically typed physical quantities

This example shows how VarQuantity integrates with both f64 and uom Quantity.

use dyn_quantity::{DynQuantity, PredefUnit, Unit};
use uom::si::electrical_resistance::ohm;
use uom::si::f64::ElectricalResistance;
use var_quantity::{FunctionWrapper, QuantityFunction, VarQuantity};

// =============================================================================
// Constant quantity with f64
let qt_const = VarQuantity::<f64>::Constant(2.0);

// Influencing factors
let infl1 = &[DynQuantity::new(6.0, PredefUnit::ElectricCurrent)];
let infl2 = &[
    DynQuantity::new(6.0, PredefUnit::ElectricCurrent),
    DynQuantity::new(20.0, PredefUnit::Temperature),
];

// Since this is a constant quantity, it returns always 2 regardless of the input.
assert_eq!(2.0, qt_const.get(&[]));
assert_eq!(2.0, qt_const.get(infl1));
assert_eq!(2.0, qt_const.get(infl2));

// =============================================================================
// Variable quantity

// A variable resistance: The resistance is 1 + temperature / 100.
// For the test, the serde feature is enabled, hence it is necessary to
// implement serialization and deserialization as well as #[typetag::serde].
// This is not needed if the feature is not enabled.
#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct ResistanceFunction;

#[typetag::serde]
impl QuantityFunction for ResistanceFunction {
    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let mut temperature = 0.0;
        let temperature_unit: Unit = PredefUnit::Temperature.into();
        for f in influencing_factors.iter() {
            if f.unit == temperature_unit {
                temperature = f.value;
                break;
            }
        }
        return DynQuantity::new(1.0 + temperature / 100.0, PredefUnit::ElectricResistance);
    }
}

let wrapper = FunctionWrapper::new(Box::new(ResistanceFunction {})).expect("type check successfull");
let qt_var = VarQuantity::<ElectricalResistance>::Function(wrapper);

// Input infl2 contains a temperature and therefore influences the resistance.
assert_eq!(ElectricalResistance::new::<ohm>(1.0), qt_var.get(&[]));
assert_eq!(ElectricalResistance::new::<ohm>(1.0), qt_var.get(infl1));
assert_eq!(ElectricalResistance::new::<ohm>(1.2), qt_var.get(infl2));

§Unit mismatch

This example shows a violation of the assumption that the DynQuantity returned by the QuantityFunction trait object is convertible to T.

use dyn_quantity::{DynQuantity, PredefUnit};
use uom::si::electrical_conductance::siemens;
use uom::si::f64::{ElectricalResistance, ElectricalConductance};
use var_quantity::{FunctionWrapper, QuantityFunction, VarQuantity};

#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct ResistanceFunction;

#[typetag::serde]
impl QuantityFunction for ResistanceFunction {
    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
    }
}

// Mismatch in type definition - catched during construction of FunctionWrapper
let wrapper = FunctionWrapper::<ElectricalConductance>::new(Box::new(ResistanceFunction {}));
assert!(wrapper.is_err());

§Features

If the serde feature is activated, this enum can be serialized and deserialized (as untagged enum). The QuantityFunction trait object is serialized / deserialized using typetag. This is also the reason why QuantityFunction::call returns a DynQuantity<f64> instead of a generic type.

Variants§

§

Constant(T)

Optimization for the common case of a constant quantity. This avoids going through dynamic dispatch when accessing the value.

§

Function(FunctionWrapper<T>)

Catch-all variant for any non-constant behaviour. Arbitrary behaviour can be realized with the contained QuantityFunction trait object, as long as the unit constraint outlined in the VarQuantity docstring is upheld.

Implementations§

Source§

impl<T: IsQuantity> VarQuantity<T>

Source

pub fn get(&self, influencing_factors: &[DynQuantity<f64>]) -> T

Matches against self and either returns the contained value (variant VarQuantity::Constant) or executes the call method of the contained FunctionWrapper (variant VarQuantity::Function).

Source

pub fn try_from_quantity_function<F: QuantityFunction>( fun: F, ) -> Result<Self, UnitsNotEqual>

Creates a new VarQuantity instance if the output Unit of the given function matches that of T.

This is a convenience wrapper around the following steps:

  1. Box fun and cast it to a trait object.
  2. Call FunctionWrapper<T>::new on the boxed trait object.
  3. Wrap the resulting FunctionWrapper<T> in VarQuantity<T>::Function.

In a similar fashion, it is also possible to skip step 1 and use the corresponding TryFrom implementation (unfortunately, this is not possible for the generic F due to colliding blanket implementations in the Rust standard library).

Trait Implementations§

Source§

impl<T: Clone + IsQuantity> Clone for VarQuantity<T>

Source§

fn clone(&self) -> VarQuantity<T>

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<T: Debug + IsQuantity> Debug for VarQuantity<T>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de, T> Deserialize<'de> for VarQuantity<T>

Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<T: IsQuantity> From<T> for VarQuantity<T>

Source§

fn from(value: T) -> Self

Converts to this type from the input type.
Source§

impl<T> Serialize for VarQuantity<T>
where T: Serialize + IsQuantity,

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl<T: IsQuantity> TryFrom<Box<dyn QuantityFunction>> for VarQuantity<T>

Source§

type Error = UnitsNotEqual

The type returned in the event of a conversion error.
Source§

fn try_from(value: Box<dyn QuantityFunction>) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

§

impl<T> Freeze for VarQuantity<T>
where T: Freeze,

§

impl<T> !RefUnwindSafe for VarQuantity<T>

§

impl<T> Send for VarQuantity<T>
where T: Send,

§

impl<T> Sync for VarQuantity<T>
where T: Sync,

§

impl<T> Unpin for VarQuantity<T>
where T: Unpin,

§

impl<T> !UnwindSafe for VarQuantity<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,