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

Convert CommonPointUnit to EQUIV-style labels #339

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion au/code/au/quantity_point_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@

using ::testing::Not;
using ::testing::StaticAssertTypeEq;
using ::testing::StrEq;

namespace au {
namespace {
template <typename T>
std::string stream_to_string(const T &t) {
std::ostringstream oss;
oss << t;
return oss.str();
}
} // namespace

struct Meters : UnitImpl<Length> {};
constexpr QuantityMaker<Meters> meters{};
Expand All @@ -34,7 +43,10 @@ constexpr auto inches_pt = QuantityPointMaker<Inches>{};
struct Feet : decltype(Inches{} * mag<12>()) {};
constexpr auto feet_pt = QuantityPointMaker<Feet>{};

struct Kelvins : UnitImpl<Temperature> {};
struct Kelvins : UnitImpl<Temperature> {
static constexpr const char label[] = "K";
};
constexpr const char Kelvins::label[];
constexpr QuantityMaker<Kelvins> kelvins{};
constexpr QuantityPointMaker<Kelvins> kelvins_pt{};

Expand All @@ -44,7 +56,10 @@ struct Celsius : Kelvins {
// little as possible (while still keeping the value an integer), because that will make the
// conversion factor as small as possible in converting to the common-point-unit.
static constexpr auto origin() { return (kelvins / mag<20>())(273'15 / 5); }

static constexpr const char label[] = "degC";
};
constexpr const char Celsius::label[];
constexpr QuantityMaker<Celsius> celsius_qty{};
constexpr QuantityPointMaker<Celsius> celsius_pt{};

Expand Down Expand Up @@ -345,6 +360,12 @@ TEST(QuantityPoint, MixedUnitAndRepDifferenceUsesCommonPointType) {
EXPECT_THAT(rep_wins - unit_wins, QuantityEquivalent(meters(100.0)));
}

TEST(QuantityPoint, CommonPointUnitLabel) {
EXPECT_THAT(stream_to_string(celsius_pt(0) - kelvins_pt(0)),
AnyOf(StrEq("5463 EQUIV{[(1 / 20) K], [(1 / 20) degC]}"),
StrEq("5463 EQUIV{[(1 / 20) degC], [(1 / 20) K]}")));
}

TEST(QuantityPoint, CanCompareUnitsWithDifferentOrigins) {
EXPECT_THAT(celsius_pt(0), ConsistentlyGreaterThan(kelvins_pt(273)));
EXPECT_THAT(celsius_pt(0), ConsistentlyEqualTo(milli(kelvins_pt)(273'150)));
Expand Down
14 changes: 5 additions & 9 deletions au/code/au/unit_of_measure.hh
Original file line number Diff line number Diff line change
Expand Up @@ -912,16 +912,12 @@ struct UnitLabel<CommonUnit<Us...>>
: CommonUnitLabel<decltype(Us{} *
(detail::MagT<CommonUnit<Us...>>{} / detail::MagT<Us>{}))...> {};

// Implementation for CommonPointUnit: unite constituent labels.
// Implementation for CommonPointUnit: give size in terms of each constituent unit, taking any
// origin displacements into account.
template <typename... Us>
struct UnitLabel<CommonPointUnit<Us...>> {
using LabelT = detail::ExtendedLabel<8 + 2 * (sizeof...(Us) - 1), Us...>;
static constexpr LabelT value =
detail::concatenate("COM_PT[", detail::join_by(", ", unit_label(Us{})...), "]");
};
template <typename... Us>
constexpr
typename UnitLabel<CommonPointUnit<Us...>>::LabelT UnitLabel<CommonPointUnit<Us...>>::value;
struct UnitLabel<CommonPointUnit<Us...>>
: CommonUnitLabel<decltype(
Us{} * (detail::MagT<CommonPointUnit<Us...>>{} / detail::MagT<Us>{}))...> {};

template <typename Unit>
constexpr const auto &unit_label(Unit) {
Expand Down
30 changes: 28 additions & 2 deletions au/code/au/unit_of_measure_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ constexpr auto common_unit(Us...) {

struct Celsius : Kelvins {
static constexpr auto origin() { return milli(kelvins)(273'150); }
static constexpr const char label[] = "deg_C";
};
constexpr const char Celsius::label[];
constexpr auto celsius = QuantityMaker<Celsius>{};

struct AlternateCelsius : Kelvins {
Expand Down Expand Up @@ -437,7 +439,9 @@ TEST(OriginDisplacement, FunctionalInterfaceHandlesInstancesCorrectly) {

struct OffsetCelsius : Celsius {
static constexpr auto origin() { return detail::OriginOf<Celsius>::value() + kelvins(10); }
static constexpr const char label[] = "offset_deg_C";
};
constexpr const char OffsetCelsius::label[];

TEST(DisplaceOrigin, DisplacesOrigin) {
EXPECT_EQ(origin_displacement(Celsius{}, OffsetCelsius{}), kelvins(10));
Expand All @@ -462,6 +466,11 @@ TEST(CommonUnit, PrefersUnitFromListIfAnyIdentical) {
StaticAssertTypeEq<CommonUnitT<Feet, Inches, Yards>, Inches>();
}

TEST(CommonUnit, DedupesUnitsMadeIdenticalAfterUnscalingSameScaledUnit) {
StaticAssertTypeEq<CommonUnitT<decltype(Feet{} * mag<3>()), decltype(Feet{} * mag<5>())>,
Feet>();
}

TEST(CommonUnit, DownranksAnonymousScaledUnits) {
StaticAssertTypeEq<CommonUnitT<Yards, decltype(Feet{} * mag<3>())>, Yards>();
}
Expand Down Expand Up @@ -648,13 +657,30 @@ TEST(UnitLabel, ReducesToSingleUnitLabelIfAllUnitsAreTheSame) {

TEST(UnitLabel, LabelsCommonPointUnitCorrectly) {
using U = CommonPointUnitT<Inches, Meters>;
EXPECT_THAT(unit_label(U{}), AnyOf(StrEq("COM_PT[in, m]"), StrEq("COM_PT[m, in]")));
EXPECT_THAT(unit_label(U{}),
AnyOf(StrEq("EQUIV{[(1 / 127) in], [(1 / 5000) m]}"),
StrEq("EQUIV{[(1 / 5000) m], [(1 / 127) in]}")));
}

TEST(UnitLabel, CommonPointUnitLabelWorksWithUnitProduct) {
using U = CommonPointUnitT<UnitQuotientT<Meters, Minutes>, UnitQuotientT<Inches, Minutes>>;
EXPECT_THAT(unit_label(U{}),
AnyOf(StrEq("COM_PT[m / min, in / min]"), StrEq("COM_PT[in / min, m / min]")));
AnyOf(StrEq("EQUIV{[(1 / 127) in / min], [(1 / 5000) m / min]}"),
StrEq("EQUIV{[(1 / 5000) m / min], [(1 / 127) in / min]}")));
}

TEST(UnitLabel, CommonPointUnitLabelTakesOriginOffsetIntoAccount) {
// NOTE: the (1 / 1000) scaling comes about because we're still using `Quantity` to define our
// origins. We may be able to do better, and re-found them on `Constant`, at least once we add
// some kind of subtraction that works at least some of the time. The ideal end point would be
// that the common point unit for `Celsius` and `OffsetCelsius` would be simply `Celsius`
// (because `OffsetCelsius` is just `Celsius` with a _higher_ origin). At that point, we'll
// need to construct a more complicated example here that will force us to use
// `CommonPointUnit<...>`, because it will be meaningfully different from all of its parameters.
using U = CommonPointUnitT<Celsius, OffsetCelsius>;
EXPECT_THAT(unit_label(U{}),
AnyOf(StrEq("EQUIV{[(1 / 1000) deg_C], [(1 / 1000) offset_deg_C]}"),
StrEq("EQUIV{[(1 / 1000) offset_deg_C], [(1 / 1000) deg_C]}")));
}

TEST(UnitLabel, APICompatibleWithUnitSlots) { EXPECT_THAT(unit_label(feet), StrEq("ft")); }
Expand Down
Loading