Skip to content

Commit

Permalink
Generate meaningful labels for common units (#334)
Browse files Browse the repository at this point in the history
Formerly, the common unit for, say, `"m"` and `"in"`, would get a label
something like `"COM[m, in]"`.  This is correct, but useless.  First, to
know what the unit _actually is_, the end user has to do some math: they
have to find the largest unit that evenly divides all inputs.  And then
there's no easy way to check whether they got the math right!

Now, we do the math for you.  The new label corresponding to the above
example would be `"EQUIV{[(1 / 127) in], [(1 / 5000) m]}"`, or something
like it.  `EQUIV{...}` asserts to the reader that everything contained
therein is quantity-equivalent.  Pick any argument, and you'd get a true
statement.

Interestingly, it can happen that the number of units that should _show
up in the label_ is **different** from the number of units _in the
common unit_.  If we have two different scaled versions of the same
unit, then both should show up in `CommonUnit<...>`, but only one should
show up in the label `"EQUIV{...}"`, because the scaled versions will
necessarily be identical after they're re-scaled to fit the common unit.
Therefore, we remove redundancies, and if only one unit remains, we drop
the `"EQUIV{...}"` shell.

Helps #105.  We still need to do a little better.  First, if the _label_
has only one unit, then we should just use that _instead of_ the common
unit!  And second, we need to handle the common _point_ units.
  • Loading branch information
chiphogg authored Dec 2, 2024
1 parent 9b2f9a7 commit 437804f
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 10 deletions.
36 changes: 28 additions & 8 deletions au/code/au/unit_of_measure.hh
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,32 @@ struct EliminateRedundantUnitsImpl<Pack<H, Ts...>>

H>> {};

template <typename U, typename... Us>
struct AllUnitsQuantityEquivalent : stdx::conjunction<AreUnitsQuantityEquivalent<U, Us>...> {};

template <typename... Us>
struct CommonUnitLabelImpl {
static_assert(sizeof...(Us) > 1u, "Common unit label only makes sense for multiple units");
static_assert(AllUnitsQuantityEquivalent<Us...>::value,
"Must pre-reduce units before constructing common-unit label");

using LabelT = ExtendedLabel<7u + 2u * (sizeof...(Us) - 1u), Us...>;
static constexpr LabelT value = concatenate("EQUIV{", join_by(", ", unit_label<Us>()...), "}");
};
template <typename... Us>
constexpr typename CommonUnitLabelImpl<Us...>::LabelT CommonUnitLabelImpl<Us...>::value;

template <typename U>
struct CommonUnitLabelImpl<U> : UnitLabel<U> {};

} // namespace detail

template <typename A, typename B>
struct InOrderFor<detail::CommonUnitLabelImpl, A, B> : InOrderFor<UnitProduct, A, B> {};

template <typename... Us>
using CommonUnitLabel = FlatDedupedTypeListT<detail::CommonUnitLabelImpl, Us...>;

template <typename... Us>
using ComputeCommonUnitImpl =
detail::EliminateRedundantUnits<FlatDedupedTypeListT<CommonUnit, Us...>>;
Expand Down Expand Up @@ -849,15 +873,11 @@ struct UnitLabel<ScaledUnit<U, M>> {
template <typename U, typename M>
constexpr typename UnitLabel<ScaledUnit<U, M>>::LabelT UnitLabel<ScaledUnit<U, M>>::value;

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

// Implementation for CommonPointUnit: unite constituent labels.
template <typename... Us>
Expand Down
25 changes: 23 additions & 2 deletions au/code/au/unit_of_measure_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ using ::testing::StaticAssertTypeEq;
using ::testing::StrEq;

namespace au {
namespace {
template <typename... Us>
constexpr auto common_unit(Us...) {
return CommonUnitT<Us...>{};
}
} // namespace

struct Celsius : Kelvins {
static constexpr auto origin() { return milli(kelvins)(273'150); }
Expand Down Expand Up @@ -611,13 +617,28 @@ TEST(UnitLabel, PowersAndProductsComposeNicely) {

TEST(UnitLabel, LabelsCommonUnitCorrectly) {
using U = CommonUnitT<Inches, Meters>;
EXPECT_THAT(unit_label(U{}), AnyOf(StrEq("COM[in, m]"), StrEq("COM[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, CommonUnitLabelWorksWithUnitProduct) {
using U = CommonUnitT<UnitQuotientT<Meters, Minutes>, UnitQuotientT<Inches, Minutes>>;
EXPECT_THAT(unit_label(U{}),
AnyOf(StrEq("COM[m / min, in / min]"), StrEq("COM[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, RemovesDuplicatesFromCommonUnitLabel) {
// A likely failure mode for this test would be `"EQUIV{[24 in], [2 ft], [2 ft]}"`.
EXPECT_THAT(
unit_label(common_unit(Feet{} * mag<6>(), Feet{} * mag<10>(), Inches{} * mag<48>())),
AnyOf(StrEq("EQUIV{[2 ft], [24 in]}"), StrEq("EQUIV{[24 in], [2 ft]}")));
}

TEST(UnitLabel, ReducesToSingleUnitLabelIfAllUnitsAreTheSame) {
// A likely failure mode for this test would be `"EQUIV{[2 ft], [2 ft]}"`.
EXPECT_THAT(unit_label(common_unit(Feet{} * mag<6>(), Feet{} * mag<10>())), StrEq("[2 ft]"));
}

TEST(UnitLabel, LabelsCommonPointUnitCorrectly) {
Expand Down

0 comments on commit 437804f

Please sign in to comment.