/* @file quantity.test.cpp */ #include "xo/unit/quantity.hpp" #include "xo/reflect/Reflect.hpp" //#include //#include #include #include #include namespace xo { using xo::unit::quantity; using xo::unit::qty::kilograms; using xo::unit::qty::milliseconds; using xo::unit::qty::seconds; using xo::unit::qty::minutes; using xo::unit::qty::volatility30d; using xo::unit::qty::volatility250d; using xo::unit::unit_find_bpu_t; using xo::unit::unit_conversion_factor_t; using xo::unit::unit_cartesian_product_t; using xo::unit::unit_cartesian_product; using xo::unit::unit_invert_t; using xo::unit::unit_abbrev_v; using xo::unit::same_dimension_v; using xo::unit::dim; using xo::unit::from_ratio; using xo::unit::stringliteral_from_ratio; using xo::unit::ratio2str_aux; using xo::unit::cstr_from_ratio; using xo::reflect::Reflect; namespace units = xo::unit::units; namespace ut { /* use 'testcase' snippet to add test cases here */ TEST_CASE("quantity", "[quantity]") { //constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); //scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quantity"), xtag("foo", foo), ...); //log && log("(A)", xtag("foo", foo)); quantity t = seconds(1L); REQUIRE(t.scale() == 1); static_assert(t.basis_power == 1); static_assert(t.basis_power == 0); } /*TEST_CASE(quantity)*/ TEST_CASE("add1", "[quantity]") { constexpr bool c_debug_flag = false; scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add1")); quantity t1 = seconds(1); quantity t2 = seconds(2); static_assert(std::same_as); static_assert(std::same_as); auto sum = t1 + t2; CHECK(strcmp(sum.unit_cstr(), "s") == 0); static_assert(std::same_as); static_assert(t1.basis_power == 1); static_assert(t2.basis_power == 1); REQUIRE(sum.scale() == 3); } /*TEST_CASE(add1)*/ TEST_CASE("add2", "[quantity]") { constexpr bool c_debug_flag = false; scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add2")); quantity t1 = seconds(1); { CHECK(strcmp(t1.unit_cstr(), "s") == 0); CHECK(t1.scale() == 1); } auto m2 = minutes(2); { static_assert(m2.basis_power == 1); log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr())); CHECK(m2.scale() == 2); CHECK(strcmp(m2.unit_cstr(), "min") == 0); } { auto m2_sec = m2.in_units_of(); static_assert(std::same_as); log && log(XTAG(m2_sec)); CHECK(m2_sec == 120); } quantity t2 = m2; { auto sum = t1 + t2; static_assert(std::same_as); static_assert(sum.basis_power == 1); log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr())); log && log(xtag("sum.unit", sum.unit_cstr())); CHECK(strcmp(t2.unit_cstr(), "s") == 0); CHECK(strcmp(sum.unit_cstr(), "s") == 0); CHECK(sum.scale() == 121); } } /*TEST_CASE(add2)*/ TEST_CASE("add3", "[quantity]") { quantity t1 = seconds(1); quantity t2 = minutes(2); /* sum will take unit from lhs argument to + */ auto sum = t1 + t2; static_assert(sum.basis_power == 1); static_assert(std::same_as); REQUIRE(sum.scale() == 121); } /*TEST_CASE(add3)*/ TEST_CASE("add4", "[quantity]") { constexpr bool c_debug_flag = false; scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add4")); using u_kgps_result = unit_cartesian_product>; using u_kgps = u_kgps_result::exact_unit_type; using u_gpm_result = unit_cartesian_product>; using u_gpm = u_gpm_result::exact_unit_type; { static_assert(u_kgps_result::c_scalefactor_inexact == 1.0); static_assert(std::same_as::power_type, std::ratio<1>>); static_assert(std::same_as::power_type, std::ratio<-1>>); static_assert(std::same_as::power_type, std::ratio<1>>); static_assert(std::same_as::power_type, std::ratio<-1>>); log && log(xtag("u_kgps", unit_abbrev_v.c_str())); log && log(xtag("u_gpm", unit_abbrev_v.c_str())); CHECK(strcmp(unit_abbrev_v.c_str(), "kg.s^-1") == 0); CHECK(strcmp(unit_abbrev_v.c_str(), "g.min^-1") == 0); static_assert(same_dimension_v); } using convert_type = unit_conversion_factor_t; { log && log(xtag("u_kgps->u_gpm", cstr_from_ratio())); CHECK(strcmp(cstr_from_ratio(), "60000") == 0); CHECK(from_ratio() == 60000); } /* note: in practice probably write * kilograms(0.1) / seconds(1); * but * 1. don't want to exercise quantity {*,/} here; * 2. want to force unit representation */ auto q1 = quantity::promote(0.1); auto q2 = quantity(); { q2 = q1; static_assert(q1.basis_power == 1); static_assert(q1.basis_power == -1); static_assert(q2.basis_power == 1); static_assert(q2.basis_power == -1); log && log(XTAG(q1), XTAG(q2)); CHECK(strcmp(q1.unit_cstr(), "kg.s^-1") == 0); CHECK(q1.scale() == 0.1); CHECK(strcmp(q2.unit_cstr(), "g.min^-1") == 0); CHECK(q2.scale() == 6000.0); } } /*TEST_CASE(add4)*/ TEST_CASE("add5", "[quantity][fractional_dimension]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add5")); //log && log("(A)", xtag("foo", foo)); auto vol_250d = volatility250d(0.2); { log && log(xtag("vol_250d", vol_250d)); CHECK(strcmp(vol_250d.unit_cstr(), "yr250^-(1/2)") == 0); CHECK(vol_250d.scale() == 0.2); } /* scaling factor from 30-day vol to 250-day vol is sqrt(250/30) ~ 2.88675 * so 0.1 -> 0.288675 */ auto vol_30d = volatility30d(0.1); { log && log(xtag("vol_30d", vol_30d)); CHECK(strcmp(vol_30d.unit_cstr(), "mo^-(1/2)") == 0); CHECK(vol_30d.scale() == Approx(0.1).epsilon(1e-6)); } /* conversion from monthly vol to (250-day) annual vol */ using u_vol250d = units::volatility_250d; { quantity q = vol_30d; log && log(xtag("q", q)); CHECK(strcmp(q.unit_cstr(), "yr250^-(1/2)") == 0); CHECK(q.scale() == Approx(0.288675).epsilon(1e-6)); } { auto sum = vol_250d + vol_30d; static_assert(sum.basis_power == -0.5); log && log(XTAG(sum)); CHECK(strcmp(sum.unit_cstr(), "yr250^-(1/2)") == 0); /* 0.1mo^-(1/2) ~ 0.288675yr250^-(1/2) */ CHECK(sum.scale() == Approx(0.4886751).epsilon(1e-6)); } } /*TEST_CASE(add5)*/ TEST_CASE("mult1", "[quantity]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult1")); //log && log("(A)", xtag("foo", foo)); auto q0 = milliseconds(5); auto q1 = seconds(60); auto q2 = minutes(1); { auto r = q0 * q1; static_assert(r.basis_power == 2); log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* taking unit from LHS */ REQUIRE(strcmp(r.unit_cstr(), "ms^2") == 0); REQUIRE(r.scale() == 300000); } { auto r = q1 * q2; static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* taking unit from LHS */ REQUIRE(strcmp(r.unit_cstr(), "s^2") == 0); REQUIRE(r.scale() == 3600); } { auto r = q2 * q1; static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* taking unit from LHS */ CHECK(strcmp(r.unit_cstr(), "min^2") == 0); CHECK(r.scale() == 1); } { auto r = q2 * 60; static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ CHECK(strcmp(r.unit_cstr(), "min") == 0); CHECK(r.scale() == 60); } { auto r = q2 * 60U; static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60U", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ CHECK(strcmp(r.unit_cstr(), "min") == 0); CHECK(r.scale() == 60U); } { auto r = (q2 * 60.5); static_assert(r.basis_power == 1); /* verify dimension */ static_assert(std::same_as); log && log(xtag("q2*60.5", q2*60.5)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ REQUIRE(strcmp(r.unit_cstr(), "min") == 0); REQUIRE(r.scale() == 60.5); } { log && log(xtag("q2*60.5f", q2*60.5f)); auto r = (q2 * 60.5f); /* verify dimension */ static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ REQUIRE(strcmp(r.unit_cstr(), "min") == 0); REQUIRE(r.scale() == 60.5f); } { auto r = 60 * q2; static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("60*q2", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ CHECK(strcmp(r.unit_cstr(), "min") == 0); CHECK(r.scale() == 60); } { log && log(xtag("60.5*q2", 60.5*q2)); auto r = 60.5 * q2; static_assert(r.basis_power == 1); log && log(xtag("r.type", Reflect::require()->canonical_name())); static_assert(std::same_as); /* preserve units of existing quantity */ CHECK(strcmp(r.unit_cstr(), "min") == 0); CHECK(r.scale() == 60.5); } { log && log(xtag("60.5f*q2", 60.5f*q2)); auto r = 60.5f * q2; static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* preserve units of existing quantity */ CHECK(strcmp(r.unit_cstr(), "min") == 0); CHECK(r.scale() == 60.5); } } /*TEST_CASE(mult1)*/ TEST_CASE("div1", "[quantity]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div1")); //log && log("(A)", xtag("foo", foo)); auto q0 = milliseconds(5); auto q1 = milliseconds(10); { /* repr_type adopts argument to milliseconds() */ static_assert(std::same_as); static_assert(std::same_as); auto r = q0/q1; log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimensionless + no type promotion */ static_assert(std::same_as); /* verify scale (truncate)*/ REQUIRE(r == 0); } auto q0p = milliseconds(5.0); { static_assert(std::same_as); auto r = q0p/q1; static_assert(std::same_as); log && log(XTAG(q0p), xtag("q0p/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(std::same_as); /* verify scale */ REQUIRE(r == 0.5); } auto r1 = 1.0 / q0; { log && log(XTAG(q0), xtag("r1=1.0/q0", r1)); /* verify dimension */ static_assert(r1.basis_power == -1); /* verify scale */ REQUIRE(r1.scale() == 0.2); } } /*TEST_CASE(div1)*/ TEST_CASE("div2", "[quantity]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div2")); auto q0 = milliseconds(5); auto q1 = milliseconds(20.0); { auto r = q0/q1; log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(std::same_as); /* verify scale */ REQUIRE(r == 0.25); } { auto r = q1/q0; log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(std::same_as); /* verify scale */ REQUIRE(r == 4.0); } { auto r = q0/(q1*q1); log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); } { auto r = (q0*q0)/q1; log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); } } /*TEST_CASE(div2)*/ TEST_CASE("div3", "[quantity]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div3")); auto q0 = milliseconds(5); auto q1 = milliseconds(20.0); { auto r = q0/q1; log && log(XTAG(q0), XTAG(q1), xtag("q0/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(std::same_as); /* verify scale */ REQUIRE(r == 0.25); } { auto r = q1/q0; log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(std::same_as); /* verify scale */ REQUIRE(r == 4.0); } { auto r = q0/(q1*q1); log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); } { auto r = (q0*q0)/q1; log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); } } /*TEST_CASE(div3)*/ TEST_CASE("div4", "[quantity]") { /* test with exact scalefactor */ constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div4")); //log && log("(A)", xtag("foo", foo)); auto q1 = milliseconds(1); auto q2 = minutes(1); auto r = q1 / q2.with_repr(); /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), * so q1/q2 ~ 0.6928 */ log && log(XTAG(q1), XTAG(q2), xtag("q1/q2", r)); /* verify dimensionless result */ static_assert(std::same_as); /* verify scale of result */ CHECK(r == Approx(0.00001666667).epsilon(1e-6)); } /*TEST_CASE(div4)*/ TEST_CASE("div5", "[quantity]") { /* test with inexact scalefactor */ constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div5")); //log && log("(A)", xtag("foo", foo)); auto q1 = volatility250d(0.2); auto q2 = volatility30d(0.1); auto r = q1/q2; /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), * so q1/q2 ~ 0.6928 */ log && log(XTAG(q1), XTAG(q2), XTAG(q1/q2)); /* verify dimensionless result */ static_assert(std::same_as); /* verify scale of result */ CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(div5)*/ TEST_CASE("muldiv5", "[quantity]") { constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; //rng::Seed seed; //auto rng = xo::rng::xoshiro256ss(seed); scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.muldiv5")); //log && log("(A)", xtag("foo", foo)); auto t = milliseconds(10); auto m = kilograms(2.5); auto a = m / (t * t); /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), * so q1/q2 ~ 0.6928 */ log && log(XTAG(m), XTAG(t), xtag("a=m.t^-2", a)); /* verify dimensions of result + sticky units */ CHECK(strcmp(t.unit_cstr(), "ms") == 0); CHECK(strcmp(m.unit_cstr(), "kg") == 0); CHECK(strcmp(a.unit_cstr(), "kg.ms^-2") == 0); CHECK(a.scale() == 0.025); /* verify scale of result */ //CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(muldiv5)*/ } /*namespace ut*/ } /*namespace xo*/ /* end quantity.test.cpp */