Skip to main content

Crate var_quantity

Crate var_quantity 

Source
Expand description

§var_quantity

This crate is an extension of dyn_quantity and provides an interface for defining variable quantities whose value is a (pure) function of other quantities.

Feedback welcome!
Found a bug, missing docs, or have a feature request?
Please open an issue on GitHub.

As an example, let’s consider the eddy current losses in a conductive material which are caused by sinusoidally changing magnetic fields. A simple model could only take the magnetic flux density amplitude into account and a more sophisticated model would also consider the field frequency. Using the VarQuantity wrapper, both models can be used with the same interface:

use var_quantity::{IsQuantityFunction, VarQuantity, QuantityFunction,
                   DynQuantity, PredefUnit, Unit};
use var_quantity::uom::si::{f64::{Power, MagneticFluxDensity, Frequency}, 
    power::watt, magnetic_flux_density::tesla, frequency::hertz};

// The serde annotations are just here because the doctests of this crate use
// the serde feature - they are not needed if the serde feature is disabled.

// Model 1: p = k * B^2
#[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
struct Model1(DynQuantity<f64>);

#[typetag::serde]
impl IsQuantityFunction for Model1 {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let mut b = DynQuantity::new(0.0, PredefUnit::MagneticFluxDensity);
        for factor in conditions.iter() {
            if b.unit == factor.unit {
                b = factor.clone();
            }
        }
        return self.0 * b.powi(2);
    }

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

// Model 2: p = k * f^2 * B^2
#[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
struct Model2(DynQuantity<f64>);

#[typetag::serde]
impl IsQuantityFunction for Model2 {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let mut b = DynQuantity::new(0.0, PredefUnit::MagneticFluxDensity);
        let mut f = DynQuantity::new(0.0, PredefUnit::Frequency);
        for factor in conditions.iter() {
            if b.unit == factor.unit {
                b = factor.clone();
            }
            if f.unit == factor.unit {
                f = factor.clone();
            }
        }
        return self.0 * f.powi(2) * b.powi(2);
    }

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

let k = DynQuantity::new(
    1000.0,
    Unit::from(PredefUnit::Power) / Unit::from(PredefUnit::MagneticFluxDensity).powi(2),
);
let model1: VarQuantity<Power> = VarQuantity::Function(
    QuantityFunction::new(Box::new(Model1(k))).expect("output unit is watt"),
);

let k = DynQuantity::new(
    2.0,
    Unit::from(PredefUnit::Power)
        / Unit::from(PredefUnit::MagneticFluxDensity).powi(2)
        / Unit::from(PredefUnit::Frequency).powi(2),
);
let model2: VarQuantity<Power> = VarQuantity::Function(
    QuantityFunction::new(Box::new(Model2(k))).expect("output unit is watt"),
);

// This function takes a variable quantity, the magnetic flux density and
// the frequency and calculates the losses
fn losses(model: &VarQuantity<Power>, b: MagneticFluxDensity, f: Frequency) -> Power {
    return model.get(&[b.into(), f.into()]);
}

let b = MagneticFluxDensity::new::<tesla>(1.2);
let f = Frequency::new::<hertz>(20.0);

assert_eq!(losses(&model1, b, f).get::<watt>(), 1440.0);
assert_eq!(losses(&model2, b, f).get::<watt>(), 1152.0);

