Giter Club home page Giter Club logo

aurora-opensource / au Goto Github PK

View Code? Open in Web Editor NEW
293.0 7.0 18.0 6.85 MB

A C++14-compatible physical units library with no dependencies and a single-file delivery option. Emphasis on safety, accessibility, performance, and developer experience.

License: Apache License 2.0

Shell 0.78% Starlark 3.94% C++ 93.54% Python 1.74%
cpp cpp14 cpp14-library dimensional-analysis physical-quantities quantity units bazel header-only header-only-library

au's Introduction

Au library logo

clang14-ubuntu clang11-ubuntu gcc10-ubuntu MSVC x64 19.29 MSVC x64 19.35

Au: A C++14-compatible units library, by Aurora

Au (pronounced "ay yoo") is a C++ units library, by Aurora. What the <chrono> library did for time variables, Au does for all physical quantities (lengths, speeds, voltages, and so on). Namely:

  • Catch unit errors at compile time, with no runtime penalty.
  • Make unit conversions effortless to get right.
  • Accelerate and improve your general developer experience.

In short: if your C++ programs handle physical quantities, Au will make you faster and more effective at your job. You'll find everything you need in our full documentation website.

Try it out on Compiler Explorer ("godbolt")!

Why Au?

There are many other C++ units libraries, several quite well established. Each of them offers some of the following properties, but only Au offers all of them:

  • Wide compatibility with C++ versions (anything C++14 or newer).
  • Easy installation in any project (including a customizable single-header option).
  • Small compile time burden.
  • Concise, readable typenames in compiler errors.

We also provide several totally new features, including fully unit-safe APIs, an adaptive "safety surface" that protects conversions against overflow, unit-aware rounding and inverse functions, and many more.

Forged in the crucible of Aurora's diverse, demanding use cases, Au has a proven track record of usability and reliability. This includes embedded support: Aurora's embedded teams have been first class customers since the library's inception.

To learn more about our place in the C++ units library ecosystem, see our detailed library comparison.

Getting started

Our installation instructions can have you up and running in minutes, in any project that supports C++14 or newer.

To use the library effectively, we recommend working through the tutorials, starting with Au 101: Quantity Makers. To get set up with the tutorials β€” or, to contribute to the library β€” check out our development guide.

As seen at CppCon 2021

At CppCon 2021, we presented the properties we found to be most important in units libraries, and advice on using them effectively. Because Au was designed from the ground up with these best practices in mind, it thoroughly exemplifies them. Check out the video below, and follow along with the slide deck if you like.

Chip Hogg's CppCon 2021 Aurora units talk

NOTE: This open-source version has been significantly improved from what was presented in the talk: both in its user interfaces, and under the hood! The one downside is that matrix and vector support hasn't yet been implemented. See #70 for more details, and subscribe to that issue to watch for progress.

au's People

Contributors

chiphogg avatar geoffviola avatar hoffbrinkle avatar matthew-reynolds avatar philsc avatar timhirsh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

au's Issues

Document angles

As with most every other mature units library, we've realized that angles should be included as first class participants in dimensional analysis. This conflicts with the widely held assumption that angles are "really" or "intrinsically" dimensionless. We owe it to our users to explain this situation. The specific deliverables should be:

  1. A "how-to" guide to explain precisely how to update equations with angular quantities. This should include a table with a column for the conventional formulae, and a column for each of the Leonard and Quincey conventions.

  2. A "discussion" page explaining:

    • Why the usual arguments that angles are "intrinsically" dimensionless are no good.
    • How to understand traditional equations: the "Quincey disclaimer".
    • In what sense we mean that angles are "intrinsically" dimensioned: not in any grand metaphysical sense, but in the same practical sense that justifies treating any other quantity as having dimension.
    • How to use the library if you insist on treating angles as dimensionless: "don't say radians if you don't mean radians".

Design solution for decibels and other "log-scale" units

This is a long-standing wish list item. We have some Aurora-internal design sketch notes which we could build on, but such as they are now, it's not really enough to begin in earnest.

The first step would be to propose the set of APIs by writing a series of usage examples. We would need to pay special attention to the distinction between "power" and "root power" units. Apparently, "decibel" encodes a different ratio depending on the dimension of unit it's applied to! Whatever APIs we design would need to satisfy this common usage while, critically, remaining "hard to use incorrectly".

