mpusz / mp-units

The quantities and units library for C++
https://mpusz.github.io/mp-units/
MIT License
1.1k stars 88 forks source link

Ordering and simplification of expression templates #499

Closed mpusz closed 4 months ago

mpusz commented 1 year ago

Expression templates are simplified with the rules described in our docs.

For units and dimensions, ordering is being done based on the symbol of this entity. This provides nice outputs (i.e. N m). However, the rest of the steps are being done based on the same type identifier (elements with the same type are simplified). This might be considered an inconsistent approach. It is possible to do the ordering of dimensions and units based on their type identifiers as this is what we do for quantity_spec, which does not expose a symbol. This could, however, result in worse-quality text outputs.

Please see the following example: https://godbolt.org/z/c4eW7c6nK.

In case the ordering was done by the identifiers, most of the outputs would look like s m s there, or in case of "newton metre" we would get m N. But maybe, this is the price that we should pay for consistency? Also, I assume that it is not surprising that aggregation is not done for "the same" units with a different type?

Please let me know your thoughts, and let's decide how to proceed.

chiphogg commented 1 year ago

Do you know of any use cases where end users should be reasoning directly in terms of the ordering? I don't. My instinct is that we should treat this as implementation-defined behaviour.

If this is true, then it means we can feel free to choose a policy (such as ordering units and dimensions by symbol) that improves output.

That said: I assume the only improvement is that like symbols are grouped with like, right? I don't know of any policy that would produce the "correct" ordering for units robustly. (I assume that N m being in the correct order is only a coincidence.)

Finally: yes, I agree it's not surprising that we don't aggregate "the same" units with a different type. I don't know a good way that we would do this. In general, it's possible for truly-different units to have the same symbol. I don't think we want to enforce global uniqueness of unit symbols; the real world is too messy to make that move confidently.

rothmichaels commented 1 year ago

I don't think we want to enforce global uniqueness of unit symbols; the real world is too messy to make that move confidently.

+1

rothmichaels commented 1 year ago

Perhaps this isn't exactly related to what you wanted feedback on, but I had a question about the "does not compile examples".

Am I correct in assuming that the reason why 1 * si::second * natural::second does not compile is that they are not units of the same kind? Is there a way to make a quantity from si::second * natural::second or derived_unit<natural::second_, si::second_>? I'm just curious if there is a way to use these derived units from the static_asserts above the "does not compile" comments.

mpusz commented 1 year ago

Do you know of any use cases where end users should be reasoning directly in terms of the ordering?

No

I don't know of any policy that would produce the "correct" ordering for units robustly.

I couldn't find such a policy, and SI and ISQ specs seem to be inconsistent as well. By this, I mean that the ordering is not dictated by the units themselves but by the traditional way to express a unit of a specific quantity. Sometimes even ISQ provides explicitly that some quantities can use different orders of units (i.e. electric charge lists C, A s, and s A which seems confusing to me). Another case is that negative powers are typically at the end, but ISQ again provides m^-3 s A as a unit of electric charge density or kg^−1 m^−2 s^4 A^2 as a unit of capacitance. SI Brochure also provides kg^−1 m^−3 s^4 A^2 as a unit of permittivity.

I do not think that we will find a generic way to type all those symbols in "the best" way so maybe to improve consistency with quantity specifications, and with other steps of unit processing we should just use type identifiers everywhere?

rothmichaels commented 1 year ago

Could you point me to where in the code the ordering predicate is for units and for quantities? Playing around with the units ordering predicate might help me form a stronger opinion.

mpusz commented 1 year ago

Am I correct in assuming that the reason why 1 si::second natural::second does not compile is that they are not units of the same kind? Is there a way to make a quantity from si::seconds * natural::second or derivedunit<natural::second, si::second_>? I'm just curious if there is a way to use these derived units from the static_asserts above the "does not compile" comments.

The multiply syntax works only for Representation * AssociatedUnit. Natural units are not associated with any quantity specification because the same unit may be used to express different quantities. The unit is an associated unit if all its ingredients are associated units as well. In this case, the si::second is fine but the natural::second is not. And this is why it fails.

To create a quantity in natural units systems we have to use an abstraction that I call a system_reference for now that manually binds a unit to quantity_spec. See:

https://github.com/mpusz/mp-units/blob/84df87dd2c48aff2adfae80d3debbba55246cfcb/src/systems/natural/include/mp-units/systems/natural/natural.h#L39-L47.

With those you can:

https://github.com/mpusz/mp-units/blob/84df87dd2c48aff2adfae80d3debbba55246cfcb/example/total_energy.cpp#L86-L100

mpusz commented 1 year ago

Could you point me to where in the code the ordering predicate is for units and for quantities? Playing around with the units ordering predicate might help me form a stronger opinion.

https://github.com/mpusz/mp-units/blob/b7e2033d33fa42843de38cfc6d0f2a4875caff4b/src/core/include/mp-units/unit.h#L372-L376

https://github.com/mpusz/mp-units/blob/b7e2033d33fa42843de38cfc6d0f2a4875caff4b/src/core/include/mp-units/quantity_spec.h#L93-L97

You can easily find a similar predicate for dimensions as well.

mpusz commented 4 months ago

Now, as all of the entities (tag types) have to be final to enforce their uniqueness, we should no longer depend on user-provided symbols (which might not be globally unique) but only on the type names.