The workflow to use the interface of this crate is as follows:

  • Define the relation between input and output by implementing IsQuantityFunction for the type representing a variable quantity (Model1 and Model2 in the previous example). The implementor is responsible for selecting the right quantities for his model from the give conditions (for unary functions, the crate provides filter_unary_function to simplify this) and also for defining sensible defaults if the needed quantity is not given (in the example above, the default flux density and frequency was defined to zero). As explained in the serialization / deserialization section, the types must not be generic.
  • Create an type instance and box it as a trait object. The trait object approach is necessary for two reasons:
    1. Reduce generic bloat (for example, when a material type is defined using multiple VarQuantity for different properties, this could lead to dozens of generic parameters).
    2. To allow for serialization and deserialization using the typetag crate.
  • Wrap the trait object in a QuantityFunction. Since IsQuantityFunction works with dynamic quantities, it needs to be tested whether the output from IsQuantityFunction::call can be converted to the statically typed quantity T using TryFrom<DynQuantity<f64>> (in the example, the quantity types provided by the uom crate were used). This check is done in the constructor QuantityFunction::new and again in QuantityFunction::call, see the docstring of QuantityFunction.
  • Wrap the QuantityFunction in VarQuantity::Function. The purpose of this enum is to offer an optimization for the important case of a constant quantity via its second variant VarQuantity::Constant. Its VarQuantity::get method either returns the constant quantity directly or forwards to QuantityFunction::call.

§Predefined variable quantity models

Some variable quantity models are very common and therefore provided with this crate. For example, model 1 from the introduction could also be realized using the Polynomial struct from the unary module:

use var_quantity::{DynQuantity, PredefUnit, Unit};
use var_quantity::{unary::Polynomial, VarQuantity, QuantityFunction};
use var_quantity::uom::si::{f64::{Power, MagneticFluxDensity, Frequency}, 
    power::watt, magnetic_flux_density::tesla, frequency::hertz};

// The input vector [a, b, c] is evaluated as ax² + bx + c. Here, b and c are
// zero, but still need to match unit-wise:
// [a] = W/T², [b] = W/T, [c] = W
// The output unit is [c] and the input unit is calculated as [c/b].
// [a] (and additional terms) can then be checked.
let a = DynQuantity::new(1000.0, Unit::from(PredefUnit::Power) / Unit::from(PredefUnit::MagneticFluxDensity).powi(2));
let b = DynQuantity::new(0.0, Unit::from(PredefUnit::Power) / Unit::from(PredefUnit::MagneticFluxDensity));
let c = DynQuantity::new(0.0, PredefUnit::Power);
let polynomial = Polynomial::new(vec![a, b, c]).expect("terms are checked during construction");

let model1: VarQuantity<Power> = VarQuantity::Function(
    QuantityFunction::new(Box::new(polynomial)).expect("output unit is watt"),
);

// This function takes a variable quantity, the magnetic flux density and
// the frequency and calculates the losses
fn losses(model: &VarQuantity<Power>, b: MagneticFluxDensity, f: Frequency) -> Power {
    return model.get(&[b.into(), f.into()]);
}

let b = MagneticFluxDensity::new::<tesla>(1.2);
let f = Frequency::new::<hertz>(20.0);

assert_eq!(losses(&model1, b, f).get::<watt>(), 1440.0);

For a full list of available models, see the following modules:

  • unary : Models representing unary functions (single input).

§Serialization and deserialization

The serde integration is gated behind the serde feature flag.

All structs / enums in this crate implement serialization and deserialization. See the docstrings of the individual types for details. The trait objects stored within QuantityFunction are handled via typetag, which is why the the implementors of IsQuantityFunction cannot be generic.

§Documentation

The full API documentation is available at https://docs.rs/var_quantity/0.3.1/var_quantity/.

Re-exports§

pub use typetag;
pub use dyn_quantity;

Modules§

error
This module contains the various errors which can occur when dealing with DynQuantity and Unit.
quantity
This module contains the DynQuantity struct and code for:
unary
This module contains unary functions which implement IsQuantityFunction.
unit
This module contains the Unit struct and supporting code. See the documentation string of Unit for more information.
uom
Units of measurement is a crate that does automatic type-safe zero-cost dimensional analysis. You can create your own systems or use the pre-built International System of Units (SI) which is based on the International System of Quantities (ISQ) and includes numerous quantities (length, mass, time, …) with conversion factors for even more numerous measurement units (meter, kilometer, foot, mile, …). No more crashing your climate orbiter!

Structs§

