var_quantity 0.3.3

Provides an interface for defining variable quantities whose value depends on that of other quantities.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]

use std::{marker::PhantomData, ops::Deref};

pub use dyn_quantity::*;

use num::Complex;

#[cfg(feature = "serde")]
pub use typetag;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

pub use dyn_quantity;
pub mod unary;

/**
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.
 */
pub trait IsQuantity:
    UnitFromType
    + TryFrom<DynQuantity<Complex<f64>>>
    + Clone
    + std::fmt::Debug
    + Into<DynQuantity<Complex<f64>>>
{
}

impl<T> IsQuantity for T where
    T: UnitFromType
        + TryFrom<DynQuantity<Complex<f64>>>
        + Clone
        + std::fmt::Debug
        + Into<DynQuantity<Complex<f64>>>
{
}

/**
Trait used to construct variable quantities whose value is a (pure) function of
other quantities.

Implementing this trait for a type marks it as being a variable quantity, whose
value can change under the influence of other quantities. For example, a
resistance can be a function of temperature:

```
use var_quantity::{DynQuantity, PredefUnit, Unit, IsQuantityFunction};

// 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.
#[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
struct Resistance;

// Again, the macro annotation is just here because of the serde feature
#[typetag::serde]
impl IsQuantityFunction for Resistance {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let mut temperature = 0.0;
        let temperature_unit: Unit = PredefUnit::Temperature.into();
        for f in conditions.iter() {
            if f.unit == temperature_unit {
                temperature = f.value;
                break;
            }
        }
        return DynQuantity::new(1.0 + temperature / 100.0, PredefUnit::ElectricResistance);
    }

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

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

let resistance = Resistance {};

assert_eq!(DynQuantity::new(1.0, PredefUnit::ElectricResistance), resistance.call(&[]));
assert_eq!(DynQuantity::new(1.0, PredefUnit::ElectricResistance), resistance.call(infl1));
assert_eq!(DynQuantity::new(1.2, PredefUnit::ElectricResistance), resistance.call(infl2));
```

An important constraint which unfortunately cannot be covered by the type
system is that the [`DynQuantity<f64>`] returned by [`IsQuantityFunction::call`]
must always have the same [`Unit`] field. See the [Features](#features) section
and the docstring of [`VarQuantity`] for details.

# Features

When the `serde` feature is enabled, any type implementing [`IsQuantityFunction`]
can be serialized / deserialized as a trait object using the
[typetag](https://docs.rs/typetag/latest/typetag/) crate. This has the following
implications:
- [`IsQuantityFunction::call`] cannot return a generic type (limitation of
typetag), which is why the dynamic [`DynQuantity`] type is used.
- When implementing [`IsQuantityFunction`] for a type, the `#[typetag::serde]`
annotation must be applied to the `impl` block (see example).

In turn, this feature enables serialization / deserialization of [`VarQuantity`]
without the need to specify the underlying function type in advance.
 */
#[cfg_attr(feature = "serde", typetag::serde)]
pub trait IsQuantityFunction: dyn_clone::DynClone + Sync + Send + std::any::Any {
    /**
    Returns a quantity as a function of `conditions`. See the
    [`IsQuantityFunction`] trait docstring for examples.
    */
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64>;

    /**
    Returns `true` if `self` and `other` are identical and `false` otherwise.

    For a [`Sized`] type which implements [`PartialEq`], this function can be
    implemented as:

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

    If `Self` cannot be compared, this function should simply return `false`.
     */
    fn dyn_eq(&self, other: &dyn IsQuantityFunction) -> bool;
}

