Line data Source code
1 : // SPDX-License-Identifier: BSD-3-Clause 2 : // Copyright (c) 2023 Scipp contributors (https://github.com/scipp) 3 : /// @file 4 : /// @author Simon Heybrock 5 : /// @author Neil Vaytet 6 : #include <regex> 7 : #include <stdexcept> 8 : 9 : #include <units/units.hpp> 10 : #include <units/units_util.hpp> 11 : 12 : #include "scipp/units/except.h" 13 : #include "scipp/units/unit.h" 14 : 15 : namespace scipp::units { 16 : 17 : namespace { 18 28017 : std::string map_unit_string(const std::string &unit) { 19 : // custom dimensionless name 20 28017 : return unit == "dimensionless" ? "" 21 : // Use Gregorian months and years by default. 22 27813 : : unit == "y" || unit == "Y" || unit == "year" ? "a_g" 23 : // Overwrite M to mean month instead of molarity for numpy 24 : // interop. 25 27610 : : unit == "M" || unit == "month" ? "mog" 26 59545 : : unit; 27 : } 28 : 29 28017 : bool is_special_unit(const llnl::units::precise_unit &unit) { 30 : using namespace llnl::units::precise::custom; 31 28017 : const auto &base = unit.base_units(); 32 : 33 : // Allowing custom_count_unit_number == 1 because that is 'arbitrary unit' 34 56019 : return is_custom_unit(base) || 35 56025 : (is_custom_count_unit(base) && custom_count_unit_number(base) != 1) || 36 56017 : unit.commodity() != 0; 37 : } 38 : } // namespace 39 : 40 28017 : Unit::Unit(const std::string &unit) 41 56034 : : Unit(llnl::units::unit_from_string(map_unit_string(unit), 42 28017 : llnl::units::strict_si)) { 43 28017 : if (const auto &u = m_unit.value(); is_special_unit(u) || !is_valid(u)) 44 19 : throw except::UnitError("Failed to convert string `" + unit + 45 38 : "` to valid unit."); 46 27998 : } 47 : 48 3348 : std::string Unit::name() const { 49 3348 : if (!has_value()) 50 29 : return "None"; 51 3319 : if (*this == Unit{"month"}) { 52 72 : return "M"; 53 : } 54 3247 : auto repr = to_string(*m_unit); 55 3247 : repr = std::regex_replace(repr, std::regex("^u"), "ยต"); 56 3247 : repr = std::regex_replace(repr, std::regex("item"), "count"); 57 3247 : repr = std::regex_replace(repr, std::regex("count(?!s)"), "counts"); 58 3247 : repr = std::regex_replace(repr, std::regex("day"), "D"); 59 3247 : repr = std::regex_replace(repr, std::regex("a_g"), "Y"); 60 3247 : return repr.empty() ? "dimensionless" : repr; 61 3247 : } 62 : 63 19 : bool Unit::isCounts() const { return *this == counts; } 64 : 65 8 : bool Unit::isCountDensity() const { 66 8 : return has_value() && !isCounts() && m_unit->base_units().count() != 0; 67 : } 68 : 69 554 : bool Unit::has_same_base(const Unit &other) const { 70 554 : return has_value() && m_unit->has_same_base(other.underlying()); 71 : } 72 : 73 2946687 : bool Unit::operator==(const Unit &other) const { 74 2946687 : return m_unit == other.m_unit; 75 : } 76 : 77 2279477 : bool Unit::operator!=(const Unit &other) const { return !(*this == other); } 78 : 79 15860 : Unit &Unit::operator+=(const Unit &other) { return *this = *this + other; } 80 : 81 122 : Unit &Unit::operator-=(const Unit &other) { return *this = *this - other; } 82 : 83 2800 : Unit &Unit::operator*=(const Unit &other) { return *this = *this * other; } 84 : 85 100 : Unit &Unit::operator/=(const Unit &other) { return *this = *this / other; } 86 : 87 20 : Unit &Unit::operator%=(const Unit &other) { return *this = *this % other; } 88 : 89 321566 : Unit operator+(const Unit &a, const Unit &b) { 90 321566 : if (a == b) 91 321549 : return a; 92 17 : throw except::UnitError("Cannot add " + a.name() + " and " + b.name() + "."); 93 : } 94 : 95 34721 : Unit operator-(const Unit &a, const Unit &b) { 96 34721 : if (a == b) 97 34717 : return a; 98 4 : throw except::UnitError("Cannot subtract " + a.name() + " and " + b.name() + 99 8 : "."); 100 : } 101 : 102 : namespace { 103 337684 : void expect_not_none(const Unit &u, const std::string &name) { 104 337684 : if (!u.has_value()) 105 11 : throw except::UnitError("Cannot " + name + " with operand of unit 'None'."); 106 337673 : } 107 : } // namespace 108 : 109 160790 : Unit operator*(const Unit &a, const Unit &b) { 110 160790 : if (a == none && b == none) 111 1466 : return none; 112 159328 : expect_not_none(a, "multiply"); 113 159326 : expect_not_none(b, "multiply"); 114 159320 : if (llnl::units::times_overflows(a.underlying(), b.underlying())) 115 2 : throw except::UnitError("Unsupported unit as result of multiplication: (" + 116 3 : a.name() + ") * (" + b.name() + ')'); 117 159319 : return Unit{a.underlying() * b.underlying()}; 118 : } 119 : 120 9518 : Unit operator/(const Unit &a, const Unit &b) { 121 9518 : if (a == none && b == none) 122 25 : return none; 123 9497 : expect_not_none(a, "divide"); 124 9495 : expect_not_none(b, "divide"); 125 9489 : if (llnl::units::divides_overflows(a.underlying(), b.underlying())) 126 4 : throw except::UnitError("Unsupported unit as result of division: (" + 127 6 : a.name() + ") / (" + b.name() + ')'); 128 9487 : return Unit{a.underlying() / b.underlying()}; 129 : } 130 : 131 100 : Unit operator%(const Unit &a, const Unit &b) { 132 100 : if (a == b) 133 91 : return a; 134 27 : throw except::UnitError("Cannot perform modulo operation with " + a.name() + 135 36 : " and " + b.name() + ". Units must be the same."); 136 : } 137 : 138 32099 : Unit operator-(const Unit &a) { return a; } 139 : 140 522 : Unit abs(const Unit &a) { return a; } 141 : 142 7 : Unit floor(const Unit &a) { return a; } 143 : 144 5 : Unit ceil(const Unit &a) { return a; } 145 : 146 6 : Unit rint(const Unit &a) { return a; } 147 : 148 200 : Unit sqrt(const Unit &a) { 149 200 : if (a == none) 150 2 : return a; 151 198 : if (llnl::units::is_error(sqrt(a.underlying()))) 152 8 : throw except::UnitError("Unsupported unit as result of sqrt: sqrt(" + 153 12 : a.name() + ")."); 154 194 : return Unit{sqrt(a.underlying())}; 155 : } 156 : 157 667 : Unit pow(const Unit &a, const int64_t power) { 158 667 : if (a == none) 159 0 : return a; 160 667 : if (llnl::units::pow_overflows(a.underlying(), static_cast<int>(power))) 161 2 : throw except::UnitError("Unsupported unit as result of pow: pow(" + 162 3 : a.name() + ", " + std::to_string(power) + ")."); 163 666 : return Unit{a.underlying().pow(static_cast<int>(power))}; 164 : } 165 : 166 163 : Unit trigonometric(const Unit &a) { 167 163 : if (a == units::rad || a == units::deg) 168 151 : return units::dimensionless; 169 12 : throw except::UnitError( 170 24 : "Trigonometric function requires rad or deg unit, got " + a.name() + "."); 171 : } 172 : 173 51 : Unit inverse_trigonometric(const Unit &a) { 174 51 : if (a == units::dimensionless) 175 36 : return units::rad; 176 15 : throw except::UnitError( 177 15 : "Inverse trigonometric function requires dimensionless unit, got " + 178 45 : a.name() + "."); 179 : } 180 : 181 120 : Unit sin(const Unit &a) { return trigonometric(a); } 182 24 : Unit cos(const Unit &a) { return trigonometric(a); } 183 19 : Unit tan(const Unit &a) { return trigonometric(a); } 184 17 : Unit asin(const Unit &a) { return inverse_trigonometric(a); } 185 17 : Unit acos(const Unit &a) { return inverse_trigonometric(a); } 186 17 : Unit atan(const Unit &a) { return inverse_trigonometric(a); } 187 28 : Unit atan2(const Unit &y, const Unit &x) { 188 32 : expect_not_none(x, "atan2"); 189 28 : expect_not_none(y, "atan2"); 190 25 : if (x == y) 191 18 : return units::rad; 192 7 : throw except::UnitError( 193 14 : "atan2 function requires matching units for input, got a " + x.name() + 194 28 : " b " + y.name() + "."); 195 : } 196 : 197 24 : Unit hyperbolic(const Unit &a) { 198 24 : if (a == units::dimensionless) 199 18 : return units::dimensionless; 200 6 : throw except::UnitError( 201 12 : "Hyperbolic function requires dimensionless input, got " + a.name() + 202 12 : "."); 203 : } 204 : 205 7 : Unit sinh(const Unit &a) { return hyperbolic(a); } 206 3 : Unit cosh(const Unit &a) { return hyperbolic(a); } 207 3 : Unit tanh(const Unit &a) { return hyperbolic(a); } 208 3 : Unit asinh(const Unit &a) { return hyperbolic(a); } 209 5 : Unit acosh(const Unit &a) { return hyperbolic(a); } 210 3 : Unit atanh(const Unit &a) { return hyperbolic(a); } 211 : 212 11 : bool identical(const Unit &a, const Unit &b) { 213 22 : return a.has_value() && b.has_value() && 214 22 : a.underlying().is_exactly_the_same(b.underlying()); 215 : } 216 : 217 39 : void add_unit_alias(const std::string &name, const Unit &unit) { 218 39 : llnl::units::addUserDefinedUnit(name, unit.underlying()); 219 39 : } 220 : 221 129 : void clear_unit_aliases() { llnl::units::clearUserDefinedUnits(); } 222 : 223 : } // namespace scipp::units