mpusz / mp-units

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

feat: negative constants #510

Open JohelEGP opened 8 months ago

JohelEGP commented 8 months ago

ISO 80000-1 Β§A.4 "Constants" references CODATA: https://physics.nist.gov/cuu/Constants/. It lists fundamental constants, including some negative ones. Browse to "All values (ascii)", search for -, and see some under the column "Value" being highlighted as negative. One such constant is helion g factor, which is rejected for being negative (https://godbolt.org/z/vao9PKv3s):

inline constexpr struct helion_g_factor :
  named_unit<basic_symbol_text{"π˜¨β‚•", "g_h"}, mag<-ratio{4'255'250'615, 1'000'000'000}> * one> {} helion_g_factor;
<source>:7:46: error: use of invalid variable template 'mag<mp_units::ratio{-851050123, 200000000}>'
    7 |   named_unit<basic_symbol_text{"π˜¨β‚•", "g_h"}, mag<-ratio{4'255'250'615, 1'000'000'000}> * one> {} helion_g_factor;
      |                                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:7:46: note: constraints not satisfied
In file included from /opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/bits/expression_template.h:25,
                 from /opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/bits/dimension_concepts.h:25,
                 from /opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/quantity.h:26,
                 from /opt/compiler-explorer/libs/mp-units/trunk/src/systems/si/include/mp-units/systems/si/constants.h:25,
                 from /opt/compiler-explorer/libs/mp-units/trunk/src/systems/si/include/mp-units/systems/si/si.h:25,
                 from <source>:1:
/opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/bits/external/math_concepts.h: In substitution of 'template<mp_units::ratio R>  requires  gt_zero<(long int)((const mp_units::ratio)R).num> constexpr const auto [requires mp_units::Magnitude<<placeholder>, >] mp_units::mag<R> [with mp_units::ratio R = mp_units::ratio{-851050123, 200000000}]':
<source>:7:51:   required from here
/opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/bits/external/math_concepts.h:30:9:   required for the satisfaction of 'gt_zero<((const mp_units::ratio)R).num>' [with R = _ZTAXtlN8mp_units5ratioELln851050123ELl200000000EEE]
/opt/compiler-explorer/libs/mp-units/trunk/src/core/include/mp-units/bits/external/math_concepts.h:30:22: note: the expression 'N > 0 [with N = (long int)_ZTAXtlN8mp_units5ratioELln851050123ELl200000000EEE.mp_units::ratio::num]' evaluated to 'false'
   30 | concept gt_zero = (N > 0);
      |                   ~~~^~~~
<source>:7:93: error: template argument 2 is invalid
    7 |   named_unit<basic_symbol_text{"π˜¨β‚•", "g_h"}, mag<-ratio{4'255'250'615, 1'000'000'000}> * one> {} helion_g_factor;
      |                                                                                             ^
Compiler returned: 1
JohelEGP commented 8 months ago

This is motivated by https://mpusz.github.io/mp-units/2.1/users_guide/framework_basics/faster_than_lightspeed_constants/.

mpusz commented 8 months ago

Good catch @JohelEGP. It seems that negative magnitudes have sense after all :-)

@chiphogg, could you please check how to address that? Is the removal of gt_zero constraints enough? Maybe we should leave non_zero there?

mpusz commented 8 months ago

BTW, I was not aware of CODATA. Maybe we should add most/all of those constants to the library as well?

mpusz commented 8 months ago

Some constants use uncertainty. We could extend #464 with those use cases.

chiphogg commented 8 months ago

A magnitude (whether represented via vector spaces, or some imagined future alternative) is a positive real number. I think it's too hasty to say that negative magnitudes make sense: I'm confident they don't. For example, I expect that magnitude logarithms will be critical for implementing logarithmic units such as decibels, but negative numbers do not have logarithms.

In mp-units, a constant is just a unit. Do "negative units" make sense? Am I "-73 neginches" tall? Are we sure we want this?

Negative constants exist, and we should support them, but it's not clear to me yet how to do that without overly complicating other abstractions. If we want to support them, it seems not all of the following can be true simultaneously.

  1. A constant is simply a unit.
  2. A unit's "size" is fully specified by its magnitude.
  3. A magnitude is a positive real number.

I think changing 3 would be a huge mistake. I could see some sense in changing 2 by adding a sign parameter to the unit definition, but I really don't want to rush into allowing negative units without thinking it through carefully. (Note that this caution around negative units also reinforces the desire to avoid changing 3, which would also effectively give us negative units.)

Ultimately, a constant is conceptually a quantity, and quantities can be negative if their values are negative. Many times, we can treat that quantity as a unit, but I think negative quantities complicate that idea: is the "unit" the quantity itself, or its (absolute) magnitude?

One other advantage to changing 1 would be the opportunity to represent uncertainty, which is worth pondering.


In any case... I think the main takeaway is that this seems like a significant challenge case, and one where we really don't want to rush into committing to a solution without considering very carefully.

JohelEGP commented 8 months ago

The problem is using the unit abstraction for constants. Non-base units carry numbers in its magnitude, which must be non-negative by definition.

I can't find any provision that a chosen reference quantity, the unit, must be non-negative. https://jcgm.bipm.org/vim/en/1.9.html simply defines "measurement unit" as