/**
A thin wrapper around a `Box<dyn IsQuantityFunction>` trait object which provides
some type checks for usage in [`VarQuantity`].

This struct wraps a `Box<dyn IsQuantityFunction>` so it can be used in the
[`VarQuantity::Function`] enum variant. As explained in the [`IsQuantityFunction`]
docstring, the unit of the [`DynQuantity`] returned by [`IsQuantityFunction::call`]
must always be the same. Even though this can unfortunately not be represented
by the type system for reasons outlined in the trait docstring, this wrapper
provides some checks to reduce the likelihood of wrong units:
- When constructing the wrapper via [`QuantityFunction::new`], it runs
[`IsQuantityFunction::call`] once with an empty slice and checks that the output unit
matches that of [`T::unit_from_type`](UnitFromType::unit_from_type). If that is
not the case, the construction fails and an error is returned.
- When calling the underlying function via [`QuantityFunction::call`], it tries
to convert the [`DynQuantity<f64>`] delivered from [`IsQuantityFunction::call`]
into `T`. If that fails, the implementation of [`IsQuantityFunction`] violates
the requirement outlined in the trait documentation. This is a bug, hence the
function panics.

This struct has the same memory representation as [`Box<dyn IsQuantityFunction>`].
The underlying trait object can be retrieved directly via
[`QuantityFunction::into_inner`] or accessed via [`AsRef::as_ref`]
and [`Deref::deref`].

# Features

This struct can be serialized / deserialized if the `serde` feature is enabled.
Since it is just a wrapper around a `Box<dyn IsQuantityFunction>` trait object,
it serializes directly to the representation of that object and deserializes
directly from it (it is["transparent"](https://serde.rs/container-attrs.html#transparent)).
 */
pub struct QuantityFunction<T: IsQuantity> {
    function: Box<dyn IsQuantityFunction>,
    phantom: PhantomData<T>,
}

impl<T: IsQuantity> QuantityFunction<T> {
    /**
    Creates a new instance of `Self` and performs a type safety check by running
    the [`IsQuantityFunction::call`] of `function` with an empty slice as
    `conditions`. The unit of the resulting [`DynQuantity`] is then
    compared to that created by [`T::unit_from_type`](UnitFromType::unit_from_type).
    If they don't match, an error is returned. See the docstring of
    [`QuantityFunction`] for more.

    # Examples

    ```
    use var_quantity::{DynQuantity, PredefUnit, Unit};
    use var_quantity::{IsQuantityFunction, QuantityFunction};
    use var_quantity::uom::si::f64::{ElectricalResistance, ElectricCurrent};

    // 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.
    #[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
    struct Resistance;

    // Again, the macro annotation is just here because of the serde feature
    #[typetag::serde]
    impl IsQuantityFunction for Resistance {
        fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
            return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
        }

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

    let resistance = Resistance {};

    // The Resistance struct always returns an electric resistance. Hence the
    // type check fails for other types
    assert!(QuantityFunction::<ElectricalResistance>::new(Box::new(resistance.clone())).is_ok());
    assert!(QuantityFunction::<f64>::new(Box::new(resistance.clone())).is_err());
    assert!(QuantityFunction::<ElectricCurrent>::new(Box::new(resistance.clone())).is_err());
    ```
     */
    pub fn new(function: Box<dyn IsQuantityFunction>) -> Result<Self, UnitsNotEqual> {
        // Call the function w/o any arguments and make sure the returned
        // DynQuantity<f64> is convertible to T
        let actual = function.call(&[]).unit;
        let expected = T::unit_from_type();
        if actual != expected {
            return Err(UnitsNotEqual(expected, actual));
        }
        return Ok(Self {
            function,
            phantom: PhantomData,
        });
    }

    /**
    Forwards the input to the [`IsQuantityFunction::call`] method of the wrapped
    trait object and asserts that the returned value can be converted to `T`.
    If that is not the case, the constraint outlined in the docstring of
    [`QuantityFunction`] is not fulfilled and the code is invalid, therefore
    the function panics.

    # Examples

    This is a valid implementation of [`IsQuantity`]: [`Unit`] is always the
    same regardless of input.
    ```
    use var_quantity::{DynQuantity, PredefUnit, Unit, IsQuantityFunction, QuantityFunction};
    use var_quantity::uom::si::electrical_resistance::ohm;
    use var_quantity::uom::si::f64::{ElectricalResistance};

    // 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.
    #[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
    struct Resistance;

    // Again, the macro annotation is just here because of the serde feature
    #[typetag::serde]
    impl IsQuantityFunction for Resistance {
        fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
            return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
        }

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

    let wrapped_resistance = QuantityFunction::<ElectricalResistance>::new(Box::new(Resistance {})).expect("units match");
    assert_eq!(ElectricalResistance::new::<ohm>(1.0), wrapped_resistance.call(&[1.0.into()]));
    ```

    This is an invalid (and nonsensical) implementation of [`IsQuantityFunction`]
    where the output unit changes with the number of arguments:
    ```should_panic
    use var_quantity::{DynQuantity, PredefUnit, Unit};
    use var_quantity::{IsQuantityFunction, QuantityFunction};
    use var_quantity::uom::si::f64::{ElectricalResistance};

    // 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.
    #[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq)]
    struct Resistance;

    // Again, the macro annotation is just here because of the serde feature
    #[typetag::serde]
    impl IsQuantityFunction for Resistance {
        fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
            if conditions.len() == 0 {
                return DynQuantity::new(1.0, PredefUnit::ElectricResistance);
            } else {
                return DynQuantity::new(1.0, PredefUnit::None);
            }
        }

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

    // Construction succeeds since the test call is done with an empty slice
    let wrapped_resistance = QuantityFunction::<ElectricalResistance>::new(Box::new(Resistance {})).expect("units match");

    // ... but calling with a quantity results in a panic
    let _ = wrapped_resistance.call(&[DynQuantity::new(1.0, PredefUnit::None)]);
    ```
     */
    pub fn call(&self, conditions: &[DynQuantity<f64>]) -> T {
        match T::try_from(self.function.call(conditions).into()) {
            Ok(val) => val,
            Err(_) => {
                panic!(
                    "conversion from DynQuantity<f64> to T failed for input {:?}.\n
                    This means that the IsQuantityFunction trait object returns
                    different DynQuantity<f64> depending on the input, which
                    is a bug in the implementation of the trait object.",
                    conditions
                )
            }
        }
    }

