diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ebeba07..2b8b6258 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ endif() project( ${UNITS_CMAKE_PROJECT_NAME} LANGUAGES C CXX - VERSION 0.11.0 + VERSION 0.11.1 ) include(CMakeDependentOption) include(CTest) diff --git a/python/units_python.cpp b/python/units_python.cpp index e15cfa89..2e912bdf 100644 --- a/python/units_python.cpp +++ b/python/units_python.cpp @@ -9,6 +9,7 @@ SPDX-License-Identifier: BSD-3-Clause #include #include "units/units.hpp" +#include "units/units_math.hpp" namespace nb = nanobind; using namespace nb::literals; @@ -41,15 +42,20 @@ NB_MODULE(units_llnl_ext, mod) units::unit_from_string(std::string(arg0)), units::getCommodity(std::string(commodity))}; }) - .def("multiplier", &units::precise_unit::multiplier) - .def( + .def_prop_ro("multiplier", &units::precise_unit::multiplier) + .def_prop_ro( "commodity", + [](const units::precise_unit& unit) { + return units::getCommodityName(unit.commodity()); + }) + .def_prop_ro( + "base_units", [](const units::precise_unit& type1) { - return units::getCommodityName(type1.commodity()); + return units::precise_unit(type1.base_units()); }) .def( "set_commodity", - [](units::precise_unit* unit, const char* commodity) { + [](const units::precise_unit* unit, const char* commodity) { return units::precise_unit( unit->multiplier(), unit->base_units(), @@ -57,11 +63,10 @@ NB_MODULE(units_llnl_ext, mod) }) .def( "set_multiplier", - [](units::precise_unit* unit, double mult) { + [](const units::precise_unit* unit, double mult) { return units::precise_unit( mult, unit->base_units(), unit->commodity()); }) - .def("inv", &units::precise_unit::inv) .def(nb::self * nb::self) .def(nb::self / nb::self) .def(float() * nb::self) @@ -72,7 +77,7 @@ NB_MODULE(units_llnl_ext, mod) .def(nb::self != nb::self) .def( "__pow__", - [](const units::precise_unit& a, int pow) { return a.pow(pow); }, + [](const units::precise_unit& unit, int pow) { return unit.pow(pow); }, nb::is_operator()) .def( "is_exactly_the_same", @@ -135,45 +140,48 @@ NB_MODULE(units_llnl_ext, mod) .def("is_equation", &units::precise_unit::is_equation) .def( "is_valid", - [](const units::precise_unit& type) { - return units::is_valid(type); + [](const units::precise_unit& unit) { + return units::is_valid(unit); }) .def( "is_normal", - [](const units::precise_unit& type) { - return units::isnormal(type); + [](const units::precise_unit& unit) { + return units::isnormal(unit); }) .def( "is_error", - [](const units::precise_unit& type) { - return units::is_error(type); + [](const units::precise_unit& unit) { + return units::is_error(unit); }, "return true if the unit has the error flags set or is infinite") .def( "isfinite", - [](const units::precise_unit& type) { - return units::isfinite(type); + [](const units::precise_unit& unit) { + return units::isfinite(unit); }) .def( "isinf", - [](const units::precise_unit& type) { return units::isinf(type); }) + [](const units::precise_unit& unit) { return units::isinf(unit); }) .def( "root", - [](const units::precise_unit& type, int root) { - return units::root(type, root); + [](const units::precise_unit& unit, int root) { + return units::root(unit, root); }) .def( "sqrt", - [](const units::precise_unit& type) { - return units::root(type, 2); + [](const units::precise_unit& unit) { + return units::root(unit, 2); }) + .def("__invert__",[](const units::precise_unit & unit){ + return unit.inv(); + }) .def( "__repr__", - [](const units::precise_unit& type) { - return units::to_string(type); + [](const units::precise_unit& unit) { + return units::to_string(unit); }) - .def("to_string", [](const units::precise_unit& type) { - return units::to_string(type); + .def("__hash__",[](const units::precise_unit& unit){ + return std::hash()(unit); }); nb::class_( @@ -202,16 +210,16 @@ NB_MODULE(units_llnl_ext, mod) const units::precise_unit& unit) { new (measurement) units::precise_measurement(value, unit); }) - .def("value", &units::precise_measurement::value) + .def_prop_ro("value", [](const units::precise_measurement& measurement){return measurement.value();}) .def( "set_value", - [](units::precise_measurement* measurement, double value) { + [](const units::precise_measurement* measurement, double value) { return units::precise_measurement(value, measurement->units()); }) - .def("units", &units::precise_measurement::units) + .def_prop_ro("units", [](const units::precise_measurement& measurement){return measurement.units();}) .def( "set_units", - [](units::precise_measurement* measurement, + [](const units::precise_measurement* measurement, const units::precise_unit& unit) { return units::precise_measurement(measurement->value(), unit); }) @@ -257,12 +265,14 @@ NB_MODULE(units_llnl_ext, mod) .def("as_unit", &units::precise_measurement::as_unit) .def(nb::self * nb::self) .def(nb::self / nb::self) + .def(nb::self % nb::self) .def(nb::self + nb::self) .def(nb::self - nb::self) .def(float() * nb::self) .def(nb::self * float()) .def(float() / nb::self) .def(nb::self / float()) + .def(nb::self % float()) .def(nb::self == nb::self) .def(nb::self != nb::self) .def(nb::self > nb::self) @@ -310,9 +320,49 @@ NB_MODULE(units_llnl_ext, mod) [](const units::precise_measurement& measurement) { return units::to_string(measurement); }) - .def("to_string", [](const units::precise_measurement& measurement) { - return units::to_string(measurement); - }); + .def( + "__format__", + [](const units::precise_measurement& measurement,std::string fmt_string) { + if (fmt_string.empty()) + { + return units::to_string(measurement); + } + if (fmt_string == "-") + { + return units::to_string(units::precise_measurement(measurement.value(),units::precise::one)); + } + if (fmt_string.front() == '-') + { + return units::to_string(units::precise_measurement(measurement.value_as(units::unit_from_string(fmt_string.substr(1))),units::precise::one)); + } + else { + return units::to_string(measurement.convert_to(units::unit_from_string(fmt_string))); + } + + }) + .def("__neg__",[](const units::precise_measurement & measurement){ + return -measurement; + }) + .def("__invert__",[](const units::precise_measurement & measurement){ + return 1.0/measurement; + }) + .def("__trunc__",[](const units::precise_measurement & measurement){ + return trunc(measurement); + }) + .def("__ceil__",[](const units::precise_measurement & measurement){ + return ceil(measurement); + }) + .def("__floor__",[](const units::precise_measurement & measurement){ + return floor(measurement); + }) + .def("__round__",[](const units::precise_measurement & measurement){ + return round(measurement); + }) + .def("__floordiv__",[](const units::precise_measurement & measurement,const units::precise_measurement &other){ + return floor(measurement/other).value(); + }) + .def("__float__",[](const units::precise_measurement &measurement){return measurement.value();}) + .def("__bool__",[](const units::precise_measurement &measurement){return (measurement.value()>=0.0);}); mod.def( "convert", diff --git a/test/python/test_measurement.py b/test/python/test_measurement.py index 611d1fa2..ab84c63a 100644 --- a/test/python/test_measurement.py +++ b/test/python/test_measurement.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause import units_llnl as u - +import math def test_basic_measurement(): m1 = u.Measurement("10 m") @@ -32,7 +32,7 @@ def test_basic_measurement3(): m4 = u.Measurement(4.0, u1 / u2) assert m1 / m2 == m4 - assert m4.units() == u1 / u2 + assert m4.units == u1 / u2 def test_conditions(): @@ -80,16 +80,16 @@ def test_comparisons(): def test_set_value(): m1 = u.Measurement("100 months") - assert m1.value() == 100 + assert m1.value == 100 m2 = m1.set_value(14) - assert m2.value() == 14 + assert m2.value == 14 def test_set_units(): m1 = u.Measurement("100 months") - assert m1.units() == u.Unit("month") + assert m1.units == u.Unit("month") m2 = m1.set_units(u.Unit("day")) - assert m2.units() == u.Unit("day") + assert m2.units == u.Unit("day") def test_value_as(): @@ -102,40 +102,68 @@ def test_value_as(): def test_convert_to(): m1 = u.Measurement("20 weeks") m2 = m1.convert_to("day") - assert m2.value() == 20 * 7 + assert m2.value == 20 * 7 u1 = u.Unit("hr") m3 = m1.convert_to("hr") - assert m3.value() == 20 * 7 * 24 + assert m3.value == 20 * 7 * 24 m4 = m1.convert_to_base() - assert m4.units() == u.Unit("s") - assert m4.units().multiplier() == 1.0 - assert m4.value() == 20 * 7 * 24 * 3600 + assert m4.units == u.Unit("s") + assert m4.units.multiplier == 1.0 + assert m4.value == 20 * 7 * 24 * 3600 def test_as_unit(): m1 = u.Measurement("15 seconds") m2 = u.Measurement(4, m1.as_unit()) - assert m2.value() == 4 + assert m2.value == 4 assert m2.value_as("s") == 60 + assert float(m1)==15 + assert m1 def test_add_sub(): m1 = u.Measurement("15 seconds") m2 = u.Measurement(1, "minute") m3 = m2 - m1 - assert m3.value() == 0.75 + assert m3.value == 0.75 m4 = m3 + m2 + m1 assert m4 == u.Measurement(120, "second") - +def test_negation(): + m1 = u.Measurement("15 seconds") + m3 = -m1 + assert m3.value == -15.0 + + +def test_mod(): + m1 = u.Measurement("18 seconds") + m2 = u.Measurement("1 min") + m3 = (m2%m1).convert_to('s') + assert math.floor(m3.value) == 6 + m4= m1%5 + assert m4.value == 3 + +def test_math_func(): + m1 = u.Measurement("15.78 seconds") + m2 = u.Measurement("15.48 seconds") + assert math.floor(m1).value == 15 + assert math.floor(m2).value == 15 + assert math.ceil(m1).value == 16 + assert math.ceil(m2).value == 16 + assert round(m1).value == 16 + assert round(m2).value == 15 + assert math.trunc(m1).value == 15 + assert math.trunc(m2).value == 15 + + def test_mult(): m1 = u.Measurement("2 meters") m2 = u.Measurement(3, "meters") m3 = m2 * m1 - assert m3.value() == 6 + assert m3.value == 6 m4 = 3 * m3 assert m4 == u.Measurement(18, "meters squared") @@ -148,7 +176,7 @@ def test_div(): m1 = u.Measurement("10 meters") m2 = u.Measurement(2, "seconds") m3 = m1 / m2 - assert m3.value() == 5 + assert m3.value == 5 m4 = 10 / m3 assert m4 == u.Measurement(2, "s/m") @@ -159,9 +187,20 @@ def test_div(): def test_string(): m1 = u.Measurement("10 lb") - assert m1.to_string() == "10 lb" + assert str(m1) == "10 lb" s3 = f"the measurement is {m1}" assert s3 == "the measurement is 10 lb" + +def test_format(): + m1 = u.Measurement("9.7552 lb") + s1 = f"the measurement is {m1:kg}" + assert "kg" in s1 + + s2 = f"the measurement is {m1:-}" + assert s2== "the measurement is 9.7552 " + + s3 = f"the measurement is {m1:-kg}" + assert "kg" not in s3 def test_close(): diff --git a/test/python/test_units.py b/test/python/test_units.py index 7e79aa7c..f70f5977 100644 --- a/test/python/test_units.py +++ b/test/python/test_units.py @@ -64,45 +64,67 @@ def test_base(): def test_multiplier(): u1 = u.Unit("nm") - assert u1.multiplier() == 1e-9 + assert u1.multiplier == 1e-9 u2 = u1.set_multiplier(1e-6) assert u2 == u.Unit("um") - assert u2.multiplier() == 1e-6 + assert u2.multiplier == 1e-6 assert u1 != u2 def test_commodity(): u1 = u.Unit("lb", "gold") - assert u1.commodity() == "gold" + assert u1.commodity == "gold" u2 = u1.set_commodity("silver") - assert u2.commodity() == "silver" + assert u2.commodity == "silver" def test_string(): u1 = u.Unit("lb") - assert u1.to_string() == "lb" + assert str(u1) == "lb" s3 = f"the unit is {u1}" assert s3 == "the unit is lb" u3 = u.Unit() - assert u3.to_string() == "" + assert str(u3) == "" def test_inv(): u1 = u.Unit("s") - assert u1.inv() == u.Unit("Hz") - u3 = u1.inv().inv() + assert ~u1 == u.Unit("Hz") + u3 = ~(~u1) assert u3 == u1 - +def test_hash(): + u1=u.Unit("25.6 in") + u2=u1 + u3=u.Unit("11.3 m") + h1=hash(u1) + h2=hash(u2) + h3=hash(u3) + assert h1 == h2 + assert h1 != h3 + + +def test_dictionary(): + d1={} + + u1=u.Unit("25.6 in") + u2=u1 + u3=u.Unit("11.3 m") + d1[u1]="in" + d1[u3] = "m" + + assert d1[u2] == "in" + assert d1[u3] == "m" + def test_float_mult(): u1 = u.Unit("m") m3 = 10 * u1 assert type(m3).__name__ == "Measurement" - assert m3.value() == 10 + assert m3.value == 10 m4 = u1 * 12 assert type(m4).__name__ == "Measurement" - assert m4.value() == 12 + assert m4.value == 12 def test_convert_units(): diff --git a/test/python/test_user_defined.py b/test/python/test_user_defined.py index 4979c149..91d5e878 100644 --- a/test/python/test_user_defined.py +++ b/test/python/test_user_defined.py @@ -15,12 +15,12 @@ def test_user_defined_unit(): assert Unit("clucks/A") == Unit("19.3 m") assert Unit("sclucks/$") == Unit("23 m*mol") - assert clucks.to_string() == "clucks" - assert clucks.inv().to_string() == "1/clucks" + assert str(clucks) == "clucks" + assert str(~clucks) == "1/clucks" sclucks = Unit("sclucks") assert not sclucks.is_error() - assert (Unit("ug") / sclucks**3).to_string() == "ug/sclucks^3" + assert str(Unit("ug") / sclucks**3) == "ug/sclucks^3" def test_user_defined_unit_from_file(): diff --git a/units/units.hpp b/units/units.hpp index fac22e17..f0affe80 100644 --- a/units/units.hpp +++ b/units/units.hpp @@ -532,7 +532,14 @@ class fixed_measurement { { return {value_ / val, units_}; } - + fixed_measurement operator%(const fixed_measurement& other) const + { + return {fmod(value_, other.value_as(units_)), units_}; + } + fixed_measurement operator%(double val) const + { + return {fmod(value_, val), units_}; + } fixed_measurement operator+(const measurement& other) const { return {value_ + other.value_as(units_), units_}; @@ -1267,7 +1274,14 @@ class precise_measurement { { return {value_ / val, units_}; } - + precise_measurement operator%(const precise_measurement& other) const + { + return {fmod(value_, other.value_as(units_)), units_}; + } + precise_measurement operator%(double val) const + { + return {fmod(value_, val), units_}; + } precise_measurement operator+(const precise_measurement& other) const { return {value_ + other.value_as(units_), units_}; @@ -1507,6 +1521,14 @@ class fixed_precise_measurement { { return {value_ / val, units_}; } + fixed_precise_measurement operator%(const precise_measurement& other) const + { + return {fmod(value_, other.value_as(units_)), units_}; + } + fixed_precise_measurement operator%(double val) const + { + return {fmod(value_, val), units_}; + } fixed_precise_measurement operator+(const precise_measurement& other) const {