We would also need to adhere to our library's core principle to avoid changing the values we store unless users explicitly request a conversion. For example: although I don't know the full set of API examples that would constitute an acceptance test, I'm pretty sure this is one of them:

EXPECT_THAT(deci(bels)(1) + bels(2), SameTypeAndValue(deci(bels)(21));

And this should be possible without ever leaving the integral domain. Explicitly, I want to rule out any solution candidates which convert the log-scale value to linear to store it.

This issue isn't likely to be the place where this design gets resolved; it's much too thorny a problem for that. We'll need to either make a Google Doc design doc, or else enable Discussions on this repo. Rather, the point of this issue is to say "yes, we're aware we don't have this; we have some ideas and plan to do it, but won't likely have time to get to it really soon".

Tighten up integer division checks on Quantity

This test fails:

EXPECT_EQ(hertz(100), 1 / milliseconds(10));

It should fail to compile. (i.e., the goal is to expand our integer division check to include the cases where only one argument is a Quantity.)

Document release procedure

We've done this 3 times, but I forget how exactly I did it. I need to write down the steps somewhere so that I can refer to them.

Guard Rep behind trait with static_assert

What if a user accidentally tries to form a Quantity whose Rep isn't an arithmetic type? Long-term, we'd like to support this by clearly defining the axioms a Rep has to fulfill. But there are some Rep types we'll never support (e.g., std::chrono::nanoseconds intrinsically makes no sense). It would be better to fail fast.

The fix here would be to add a static_assert on the Rep. We could check for is_arithmetic. We could also consider defining a trait which defaults to is_arithmetic, although maybe it's best to save that for when we are ready to support this for real.

Document migration facilities

Deliverables:

  • Document the general facilities, via "corresponding quantity".
  • Migrate our nholthaus/units conversion file, including the unit tests.
  • Add documentation page using nholthaus as worked migration example.

Improve error messages for dangerous conversion

Consider this code:

(meters / second)(1).in(miles / hour);

It produces this error message:

In file included from au/au_test.cc:15:
In file included from ./au/au.hh:19:
In file included from ./au/math.hh:20:
./au/quantity.hh:159:9: error: static_assert failed due to requirement 'implicit_rep_permitted_from_source_to_target<int>(unit, u)' "Dangerous conversion: use .as<Rep>(NewUnit) instead"
        static_assert(implicit_rep_permitted_from_source_to_target<Rep>(unit, u),
        ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./au/quantity.hh:181:20: note: in instantiation of function template specialization 'au::Quantity<au::UnitProduct<au::Meters, au::Pow<au::Seconds, -1>>, int>::as<au::UnitProduct<au::Miles, au::Pow<au::Hours, -1>>, void>' requested here
            return as(u).in(u);
                   ^
./au/quantity.hh:203:16: note: in instantiation of function template specialization 'au::Quantity<au::UnitProduct<au::Meters, au::Pow<au::Seconds, -1>>, int>::in<au::UnitProduct<au::Miles, au::Pow<au::Hours, -1>>, void>' requested here
        return in(NewUnit{});
               ^
au/au_test.cc:100:26: note: in instantiation of function template specialization 'au::Quantity<au::UnitProduct<au::Meters, au::Pow<au::Seconds, -1>>, int>::in<au::UnitProduct<au::Miles, au::Pow<au::Hours, -1>>>' requested here
    (meters / second)(1).in(miles / hour);
                         ^
1 error generated.

This could be better.

  1. Dangerous conversions basically always use integral rep, AFAIK. Users might not notice that they're using integers, and might not realize that Au performs unit conversions in the same type. It'd be nice if we could add hints about the integral rep being the part that makes the conversion dangerous.
  2. We tell users to use .as<Rep>(...), but we don't say instead of what. In this case, it's because .as is an implementation detail for .in. This means it looks to the end user as though we're saying to replace .in by .as! We should probably just link directly to the troubleshooting guide, so that we're not unconditionally guiding users to the explicit-Rep version when alternative solutions (such as just using 1.0 in this case) would be better.

Once we update the error messages, we'll need to regenerate the error messages in the troubleshooting guide as well.

[Question] Why was .as<Unit, Rep>() deprecated?

In our code base we pass around temperatures in uint32_t deci-kelvins since it gives us the right amount of precision we want without requiring floating point type, and the relevant range is contained within uint32_t, so we defined:

using DeciKelvins = au::QuantityPointU32<au::Deci<au::Kelvins>>;
constexpr auto decikelvins = au::deci(au::kelvins_pt);

But most of the temperature constants are expressed in degrees-Celsius since that is what we usually receive from EE teams and in general more human friendly.
This creates a bit of annoyance when wanting to convert these Celsius constant to DeciKelvins, since a lot of data needs to be repeated:

constexpr DeciKelvins MAX_CHARGING_THRESHOLD = celsius(43).as<DeciKelvins::Rep>(decikelvins);

Ideally I wish I could just write:

constexpr auto MAX_CHARGING_THRESHOLD = celsius(43).as<DeciKelvins>();

Since DeciKelvins already contains both the desired representation and the unit, everything is explicit and to point.
Also the use of actual type feels more natural with as and in as they are both act as some kind of casting mechanism.

Was there a specific reason to deprecate as<Unit, Rep>?
Would it be possible to bring it back, potentially with as<Quantity<Unit,Rep>>?
Or maybe I'm looking at this all wrong and the idiomatic usage of this library avoids this problem altogether?

Consider adding bernedom/SI to alternatives matrix

We weren't aware of this library when we made our initial comparison. Although it's a little harder to find, it does have >400 stars, so it may be worth including purely based on the high number of stars.

Support non-arithmetic Rep

As mentioned in #38, it's not our intention to restrict Rep to built-in arithmetic types forever. This issue tracks the idea of defining a separate trait which specifies exactly which properties are needed for a valid Rep.

Note that as part of this effort, we would need to check the impact on compile times.

Support value conversion with rational exponents

Au supports units with rational exponents (e.g., square-root meters). You can get values in and out. What you can't do is to perform conversions whose factor includes rational exponents. The reason is simple: we compute our conversion factors at compile time, and we don't have a constexpr version of std::powl. (We could perhaps get one from kthohr/gcem, but we really don't want to go from 0 to 1 external dependencies.)

Here's a simple "acceptance test" which gives an example of what this feature would enable.

constexpr auto geo_mean = sqrt(inches(1.0) * centi(meters)(1.0)).in(centi(meters));

This issue is resolved when this line compiles and gives a correct answer.

It's not anticipated to be a high priority any time soon. We've basically never needed this in our applications. I'm filing it to keep track of it for completeness' sake.

QuantityPoint usage for altitudes

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.

Solve most `[UNLABELED_UNIT]` instances by generating magnitude labels

Right now, the unit resulting from scaling a unit by a magnitude gets the default label, [UNLABELED_UNIT]. It's important that it not have the same label as the unscaled unit. Still, we can do much better if we design magnitude labels: automatic string constants that represent magnitudes.

Possible examples:

  • "1 / 18"
  • "Pi / 180"
  • "5,280"
  • "(3 * sqrt(3)) / (5 * Pi)"

To solve this, we would need to come up with a collection of canonical examples we can use as acceptance tests, and work out the general rules. We also need to figure out how to handle things like parentheses, powers, and roots.

Support abbreviated symbols

We never migrated these upstream! It'd be nice to have them in place before we announce the library.

We could include the UDL inside each "au/units" file, but that makes me wary.

  • Having split out units into individual files makes the cost of adding a new unit virtually zero. Literals could change that, because we would have to worry about literal collision (e.g., farads and fahrenheit might each be tempted to use _F).
  • Some literals correspond to units that don't have an "au/units" file, e.g., _MPH, _nm, _kg.
  • We've been doing really well on the "don't pay for what you don't use" front, and I'd like to keep that up.

So here's a proposal.

  1. Define the UDLs inside an "au/units/literals" file (or, perhaps, simply "au/literals"), exactly corresponding to the "au/units" file. So, either "au/units/literals/meters.hh", or "au/literals/meters.hh".
  2. The literals file would automatically include the units file. So, "au/units/literals/miles_per_hour.hh" would give "au/units/miles.hh" and "au/units/hours.hh".
  3. The single-file script would gain a new --literals argument which would act just like --units, but for the literals folder.
  4. The pre-built single-file packages would include literals.

Test basic functionality for wide variety of platforms/compilers

#143 has exposed a tooling deficiency. So far, we've assumed that simply "write correct C++14" is good enough to support everyone who's using a standard at least as recent as C++14. But that issue shows a compiler error in a product whose name includes "2019", which suggests this isn't true. It shows we're currently blind to our actual level of support for various platform/compiler combinations.

This issue has two components for a definition of "done":

  • Document our two "tiers" of platform/compiler support
  • Add measurements for a wider variety of platforms/compilers

As to the first, a platform/compiler is "fully supported" whenever we fulfill two conditions:

  1. We have automatic gating tests for that platform/compiler.
  2. It is easy for almost all end users to run those tests locally.

The motivation for the second condition is that working on mpusz/mp-units#300 was quite painful due to the gating MSVC jobs --- it ended up being delayed about 2 or 3 months because I had a bizarre error which I couldn't iterate on locally. (I think it turned out to be an actual compiler bug! πŸ˜…)

The second tier of support would be "best effort", where we have automatic tests to measure support for that platform, but we don't strictly require that those tests pass. If they fail, we need to make an effort to get them passing, but we won't guarantee that we'll block PRs on keeping them passing.

Currently, our only real support is for platforms that can offer a hermetic bazel toolchain, which includes two versions of clang and one of gcc. I believe we can make GitHub actions for a wider variety of platforms, including various versions of MSVC. If it's possible to run these GitHub actions locally, then we could significantly broaden the set of platforms/compilers that will be fully supported. That'll be the goal. Otherwise, we will still try to add them, but as "best effort".

Deprecate "bare" unit-named Quantity Makers for offset units

celsius(0) == fahrenheit(32) is false, because these are quantities. (celsius(5) == fahrenheit(9) would be true.)

This is all technically correct, but it's surprising for non-library-experts, and very bugprone.

In the spirit of "making APIs hard to use incorrectly", we should avoid using the bare unit name in code for offset units, because it's ambiguous. We have so far been using the _pt suffix for quantity point makers. We should add the _qty suffix for quantity makers where we need to emphasize the quantity-ness.

We can ease the transition by marking, e.g., celsius() and fahrenheit() as deprecated. In fact, we may want to keep them around as "permanently deprecated", because they are the natural things for users to try to use, and it'd be nice if users could get a direct message telling them what to do instead.

[Feature Request] Expose Quantity's underlying data to users

Hi,

I think it'd be useful to enable accessing a QuantityD<Meters>'s underlying storage address (i.e. the double it’s represented by), so that I can give it to a third party library that’s not units aware (e.g. ceres). For instance, lots of code in ceres does something like the following:

double distance_m;
ceres::optimize(&distance_m, data);

but I’d like to use units instead:

QuantityD<Meters> distance;
ceres::optimize(&distance.data(), data);

Note, ceres::optimize() is my made-up simplified version of this.

Document style guide

As pointed out in #20, our .hh extension differs from the Google style guide. We probably also want to make explicit that .clang-format is authoritative in all matters of formatting, especially since we're likely to change it before release so that it differs significantly from Google's formatting guide.

Consider separating "forcing" from explicit-Rep

I have always conflated these ideas, probably because static_cast does both. But it could be nice to be able to write, say, inches(50).force_as(feet): we wouldn't need to repeat the Rep, and the "force" makes the potential lossiness more obvious. This would free up q.as<T>(unit) to respect the safety checks.

Make doc website "core-complete"

This means no more stubs, no more TODO. There may be other pages we'd like to write --- such as other tutorials, or a discussion on angular units --- but we shouldn't be missing anything that's really part of the basic product.

Add contributing guidelines

It would be good to get something in place before we publicize the library widely. We can start with something rudimentary, and adapt it over time based on our experience.

Deprecate old-style .in, .as APIs

Despite the nostalgia of the syntax we used in the CppCon 2021 talk, we want to avoid having it appear in the wild. The following APIs for Quantity and QuantityPoint should be marked as deprecated, and later removed.

template <typename U>
constexpr auto as() const;

template <typename U, typename R, typename = std::enable_if_t<IsUnit<U>::value>>
constexpr auto as() const;

template <typename U>
constexpr auto in() const;

template <typename U, typename R, typename = std::enable_if_t<IsUnit<U>::value>>
constexpr auto in() const;

Each of the deprecation and the removal should cause a minor version bump. (We don't need a major version bump because we're still 0.x.)

clang11 toolchain may not be fully hermetic

Note: this is specifically on my dev laptop (Dell XPS 15 7590), not on my vdesk.

When I clone the repo, and build with clang14, everything is fine. However, when I build with clang11, I get surprising linker errors:

$ bazel test --config=clang11 //...:all 
WARNING: option '--platform_suffix' was expanded to from both option '--config=clang14' (source /home/chogg/au/.bazelrc) and option '--config=clang11' (source command line options)
INFO: Analyzed 3 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets and 1 test target...
ERROR: /home/chogg/au/au/BUILD:11:8: Linking au/zero_test failed: (Exit 1): cc_wrapper.sh failed: error executing command external/llvm_11_toolchain/bin/cc_wrapper.sh @bazel-out/k8-fastbuild-clang11/bin/au/zero_test-2.params

Use --sandbox_debug to see verbose messages from the sandbox and retain the sandbox build root for debugging
ld.lld: error: undefined symbol: lzma_stream_footer_decode
>>> referenced by elfxx.c:194
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a

ld.lld: error: undefined symbol: lzma_index_buffer_decode
>>> referenced by elfxx.c:201
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a

ld.lld: error: undefined symbol: lzma_index_size
>>> referenced by elfxx.c:205
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a

ld.lld: error: undefined symbol: lzma_index_end
>>> referenced by elfxx.c:210
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a
>>> referenced by elfxx.c:210
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a

ld.lld: error: undefined symbol: lzma_index_uncompressed_size
>>> referenced by elfxx.c:207
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a

ld.lld: error: undefined symbol: lzma_stream_buffer_decode
>>> referenced by elfxx.c:278
>>>               elf64.o:(_Uelf64_get_proc_name_in_image) in archive /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../x86_64-linux-gnu/libunwind.a
clang: error: linker command failed with exit code 1 (use -v to see invocation)
INFO: Elapsed time: 0.218s, Critical Path: 0.08s
INFO: 2 processes: 2 internal.
FAILED: Build did NOT complete successfully
//au:zero_test                                                  FAILED TO BUILD

FAILED: Build did NOT complete successfully

The first thing I thought was that it was inappropriately using some system package. So I ran sudo apt install liblzma-dev. It did update some packages, but this didn't make any difference.

In looking at the error, I think it's interesting that we're referencing system files for gcc (note: /usr/lib/gcc/...), even though we're supposed to be using a hermetic version of clang.

Indicate relative magnitudes in common unit labels

A core part of Au's philosophy is "no preferred units". So when we subtract, say, MPH from KPH, the result is labeled as essentially "the common unit of MPH and KPH": COM[km / h, mi / h].

I think this is a mixed bag. It's good that we don't conjure up any unmentioned units. COM[km / h, mi / h] does unambiguously identify what unit is being used. But this isn't a commonly used unit, and it's clearly bad that the label doesn't give any clue as to its magnitude, relative to any other unit.

I've found a new candidate solution. We can express the common unit in terms of each constituent unit --- and, for clarity, replace the , with ==. For example: COM([1 / 15625] km / h == [1 / 25146] mi / h).

  • PRO: Doesn't put any unit on special footing.
  • PRO: Clearly indicates the common unit's magnitude in terms of each constituent unit.
  • CON: A little verbose.

I think this is a clear improvement. Common-unit quantities generally only occur as the result of intermediate computations. They're not the kind of thing that end users are going to want to print, unmodified. But when they do end up being output, they'd better be unambiguous!

This solution depends on #85, because we need to be able to print magnitudes.

buildifier wrapper inserts "slowness" warning into files

I have buildifier-on-save integrated in vim. When I save a BUILD file, it inserts the explanatory message from #58 into the file itself, followed by a repetition of ^H ^H.

I believe we can fix this by writing the error message to the stderr stream.

Indicate to user when clang-format is building

tools/bin/clang-format is a thin wrapper around a bazel call. This is mostly good, since it lets us use a known hermetic version, and subsequent calls are fast.

We also suppress bazel's output to make it feel more like a "real" tool. However, this has a big drawback: the first run is slow (because bazel is running), but end users have no idea why.

It would be good to find some way to communicate when we're running.

This feels pretty hard in general. One idea would be to print a known string, something like,

Running bazel build @llvm_14_toolchain_llvm//:bin/clang-format...

and then echo backspaces to the terminal to "erase" this. It would be interesting to see how that works.

Include metadata comments in generated single files

It would be really nice if we could include:

  • The git ID (commit hash or tag)
  • Whether <iostream> support is included
  • The list of units included.

The git ID might be a little tricky, because it conflicts with bazel's sandboxing. It looks like workspace status is a mechanism for dealing with this use case.

Support matrices, vectors, and linear algebra

This was mentioned in our CppCon 2021 talk, but it hasn't yet actually made its way to the library. This is likely to be frustrating for many who found the library via the talk, so I wanted to give some backstory and share the future plans.

I had worked out the core details from the "units" side as far back as July 2021 (which is why it made it into the talk). However, before implementing a production version, I found Daniel Withopf's work (seen most recently in Daniel's CppCon 2022 talk). Besides the fact that it was essentially an existence proof for a production-quality solution, the most exciting aspect was that it also handled frames of reference. This was consistent with our internal experience that when it comes to matrices and vectors, supporting units is nice to have, but supporting reference frames adds much more value. At this point, reference frame support became an essential feature, which raised the bar for the production API design, to the point where it fell by the wayside.

In the long term, this may be for the best. In that intervening time, I wrote the new foundations and more fluent interfaces for the units library --- essentially, the parts that became Au. The matrix APIs we build on top of this stronger foundation will be much better and more usable than what we would have built in 2021. However, in the short term, it means we don't have any solution to offer, which I know is frustrating.

For clarity: this feature is still considered "in scope" for the library. I hope we will be able to provide an open source matrix solution that handles both units and reference frames, so the world can benefit. However, 2023 is a critical year for Aurora's product roadmap, so it's unlikely to happen in this year.

Meanwhile, if this is a feature you're interested in, please feel free to "vote" by reacting with πŸ‘ to help us gauge community interest!

Try cleaning up Dim and Mag members in Pow and RatioPow

This is very deep-in-the-weeds. It's not causing any actual problems as far as I'm aware, but it is a bit of a blemish.

It's a little weird that, say, Pow<Prime<2>, 2> has a Mag member, and a Dim member. They're basically just wrapped void in this case, but it's still bizarre. And it leads to a lot of entanglement among :packs, :magnitude, and :dimension.

Maybe we can make Pow inherit from some trait, and override the trait in the case of things-with-Dim and things-with-Mag?

Assuming we can do this and keep all tests passing, the critical gating factor will be its impact on compile time. If our solution causes really any regression at all, it's probably not worth it. After all, au is a units library, not a "generic metaprogramming for products-of-powers-of-bases" library.

Revisit inverse threshold of 1000

Right now, we permit taking inverses of integers as long as the value we divide into is at least 1,000. This works well for toy examples like the following:

inverse_as(hertz, milli(seconds)(5)); // Yields hertz(200)

However, it gets pretty bad for "medium" and "large" values.

inverse_as(hertz, milli(seconds)(73));  // Yields hertz(13), instead of ~13.699 Hz
inverse_as(hertz, milli(seconds)(251)); // Yields hertz( 3), instead of ~ 3.984 Hz (!)

In the overflow safety surface, we used the intuition that we want to be very safe for values up to 1,000, because users would tend to use the next prefixed unit for values much bigger than this. (We've since raised the threshold to over 4,000 in that case.)

Perhaps a similar intuition here would yield a threshold of 1,000,000 (inclusive)? In that case, none of the above examples would compile (with integer inputs). However, micro(seconds) would work. If we had these same underlying values for units of microseconds instead of milliseconds, we would obtain:

                                         // Result          True answer
inverse_as(hertz, micro(seconds)(5));    // hertz(200'000)  200,000.0 Hz
inverse_as(hertz, micro(seconds)(73));   // hertz( 13,698)   13,698.6 Hz
inverse_as(hertz, micro(seconds)(251));  // hertz(  3,984)    3,984.1 Hz

(Of course, with floating point inputs all of the above would work and would give accurate answers --- this issue is purely about tuning the safety surface for calculations that never leave the integer domain.)

Provide runtime convertibility checks

The overflow safety surface is pretty useful, but it's also just a heuristic. It can be too restrictive in some cases, and even perhaps too permissive in a few.

In practice, unit conversions should never happen in hot loops. Thus, it would be nice if every unit conversion could be checked at runtime. These checks can be very efficient. We can generate one at compile time for every conversion. For overflow risk, we can simply compare the actual runtime value to the (compile-time constant) threshold. And for truncation error, we can perform the mod operation.

Really, the only thing stopping us is: what do we do when the check fails? Different error handling strategies are appropriate for different domains. There is no "one true error handling strategy".

Fortunately, we can separate out two steps: there's the error handling, and then there's the checking as to whether it should trigger in the first place. For the latter, we can provide functions which simply return bool. Then each project can make their own "checked conversion" function that handles errors in their preferred way.

Support versioned documentation

Inspired by mpusz/mp-units#435.

I have done some initial exploration. I've understood how mike works, and I've got a proof of concept showing that I can get it running in our repo.

Here's what I think we'd need to accomplish to call this done.

  • The documentation has a version selector combo box at the top.
  • The release process has a new step to manually generate the documentation at that version.
  • main is the default label.
  • Every existing page redirects to the corresponding version on main.
  • Every commit to main automatically updates the docs stored there.
  • au-docs-serve still works, and shows the latest version of the content that would go to main.

So, this would change our URLs in the following way:

We could also consider stretch goals around warnings for old versions:

  • Add warning for concrete versions other than the latest.

Rename Magnitude numerator/denominator traits

numerator(m) gives the integer part of the numerator. This is counterintuitive. It should instead correspond to "the parts that a human would put on top of the fraction". Similar reasoning applies to denominator(m).

To fill the role played by the current versions of numerator and denominator, we can either make "integer"-named versions, or a generic integer_part(m) function which can compose with them.

Windows MSVC C++17 CMake/Ninja Compile Errors on prebuilt header

Using prebuilt https://aurora-opensource.github.io/au/0.3.2/au_noio.hh, I included the file in my project and I get this block of errors.

C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au\au.h(1247): error C2672: 'au::detail::are_consecutive_elements_in_order_for': no matching overloaded function found
  C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au/au.h(1247): note: see reference to class template instantiation 'au::AreElementsInOrder<Pack,Pack<Ts...>>' being compiled
C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au\au.h(1247): error C3207: 'au::detail::are_consecutive_elements_in_order_for': invalid template argument for 'Pack', class template expected
  C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au/au.h(1229): note: see declaration of 'au::detail::are_consecutive_elements_in_order_for'
C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au\au.h(1247): error C2977: 'au::detail::are_consecutive_elements_in_order_for': too many template arguments
  C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au/au.h(1219): note: see declaration of 'au::detail::are_consecutive_elements_in_order_for'
C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au\au.h(1261): error C2440: '<function-style-cast>': cannot convert from 'initializer list' to 'std::array<bool,sizeof...(Ts)>'
  C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au/au.h(1261): note: The target type has no constructors
  C:\Users\conno\Documents\SC\Platform\Compute_Server\externals\Au/au.h(1261): note: see reference to class template instantiation 'au::AreAllPowersNonzero<Pack,Pack<Ts...>>' being compiled

I am unable to figure out what the cause might be. It seems this may be the only problem with Au on this compiler, though, so it's valuable to know a fix or workaround.

Compile info:

  • Windows 10
  • RelWithDebInfo Configuration
  • Toolchain: msvc_x64_64
  • MSVC v142 - VS 2019
  • cl version 14.29.30133
  • CMake build through Visual Studio's CMake support (Ninja)
  • CMake configuration:
set(CMAKE_CXX_STANDARD 17) 
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

Avoid names which differ only by `T`

We strive to be "easy to use correctly; hard to use incorrectly".

Right now, one significant way the library is "easy to use incorrectly" is to do with very different type names that differ only by T. Examples:

  • UnitProductT and UnitProduct.
    • The former accepts any units in any order. The latter has strict rules for ordering and should only ever be auto-generated.
  • CommonUnitT and CommonUnit
    • Same considerations as above.

For UnitProduct, I could see using something like CompoundUnit. I don't have any good ideas for an alternative name for CommonUnit.

Fix edge cases in QuantityMaker composition

Known issues include:

  • We can't compose units whose singular name is the same as their plural name (e.g., hertz).
  • We can't compose singular names, even when this is more natural (e.g., we must write meters / radian / second, not meters / (radian * second)).

Even for cases which we correctly forbid, such as (newtons * meters): should this really be a compiler error? One alternative might be to add composition between two QuantityMaker instances, but mark it as [[deprecated]] and give a clear error message.

Plans for a v1.0.0 release?

Hi! I think this library looks wonderful, and I'm very interested in using it in my company. Do you have plans to release a stable v1.0.0? I'm guessing many "production" use-cases would require that. It would certainly make it easier for me to integrate au.

Migrate single-file script to hermetic python

When I wrote make-single-file, I didn't realize we were going to be getting a hermetic python version. We should migrate the script implementation to a proper python rule which uses the hermetic python we have. The script itself should change to bash, and become a thin wrapper around a bazel call, as we do for clang-format.

QuantityPoint::InheritsOverflowSafetySurfaceFromUnderlyingQuantityTypes test broken

This is one of those "uncomment to test" cases, because it is supposed to produce a hard compiler error. I uncommented it, and it didn't produce the error.

I think there are two contributing causes.

  1. We respect the underlying integer promotion rules of C++ (inherited from C). This means that the subtraction that takes place inside of the QuantityPoint conversion gets promoted to int when it takes place between two 16-bit types.

  2. Our origin happens to be defined with Rep int, which means the subtraction with a 16-bit Rep would produce int even if there weren't any integer promotion.

Impact

I don't see any safety risks here. It looks like just a small annoyance for people who are trying to use smaller integral Reps, and converting temperatures from one scale to another.

Remedy

The minimal remedy is to fix up the test case. I'll see if I can tackle the underlying behaviour as well.

Add moles and candelas

We will want to support all SI base units before public release. It will be easy to do so once we've finished splitting out units.hh into separate files.

Support constants

In some sense, we can already do this.

constexpr auto RAD = radians(1);

However, this brings int into the equation, via the 1. This could affect the arithmetic of the underlying Reps in some equations where it gets used. It could also run afoul of our guards against integer division.

It would be nice if we could express constants solely in the realm of dimensions and magnitudes. We might need to make a new type template, such as Constant. It would probably be a "monovalue type" (in the nomenclature introduced in #88). Perhaps something like this:

constexpr auto RAD = make_constant(Radians{});

Alternatively: maybe a "constant" is just an instance of a unit? We would need to enable multiplying and dividing a quantity by a unit, if so. Then we would write:

constexpr auto RAD = Radians{};

It's interesting to imagine how this would look with other constants. Consider something like $E = mc^2$. We might write kilo(grams)(10.0) * squared(C)... and the result might be labeled as 10 kg * c^2. Of course we could also apply .as(joules) to the result to get a more familiar unit.

This is an intriguing idea worth exploring more later.

Allow std::atomic usage of QuantityPoint with gcc-9

It appears to be impossible to define std::atomic variable of a QuantityPoint when using gcc-9 or older version.

Example:

#include <atomic>

using Kelvins = au::QuantityPointU32<au::Kelvins>;

int main()
{
    std::atomic<Kelvins> m_atomic_temp;
}

Output:

In function 'int main()':
<source>:5023:26: error: use of deleted function 'constexpr std::atomic<_Tp>::atomic() [with _Tp = au::QuantityPoint<au::Kelvins, unsigned int>]'
 5023 |     std::atomic<Kelvins> m_atomic_temp;
      |                          ^~~~~~~~~~~~~
In file included from <source>:5017:
/opt/compiler-explorer/gcc-9.5.0/include/c++/9.5.0/atomic:198:7: note: 'constexpr std::atomic<_Tp>::atomic() noexcept [with _Tp = au::QuantityPoint<au::Kelvins, unsigned int>]' is implicitly deleted because its exception-specification does not match the implicit exception-specification ''
  198 |       atomic() noexcept = default;
      |       ^~~~~~

In godbolt.org: https://godbolt.org/z/beG9Tfee3

On gcc-10 and up the above code compiles just fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.