    /**
    Returns the underlying boxed [`IsQuantityFunction`] trait object.
     */
    pub fn into_inner(self) -> Box<dyn IsQuantityFunction> {
        return self.function;
    }
}

impl<T: IsQuantity> std::fmt::Debug for QuantityFunction<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("QuantityFunction").finish()
    }
}

impl<T: IsQuantity> PartialEq<QuantityFunction<T>> for QuantityFunction<T> {
    fn eq(&self, other: &QuantityFunction<T>) -> bool {
        return self.function.dyn_eq(&*other.function);
    }
}

impl<T: IsQuantity> Clone for QuantityFunction<T> {
    fn clone(&self) -> Self {
        return Self {
            function: dyn_clone::clone_box(&*self.function),
            phantom: PhantomData,
        };
    }
}

impl<T: IsQuantity> AsRef<dyn IsQuantityFunction> for QuantityFunction<T> {
    fn as_ref(&self) -> &dyn IsQuantityFunction {
        &*self.function
    }
}

impl<T: IsQuantity> Deref for QuantityFunction<T> {
    type Target = dyn IsQuantityFunction;

    fn deref(&self) -> &dyn IsQuantityFunction {
        &*self.function
    }
}

/**
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
[`IsQuantityFunction`] trait object (wrapped in [`QuantityFunction`]). 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 [`IsQuantityFunction::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](#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](https://crates.io/crates/uom)
library), for which [`From<DynQuantity<f64>>`] can obviously not be implemented.
The conversion is checked once when constructing a [`QuantityFunction`] from a
[`IsQuantityFunction`] trait object by calling [`IsQuantityFunction::call`] with
`conditions = &[]`, but of course it is impossible to test all
potential values for `conditions`.

It is therefore up to the provider of the trait object to make sure that the
[`DynQuantity<f64>`] returned by [`IsQuantityFunction::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](https://crates.io/crates/uom) [`Quantity`](https://docs.rs/uom/latest/uom/si/struct.Quantity.html).
```
use var_quantity::{DynQuantity, PredefUnit, Unit};
use var_quantity::uom::si::electrical_resistance::ohm;
use var_quantity::uom::si::f64::ElectricalResistance;
use var_quantity::{QuantityFunction, IsQuantityFunction, 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, PartialEq)]
struct ResistanceFunction;

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

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

let wrapper = QuantityFunction::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 [`IsQuantityFunction`] trait object is convertible to `T`.
```
use var_quantity::{DynQuantity, PredefUnit};
use var_quantity::uom::si::electrical_conductance::siemens;
use var_quantity::uom::si::f64::{ElectricalResistance, ElectricalConductance};
use var_quantity::{QuantityFunction, IsQuantityFunction, VarQuantity};

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

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

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

// Mismatch in type definition - catched during construction of QuantityFunction
let wrapper = QuantityFunction::<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 [`IsQuantityFunction`] trait object is
serialized / deserialized using [typetag](https://docs.rs/typetag/latest/typetag/).
This is also the reason why [`IsQuantityFunction::call`] returns a
[`DynQuantity<f64>`] instead of a generic type.
 */
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum VarQuantity<T: IsQuantity> {
    /**
    Optimization for the common case of a constant quantity. This avoids going
    through dynamic dispatch when accessing the value.
     */
    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
    #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_quantity"))]
    Constant(T),
    /**
    Catch-all variant for any non-constant behaviour. Arbitrary behaviour
    can be realized with the contained [`IsQuantityFunction`] trait object, as
    long as the unit constraint outlined in the [`VarQuantity`] docstring is
    upheld.
     */
    Function(QuantityFunction<T>),
}

