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>
impl<T: IsQuantity> VarQuantity<T>
Sourcepub fn get(&self, influencing_factors: &[DynQuantity<f64>]) -> T
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).
Sourcepub fn try_from_quantity_function<F: QuantityFunction>(
fun: F,
) -> Result<Self, UnitsNotEqual>
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:
- Box
funand cast it to a trait object. - Call
FunctionWrapper<T>::newon the boxed trait object. - Wrap the resulting
FunctionWrapper<T>inVarQuantity<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>
impl<T: Clone + IsQuantity> Clone for VarQuantity<T>
Source§fn clone(&self) -> VarQuantity<T>
fn clone(&self) -> VarQuantity<T>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more