https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
17const 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
22const 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
207MooseUnits::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
213bool
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
225Real
226MooseUnits::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
233void
234MooseUnits::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)
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)
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
446MooseUnits::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
478bool
480{
481 if (_factor != rhs._factor || _shift != rhs._shift)
482 return false;
483 return conformsTo(rhs);
484}
485
486bool
487MooseUnits::operator==(const Real rhs) const
488{
489 return (_factor == rhs && _shift == 0.0 && _base.empty());
490}
491
492MooseUnits::operator Real() const
493{
494 if (!_base.empty())
495 mooseError("Unit '", *this, "' is not a pure number.");
496 return _factor;
497}
498
499void
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
519bool
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
534namespace std
535{
537pow(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
548std::ostream &
549MooseUnits::latex(std::ostream & os)
550{
551 os.iword(geti()) = 1;
552 return os;
553}
554std::ostream &
555MooseUnits::text(std::ostream & os)
556{
557 os.iword(geti()) = 0;
558 return os;
559}
560
561int
563{
564 static int i = std::ios_base::xalloc();
565 return i;
566}
567
568std::ostream &
569operator<<(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}
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application.
Definition MooseError.h:311
std::ostream & operator<<(std::ostream &os, const MooseUnits &u)
Definition Units.C:569
void ErrorVector unsigned int
Physical unit management class with runtime unit string parsing, unit checking, unit conversion,...
Definition Units.h:33
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
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
MooseUnits operator/(const MooseUnits &rhs) const
Definition Units.C:466
std::vector< std::pair< BaseUnit, int > > _base
base SI units and their exponents
Definition Units.h:118
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
Real _factor
conversion factor w.r.t. the base SI units
Definition Units.h:109
void parse(const std::string &unit_string)
parse a unit string into a MooseUnits object
Definition Units.C:234
bool conformsTo(const MooseUnits &) const
checks if the units are dimensionally conforming (i.e. the describe the same physical quanitity)
Definition Units.C:214
MooseUnits(const std::string &unit_string)
Definition Units.C:207
Real _shift
additive shift (for Celsius and Fahrenheit)
Definition Units.h:112
static const std::vector< std::pair< std::string, MooseUnits > > _unit_table
Definition Units.h:22
MooseUnits operator*(const Real f) const
Unit prefactor scaling.
Definition Units.C:446
bool operator==(const MooseUnits &rhs) const
Definition Units.C:479
static std::ostream & latex(std::ostream &os)
iostream manipulators
Definition Units.C:549
static const std::map< std::string, Real > _si_prefix
data tables with SI prefixes and known units
Definition Units.h:17
void simplify()
simplify into the canonical form that permits comparisons
Definition Units.C:500
static std::ostream & text(std::ostream &os)
Definition Units.C:555
MooseUnits pow(const MooseUnits &, int)
Definition Units.C:537