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

QuantityPoint usage for altitudes #130

Closed
jasonbeach opened this issue May 16, 2023 · 5 comments
Closed

QuantityPoint usage for altitudes #130

jasonbeach opened this issue May 16, 2023 · 5 comments
Labels
discussion Discussing the library. Not necessarily representing work to be done

Comments

@jasonbeach
Copy link

This isn't an issue per se, but a continuing of the conversation from mp-units (mpusz/mp-units#457).

Only reason I bring it here is I'd like to start using this at work and I get the idea that mp-units is still a work in progress and probably not ready for integration into a production system, whereas Au seems to be.

Just wondering if you could hopefully give a few quick pointers on the QuantityPoint types

I think Fahrenheit and Celsius actually provide a much better analog than I was originally thinking:
0 HAE (height above the WGS84 ellipsoid) is like 0 Kelvin (i.e. the absolute datum)
0 meters MSL (mean sea level) is like 0 Celsius (unit size is same (meters) but just offset from absolute datum)
0 feet MSL is like 0 Fahrenheit. (unit size is scaled and offset from absolute datum)

The only kind of weird thing is that altitude is still measured in meters not haes or msls, but that's just a semantic / terminology issue which I don't think affects using the library.

So I've defined:

struct AltHAE : Meters {
  static constexpr inline const char label[] = "hae (m)";
};
constexpr au::QuantityMaker<AltHAE> alt_hae {};
constexpr au::QuantityPointMaker<AltHAE> alt_hae_pt {};

struct AltMsl : AltHAE {
  static constexpr const char label[] = "msl (m)";
  constexpr auto origin() { return geoid_undulation_; };
  static constexpr au::QuantityD<au::Meters> geoid_undulation_ = meters(10.0); // this number shouldn't be fixed, it varies by lat/lon.
};
constexpr au::QuantityMaker<AltMsl> alt_msl {};
constexpr au::QuantityPointMaker<AltMsl> alt_msl_pt {};

With this

  const auto alt = alt_hae_pt(35.0);
  fmt::print("alt: {}\n", alt);
  fmt::print("alt msl: {}\n", alt.as<double>(alt_msl_pt));

(translating the ostream operators to fmt formatters was super easy). output is

alt: @(35 hae (m))
alt msl: @(35 msl (m))

so it didn't apply the offset. Just trying to figure out what I don't have defined correctly and if this all makes sense. I may not be able to define AltMSL this and have to have a non-constexpr function that does the conversion at runtime.

@jasonbeach
Copy link
Author

jasonbeach commented May 17, 2023

The only kind of weird thing is that altitude is still measured in meters not haes or msls, but that's just a semantic / terminology issue which I don't think affects using the library.

so it occurred to me this even the reason why this is weird is because there is no quantity called hae or msl. The quantity is meters or feet--it's the quantity_point is msl or hae. So I would want to define something like this:

constexpr au::QuantityPointMaker<au::Meters> alt_hae_pt {};

Although that wouldn't work because origin is part of the quantity and not quantity point and also because similarly defining a QuantityPointMaker<au::Meters> alt_msl_pt wouldn't be a distinct type. I guess that's the kind of issue the QuantityKind type in mp-units is trying to address.

@chiphogg
Copy link
Contributor

This isn't an issue per se, but a continuing of the conversation from mp-units (mpusz/units#457).

Only reason I bring it here is I'd like to start using this at work and I get the idea that mp-units is still a work in progress and probably not ready for integration into a production system, whereas Au seems to be.

Yep, I agree that Au is more production-ready, with more stable interfaces. While there are some really cool interfaces/features I'd like to add someday, there's zero appetite for breaking changes for the foreseeable future.

Just wondering if you could hopefully give a few quick pointers on the QuantityPoint types

I think Fahrenheit and Celsius actually provide a much better analog than I was originally thinking: 0 HAE (height above the WGS84 ellipsoid) is like 0 Kelvin (i.e. the absolute datum) 0 meters MSL (mean sea level) is like 0 Celsius (unit size is same (meters) but just offset from absolute datum) 0 feet MSL is like 0 Fahrenheit. (unit size is scaled and offset from absolute datum)

Yes, I think that's a really good analogy! The only thing to keep in mind is that knowing the offsets at compile time is a hard requirement for using QuantityPoint to model them, and it probably always will be. If the geoid undulation isn't constant, but varies by location, then unfortunately that's enough to rule out QuantityPoint.

The only kind of weird thing is that altitude is still measured in meters not haes or msls, but that's just a semantic / terminology issue which I don't think affects using the library.

So I've defined:

struct AltHAE : Meters {
  static constexpr inline const char label[] = "hae (m)";
};
constexpr au::QuantityMaker<AltHAE> alt_hae {};
constexpr au::QuantityPointMaker<AltHAE> alt_hae_pt {};

