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

Support constants #90

Closed
chiphogg opened this issue Feb 6, 2023 · 3 comments · Fixed by #340
Closed

Support constants #90

chiphogg opened this issue Feb 6, 2023 · 3 comments · Fixed by #340
Labels
⬇️ affects: code (interfaces) Affects the way end users will interact with the library 📁 kind: enhancement New feature or request 💪 effort: medium
Milestone

Comments

@chiphogg
Copy link
Contributor

chiphogg commented Feb 6, 2023

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.

@chiphogg chiphogg added the 📁 kind: enhancement New feature or request label Feb 6, 2023
@chiphogg
Copy link
Contributor Author

chiphogg commented Mar 5, 2023

Having thought more about this: no, a constant should not just be an instance of a unit. A "unit" could be basically any type, so we'd need SFINAE, which would be expensive. This argues in favor of using a Constant template.

It's probably still good to keep this template as a monovalue type, and use a labeled unit.

@chiphogg chiphogg added ⬇️ affects: code (interfaces) Affects the way end users will interact with the library 💪 effort: medium labels Mar 26, 2023
This was referenced Jun 15, 2023
@chiphogg chiphogg added this to the 0.4.0 milestone Jun 21, 2023
@chiphogg
Copy link
Contributor Author

chiphogg commented Oct 21, 2023

Here's a running checklist for what it would take to call this feature "done". (I'll check things off as they're implemented locally in my client, rather than on remote.)

  • Multiplication and division
    • Regular numbers
    • Quantity
    • Magnitude
    • Other Constant
    • QuantityMaker
    • SingularNameFor
    • QuantityPoint (= delete)
    • QuantityPointMaker (= delete)
  • Explicit conversions to Quantity
    • .as<T>(): initialize with T{1} in units of the constant
    • .as<T>(unit): choose unit-and-rep
      • Exact representability checks (no "safety surface" heuristics)
    • .coerce_as<T>(unit): choose unit-and-rep, ignore safety checks
  • Explicit unit conversions to raw number
    • .in<T>(unit): choose unit-and-rep
      • Exact representability checks (no "safety surface" heuristics)
    • .coerce_in<T>(unit): choose unit-and-rep, ignore safety checks
  • Implicit conversions to Quantity
  • make_constant(...)
  • Math functions
    • min
    • max
    • clamp
    • fmod?
    • %
    • (Skip rounding functions because rep is undetermined, and people can use .as<T>() to get this)
  • AssociatedUnitT support (so v.in(m / s) would work)
  • Presentation
    • How to name constants? c vs. C vs. SPEED_OF_LIGHT; h (or h_bar) vs. H (or H_BAR) vs. PLANCK_CONSTANT; etc.
    • Use constants sub-namespace?
    • Update single-file script to include constants
  • Documentation
    • Reference page
    • Update single-file section of installation docs
    • Alternatives page
    • If we have a list of common monovalue types, add this to the list
    • Howto page: add a new constant

chiphogg added a commit that referenced this issue Oct 24, 2023
Right now, `get_value<T>(m)` for a magnitude `m` either produces the
requested value, or invokes a hard compiler error via `static_assert`.
It would be nice if we had a way to ask whether this operation would
succeed.  For this purpose, we now provide `representable_in<T>(m)`.

It turns out that we can't actually answer this question without
effectively doing all of the work to compute the value.  We won't want
to do this twice, because that would uselessly slow down compile times.
Therefore, we extract all of our logic into a common implementation
function.  The logic is basically equivalent to the pre-existing version
of `get_value<T>(m)`, except that instead of each `static_cast`, we
return a unique value of a new `enum` we created for this purpose.

This means that each function can get its result efficiently.  For
`get_value`, we change the trigger for each `static_assert` to the
corresponding enum value.  For `representable_in`, we simply check
whether that enum has the `OK` value.

This PR helps pave the way for #90.  When we make our `Constant` class
implicitly convertible to `Quantity` types, it turns out that we can do
better than the "overflow safety surface"... because we know the exact
value!  We can achieve perfect compile-time checking.  This PR will give
us a single function that we can call in our implementation for that.

Includes docs and tests.

---------

Co-authored-by: Geoffrey Viola <geoffviola@users.noreply.github.com>
@geoffviola
Copy link
Contributor

We may want to add some documentation for how to add a new constant, after we settle on the presentation.