impl<T: IsQuantity> VarQuantity<T> {
    /**
    Matches against `self` and either returns the contained value (variant
    [`VarQuantity::Constant`]) or executes the call method of the contained
    [`QuantityFunction`] (variant [`VarQuantity::Function`]).
    */
    pub fn get(&self, conditions: &[DynQuantity<f64>]) -> T {
        match self {
            Self::Constant(val) => val.clone(),
            Self::Function(fun) => fun.call(conditions),
        }
    }

    /**
    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 [`QuantityFunction<T>::new`] on the boxed trait object.
    3) Wrap the resulting [`QuantityFunction<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).
    */
    pub fn try_from_quantity_function<F: IsQuantityFunction>(
        fun: F,
    ) -> Result<Self, UnitsNotEqual> {
        let boxed: Box<dyn IsQuantityFunction> = Box::new(fun);
        return boxed.try_into();
    }

    /**
    Returns a reference to the underlying function if `self` is a
    [`VarQuantity::Function`].
     */
    pub fn function(&self) -> Option<&dyn IsQuantityFunction> {
        match self {
            VarQuantity::Constant(_) => return None,
            VarQuantity::Function(quantity_function) => return Some(quantity_function.as_ref()),
        }
    }
}

impl<T: IsQuantity> TryFrom<Box<dyn IsQuantityFunction>> for VarQuantity<T> {
    type Error = UnitsNotEqual;

    fn try_from(value: Box<dyn IsQuantityFunction>) -> Result<Self, Self::Error> {
        let wrapper = QuantityFunction::new(value)?;
        return Ok(Self::Function(wrapper));
    }
}

impl<T: IsQuantity> From<T> for VarQuantity<T> {
    fn from(value: T) -> Self {
        return Self::Constant(value);
    }
}

#[cfg(feature = "serde")]
mod serde_impl {
    use serde::de::DeserializeOwned;

    use super::*;

    impl<T: IsQuantity> Serialize for QuantityFunction<T> {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            self.function.serialize(serializer)
        }
    }

    impl<'de, T: IsQuantity> serde::Deserialize<'de> for QuantityFunction<T> {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            let v = <Box<dyn IsQuantityFunction>>::deserialize(deserializer)?;
            QuantityFunction::new(v).map_err(serde::de::Error::custom)
        }
    }

    impl<'de, T> serde::Deserialize<'de> for VarQuantity<T>
    where
        T: DeserializeOwned + IsQuantity,
        <T as TryFrom<DynQuantity<Complex<f64>>>>::Error: std::fmt::Display,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            use std::str::FromStr;

            #[derive(deserialize_untagged_verbose_error::DeserializeUntaggedVerboseError)]
            enum InnerOrString<T> {
                Inner(T),
                #[cfg(feature = "from_str")]
                String(String),
            }

            let content: serde_value::Value = serde::Deserialize::deserialize(deserializer)?;

            // Try to deserialize as a quantity. If that fails, try to deserialize as a
            // function trait object
            match InnerOrString::<T>::deserialize(serde_value::ValueDeserializer::<D::Error>::new(
                content.clone(),
            )) {
                Ok(number_or_string) => match number_or_string {
                    InnerOrString::Inner(q) => return Ok(VarQuantity::Constant(q)),
                    InnerOrString::String(s) => {
                        let dq = DynQuantity::<Complex<f64>>::from_str(&s)
                            .map_err(serde::de::Error::custom)?;
                        let q = T::try_from(dq).map_err(serde::de::Error::custom)?;
                        return Ok(VarQuantity::Constant(q));
                    }
                },
                Err(_) => {
                    let wrapper =
                        QuantityFunction::deserialize(
                            serde_value::ValueDeserializer::<D::Error>::new(content.clone()),
                        )?;
                    return Ok(VarQuantity::Function(wrapper));
                }
            }
        }
    }
}

