Cantera  2.5.1
Units.cpp
Go to the documentation of this file.
1 //! @file Units.cpp
2 
3 // This file is part of Cantera. See License.txt in the top-level directory or
4 // at https://cantera.org/license.txt for license and copyright information.
5 
6 #include "cantera/base/Units.h"
8 #include "cantera/base/global.h"
10 #include "cantera/base/AnyMap.h"
11 
12 namespace {
13 using namespace Cantera;
14 
15 const std::map<std::string, Units> knownUnits{
16  {"", Units(1.0)},
17  {"1", Units(1.0)},
18 
19  // Mass [M]
20  {"kg", Units(1.0, 1, 0, 0)},
21  {"g", Units(1e-3, 1, 0, 0)},
22 
23  // Length [L]
24  {"m", Units(1.0, 0, 1, 0)},
25  {"micron", Units(1e-6, 0, 1, 0)},
26  {"angstrom", Units(1e-10, 0, 1, 0)},
27  {"Å", Units(1e-10, 0, 1, 0)},
28 
29  // Time [T]
30  {"s", Units(1.0, 0, 0, 1)},
31  {"min", Units(60, 0, 0, 1)},
32  {"hr", Units(3600, 0, 0, 1)},
33 
34  // Temperature [K]
35  {"K", Units(1.0, 0, 0, 0, 1)},
36  {"C", Units(1.0, 0, 0, 0, 1)},
37 
38  // Current [A]
39  {"A", Units(1.0, 0, 0, 0, 0, 1)},
40 
41  // Quantity [Q]
42  {"mol", Units(1e-3, 0, 0, 0, 0, 0, 1)},
43  {"gmol", Units(1e-3, 0, 0, 0, 0, 0, 1)},
44  {"mole", Units(1e-3, 0, 0, 0, 0, 0, 1)},
45  {"kmol", Units(1.0, 0, 0, 0, 0, 0, 1)},
46  {"kgmol", Units(1.0, 0, 0, 0, 0, 0, 1)},
47  {"molec", Units(1.0/Avogadro, 0, 0, 0, 0, 0, 1)},
48 
49  // Energy [M*L^2/T^2]
50  {"J", Units(1.0, 1, 2, -2)},
51  {"cal", Units(4.184, 1, 2, -2)},
52  {"erg", Units(1e-7, 1, 2, -2)},
53  {"eV", Units(ElectronCharge, 1, 2, -2)},
54 
55  // Force [M*L/T^2]
56  {"N", Units(1.0, 1, 1, -2)},
57  {"dyn", Units(1e-5, 1, 1, -2)},
58 
59  // Pressure [M/L/T^2]
60  {"Pa", Units(1.0, 1, -1, -2)},
61  {"atm", Units(OneAtm, 1, -1, -2)},
62  {"bar", Units(1.0e5, 1, -1, -2)},
63  {"dyn/cm^2", Units(0.1, 1, -1, -2)},
64 
65  // Volume [L^3]
66  {"m^3", Units(1.0, 0, 3, 0)},
67  {"liter", Units(0.001, 0, 3, 0)},
68  {"L", Units(0.001, 0, 3, 0)},
69  {"l", Units(0.001, 0, 3, 0)},
70  {"cc", Units(1.0e-6, 0, 3, 0)},
71 
72  // Other electrical units
73  {"ohm", Units(1.0, 1, 2, -3, 0, -2)}, // kg*m^2/s^3/A^2
74  {"V", Units(1.0, 1, 2, -3, 0, -1)}, // kg*m^2/s^3/A
75  {"coulomb", Units(1.0, 0, 0, 1, 0, 1)}, // A*s
76 
77  //! Activation energy units [M*L^2/T^2/Q]
78  {"J/kmol", Units(1.0, 1, 2, -2, 0, 0, -1)},
79 };
80 
81 const std::map<std::string, double> prefixes{
82  {"Y", 1e24},
83  {"Z", 1e21},
84  {"E", 1e18},
85  {"P", 1e15},
86  {"T", 1e12},
87  {"G", 1e9},
88  {"M", 1e6},
89  {"k", 1e3},
90  {"h", 1e2},
91  {"d", 1e-1},
92  {"c", 1e-2},
93  {"m", 1e-3},
94  {"u", 1e-6},
95  {"n", 1e-9},
96  {"p", 1e-12},
97  {"f", 1e-15},
98  {"a", 1e-18},
99  {"z", 1e-21},
100  {"y", 1e-24}
101 };
102 }
103 
104 namespace Cantera
105 {
106 
107 Units::Units(double factor, double mass, double length, double time,
108  double temperature, double current, double quantity)
109  : m_factor(factor)
110  , m_mass_dim(mass)
111  , m_length_dim(length)
112  , m_time_dim(time)
113  , m_temperature_dim(temperature)
114  , m_current_dim(current)
115  , m_quantity_dim(quantity)
116  , m_pressure_dim(0)
117  , m_energy_dim(0)
118 {
119  if (mass != 0 && length == -mass && time == -2 * mass
120  && temperature == 0 && current == 0 && quantity == 0) {
121  // Dimension looks like Pa^n
122  m_pressure_dim = mass;
123  } else if (mass != 0 && length == 2 * mass && time == -2 * mass
124  && temperature == 0 && current == 0 && quantity == 0)
125  {
126  // Dimension looks like J^n
127  m_energy_dim = mass;
128  }
129 }
130 
131 Units::Units(const std::string& name)
132  : m_factor(1.0)
133  , m_mass_dim(0)
134  , m_length_dim(0)
135  , m_time_dim(0)
136  , m_temperature_dim(0)
137  , m_current_dim(0)
138  , m_quantity_dim(0)
139  , m_pressure_dim(0)
140  , m_energy_dim(0)
141 {
142  size_t start = 0;
143  while (true) {
144  // Split into groups of the form 'unit^exponent'
145  size_t stop = name.find_first_of("*/", start);
146  size_t carat = name.find('^', start);
147  if (carat > stop) {
148  // No carat in this group
149  carat = npos;
150  }
151  std::string unit = trimCopy(
152  name.substr(start, std::min(carat, stop) - start));
153 
154  double exponent = 1.0;
155  if (carat != npos) {
156  exponent = fpValueCheck(name.substr(carat+1, stop-carat-1));
157  }
158  if (start != 0 && name[start-1] == '/') {
159  // This unit is in the denominator
160  exponent = -exponent;
161  }
162 
163  if (knownUnits.find(unit) != knownUnits.end()) {
164  // Incorporate the unit defined by the current group
165  *this *= knownUnits.at(unit).pow(exponent);
166  } else {
167  // See if the unit looks like a prefix + base unit
168  std::string prefix = unit.substr(0, 1);
169  std::string suffix = unit.substr(1);
170  if (prefixes.find(prefix) != prefixes.end() &&
171  knownUnits.find(suffix) != knownUnits.end()) {
172  Units u = knownUnits.at(suffix);
173  u.scale(prefixes.at(prefix));
174  *this *= u.pow(exponent);
175  } else {
176  throw CanteraError("Units::Units(string)",
177  "Unknown unit '{}' in unit string '{}'", unit, name);
178  }
179  }
180 
181  start = stop+1;
182  if (stop == npos) {
183  break;
184  }
185  }
186 }
187 
188 bool Units::convertible(const Units& other) const
189 {
190  return (m_mass_dim == other.m_mass_dim &&
191  m_length_dim == other.m_length_dim &&
192  m_time_dim == other.m_time_dim &&
193  m_temperature_dim == other.m_temperature_dim &&
194  m_current_dim == other.m_current_dim &&
195  m_quantity_dim == other.m_quantity_dim);
196 }
197 
199 {
200  m_factor *= other.m_factor;
201  m_mass_dim += other.m_mass_dim;
202  m_length_dim += other.m_length_dim;
203  m_time_dim += other.m_time_dim;
204  m_temperature_dim += other.m_temperature_dim;
205  m_current_dim += other.m_current_dim;
206  m_quantity_dim += other.m_quantity_dim;
208  m_energy_dim += other.m_energy_dim;
209  return *this;
210 }
211 
212 Units Units::pow(double exponent) const {
213  return Units(std::pow(m_factor, exponent),
214  m_mass_dim * exponent,
215  m_length_dim * exponent,
216  m_time_dim * exponent,
217  m_temperature_dim * exponent,
218  m_current_dim * exponent,
219  m_quantity_dim * exponent);
220 }
221 
222 std::string Units::str() const {
223  return fmt::format("Units({} kg^{} * m^{} * s^{} * K^{} * A^{} * kmol^{})",
224  m_factor, m_mass_dim, m_length_dim, m_time_dim,
225  m_temperature_dim, m_current_dim, m_quantity_dim);
226 }
227 
228 UnitSystem::UnitSystem(std::initializer_list<std::string> units)
229  : m_mass_factor(1.0)
230  , m_length_factor(1.0)
231  , m_time_factor(1.0)
232  , m_pressure_factor(1.0)
233  , m_energy_factor(1.0)
234  , m_activation_energy_factor(1.0)
235  , m_quantity_factor(1.0)
236  , m_explicit_activation_energy(false)
237 {
238  setDefaults(units);
239 }
240 
241 void UnitSystem::setDefaults(std::initializer_list<std::string> units)
242 {
243  for (const auto& name : units) {
244  auto unit = Units(name);
245  if (unit.convertible(knownUnits.at("kg"))) {
246  m_mass_factor = unit.factor();
247  } else if (unit.convertible(knownUnits.at("m"))) {
248  m_length_factor = unit.factor();
249  } else if (unit.convertible(knownUnits.at("s"))) {
250  m_time_factor = unit.factor();
251  } else if (unit.convertible(knownUnits.at("kmol"))) {
252  m_quantity_factor = unit.factor();
253  } else if (unit.convertible(knownUnits.at("Pa"))) {
254  m_pressure_factor = unit.factor();
255  } else if (unit.convertible(knownUnits.at("J"))) {
256  m_energy_factor = unit.factor();
257  } else if (unit.convertible(knownUnits.at("K"))
258  || unit.convertible(knownUnits.at("A"))) {
259  // Do nothing -- no other scales are supported for temperature and current
260  } else {
261  throw CanteraError("UnitSystem::setDefaults",
262  "Unable to match unit '{}' to a basic dimension", name);
263  }
264  }
267  }
268 }
269 
270 void UnitSystem::setDefaults(const std::map<std::string, std::string>& units)
271 {
272  for (const auto& item : units) {
273  auto& name = item.first;
274  Units unit(item.second);
275  if (name == "mass" && unit.convertible(knownUnits.at("kg"))) {
276  m_mass_factor = unit.factor();
277  } else if (name == "length" && unit.convertible(knownUnits.at("m"))) {
278  m_length_factor = unit.factor();
279  } else if (name == "time" && unit.convertible(knownUnits.at("s"))) {
280  m_time_factor = unit.factor();
281  } else if (name == "temperature" && item.second == "K") {
282  // do nothing - no other temperature scales are supported
283  } else if (name == "current" && item.second == "A") {
284  // do nothing - no other current scales are supported
285  } else if (name == "quantity" && unit.convertible(knownUnits.at("kmol"))) {
286  m_quantity_factor = unit.factor();
287  } else if (name == "pressure" && unit.convertible(knownUnits.at("Pa"))) {
288  m_pressure_factor = unit.factor();
289  } else if (name == "energy" && unit.convertible(knownUnits.at("J"))) {
290  m_energy_factor = unit.factor();
291  } else if (name == "activation-energy") {
292  // handled separately to allow override
293  } else {
294  throw CanteraError("UnitSystem::setDefaults",
295  "Unable to set default unit for '{}' to '{}' ({}).",
296  name, item.second, unit.str());
297  }
298  }
299  if (units.find("activation-energy") != units.end()) {
300  setDefaultActivationEnergy(units.at("activation-energy"));
301  } else if (!m_explicit_activation_energy) {
303  }
304 }
305 
306 void UnitSystem::setDefaultActivationEnergy(const std::string& e_units)
307 {
308  Units u(e_units);
309  if (u.convertible(Units("J/kmol"))) {
311  } else if (u.convertible(knownUnits.at("K"))) {
313  } else if (u.convertible(knownUnits.at("eV"))) {
315  } else {
316  throw CanteraError("Units::setDefaultActivationEnergy",
317  "Unable to match unit '{}' to a unit of activation energy", e_units);
318  }
320 }
321 
322 double UnitSystem::convert(double value, const std::string& src,
323  const std::string& dest) const
324 {
325  return convert(value, Units(src), Units(dest));
326 }
327 
328 double UnitSystem::convert(double value, const Units& src,
329  const Units& dest) const
330 {
331  if (!src.convertible(dest)) {
332  throw CanteraError("UnitSystem::convert",
333  "Incompatible units:\n {} and\n {}", src.str(), dest.str());
334  }
335  return value * src.factor() / dest.factor();
336 }
337 
338 double UnitSystem::convert(double value, const std::string& dest) const
339 {
340  return convert(value, Units(dest));
341 }
342 
343 double UnitSystem::convert(double value, const Units& dest) const
344 {
345  return value / dest.factor()
346  * pow(m_mass_factor, dest.m_mass_dim - dest.m_pressure_dim - dest.m_energy_dim)
347  * pow(m_length_factor, dest.m_length_dim + dest.m_pressure_dim - 2*dest.m_energy_dim)
348  * pow(m_time_factor, dest.m_time_dim + 2*dest.m_pressure_dim + 2*dest.m_energy_dim)
349  * pow(m_quantity_factor, dest.m_quantity_dim)
350  * pow(m_pressure_factor, dest.m_pressure_dim)
351  * pow(m_energy_factor, dest.m_energy_dim);
352 }
353 
354 static std::pair<double, std::string> split_unit(const AnyValue& v) {
355  if (v.is<std::string>()) {
356  // Should be a value and units, separated by a space, e.g. '2e4 J/kmol'
357  std::string val_units = v.asString();
358  size_t space = val_units.find(" ");
359  if (space == npos) {
360  throw CanteraError("split_unit (UnitSystem)",
361  "Couldn't parse '{}' as a space-separated value/unit pair\n",
362  val_units);
363  }
364  return {fpValueCheck(val_units.substr(0, space)),
365  val_units.substr(space+1)};
366  } else {
367  // Just a value
368  return {v.asDouble(), ""};
369  }
370 }
371 
372 double UnitSystem::convert(const AnyValue& v, const std::string& dest) const
373 {
374  return convert(v, Units(dest));
375 }
376 
377 double UnitSystem::convert(const AnyValue& v, const Units& dest) const
378 {
379  auto val_units = split_unit(v);
380  if (val_units.second.empty()) {
381  // Just a value, so convert using default units
382  return convert(val_units.first, dest);
383  } else {
384  // Both source and destination units are explicit
385  return convert(val_units.first, Units(val_units.second), dest);
386  }
387 }
388 
389 vector_fp UnitSystem::convert(const std::vector<AnyValue>& vals,
390  const std::string& dest) const
391 {
392  return convert(vals, Units(dest));
393 }
394 
395 vector_fp UnitSystem::convert(const std::vector<AnyValue>& vals,
396  const Units& dest) const
397 {
398  vector_fp out;
399  for (const auto& val : vals) {
400  out.emplace_back(convert(val, dest));
401  }
402  return out;
403 }
404 
405 double UnitSystem::convertActivationEnergy(double value, const std::string& src,
406  const std::string& dest) const
407 {
408  // Convert to J/kmol
409  Units usrc(src);
410  if (usrc.convertible(Units("J/kmol"))) {
411  value *= usrc.factor();
412  } else if (usrc.convertible(Units("K"))) {
413  value *= GasConstant * usrc.factor();
414  } else if (usrc.convertible(Units("eV"))) {
415  value *= Avogadro * usrc.factor();
416  } else {
417  throw CanteraError("UnitSystem::convertActivationEnergy",
418  "Don't understand units '{}' as an activation energy", src);
419  }
420 
421  // Convert from J/kmol
422  Units udest(dest);
423  if (udest.convertible(Units("J/kmol"))) {
424  value /= udest.factor();
425  } else if (udest.convertible(Units("K"))) {
426  value /= GasConstant * udest.factor();
427  } else if (udest.convertible(Units("eV"))) {
428  value /= Avogadro * udest.factor();
429  } else {
430  throw CanteraError("UnitSystem::convertActivationEnergy",
431  "Don't understand units '{}' as an activation energy", dest);
432  }
433 
434  return value;
435 }
436 
438  const std::string& dest) const
439 {
440  Units udest(dest);
441  if (udest.convertible(Units("J/kmol"))) {
442  return value * m_activation_energy_factor / udest.factor();
443  } else if (udest.convertible(knownUnits.at("K"))) {
444  return value * m_activation_energy_factor / GasConstant;
445  } else if (udest.convertible(knownUnits.at("eV"))) {
446  return value * m_activation_energy_factor / (Avogadro * udest.factor());
447  } else {
448  throw CanteraError("UnitSystem::convertActivationEnergy",
449  "'{}' is not a unit of activation energy", dest);
450  }
451 }
452 
454  const std::string& dest) const
455 {
456  auto val_units = split_unit(v);
457  if (val_units.second.empty()) {
458  // Just a value, so convert using default units
459  return convertActivationEnergy(val_units.first, dest);
460  } else {
461  // Both source and destination units are explicit
462  return convertActivationEnergy(val_units.first, val_units.second, dest);
463  }
464 }
465 
466 }
Header for unit conversion utilities, which are used to translate user input from input files (See In...
A wrapper for a variable whose type is determined at runtime.
Definition: AnyMap.h:77
const std::string & asString() const
Return the held value, if it is a string.
Definition: AnyMap.cpp:424
double & asDouble()
Return the held value as a double, if it is a double or a long int.
Definition: AnyMap.cpp:465
bool is() const
Returns true if the held value is of the specified type.
Definition: AnyMap.inl.h:61
Base class for exceptions thrown by Cantera classes.
Definition: ctexceptions.h:61
double m_activation_energy_factor
Factor to convert activation energy from this unit system to J/kmol.
Definition: Units.h:202
bool m_explicit_activation_energy
True if activation energy units are set explicitly, rather than as a combination of energy and quanti...
Definition: Units.h:209
double m_time_factor
Factor to convert time from this unit system to seconds.
Definition: Units.h:193
double m_pressure_factor
Factor to convert pressure from this unit system to Pa.
Definition: Units.h:196
UnitSystem()
Default constructor for unit system (needed as VS2019 does not recognize an optional argument with a ...
Definition: Units.h:108
double m_energy_factor
Factor to convert energy from this unit system to J.
Definition: Units.h:199
double m_length_factor
Factor to convert length from this unit system to meters.
Definition: Units.h:190
double convert(double value, const std::string &src, const std::string &dest) const
Convert value from the units of src to the units of dest.
Definition: Units.cpp:322
void setDefaultActivationEnergy(const std::string &e_units)
Set the default units to convert from when using the convertActivationEnergy function.
Definition: Units.cpp:306
double convertActivationEnergy(double value, const std::string &src, const std::string &dest) const
Convert value from the units of src to the units of dest, allowing for the different dimensions that ...
Definition: Units.cpp:405
double m_mass_factor
Factor to convert mass from this unit system to kg.
Definition: Units.h:187
double m_quantity_factor
Factor to convert quantity from this unit system to kmol.
Definition: Units.h:205
void setDefaults(std::initializer_list< std::string > units)
Set the default units to convert from when explicit units are not provided.
Definition: Units.cpp:241
A representation of the units associated with a dimensional quantity.
Definition: Units.h:30
Units pow(double expoonent) const
Raise these Units to a power, changing both the conversion factor and the dimensions of these Units.
Definition: Units.cpp:212
double m_energy_dim
pseudo-dimension to track explicit energy units
Definition: Units.h:70
double m_pressure_dim
pseudo-dimension to track explicit pressure units
Definition: Units.h:69
void scale(double k)
Scale the unit by the factor k
Definition: Units.h:60
double m_factor
conversion factor to Cantera base units
Definition: Units.h:62
Units & operator*=(const Units &other)
Multiply two Units objects, combining their conversion factors and dimensions.
Definition: Units.cpp:198
bool convertible(const Units &other) const
Returns true if the specified Units are dimensionally consistent.
Definition: Units.cpp:188
double factor() const
Return the factor for converting from this unit to Cantera's base units.
Definition: Units.h:45
std::string str() const
Provide a string representation of these Units.
Definition: Units.cpp:222
Units(double factor=1.0, double mass=0, double length=0, double time=0, double temperature=0, double current=0, double quantity=0)
Create a Units object with the specified dimensions.
Definition: Units.cpp:107
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...
This file contains definitions for utility functions and text for modules, inputfiles,...
const size_t npos
index returned by functions to indicate "no position"
Definition: ct_defs.h:188
const double Avogadro
Avogadro's Number [number/kmol].
Definition: ct_defs.h:63
const double OneAtm
One atmosphere [Pa].
Definition: ct_defs.h:78
std::vector< double > vector_fp
Turn on the use of stl vectors for the basic array type within cantera Vector of doubles.
Definition: ct_defs.h:180
const double GasConstant
Universal Gas Constant [J/kmol/K].
Definition: ct_defs.h:109
const double ElectronCharge
Elementary charge [C].
Definition: ct_defs.h:72
Namespace for the Cantera kernel.
Definition: AnyMap.cpp:264
std::string trimCopy(const std::string &input)
Trim.
doublereal fpValueCheck(const std::string &val)
Translate a string into one doublereal value, with error checking.
Contains declarations for string manipulation functions within Cantera.