SagaciousB pushed a commit to SagaciousB/au that referenced this issue Nov 22, 2023
Constants (aurora-opensource#90) and symbols (aurora-opensource#43) have a lot of similarities.  For
example:

- Multiplying by a raw number produces a quantity.
- Multiplying by a quantity changes the units of the quantity.
- They can compose with other instances of the same family (e.g., the
  product of two constants is a constant).

Let's use the term "wrapper" to refer to constants and symbols here. To
make each wrapper as easy as possible to implement, I've created some
"mixin" classes, each of which adds the full set of multiplication and
division operations for a single combination of the wrapper and some
other family of types (raw numbers, quantities, other wrappers, etc.).

These wrappers use a "CRTP-ish" syntax.  But instead of the _type
itself_ being the first template parameter (e.g., `Wrapper<Unit>`), we
provide the _wrapper_ and _unit_ as two separate parameters (i.e.,
`Wrapper` and `Unit`).  The reason is that we need to know the `Unit`,
and providing it explicitly makes it easy to get.  These first two
parameters are the same for all mixins, which makes the list-of-mixins
at the class definition easier to read.

The unit tests are based on an example, `UnitWrapper`, which aggregates
a variety of properties.  The forthcoming "true" classes, `Constant` and
`SymbolFor`, will be defined very similarly to this.

Finally, a word about the plan for "numeric" inputs.  For now, we are
restricting to `std::is_arithmetic`.  This doesn't mean we don't support
other reps; it just means we won't be able to create quantities from
them via constants or symbols for a while.  The evolution plan is to
create a well-defined concept that defines what is a valid rep, and then
replace `std::arithmetic` with that concept (see also aurora-opensource#52).

Test plan:

- [x] Add extensive new unit tests
- [x] Manually uncomment each individual "uncomment to test" case
SagaciousB pushed a commit to SagaciousB/au that referenced this issue Nov 28, 2023
This is the basic machinery for representing physical constants.  Later
on, we plan to include `Constant` instances in the library out of the
box, but first we want to explore the feature in production inside
Aurora's internal repo.

The first set of basic features is to multiply and divide a wide variety
of types.  When we do this, the operations always take place _at compile
time_, and symbolically.

The next set of features is various kinds of conversions to `Quantity`
types and/or raw numbers.  The standout feature here is the _perfect
conversion policy_: since each `Constant` fully encodes its value in the
type, we know exactly which `Quantity<U, R>` instances it can convert
to.

Doc updates are included: I added a new reference page for `Constant`. I
also updated the alternatives page.  A couple libraries that were
previously "good" are now "fair", because they use quantities for their
constants, which is sub-optimal.  mp-units moved from "best" to "good"
because I think on balance we're now tied. On the one hand, they have
actually included pre-defined constants.  On the other hand, our core
implementation is better because we also support converting to
`Quantity` types.  Once we include constants out of the box, I expect
we'll be "best".

Finally, I re-alphabetized the BUILD rules.  This basically meant simply
moving `//au:operators` to its rightful place; not sure how it ended up
way up in the "C" section in the first place.

Helps aurora-opensource#90.
@chiphogg chiphogg modified the milestones: 0.4.0, 0.3.6 Jul 24, 2024
chiphogg added a commit that referenced this issue Nov 23, 2024
Specifically, we add support for `min`, `max`, `clamp`, and `%`.

It turns out that the most economical way to do this is via hidden
friends.  The downside is that this change is invasive to `Quantity`,
whereas we'd generally rather add functionality from the outside.  But
the upsides are that we get to remove a fair bit of extra special casing
that we had done for `Zero` overloads, and even some disambiguating
overloads.  Not only that, but we can now support some combinations that
we hadn't added before, simply because it would have been too much work!
Here's how it works.

The hidden friend approach covers us whenever somebody calls a function
with two exactly-identical types, _or_ whenever _one_ of the types is an
exact match, and the _other_ can _implicitly convert_ to it.  This lets
us cover all "shapeshifter types" --- `Zero`, `Constant` --- at one
stroke.  It even automatically covers _new shapeshifter types we don't
know about_: anything implicitly convertible to `Quantity` will work!

For the `min` and `max` implementation, I went with the Walter Brown
approach where `min` prefers to return `a`, and `max` prefers `b`.  This
is the most general and correct approach w.r.t. how it handles "ties",
although in our specific case this doesn't matter because we're not
returning a reference.  Still, I'm glad to put one more example of the
Right Approach out in the wild, and I prefer it to a call to `std::min`
because it doesn't force us to take a direct dependency on `<cmath>`.

We have two "disambiguating" overloads remaining in `math.hh`, both
applying to `QuantityPoint`: one for `min`, one for `max`.  I decided
not to add hidden friends there, because the cost of an invasive change,
plus the cost of moving these implementations far from the other
overloads in `math.hh`, outweighs the smaller benefits we would obtain
in this case.

Helps #90.  At this point, the `Constant` _implementation_ is feature
complete, and all we need to do is add concrete examples of `Constant`
to our library, updating the single-file package script and
documentation!
chiphogg added a commit that referenced this issue Nov 23, 2024
Specifically, we add support for `min`, `max`, `clamp`, and `%`.

It turns out that the most economical way to do this is via hidden
friends.  The downside is that this change is invasive to `Quantity`,
whereas we'd generally rather add functionality from the outside.  But
the upsides are that we get to remove a fair bit of extra special casing
that we had done for `Zero` overloads, and even some disambiguating
overloads.  Not only that, but we can now support some combinations that
we hadn't added before, simply because it would have been too much work!
Here's how it works.

The hidden friend approach covers us whenever somebody calls a function
with two exactly-identical types, _or_ whenever _one_ of the types is an
exact match, and the _other_ can _implicitly convert_ to it.  This lets
us cover all "shapeshifter types" --- `Zero`, `Constant` --- at one
stroke.  It even automatically covers _new shapeshifter types we don't
know about_: anything implicitly convertible to `Quantity` will work!

The one downside is that using the unqualified forms of `min`, `max`,
and `clamp`, goes from "recommended" to "mandatory".  We found some
instances of this in Aurora's code from testing this PR; they were
easily fixed by changing `au::min(...)` to `min(...)`, etc.

For the `min` and `max` implementation, I went with the Walter Brown
approach where `min` prefers to return `a`, and `max` prefers `b`.  This
is the most general and correct approach w.r.t. how it handles "ties",
although in our specific case this doesn't matter because we're not
returning a reference.  Still, I'm glad to put one more example of the
Right Approach out in the wild, and I prefer it to a call to `std::min`
because it doesn't force us to take a direct dependency on `<cmath>`.

We have two "disambiguating" overloads remaining in `math.hh`, both
applying to `QuantityPoint`: one for `min`, one for `max`.  I decided
not to add hidden friends there, because the cost of an invasive change,
plus the cost of moving these implementations far from the other
overloads in `math.hh`, outweighs the smaller benefits we would obtain
in this case.

Helps #90.  At this point, the `Constant` _implementation_ is feature
complete, and all we need to do is add concrete examples of `Constant`
to our library, updating the single-file package script and
documentation!
chiphogg added a commit that referenced this issue Dec 2, 2024
For Au's built-in constants, we follow the exact same policies as for
units, including:

- A new include folder, `"au/constants/..."`
- A new target, `"//au:constants"`, which globs headers from that folder
- Corresponding unit tests
- Inclusion in the single-file script by individual names
- An `--all-constants` option for the single-file script

We _don't_ provide `_fwd.hh` files, because there's nothing we could
really forward declare.

Constant objects are defined with spelled-out names in `ALL_CAPS`
format.  The corresponding file is the snake-case version.  This keeps
the constant itself unambiguous.  We expect end users to actually use
them in the following manner:

```cpp
constexpr auto c = au::SPEED_OF_LIGHT;
```

Finally, we now mention new constants in the release notes.

Helps #90.  Remaining work includes adding more constants, and adding
documentation.
chiphogg added a commit that referenced this issue Dec 2, 2024
For Au's built-in constants, we follow the exact same policies as for
units, including:

- A new include folder, `"au/constants/..."`
- A new target, `"//au:constants"`, which globs headers from that folder
- Corresponding unit tests
- Inclusion in the single-file script by individual names
- An `--all-constants` option for the single-file script

Oh, and while I was updating the single-file script, I noticed a slight
"bug": ever since we started providing `_fwd.hh` files for the units,
the single file script was treating those files as their own units.
This doesn't _hurt_ anything, but it's just a little silly (see image).
This PR fixes that bug as well.


![image](https://github.com/user-attachments/assets/22381e3e-4785-4c69-8ae9-94e4ff7c7c41)

We _don't_ provide `_fwd.hh` files for _constants_, because there's
nothing we could really forward declare.

Constant objects are defined with spelled-out names in `ALL_CAPS`
format.  The corresponding file is the snake-case version.  This keeps
the constant itself unambiguous.  We expect end users to actually use
them in the following manner:

```cpp
constexpr auto c = au::SPEED_OF_LIGHT;
```

Finally, we now mention new constants in the release notes.

Helps #90.  Remaining work includes adding more constants, and adding
documentation.
chiphogg added a commit that referenced this issue Dec 2, 2024
Turns out, using `IToA` for magnitudes was a little bit sloppy.
Magnitude values are always unsigned, which means there are meaningful
values that won't fit inside of an `int64_t`.  Fortunately, adding all
those constants (#336) was a great stress test to expose this.

I think the most natural fix would be to add a `UIToA`, which can't
handle signed values, but which _can_ handle the "upper half" of
`uint64_t` values.  This way, we can have `IToA` delegate to `UIToA` for
the "integer magnitude" parts of the logic.

Why not just get rid of `IToA` altogether?  Well, we do need it for
printing exponents, which can be negative.

Helps #90: this gets the test to pass on #336.
chiphogg added a commit that referenced this issue Dec 2, 2024
Turns out, using `IToA` for magnitudes was a little bit sloppy.
Magnitude values are always unsigned, which means there are meaningful
values that won't fit inside of an `int64_t`.  Fortunately, adding all
those constants (#336) was a great stress test to expose this.

I think the most natural fix would be to add a `UIToA`, which can't
handle signed values, but which _can_ handle the "upper half" of
`uint64_t` values.  This way, we can have `IToA` delegate to `UIToA` for
the "integer magnitude" parts of the logic.

Why not just get rid of `IToA` altogether?  Well, we do need it for
printing exponents, which can be negative.

Helps #90: this gets the test to pass on #336.
chiphogg added a commit that referenced this issue Dec 2, 2024
All values were taken from [this section] of the wikipedia page on the
2019 revision of the SI.

Admittedly, two of them --- `Delta_nu_Cs` and `K_cd` --- have horribly
awkward names.  The best I could do for the constant names was
`CESIUM_HYPERFINE_TRANSITION_FREQUENCY` and
`LUMINOUS_EFFICACY_540_TERAHERTZ`, respectively.  That's fine; we expect
most users to do something like this in their programs:

```cpp
constexpr auto K_cd = au::LUMINOUS_EFFICACY_540_TERAHERTZ;
```

Helps #90.

[this section]:
https://en.wikipedia.org/wiki/2019_revision_of_the_SI#Defining_constants
chiphogg added a commit that referenced this issue Dec 2, 2024
We update the installation guide, both to point users to the headers for
the constants, and to tell them how to include constants in the
single-file script.  This made me realize that we had forgotten to make
constants available in `@au//au`, so I updated the BUILD file.

On the constant reference page, we now list the built-in constants.  I
like the way the table looks!

On the monovalue types page, constants are a great example of monovalue
types, so I add them as an example.

We also add a how-to guide for making new constants.

Finally, now that we include built-in constants with the library, I
think Au's constant support is now best-in-class, so I am updating our
Alternatives page to list us as such.

Fixes #90.
chiphogg added a commit that referenced this issue Dec 10, 2024
It took me a little while to figure out exactly what I wanted to do
here.

Initially, I was going to _deprecate_ the standard gravity unit.  Then I
realized we need it to define certain other units, such as the
correspondence between "pounds" as a force and a mass --- and I didn't
want to introduce a circular dependency between `:units` and
`:constants`.

Then I was going to deprecate only the _quantity maker_,
`standard_gravity`.  But then I realized it would be weird to create a
situation where certain units have quantity makers, and certain other
ones don't.

In the end, I think the right move is to just provide standard gravity
as a new constant.  This is the preferred way to interact with it, but
the old ways will still be there for the foreseeable future.

Follow-on to #90.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⬇️ affects: code (interfaces) Affects the way end users will interact with the library 📁 kind: enhancement New feature or request 💪 effort: medium
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants