iliekturtles / uom

Units of measurement -- type-safe zero-cost dimensional analysis

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Computing an average temperature

nickmertin opened this issue · comments

Currently, ThermodynamicTemperature values cannot be added together; the module documentation does a good job of explaining why. However, that makes it rather difficult to calculate the average of a collection of ThermodynamicTemperature values. For non-affine quanities, one can just use .sum() on an iterator then divide by the number of elements.

Is there a recommended way to do this for temperature values?

Not a great answer, but I would convert to temperature interval and then back again: 0 K ThermodynamicTemperature + SUM(ThermodynamicTemperature - 0 K TemperatureInterval)

I hit a similar issue, but subtracting TemperatureInterval to a ThermodynamicTemperature yields a ThermodynamicTemperature, so SUM(ThermodynamicTemperature - 0 K TemperatureInterval) is not really possible and the only way to do this is by getting the underlying value and creating a new TemperatureInterval with it, right?

More generally speaking, while summing affine quantities makes no sense, computing their mean does. So affine quantities, not just ThermodynamicTemperature but also anything that might eventually emerge from #289, should support computing means, without forcing the user to hack around the type system in order to achieve something perfectly reasonable.

I've not had the need to use uom's temperatures yet, so I was expecting to be able to subtract one ThermodynamicTemperature from another and get a TemperatureInterval, but that appears not to work:

use uom::si::f32::{TemperatureInterval as dT, ThermodynamicTemperature as T};

fn temp_difference(t1: T, t2: T) -> dT { t2 - t1 }

gives a compilation error or the subtraction `t2 - t1', which prevents these sorts of implementations

fn mean_t(t1: T, t2: T) -> T {
    let dt: dT = t2 - t1;
    t1 + dt / 2.0
}

fn mean_temp(ts: &[T]) -> Option<T> {
    if ts.is_empty() { return None }
    let n = ts.len() as f32;
    let t0: T = ts[0]; // An arbitrary reference value, any `T` will do
    let mean_deviation_from_t0: dT = ts.iter().map(|t| t - t0).sum::<dT>() / n;
    Some(t0 + mean_deviation_from_t0)
 }

@jacg issue #380 has the explanation for why it is like that. I do wonder if TemperatureKind is the correct way to go for affine quantities, instead of something else more generic on top of uom. Position and distance are also different, yet I don't see a LengthKind for it or a LengthInterval and the same goes for time and energy. And then how would you account for the fact that Velocity * Time = LengthInterval and not Length?

In fact, I believe a similar problem already exists: ThermalConductance and ThermalConductivity should only be multipliable with TemperatureInterval, that's what makes physical sense.

issue #380 has the explanation for why it is like that.

I trust that we agree that this is a restriction imposed by an unfortunate shortcoming of the current implementation details of uom rather than something fundamental. Put another way ThermodynamicTemperature - ThermodynamicTemperature should give TemperatureInterval.

I do wonder if TemperatureKind is the correct way to go for affine quantities, instead of something else more generic on top of uom. Position and distance are also different, yet I don't see a LengthKind for it or a LengthInterval and the same goes for time and energy.

... and for any Quantity, which is what the aforementioned #289 is all about.

And then how would you account for the fact that Velocity * Time = LengthInterval and not Length?

  • All Quantities need an affine (Q<A>) and a vector (Q<V>) version.

  • The affine versions cannot be added or multiplied, only subtracted (note the Vs in some of the < >):

    • Q<A> - Q<A> = Q<V>
    • Q<A> + Q<V> = Q<A>
    • Q<A> + Q<A> - meaningless
    • Q<A> * <anything> - meaningless
  • The vector versions support many more operations:

    • Q<V> - Q<V> = Q<V>
    • Q<V> + Q<V> = Q<V>
    • Q<V> + Q<A> = Q<A>
    • Q1<V> * Q2<V> = Q3<V>
    • etc.

When you say that

Velocity * Time = LengthInterval

You mean

Velocity<V> * Time<V> = Length<V>

They cannot possibly be <A>s because multiplication is not implemented for <A>s.

Your Length is really Length<A> and that cannot be the result of a multiplication: only Q<V> can be the result of a multiplication; your LengthInterval is really Length<V> which is just as well, because Q<V>s are the only things that can result from multiplying Qs.

In fact, I believe a similar problem already exists: ThermalConductance and ThermalConductivity should only be multipliable with TemperatureInterval, that's what makes physical sense.

In general, it only makes sense to multiply vector, not affine, quantities.

When it comes to quantities like ThermalConductance or Mass there is a stronger tendency to conclude that their affine varieties do not exist, than it is for quantities like Velocity: any 21st century physicist knows that absolute velocities are expressed in some arbitrary frame of reference. Mass has an obvious, objective, non-arbitrary frame of reference on which everyone agrees, so it's more difficult to appreciate that said obvious choice of zero for affine mass is not the only possible choice. When you make the obvious choice, you can get away with confounding affine and vector mass, which makes it easy to overlook that Mass also comes in affine and vector varieties, and that it is important to distinguish between the two:

  • Place a bowl on top of some kitchen scales.
  • Do NOT press the TARE button.
  • Put some rice in the bowl.
  • How would you double the amount of rice in the bowl?

If your solution is to keep pouring in rice until the number shown by the display doubles, then you'll get the wrong answer precisely because you performed an operation that is only permitted on vector quantities on an affine quantity: you multiplied an affine mass by 2, and multiplying affine quantities doesn't make sense.