/**
A wrapper around a type implementing [`IsQuantityFunction`] trait object which
clamps the output of [`IsQuantityFunction::call`] using the provided upper and
lower limits.

If the `serde` feature is not activated, it implements [`IsQuantityFunction`]
in a generic manner and can therefore be used in a [`QuantityFunction`]. If
`serde` is activated, it is unfortately not possible to provide a generic
implementation due to the macro `#[typetag::serde]` not being able to deal with
generics. As a workaround, it is possible to provide a simple custom
implementation for each concrete type in your own crate:

```ignore
#[cfg_attr(feature = "serde", typetag::serde)]
impl IsQuantityFunction for ClampedQuantity<YourTypeHere> {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        return self.call_clamped(conditions);
    }
}
```

This approach is used for all the implementors of [`IsQuantityFunction`] provided
with this crate.
 */
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ClampedQuantity<T: IsQuantityFunction> {
    upper_limit: f64,
    lower_limit: f64,
    function: T,
}

impl<T: IsQuantityFunction> ClampedQuantity<T> {
    /**
    Checks if `upper_limit >= lower_limit` and returns a new instance of
    [`ClampedQuantity`] if true.
    */
    pub fn new(upper_limit: f64, lower_limit: f64, function: T) -> Result<Self, &'static str> {
        if upper_limit < lower_limit {
            return Err("upper limit must not be smaller than the lower limit");
        }
        return Ok(Self {
            upper_limit,
            lower_limit,
            function,
        });
    }

    /**
    Returns the underlying [`IsQuantityFunction`].
     */
    pub fn inner(&self) -> &T {
        return &self.function;
    }

    /**
    Returns the underlying [`IsQuantityFunction`] as a trait object.
     */
    pub fn inner_dyn(&self) -> &dyn IsQuantityFunction {
        return &self.function;
    }

    /// Returns the upper limit.
    pub fn upper_limit(&self) -> f64 {
        return self.upper_limit;
    }

    /// Returns the lower limit.
    pub fn lower_limit(&self) -> f64 {
        return self.lower_limit;
    }

    /**
    Clamps the output value of `T::call` using the provided upper and lower
    limits. This function is mainly here to simplify custom [`IsQuantityFunction`]
    implementations, see the [`ClampedQuantity`] docstring.
     */
    pub fn call_clamped(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        let mut dyn_quantity = self.function.call(conditions);
        dyn_quantity.value = dyn_quantity.value.clamp(self.lower_limit, self.upper_limit);
        return dyn_quantity;
    }
}

// Only available if the serde feature is not active because deserialization of
// generic trait objects with typetag is not possible
#[cfg(not(feature = "serde"))]
impl<T: IsQuantityFunction + Clone + PartialEq> IsQuantityFunction for ClampedQuantity<T> {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        return self.call_clamped(conditions);
    }

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

/**
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.

The main purpose of this function is to simplify writing unary functions. For
example, the [`IsQuantityFunction::call`] implementation of a linear function
can look like this:

```
use dyn_quantity::{DynQuantity, Unit};
use var_quantity::{filter_unary_function, IsQuantityFunction};

// 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.
#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct Linear {
    slope: f64,
    base_value: f64,
}

// Again, the macro annotation is just here because of the serde feature
#[cfg_attr(feature = "serde", typetag::serde)]
impl IsQuantityFunction for Linear {
    fn call(&self, conditions: &[DynQuantity<f64>]) -> DynQuantity<f64> {
        return filter_unary_function(
            conditions,
            Unit::default(),
            |input| {
                DynQuantity::new(
                    self.base_value + self.slope * input.value,
                    Unit::default(),
                )
            },
            || DynQuantity::new(
                    self.base_value,
                    Unit::default(),
                ),
        );
    }

    fn dyn_eq(&self, other: &dyn IsQuantityFunction) -> bool {
        (other as &dyn std::any::Any).downcast_ref::<Self>() == Some(self)
    }
}
```
 */
pub fn filter_unary_function<F, G>(
    conditions: &[DynQuantity<f64>],
    match_for: Unit,
    with_matched: F,
    no_match: G,
) -> DynQuantity<f64>
where
    F: FnOnce(DynQuantity<f64>) -> DynQuantity<f64>,
    G: FnOnce() -> DynQuantity<f64>,
{
    for iq in conditions {
        if iq.unit == match_for {
            return with_matched(iq.clone());
        }
    }
    no_match()
}