LCOV - code coverage report
Current view: top level - src/utils - Units.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 181 218 83.0 %
Date: 2025-07-17 01:28:37 Functions: 14 17 82.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "Units.h"
      11             : #include "libmesh/int_range.h"
      12             : 
      13             : #include <stack>
      14             : #include <algorithm>
      15             : #include <sstream>
      16             : 
      17             : const std::map<std::string, Real> MooseUnits::_si_prefix = {
      18             :     {"Y", 1e24}, {"Z", 1e21},  {"E", 1e18},  {"P", 1e15},  {"T", 1e12},  {"G", 1e9},  {"M", 1e6},
      19             :     {"k", 1e3},  {"h", 1e2},   {"da", 1e1},  {"d", 1e-1},  {"c", 1e-2},  {"m", 1e-3}, {"mu", 1e-6},
      20             :     {"n", 1e-9}, {"p", 1e-12}, {"f", 1e-15}, {"a", 1e-18}, {"z", 1e-21}, {"y", 1e-24}};
      21             : 
      22             : const std::vector<std::pair<std::string, MooseUnits>> MooseUnits::_unit_table = {
      23             :     // avoid matching meters
      24             :     {"Ohm",
      25             :      {
      26             :          1, // conversion factor
      27             :          0, // additive shift (currently only used for Celsius and Fahrenheit)
      28             :          {{MooseUnits::BaseUnit::KILOGRAM, 1},
      29             :           {MooseUnits::BaseUnit::METER, 2},
      30             :           {MooseUnits::BaseUnit::SECOND, -3},
      31             :           {MooseUnits::BaseUnit::AMPERE, -2}},
      32             :      }},
      33             :     {"atm",
      34             :      {101.325e3,
      35             :       0,
      36             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
      37             :        {MooseUnits::BaseUnit::METER, -1},
      38             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // Standard atmosphere
      39             :     // avoid matching volts
      40             :     {"eV",
      41             :      {
      42             :          1.602176634e-19,
      43             :          0,
      44             :          {{MooseUnits::BaseUnit::KILOGRAM, 1},
      45             :           {MooseUnits::BaseUnit::METER, 2},
      46             :           {MooseUnits::BaseUnit::SECOND, -2}},
      47             :      }}, // electronvolt
      48             :     // avoid matching grams
      49             :     {"erg",
      50             :      {
      51             :          1e-7,
      52             :          0,
      53             :          {{MooseUnits::BaseUnit::KILOGRAM, 1},
      54             :           {MooseUnits::BaseUnit::METER, 2},
      55             :           {MooseUnits::BaseUnit::SECOND, -2}},
      56             :      }}, // erg
      57             : 
      58             :     // avoid matching Farad or Coulomb
      59             :     {"degC", {1, 273.15, {{MooseUnits::BaseUnit::KELVIN, 1}}}},         // Celsius
      60             :     {"degF", {5.0 / 9.0, 459.67, {{MooseUnits::BaseUnit::KELVIN, 1}}}}, // Fahrenheit
      61             :     {"degR", {5.0 / 9.0, 0, {{MooseUnits::BaseUnit::KELVIN, 1}}}},      // Rankine
      62             : 
      63             :     {"Ang", {1e-10, 0, {{MooseUnits::BaseUnit::METER, 1}}}}, // Angstrom
      64             : 
      65             :     // Base units
      66             :     {"m", {1, 0, {{MooseUnits::BaseUnit::METER, 1}}}},
      67             :     {"g", {0.001, 0, {{MooseUnits::BaseUnit::KILOGRAM, 1}}}},
      68             :     {"s", {1, 0, {{MooseUnits::BaseUnit::SECOND, 1}}}},
      69             :     {"A", {1, 0, {{MooseUnits::BaseUnit::AMPERE, 1}}}},
      70             :     {"K", {1, 0, {{MooseUnits::BaseUnit::KELVIN, 1}}}},
      71             :     {"mol", {6.02214076e23, 0, {{MooseUnits::BaseUnit::COUNT, 1}}}},
      72             :     {"cd", {1, 0, {{MooseUnits::BaseUnit::CANDELA, 1}}}},
      73             : 
      74             :     // Derived units
      75             :     {"N",
      76             :      {1,
      77             :       0,
      78             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
      79             :        {MooseUnits::BaseUnit::METER, 1},
      80             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // Newton
      81             :     {"Pa",
      82             :      {1,
      83             :       0,
      84             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
      85             :        {MooseUnits::BaseUnit::METER, -1},
      86             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // Pascal
      87             :     {"J",
      88             :      {1,
      89             :       0,
      90             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
      91             :        {MooseUnits::BaseUnit::METER, 2},
      92             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // Joule
      93             :     {"cal",
      94             :      {4.184,
      95             :       0,
      96             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
      97             :        {MooseUnits::BaseUnit::METER, 2},
      98             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // Calorie
      99             :     {"W",
     100             :      {1,
     101             :       0,
     102             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     103             :        {MooseUnits::BaseUnit::METER, 2},
     104             :        {MooseUnits::BaseUnit::SECOND, -3}}}}, // Watt
     105             :     {"C",
     106             :      {1, 0, {{MooseUnits::BaseUnit::AMPERE, 1}, {MooseUnits::BaseUnit::SECOND, 1}}}}, // Coulomb
     107             :     {"V",
     108             :      {1,
     109             :       0,
     110             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     111             :        {MooseUnits::BaseUnit::METER, 2},
     112             :        {MooseUnits::BaseUnit::SECOND, -3},
     113             :        {MooseUnits::BaseUnit::AMPERE, -1}}}},
     114             :     {"F",
     115             :      {1,
     116             :       0,
     117             :       {{MooseUnits::BaseUnit::KILOGRAM, -1},
     118             :        {MooseUnits::BaseUnit::METER, -2},
     119             :        {MooseUnits::BaseUnit::SECOND, 4},
     120             :        {MooseUnits::BaseUnit::AMPERE, 2}}}},
     121             :     {"S",
     122             :      {1,
     123             :       0,
     124             :       {{MooseUnits::BaseUnit::KILOGRAM, -1},
     125             :        {MooseUnits::BaseUnit::METER, -2},
     126             :        {MooseUnits::BaseUnit::SECOND, 3},
     127             :        {MooseUnits::BaseUnit::AMPERE, 2}}}}, // Siemens = 1/Ohm (electrical conductance)
     128             :     {"Wb",
     129             :      {1,
     130             :       0,
     131             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     132             :        {MooseUnits::BaseUnit::METER, 2},
     133             :        {MooseUnits::BaseUnit::SECOND, -2},
     134             :        {MooseUnits::BaseUnit::AMPERE, -1}}}}, // Weber (magnetic flux)
     135             :     {"T",
     136             :      {1,
     137             :       0,
     138             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     139             :        {MooseUnits::BaseUnit::SECOND, -2},
     140             :        {MooseUnits::BaseUnit::AMPERE, -1}}}}, // Tesla (magnetic flux density)
     141             :     {"H",
     142             :      {1,
     143             :       0,
     144             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     145             :        {MooseUnits::BaseUnit::METER, 2},
     146             :        {MooseUnits::BaseUnit::SECOND, -2},
     147             :        {MooseUnits::BaseUnit::AMPERE, -2}}}}, // Henry (inductance)
     148             : 
     149             :     // cgs units
     150             :     {"Ba",
     151             :      {0.1,
     152             :       0,
     153             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     154             :        {MooseUnits::BaseUnit::METER, -1},
     155             :        {MooseUnits::BaseUnit::SECOND,
     156             :         -2}}}}, // barye (unit of pressure - not to be confused with bar)
     157             :     {"dyn",
     158             :      {1e-5,
     159             :       0,
     160             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     161             :        {MooseUnits::BaseUnit::METER, 1},
     162             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // dyne
     163             : 
     164             :     // Freedom units
     165             :     {"ft", {0.3048, 0, {{MooseUnits::BaseUnit::METER, 1}}}},        // foot
     166             :     {"in", {25.4e-3, 0, {{MooseUnits::BaseUnit::METER, 1}}}},       // inch
     167             :     {"lb", {0.45359237, 0, {{MooseUnits::BaseUnit::KILOGRAM, 1}}}}, // pound of mass
     168             :     {"lbf",
     169             :      {4.4482216152605,
     170             :       0,
     171             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     172             :        {MooseUnits::BaseUnit::METER, 1},
     173             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // pound of force
     174             :     {"psi",
     175             :      {6.894757e3,
     176             :       0,
     177             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     178             :        {MooseUnits::BaseUnit::METER, -1},
     179             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // pound-force per square inch
     180             : 
     181             :     // misc.
     182             :     {"BTU",
     183             :      {1055.06,
     184             :       0,
     185             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     186             :        {MooseUnits::BaseUnit::METER, 2},
     187             :        {MooseUnits::BaseUnit::SECOND, -2}}}}, // ISO 31-4 British thermal unit
     188             :     {"bar",
     189             :      {1e5,
     190             :       0,
     191             :       {{MooseUnits::BaseUnit::KILOGRAM, 1},
     192             :        {MooseUnits::BaseUnit::METER, -1},
     193             :        {MooseUnits::BaseUnit::SECOND, -2}}}},                        // bar (unit of pressure)
     194             :     {"h", {60 * 60, 0, {{MooseUnits::BaseUnit::SECOND, 1}}}},        // hour
     195             :     {"day", {60 * 60 * 24, 0, {{MooseUnits::BaseUnit::SECOND, 1}}}}, // day
     196             :     {"year",
     197             :      {60 * 60 * 24 * 365.25,
     198             :       0,
     199             :       {{MooseUnits::BaseUnit::SECOND, 1}}}}, // year (equal to an annum, we did not use a for the
     200             :     // symbol as 1 Pa would be both 1 Pascal and 1e15 annums)
     201             :     {"l", {1e-3, 0, {{MooseUnits::BaseUnit::METER, 3}}}}, // liter
     202             :     {"u",
     203             :      {1.66053906660e-27, 0, {{MooseUnits::BaseUnit::KILOGRAM, 3}}}}, // unified atomic mass unit
     204             :     {"at", {1, 0, {{MooseUnits::BaseUnit::COUNT, 1}}}}               // 1 single count (atom)
     205             : };
     206             : 
     207      204728 : MooseUnits::MooseUnits(const std::string & unit_string) : _factor(1.0), _shift(0.0), _base()
     208             : {
     209             :   // parse the passed in unit string
     210      204728 :   parse(unit_string);
     211      204728 : }
     212             : 
     213             : bool
     214          78 : MooseUnits::conformsTo(const MooseUnits & u) const
     215             : {
     216          78 :   if (_base.size() != u._base.size())
     217           0 :     return false;
     218         247 :   for (std::size_t i = 0; i < _base.size(); ++i)
     219         169 :     if (_base[i] != u._base[i])
     220           0 :       return false;
     221             : 
     222          78 :   return true;
     223             : }
     224             : 
     225             : Real
     226          48 : MooseUnits::convert(Real from_value, const MooseUnits & from_unit) const
     227             : {
     228          48 :   if (!conformsTo(from_unit))
     229           0 :     mooseError("Cannot convert between non-conforming units '", *this, "' and '", from_unit, "'.");
     230          48 :   return ((from_value + from_unit._shift) * from_unit._factor) / _factor - _shift;
     231             : }
     232             : 
     233             : void
     234      204728 : MooseUnits::parse(const std::string & unit_string)
     235             : {
     236             :   // we need to understand * / ^int ( ) string
     237             :   // m^2*kg/(N*s)^2
     238             :   // no numerical expressions are permitted (e.g. no 1/2 0.5 4*2)
     239             : 
     240             :   // parse stack
     241      204728 :   std::stack<std::vector<std::pair<MooseUnits::BaseUnit, int>>> stack;
     242      204728 :   std::stack<char> op_stack;
     243      204728 :   std::stack<MooseUnits> out;
     244             : 
     245      204728 :   auto it = unit_string.begin();
     246      204728 :   const auto end = unit_string.end();
     247             : 
     248             :   do
     249             :   {
     250             :     // skip whitespace
     251      611785 :     if (*it == ' ')
     252             :     {
     253           2 :       it++;
     254           2 :       continue;
     255             :     }
     256             :     // opening parenthesis
     257      611783 :     else if (*it == '(')
     258           7 :       op_stack.push(*(it++));
     259             :     // multiply or divide
     260      611776 :     else if (*it == '*' || *it == '/')
     261             :     {
     262             :       // pop */ off
     263      203525 :       while (!op_stack.empty() && (op_stack.top() == '*' || op_stack.top() == '/'))
     264             :       {
     265          12 :         auto top = op_stack.top();
     266          12 :         op_stack.pop();
     267             : 
     268          12 :         if (out.size() < 2)
     269           0 :           parseError(
     270             :               unit_string, it, "Applying ", top, " but don't have enough items on the stack.");
     271          12 :         auto rhs = out.top();
     272          12 :         out.pop();
     273          12 :         if (top == '*')
     274           6 :           out.top() = out.top() * rhs;
     275             :         else
     276           6 :           out.top() = out.top() / rhs;
     277          12 :       }
     278             : 
     279      203513 :       op_stack.push(*(it++));
     280             :     }
     281             :     // closing parenthesis
     282      408263 :     else if (*it == ')')
     283             :     {
     284             :       do
     285             :       {
     286          13 :         if (op_stack.empty())
     287           0 :           parseError(unit_string, it, "Mismatching right parenthesis.");
     288             : 
     289          13 :         auto top = op_stack.top();
     290          13 :         op_stack.pop();
     291             : 
     292          13 :         if (top == '(')
     293           7 :           break;
     294             : 
     295           6 :         if (top == '*' || top == '/')
     296             :         {
     297           6 :           if (out.size() < 2)
     298           0 :             parseError(
     299             :                 unit_string, it, "Applying ", top, " but don't have enough items on the stack.");
     300           6 :           auto rhs = out.top();
     301           6 :           out.pop();
     302           6 :           if (top == '*')
     303           4 :             out.top() = out.top() * rhs;
     304             :           else
     305           2 :             out.top() = out.top() / rhs;
     306           6 :         }
     307           6 :       } while (true);
     308           7 :       it++;
     309             :     }
     310             :     // exponent
     311      408256 :     else if (*it == '^')
     312             :     {
     313             :       // check for sign
     314          15 :       int sign = 1;
     315          15 :       ++it;
     316          15 :       if (it != end && *it == '-')
     317             :       {
     318           0 :         sign = -1;
     319           0 :         it++;
     320             :       }
     321             : 
     322          15 :       int num = 0;
     323          15 :       bool valid = false;
     324          30 :       while (*it >= '0' && *it <= '9')
     325             :       {
     326          15 :         num *= 10;
     327          15 :         num += *(it++) - '0';
     328          15 :         valid = true;
     329             :       }
     330             : 
     331             :       // error
     332          15 :       if (!valid)
     333             :       {
     334           0 :         if (it == end)
     335           0 :           parseError(unit_string, it, "Expected a number but found end of string.");
     336             :         else
     337           0 :           parseError(unit_string, it, "Expected a number but got '", (*it), "'.");
     338             :       }
     339             : 
     340             :       // exponents do not get pushed on the stack, but are applied immediately
     341          15 :       if (out.empty())
     342           0 :         parseError(unit_string, it, "Applying an exponent, but found no units in front of it.");
     343          15 :       out.top() = std::pow(out.top(), sign * num);
     344             :     }
     345             :     else
     346             :     {
     347      408241 :       std::string unit;
     348     1227424 :       while (it != end && ((*it >= 'a' && *it <= 'z') || (*it >= 'A' && *it <= 'Z') ||
     349      407817 :                            (*it >= '0' && *it <= '9') || *it == '.' || *it == '-'))
     350      411366 :         unit += *(it++);
     351             : 
     352             :       // check if a number prefix is present
     353             :       Real si_factor;
     354      408241 :       std::stringstream ss(unit);
     355      408241 :       if ((ss >> si_factor).fail() || !std::isfinite(si_factor))
     356      204789 :         si_factor = 1.0;
     357             :       else
     358             :       {
     359      203452 :         if (ss.eof())
     360             :         {
     361      203443 :           out.push(MooseUnits(si_factor));
     362      203443 :           continue;
     363             :         }
     364           9 :         ss >> unit;
     365             :       }
     366             : 
     367             :       // parse the unit and a potential SI prefix
     368      204798 :       auto len = unit.length();
     369      204798 :       if (len == 0)
     370             :       {
     371           0 :         if (it == end)
     372           0 :           parseError(unit_string, it, "Expected unit but found end of string.");
     373             :         else
     374           0 :           parseError(unit_string, it, "Expected unit but found '", *it, "'.");
     375             :       }
     376             : 
     377      204798 :       unsigned int i = 0;
     378     1857354 :       for (; i < _unit_table.size(); ++i)
     379             :       {
     380     1857354 :         const auto & s = _unit_table[i].first;
     381     1857354 :         const auto slen = s.length();
     382     1857354 :         if (slen <= len && unit.substr(len - slen) == s)
     383      204798 :           break;
     384             :       }
     385             : 
     386             :       // invalid unit
     387      204798 :       if (i == _unit_table.size())
     388           0 :         parseError(unit_string, it, "Unknown unit '", unit, "'.");
     389             : 
     390             :       // get prefix
     391      204798 :       const auto & s = _unit_table[i].first;
     392      204798 :       const auto slen = s.length();
     393      204798 :       if (slen < len)
     394             :       {
     395         918 :         const auto prefix = unit.substr(0, len - slen);
     396         918 :         auto jt = _si_prefix.find(prefix);
     397         918 :         if (jt != _si_prefix.end())
     398         918 :           si_factor = jt->second;
     399             :         else
     400           0 :           parseError(unit_string, it, "Unknown SI prefix '", prefix, "' on unit '", unit, "'.");
     401         918 :       }
     402             : 
     403             :       // construct a unit object and push to the output stack
     404      204798 :       MooseUnits d = _unit_table[i].second * si_factor;
     405      204798 :       out.push(d);
     406      611684 :     }
     407      611785 :   } while (it != end);
     408             : 
     409             :   // unwind operator stack
     410      408223 :   while (!op_stack.empty())
     411             :   {
     412      203495 :     auto top = op_stack.top();
     413      203495 :     op_stack.pop();
     414             : 
     415      203495 :     if (top == '(' || top == ')')
     416           0 :       parseError(unit_string, it, "Unit string contains unmatched parenthesis.");
     417             : 
     418      203495 :     if (top == '*' || top == '/')
     419             :     {
     420      203495 :       if (out.size() < 2)
     421           0 :         parseError(unit_string,
     422             :                    it,
     423             :                    "Applying ",
     424             :                    top,
     425             :                    " but don't have enough items on the stack.",
     426             :                    out.size());
     427      203495 :       auto rhs = out.top();
     428      203495 :       out.pop();
     429      203495 :       if (top == '*')
     430      203400 :         out.top() = out.top() * rhs;
     431             :       else
     432          95 :         out.top() = out.top() / rhs;
     433      203495 :     }
     434             :   }
     435             : 
     436             :   // consistency check
     437      204728 :   if (out.size() != 1)
     438           0 :     mooseError("Internal parse error. Remaining output stack size should be 1, but is ",
     439           0 :                out.size());
     440             : 
     441      204728 :   *this = out.top();
     442      204728 :   simplify();
     443      204728 : }
     444             : 
     445             : MooseUnits
     446      204798 : MooseUnits::operator*(const Real f) const
     447             : {
     448      204798 :   MooseUnits u = *this;
     449      204798 :   u._factor *= f;
     450      204798 :   u._shift *= f;
     451      204798 :   return u;
     452             : }
     453             : 
     454             : MooseUnits
     455      203452 : MooseUnits::operator*(const MooseUnits & rhs) const
     456             : {
     457      203452 :   MooseUnits u = rhs;
     458      203452 :   u._factor *= _factor;
     459      203452 :   u._base.insert(u._base.end(), _base.begin(), _base.end());
     460      203452 :   u._shift = 0.0;
     461      203452 :   u.simplify();
     462      203452 :   return u;
     463           0 : }
     464             : 
     465             : MooseUnits
     466        1174 : MooseUnits::operator/(const MooseUnits & rhs) const
     467             : {
     468        1174 :   MooseUnits u = rhs;
     469        1174 :   u._factor = _factor / u._factor;
     470        3427 :   for (auto & b : u._base)
     471        2253 :     b.second *= -1;
     472        1174 :   u._base.insert(u._base.end(), _base.begin(), _base.end());
     473        1174 :   u._shift = 0.0;
     474        1174 :   u.simplify();
     475        1174 :   return u;
     476           0 : }
     477             : 
     478             : bool
     479           1 : MooseUnits::operator==(const MooseUnits & rhs) const
     480             : {
     481           1 :   if (_factor != rhs._factor || _shift != rhs._shift)
     482           0 :     return false;
     483           1 :   return conformsTo(rhs);
     484             : }
     485             : 
     486             : bool
     487           0 : MooseUnits::operator==(const Real rhs) const
     488             : {
     489           0 :   return (_factor == rhs && _shift == 0.0 && _base.empty());
     490             : }
     491             : 
     492        1115 : MooseUnits::operator Real() const
     493             : {
     494        1115 :   if (!_base.empty())
     495           0 :     mooseError("Unit '", *this, "' is not a pure number.");
     496        1115 :   return _factor;
     497             : }
     498             : 
     499             : void
     500      409354 : MooseUnits::simplify()
     501             : {
     502      409354 :   std::map<BaseUnit, int> base_map;
     503      823586 :   for (auto & b : _base)
     504             :   {
     505      414232 :     auto j = base_map.find(b.first);
     506      414232 :     if (j == base_map.end())
     507      412010 :       base_map.insert(b);
     508             :     else
     509        2222 :       j->second += b.second;
     510             :   }
     511             : 
     512             :   // replace base vector with map contents
     513      409354 :   _base.clear();
     514      821364 :   for (auto & u : base_map)
     515      412010 :     if (u.second != 0)
     516      409799 :       _base.push_back(u);
     517      409354 : }
     518             : 
     519             : bool
     520         140 : MooseUnits::isBase(const MooseUnits::BaseUnit base) const
     521             : {
     522             :   // must have only one base unit
     523         140 :   if (_base.size() != 1)
     524           0 :     return false;
     525             : 
     526             :   // its exponent must be one
     527         140 :   if (_base[0].second != 1)
     528           0 :     return false;
     529             : 
     530             :   // and it has to be the right base unit
     531         140 :   return (_base[0].first == base);
     532             : }
     533             : 
     534             : namespace std
     535             : {
     536             : MooseUnits
     537          15 : pow(const MooseUnits & u, int e)
     538             : {
     539          15 :   MooseUnits r = u;
     540          15 :   r._factor = std::pow(u._factor, e);
     541          15 :   r._shift = 0.0;
     542          29 :   for (auto & b : r._base)
     543          14 :     b.second *= e;
     544          15 :   return r;
     545             : }
     546             : }
     547             : 
     548             : std::ostream &
     549           0 : MooseUnits::latex(std::ostream & os)
     550             : {
     551           0 :   os.iword(geti()) = 1;
     552           0 :   return os;
     553             : }
     554             : std::ostream &
     555           0 : MooseUnits::text(std::ostream & os)
     556             : {
     557           0 :   os.iword(geti()) = 0;
     558           0 :   return os;
     559             : }
     560             : 
     561             : int
     562          18 : MooseUnits::geti()
     563             : {
     564          18 :   static int i = std::ios_base::xalloc();
     565          18 :   return i;
     566             : }
     567             : 
     568             : std::ostream &
     569          18 : operator<<(std::ostream & os, const MooseUnits & u)
     570             : {
     571          18 :   bool latex = (os.iword(MooseUnits::geti()) == 1);
     572          26 :   static const std::vector<std::string> base_unit = {"m", "kg", "s", "A", "K", "at", "cd"};
     573             : 
     574          18 :   if (u._factor != 1.0 || u._base.empty())
     575             :   {
     576          14 :     os << u._factor;
     577          14 :     if (!u._base.empty())
     578          11 :       os << ' ';
     579             :   }
     580             : 
     581          57 :   for (const auto i : index_range(u._base))
     582             :   {
     583          39 :     os << (i ? (latex ? "\\cdot " : "*") : "");
     584             : 
     585          39 :     const auto & unit = base_unit[int(u._base[i].first)];
     586          39 :     if (latex)
     587           0 :       os << "\\text{" << unit << "}";
     588             :     else
     589          39 :       os << unit;
     590             : 
     591          39 :     if (u._base[i].second != 1)
     592             :     {
     593          25 :       if (latex)
     594           0 :         os << "^{" << u._base[i].second << '}';
     595             :       else
     596          25 :         os << '^' << u._base[i].second;
     597             :     }
     598             :   }
     599             : 
     600          18 :   return os;
     601           2 : }

Generated by: LCOV version 1.14