ClampedQuantity
A wrapper around a type implementing IsQuantityFunction trait object which clamps the output of IsQuantityFunction::call using the provided upper and lower limits.
DynQuantity
This type represents a physical quantity via its numerical value and a unit of measurement (field exponents). The unit of measurement is not defined via the type system, but rather via the values of the Unit. This means that the unit of measurement is not fixed at compile time, but can change dynamically at runtime.
NotConvertibleFromComplexF64
Error describing a failed attempt to convert a Complex<f64> into the type V of DynQuantity<V>.
ParseError
Error representing a failed attempt to parse a string into a DynQuantity.
QuantityFunction
A thin wrapper around a Box<dyn IsQuantityFunction> trait object which provides some type checks for usage in VarQuantity.
RootError
Error representing a failed attempt to calculate the nth root of an Unit.
Unit
Struct representing a unit of measurement in the SI system via the exponents of the base units. The unit is purely defined by the values of its fields, meaning that it can change at runtime. The struct implements basic arithmetic functions such as multiplication and division (via the Mul, MulAssign, Div, DivAssign traits), exponentiation (Unit::powi) and a fallible version of root calculation (Unit::try_nthroot).
UnitsNotEqual
Error representing unequality of units.

Enums§

ConversionError
Error describing a failed attempt to convert between different types representing quantities.
ParseErrorReason
The varying reasons parsing a string to a DynQuantity can fail. This struct is part of ParseError, which contains the information where the parsing failed.
PredefUnit
An enum representing predefined Units.
VarQuantity
A quantity whose value can either be constant or a function of one or more other quantities.

Constants§

SERIALIZE_WITH_UNITS
A thread-local, static variable which enables / disables serialization of quantities with or without units. It is used within the functions serialize_quantity, serialize_opt_quantity, serialize_angle and serialize_opt_angle as a thread-local context to decide whether a quantity should be serialized with or without its units. By default, its value is false, meaning that quantities are serialized without their units. The serialize_with_units function sets it temporarily to true, then performs the actual serialization, and afterwards resets it to false again (return to default behaviour).

Traits§

IsQuantity
This is a marker trait which defines trait bounds for all types T which can be used as “quantities” in VarQuantity<T>. It does not provide any methods and is auto-implemented for all T fulfilling the bounds, hence it is not necessary to ever import this trait. It is only public to make compiler error messages more helpful.
IsQuantityFunction
Trait used to construct variable quantities whose value is a (pure) function of other quantities.
UnitFromType
A trait to derive Unit from a type. This trait bridges the gap between (external) types representing physical quantities (such as e.g. the Quantity type from the uom crate) and Unit.

Functions§

deserialize_angle
Deserializes an angle from a valid DynQuantity representation (see docstring of DynQuantity). The output value is always in radians.
deserialize_opt_angle
Like deserialize_angle, but deserializes into an Option<f64> instead of a f64.
deserialize_opt_quantity
Like deserialize_quantity, but deserializes into an Option<T> instead of a T implementing TryFrom<DynQuantity>.
deserialize_opt_vec_of_quantities
Like deserialize_vec_of_quantities, but deserializes into an Option<Vec<T>> instead of a Vec<T>.
deserialize_quantity
Deserializes a type T implementing TryFrom<DynQuantity> from a valid DynQuantity representation (see docstring of DynQuantity).
deserialize_vec_of_quantities
Deserializes a vector of T which implements TryFrom<DynQuantity> from:
filter_unary_function
A helper function which filters the conditions for a quantity with the type match_for. If a matching quantity is found, it is used as argument for F and the result is returned. Otherwise, the result of G() is returned.
serialize_angle
Enables serialization of an angle into a string containing both the value and the “rad” unit.
serialize_opt_angle
Like serialize_angle, but serializes an [&Option<T>] instead of a &T implementing Into<DynQuantity>.
serialize_opt_quantity
Like serialize_quantity, but serializes an [&Option<T>] instead of a &T implementing Into<DynQuantity>.
serialize_quantity
Enables serialization of a quantity (any type implementing Into<DynQuantity>) into a string containing both the value and the units.
serialize_with_units
A wrapper around a serialization function / closure which enables serialization with units.