-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: raise level of abstraction from dimension to structure describing a quantity #405
Comments
I think that might be a good idea, but we have to do some POC based on V2 first and find what suits here and what does not. Please also note that we do not have much time if we want to write the first papers for Issaquah. |
I saw your approach to defining such quantities some time ago. It was really nice and clear. However, the more I think about it the more I am concerned about use case for it. For example, do we really want the below to compile? static_assert(quantity_cast<diameter>(2 * radius[m]).number() == 4); I think it would be really surprising to the users that the conversion between a radius and diameter type happens automatically. Lots of users will like to write this 2x multiplier by themselves in their codebase. If the information you propose will not be used as above then why to provide it at all? |
I think we do.
Not only will they have to write |
I am not sure what you mean here. My assumption is that: auto r = 5 * radius[m]; // 5 m
quantity<diameter[m]> d = r; // 10 m Where do they need to multiply by |
That's right. I think I misunderstood #405 (comment) because there's a |
I planned to provide kinds just by deriving from dimensions. For example: inline constexpr struct radius : decltype(isq::length) {} radius;
inline constexpr struct diameter : decltype(isq::length) {} diameter; With that the following was not meant to compile: auto r = 5 * radius[m];
quantity<diameter[m]> d = 2 * r; and one of the following was meant to be needed: constexpr quantity_of<diameter> auto to_diameter(quantity_of<radius> auto r)
{
return quantity_cast<diameter>(2 * quantity_cast<isq::length>(r));
} constexpr quantity_of<diameter> auto to_diameter(quantity_of<radius> auto r)
{
return quantity_cast<diameter>(2 * r);
} depending on how powerful we want a |
But I am open to other ideas as well. We need a POC here. |
I'm not a fan of inline constexpr auto rotation = defn<"𝘕", "𝘕 = 𝜑/(2π)", rotational_displacement("𝜑")>();
inline constexpr auto angular_frequency = defn<"𝜔", "𝜔 = 2π𝘧", frequency("𝘧")>();
inline constexpr auto magnetic_polarization = defn<"𝙅ₘ", "𝙅ₘ = 𝜇₀𝙈", magnetic_constant("𝜇₀"), magnetization("𝙈")>();
inline constexpr auto power_attenuation_coefficient = defn<"", "𝘮 = 2𝛼", attenuation_coefficient("")>();
inline constexpr auto rest_energy = defn<"", "𝘌₀ = 𝘮₀𝘤₀²", rest_mass(""), speed_of_light("𝘤₀"), "𝘌₀", energy("𝘌")>();
inline constexpr quantity charge_number = defn<"", "dim 𝘤 = 𝘲𝘦⁻¹", electric_charge("𝘲"), elementary_charge("")>();
inline constexpr auto Larmor_frequency = defn<"", "𝘷_L = 𝜔_L/(2π)", Larmor_angular_frequency("")>();
inline constexpr auto Compton_wavelength = defn<"", "𝜆_C = 𝘩/(𝘮𝘤₀)", Planck_constant("𝘩"), rest_mass(""), speed_of_light("𝘤₀")>();
inline constexpr auto level_width = defn<"", "𝛤 = ℏ/𝜏", reduced_Planck_constant(""), mean_duration_of_life("")>(); So it'd be better to centralize the handling of factors, rather than having to write 2N functions for converting to and from (and probably even more at the call site because there's no swift transition, e.g. from frequency to a quantity that's a factor of angular_frequency, or having to write more than 2N functions).
|
I agree with you. We should definitely look into this. If we can achieve this while still having easy to understand code and compiler error messages it would be great. |
Yes, this is why I provided two |
How would you implement |
There might be some overlap with BobSteagall/wg21#36. For now, I just define it as inline constexpr auto propagation_coefficient = defn<"𝛾", "𝛾 = 𝛼 + i𝛽", attenuation("𝛼"), phase_coefficient("𝛽")>(); 𝛽 does not necessarily represent an imaginary number, so if you define |
I am not sure if I follow you. How to actually construct such a quantity from the attenuation and phase coefficient and how to validate such a construction? I mean in the dimensional analysis sense here. |
q<attenuation> α;
q<phase_coefficient> β;
q<propagation_coefficient, std::complex<double>> γ = α + i * β;
|
I started to work on that on the V2 branch already. |
Which of the following should compile directly, which should require quantity<isq::speed[km / h]> s1 = 100 * isq::distance[km] / (2 * isq::duration[h]); // exact match
quantity<isq::speed[km / h]> s2 = 100 * isq::length[km] / (2 * isq::time[h]); // different kinds (more generic)
quantity<isq::speed[km / h]> s3 = 100 * isq::diameter[km] / (2 * isq::duration[h]) // different kinds (more specific)
quantity<isq::velocity[km / h]> s4 = 100 * isq::distance[km] / (2 * isq::duration[h]); // different characteristics (scalar not vector)
quantity<isq::speed[km / h]> s5 = 100 * isq::position_vector[km] / (2 * isq::duration[h]); // different characteristics (vector not scalar) quantity<isq::velocity[km / h]> s6 = 100 * isq::acceleration[km] * (2 * isq::duration[h]); // exact match
quantity<isq::velocity[km / h]> s7 = 100 * isq::acceleration[km] * (2 * isq::time[h]); // time vs duration (should they simplify in the resulting type)
quantity<isq::speed[km / h]> s8 = 100 * isq::acceleration[km] * (2 * isq::duration[h]); // different characteristics (vector not scalar) |
I fixed the comments. Both distance and diameter and kinds of length, so they can stand in for a length.
Dimensional analysis should fail. It can't be left up to the number type, as they could be convertible, like
They should be synonyms. For simplicity, one of them should be a reference, rather than a "strong alias". None of these should require |
I think that Please note that every scalar can also be a vector and a tensor. It is not the case in the reverse direction. |
I am also not sure if time and duration should be synonyms. |
I am also afraid that if most of the above are to compile then it might be hard to prevent compilation of some kinds derived from i.e. struct speed_kind1 : speed {};
struct speed_kind2 : speed {}; At least I cannot invent the algorithm to cover all the bases here. |
I'm not sure. ISO just defines speed as "magnitude of the velocity". By a definition that uses "distance", I think the example in https://en.wikipedia.org/wiki/Speed#Difference_between_speed_and_velocity, paragraph 2, would also have the speed be 0, because the distance from the starting and end points are 0.
In general, I don't think it's so clear-cut. Force is a vector quantity that is prominently used as a scalar in quantity definitions: inline constexpr auto normal_stress = defn<"𝜎ₙ", "𝜎ₙ = d𝘍ₙ/d𝘈", force("𝘍ₙ"), area("𝘈")>();
inline constexpr auto shear_stress = defn<"𝜏ₛ", "𝜏ₛ = d𝘍ₜ/d𝘈", force("𝘍ₜ"), area("𝘈")>();
inline constexpr auto static_friction_factor = defn<"𝜇ₛ", "𝘍ₘₐₓ = 𝜇ₛ𝘕", static_friction_force("𝘍ₘₐₓ"), force("𝘕")>();
inline constexpr auto kinetic_friction_factor = defn<"𝜇", "𝘍_μ = 𝜇𝘕", kinetic_friction_force("𝘍_μ"), force("𝘕")>();
inline constexpr auto rolling_resistance_factor = defn<"𝘊ᵣᵣ", "𝘍 = 𝘊ᵣᵣ𝘕", force("𝘍"), force("𝘕")>();
inline constexpr auto drag_coefficient = defn<"𝘊_D", "𝘍_D = ½𝘊_D𝜌𝘷²𝘈", drag_force("𝘍_D"), mass_density("𝜌"), speed("𝘷"), area("𝘈")>(); Some of these are components of the vector, others their magnitude, IIRC. It's necessary to support these uses.
That's indeed how ISO defines it. And it remarks that "duration is often just called time". And indeed, most quantity definitions outside ISO 80000-3 use "time" instead of "difference" for what's a "time difference between to events". inline constexpr auto force = defn<"𝙁", "dim 𝙁 = 𝘮𝘭𝘵⁻²", mass("𝘮"), length("𝘭"), time("𝘵")>(); // Part 4
inline constexpr quantity energy = defn<"𝘌", "dim 𝘌 = 𝘮𝘭²𝘵⁻²", mass("𝘮"), length("𝘭"), time("𝘵")>(); // Part 5
inline constexpr auto electric_charge = defn<"𝘘", "d𝘘 = 𝘐d𝘵", electric_current("𝘐"), time("𝘵")>(); // Part 6
inline constexpr auto radiant_flux = defn<"𝛷ₑ", "𝛷ₑ = d𝘘ₑ/d𝘵", radiant_energy<electromagnetism>("𝘘ₑ"), time("𝘵")>(); // Part 7
inline constexpr auto sound_particle_velocity = defn<"𝙫", "𝙫 = ∂𝞭/∂𝘵", sound_particle_displacement("𝞭"), time("𝘵")>(); // Part 8
inline constexpr auto reverberation_time = defn<"𝘛_𝘯", duration>(); // Part 9
inline constexpr auto decay_constant = defn<"", "𝜆 = -(1/𝘕)(d𝘕/d𝘵)", number_of_entities<E>("𝘕(X)"), time("𝘵")>(); // Part 10
You could allow defining kinds not by deriving. |
I do not agree with that. Actually, the usage of
Please see the tensor definition here: https://en.wikipedia.org/wiki/Physical_quantity#Size. I think that the framework should assume that scalar is also a vector and a tensor, and that a vector is also a tensor. So the same scalar representation type could be used for all three abstractions. For example, I can imagine a coordinate system where an
Good point.
Deriving is just an implementation detail here. In general, I just cannot find a good algorithm that will prevent conversions between those two but will allow all the rest to work. |
Of course it can't, it loses the information on direction. But you should somehow be able to represent a component or magnitude of a vector. The simplest way would be to allow replacing the number representation type of a vector quantity with a scalar.
Once you do a rotation and end up in the same starting position, the distance is 0 and the path length
I think it's simpler with values (or reflection). So rather than deriving from the |
ISO 80000 defines distance as the "shortest path length" so they should have the same value here assuming that your path is circular in your example. I would argue that actually a length could be zero in this case.
Again, the problem is not with inheritance but with the algorithm itself. Having all of the recipes for all the quantities I cannot find a good algorithm that covers all of the cases here. |
There are infinite path lengths from point A to point A, one being a rotation around the center of a circle. The shortest one from and to the same point, the distance, is 0.
What cases? |
As long as I can somehow imagine user not being super confused by: auto r = 5 * radius[m]; // 5 m
quantity<diameter[m]> d = r; // 10 m what about kinetic energy being defined as auto mass = 10 * isq::mass[kg];
auto speed = 2 * isq::speed[m/s];
auto e1 = mass * pow<2>(speed); // 40 J
auto e2 = quantity_cast<kinetic_energy>(e1); // 20 J
quantity<kinetic_energy[J]> e3 = e1; // 20 J
quantity<kinetic_energy[J]> e4 = mass * pow<2>(speed) / 2; // 10 J I think that omitting the need to divide the above by What do you think about it? Feedback is welcomed from everyone on this. |
Maybe we should move the discussion about computing/confirming "character" to #409, and keep this thread here focused on some sort of
As always, I ask for precise language, so we can clearly reason about concepts. Your statements "A is a kind of B" describe a hierarchy of quantities, but it may not match the ISO 80000 definition of kind. For clarity, let us use type (or still, quantity_spec), so we can discuss if that concept is the same as ISO 80000 kind. So to describe the hierarchy in question here, we may say Let me visualise that, together with a few more types, which I'll need later: graph TD
subgraph Length["kind: length"]
length["length := root(dim=L)"]
length-->width
width-->diameter
length-->radius
radius -.- |equivalent as properties of circle| diameter
end
subgraph Position["kind: position_vector"]
length---->position_vector
end
subgraph InvTime["kind: inverse_time"]
invtime("root(dim=1/T) = 1/time")
end
subgraph Freq["kind: frequency"]
invtime --> freq["frequency"]
end
subgraph Activity["kind: radioactivity"]
invtime --> activity["radioactivity"]
end
subgraph LengthByTime["kind: length_by_time"]
length_by_time("root(dim=L/T) = length * inverse_time")
length_by_time --> width_by_time("width * inverse_time")
end
subgraph LengthFreq["kind: length_times_freq"]
length_by_time ---> length_times_freq("length*frequency")
length_times_freq --> width_times_freq("width*frequency")
end
subgraph Velocity["kind: velocity"]
length_by_time ---> velocity["velocity := position_vector * inverse_time"]
end
subgraph PosTimesFreq["kind: position_vector*frequency"]
velocity --> pos_times_freq("position_vector * frequency")
end
Note that I do not have a ISO 80000 reference that defines a hierarchy of kinds, but it's definition of quantity types seems to indicate at least a hierarchy of types. (It doesn't use the term quantity type as a concept though, it simply calls it a quantity). There are a few assumptions here, but bear with me for the moment. One assumption here is that the kind of Let us compare this with our So what about the type We have a couple of options here:
Similar logic applies for |
So to say, with that |
That reminds me that the number type can be
Diameter is a kind of width.
I was working on expressing that in C++. You can find it here for reference: https://compiler-explorer.com/z/9Ta34nTK7. |
Coincidentally, I checked the Wikipedia article for some quantities of latter parts of ISO 80000. Unsurprisingly, they have less general formulas that work in particular cases that involve different quantities. How would the library accommodate for that? Consider that position vector is defined by ISO 80000-3 as
We have inline constexpr auto length = defn<"𝘭">();
inline constexpr auto width = defn<"𝘣", "𝘣 = {𝘹 ∈ 𝘭 | {𝘹} ≥ 0}", length("𝘭")>();
inline constexpr auto diameter = defn<"𝘥", width>();
inline constexpr auto radius = defn<"𝘳", "𝘳 = ½𝘥", diameter("𝘥")>();
inline constexpr auto time = defn<"𝘵">();
inline constexpr auto velocity = defn<"𝙫", "𝙫 = d𝙧/d𝘵", position_vector("𝙧"), duration("𝘵")>();
inline constexpr auto speed = defn<"𝘷", "𝘷 = |𝙫|", velocity("𝙫")>(); 𝘷 = 𝘳/𝘵, where 𝘷 is speed, 𝘳 is radius, and 𝘵 is time, can be a valid formula for calculating speed. That can happen when movement is in a straight line. The origin of the coordinate system, as present in the definition of velocity, would be the center of a circle. And so the traveled length is the radius. That formula for speed could be the speed formula for a domain. So I've had my reservations about enforcing the "one true way". |
There are also quantities defined by ISO 80000 without a formula, and with multiple formulas, sometimes none of general application. Famously, there's energy, with myriads of quantities of its kind, and no formula itself. The Wikipedia article does list plenty of formulas for energy itself and not one of its kinds. So |
Please clarify; NOTE 2 uses the language "the kind of [QUANTITY] is [KIND]"; never "[QUANTITY] is a kind of [WHAT-IS-THIS?]". Their language does not create a hierarchy, but the latter language does. Because of that, I suggested to use the term type instead, and draw that hierarchy using the language of type. As I have drawn it,
Actually, in my field of physics, we have regularly used r as a symbol for a |
Nice work! But I don't see how this defines a hierarchy, or "compatibility" as in the sense of ISO 80000 kind, or more generically as required for the semantics of quantity calculus in mp-units. It defines relations between quantities. How do I use that to answer questions of the form "is every |
We define what a kind is in such a way that it enables safe manipulation of quantities. I don't believe it's necessary to introduce more terms. It should suffice that width and height are kinds of length, but width is not a kind of height, height is not a kind of width. And so certain expressions involving width and length are allowed by the library, whereas the same with width and height are not. |
https://gcc.godbolt.org/z/va6fMsd75 is more complete, but it doesn't have any of that. That's why my suggestions in the matter are not accompanied with working code. |
I believe that is not what ISO 80000 is trying to tell us. Specifically, chapter 4.2 that @mpusz quoted says:
So So if we want to allow that, then we need to introduce more terms. Otherwise, you are right, we don't need separate terms (this was my option (2) at the end of my latest essay).
Thanks! I'll check it out - maybe I can come up with a suitable |
That last expression doesn't involve height. Only kinds of width. |
Where does it say that Either way, then take |
inline constexpr auto diameter = defn<"𝘥", width>();
inline constexpr auto radius = defn<"𝘳", "𝘳 = ½𝘥", diameter("𝘥")>(); Diameter is a width and radius is half of a diameter. Both are kinds of width. inline constexpr auto wavelength = defn<"𝜆", length>(); Wavelength is a kind of length. I don't know whether 𝜆 < 𝘥, where 𝜆 is wavelength and 𝘥 is diameter, should be valid when expressed in C++. |
ISO 80000 explicitly says that Which of the following expressions should be disallowed?
Give me a rule of what is compatible (and what the resulting kind of the expression is)! There are many options, such as:
Let me know what you want to do with mp-units, and if I can help with any of that |
PS: I don't think that we'll get a reasonable hierarchy from any |
Yeah. The code mostly has formulas. But ISO 80000 usually has more, including definitions and remarks. For example, the definitions quoted at #405 (comment) describe "is-a" relationships. The intent is that the way they're defined in code are in line with the ISO 80000 definitions, so considering: inline constexpr auto height = defn<"𝘩", length>();
inline constexpr auto radius = defn<"𝘳", "𝘳 = ½𝘥", diameter("𝘥")>();
|
There is no such thing in ISO 80000 as a hierarchy or even a list of kinds. ISO 80000 never specifies exactly a kind of quantity and even says "The division of the concept ‘quantity’ into several kinds is to some extent arbitrary." It does not say if a kind is equivalent to a dimension or to the more fine-grained hierarchy of named quantities. The only thing that ISO 80000 says is that kind is an "aspect common to mutually comparable quantities", but again, does not specify which quantities should be comparable. This is up to us to specify how much type-safety and how many casts we will introduce that will still not make the library unusable. We have to find some compromise here. Practically all quantities of the same dimensions are comparable and could be added and subtracted. It is trivial to implement as well. But is it enough to say our library is type-safe? In such a case, why bother defining all ISO 80000 quantities and not only one quantity per dimension? As @burnpanck correctly listed in #405 (comment) it is up to us (and all other users) to define what is good for a C++ type-safe library and what is not. I hope that as a result of this thread, we will come to some common conclusions that could result in a successful logic for a library. 😃 |
Yeah I'm not fully convinced by that - "radius is a diameter" just doesn't feel right. But I'm willing to accept that if that is what prevents us to at least agree on some definition that is self-consistent under quantity calculus.
Exactly. In fact, I don't know of any package in any language that imposes compatibility restrictions on ISO 80000 quantities beyond dimensional analysis - probably because anything else appears to be much more difficult. As a user of mp-units "classic", I only sparingly made use of the So why bother? You have provided me with a few examples where the ISO 80000 is rather clear, first and foremost IMHO, we should do the following: We keep most of the quantities compatible with each other, unless the case is clear - based on examples that we should collect somewhere. For those, we handcraft a suitable hierarchy of kind, and make the machinery available as a customisation point. Then, users can further subdivide the kind hierarchy with what feels right in their domain. We may still define all of the ISO 80000 quantities as a more fine-grained subdivision of quantity types, which can serve as basis for that domain-specific customisation. Terms kind and type up for bikeshedding, I don't care, as long as we have separate terms that we can reason about. |
That's why I said
Thought I misspelled "as" instead of "is". |
I really do not know how to proceed now. Should we strive to provide ISO 80000 quantities to provide vocabulary types for users or just limit the implementation to the simplest cases and leave the rest for users to define based on their requirements? Also, should we continue to support quantity kinds as we do for now in ISO 80000, which does not introduce much type safety (at least, it seems we do not know how to make it work), or should we restore the V1 design with a dedicated |
For some context, see #413 (comment) and the last part of #413 (comment). A radius is half of a diameter. @burnpanck correctly points out that the factor between these quantities only applies when converting one of these quantities to the other. So it is dependent on the object being measured. That is different from the conversion factor between temperatures differences, which apply between units. With that in mind, it seems that comparisons should only look at the (number, unit) pair of the quantity arguments, as is customary. So Similarly, construction would only look at the (number, unit) pair of its input argument. Just like we commonly formulate that the radius 𝘢 equals |
It was my mistake to define inline constexpr auto length = defn<"𝘭">();
inline constexpr auto width = defn<"𝘣", "𝘣 = {𝘹 ∈ 𝘭 | {𝘹} ≥ 0}", length("𝘭")>();
inline constexpr auto diameter = defn<"𝘥", width>();
inline constexpr auto radius = defn<"𝘳", "𝘳 = ½𝘥", diameter("𝘥")>(); ISO 80000 literally defines it as
It goes out of its way to not define a formula for a reason. I had once tried turning such a quantity definition into a formula. Then I read up a bit about it on Wikipedia, and realized it was wrong to do that. Since then, I've refrained from doing that kind of transformation. That happened in a much later part of the ISO 80000 series, and I didn't realize I had earlier mistakes to fix. P.S. I think #405 (comment) is going in the same, wrong direction, by deviating from ISO 80000, as I argument in latter comments. So the definition of a radius should be like that of diameter, i.e. a kind of width. |
At least until partway through ISO 80000-10, the remaining quantities that are a factor of another (and not dependent on the measurand, and not a factor between units like the temperature units) are angular quantities, all with the factor inline constexpr auto rotation = defn<"𝘕", "𝘕 = 𝜑/(2π)", rotational_displacement("𝜑")>();
inline constexpr auto angular_frequency = defn<"𝜔", "𝜔 = 2π𝘧", frequency("𝘧")>();
inline constexpr auto angular_repetency = defn<"𝘬", "𝘬 = |𝙠| = 2π/𝜆", wave_vector("𝙠"), wavelength("𝜆")>(); |
In V2, we also have |
To answer #405 (comment), with #405 (comment) in mind, I don't think the library should apply the factor ½ when converting to kinetic energy. You can't know if the input quantity has already had ½ applied. And I insist in #405 (comment), that if 𝘛 were a function with the input quantities of mass and speed, 𝘛(𝘮, 𝘷) = ½𝘮𝘷², then the library should apply both ½ and ². |
Addressed in V2 with |
#401 is too big of a step.
The
quantity
class template takes a template argument for the quantity dimension. Thequantity_kind
class template slightly extends them to describe the "is a kind of" property. The same happens forquantity_point
andquantity_point_kind
.These are insufficient. And I don't think extending
quantity
, likequantity_kind
, did is the way to go. There are more properties than "is a kind of".A suitable structure, which describes these properties, should be used in place of the dimension of
quantity
. For starters:quantity_kind
andquantity_point_kind
can go, which are replaced by a converting constructor inquanity
andquantity_point
(or whatever else according to how we define what a "kind" is).The last two are enough to also resolve #232, e.g. Fahrenheit is a factor of ⁵⁄₉ kelvin, and its points are also offset by 459.67 K.
The text was updated successfully, but these errors were encountered: