LCOV - code coverage report
Current view: top level - units - unit.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 141 142 99.3 %
Date: 2024-12-01 01:56:34 Functions: 46 46 100.0 %

          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

Generated by: LCOV version 1.14