Skip to main content

var_quantity/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4use std::marker::PhantomData;
5
6use dyn_quantity::{DynQuantity, Unit, UnitFromType, UnitsNotEqual};
7
8use num::Complex;
9#[cfg(feature = "serde")]
10pub use typetag;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15pub mod unary;
16
17/**
18This is a marker trait which defines trait bounds for all types `T` which can
19be used as "quantities" in [`VarQuantity<T>`]. It does not provide any methods
20and is auto-implemented for all `T` fulfilling the bounds, hence it is not
21necessary to ever import this trait. It is only public to make compiler error
22messages more helpful.
23 */
24pub trait IsQuantity:
25    UnitFromType + TryFrom<DynQuantity<Complex<f64>>> + Clone + std::fmt::Debug
26{
27}
28
29impl<T> IsQuantity for T where
30    T: UnitFromType + TryFrom<DynQuantity<Complex<f64>>> + Clone + std::fmt::Debug
31{
32}
33
34/**
35Trait used to construct variable quantities whose value is a (pure) function of
36other quantities.
37
38Implementing this trait for a type marks it as being a variable quantity, whose
39value can change under the influence of other quantities. For example, a
40resistance can be a function of temperature:
41
42```
43use dyn_quantity::{DynQuantity, PredefUnit, Unit};
44use var_quantity::QuantityFunction;
45
46// The serde annotations are just here because the doctests of this crate use
47// the serde feature - they are not needed if the serde feature is disabled.
48#[derive(Clone, serde::Deserialize, serde::Serialize)]
49struct Resistance;
50
51// Again, the macro annotation is just here because of the serde feature
52#[typetag::serde]
53impl QuantityFunction for Resistance {
54    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
55        let mut temperature = 0.0;
56        let temperature_unit: Unit = PredefUnit::Temperature.into();
57        for f in influencing_factors.iter() {
58            if f.unit == temperature_unit {
59                temperature = f.value;
60                break;
61            }
62        }
63        return DynQuantity::new(1.0 + temperature / 100.0, PredefUnit::ElectricResistance);
64    }
65}
66
67// Influencing factors
68let infl1 = &[DynQuantity::new(6.0, PredefUnit::ElectricCurrent)];
69let infl2 = &[
70    DynQuantity::new(6.0, PredefUnit::ElectricCurrent),
71    DynQuantity::new(20.0, PredefUnit::Temperature),
72];
73
74let resistance = Resistance {};
75
76assert_eq!(DynQuantity::new(1.0, PredefUnit::ElectricResistance), resistance.call(&[]));
77assert_eq!(DynQuantity::new(1.0, PredefUnit::ElectricResistance), resistance.call(infl1));
78assert_eq!(DynQuantity::new(1.2, PredefUnit::ElectricResistance), resistance.call(infl2));
79```
80
81An important constraint which unfortunately cannot be covered by the type
82system is that the [`DynQuantity<f64>`] returned by [`QuantityFunction::call`]
83must always have the same [`Unit`] field. See the [Features](#features) section
84and the docstring of [`VarQuantity`] for details.
85
86# Features
87
88When the `serde` feature is enabled, any type implementing [`QuantityFunction`]
89can be serialized / deserialized as a trait object using the
90[typetag](https://docs.rs/typetag/latest/typetag/) crate. This has the following
91implications:
92- [`QuantityFunction::call`] cannot return a generic type (limitation of
93typetag), which is why the dynamic [`DynQuantity`] type is used.
94- When implementing [`QuantityFunction`] for a type, the `#[typetag::serde]`
95annotation must be applied to the `impl` block (see example).
96
97In turn, this feature enables serialization / deserialization of [`VarQuantity`]
98without the need to specify the underlying function type in advance.
99 */
100#[cfg_attr(feature = "serde", typetag::serde)]
101pub trait QuantityFunction: dyn_clone::DynClone + Sync + Send + std::any::Any {
102    /**
103    Returns a quantity as a function of `influencing_factors`. See the
104    [`QuantityFunction`] trait docstring for examples.
105    */
106    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64>;
107}
108
109/**
110A thin wrapper around a `Box<dyn QuantityFunction>` trait object which provides
111some type checks for usage in [`VarQuantity`].
112
113This struct wraps a `Box<dyn QuantityFunction>` so it can be used in the
114[`VarQuantity::Function`] enum variant. As explained in the [`QuantityFunction`]
115docstring, the unit of the [`DynQuantity`] returned by [`QuantityFunction::call`]
116must always be the same. Even though this can unfortunately not be represented
117by the type system for reasons outlined in the trait docstring, this wrapper
118provides some checks to reduce the likelihood of wrong units:
119- When constructing the wrapper via [`FunctionWrapper::new`], it runs
120[`QuantityFunction::call`] once with an empty slice and checks that the output unit
121matches that of [`T::unit_from_type`](UnitFromType::unit_from_type). If that is
122not the case, the construction fails and an error is returned.
123- When calling the underlying function via [`FunctionWrapper::call`], it tries
124to convert the [`DynQuantity<f64>`] delivered from [`QuantityFunction::call`]
125into `T`. If that fails, the implementation of [`QuantityFunction`] violates
126the requirement outlined in the trait documentation. This is a bug, hence the
127function panics.
128
129This struct has the same memory representation as [`Box<dyn QuantityFunction>`].
130The underlying trait object can be accessed directly via [`FunctionWrapper::inner`].
131
132# Features
133
134This struct can be serialized / deserialized if the `serde` feature is enabled.
135Since it is just a wrapper around a `Box<dyn QuantityFunction>` trait object,
136it serializes directly to the representation of that object and deserializes
137directly from it (it is["transparent"](https://serde.rs/container-attrs.html#transparent)).
138 */
139pub struct FunctionWrapper<T: IsQuantity> {
140    function: Box<dyn QuantityFunction>,
141    phantom: PhantomData<T>,
142}
143
144impl<T: IsQuantity> FunctionWrapper<T> {
145    /**
146    Creates a new instance of `Self` and performs a type safety check by running
147    the [`QuantityFunction::call`] of `function` with an empty slice as
148    `influencing_factors`. The unit of the resulting [`DynQuantity`] is then
149    compared to that created by [`T::unit_from_type`](UnitFromType::unit_from_type).
150    If they don't match, an error is returned. See the docstring of
151    [`FunctionWrapper`] for more.
152
153    # Examples
154
155    ```
156    use dyn_quantity::{DynQuantity, PredefUnit, Unit};
157    use var_quantity::{QuantityFunction, FunctionWrapper};
158    use uom::si::f64::{ElectricalResistance, ElectricCurrent};
159
160    // The serde annotations are just here because the doctests of this crate use
161    // the serde feature - they are not needed if the serde feature is disabled.
162    #[derive(Clone, serde::Deserialize, serde::Serialize)]
163    struct Resistance;
164
165    // Again, the macro annotation is just here because of the serde feature
166    #[typetag::serde]
167    impl QuantityFunction for Resistance {
168        fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
169            return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
170        }
171    }
172
173    let resistance = Resistance {};
174
175    // The Resistance struct always returns an electric resistance. Hence the
176    // type check fails for other types
177    assert!(FunctionWrapper::<ElectricalResistance>::new(Box::new(resistance.clone())).is_ok());
178    assert!(FunctionWrapper::<f64>::new(Box::new(resistance.clone())).is_err());
179    assert!(FunctionWrapper::<ElectricCurrent>::new(Box::new(resistance.clone())).is_err());
180    ```
181     */
182    pub fn new(function: Box<dyn QuantityFunction>) -> Result<Self, UnitsNotEqual> {
183        // Call the function w/o any arguments and make sure the returned
184        // DynQuantity<f64> is convertible to T
185        let actual = function.call(&[]).unit;
186        let expected = T::unit_from_type();
187        if actual != expected {
188            return Err(UnitsNotEqual(expected, actual));
189        }
190        return Ok(Self {
191            function,
192            phantom: PhantomData,
193        });
194    }
195
196    /**
197    Forwards the input to the [`QuantityFunction::call`] method of the wrapped
198    trait object and asserts that the returned value can be converted to `T`.
199    If that is not the case, the constraint outlined in the docstring of
200    [`FunctionWrapper`] is not fulfilled and the code is invalid, therefore
201    the function panics.
202
203    # Examples
204
205    This is a valid implementation of [`IsQuantity`]: [`Unit`] is always the
206    same regardless of input.
207    ```
208    use dyn_quantity::{DynQuantity, PredefUnit, Unit};
209    use var_quantity::{QuantityFunction, FunctionWrapper};
210    use uom::si::electrical_resistance::ohm;
211    use uom::si::f64::{ElectricalResistance};
212
213    // The serde annotations are just here because the doctests of this crate use
214    // the serde feature - they are not needed if the serde feature is disabled.
215    #[derive(Clone, serde::Deserialize, serde::Serialize)]
216    struct Resistance;
217
218    // Again, the macro annotation is just here because of the serde feature
219    #[typetag::serde]
220    impl QuantityFunction for Resistance {
221        fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
222            return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
223        }
224    }
225
226    let wrapped_resistance = FunctionWrapper::<ElectricalResistance>::new(Box::new(Resistance {})).expect("units match");
227    assert_eq!(ElectricalResistance::new::<ohm>(1.0), wrapped_resistance.call(&[1.0.into()]));
228    ```
229
230    This is an invalid (and nonsensical) implementation of [`QuantityFunction`]
231    where the output unit changes with the number of arguments:
232    ```should_panic
233    use dyn_quantity::{DynQuantity, PredefUnit, Unit};
234    use var_quantity::{QuantityFunction, FunctionWrapper};
235    use uom::si::f64::{ElectricalResistance};
236
237    // The serde annotations are just here because the doctests of this crate use
238    // the serde feature - they are not needed if the serde feature is disabled.
239    #[derive(Clone, serde::Deserialize, serde::Serialize)]
240    struct Resistance;
241
242    // Again, the macro annotation is just here because of the serde feature
243    #[typetag::serde]
244    impl QuantityFunction for Resistance {
245        fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
246            if influencing_factors.len() == 0 {
247                return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
248            } else {
249                return DynQuantity::new(1.0, PredefUnit::None);
250            }
251        }
252    }
253
254    // Construction succeeds since the test call is done with an empty slice
255    let wrapped_resistance = FunctionWrapper::<ElectricalResistance>::new(Box::new(Resistance {})).expect("units match");
256
257    // ... but calling with a quantity results in a panic
258    let _ = wrapped_resistance.call(&[DynQuantity::new(1.0, PredefUnit::None)]);
259    ```
260     */
261    pub fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> T {
262        match T::try_from(self.function.call(influencing_factors).into()) {
263            Ok(val) => val,
264            Err(_) => {
265                panic!(
266                    "conversion from DynQuantity<f64> to T failed for input {:?}.\n
267                    This means that the QuantityFunction trait object returns
268                    different DynQuantity<f64> depending on the input, which
269                    is a bug in the implementation of the trait object.",
270                    influencing_factors
271                )
272            }
273        }
274    }
275
276    /**
277    Returns the underlying [`QuantityFunction`] trait object.
278     */
279    pub fn inner(&self) -> &dyn QuantityFunction {
280        return &*self.function;
281    }
282}
283
284impl<T: IsQuantity> std::fmt::Debug for FunctionWrapper<T> {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        f.debug_tuple("FunctionWrapper").finish()
287    }
288}
289
290impl<T: IsQuantity> Clone for FunctionWrapper<T> {
291    fn clone(&self) -> Self {
292        return Self {
293            function: dyn_clone::clone_box(&*self.function),
294            phantom: PhantomData,
295        };
296    }
297}
298
299/**
300A quantity whose value can either be constant or a function of one or more other
301quantities.
302
303The value of (physical) quantities can depend on the values of other quantities.
304This is often the case for quantities representing physical properties such as
305e.g. the electric resistance of a conductor. This enum serves as a general
306container for such quantities with the variant [`VarQuantity::Constant`] being
307an optimization for the important case of a constant quantitity and with the
308variant [`VarQuantity::Function`] covering all other cases via a
309[`QuantityFunction`] trait object (wrapped in [`FunctionWrapper`]). Due to the
310generic design, it can also be used for dimensionless quantities which can be
311represented by a simple [`f64`].
312
313The value of the underlying quantity can be read out via the [`VarQuantity::get`]
314method. It takes a slice of [`DynQuantity`] representing influencing factors,
315for example the temperature in case of a resistance. If the enum variant is
316constant, the value field is simply cloned, otherwise the [`QuantityFunction::call`]
317function is called. This returns a [`DynQuantity<f64>`], which must be convertable
318via [`TryFrom`] to `T` (enforced by trait bound). This dynamic approach is
319chosen to make this enum serializable / deserializable (see section
320[Features](#features)).
321
322Even though the conversion from [`DynQuantity<f64>`] to `T` is fallible from the
323perspective of the type system, in actual implementations it must be infallible
324(i.e. the conversion must always succeed). This is done so `T` can be a
325statically typed physical quantity (e.g. from the [uom](https://crates.io/crates/uom)
326library), for which [`From<DynQuantity<f64>>`] can obviously not be implemented.
327The conversion is checked once when constructing a [`FunctionWrapper`] from a
328[`QuantityFunction`] trait object by calling [`QuantityFunction::call`] with
329`influencing_factors = &[]`, but of course it is impossible to test all
330potential values for `influencing_factors`.
331
332It is therefore up to the provider of the trait object to make sure that the
333[`DynQuantity<f64>`] returned by [`QuantityFunction::call`] always has the same
334[`Unit`]. If this is not the case, the trait object has a bug and the program
335has entered an invalid state, resulting in a [`panic!`].
336
337# Examples
338
339## f64 and statically typed physical quantities
340
341This example shows how [`VarQuantity`] integrates with both [`f64`] and
342[uom](https://crates.io/crates/uom) [`Quantity`](https://docs.rs/uom/latest/uom/si/struct.Quantity.html).
343```
344use dyn_quantity::{DynQuantity, PredefUnit, Unit};
345use uom::si::electrical_resistance::ohm;
346use uom::si::f64::ElectricalResistance;
347use var_quantity::{FunctionWrapper, QuantityFunction, VarQuantity};
348
349// =============================================================================
350// Constant quantity with f64
351let qt_const = VarQuantity::<f64>::Constant(2.0);
352
353// Influencing factors
354let infl1 = &[DynQuantity::new(6.0, PredefUnit::ElectricCurrent)];
355let infl2 = &[
356    DynQuantity::new(6.0, PredefUnit::ElectricCurrent),
357    DynQuantity::new(20.0, PredefUnit::Temperature),
358];
359
360// Since this is a constant quantity, it returns always 2 regardless of the input.
361assert_eq!(2.0, qt_const.get(&[]));
362assert_eq!(2.0, qt_const.get(infl1));
363assert_eq!(2.0, qt_const.get(infl2));
364
365// =============================================================================
366// Variable quantity
367
368// A variable resistance: The resistance is 1 + temperature / 100.
369// For the test, the serde feature is enabled, hence it is necessary to
370// implement serialization and deserialization as well as #[typetag::serde].
371// This is not needed if the feature is not enabled.
372#[derive(Clone, serde::Deserialize, serde::Serialize)]
373struct ResistanceFunction;
374
375#[typetag::serde]
376impl QuantityFunction for ResistanceFunction {
377    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
378        let mut temperature = 0.0;
379        let temperature_unit: Unit = PredefUnit::Temperature.into();
380        for f in influencing_factors.iter() {
381            if f.unit == temperature_unit {
382                temperature = f.value;
383                break;
384            }
385        }
386        return DynQuantity::new(1.0 + temperature / 100.0, PredefUnit::ElectricResistance);
387    }
388}
389
390let wrapper = FunctionWrapper::new(Box::new(ResistanceFunction {})).expect("type check successfull");
391let qt_var = VarQuantity::<ElectricalResistance>::Function(wrapper);
392
393// Input infl2 contains a temperature and therefore influences the resistance.
394assert_eq!(ElectricalResistance::new::<ohm>(1.0), qt_var.get(&[]));
395assert_eq!(ElectricalResistance::new::<ohm>(1.0), qt_var.get(infl1));
396assert_eq!(ElectricalResistance::new::<ohm>(1.2), qt_var.get(infl2));
397```
398
399## Unit mismatch
400
401This example shows a violation of the assumption that the [`DynQuantity`] returned
402by the [`QuantityFunction`] trait object is convertible to `T`.
403```
404use dyn_quantity::{DynQuantity, PredefUnit};
405use uom::si::electrical_conductance::siemens;
406use uom::si::f64::{ElectricalResistance, ElectricalConductance};
407use var_quantity::{FunctionWrapper, QuantityFunction, VarQuantity};
408
409#[derive(Clone, serde::Deserialize, serde::Serialize)]
410struct ResistanceFunction;
411
412#[typetag::serde]
413impl QuantityFunction for ResistanceFunction {
414    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
415        return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
416    }
417}
418
419// Mismatch in type definition - catched during construction of FunctionWrapper
420let wrapper = FunctionWrapper::<ElectricalConductance>::new(Box::new(ResistanceFunction {}));
421assert!(wrapper.is_err());
422```
423
424# Features
425
426If the `serde` feature is activated, this enum can be serialized and
427deserialized (as untagged enum). The [`QuantityFunction`] trait object is
428serialized / deserialized using [typetag](https://docs.rs/typetag/latest/typetag/).
429This is also the reason why [`QuantityFunction::call`] returns a
430[`DynQuantity<f64>`] instead of a generic type.
431 */
432#[derive(Clone, Debug)]
433#[cfg_attr(feature = "serde", derive(Serialize))]
434#[cfg_attr(feature = "serde", serde(untagged))]
435pub enum VarQuantity<T: IsQuantity> {
436    /**
437    Optimization for the common case of a constant quantity. This avoids going
438    through dynamic dispatch when accessing the value.
439     */
440    Constant(T),
441    /**
442    Catch-all variant for any non-constant behaviour. Arbitrary behaviour
443    can be realized with the contained [`QuantityFunction`] trait object, as
444    long as the unit constraint outlined in the [`VarQuantity`] docstring is
445    upheld.
446     */
447    Function(FunctionWrapper<T>),
448}
449
450impl<T: IsQuantity> VarQuantity<T> {
451    /**
452    Matches against `self` and either returns the contained value (variant
453    [`VarQuantity::Constant`]) or executes the call method of the contained
454    [`FunctionWrapper`] (variant [`VarQuantity::Function`]).
455    */
456    pub fn get(&self, influencing_factors: &[DynQuantity<f64>]) -> T {
457        match self {
458            Self::Constant(val) => val.clone(),
459            Self::Function(fun) => fun.call(influencing_factors),
460        }
461    }
462
463    /**
464    Creates a new [`VarQuantity`] instance if the output [`Unit`] of the given
465    function matches that of `T`.
466
467    This is a convenience wrapper around the following steps:
468    1) Box `fun` and cast it to a trait object.
469    2) Call [`FunctionWrapper<T>::new`] on the boxed trait object.
470    3) Wrap the resulting [`FunctionWrapper<T>`] in [`VarQuantity<T>::Function`].
471
472    In a similar fashion, it is also possible to skip step 1 and use the
473    corresponding [`TryFrom`] implementation (unfortunately, this is not
474    possible for the generic `F` due to colliding blanket implementations in
475    the Rust standard library).
476    */
477    pub fn try_from_quantity_function<F: QuantityFunction>(fun: F) -> Result<Self, UnitsNotEqual> {
478        let boxed: Box<dyn QuantityFunction> = Box::new(fun);
479        return boxed.try_into();
480    }
481}
482
483impl<T: IsQuantity> TryFrom<Box<dyn QuantityFunction>> for VarQuantity<T> {
484    type Error = UnitsNotEqual;
485
486    fn try_from(value: Box<dyn QuantityFunction>) -> Result<Self, Self::Error> {
487        let wrapper = FunctionWrapper::new(value)?;
488        return Ok(Self::Function(wrapper));
489    }
490}
491
492impl<T: IsQuantity> From<T> for VarQuantity<T> {
493    fn from(value: T) -> Self {
494        return Self::Constant(value);
495    }
496}
497
498#[cfg(feature = "serde")]
499mod serde_impl {
500    use serde::de::DeserializeOwned;
501
502    use super::*;
503
504    impl<T: IsQuantity> Serialize for FunctionWrapper<T> {
505        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
506        where
507            S: serde::Serializer,
508        {
509            self.function.serialize(serializer)
510        }
511    }
512
513    impl<'de, T: IsQuantity> serde::Deserialize<'de> for FunctionWrapper<T> {
514        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
515        where
516            D: serde::Deserializer<'de>,
517        {
518            let v = <Box<dyn QuantityFunction>>::deserialize(deserializer)?;
519            FunctionWrapper::new(v).map_err(serde::de::Error::custom)
520        }
521    }
522
523    impl<'de, T> serde::Deserialize<'de> for VarQuantity<T>
524    where
525        T: DeserializeOwned + IsQuantity,
526        <T as TryFrom<DynQuantity<Complex<f64>>>>::Error: std::fmt::Display,
527    {
528        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
529        where
530            D: serde::Deserializer<'de>,
531        {
532            use std::str::FromStr;
533
534            #[derive(deserialize_untagged_verbose_error::DeserializeUntaggedVerboseError)]
535            enum InnerOrString<T> {
536                Inner(T),
537                #[cfg(feature = "from_str")]
538                String(String),
539            }
540
541            let content: serde_value::Value = serde::Deserialize::deserialize(deserializer)?;
542
543            // Try to deserialize as a quantity. If that fails, try to deserialize as a
544            // function trait object
545            match InnerOrString::<T>::deserialize(serde_value::ValueDeserializer::<D::Error>::new(
546                content.clone(),
547            )) {
548                Ok(number_or_string) => match number_or_string {
549                    InnerOrString::Inner(q) => return Ok(VarQuantity::Constant(q)),
550                    InnerOrString::String(s) => {
551                        let dq = DynQuantity::<Complex<f64>>::from_str(&s)
552                            .map_err(serde::de::Error::custom)?;
553                        let q = T::try_from(dq).map_err(serde::de::Error::custom)?;
554                        return Ok(VarQuantity::Constant(q));
555                    }
556                },
557                Err(_) => {
558                    let wrapper =
559                        FunctionWrapper::deserialize(
560                            serde_value::ValueDeserializer::<D::Error>::new(content.clone()),
561                        )?;
562                    return Ok(VarQuantity::Function(wrapper));
563                }
564            }
565        }
566    }
567}
568
569/**
570A wrapper around a type implementing [`QuantityFunction`] trait object which
571clamps the output of [`QuantityFunction::call`] using the provided upper and
572lower limits.
573
574If the `serde` feature is not activated, it implements [`QuantityFunction`]
575in a generic manner and can therefore be used in a [`FunctionWrapper`]. If
576`serde` is activated, it is unfortately not possible to provide a generic
577implementation due to the macro `#[typetag::serde]` not being able to deal with
578generics. As a workaround, it is possible to provide a simple custom
579implementation for each concrete type in your own crate:
580
581```ignore
582#[cfg_attr(feature = "serde", typetag::serde)]
583impl QuantityFunction for ClampedQuantity<YourTypeHere> {
584    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
585        return self.call_clamped(influencing_factors);
586    }
587}
588```
589
590This approach is used for all the implementors of [`QuantityFunction`] provided
591with this crate.
592 */
593#[derive(Clone)]
594#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
595pub struct ClampedQuantity<T: QuantityFunction> {
596    upper_limit: f64,
597    lower_limit: f64,
598    function: T,
599}
600
601impl<T: QuantityFunction> ClampedQuantity<T> {
602    /**
603    Checks if `upper_limit >= lower_limit` and returns a new instance of
604    [`ClampedQuantity`] if true.
605    */
606    pub fn new(upper_limit: f64, lower_limit: f64, function: T) -> Result<Self, &'static str> {
607        if upper_limit < lower_limit {
608            return Err("upper limit must not be smaller than the lower limit");
609        }
610        return Ok(Self {
611            upper_limit,
612            lower_limit,
613            function,
614        });
615    }
616
617    /**
618    Returns the underlying [`QuantityFunction`].
619     */
620    pub fn inner(&self) -> &T {
621        return &self.function;
622    }
623
624    /**
625    Returns the underlying [`QuantityFunction`] as a trait object.
626     */
627    pub fn inner_dyn(&self) -> &dyn QuantityFunction {
628        return &self.function;
629    }
630
631    /// Returns the upper limit.
632    pub fn upper_limit(&self) -> f64 {
633        return self.upper_limit;
634    }
635
636    /// Returns the lower limit.
637    pub fn lower_limit(&self) -> f64 {
638        return self.lower_limit;
639    }
640
641    /**
642    Clamps the output value of `T::call` using the provided upper and lower
643    limits. This function is mainly here to simplify custom [`QuantityFunction`]
644    implementations, see the [`ClampedQuantity`] docstring.
645     */
646    pub fn call_clamped(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
647        let mut dyn_quantity = self.function.call(influencing_factors);
648        dyn_quantity.value = dyn_quantity.value.clamp(self.lower_limit, self.upper_limit);
649        return dyn_quantity;
650    }
651}
652
653#[cfg(not(feature = "serde"))]
654impl<T: QuantityFunction + Clone> QuantityFunction for ClampedQuantity<T> {
655    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
656        return self.call_clamped(influencing_factors);
657    }
658}
659
660/**
661A helper function which filters the `influencing_factors` for a quantity with
662the type `match_for`. If a matching quantity is found, it is used as argument
663for `F` and the result is returned. Otherwise, the result of `G()` is returned.
664
665The main purpose of this function is to simplify writing unary functions. For
666example, the [`QuantityFunction::call`] implementation of a linear function
667can look like this:
668
669```
670use dyn_quantity::{DynQuantity, Unit};
671use var_quantity::{filter_unary_function, QuantityFunction};
672
673// The serde annotations are just here because the doctests of this crate use
674// the serde feature - they are not needed if the serde feature is disabled.
675#[derive(Clone, serde::Serialize, serde::Deserialize)]
676pub struct Linear {
677    slope: f64,
678    base_value: f64,
679}
680
681// Again, the macro annotation is just here because of the serde feature
682#[cfg_attr(feature = "serde", typetag::serde)]
683impl QuantityFunction for Linear {
684    fn call(&self, influencing_factors: &[DynQuantity<f64>]) -> DynQuantity<f64> {
685        return filter_unary_function(
686            influencing_factors,
687            Unit::default(),
688            |input| {
689                DynQuantity::new(
690                    self.base_value + self.slope * input.value,
691                    Unit::default(),
692                )
693            },
694            || DynQuantity::new(
695                    self.base_value,
696                    Unit::default(),
697                ),
698        );
699    }
700}
701```
702 */
703pub fn filter_unary_function<F, G>(
704    influencing_factors: &[DynQuantity<f64>],
705    match_for: Unit,
706    with_matched: F,
707    no_match: G,
708) -> DynQuantity<f64>
709where
710    F: FnOnce(DynQuantity<f64>) -> DynQuantity<f64>,
711    G: FnOnce() -> DynQuantity<f64>,
712{
713    for iq in influencing_factors {
714        if iq.unit == match_for {
715            return with_matched(iq.clone());
716        }
717    }
718    no_match()
719}