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}