struct AltMsl : AltHAE {
  static constexpr const char label[] = "msl (m)";
  constexpr auto origin() { return geoid_undulation_; };
  static constexpr au::QuantityD<au::Meters> geoid_undulation_ = meters(10.0); // this number shouldn't be fixed, it varies by lat/lon.
};
constexpr au::QuantityMaker<AltMsl> alt_msl {};
constexpr au::QuantityPointMaker<AltMsl> alt_msl_pt {};

With this

  const auto alt = alt_hae_pt(35.0);
  fmt::print("alt: {}\n", alt);
  fmt::print("alt msl: {}\n", alt.as<double>(alt_msl_pt));

(translating the ostream operators to fmt formatters was super easy). output is

alt: @(35 hae (m))
alt msl: @(35 msl (m))

so it didn't apply the offset. Just trying to figure out what I don't have defined correctly and if this all makes sense. I may not be able to define AltMSL this and have to have a non-constexpr function that does the conversion at runtime.

You were close! Just make the origin() operator also static:

  static constexpr auto origin() { return geoid_undulation_; };

This should get everything working as you expect in your toy example.


Now for the question: how can you meet your actual needs, in a world where the conversion between your quantities varies?

I think you're right that the "quantity kind" work from mp-units handles this problem beautifully. I've been "kind-skeptical" for a long time, but recently I've come around to thinking that the approach in mp-units, at least, will probably actually work well! I'm looking forward to seeing whether real-world usage over the next couple years bears that out.

I don't think Au will support "kinds" or "frames" any time in the next couple years at least. I would expect #85, #90, #105, #110, and #122 to be done before we'd consider a major undertaking like kinds, because these are all significant enhancements which are also fairly straightforward to do. And the next "big" project we are likely to attack is #70.

There is some small chance that the best "frames" solution would be orthogonal to the vector/matrix aspects of #70, which would mean we could apply it to scalars. Now that I think of it, we would have some use cases for "scalar frames", not just vector frames. But figuring that out would mean actually attempting #70, which we don't expect to have the bandwidth to do for quite some time.

One more immediate approach you could take for frames would be a generic strong-type library. I think foonathan/type_safe would be a great choice, because you could easily add the desired arithmetic operations via mix-ins. This would also decouple your frame handling from your choice of units library, which could be nice, at the cost of a little extra friction in the interfaces.

@chiphogg chiphogg added the discussion Discussing the library. Not necessarily representing work to be done label May 20, 2023
@jasonbeach
Copy link
Author

The only thing to keep in mind is that knowing the offsets at compile time is a hard requirement for using QuantityPoint to model them, and it probably always will be. If the geoid undulation isn't constant, but varies by location, then unfortunately that's enough to rule out QuantityPoint.

Yep I figured that out. Kind of a bummer since that would've worked really nice. Another example of a non-compile time unit conversion that would be useful is in camera focal length. Some gimbals we've use publish their focal lengths in mm but almost all the math is done with the focal length in terms of pixels and the conversion between mm and pixels obviously varies by camera.

This last week I pulled Au into a branch of the main common repository I've been working on and for the most part it went really smoothly. I left the altitudes basically as is (with only the type changed to a meters quantity instead of just a double). The WGS84 conversions between Lat/Lon / ECEF / and NED were slick as anything. The only annoying thing was that the Approx class type of the Catch2 unit test framework we use didn't handle the units so I wound up placing a lot of .in(au::meters).

I would agree that having #70 as the next big thing (even if not for a while) makes sense. I'm still parsing through Daniel's 2022 CppCon talk.

@chiphogg
Copy link
Contributor

Unfortunately, the unit test framework error is depressingly common. There's a longstanding issue (google/googletest#890) for googletest as well. I've tried to find some reasonable solutions (google/googletest#2077 (comment)), but Google has maintained radio silence for years in the face of repeated pings. I think they've given up on fixing it.

The most frustrating aspect (with both googletest and, apparently, Catch2) is that if the utilities were written in the most natural way, just using the comparison operators directly, they would have supported libraries like this out of the box, with no special casing!

Oh well. For googletest at least, I made a custom matcher that fills this need. In some ways, it's even better than a correctly written native version would have been, because the difference gets printed in units of the tolerance. (So passing meters(1e-3) for the tolerance prints them in m, but passing milli(meters)(1), while the same tolerance, prints them in mm.)

I assume Catch2 has some extension features. Maybe you can borrow some of these ideas and improve the ergonomics of your tests?

@chiphogg
Copy link
Contributor

I'm going to close this to keep our Issues page tidy, but feel free to reopen if you'd like to continue the conversation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Discussing the library. Not necessarily representing work to be done
Projects
None yet
Development

No branches or pull requests

2 participants