r/cpp 6d ago

Au (units) 0.6.0 out: blockbuster release!

https://github.com/aurora-opensource/au/releases/tag/0.6.0

It's been just over a year since our last significant release, and this one ended up big... honestly, maybe a bit over-stuffed. πŸ˜… We did tackle both of the biggest requests from the last post: we no longer swallow compiler errors (see our new compiler warnings philosophy doc for more details), and we have worked examples. Besides these, some of the remaining highlights are:

  • Full vector and matrix support: everything except mixed-units in a single vector/matrix
  • First-class Eigen support --- the first units library to preserve Eigen's full performance in all cases
  • CUDA (and HIP) are now supported out of the box
  • More powerful/flexible user-defined literals compared to other libraries (see this fascinating Abbreviated Quantity Construction discussion doc for the nuances here)
  • More ergonomic integer division: divide_using_common_unit(a, b) is almost always what you want for same-dimension inputs
  • Constant and Magnitude now get arithmetic and comparison operators whenever the results are computable, making them much more ergonomic

We also refreshed our C++ units library comparison page. It's awesome to see all the progress on the other leading libraries, as well as ours!

We hope you find the new release useful and fun, and we're excited to hear any feedback you may have!

41 Upvotes

10 comments sorted by

β€’

u/STL MSVC STL Dev 6d ago

Mod-approved.

9

u/chiphogg 6d ago

If I could highlight just one thing for existing Au users, it'd be: bite the bullet and upgrade; all future upgrades will be easier once you do.

Yes, there's a decent amount of breaking changes in this release, but we bend over backwards to enable incremental upgrades.

  • Every breaking change has a syntax that works on the old and new versions.
  • We include "future proof" release artifacts to surface upcoming breakages early, so you can update your callsites incrementally, and make the final "version bump" commit as small as possible.

See our upgrade guide for more details.

Note that after this release, we are moving to a 4 month "release train" cadence, so we're very unlikely to have anywhere near this size of release ever again!

If you hit any snags upgrading, please get in touch. Feel free to file an Au issue, or even DM me here. I want to help people make use of all these new features we worked so hard on! πŸ™‚

3

u/PunctuationGood 3d ago

In that context, what does "version 0.6.0" mean? Given that you're encouraging consumers to upgrade regardless and, presumably, continuously upgrade three times a year, perhaps a versioning of the type [year].[1/2/3] would provide better information?

Otherwise, I'm temped to wait for you to version your project "1.0" which I would take as a token of confidence and fewer expected upgrade headaches.

2

u/chiphogg 3d ago

Our version numbers have always been semver, although I will say it's my first major open source project, so I've been learning best practices as I go. Rereading semver's 0.y.z rules, I can see they seem a lot scarier (anything changing at any time; public API not stable) than how we actually think of the library.

I just looked at our open issues, and none of them break idiomatic 0.6.0 code. The closest to "breaking" are the 0.6.0-future ones, covering old idioms that are still possible to write in 0.6.0, but not encouraged. By the next release, those should all be gone. So, concretely: users who upgrade to 0.6.0, avoid T-suffixed names (i.e., write UnitQuotient, not UnitQuotientT, etc.), and avoid coerce_as and coerce_in (already officially deprecated) will be in great shape. The only remaining hurdle would be ScalarOf becoming required in the next release, and that only affects users going beyond the usual reps (arithmetic types, Eigen, std::complex, and more should be fine).

