Loading [MathJax]/extensions/tex2jax.js
https://mooseframework.inl.gov
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends
Units.C
Go to the documentation of this file.
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)
32  }},
33  {"atm",
34  {101.325e3,
35  0,
38  {MooseUnits::BaseUnit::SECOND, -2}}}}, // Standard atmosphere
39  // avoid matching volts
40  {"eV",
41  {
42  1.602176634e-19,
43  0,
47  }}, // electronvolt
48  // avoid matching grams
49  {"erg",
50  {
51  1e-7,
52  0,
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,
80  {MooseUnits::BaseUnit::SECOND, -2}}}}, // Newton
81  {"Pa",
82  {1,
83  0,
86  {MooseUnits::BaseUnit::SECOND, -2}}}}, // Pascal
87  {"J",
88  {1,
89  0,
92  {MooseUnits::BaseUnit::SECOND, -2}}}}, // Joule
93  {"cal",
94  {4.184,
95  0,
98  {MooseUnits::BaseUnit::SECOND, -2}}}}, // Calorie
99  {"W",
100  {1,
101  0,
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,
114  {"F",
115  {1,
116  0,
121  {"S",
122  {1,
123  0,
127  {MooseUnits::BaseUnit::AMPERE, 2}}}}, // Siemens = 1/Ohm (electrical conductance)
128  {"Wb",
129  {1,
130  0,
134  {MooseUnits::BaseUnit::AMPERE, -1}}}}, // Weber (magnetic flux)
135  {"T",
136  {1,
137  0,
140  {MooseUnits::BaseUnit::AMPERE, -1}}}}, // Tesla (magnetic flux density)
141  {"H",
142  {1,
143  0,
147  {MooseUnits::BaseUnit::AMPERE, -2}}}}, // Henry (inductance)
148 
149  // cgs units
150  {"Ba",
151  {0.1,
152  0,
156  -2}}}}, // barye (unit of pressure - not to be confused with bar)
157  {"dyn",
158  {1e-5,
159  0,
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,
173  {MooseUnits::BaseUnit::SECOND, -2}}}}, // pound of force
174  {"psi",
175  {6.894757e3,
176  0,
179  {MooseUnits::BaseUnit::SECOND, -2}}}}, // pound-force per square inch
180 
181  // misc.
182  {"BTU",
183  {1055.06,
184  0,
187  {MooseUnits::BaseUnit::SECOND, -2}}}}, // ISO 31-4 British thermal unit
188  {"bar",
189  {1e5,
190  0,
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 MooseUnits::MooseUnits(const std::string & unit_string) : _factor(1.0), _shift(0.0), _base()
208 {
209  // parse the passed in unit string
210  parse(unit_string);
211 }
212 
213 bool
215 {
216  if (_base.size() != u._base.size())
217  return false;
218  for (std::size_t i = 0; i < _base.size(); ++i)
219  if (_base[i] != u._base[i])
220  return false;
221 
222  return true;
223 }
224 
225 Real
226 MooseUnits::convert(Real from_value, const MooseUnits & from_unit) const
227 {
228  if (!conformsTo(from_unit))
229  mooseError("Cannot convert between non-conforming units '", *this, "' and '", from_unit, "'.");
230  return ((from_value + from_unit._shift) * from_unit._factor) / _factor - _shift;
231 }
232 
233 void
234 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  std::stack<std::vector<std::pair<MooseUnits::BaseUnit, int>>> stack;
242  std::stack<char> op_stack;
243  std::stack<MooseUnits> out;
244 
245  auto it = unit_string.begin();
246  const auto end = unit_string.end();
247 
248  do
249  {
250  // skip whitespace
251  if (*it == ' ')
252  {
253  it++;
254  continue;
255  }
256  // opening parenthesis
257  else if (*it == '(')
258  op_stack.push(*(it++));
259  // multiply or divide
260  else if (*it == '*' || *it == '/')
261  {
262  // pop */ off
263  while (!op_stack.empty() && (op_stack.top() == '*' || op_stack.top() == '/'))
264  {
265  auto top = op_stack.top();
266  op_stack.pop();
267 
268  if (out.size() < 2)
269  parseError(
270  unit_string, it, "Applying ", top, " but don't have enough items on the stack.");
271  auto rhs = out.top();
272  out.pop();
273  if (top == '*')
274  out.top() = out.top() * rhs;
275  else
276  out.top() = out.top() / rhs;
277  }
278 
279  op_stack.push(*(it++));
280  }
281  // closing parenthesis
282  else if (*it == ')')
283  {
284  do
285  {
286  if (op_stack.empty())
287  parseError(unit_string, it, "Mismatching right parenthesis.");
288 
289  auto top = op_stack.top();
290  op_stack.pop();
291 
292  if (top == '(')
293  break;
294 
295  if (top == '*' || top == '/')
296  {
297  if (out.size() < 2)
298  parseError(
299  unit_string, it, "Applying ", top, " but don't have enough items on the stack.");
300  auto rhs = out.top();
301  out.pop();
302  if (top == '*')
303  out.top() = out.top() * rhs;
304  else
305  out.top() = out.top() / rhs;
306  }
307  } while (true);
308  it++;
309  }
310  // exponent
311  else if (*it == '^')
312  {
313  // check for sign
314  int sign = 1;
315  ++it;
316  if (it != end && *it == '-')
317  {
318  sign = -1;
319  it++;
320  }
321 
322  int num = 0;
323  bool valid = false;
324  while (*it >= '0' && *it <= '9')
325  {
326  num *= 10;
327  num += *(it++) - '0';
328  valid = true;
329  }
330 
331  // error
332  if (!valid)
333  {
334  if (it == end)
335  parseError(unit_string, it, "Expected a number but found end of string.");
336  else
337  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  if (out.empty())
342  parseError(unit_string, it, "Applying an exponent, but found no units in front of it.");
343  out.top() = std::pow(out.top(), sign * num);
344  }
345  else
346  {
347  std::string unit;
348  while (it != end && ((*it >= 'a' && *it <= 'z') || (*it >= 'A' && *it <= 'Z') ||
349  (*it >= '0' && *it <= '9') || *it == '.' || *it == '-'))
350  unit += *(it++);
351 
352  // check if a number prefix is present
353  Real si_factor;
354  std::stringstream ss(unit);
355  if ((ss >> si_factor).fail() || !std::isfinite(si_factor))
356  si_factor = 1.0;
357  else
358  {
359  if (ss.eof())
360  {
361  out.push(MooseUnits(si_factor));
362  continue;
363  }
364  ss >> unit;
365  }
366 
367  // parse the unit and a potential SI prefix
368  auto len = unit.length();
369  if (len == 0)
370  {
371  if (it == end)
372  parseError(unit_string, it, "Expected unit but found end of string.");
373  else
374  parseError(unit_string, it, "Expected unit but found '", *it, "'.");
375  }
376 
377  unsigned int i = 0;
378  for (; i < _unit_table.size(); ++i)
379  {
380  const auto & s = _unit_table[i].first;
381  const auto slen = s.length();
382  if (slen <= len && unit.substr(len - slen) == s)
383  break;
384  }
385 
386  // invalid unit
387  if (i == _unit_table.size())
388  parseError(unit_string, it, "Unknown unit '", unit, "'.");
389 
390  // get prefix
391  const auto & s = _unit_table[i].first;
392  const auto slen = s.length();
393  if (slen < len)
394  {
395  const auto prefix = unit.substr(0, len - slen);
396  auto jt = _si_prefix.find(prefix);
397  if (jt != _si_prefix.end())
398  si_factor = jt->second;
399  else
400  parseError(unit_string, it, "Unknown SI prefix '", prefix, "' on unit '", unit, "'.");
401  }
402 
403  // construct a unit object and push to the output stack
404  MooseUnits d = _unit_table[i].second * si_factor;
405  out.push(d);
406  }
407  } while (it != end);
408 
409  // unwind operator stack
410  while (!op_stack.empty())
411  {
412  auto top = op_stack.top();
413  op_stack.pop();
414 
415  if (top == '(' || top == ')')
416  parseError(unit_string, it, "Unit string contains unmatched parenthesis.");
417 
418  if (top == '*' || top == '/')
419  {
420  if (out.size() < 2)
421  parseError(unit_string,
422  it,
423  "Applying ",
424  top,
425  " but don't have enough items on the stack.",
426  out.size());
427  auto rhs = out.top();
428  out.pop();
429  if (top == '*')
430  out.top() = out.top() * rhs;
431  else
432  out.top() = out.top() / rhs;
433  }
434  }
435 
436  // consistency check
437  if (out.size() != 1)
438  mooseError("Internal parse error. Remaining output stack size should be 1, but is ",
439  out.size());
440 
441  *this = out.top();
442  simplify();
443 }
444 
446 MooseUnits::operator*(const Real f) const
447 {
448  MooseUnits u = *this;
449  u._factor *= f;
450  u._shift *= f;
451  return u;
452 }
453 
456 {
457  MooseUnits u = rhs;
458  u._factor *= _factor;
459  u._base.insert(u._base.end(), _base.begin(), _base.end());
460  u._shift = 0.0;
461  u.simplify();
462  return u;
463 }
464 
467 {
468  MooseUnits u = rhs;
469  u._factor = _factor / u._factor;
470  for (auto & b : u._base)
471  b.second *= -1;
472  u._base.insert(u._base.end(), _base.begin(), _base.end());
473  u._shift = 0.0;
474  u.simplify();
475  return u;
476 }
477 
478 bool
480 {
481  if (_factor != rhs._factor || _shift != rhs._shift)
482  return false;
483  return conformsTo(rhs);
484 }
485 
486 bool
487 MooseUnits::operator==(const Real rhs) const
488 {
489  return (_factor == rhs && _shift == 0.0 && _base.empty());
490 }
491 
492 MooseUnits::operator Real() const
493 {
494  if (!_base.empty())
495  mooseError("Unit '", *this, "' is not a pure number.");
496  return _factor;
497 }
498 
499 void
501 {
502  std::map<BaseUnit, int> base_map;
503  for (auto & b : _base)
504  {
505  auto j = base_map.find(b.first);
506  if (j == base_map.end())
507  base_map.insert(b);
508  else
509  j->second += b.second;
510  }
511 
512  // replace base vector with map contents
513  _base.clear();
514  for (auto & u : base_map)
515  if (u.second != 0)
516  _base.push_back(u);
517 }
518 
519 bool
521 {
522  // must have only one base unit
523  if (_base.size() != 1)
524  return false;
525 
526  // its exponent must be one
527  if (_base[0].second != 1)
528  return false;
529 
530  // and it has to be the right base unit
531  return (_base[0].first == base);
532 }
533 
534 namespace std
535 {
537 pow(const MooseUnits & u, int e)
538 {
539  MooseUnits r = u;
540  r._factor = std::pow(u._factor, e);
541  r._shift = 0.0;
542  for (auto & b : r._base)
543  b.second *= e;
544  return r;
545 }
546 }
547 
548 std::ostream &
549 MooseUnits::latex(std::ostream & os)
550 {
551  os.iword(geti()) = 1;
552  return os;
553 }
554 std::ostream &
555 MooseUnits::text(std::ostream & os)
556 {
557  os.iword(geti()) = 0;
558  return os;
559 }
560 
561 int
563 {
564  static int i = std::ios_base::xalloc();
565  return i;
566 }
567 
568 std::ostream &
569 operator<<(std::ostream & os, const MooseUnits & u)
570 {
571  bool latex = (os.iword(MooseUnits::geti()) == 1);
572  static const std::vector<std::string> base_unit = {"m", "kg", "s", "A", "K", "at", "cd"};
573 
574  if (u._factor != 1.0 || u._base.empty())
575  {
576  os << u._factor;
577  if (!u._base.empty())
578  os << ' ';
579  }
580 
581  for (const auto i : index_range(u._base))
582  {
583  os << (i ? (latex ? "\\cdot " : "*") : "");
584 
585  const auto & unit = base_unit[int(u._base[i].first)];
586  if (latex)
587  os << "\\text{" << unit << "}";
588  else
589  os << unit;
590 
591  if (u._base[i].second != 1)
592  {
593  if (latex)
594  os << "^{" << u._base[i].second << '}';
595  else
596  os << '^' << u._base[i].second;
597  }
598  }
599 
600  return os;
601 }
MooseUnits operator*(const Real f) const
Unit prefactor scaling.
Definition: Units.C:446
static const std::vector< std::pair< std::string, MooseUnits > > _unit_table
Definition: Units.h:67
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:302
bool operator==(const MooseUnits &rhs) const
Definition: Units.C:479
void parseError(const std::string &unit_string, std::string::const_iterator it, Args... args)
helper function to generate a pretty mooseError
Definition: Units.h:126
std::basic_ostream< charT, traits > * os
Definition: InfixIterator.h:33
std::vector< std::pair< BaseUnit, int > > _base
base SI units and their exponents
Definition: Units.h:118
static const std::map< std::string, Real > _si_prefix
data tables with SI prefixes and known units
Definition: Units.h:66
bool isBase(const MooseUnits::BaseUnit) const
check if the unit has a pure base
Definition: Units.C:520
static int geti()
iosteam manipulator helper to toggle latex / text output
Definition: Units.C:562
T sign(T x)
Definition: MathUtils.h:84
MooseUnits operator/(const MooseUnits &rhs) const
Definition: Units.C:466
std::ostream & operator<<(std::ostream &os, const MooseUnits &u)
Definition: Units.C:569
MooseUnits(const std::string &unit_string)
Definition: Units.C:207
BaseUnit
Definition: Units.h:34
Physical unit management class with runtime unit string parsing, unit checking, unit conversion...
Definition: Units.h:32
bool conformsTo(const MooseUnits &) const
checks if the units are dimensionally conforming (i.e. the describe the same physical quanitity) ...
Definition: Units.C:214
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
OStreamProxy out
Real _factor
conversion factor w.r.t. the base SI units
Definition: Units.h:109
Real convert(Real from_value, const MooseUnits &from_unit) const
Converts from_value in units of from_units to value this units.
Definition: Units.C:226
static std::ostream & text(std::ostream &os)
Definition: Units.C:555
Real _shift
additive shift (for Celsius and Fahrenheit)
Definition: Units.h:112
std::vector< ElemQuality > valid(const ElemType t)
MooseUnits pow(const MooseUnits &, int)
Definition: Units.C:537
void parse(const std::string &unit_string)
parse a unit string into a MooseUnits object
Definition: Units.C:234
void ErrorVector unsigned int
auto index_range(const T &sizable)
static std::ostream & latex(std::ostream &os)
iostream manipulators
Definition: Units.C:549
void simplify()
simplify into the canonical form that permits comparisons
Definition: Units.C:500