Skip to content

Commit

Permalink
Add unit symbols (#196)
Browse files Browse the repository at this point in the history
This PR defines the mechanisms for symbols, using the new `SymbolFor`
monovalue type, and the `symbol_for` utility function.  We are starting
out conservative here, and only supplying operations for raw numbers,
quantity, and self-composition for now.  It's easy to add more later if
we want; easier than subtracting something we regret.

`SymbolFor` supports prefixes, too.  We can apply a prefix applier to an
instance, which makes it easy to create a symbol called `nm` as
`nano(m)` if we already have a symbol `m`.

We also update the docs.  For now, we just provide reference docs,
update the how-to, and update the comparison chart.  Since unit symbols
are simply better than UDLs, we bump the UDL libraries down from "good"
to "fair". mp-units gets bumped down from "best" to "good" because now
there is no "best".

Helps #43.
  • Loading branch information
chiphogg authored Nov 22, 2023
1 parent c20bf4e commit 02b7e1a
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 9 deletions.
20 changes: 20 additions & 0 deletions au/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ cc_library(
":quantity",
":quantity_point",
":unit_of_measure",
":unit_symbol",
],
)

Expand Down Expand Up @@ -304,6 +305,7 @@ cc_library(
":quantity",
":quantity_point",
":unit_of_measure",
":unit_symbol",
],
)

Expand Down Expand Up @@ -416,6 +418,24 @@ cc_test(
],
)

cc_library(
name = "unit_symbol",
hdrs = ["unit_symbol.hh"],
deps = [":wrapper_operations"],
)

cc_test(
name = "unit_symbol_test",
size = "small",
srcs = ["unit_symbol_test.cc"],
deps = [
":testing",
":unit_symbol",
":units",
"@com_google_googletest//:gtest_main",
],
)

