Skip to content
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: quantity equation #401

Closed
JohelEGP opened this issue Nov 4, 2022 · 16 comments
Closed

feat: quantity equation #401

JohelEGP opened this issue Nov 4, 2022 · 16 comments

Comments

@JohelEGP
Copy link
Collaborator

JohelEGP commented Nov 4, 2022

Thanks to your help, @mpusz, I've come to appreciate the need (and more) of systems of quantities and units. It has become evident to me that dimensions are not enough to specify a number of quantities and their relations. So I suggest raising the level of abstraction from quantity dimension to quantity equation (as seen in example 3 of the former definition, a quantity equation has a dimension).

I've been experimenting with quantity equations. They should have the following benefits:

  • Unify the quantity kind templates with their quantity counterparts.
  • Express quantities with equal dimension.
  • Specify in the type system relations that look beyond the dimension.

The last point above includes:

  • Factors, e.g. radius defined as half of a diameter.
  • Offsets, e.g. Celsius temperature defined as 𝘵 = 𝘛 - 𝘛₀ where 𝘛₀ = 273.15 K.
  • Addition, e.g. mechanical energy is equal to kinetic energy plus potential energy.

The quantities in the last point above, although of equal dimension, should not be implicitly convertible. That'd only give the right answer when one of the terms was 0. It also stands out that the addition of those two latter quantities should result in the first quantity.

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 4, 2022

This is the playground where I'm experimenting with this: https://gcc.godbolt.org/z/hW6Gvc31T.

@mpusz
Copy link
Owner

mpusz commented Nov 4, 2022

@JohelEGP, I really like your ideas! However, I am not a fan of the actual approach. Even though you prove that it is possible, which is really cool BTW 😉, I do not think we should strive to use values instead of templates everywhere. This makes the error messages really long and nasty. Providing user-friendly types is, in my opinion, the most important feature of the library. This is why I keep working on the V2 design. Would you like to port some of your ideas to V2 in order to improve it?

@mpusz
Copy link
Owner

mpusz commented Nov 4, 2022

BTW, I do not think that we need a system abstraction. It is probably better to keep systems open and allow for example to add lengths from the SI and CGS systems if only unit definitions are compatible (expressed in terms of another). This also allows extending the system easily by for example adding an angle as a strong dimension for SI.

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 4, 2022

@JohelEGP, I really like your ideas! However, I am not a fan of the actual approach. Even though you prove that it is possible, which is really cool BTW wink, I do not think we should strive to use values instead of templates everywhere. This makes the error messages really long and nasty. Providing user-friendly types is, in my opinion, the most important feature of the library. This is why I keep working on the V2 design. Would you like to port some of your ideas to V2 in order to improve it?

I want to try using as much values as possible. That makes experimenting easier. It should be trivial to pedal back from qty::ref{qty::num{1}} and qty::ref{qty::ref::p{(& qty::mega), (& qty::metre), qty::exp{2}}} to something like 1 and prefixed_unit<mega, metre, exp{2}> instead. That'd mean changing an NTTP of a fixed type, which I have yet to experiment on the effects it'll have, to a duck type.

@mpusz
Copy link
Owner

mpusz commented Nov 4, 2022

Besides being really cool and interesting, the question is if this will make implementation easier to understand and maintain? Or maybe it is easier to work on fixed types all the way? Also, what about compile times? Did you compare those already? constexpr is known to be really slow on current compilers.

@mpusz
Copy link
Owner

mpusz commented Nov 4, 2022

Also, with the values approach, I am afraid that we will lose a lot of existing library users as only the latest compilers support them properly.

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 4, 2022

Besides being really cool and interesting, the question is if this will make implementation easier to understand and maintain?

Of course. That's why I've moved away from TMP.

Or maybe it is easier to work on fixed types all the way?

It's certainly easier. But as you point out, that doesn't correlate to better error messages.

Also, what about compile times? Did you compare those already? constexpr is known to be really slow on current compilers.

No. I'll do that when I have something comparable to mp-units. Hopefully it'll win hands-down. And there's certainly free lunch to come, as your "current" suggests.

Also, with the values approach, I am afraid that we will lose a lot of existing library users as only the latest compilers support them properly.

Yeah. A evolutionary approach is more appropriate for an existing library.

BTW, I do not think that we need a system abstraction. It is probably better to keep systems open and allow for example to add lengths from the SI and CGS systems if only unit definitions are compatible (expressed in terms of another). This also allows extending the system easily by for example adding an angle as a strong dimension for SI.

The devil is in the details. I plan to allow this, with or without a system's abstraction. In fact, I've come to think that it's a phenomenal feature.

// What I hope to implement to allow angle as a base quantity.
constexpr system si = /*...*/;
constexpr system si_base_angle = [] {
  auto sys = si;
  sys[plane_angle].equal = quantity::base{};
  return sys;
}();

However, I do believe systems may be necessary to disambiguate. As evidenced by #205.

@mpusz
Copy link
Owner