I had been thinking of 1.0 as a sign that all planned features were complete. But you're helping me realize that's not the right bar. If users are taking pre-1.0 as a signal for stability, then it probably makes sense to pull forward the 1.0 label to the first release where we can make that promise. (Re-reading semver, it's clear that, yeah: we should be 1.0 sooner rather than later.) I'll bring it up with the team in our next meeting.

Meanwhile, at least you know the incentives are aligned! πŸ˜… Au is ubiquitously used in Aurora's monorepo, and I'm the one on the hook for upgrading it when we cut a new Au release. I would like to make my life as easy as possible in this regard.

4

u/PsecretPseudonym 5d ago

Looks great. Any sense on how the latest version of Au compares with that of mp-units?

I’ve been considering both, and they seem similar but rapidly evolving, so it’s challenging to keep track of how to consider/choose presently.

3

u/chiphogg 4d ago

By the way: if I could wave a magic wand, and bring in any features I wanted from mp-units? There is only one: composable type names. Before C++20, all of our options are awkward. Au can write 3.5f * m / s, but we can't write QuantityF<m / s>, and it breaks my heart.

(I would also like to get some kind of "quantity kind" support, but instead of taking the mp-units approach wholesale, I'm musing about looking for some simpler approach that provides 80% to 90% of the value.)

3

u/chiphogg 4d ago

We do have our full alternatives comparison, which has been recently updated. Here's my overall high level take.

First, this comment does a good job of comparing the different regions in design space that mp-units and Au occupy. mp-units is a full quantities library: hierarchies of different "kinds" of the same unit, building in physics equations, trying to keep track of vector vs tensor character for both inputs and outputs, etc. Au focuses on the core bits only --- dimensions and magnitudes --- and aims to really knock it out of the park in how we handle them. This means there are things mp-units can do that Au can't (such as preventing passing a "height" to a function expecting a "width"), but Au is much leaner while still handling the overwhelmingly most important use cases.

Now for more detail. Reasons to choose Au include:

  • "Core" reasons (structural; not likely to change):
    • If you're on C++14 or C++17, game over: Au is clearly best. (Doesn't sound like this applies to you, but I mention it for others reading this.)
    • Compile time cost: Au is close to best, mp-units is actually the worst (although C++20 modules may help with this if your project can use them). You'll see this, for instance, in mp-units godbolt links: you can really feel the slowness, and I often see them time out before being able to produce a result.
    • Smooth incremental upgrade paths over time. Au is committed to always provide syntax that works in consecutive versions, as a bridge, because Aurora uses it widely in a huge monorepo (so our incentives are aligned). mp-units 0.8 -> 2.x badly broke users who had been treating it as production code. (I think 3.x is expected to have a smoother upgrade path, but I don't know for sure.)
    • If you need Eigen's full performance in all cases, only Au provides that.
    • Subjective, but I think Au's #include structure is better.
      • mp-units has more "top level" headers; Au has "au/au.hh" for the full core functionality, and either "au/io.hh" or "au/std_format.hh" (C++20 only) for I/O support.
      • Bringing in units is also easier with Au: it's just #include "au/units/foos.hh" (natural, IWYU approach), whereas mp-units forces you to know which system of units your unit is in. Au's choice is easier to use in practice, and maximally efficient for compile time.
  • Current advantages (where I could imagine mp-units upgrading to match):
    • CUDA support: Au supports CUDA out of the box, but mp-units probably could too. It wasn't a huge lift for us, honestly.
    • Ad hoc constants: Au's Constant is amazingly useful, because of its perfect conversion policy. With 0.6.0, it's turbocharged, because now you can do arithmetic and comparison with Constant objects for the subset of cases where we can compute exact results (which turns out to be a pretty big subset!). In practice, this means you can make new Constant objects on the fly. This is huge in embedded applications, when you're doing math with bits and bytes on board specs, and then storing the result in some small integer type. Oh, and 0.6.0 also makes it a lot easier to make those Constant objects, thanks to our new user-defined literals; see our abbreviated quantity construction doc for much more detail on the pros/cons vs. unit symbols.
    • If you need negative units, Au's currently the only library that supports them and gets all the comparison functions right. I think mp-units already has a partial implementation, though, so I expect them to come along here. (Pretty niche, obviously, but we did find real world use cases for them in some embedded boards and certain CAN bus messages!)

Reasons to choose mp-units include:

  • "Core" reasons (structural; not likely to change):
    • Composable type names: quantity<m / s, float> is awesome! Au's options are more awkward: QuantityF<decltype(Meters{} / Seconds{})> or QuantityF<UnitQuotient<Meters, Seconds>>. This is a limitation that only C++20 enables addressing.
    • Quantity kinds: mp-units is best in class by far. Many kind hierarchies built in, and it's easy to make more on the fly.
    • Absolute quantities is an upcoming feature that seems very promising. Au will never get this because: if you try constructing one with a value that is negative, what do you do? (We can't take a dependency on C++26 contracts.)
    • Vector/tensor character tracking: if you want this feature, it will always be mp-units only, since Au fundamentally takes a different philosophical approach: let the rep handle it, and keep the library simpler.
  • Current advantages (where I could imagine Au upgrading to match):

These are just off the top of my head, but I hope it gives a good overview, and besides, this post is getting long enough. πŸ˜…

2

u/SunnybunsBuns 4d ago

unit aware rounding

Does this have, or lead easily to having, the ability to wrap angel units? Either +/- pi or [0,2pi)? Obviously not a deal breaker, but would certainly keep a bunch of engineers here from foobaring their filter math.

1

u/chiphogg 4d ago

We tend to think of auto-wrapping angle types as a separate layer: something outside of Au, but built on top of it, which Au makes easier.

A key tool here is:

constexpr auto FULL_TURN = make_constant(revolution);

For integer-backed Quantity types, you can % against this (except with radians, milli(radians), etc., but that's obviously incoherent for wrapping). For floating point Quantity types, you'd need fmod, which I think is centred rather than zero-based, so you might have to do a normalization pass. Also, fmod doesn't accept a Constant directly today, so you'd have to pass FULL_TURN.as<R>(U{}) to turn it into a Quantity<U, R>. I filed #744 to track improving the ergonomics here.

2

u/DryEnergy4398 6d ago

Awesome! We love AU :-)