Add 'Units' class for doing conversions with dimensionality checks
Introduces a new, more natural notation for writing unit strings, for use in YAML input files. Unlike 'toSI', conversions are checked for dimensional consistency.
This commit is contained in:
parent
ea88d4f9fb
commit
b8cb2c30f8
4 changed files with 318 additions and 1 deletions
95
include/cantera/base/Units.h
Normal file
95
include/cantera/base/Units.h
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* @file Units.h
|
||||||
|
* Header for unit conversion utilities, which are used to translate
|
||||||
|
* user input from input files (See \ref inputfiles and
|
||||||
|
* class \link Cantera::Units Units\endlink).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
||||||
|
// at https://www.cantera.org/license.txt for license and copyright information.
|
||||||
|
|
||||||
|
#ifndef CT_UNITS_H
|
||||||
|
#define CT_UNITS_H
|
||||||
|
|
||||||
|
#include "cantera/base/ct_defs.h"
|
||||||
|
|
||||||
|
namespace Cantera
|
||||||
|
{
|
||||||
|
|
||||||
|
//! A representation of the units associated with a dimensional quantity.
|
||||||
|
/*!
|
||||||
|
* Used for converting quantities between unit systems and checking for
|
||||||
|
* dimensional consistency. Units objects are mainly used within UnitSystem
|
||||||
|
* class to convert values from a user-specified Unit system to Cantera's
|
||||||
|
* base units (SI + kmol).
|
||||||
|
*/
|
||||||
|
class Units
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Create a Units object with the specified dimensions.
|
||||||
|
explicit Units(double factor=1.0, double mass=0, double length=0,
|
||||||
|
double time=0, double temperature=0, double current=0,
|
||||||
|
double quantity=0);
|
||||||
|
|
||||||
|
//! Create an object with the specified dimensions
|
||||||
|
explicit Units(const std::string& name);
|
||||||
|
|
||||||
|
//! Returns `true` if the specified Units are dimensionally consistent
|
||||||
|
bool convertible(const Units& other) const;
|
||||||
|
|
||||||
|
//! Return the factor for converting from this unit to Cantera's base
|
||||||
|
//! units.
|
||||||
|
double factor() const { return m_factor; }
|
||||||
|
|
||||||
|
//! Multiply two Units objects, combining their conversion factors and
|
||||||
|
//! dimensions
|
||||||
|
Units& operator*=(const Units& other);
|
||||||
|
|
||||||
|
//! Provide a string representation of these Units
|
||||||
|
std::string str() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Scale the unit by the factor `k`
|
||||||
|
void scale(double k) { m_factor *= k; }
|
||||||
|
|
||||||
|
//! Raise these Units to a power, changing both the conversion factor and
|
||||||
|
//! the dimensions of these Units.
|
||||||
|
Units pow(double expoonent) const;
|
||||||
|
|
||||||
|
double m_factor; //!< conversion factor to Cantera base units
|
||||||
|
double m_mass_dim;
|
||||||
|
double m_length_dim;
|
||||||
|
double m_time_dim;
|
||||||
|
double m_temperature_dim;
|
||||||
|
double m_current_dim;
|
||||||
|
double m_quantity_dim;
|
||||||
|
|
||||||
|
friend class UnitSystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//! Unit conversion utility
|
||||||
|
/*!
|
||||||
|
* String representations of units can be written using multiplication,
|
||||||
|
* division, and exponentiation. Spaces are ignored. Positive, negative, and
|
||||||
|
* decimal exponents are permitted. Examples:
|
||||||
|
*
|
||||||
|
* kg*m/s^2
|
||||||
|
* J/kmol
|
||||||
|
* m*s^-2
|
||||||
|
* J/kg/K
|
||||||
|
*
|
||||||
|
* @ingroup inputfiles
|
||||||
|
*/
|
||||||
|
class UnitSystem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Convert `value` from the units of `src` to the units of `dest`.
|
||||||
|
double convert(double value, const std::string& src,
|
||||||
|
const std::string& dest) const;
|
||||||
|
double convert(double value, const Units& src, const Units& dest) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
190
src/base/Units.cpp
Normal file
190
src/base/Units.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
//! @file Units.cpp
|
||||||
|
|
||||||
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
||||||
|
// at https://www.cantera.org/license.txt for license and copyright information.
|
||||||
|
|
||||||
|
#include "cantera/base/Units.h"
|
||||||
|
#include "cantera/base/ctexceptions.h"
|
||||||
|
#include "cantera/base/global.h"
|
||||||
|
#include "cantera/base/stringUtils.h"
|
||||||
|
#include "cantera/base/AnyMap.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using namespace Cantera;
|
||||||
|
|
||||||
|
const std::map<std::string, Units> knownUnits{
|
||||||
|
{"", Units(1.0)},
|
||||||
|
{"1", Units(1.0)},
|
||||||
|
|
||||||
|
// Mass [M]
|
||||||
|
{"kg", Units(1.0, 1, 0, 0)},
|
||||||
|
{"g", Units(1e-3, 1, 0, 0)},
|
||||||
|
|
||||||
|
// Length [L]
|
||||||
|
{"m", Units(1.0, 0, 1, 0)},
|
||||||
|
{"micron", Units(1e-6, 0, 1, 0)},
|
||||||
|
{"angstrom", Units(1e-10, 0, 1, 0)},
|
||||||
|
{"Å", Units(1e-10, 0, 1, 0)},
|
||||||
|
|
||||||
|
// Time [T]
|
||||||
|
{"s", Units(1.0, 0, 0, 1)},
|
||||||
|
{"min", Units(60, 0, 0, 1)},
|
||||||
|
{"hr", Units(3600, 0, 0, 1)},
|
||||||
|
|
||||||
|
// Temperature [K]
|
||||||
|
{"K", Units(1.0, 0, 0, 0, 1)},
|
||||||
|
{"C", Units(1.0, 0, 0, 0, 1)},
|
||||||
|
|
||||||
|
// Current [A]
|
||||||
|
{"A", Units(1.0, 0, 0, 0, 0, 1)},
|
||||||
|
|
||||||
|
// Quantity [Q]
|
||||||
|
{"mol", Units(1e-3, 0, 0, 0, 0, 0, 1)},
|
||||||
|
{"gmol", Units(1e-3, 0, 0, 0, 0, 0, 1)},
|
||||||
|
{"mole", Units(1e-3, 0, 0, 0, 0, 0, 1)},
|
||||||
|
{"kmol", Units(1.0, 0, 0, 0, 0, 0, 1)},
|
||||||
|
{"kgmol", Units(1.0, 0, 0, 0, 0, 0, 1)},
|
||||||
|
{"molec", Units(1.0/Avogadro, 0, 0, 0, 0, 0, 1)},
|
||||||
|
|
||||||
|
// Energy [M*L^2/T^2]
|
||||||
|
{"J", Units(1.0, 1, 2, -2)},
|
||||||
|
{"cal", Units(4.184, 1, 2, -2)},
|
||||||
|
{"erg", Units(1e-7, 1, 2, -2)},
|
||||||
|
{"eV", Units(ElectronCharge, 1, 2, -2)},
|
||||||
|
|
||||||
|
// Pressure [M/L/T^2]
|
||||||
|
{"Pa", Units(1.0, 1, -1, -2)},
|
||||||
|
{"atm", Units(OneAtm, 1, -1, -2)},
|
||||||
|
{"bar", Units(1.0e5, 1, -1, -2)},
|
||||||
|
|
||||||
|
// Volume [L^3]
|
||||||
|
{"m^3", Units(1.0, 0, 3, 0)},
|
||||||
|
{"liter", Units(0.001, 0, 3, 0)},
|
||||||
|
{"L", Units(0.001, 0, 3, 0)},
|
||||||
|
{"l", Units(0.001, 0, 3, 0)},
|
||||||
|
{"cc", Units(1.0e-6, 0, 3, 0)},
|
||||||
|
|
||||||
|
// Other electrical units
|
||||||
|
{"ohm", Units(1.0, 1, 2, -3, 0, -2)}, // kg*m^2/s^3/A^2
|
||||||
|
{"V", Units(1.0, 1, 2, -3, 0, -1)}, // kg*m^2/s^3/A
|
||||||
|
{"coulomb", Units(1.0, 0, 0, 1, 0, 1)}, // A*s
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Cantera
|
||||||
|
{
|
||||||
|
|
||||||
|
Units::Units(double factor, double mass, double length, double time,
|
||||||
|
double temperature, double current, double quantity)
|
||||||
|
: m_factor(factor)
|
||||||
|
, m_mass_dim(mass)
|
||||||
|
, m_length_dim(length)
|
||||||
|
, m_time_dim(time)
|
||||||
|
, m_temperature_dim(temperature)
|
||||||
|
, m_current_dim(current)
|
||||||
|
, m_quantity_dim(quantity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Units::Units(const std::string& name)
|
||||||
|
: m_factor(1.0)
|
||||||
|
, m_mass_dim(0)
|
||||||
|
, m_length_dim(0)
|
||||||
|
, m_time_dim(0)
|
||||||
|
, m_temperature_dim(0)
|
||||||
|
, m_current_dim(0)
|
||||||
|
, m_quantity_dim(0)
|
||||||
|
{
|
||||||
|
size_t start = 0;
|
||||||
|
while (true) {
|
||||||
|
// Split into groups of the form 'unit^exponent'
|
||||||
|
size_t stop = name.find_first_of("*/", start);
|
||||||
|
size_t carat = name.find('^', start);
|
||||||
|
if (carat > stop) {
|
||||||
|
// No carat in this group
|
||||||
|
carat = npos;
|
||||||
|
}
|
||||||
|
std::string unit = trimCopy(
|
||||||
|
name.substr(start, std::min(carat, stop) - start));
|
||||||
|
|
||||||
|
double exponent = 1.0;
|
||||||
|
if (carat != npos) {
|
||||||
|
exponent = fpValueCheck(name.substr(carat+1, stop-carat-1));
|
||||||
|
}
|
||||||
|
if (start != 0 && name[start-1] == '/') {
|
||||||
|
// This unit is in the denominator
|
||||||
|
exponent = -exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knownUnits.find(unit) != knownUnits.end()) {
|
||||||
|
// Incorporate the unit defined by the current group
|
||||||
|
*this *= knownUnits.at(unit).pow(exponent);
|
||||||
|
} else {
|
||||||
|
throw CanteraError("Units::Units(string)",
|
||||||
|
"Unknown unit '{}' in unit string '{}'", unit, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = stop+1;
|
||||||
|
if (stop == npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Units::convertible(const Units& other) const
|
||||||
|
{
|
||||||
|
return (m_mass_dim == other.m_mass_dim &&
|
||||||
|
m_length_dim == other.m_length_dim &&
|
||||||
|
m_time_dim == other.m_time_dim &&
|
||||||
|
m_temperature_dim == other.m_temperature_dim &&
|
||||||
|
m_current_dim == other.m_current_dim &&
|
||||||
|
m_quantity_dim == other.m_quantity_dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
Units& Units::operator*=(const Units& other)
|
||||||
|
{
|
||||||
|
m_factor *= other.m_factor;
|
||||||
|
m_mass_dim += other.m_mass_dim;
|
||||||
|
m_length_dim += other.m_length_dim;
|
||||||
|
m_time_dim += other.m_time_dim;
|
||||||
|
m_temperature_dim += other.m_temperature_dim;
|
||||||
|
m_current_dim += other.m_current_dim;
|
||||||
|
m_quantity_dim += other.m_quantity_dim;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Units Units::pow(double exponent) const {
|
||||||
|
return Units(std::pow(m_factor, exponent),
|
||||||
|
m_mass_dim * exponent,
|
||||||
|
m_length_dim * exponent,
|
||||||
|
m_time_dim * exponent,
|
||||||
|
m_temperature_dim * exponent,
|
||||||
|
m_current_dim * exponent,
|
||||||
|
m_quantity_dim * exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Units::str() const {
|
||||||
|
return fmt::format("Units({} kg^{} * m^{} * s^{} * K^{} * A^{} * kmol^{})",
|
||||||
|
m_factor, m_mass_dim, m_length_dim, m_time_dim,
|
||||||
|
m_temperature_dim, m_current_dim, m_quantity_dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double UnitSystem::convert(double value, const std::string& src,
|
||||||
|
const std::string& dest) const
|
||||||
|
{
|
||||||
|
return convert(value, Units(src), Units(dest));
|
||||||
|
}
|
||||||
|
|
||||||
|
double UnitSystem::convert(double value, const Units& src,
|
||||||
|
const Units& dest) const
|
||||||
|
{
|
||||||
|
if (!src.convertible(dest)) {
|
||||||
|
throw CanteraError("UnitSystem::convert",
|
||||||
|
"Incompatible units:\n {} and\n {}", src.str(), dest.str());
|
||||||
|
}
|
||||||
|
return value * src.factor() / dest.factor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// This file is part of Cantera. See License.txt in the top-level directory or
|
// This file is part of Cantera. See License.txt in the top-level directory or
|
||||||
// at http://www.cantera.org/license.txt for license and copyright information.
|
// at https://www.cantera.org/license.txt for license and copyright information.
|
||||||
|
|
||||||
#ifndef CT_UNITS_H
|
#ifndef CT_UNITS_H
|
||||||
#define CT_UNITS_H
|
#define CT_UNITS_H
|
||||||
|
|
|
||||||
32
test/general/test_units.cpp
Normal file
32
test/general/test_units.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "cantera/base/Units.h"
|
||||||
|
|
||||||
|
using namespace Cantera;
|
||||||
|
|
||||||
|
TEST(Units, convert_to_base_units) {
|
||||||
|
UnitSystem U;
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "Pa", "kg/m/s^2"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "J", "kg*m^2/s^2"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "ohm", "kg*m^2/s^3/A^2"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "V", "kg*m^2/A*s^-3"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "coulomb", "A*s"), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Units, notation) {
|
||||||
|
UnitSystem U;
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(2.0, "m^2", "m*m"), 2.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(3.0, "", "kg/kg"), 3.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "1/m^2", "m^-2"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(4.0, "s^3", "s^5/s^2"), 4.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, "kg * m/s ^2", "s^-2*kg*m"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1.0, " kg*m / s^ 2", "s ^-2 * kg*m"), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Units, basic_conversions) {
|
||||||
|
UnitSystem U;
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(100, "cm", "m"), 1.0);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(2, "kmol", "mol"), 2000);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1000, "cal", "J"), 4184);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(2, "m^3", "l"), 2000);
|
||||||
|
EXPECT_DOUBLE_EQ(U.convert(1, "atm", "Pa"), 101325);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue