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

Gather factors and add labels for scaled units #332

Merged
merged 1 commit 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
6 changes: 3 additions & 3 deletions au/code/au/quantity_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -916,10 +916,10 @@ TEST(IsConversionLossy, CorrectlyDiscriminatesBetweenLossyAndLosslessConversions

TEST(AreQuantityTypesEquivalent, RequiresSameRepAndEquivalentUnits) {
using IntQFeet = decltype(feet(1));
using IntQFeetTimesOne = decltype((feet * ONE)(1));
using IntQTwelveInches = decltype((inches * mag<12>())(1));

ASSERT_FALSE((std::is_same<IntQFeet, IntQFeetTimesOne>::value));
EXPECT_TRUE((AreQuantityTypesEquivalent<IntQFeet, IntQFeetTimesOne>::value));
ASSERT_FALSE((std::is_same<IntQFeet, IntQTwelveInches>::value));
EXPECT_TRUE((AreQuantityTypesEquivalent<IntQFeet, IntQTwelveInches>::value));
}

TEST(UnblockIntDiv, EnablesTruncatingIntegerDivisionIntoQuantity) {
Expand Down
57 changes: 50 additions & 7 deletions au/code/au/unit_of_measure.hh
Original file line number Diff line number Diff line change
Expand Up @@ -274,17 +274,29 @@ constexpr auto associated_unit_for_points(U) {
// one for the anonymous scaled unit (e.g., `Inches * mag<12>()`). We explicitly assume that this
// will not cause any performance problems, because these should all be empty classes anyway. If we
// find out we're mistaken, we'll need to revisit this idea.
template <typename Unit, typename ScaleFactor>
struct ScaledUnit;

template <typename Unit, typename ScaleFactor>
struct ComputeScaledUnitImpl : stdx::type_identity<ScaledUnit<Unit, ScaleFactor>> {};
template <typename Unit, typename ScaleFactor>
using ComputeScaledUnit = typename ComputeScaledUnitImpl<Unit, ScaleFactor>::type;
template <typename Unit, typename ScaleFactor, typename OldScaleFactor>
struct ComputeScaledUnitImpl<ScaledUnit<Unit, OldScaleFactor>, ScaleFactor>
: ComputeScaledUnitImpl<Unit, MagProductT<OldScaleFactor, ScaleFactor>> {};
template <typename Unit>
struct ComputeScaledUnitImpl<Unit, Magnitude<>> : stdx::type_identity<Unit> {};
// Disambiguating specialization:
template <typename Unit, typename OldScaleFactor>
struct ComputeScaledUnitImpl<ScaledUnit<Unit, OldScaleFactor>, Magnitude<>>
: stdx::type_identity<ScaledUnit<Unit, OldScaleFactor>> {};

template <typename Unit, typename ScaleFactor>
struct ScaledUnit : Unit {
static_assert(IsValidPack<Magnitude, ScaleFactor>::value,
"Can only scale by a Magnitude<...> type");
using Dim = detail::DimT<Unit>;
using Mag = MagProductT<detail::MagT<Unit>, ScaleFactor>;

// We must ensure we don't give this unit the same label as the unscaled version!
//
// Later on, we could try generating a new label by "pretty printing" the scale factor.
static constexpr auto &label = DefaultUnitLabel<void>::value;
};

// Type template to hold the product of powers of Units.
Expand Down Expand Up @@ -321,13 +333,13 @@ using UnitQuotientT = UnitProductT<U1, UnitInverseT<U2>>;

// Scale this Unit by multiplying by a Magnitude.
template <typename U, typename = std::enable_if_t<IsUnit<U>::value>, typename... BPs>
constexpr ScaledUnit<U, Magnitude<BPs...>> operator*(U, Magnitude<BPs...>) {
constexpr ComputeScaledUnit<U, Magnitude<BPs...>> operator*(U, Magnitude<BPs...>) {
return {};
}

// Scale this Unit by dividing by a Magnitude.
template <typename U, typename = std::enable_if_t<IsUnit<U>::value>, typename... BPs>
constexpr ScaledUnit<U, MagInverseT<Magnitude<BPs...>>> operator/(U, Magnitude<BPs...>) {
constexpr ComputeScaledUnit<U, MagInverseT<Magnitude<BPs...>>> operator/(U, Magnitude<BPs...>) {
return {};
}

Expand Down Expand Up @@ -821,6 +833,22 @@ struct UnitLabel<UnitProduct<Us...>>
detail::DenominatorPartT<UnitProduct<Us...>>,
void> {};

// Implementation for ScaledUnit: scaling unit U by M gets label `"[M U]"`.
template <typename U, typename M>
struct UnitLabel<ScaledUnit<U, M>> {
using MagLab = MagnitudeLabel<M>;
using LabelT = detail::
ExtendedLabel<detail::parens_if<MagLab::has_exposed_slash>(MagLab::value).size() + 3u, U>;
static constexpr LabelT value =
detail::concatenate("[",
detail::parens_if<MagLab::has_exposed_slash>(MagLab::value),
" ",
UnitLabel<U>::value,
"]");
};
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...>> {
Expand Down Expand Up @@ -859,6 +887,20 @@ struct OrderByDim : InStandardPackOrder<DimT<A>, DimT<B>> {};
template <typename A, typename B>
struct OrderByMag : InStandardPackOrder<MagT<A>, MagT<B>> {};

// Order by "scaledness" of scaled units. This is always false unless BOTH are specializations of
// the `ScaledUnit<U, M>` template. If they are, we *assume* we would never call this unless both
// `OrderByDim` and `OrderByMag` are tied. Therefore, we go by the _scale factor itself_.
template <typename A, typename B>
struct OrderByScaledness : std::false_type {};
template <typename A, typename B>
struct OrderByScaleFactor : std::false_type {};
template <typename U1, typename M1, typename U2, typename M2>
struct OrderByScaleFactor<ScaledUnit<U1, M1>, ScaledUnit<U2, M2>> : InStandardPackOrder<M1, M2> {};

template <typename U1, typename M1, typename U2, typename M2>
struct OrderByScaledness<ScaledUnit<U1, M1>, ScaledUnit<U2, M2>>
: LexicographicTotalOrdering<ScaledUnit<U1, M1>, ScaledUnit<U2, M2>, OrderByScaleFactor> {};

// OrderAsUnitProduct<A, B> can only be true if both A and B are unit products, _and_ they are in
// the standard pack order for unit products. This default case handles the usual case where either
// A or B (or both) is not a UnitProduct<...> in the first place.
Expand Down Expand Up @@ -913,6 +955,7 @@ struct InOrderFor<UnitProduct, A, B> : LexicographicTotalOrdering<A,
detail::OrderByUnitAvoidance,
detail::OrderByDim,
detail::OrderByMag,
detail::OrderByScaleFactor,
detail::OrderByOrigin,
detail::OrderAsUnitProduct> {};

Expand Down
37 changes: 27 additions & 10 deletions au/code/au/unit_of_measure_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ struct SomePack {};
template <typename A, typename B>
struct InOrderFor<SomePack, A, B> : InOrderFor<CommonUnit, A, B> {};

struct UnlabeledUnit : decltype(Feet{} * mag<9>()) {};
struct UnlabeledUnit : UnitImpl<Length> {};

MATCHER_P(QuantityEquivalentToUnit, target, "") {
return are_units_quantity_equivalent(arg, target);
Expand All @@ -111,6 +111,11 @@ TEST(Unit, OriginRetainedForProductWithMagnitudeButNotWithUnit) {
(stdx::experimental::is_detected<detail::OriginMemberType, decltype(scaled_by_unit)>{}));
}

TEST(ScaledUnit, IsTypeIdentityWhenScalingByOne) {
StaticAssertTypeEq<decltype(Feet{} * mag<1>()), Feet>();
StaticAssertTypeEq<decltype((Feet{} * mag<3>()) / mag<3>()), Feet>();
}

TEST(IsUnit, TrueForUnitImpl) { EXPECT_TRUE(IsUnit<UnitImpl<Length>>::value); }

TEST(IsUnit, TrueForOpaqueTypedef) { EXPECT_TRUE(IsUnit<Feet>::value); }
Expand Down Expand Up @@ -452,15 +457,7 @@ TEST(CommonUnit, PrefersUnitFromListIfAnyIdentical) {
}

TEST(CommonUnit, DownranksAnonymousScaledUnits) {
StaticAssertTypeEq<CommonUnitT<Feet, decltype(Feet{} * mag<1>())>, Feet>();
StaticAssertTypeEq<CommonUnitT<Meters, UnitImpl<Length>>, Meters>();

using OpaqueFeetSquared = decltype(pow<2>(Feet{}) * ONE);
using OpaqueFeet = UnitProductT<OpaqueFeetSquared, UnitInverseT<Feet>>;
ASSERT_FALSE((std::is_same<OpaqueFeet, Feet>::value));
ASSERT_TRUE((AreUnitsQuantityEquivalent<OpaqueFeet, Feet>::value));
ASSERT_TRUE((InOrderFor<CommonUnit, Feet, OpaqueFeet>::value));
StaticAssertTypeEq<CommonUnitT<Feet, OpaqueFeet>, Feet>();
StaticAssertTypeEq<CommonUnitT<Yards, decltype(Feet{} * mag<3>())>, Yards>();
}

// Four coprime units of the same dimension.
Expand All @@ -483,6 +480,10 @@ TEST(CommonUnit, UnpacksTypesInNestedCommonUnit) {
StaticAssertTypeEq<Common, CommonUnitT<W, X, Y, Z>>();
}

TEST(CommonUnit, CanCombineUnitsThatWouldBothBeAnonymousScaledUnits) {
EXPECT_EQ((feet / mag<3>())(1), (inches * mag<4>())(1));
}

TEST(CommonPointUnit, FindsCommonMagnitude) {
EXPECT_THAT((CommonPointUnitT<Feet, Feet>{}), PointEquivalentToUnit(Feet{}));
EXPECT_THAT((CommonPointUnitT<Feet, Inches>{}), PointEquivalentToUnit(Inches{}));
Expand Down Expand Up @@ -544,6 +545,22 @@ TEST(UnitLabel, PicksUpLabelForLabeledUnit) {
EXPECT_EQ(sizeof(unit_label<Feet>()), 3);
}

TEST(UnitLabel, PrependsScaleFactorToLabelForScaledUnit) {
EXPECT_THAT(unit_label<decltype(Feet{} * mag<3>())>(), StrEq("[3 ft]"));
EXPECT_THAT(unit_label<decltype(Feet{} / mag<12>())>(), StrEq("[(1 / 12) ft]"));
}

TEST(UnitLabel, ApplyingMultipleScaleFactorsComposesToOneSingleScaleFactor) {
EXPECT_THAT(unit_label<decltype(Feet{} * mag<7>() / mag<12>())>(), StrEq("[(7 / 12) ft]"));
EXPECT_THAT(unit_label<decltype(Feet{} * mag<2>() / mag<3>() * mag<5>() / mag<7>())>(),
StrEq("[(10 / 21) ft]"));
}

TEST(UnitLabel, OmitsTrivialScaleFactor) {
EXPECT_THAT(unit_label<decltype(Feet{} * mag<1>())>(), StrEq("ft"));
EXPECT_THAT(unit_label<decltype((Feet{} * mag<3>()) / mag<3>())>(), StrEq("ft"));
}

TEST(UnitLabel, PrintsExponentForUnitPower) {
EXPECT_THAT(unit_label(Pow<Minutes, 2>{}), StrEq("min^2"));
EXPECT_THAT(unit_label(Pow<Feet, 33>{}), StrEq("ft^33"));
Expand Down