real scalar quantity, defined and adopted by convention, with which any other quantity of the same kind can be compared to express the ratio of the two quantities as a number

I think that'd be a better reference for P2982R0, which says in 6.5.3 Characters don’t apply to dimensions and units,

[ISO/IEC 80000] [...] explicitly states that:

All units are scalars.

That's in the context of ISO 80000-2:2019 "18 Scalars, vectors and tensors".

Instead of treating each coordinate of a vector as a physical quantity value (i.e. a number multiplied by a unit), the vector could be written as a numerical vector multiplied by a unit. All units are scalars. EXAMPLE Force acting on a given particle, e.g. in Cartesian components (Fx; Fy; Fz) = (βˆ’31,5; 43,2; 17,0) N.

It doesn't read like a requirement, but as a fact. So it'd be better to reference the definition of unit.

mpusz commented 8 months ago

I am puzzled now, and I don't really know how to proceed here. We could consider add some structural-type like quantity_constant<Quantity> that would take a compile-time known quantity (similarly to relative_point_origin). This could take any quantity as a constant (even a negative one) but this would not provide any automated unit simplification as this is not a unit :-(.

Please let me know if you have some ideas.

JohelEGP commented 8 months ago

I can't find any provision that a chosen reference quantity, the unit, must be non-negative.

It must be in the definition of quantity (https://jcgm.bipm.org/vim/en/1.1.html):

property of a phenomenon, body, or substance, where the property has a magnitude that can be expressed as a number and a reference

Which would imply that units are non-negative. It checks out ISO 80000-1:2022 "4.1 The concept of quantity", which mentions

It is customary to use the same term, "quantity", to refer to both general quantities, such as length, mass, etc., and their instances, such as given lengths, given masses, etc. Accordingly, we are used to saying both that length is a quantity and that a given length is a quantity, by maintaining the specification – "general quantity, Q" or "individual quantity, Qa" – implicit and exploiting the linguistic context to remove the ambiguity.

So quantity is defined as a magnitude. And the context can imply a quantity as a vector. This is what I was assuming when I thought that units could be non-negative.

JohelEGP commented 8 months ago

I can't find any provision that a chosen reference quantity, the unit, must be non-negative.

It must be in the definition of quantity (https://jcgm.bipm.org/vim/en/1.1.html):

property of a phenomenon, body, or substance, where the property has a magnitude that can be expressed as a number and a reference

Which would imply that units are non-negative.

It's also true that a number can be negative. And that a negative quantity has a magnitude. So I'm not totally sure that this means that units must be non-negative.

chiphogg commented 8 months ago

If we can't find a clear answer from the definition as to whether units can be negative, then we should reach out to leading experts in the field to clarify. It's quite possible that they never mentioned it because it never occurred to them that somebody might try defining a negative unit.

JohelEGP commented 8 months ago

I considered that. I was hoping @mpusz would do that.

I also tried checking if some truths still hold. A quantity with a negative unit still forms a vector space. Anything else comes to mind that might not work given negative units?

chiphogg commented 8 months ago

Good point about the vector space.

I can't think of anything that wouldn't "work" per se, I just think from a practical perspective it's likely to be full of gotchas and surprises.

On the other hand, it probably wouldn't have any effect on people who don't use negative constants. And, it probably would be possible to replace magnitudes with something that is, essentially, "magnitude and a sign bit" (although I think we'd want to come up with a different name).

We'll probably want to tread carefully and solicit expert opinion, but who knows --- maybe this could work!

mpusz commented 1 week ago

Let's provide a new thing called constant that will still satisfy a Unit concept but will allow negative magnitude in its definition. named_unit will be constrained to require positive magnitude.

@chiphogg, how do you like this solution?

chiphogg commented 6 days ago

My initial reaction is that I like it!

Au has Constant as a separate thing compared to "unit": a constant is templated on a unit. I like the conceptual separation. And Constant would give us a place to "hang" a negative value (it could be a simple bool template parameter) without forcing us to try to support "negative units".

mpusz commented 6 days ago

Sure, but I think that I would prefer to be able to do mag<-1> there over some bool flag.

chiphogg commented 4 days ago

I'm wary of changing the core semantics of magnitude away from "a positive real number". Making it "a nonzero real number" would put a hole in the middle (0), and contradicts how most people generally think of a "magnitude". Plus, this would automatically open the door to negative units, which I had thought was what we were trying to avoid (although re-reading your previous comment makes me think I was wrong about this).

I do see the appeal of being able to write it the same way as for other numbers, but I don't think it's worth the cost of complicating our reasoning about magnitudes.

Now that I read more carefully, I'm not sure I would want a constant to satisfy the unit concept. Maybe it's the other way around? :thinking:

JohelEGP commented 4 days ago

What about introducing an abstraction for declaring negative constants?

inline constexpr struct helion_g_factor_magnitude :
  named_unit<basic_symbol_text{"π˜¨β‚•", "g_h"}, mag<ratio{4'255'250'615, 1'000'000'000}> * one> {} helion_g_factor_magnitude;
inline constexpr struct helion_g_factor : /* ??? */ {} helion_g_factor;

So doing 1 * helion_g_factor creates a negative quantity.

chiphogg commented 4 days ago

So basically, "solve for /* ??? */? I like it. We can bikeshed what the /* ??? */ would look like, but I'm sure there are many reasonable choices that would look good.