mpusz commented Nov 4, 2022

I do not think that #205 is an issue in V2 anymore.

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 4, 2022

That makes sense. The fix should be trivial for the comparison operators. You could arbitrarily choose a system on which to perform the comparison.

What about operators that result in a quantity?

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 4, 2022

Would you like to port some of your ideas to V2 in order to improve it?

We'll see about that after I have a proof of concept. Since V2 is a redesign of the API without new features, this suggestion shouldn't hold it back.

@JohelEGP
Copy link
Collaborator Author

JohelEGP commented Nov 14, 2022

While I work on this, here's an idea to help reduce boilerplate. Use a string to specify stuff, parsed like CTRE (i.e. from string to data structure). For example, with the template head template<fixed_string S, something... else>, where else describes how to interpret the symbols in S. Perhaps you can take some inspiration from lines 1275-1467 of https://gcc.godbolt.org/z/r4Gzn6h1v.

With such a framework, here's some examples of possible API changes to V2:

-  constant_unit<"h", mag<ratio{662'607'015, 100'000'000}> * mag_power<10, -34> * joule * second> {} planck_constant_unit;
+  constant_unit<"h = 6.626 070 15 × 10⁻³⁴ J s", joule, second> {} planck_constant_unit;
-  constant_unit<"h", mag<ratio{662'607'015, 100'000'000}> * mag_power<10, -34> * joule * second> {} planck_constant_unit;
+  constant_unit<"h = 6.626 070 15 × 10⁻³⁴ J s", si> {} planck_constant_unit;
-template<PrefixableUnit auto U> struct yocto_ : prefixed_unit<"y", mag_power<10, -24>, U> {};
+template<PrefixableUnit auto U> struct yocto_ : prefixed_unit<"y = 10⁻²⁴", U> {};
-inline constexpr struct newton : named_unit<"N", kilogram * metre / square<second>> {} newton;
+inline constexpr struct newton : named_unit<"N = kg m / s²", kilogram, metre, second> {} newton;
-inline constexpr struct newton : named_unit<"N", kilogram * metre / square<second>> {} newton;
+inline constexpr struct newton : named_unit<"N = kg m / s²", si> {} newton;

@mpusz
Copy link
Owner

mpusz commented Nov 14, 2022

This looks really nice, and it is great that such things are possible with C++ 😉

The reason I do not think it is a good idea is that you do not immediately know which types are involved in the definition, which may be both surprising or frustrating in case of error messages. Also, you cannot just click "Go to definition" on a unit in the IDE. I also think it is more error-prone as it is much easier to make a typo there (no help from the IDE suggesting names), and such an issue will be harder to resolve. Additionally, a lot of code has to be written in order to support such an engine. Do you agree with that?

@JohelEGP
Copy link
Collaborator Author

Ah, yes, the wonders of stringly typed interfaces.

@JohelEGP
Copy link
Collaborator Author

The reason I do not think it is a good idea is that you do not immediately know which types are involved in the definition, which may be both surprising

My current opinion is that formulas are better to read than code, and that types are incidental.

or frustrating in case of error messages.

IIRC, CTRE gives good error messages. And I think it's definitely possible to do better. But the engine would have to make that possible, as you can't piggy-back on the type system.

Also, you cannot just click "Go to definition" on a unit in the IDE.

Definitely true.

I also think it is more error-prone as it is much easier to make a typo there (no help from the IDE suggesting names), and such an issue will be harder to resolve.

That can certainly be a problem. I plan to use tests to guard against that:

-  constant_unit<"h", mag<ratio{662'607'015, 100'000'000}> * mag_power<10, -34> * joule * second> {} planck_constant_unit;
+  constant_unit<"h = 6.626 070 15 × 10⁻³⁴ J s", si> {} planck_constant_unit;
 // In the test file:
+static_assert(planck_constant_unit.number == /*...*/);
+static_assert(planck_constant_unit.reference == joule * second);

So while the API reads better, more tests are required. I do not think this is state of affairs, given that code is read more than written (some say 80/20, others 90/10).
When the error occurs before the tests have a chance to run, it could manifest in the engine's error reporting module or as usual in the type system from the generated types.

Additionally, a lot of code has to be written in order to support such an engine. Do you agree with that?

Yes. Some of it will be exclusive to the engine.

@mpusz
Copy link
Owner

mpusz commented Nov 15, 2022

So while the API reads better, more tests are required. I do not think this is state of affairs, given that code is read more than written (some say 80/20, others 90/10).

I tend to agree with that. However, the system is being defined once, and nearly no users will look into it after that. I think we should not overcomplicate the system definition, as regular users will not benefit from it. Regular users will be more concerned about quantity creation and dimensional manipulation, and this is where we should put the most effort into making it user-friendly and easy to understand when something goes wrong.

@mpusz
Copy link
Owner

mpusz commented Jun 15, 2023

Done in V2.

@mpusz mpusz closed this as completed Jun 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants