diff --git a/include/cantera/base/Units.h b/include/cantera/base/Units.h index 482de891f..79f34d36a 100644 --- a/include/cantera/base/Units.h +++ b/include/cantera/base/Units.h @@ -16,6 +16,8 @@ namespace Cantera { +class AnyValue; + //! A representation of the units associated with a dimensional quantity. /*! * Used for converting quantities between unit systems and checking for @@ -116,11 +118,27 @@ public: 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; + //! Convert a generic AnyValue node to the units specified in `dest`. If the + //! input is a double, convert it using the default units. If the input is a + //! string, treat this as a dimensioned value, e.g. '988 kg/m^3' and convert + //! from the specified units. + double convert(const AnyValue& val, const std::string& dest) const; + double convert(const AnyValue& val, const Units& dest) const; + + //! Convert an array of AnyValue nodes to the units specified in `dest`. For + //! each node, if the value is a double, convert it using the default units, + //! and if it is a string, treat it as a value with the given dimensions. + vector_fp convert(const std::vector& vals, + const std::string& dest) const; + vector_fp convert(const std::vector& vals, + const Units& dest) const; + //! Convert `value` from the units of `src` to the units of `dest`, allowing //! for the different dimensions that can be used for molar energies double convertMolarEnergy(double value, const std::string& src, @@ -130,6 +148,12 @@ public: //! specified units double convertMolarEnergy(double value, const std::string& dest) const; + //! Convert a generic AnyValue node to the units specified in `dest`. If the + //! input is a double, convert it using the default units. If the input is a + //! string, treat this as a dimensioned value, e.g. '2.7e4 J/kmol' and + //! convert from the specified units. + double convertMolarEnergy(const AnyValue& val, const std::string& dest) const; + private: //! Factor to convert mass from this unit system to kg double m_mass_factor; diff --git a/src/base/Units.cpp b/src/base/Units.cpp index 0630a94a9..5420f5def 100644 --- a/src/base/Units.cpp +++ b/src/base/Units.cpp @@ -291,6 +291,57 @@ double UnitSystem::convert(double value, const Units& dest) const * pow(m_pressure_factor, dest.m_pressure_dim); } +static std::pair split_unit(const AnyValue& v) { + if (v.is()) { + // Should be a value and units, separated by a space, e.g. '2e4 J/kmol' + std::string val_units = v.asString(); + size_t space = val_units.find(" "); + if (space == npos) { + throw CanteraError("UnitSystem::convert", + "Couldn't parse '{}' as a space-separated value/unit pair\n", + val_units); + } + return {fpValueCheck(val_units.substr(0, space)), + val_units.substr(space+1)}; + } else { + // Just a value + return {v.asDouble(), ""}; + } +} + +double UnitSystem::convert(const AnyValue& v, const std::string& dest) const +{ + return convert(v, Units(dest)); +} + +double UnitSystem::convert(const AnyValue& v, const Units& dest) const +{ + auto val_units = split_unit(v); + if (val_units.second.empty()) { + // Just a value, so convert using default units + return convert(val_units.first, dest); + } else { + // Both source and destination units are explicit + return convert(val_units.first, Units(val_units.second), dest); + } +} + +vector_fp UnitSystem::convert(const std::vector& vals, + const std::string& dest) const +{ + return convert(vals, Units(dest)); +} + +vector_fp UnitSystem::convert(const std::vector& vals, + const Units& dest) const +{ + vector_fp out; + for (const auto& val : vals) { + out.emplace_back(convert(val, dest)); + } + return out; +} + double UnitSystem::convertMolarEnergy(double value, const std::string& src, const std::string& dest) const { @@ -322,6 +373,7 @@ double UnitSystem::convertMolarEnergy(double value, const std::string& src, return value; } + double UnitSystem::convertMolarEnergy(double value, const std::string& dest) const { Units udest(dest); @@ -337,4 +389,17 @@ double UnitSystem::convertMolarEnergy(double value, const std::string& dest) con } } +double UnitSystem::convertMolarEnergy(const AnyValue& v, + const std::string& dest) const +{ + auto val_units = split_unit(v); + if (val_units.second.empty()) { + // Just a value, so convert using default units + return convertMolarEnergy(val_units.first, dest); + } else { + // Both source and destination units are explicit + return convertMolarEnergy(val_units.first, val_units.second, dest); + } +} + } diff --git a/test/general/test_units.cpp b/test/general/test_units.cpp index 33307b846..34afc4fc7 100644 --- a/test/general/test_units.cpp +++ b/test/general/test_units.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "cantera/base/Units.h" +#include "cantera/base/AnyMap.h" using namespace Cantera; @@ -67,3 +68,17 @@ TEST(Units, activation_energies) { EXPECT_DOUBLE_EQ(U.convertMolarEnergy(2000, "K"), 2000); EXPECT_DOUBLE_EQ(U.convertMolarEnergy(2000, "J/kmol"), 2000 * GasConstant); } + +TEST(Units, from_anymap) { + AnyMap m = AnyMap::fromYamlString( + "{p: 12 bar, v: 10, A: 1 cm^2, V: 1," + " k1: [5e2, 2, 29000], k2: [1e14, -1, 1300 cal/kmol]}"); + UnitSystem U({"mm", "min", "atm"}); + EXPECT_DOUBLE_EQ(U.convert(m["p"], "Pa"), 12e5); + EXPECT_DOUBLE_EQ(U.convert(m["v"], "cm/min"), 1.0); + EXPECT_DOUBLE_EQ(U.convert(m["A"], "mm^2"), 100); + EXPECT_DOUBLE_EQ(U.convert(m["V"], "m^3"), 1e-9); + auto k1 = m["k1"].asVector(); + EXPECT_DOUBLE_EQ(U.convert(k1[0], "m^3/kmol"), 1e-9*5e2); + EXPECT_DOUBLE_EQ(U.convertMolarEnergy(k1[2], "J/kmol"), 29000); +}