Skip to content

Commit

Permalink
Add implementation and docs for Constant (#200)
Browse files Browse the repository at this point in the history
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 #90.
  • Loading branch information
chiphogg authored Nov 27, 2023
1 parent ec2cb21 commit 8fa4e38
Show file tree
Hide file tree
Showing 6 changed files with 693 additions and 11 deletions.
32 changes: 27 additions & 5 deletions au/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,23 @@ cc_test(
)

cc_library(
name = "operators",
hdrs = ["operators.hh"],
name = "constant",
hdrs = ["constant.hh"],
deps = [
":quantity",
":unit_of_measure",
":wrapper_operations",
],
)

cc_test(
name = "operators_test",
name = "constant_test",
size = "small",
srcs = ["operators_test.cc"],
srcs = ["constant_test.cc"],
deps = [
":operators",
":constant",
":testing",
":units",
"@com_google_googletest//:gtest_main",
],
)
Expand Down Expand Up @@ -263,6 +269,22 @@ cc_test(
],
)

cc_library(
name = "operators",
hdrs = ["operators.hh"],
)

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

cc_library(
name = "packs",
hdrs = ["packs.hh"],
Expand Down
105 changes: 105 additions & 0 deletions au/constant.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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/quantity.hh"
#include "au/quantity_point.hh"
#include "au/stdx/type_traits.hh"
#include "au/unit_of_measure.hh"
#include "au/wrapper_operations.hh"

namespace au {

//
// A monovalue type to represent a constant value, including its units, if any.
//
// Users can multiply or divide `Constant` instances by raw numbers or `Quantity` instances, and it
// will perform symbolic arithmetic at compile time without affecting the stored numeric value.
// `Constant` also composes with other constants, and with `QuantityMaker` and other related types.
//
// Although `Constant` does not have any specific numeric type associated with it (as opposed to
// `Quantity`), it can easily convert to any appropriate `Quantity` type, with any rep. Unlike
// `Quantity`, these conversions support _exact_ safety checks, so that every conversion producing a
// correctly representable value will succeed, and every unrepresentable conversion will fail.
//
template <typename Unit>
struct Constant : detail::MakesQuantityFromNumber<Constant, Unit>,
detail::ScalesQuantity<Constant, Unit>,
detail::ComposesWith<Constant, Unit, Constant, Constant>,
detail::ComposesWith<Constant, Unit, QuantityMaker, QuantityMaker>,
detail::ComposesWith<Constant, Unit, SingularNameFor, SingularNameFor>,
detail::ForbidsComposingWith<Constant, Unit, QuantityPointMaker>,
detail::ForbidsComposingWith<Constant, Unit, QuantityPoint>,
detail::CanScaleByMagnitude<Constant, Unit> {
// Convert this constant to a Quantity of the given rep.
template <typename T>
constexpr auto as() const {
return make_quantity<Unit>(static_cast<T>(1));
}

// Convert this constant to a Quantity of the given unit and rep, ignoring safety checks.
template <typename T, typename OtherUnit>
constexpr auto coerce_as(OtherUnit u) const {
return as<T>().coerce_as(u);
}

// Convert this constant to a Quantity of the given unit and rep.
template <typename T, typename OtherUnit>
constexpr auto as(OtherUnit u) const {
static_assert(can_store_value_in<T>(u), "Cannot represent constant in this unit/rep");
return coerce_as<T>(u);
}

// Get the value of this constant in the given unit and rep, ignoring safety checks.
template <typename T, typename OtherUnit>
constexpr auto coerce_in(OtherUnit u) const {
return as<T>().coerce_in(u);
}

// Get the value of this constant in the given unit and rep.
template <typename T, typename OtherUnit>
constexpr auto in(OtherUnit u) const {
static_assert(can_store_value_in<T>(u), "Cannot represent constant in this unit/rep");
return coerce_in<T>(u);
}

// Implicitly convert to any quantity type which passes safety checks.
template <typename U, typename R>
constexpr operator Quantity<U, R>() const {
return as<R>(U{});
}

// Static function to check whether this constant can be exactly-represented in the given rep
// `T` and unit `OtherUnit`.
template <typename T, typename OtherUnit>
static constexpr bool can_store_value_in(OtherUnit other) {
return representable_in<T>(unit_ratio(Unit{}, other));
}
};

// Make a constant from the given unit.
//
// Note that the argument is a _unit slot_, and thus can also accept things like `QuantityMaker` and
// `SymbolFor` in addition to regular units.
template <typename UnitSlot>
constexpr Constant<AssociatedUnitT<UnitSlot>> make_constant(UnitSlot) {
return {};
}

// Support using `Constant` in a unit slot.
template <typename Unit>
struct AssociatedUnit<Constant<Unit>> : stdx::type_identity<Unit> {};

} // namespace au
Loading

0 comments on commit 8fa4e38

Please sign in to comment.