cc_library(
name = "utility",
hdrs = glob(["utility/*.hh"]),
Expand Down
8 changes: 8 additions & 0 deletions au/prefix.hh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "au/quantity.hh"
#include "au/quantity_point.hh"
#include "au/unit_of_measure.hh"
#include "au/unit_symbol.hh"

namespace au {

Expand Down Expand Up @@ -46,6 +47,13 @@ struct PrefixApplier {
constexpr auto operator()(SingularNameFor<U>) const {
return SingularNameFor<Prefix<U>>{};
}

// Applying a Prefix to a SymbolFor instance, creates a symbolically-named instance of the
// Prefixed unit.
template <typename U>
constexpr auto operator()(SymbolFor<U>) const {
return SymbolFor<Prefix<U>>{};
}
};

////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
5 changes: 5 additions & 0 deletions au/prefix_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ TEST(PrefixApplier, ConvertsSingularNameForToCorrespondingPrefixedType) {
::testing::StaticAssertTypeEq<decltype(make_milli(inch)), SingularNameFor<Milli<Inches>>>();
}

TEST(PrefixApplier, ConvertsSymbolForToCorrespondingPrefixedType) {
constexpr auto X = symbol_for(XeroxedBytes{});
StaticAssertTypeEq<decltype(kibi(X)), SymbolFor<Kibi<XeroxedBytes>>>();
}

TEST(SiPrefixes, HaveCorrectAbsoluteValues) {
EXPECT_EQ(unit_ratio(Yotta<Bytes>{}, Bytes{}), pow<24>(mag<10>()));
EXPECT_EQ(unit_ratio(Zetta<Bytes>{}, Bytes{}), pow<21>(mag<10>()));
Expand Down
51 changes: 51 additions & 0 deletions au/unit_symbol.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2023 Aurora Operations, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "au/wrapper_operations.hh"

namespace au {

//
// A representation of the symbol for a unit.
//
// To use, create an instance variable templated on a unit, and make the instance variable's name
// the symbol to represent. For example:
//
// constexpr auto m = SymbolFor<Meters>{};
//
template <typename Unit>
struct SymbolFor : detail::MakesQuantityFromNumber<SymbolFor, Unit>,
detail::ScalesQuantity<SymbolFor, Unit>,
detail::ComposesWith<SymbolFor, Unit, SymbolFor, SymbolFor> {};

//
// Create a unit symbol using the more fluent APIs that unit slots make possible. For example:
//
// constexpr auto mps = symbol_for(meters / second);
//
// This is generally easier to work with and makes code that is easier to read, at the cost of being
// (very slightly) slower to compile.
//
template <typename UnitSlot>
constexpr auto symbol_for(UnitSlot) {
return SymbolFor<AssociatedUnitT<UnitSlot>>{};
}

// Support using symbols in unit slot APIs (e.g., `v.in(m / s)`).
template <typename U>
struct AssociatedUnit<SymbolFor<U>> : stdx::type_identity<U> {};

} // namespace au
46 changes: 46 additions & 0 deletions au/unit_symbol_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2023 Aurora Operations, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "au/unit_symbol.hh"

#include <type_traits>

#include "au/testing.hh"
#include "au/units/meters.hh"
#include "au/units/seconds.hh"
#include "gtest/gtest.h"

using ::testing::StaticAssertTypeEq;

namespace au {
namespace {
constexpr auto m = symbol_for(meters);
constexpr auto s = symbol_for(seconds);
} // namespace

TEST(SymbolFor, TakesUnitSlot) {
StaticAssertTypeEq<std::decay_t<decltype(m)>, SymbolFor<Meters>>();
}

TEST(SymbolFor, CreatesQuantityFromRawNumber) {
EXPECT_THAT(3.5f * m, SameTypeAndValue(meters(3.5f)));
}

TEST(SymbolFor, ScalesUnitsOfExistingQuantity) {
EXPECT_THAT(meters(25.4) / s, SameTypeAndValue((meters / second)(25.4)));
}

TEST(SymbolFor, CompatibleWithUnitSlot) { EXPECT_THAT(meters(35u).in(m), SameTypeAndValue(35u)); }

} // namespace au
16 changes: 8 additions & 8 deletions docs/alternatives/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -749,15 +749,15 @@ features.
</details>
</td>
<td class="na"></td>
<td class="good">User-defined literals (UDLs)</td>
<td class="good">User-defined literals (UDLs)</td>
<td class="best">
<a
href="https://mpusz.github.io/units/framework/quantities.html#quantity-references-experimental">Quantity
References</a>
<td class="fair">User-defined literals (UDLs)</td>
<td class="fair">User-defined literals (UDLs)</td>
<td class="good">
Unit symbols
</td>
<td class="poor">
Planned to add: <a href="https://github.com/aurora-opensource/au/issues/43">#43</a>
<td class="good">
<a href="https://aurora-opensource.github.io/au/main/reference/unit/#symbols">
Unit symbols
</a>
</td>
</tr>
<tr>
Expand Down
15 changes: 15 additions & 0 deletions docs/howto/new-units.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ a complete sample definition of a new Unit, with these features annotated and ex
constexpr auto fathoms = QuantityMaker<Fathoms>{}; // *[4]
constexpr auto fathoms_pt = QuantityPointMaker<Fathoms>{}; // [5; less common]

namespace symbols {
constexpr auto ftm = SymbolFor<Fathoms>{}; // [6]
}

// In .cc file:
constexpr const char Fathoms::label[]; // [2b]
```
Expand All @@ -54,6 +58,10 @@ a complete sample definition of a new Unit, with these features annotated and ex
constexpr auto fathom = SingularNameFor<Fathoms>{}; // [3]
constexpr auto fathoms = QuantityMaker<Fathoms>{}; // *[4]
constexpr auto fathoms_pt = QuantityPointMaker<Fathoms>{}; // [5; less common]

namespace symbols {
constexpr auto ftm = SymbolFor<Fathoms>{}; // [6]
}
```

!!! note
Expand Down Expand Up @@ -102,6 +110,13 @@ Here are the features.
- **If omitted:** _this is usually fine to omit:_ most Units are only used with `Quantity`, not
`QuantityPoint`.

6. _Unit symbol_.
- This lets you create quantities of this unit by simply multiplying or dividing raw numbers.
You can also change the units of existing quantities in the same way. See the docs for [unit
symbols](../reference/unit.md#symbols).
- **If omitted:** Users will either need to create their own symbols on the fly, or else spell
out the full name of the unit.

!!! note
Not shown here: adding an `origin` member. We skipped this because it is very rare. It only
has any effect at all for Units you plan to use with `QuantityPoint`, which is not the usual
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/prefix.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ results in a new unit with the following properties:
Therefore, `Centi<Meters>` is a unit whose symbol is `cm`, and whose magnitude is
$1/100\,\text{m}$.

## Applying to instances: the "prefix applier"
## Applying to instances: the "prefix applier" {#prefix-applier}

Au uses many kinds of instances, not just types. These include [QuantityMaker](./quantity.md),
[QuantityPointMaker](./quantity_point.md), `SingularNameFor`, and even [instances of unit
Expand All @@ -42,6 +42,7 @@ applier can be used.
| `QuantityMaker` | `meters` | `centi(meters)` | `centi(meters)(170)` |
| `QuantityPointMaker` | `meters_pt` | `centi(meters_pt)` | `centi(meters_pt)(1.5)` |
| `SingularNameFor` | `meter` | `centi(meter)` | `curvature.in(radians / centi(meter))` |
| `SymbolFor` | `m` | `centi(m)` | `constexpr auto cm = centi(m); 170 * cm` |

Note again that every output here is the same kind of thing as the input. So, `centi(meters_pt)` is
a `QuantityPointMaker`, and `centi(meters_pt)(1.5)` creates a `QuantityPoint` of $1.5\,\text{cm}$.
Expand Down
120 changes: 120 additions & 0 deletions docs/reference/unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,124 @@ users, so they have the best chance of recognizing the offending unit, and perha
scaling a unit by a magnitude. We are tracking this in
[#85](https://github.com/aurora-opensource/au/issues/85).

## Unit symbols {#symbols}

Unit symbols provide a way to create `Quantity` instances concisely: by simply multiplying or
dividing a raw number by the symbol.

For example, suppose we create symbols for `Meters` and `Seconds`:

```cpp
constexpr auto m = symbol_for(meters);
constexpr auto s = symbol_for(seconds);
```

Then we can write `3.5f * m / s` instead of `(meters / second)(3.5f)`.

### Creation

There are two ways to create an instance of a unit symbol.

1. Call `symbol_for(your_units)`.
- PRO: The argument acts as a [unit slot](../discussion/idioms/unit-slots.md), giving maximum
flexibility and composability.
- CON: Instantiating the `symbol_for` overload adds to compilation time (although only very
slightly).

2. Make an instance of `SymbolFor<YourUnits>`.
- PRO: This directly uses the type itself without instantiating anything else, so it should be
the fastest to compile.
- CON: Since the argument is a type, it's less flexible and more awkward to compose.

??? example "Examples of both methods"

=== "Using `symbol_for`"

```cpp
constexpr auto m = symbol_for(meters);
constexpr auto mps = symbol_for(meters / second);
```

These are easier to compose, although at the cost of instantiating an extra function.

=== "Using `SymbolFor`"

```cpp
constexpr auto m = SymbolFor<Meters>{};
constexpr auto mps = SymbolFor<UnitQuotientT<Meters, Seconds>>{};
```

These are the fastest to compile, although they're a little more verbose, and composition
uses awkward type traits such as `UnitQuotientT`.

#### Prefixed symbols

To create a symbol for a prefixed unit, both of the ways mentioned above (namely, calling
`symbol_for()`, and creating a `SymbolFor<>` instance) will still work. However, there is also
a third way: you can use the appropriate [prefix applier](./prefix.md#prefix-applier) with an
existing symbol for the unit to be prefixed. This can be concise and readable.

??? example "Example: creating a symbol for `Nano<Meters>`"

Assume we have a unit `Meters`, which has a quantity maker `meters` and a symbol `m`. Here are
your three options for creating a symbol for the prefixed unit `Nano<Meters>`.

=== "Using `symbol_for`"

```cpp
constexpr auto nm = symbol_for(nano(meters));
```

=== "Using `SymbolFor`"

```cpp
constexpr auto nm = SymbolFor<Nano<Meters>>{};
```

=== "Using a prefix applier"

```cpp
constexpr auto nm = nano(m);
```

### Operations

Each operation with a `SymbolFor` consists in multiplying or dividing with some other family of
types.

#### Raw numeric type `T`

Multiplying or dividing `SymbolFor<Unit>` with a raw numeric type `T` produces a `Quantity` whose rep
is `T`, and whose unit is derived from `Unit`.

| Operation | Resulting Type | Notes |
|-----------|----------------|-------|
| `SymbolFor<Unit> * T` | `Quantity<Unit, T>` | |
| `SymbolFor<Unit> / T` | `Quantity<Unit, T>` | Disallowed for integer `T` |
| `T * SymbolFor<Unit>` | `Quantity<Unit, T>` | |
| `T / SymbolFor<Unit>` | `Quantity<UnitInverseT<Unit>, T>` | |

#### `Quantity<U, R>`

Multiplying or dividing `SymbolFor<Unit>` with a `Quantity<U, R>` produces a new `Quantity`. It has
the same underlying value and same rep `R`, but its units `U` are scaled appropriately by `Unit`.

| Operation | Resulting Type | Notes |
|-----------|----------------|-------|
| `SymbolFor<Unit> * Quantity<U, R>` | `Quantity<UnitProductT<Unit, U>, R>` | |
| `SymbolFor<Unit> / Quantity<U, R>` | `Quantity<UnitQuotientT<Unit, U>, R>` | Disallowed for integer `R` |
| `Quantity<U, R> * SymbolFor<Unit>` | `Quantity<UnitProductT<U, Unit>, R>` | |
| `Quantity<U, R> / SymbolFor<Unit>` | `Quantity<UnitQuotientT<U, Unit>, R>` | |

#### `SymbolFor<OtherUnit>`

Symbols compose: the product or quotient of two `SymbolFor` instances is a new `SymbolFor` instance.

| Operation | Resulting Type |
|-----------|----------------|
| `SymbolFor<Unit> * SymbolFor<OtherUnit>` | `SymbolFor<UnitProductT<Unit, OtherUnit>>` |
| `SymbolFor<Unit> / SymbolFor<OtherUnit>` | `SymbolFor<UnitQuotientT<Unit, OtherUnit>>` |

## Unit origins {#origins}

The "origin" of a unit is only useful for `QuantityPoint`, our [affine space
Expand Down Expand Up @@ -446,3 +564,5 @@ associative, and symmetric under interchange of any inputs.
- For _types_ `Us...`:
- `CommonPointUnitT<Us...>`
<script src="../assets/hrh4.js" async=false defer=false></script>

0 comments on commit 02b7e1a

Please sign in to comment.