mp-units
mp-units copied to clipboard
feat!: add number concepts
Resolves #29.
The number concepts are meant to be at a lower level than the units' library.
This means that quantity can use it to constrain rep, and quantity itself can model vector_space.
So number_one_v would supersede quantity_values::one(), and same for zero.
https://github.com/BobSteagall/wg21/ doesn't work because it doesn't provide compound assignments.
https://github.com/mpusz/mp-units/blob/a356db749ef8651d6b8c48cf732673286a0faa39/test/unit_test/runtime/linear_algebra_test.cpp#L97
v + v is valid and the same type as v.
But the equivalent for lvalues, v += v, isn't provided to mean the same thing.
There's still room for more integration.
That would involve breaking up mp_units's specific concepts
that couple checks on the reference and the representation:
https://github.com/mpusz/mp-units/blob/a356db749ef8651d6b8c48cf732673286a0faa39/src/core/include/mp-units/quantity.h#L60-L67
Because concepts can't be generically composed (i.e., passed to a concept template parameter),
interfaces would have to check the reference and representation separately.
These are the reference documentations: mp_units.pdf. This is the paper on the number concepts: P3003R0 The design of a library of number concepts.
https://github.com/BobSteagall/wg21/ doesn't work because it doesn't provide compound assignments. https://github.com/mpusz/mp-units/blob/a356db749ef8651d6b8c48cf732673286a0faa39/test/unit_test/runtime/linear_algebra_test.cpp#L97
v + vis valid and the same type asv. But the equivalent for lvalues,v += v, isn't provided to mean the same thing.
I imagine it doesn't provide compound assignments to encourage the use of Eigen's template expressions.
There are two other ways I think the concepts could fall short:
- When using representation types that directly do template expressions.
- When performing operations with heterogeneous representation types
that don't model C++20's
std::common_with(i.e., they need to specializestd::common_type, in contrast withstd::complex, which doesn't need to).
https://github.com/BobSteagall/wg21/ doesn't work because it doesn't provide compound assignments.
This probably should not be a hard requirement for a representation type. There are plenty of usage representation types that do not support compound assignments.
But the equivalent for lvalues, v += v, isn't provided to mean the same thing.
Of course, we may, and probably should, submit a bug for this library as it strives to be standardized as well.
When performing operations with heterogeneous representation types that don't model C++20's std::common_with (i.e., they need to specialize std::common_type, in contrast with std::complex, which doesn't need to).
quantity needs to specialize std::common_type (at least until operator?: is standardized) but you said that quantity satisfies vector_space
These are the reference documentations: mp_units.pdf.
I love the docs. However, I think the documentation generation framework could be provided in a separate PR. Merging the framework forces us to provide all the rest of the documentation for the library in this framework.
I do not find myself too comfortable with LaTex for now (but probably I should learn it anyway). Do you volunteer to document the rest of the framework as well? 😉
https://github.com/BobSteagall/wg21/ doesn't work because it doesn't provide compound assignments.
This probably should not be a hard requirement for a representation type. There are plenty of usage representation types that do not support compound assignments.
I think those representation types are wrong.
That'd be akin to needing to write v = std::move(v) + number_one_v<decltype(v)>; because ++v isn't provided.
These are the reference documentations: mp_units.pdf.
I love the docs. However, I think the documentation generation framework could be provided in a separate PR. Merging the framework forces us to provide all the rest of the documentation for the library in this framework.
When you think the added concepts work fine and are ready to merge, if you still think it's OK to merge without documentation, I'll rip it off this PR.
I do not find myself too comfortable with LaTex for now (but probably I should learn it anyway). Do you volunteer to document the rest of the framework as well? 😉
Sure.
Do you write all the LaTex markup by hand, or do you use some tools/IDE/WYSIWYG to do that?
Yes, it's all manual right now.
A while ago, I started #493, but I never had time to finish it. Please check how it relates to your changes.
I'm all-in for a solution that is specific to mp-units' representation types.
I'm afraid that my more general solution here could stagnate the efforts of standardization. It's certainly incomplete, and not something that I expect to be done with design-by-committee.
I'm afraid that my more general solution here could stagnate the efforts of standardization.
Yes, I am also a bit surprised by the scope of your change. Despite this being awesome, it may be a blocker for the library. It deserves a separate paper directed to SG6, for sure. The sooner we provide it, the better. I may champion it in the Committee, but you should be the primary author and be present in the room when we discuss that in the room as I will for sure not have all the answers. You will see soon that I have lots of questions in my review :wink:
I'm all-in for a solution that is specific to mp-units' representation types.
Sure, but you did not touch vectors and tensors at all but we need them. Moreover, vector representations collide with vector_space. I think that in the initial papers, we can base the quantity implementation on is_scalar, is_vector, and is_tensor customization points, but in the mp-units actually experiment with your numbers to get more experience with it over the years.
In case the numbers paper will be well received in SG6 we can easily merge those later.
Let's finish this review first and then regenerate the docs. I will send it then to some ISO C++ Committee friend with mathematic backgrounds to ask for their opinion. Maybe I will meet some of them at the CppCon as well.
CI errors have to be fixed as well...
I think my number concepts may be too immature to be proposed for C++ standardization.
I took into account the feedback on https://wg21.link/P1813 from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p1673r12.html#constraining-matrix-and-vector-element-types-and-scalars (and https://wg21.link/P2402). My specification is more relaxed and thus more widely useful. But it does mean that semantic requirements might still be necessary.
For example, unsigned's addition is modulo UNSIGNED_MAX,
but such an operation still makes for a valid vector space
(see the last sentence of the "Note 1 to entry" of IEV 102-01-11).
Let's consider a generic algorithm that calculates a "total",
with an input range of elements that model a vector_space.
It still needs a semantic requirement to protect against unsigned overflows,
because vector_space isn't enough for it to guarantee its result won't be erroneous.
I have considered some alternatives to semantic requirements.
- Adding a subsuming concept,
or a non-subsuming concept that just adds the semantic requirement
(e.g., like a
std::regular_invocableforstd::invocable). For example, a concept for integer (but that's also countably infinite). - A set of traits, orthogonal to the concepts, for when more control of semantics are needed. This would be most similar to https://wg21.link/P1813.
- Nothing! The algorithm with the semantic constraint already works perfectly fine. Especially if the inputs to won't give an erroneous answer for a given application.
So I'm still working on it as the needs materialize.
By the way, I didn't craft this hierarchy of number concepts "by looking at the algorithms".
Instead, I looked at the needs of templates like mp_units::quantity and mp_units::quantity_point.
Hi all! Thank you @JohelEGP for all your work! @mpusz asked me to review this proposal.
I'll begin by saying that it's great that the proposal already has wording. That being said, would you consider also adding some motivation (non-wording content that explains why this proposal should go into the C++ Standard and why it has the design that it has)? I am confident that both SG6 and (especially LEWG) will very much want to understand the motivation behind this proposal. They will be uncomfortable reviewing this proposal without first having seen motivation.
It's important that a motivation section explain how this proposal relates to earlier SG6 work on number types. Several years ago, SG6 spent some effort coming up with something like a unified vision for number types. It would make sense to talk about how this proposal either fits or doesn't fit into that vision.
Thank you for your answer.
I'll begin by saying that it's great that the proposal already has wording. That being said, would you consider also adding some motivation (non-wording content that explains why this proposal should go into the C++ Standard and why it has the design that it has)? I am confident that both SG6 and (especially LEWG) will very much want to understand the motivation behind this proposal. They will be uncomfortable reviewing this proposal without first having seen motivation.
As encouraged by Mateusz, I mentioned I'll be doing something like that at https://github.com/mpusz/mp-units/pull/492#discussion_r1335070616. However, it'll take the form of an informational paper rather than a proposal for adoption. It'll have wording, but doesn't have adoption experience, as should be required by anything going into the C++ standard.
It's important that a motivation section explain how this proposal relates to earlier SG6 work on number types. Several years ago, SG6 spent some effort coming up with something like a unified vision for number types. It would make sense to talk about how this proposal either fits or doesn't fit into that vision.
I do remember some proposals about having an unified interface for the number types on SG6's plate. But with regards to number concepts, I'm only aware of those I mention at https://github.com/mpusz/mp-units/pull/492#issuecomment-1732070030.
@JohelEGP wrote:
However, it'll take the form of an informational paper rather than a proposal for adoption. It'll have wording, but doesn't have adoption experience, as should be required by anything going into the C++ standard.
I'm a bit confused -- a proposal with "wording" is a proposal to put something into the C++ Standard.
In my experience, WG21 generally prefers that papers with wording also have motivation. WG21 doesn't like to separate motivation from wording. The two should go into the same paper.
Yeah, naming it "wording" would be a misnomer. It's actually the API of the concepts I have worked on. It just so happen that I know no documentation framework would do me justice, so I use the same framework as the C++ standard draft. (I rewrote it in Cpp2, and had to translate it to C++ for this PR. I really doubt there's a C++ documentation framework that would help me with Cpp2 code). I'm not separating the "wording" from everything else.
@JohelEGP wrote:
I do remember some proposals about having an unified interface for the number types on SG6's plate. But with regards to number concepts, I'm only aware of those I mention at https://github.com/mpusz/mp-units/pull/492#issuecomment-1732070030.
Concepts didn't exist in the Standard back then, but SG6 still had some ideas about how number types should fit together. SG6 recorded these ideas in a big proposal. We don't have to like those ideas, but they do represent an earlier consensus. Due diligence requires that authors of a new proposal about number concepts at least be aware of prior work and have some opinion about whether prior work fits.
I'll look for that big proposal and post a link here. Matthias Kretz or some other SG6 person should be able to find it if I can't.
I reviewed e-mail lists and found some SG6 papers relating to number types.
-
A vision of numeric types presented by John McFarlane (P0037, P0554, P0828, P1050, and P1751), that was last discussed in SG6 at the 2019 WG21 meeting in Cologne
-
P1889, which collects several papers together, but unfortunately does not track which papers it collected, and also did not keep so much design rationale
I can't speak for SG6. However, I think it's a good idea to review prior work that gives a unified vision on number types, because that should inform number concepts.
Yeah, I remember some of those.
Those are mostly templates to get some kind of "number".
The kind that here are models of scalar_number.
There are a bunch of locations where compound statements are requirements of concepts, they would almost all need to be removed to support safe-arithmetic. I didn't point them all out in the review, but I can if desired.
Thank you for your review. Looks like I'll have to rework the requirements on compound operations to allow more interesting number types.
What is the absolute minimum set of requirements for numerical values for them to work as expected with mp-units? Do these requirements belong on Quantity, or can they be split up to the various operations that can be performed on Quantity?
It's as mentioned at https://github.com/mpusz/mp-units/issues/29#issuecomment-1712552324:
The current approach seems like the right one. By constraining an operator on the representation's operator,
units::quantitycan work with any algebraic strucutre.Things have changed since. We have
quantityandquantity_point, documented to model a vector space and point space, respectively (https://mpusz.github.io/mp-units/2.0/users_guide/framework_basics/the_affine_space/). So now we know that we require the number to model those (and not just any algebraic structure). We further support operations on a scalar quantity and modulo.
@mpusz explained to me that the document isn't actually trying to propose changes to the Standard, but is just using the LaTeX format. Given the lack of motivation text, I'll do my best to try to reverse-engineer the intent.
number concept
It looks like users have to opt into this concept by specializing the enable_number trait. I find this a bit surprising; the point of a "number concept" to me would be to identify a set of requirements that makes an arbitrary type work with some set of algorithms. If a type satisfies those requirements, why should it have to "opt in" by specializing a trait? Furthermore, opting in to the trait doesn't actually make the type a number.
Providing a set of syntactic and semantic requirements for a number would make the trait unnecessary. number<T> would be true or false, without users needing to do anything.
Regarding the regular requirement, it's useful to know that value-initializing a number type results in a "zero" for that type. It's not enough just to be able to default-construct a value; you have to know that value-initializing (e.g., by invoking the default constructor) results in an additive identity.
ordered_number concept
Given that the concept depends on totally_ordered and number, why not call this concept totally_ordered_number? Floating-point types are not totally ordered, yet they are ordered; it seems reasonable to want a concept to describe them as well.
Arithmetic expression concepts
P1673 wants to be able to support mixed arithmetic expressions that might involve expression templates and/or proxy reference types. This means, for example, that x * y might not be a regular type, even if x and y are. (Expression templates generally hold references, so they aren't regular types.) Expressions might only relate to number types indirectly, through assignment or conversion.
Other concepts
Terms like "vector space" and "field" suggest linearity and associativity. Users may never use number types that are associative.
numberconceptIt looks like users have to opt into this concept by specializing the
enable_numbertrait. I find this a bit surprising; the point of a "number concept" to me would be to identify a set of requirements that makes an arbitrary type work with some set of algorithms. If a type satisfies those requirements, why should it have to "opt in" by specializing a trait? Furthermore, opting in to the trait doesn't actually make the type anumber.
Remember std::views::iota.
It works with ints.
So without defaulting enable_number_v to something like !requires { std::iter_reference_t<T>; },
you could have iterators accidentally satisfying point_space.
After a lot of thinking, I simply chose to make it an explicit opt-in.
That's in no way final.
Regarding the
regularrequirement, it's useful to know that value-initializing a number type results in a "zero" for that type. It's not enough just to be able to default-construct a value; you have to know that value-initializing (e.g., by invoking the default constructor) results in an additive identity.
That's why number_zero is a separate type trait.
I don't require anything from default initialization beyond what std::regular might guarantee.
ordered_numberconceptGiven that the concept depends on
totally_orderedandnumber, why not call this concepttotally_ordered_number? Floating-point types are not totally ordered, yet they are ordered; it seems reasonable to want a concept to describe them as well.
double is definitely a model: https://compiler-explorer.com/z/Yao9hv16x.
Remember https://eel.is/c++draft/concepts#equality-2.
Arithmetic expression concepts
P1673 wants to be able to support mixed arithmetic expressions that might involve expression templates and/or proxy reference types. This means, for example, that
x * ymight not be a regular type, even ifxandyare. (Expression templates generally hold references, so they aren't regular types.) Expressions might only relate to number types indirectly, through assignment or conversion.
I hadn't considered that expression templates might not be regular. I assume this would be fixed with https://github.com/mpusz/mp-units/pull/492#discussion_r1349200471.
Other concepts
Terms like "vector space" and "field" suggest linearity and associativity. Users may never use number types that are associative.
Neither do I.
The solution I came up with was splitting the set addition happens
from the mapping of the resulting element to a value of the type.
This also allows things like https://github.com/mpusz/mp-units/pull/492#discussion_r1335073402.
I think that's good, which is why scalar_number is only an approximation of a scalar number
(which is a real or complex number, which double isn't, but it still models scalar_number).
@JohelEGP wrote:
I think that's good, which is why scalar_number is only an approximation of a scalar number (which is a real or complex number, which double isn't, but it still models
scalar_number).
I generally prefer to avoid the word "approximation" when referring to concepts.
First, a concept is binary. What does "approximating" a concept mean?
Second, "approximation" suggests rounding error, but error due to assuming that a nonassociative number type is associative can be unbounded. (Consider, for example, a saturating integer number type.)