diff --git a/include/cantera/base/Units.h b/include/cantera/base/Units.h index b9fac71a5..8c72b7ed0 100644 --- a/include/cantera/base/Units.h +++ b/include/cantera/base/Units.h @@ -63,6 +63,7 @@ private: double m_temperature_dim; double m_current_dim; double m_quantity_dim; + double m_pressure_dim; //!< pseudo-dimension to track explicit pressure units friend class UnitSystem; }; @@ -70,6 +71,11 @@ private: //! Unit conversion utility /*! + * Provides functions for converting dimensional values from a given unit system. + * The main use is for converting values specified in input files to Cantera's + * native unit system, which is SI units except for the use of kmol as the base + * unit of quantity, i.e. kilogram, meter, second, kelvin, ampere, and kmol. + * * String representations of units can be written using multiplication, * division, and exponentiation. Spaces are ignored. Positive, negative, and * decimal exponents are permitted. Examples: @@ -86,10 +92,45 @@ private: class UnitSystem { public: + //! Create a unit system with the specified default units + UnitSystem(std::initializer_list units={}); + + //! Set the default units to convert from when explicit units are not + //! provided. Defaults can be set for mass, length, time, quantity, and + //! pressure. Conversion using the pressure unit is done only when the + //! target units explicitly contain pressure units. + //! + //! * To use SI+kmol: `setDefaults({"kg", "m", "s", "kmol"});` + //! * To use CGS+mol: `setDefaults({"cm", "g", "mol"});` + void setDefaults(std::initializer_list units); + //! 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; + //! Convert `value` from this unit system (defined by `setDefaults`) to the + //! specified units. + double convert(double value, const std::string& dest) const; + double convert(double value, const Units& dest) const; + +private: + //! Factor to convert mass from this unit system to kg + double m_mass_factor; + + //! Factor to convert length from this unit system to meters + double m_length_factor; + + //! Factor to convert time from this unit system to seconds + double m_time_factor; + + //! Factor to convert energy from this unit system to Joules + double m_energy_factor; + + //! Factor to convert pressure from this unit system to Pa + double m_pressure_factor; + + //! Factor to convert quantity from this unit system to kmol + double m_quantity_factor; }; } diff --git a/src/base/Units.cpp b/src/base/Units.cpp index 6d4a3e6d2..a8ea6d66d 100644 --- a/src/base/Units.cpp +++ b/src/base/Units.cpp @@ -105,7 +105,13 @@ Units::Units(double factor, double mass, double length, double time, , m_temperature_dim(temperature) , m_current_dim(current) , m_quantity_dim(quantity) + , m_pressure_dim(0) { + if (mass != 0 && length == -mass && time == -2 * mass + && temperature == 0 && current == 0 && quantity == 0) { + // Dimension looks like Pa^n + m_pressure_dim = mass; + } } Units::Units(const std::string& name) @@ -116,6 +122,7 @@ Units::Units(const std::string& name) , m_temperature_dim(0) , m_current_dim(0) , m_quantity_dim(0) + , m_pressure_dim(0) { size_t start = 0; while (true) { @@ -182,6 +189,7 @@ Units& Units::operator*=(const Units& other) m_temperature_dim += other.m_temperature_dim; m_current_dim += other.m_current_dim; m_quantity_dim += other.m_quantity_dim; + m_pressure_dim += other.m_pressure_dim; return *this; } @@ -202,6 +210,41 @@ std::string Units::str() const { } +UnitSystem::UnitSystem(std::initializer_list units) + : m_mass_factor(1.0) + , m_length_factor(1.0) + , m_time_factor(1.0) + , m_energy_factor(1.0) + , m_pressure_factor(1.0) + , m_quantity_factor(1.0) +{ + setDefaults(units); +} + +void UnitSystem::setDefaults(std::initializer_list units) +{ + for (const auto& name : units) { + auto unit = Units(name); + if (unit.convertible(knownUnits.at("kg"))) { + m_mass_factor = unit.factor(); + } else if (unit.convertible(knownUnits.at("m"))) { + m_length_factor = unit.factor(); + } else if (unit.convertible(knownUnits.at("s"))) { + m_time_factor = unit.factor(); + } else if (unit.convertible(knownUnits.at("kmol"))) { + m_quantity_factor = unit.factor(); + } else if (unit.convertible(knownUnits.at("Pa"))) { + m_pressure_factor = unit.factor(); + } else if (unit.convertible(knownUnits.at("K")) + || unit.convertible(knownUnits.at("A"))) { + // Do nothing -- no other scales are supported for temperature and current + } else { + throw CanteraError("UnitSystem::setDefaults", + "Unable to match unit '{}' to a basic dimension", name); + } + } +} + double UnitSystem::convert(double value, const std::string& src, const std::string& dest) const { @@ -218,4 +261,19 @@ double UnitSystem::convert(double value, const Units& src, return value * src.factor() / dest.factor(); } +double UnitSystem::convert(double value, const std::string& dest) const +{ + return convert(value, Units(dest)); +} + +double UnitSystem::convert(double value, const Units& dest) const +{ + return value / dest.factor() + * pow(m_mass_factor, dest.m_mass_dim - dest.m_pressure_dim) + * pow(m_length_factor, dest.m_length_dim + dest.m_pressure_dim) + * pow(m_time_factor, dest.m_time_dim + 2*dest.m_pressure_dim) + * pow(m_quantity_factor, dest.m_quantity_dim) + * pow(m_pressure_factor, dest.m_pressure_dim); +} + } diff --git a/test/general/test_units.cpp b/test/general/test_units.cpp index 133c575c0..b0c25c896 100644 --- a/test/general/test_units.cpp +++ b/test/general/test_units.cpp @@ -38,3 +38,14 @@ TEST(Units, prefixes) { EXPECT_DOUBLE_EQ(U.convert(1.0, "m^2", "cm^2"), 1e4); EXPECT_DOUBLE_EQ(U.convert(1.0, "m/s", "km/hr"), 3.6); } + +TEST(Units, with_defaults) { + UnitSystem U({"cm", "g", "mol", "atm"}); + EXPECT_DOUBLE_EQ(U.convert(1.0, "m"), 0.01); + EXPECT_DOUBLE_EQ(U.convert(1.0, "kmol/m^3"), 1000); + EXPECT_DOUBLE_EQ(U.convert(1.0, "kg/kmol"), 1.0); + EXPECT_DOUBLE_EQ(U.convert(1.0, "cm^2"), 1.0); + EXPECT_DOUBLE_EQ(U.convert(1.0, "Pa"), 101325); + EXPECT_DOUBLE_EQ(U.convert(1.0, "hPa"), 1013.25); + EXPECT_DOUBLE_EQ(U.convert(1.0, "Pa*m^6/kmol"), 101325*1e-12*1000); +}