diff --git a/data/liquidvapor.yaml b/data/liquidvapor.yaml index 6cc017ccdc..123a234cf8 100644 --- a/data/liquidvapor.yaml +++ b/data/liquidvapor.yaml @@ -57,6 +57,14 @@ phases: T: 300.0 P: 1.01325e+05 pure-fluid-name: oxygen +- name: carbon-dioxide + thermo: pure-fluid + elements: [C, O] + species: [CO2] + state: + T: 280.0 + P: 1.01325e+05 + pure-fluid-name: carbon-dioxide - name: carbondioxide thermo: pure-fluid elements: [C, O] @@ -64,7 +72,10 @@ phases: state: T: 280.0 P: 1.01325e+05 - pure-fluid-name: carbondioxide + deprecated: >- + The phase name 'carbondioxide' in 'liquidvapor.yaml' is deprecated and will + be removed after Cantera 2.5. Use the phase name 'carbon-dioxide' instead. + pure-fluid-name: carbon-dioxide - name: heptane thermo: pure-fluid elements: [C, H] @@ -73,6 +84,14 @@ phases: T: 300.0 P: 1.01325e+05 pure-fluid-name: heptane +- name: HFC-134a + thermo: pure-fluid + elements: [C, F, H] + species: [C2F4H2] + state: + T: 300.0 + P: 1.01325e+05 + pure-fluid-name: HFC-134a - name: hfc134a thermo: pure-fluid elements: [C, F, H] @@ -80,7 +99,10 @@ phases: state: T: 300.0 P: 1.01325e+05 - pure-fluid-name: HFC134a + pure-fluid-name: HFC-134a + deprecated: >- + The phase name 'hfc134a' in 'liquidvapor.yaml' is deprecated and will + be removed after Cantera 2.5. Use the phase name 'HFC-134a' instead. # Note that these species definitions are used ONLY to set the # reference state values for the entropy and enthalpy at 298.15 K, diff --git a/doc/sphinx/yaml/phases.rst b/doc/sphinx/yaml/phases.rst index 15608f942b..db4d7adf91 100644 --- a/doc/sphinx/yaml/phases.rst +++ b/doc/sphinx/yaml/phases.rst @@ -316,6 +316,40 @@ Example:: - species: [Na+, OH-] beta: 0.06 +In addition, the Debye-Hückel model uses several species-specific properties +which may be defined in the ``Debye-Huckel`` field of the *species* entry. These +properties are: + +``ionic-radius`` + Size of the species. + +``electrolyte-species-type`` + One of ``solvent``, ``charged-species``, ``weak-acid-associated``, + ``strong-acid-associated``, ``polar-neutral``, or ``nonpolar-neutral``. + The type ``solvent`` is the default for the first species in the phase. The + type ``charged-species`` is the default for species with a net charge. + Otherwise, the default is and ``nonpolar-neutral``. + +``weak-acid-charge`` + Charge to use for species that can break apart into charged species. + +Example:: + + name: NaCl(aq) + composition: {Na: 1, Cl: 1} + thermo: + model: piecewise-Gibbs + h0: -96.03E3 cal/mol + dimensionless: true + data: {298.15: -174.5057463, 333.15: -174.5057463} + equation-of-state: + model: constant-volume + molar-volume: 1.3 + Debye-Huckel: + ionic-radius: 4 angstrom + electrolyte-species-type: weak-acid-associated + weak-acid-charge: -1.0 + .. _sec-yaml-edge: @@ -750,9 +784,9 @@ Additional fields: ``pure-fluid-name`` Name of the pure fluid model to use: - - ``carbondioxide`` + - ``carbon-dioxide`` - ``heptane`` - - ``hfc134a`` + - ``HFC-134a`` - ``hydrogen`` - ``methane`` - ``nitrogen`` diff --git a/doc/sphinx/yaml/species.rst b/doc/sphinx/yaml/species.rst index 8dd0f1bfe6..c9e83673bc 100644 --- a/doc/sphinx/yaml/species.rst +++ b/doc/sphinx/yaml/species.rst @@ -20,10 +20,11 @@ The fields of a ``species`` entry are: and parameters. See :ref:`sec-yaml-species-thermo`. ``equation-of-state`` - Mapping containing the equation of state model specification for the - species, any parameters for that model, and any parameters for interactions - with other species. :ref:`sec-yaml-species-eos`. If this field is absent, - the ``ideal-gas`` model is assumed. + A mapping or list of mappings. Each mapping contains an equation of state + model specification for the species, any parameters for that model, and any + parameters for interactions with other species. See + :ref:`sec-yaml-species-eos`. If this field is absent and a model is + required, the ``ideal-gas`` model is assumed. ``transport`` Mapping containing the species transport model specification and @@ -32,18 +33,9 @@ The fields of a ``species`` entry are: ``sites`` The number of sites occupied by a surface or edge species. Default is 1. -``ionic-radius`` - Size of the species. Used in the Debye-Hückel model. - -``electrolyte-species-type`` - One of ``solvent``, ``charged-species``, ``weak-acid-associated``, - ``strong-acid-associated``, ``polar-neutral``, or ``nonpolar-neutral``. - The types ``solvent``, ``charged-species``, and ``nonpolar-neutral`` can be - inferred automatically. Used in the Debye-Hückel model. - -``weak-acid-charge`` - Charge to use for species can break apart into charged species. Used in the - Debye-Hückel model. +``Debye-Huckel`` + Additional model parameters used in the Debye-Hückel model. See + :ref:`sec-yaml-Debye-Huckel` for more information. .. _sec-yaml-species-thermo: diff --git a/include/cantera/base/AnyMap.h b/include/cantera/base/AnyMap.h index f60cd75c65..0725044aae 100644 --- a/include/cantera/base/AnyMap.h +++ b/include/cantera/base/AnyMap.h @@ -26,7 +26,6 @@ namespace Cantera { class AnyMap; -class InputFile; //! A wrapper for a variable whose type is determined at runtime /*! @@ -54,6 +53,9 @@ class AnyValue AnyValue& operator=(AnyValue const& other); AnyValue& operator=(AnyValue&& other); + bool operator==(const AnyValue& other) const; + bool operator!=(const AnyValue& other) const; + //! If this AnyValue is an AnyMap, return the value stored in `key`. AnyValue& operator[](const std::string& key); const AnyValue& operator[](const std::string& key) const; @@ -71,9 +73,12 @@ class AnyValue //! messages. void setLoc(int line, int column); - //! Set information about the file used to create this value. Recursively - //! sets the information on any child elements. - void setFile(shared_ptr& file); + //! Get a value from the metadata applicable to the AnyMap tree containing + //! this AnyValue. + const AnyValue& getMetadata(const std::string& key) const; + + //! Propagate metadata to any child elements + void propagateMetadata(shared_ptr& file); //! Get the value of this key as the specified type. template @@ -102,6 +107,10 @@ class AnyValue AnyValue& operator=(const char* value); //! Return the held value, if it is a string const std::string& asString() const; + bool operator==(const std::string& other) const; + bool operator!=(const std::string& other) const; + friend bool operator==(const std::string& lhs, const AnyValue& rhs); + friend bool operator!=(const std::string& lhs, const AnyValue& rhs); explicit AnyValue(double value); AnyValue& operator=(double value); @@ -109,6 +118,10 @@ class AnyValue //! int`. double& asDouble(); const double& asDouble() const; + bool operator==(const double& other) const; + bool operator!=(const double& other) const; + friend bool operator==(const double& lhs, const AnyValue& rhs); + friend bool operator!=(const double& lhs, const AnyValue& rhs); explicit AnyValue(bool value); AnyValue& operator=(bool value); @@ -117,11 +130,20 @@ class AnyValue const bool& asBool() const; explicit AnyValue(long int value); + explicit AnyValue(int value); AnyValue& operator=(long int value); AnyValue& operator=(int value); //! Return the held value, if it is a `long int`. long int& asInt(); const long int& asInt() const; + bool operator==(const long int& other) const; + bool operator!=(const long int& other) const; + bool operator==(const int& other) const; + bool operator!=(const int& other) const; + friend bool operator==(const long int& lhs, const AnyValue& rhs); + friend bool operator!=(const long int& lhs, const AnyValue& rhs); + friend bool operator==(const int& lhs, const AnyValue& rhs); + friend bool operator!=(const int& lhs, const AnyValue& rhs); template AnyValue& operator=(const std::vector& value); @@ -161,10 +183,22 @@ class AnyValue std::unordered_map asMap(const std::string& name) const; std::unordered_map asMap(const std::string& name); - //! For objects of type `vector`, return the item where the given - //! key has the specified value. If value is the empty string, returns the - //! first item in the list. - AnyMap& getMapWhere(const std::string& key, const std::string& value); + //! Treating the value as `vector`, return the item where the given + //! key has the specified value. + /*! + * If value is the empty string, returns the first item in the list. + * + * If the contained type is just `AnyMap` rather than `vector`, it + * will be treated as a vector of length 1. + * + * If the value does not exist but the `create` flag is set to true, a new + * map with that key and value will be created and returned. + */ + AnyMap& getMapWhere(const std::string& key, const std::string& value, bool create=false); + const AnyMap& getMapWhere(const std::string& key, const std::string& value) const; + + //! Returns `true` when getMapWhere() would succeed + bool hasMapWhere(const std::string& key, const std::string& value) const; //! @see AnyMap::applyUnits void applyUnits(const UnitSystem& units); @@ -181,8 +215,9 @@ class AnyValue //! Column where this value occurs in the input file int m_column; - //! Information about the input file used to create this object - shared_ptr m_file; + //! Metadata relevant to an entire AnyMap tree, such as information about + // the input file used to create it + shared_ptr m_metadata; //! Key of this value in a parent `AnyMap` std::string m_key; @@ -194,6 +229,25 @@ class AnyValue //! `boost::demangle` is not available. static std::map s_typenames; + typedef bool (*Comparer)(const boost::any&, const boost::any&); + + //! Equality comparison function used when *lhs* is of type *T* + template + static bool eq_comparer(const boost::any& lhs, const boost::any& rhs); + + //! Helper function for comparing vectors of different (but comparable) + //! types, e.g. `vector` and `vector` + template + static bool vector_eq(const boost::any& lhs, const boost::any& rhs); + + //! Helper function for comparing nested vectors of different (but + //! comparable) types, e.g. `vector>` and + //! `vector>` + template + static bool vector2_eq(const boost::any& lhs, const boost::any& rhs); + + mutable Comparer m_equals; + friend class InputFileError; }; @@ -218,6 +272,13 @@ const std::vector& AnyValue::asVector(size_t nMin, size_t template<> std::vector& AnyValue::asVector(size_t nMin, size_t nMax); +//! Implicit conversion of AnyMap to a vector of length 1, or an empty +//! vector an empty vector +template<> +const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) const; + +template<> +std::vector& AnyValue::asVector(size_t nMin, size_t nMax); //! A map of string keys to values whose type can vary at runtime /*! @@ -317,6 +378,9 @@ class AnyMap //! Erase the value held by `key`. void erase(const std::string& key); + //! Erase all items in the mapping + void clear(); + //! Return a string listing the keys in this AnyMap, e.g. for use in error //! messages std::string keys_str() const; @@ -326,18 +390,16 @@ class AnyMap //! error messages. void setLoc(int line, int column); - //! Set information about the file used to create this AnyMap. Recursively - //! sets the information on any child elements. - void setFile(shared_ptr& file); + //! Set a metadata value that applies to this AnyMap and its children. + //! Mainly for internal use in reading or writing from files. + void setMetadata(const std::string& key, const AnyValue& value); - //! Set the name of the file used to create this AnyMap. Recursively sets - //! the information on any child elements. - void setFileName(const std::string& filename); + //! Get a value from the metadata applicable to the AnyMap tree containing + //! this AnyMap. + const AnyValue& getMetadata(const std::string& key) const; - //! Set the contents of the file used to create this AnyMap. Used in the - //! case where the AnyMap is created from an input string rather than a - //! file. Recursively sets the information on any child elements. - void setFileContents(const std::string& contents); + //! Propagate metadata to any child elements + void propagateMetadata(shared_ptr& file); //! If `key` exists, return it as a `bool`, otherwise return `default_`. bool getBool(const std::string& key, bool default_) const; @@ -385,23 +447,47 @@ class AnyMap vector_fp convertVector(const std::string& key, const std::string& units, size_t nMin=npos, size_t nMax=npos) const; - using const_iterator = std::unordered_map::const_iterator; + //! Defined to allow use with range-based for loops. Iteration automatically + //! skips over keys that start and end with double underscores. + class Iterator { + public: + Iterator(const std::unordered_map::const_iterator& start, + const std::unordered_map::const_iterator& stop); + + const std::pair& operator*() const { + return *m_iter; + } + const std::pair* operator->() const { + return &*m_iter; + } + bool operator!=(const Iterator& right) const { + return m_iter != right.m_iter; + } + Iterator& operator++(); + + private: + std::unordered_map::const_iterator m_iter; + std::unordered_map::const_iterator m_stop; + }; //! Defined to allow use with range-based for loops - const_iterator begin() const { - return m_data.begin(); + Iterator begin() const { + return Iterator(m_data.begin(), m_data.end()); } //! Defined to allow use with range-based for loops - const_iterator end() const { - return m_data.end(); + Iterator end() const { + return Iterator(m_data.end(), m_data.end()); } //! Returns the number of elements in this map - size_t size() { + size_t size() const { return m_data.size(); }; + bool operator==(const AnyMap& other) const; + bool operator!=(const AnyMap& other) const; + //! Return the default units that should be used to convert stored values const UnitSystem& units() const { return m_units; } @@ -433,8 +519,9 @@ class AnyMap //! Starting column for this map in the input file int m_column; - //! Information about the file used to create this map - shared_ptr m_file; + //! Metadata relevant to an entire AnyMap tree, such as information about + // the input file used to create it + shared_ptr m_metadata; //! Cache for previously-parsed input (YAML) files. The key is the full path //! to the file, and the second element of the value is the last-modified @@ -446,8 +533,8 @@ class AnyMap }; // Define begin() and end() to allow use with range-based for loops -AnyMap::const_iterator begin(const AnyValue& v); -AnyMap::const_iterator end(const AnyValue& v); +AnyMap::Iterator begin(const AnyValue& v); +AnyMap::Iterator end(const AnyValue& v); //! Error thrown for problems processing information contained in an AnyMap or //! AnyValue. @@ -468,7 +555,7 @@ class InputFileError : public CanteraError : CanteraError( procedure, formatError(fmt::format(message, args...), - node.m_line, node.m_column, node.m_file)) + node.m_line, node.m_column, node.m_metadata)) { } @@ -481,7 +568,7 @@ class InputFileError : public CanteraError : CanteraError( procedure, formatError(fmt::format(message, args...), - node.m_line, node.m_column, node.m_file)) + node.m_line, node.m_column, node.m_metadata)) { } @@ -491,7 +578,7 @@ class InputFileError : public CanteraError protected: static std::string formatError(const std::string& message, int line, int column, - const shared_ptr& file); + const shared_ptr& metadata); }; } diff --git a/include/cantera/base/AnyMap.inl.h b/include/cantera/base/AnyMap.inl.h index 6982c708ce..fcf5c1abc6 100644 --- a/include/cantera/base/AnyMap.inl.h +++ b/include/cantera/base/AnyMap.inl.h @@ -19,6 +19,7 @@ const T &AnyValue::as() const { if (typeid(T) == typeid(double) && m_value->type() == typeid(long int)) { // Implicit conversion of long int to double *m_value = static_cast(as()); + m_equals = eq_comparer; } return boost::any_cast(*m_value); } catch (boost::bad_any_cast&) { @@ -40,6 +41,7 @@ T &AnyValue::as() { if (typeid(T) == typeid(double) && m_value->type() == typeid(long int)) { // Implicit conversion of long int to double *m_value = static_cast(as()); + m_equals = eq_comparer; } return boost::any_cast(*m_value); } catch (boost::bad_any_cast&) { @@ -63,6 +65,7 @@ bool AnyValue::is() const { template AnyValue &AnyValue::operator=(const std::vector &value) { *m_value = value; + m_equals = eq_comparer>; return *this; } @@ -83,6 +86,7 @@ std::vector &AnyValue::asVector(size_t nMin, size_t nMax) { template AnyValue& AnyValue::operator=(const std::unordered_map items) { *m_value = AnyMap(); + m_equals = eq_comparer; AnyMap& dest = as(); for (const auto& item : items) { dest[item.first] = item.second; @@ -93,6 +97,7 @@ AnyValue& AnyValue::operator=(const std::unordered_map items) { template AnyValue& AnyValue::operator=(const std::map items) { *m_value = AnyMap(); + m_equals = eq_comparer; AnyMap& dest = as(); for (const auto& item : items) { dest[item.first] = item.second; @@ -107,6 +112,7 @@ inline AnyMap& AnyValue::as() { // m[key1][key2] is used. if (m_value->type() == typeid(void)) { *m_value = AnyMap(); + m_equals = eq_comparer; } return boost::any_cast(*m_value); } catch (boost::bad_any_cast&) { @@ -120,7 +126,7 @@ template std::map AnyValue::asMap() const { std::map dest; - for (const auto& item : as().m_data) { + for (const auto& item : as()) { dest[item.first] = item.second.as(); } return dest; @@ -141,5 +147,85 @@ void AnyValue::checkSize(const std::vector& v, size_t nMin, size_t nMax) cons } } +template +bool AnyValue::vector_eq(const boost::any& lhs, const boost::any& rhs) +{ + const auto& lvec = boost::any_cast(lhs); + const auto& rvec = boost::any_cast(rhs); + if (lvec.size() != rvec.size()) { + return false; + } else { + return std::equal(lvec.begin(), lvec.end(), rvec.begin()); + } +} + +template +bool AnyValue::vector2_eq(const boost::any& lhs, const boost::any& rhs) +{ + const auto& lvec = boost::any_cast>(lhs); + const auto& rvec = boost::any_cast>(rhs); + if (lvec.size() != rvec.size()) { + return false; + } else { + for (size_t i = 0; i < lvec.size(); i++) { + if (!std::equal(lvec[i].begin(), lvec[i].end(), rvec[i].begin())) { + return false; + } + } + return true; + } +} + +template +bool AnyValue::eq_comparer(const boost::any& lhs, const boost::any& rhs) +{ + using boost::any_cast; + using std::vector; + typedef vector vd; + typedef vector vi; + typedef vector va; + typedef vector vs; + + auto& ltype = lhs.type(); + auto& rtype = rhs.type(); + AssertThrowMsg(ltype == typeid(T), + "AnyValue::eq_comparer", "Compare function does not match held type"); + + if (ltype == rtype) { + return any_cast(lhs) == any_cast(rhs); + } else if (ltype == typeid(double) && rtype == typeid(long int)) { + return any_cast(lhs) == any_cast(rhs); + } else if (ltype == typeid(long int) && rtype == typeid(double)) { + return any_cast(lhs) == any_cast(rhs); + + } else if (ltype == typeid(vd) && rtype == typeid(vi)) { + return vector_eq(lhs, rhs); + } else if (ltype == typeid(vi) && rtype == typeid(vd)) { + return vector_eq(lhs, rhs); + + } else if (ltype == typeid(va)) { + if (rtype == typeid(vd)) { + return vector_eq(lhs, rhs); + } else if (rtype == typeid(vi)) { + return vector_eq(lhs, rhs); + } else if (rtype == typeid(vs)) { + return vector_eq(lhs, rhs); + } + } else if (rtype == typeid(va)) { + if (ltype == typeid(vd)) { + return vector_eq(lhs, rhs); + } else if (ltype == typeid(vi)) { + return vector_eq(lhs, rhs); + } else if (ltype == typeid(vs)) { + return vector_eq(lhs, rhs); + } + } else if (ltype == typeid(vector) && rtype == typeid(vector)) { + return vector2_eq(lhs, rhs); + } else if (ltype == typeid(vector) && rtype == typeid(vector)) { + return vector2_eq(lhs, rhs); + } + return false; +} + } #endif diff --git a/include/cantera/base/FactoryBase.h b/include/cantera/base/FactoryBase.h index 0431e2cd59..ada0cf84c7 100644 --- a/include/cantera/base/FactoryBase.h +++ b/include/cantera/base/FactoryBase.h @@ -74,20 +74,8 @@ class Factory : public FactoryBase { //! Create an object using the object construction function corresponding to //! "name" and the provided constructor arguments - T* create(std::string name, Args... args) { - try { - return m_creators.at(name)(args...); - } catch (std::out_of_range&) { - if (m_synonyms.find(name) != m_synonyms.end()) { - return m_creators.at(m_synonyms.at(name))(args...); - } else if (m_deprecated_names.find(name) != m_deprecated_names.end()) { - warn_deprecated(name, - fmt::format("Use '{}' instead.", m_deprecated_names.at(name))); - return m_creators.at(m_deprecated_names.at(name))(args...); - } else { - throw CanteraError("Factory::create", "No such type: '{}'", name); - } - } + T* create(const std::string& name, Args... args) { + return m_creators.at(canonicalize(name))(args...); } //! Register a new object construction function @@ -95,7 +83,47 @@ class Factory : public FactoryBase { m_creators[name] = f; } + //! Add an alias for an existing registered type + void addAlias(const std::string& original, const std::string& alias) { + if (!m_creators.count(original)) { + throw CanteraError("Factory::addAlias", + "Name '{}' not registered", original); + } + m_synonyms[alias] = original; + } + + //! Get the canonical name registered for a type + std::string canonicalize(const std::string& name) { + if (m_creators.count(name)) { + return name; + } else if (m_synonyms.count(name)) { + return m_synonyms.at(name); + } else if (m_deprecated_names.count(name)) { + warn_deprecated(name, + fmt::format("Use '{}' instead.", m_deprecated_names.at(name))); + return m_deprecated_names.at(name); + } else { + throw CanteraError("Factory::canonicalize", "No such type: '{}'", name); + } + } + + //! Returns true if `name` is registered with this factory + bool exists(const std::string& name) const { + return m_creators.count(name) || m_synonyms.count(name); + } + protected: + //! Add a deprecated alias for an existing registered type + void addDeprecatedAlias(const std::string& original, + const std::string& alias) { + if (!m_creators.count(original)) { + throw CanteraError("Factory::addDeprecatedAlias", + "Name '{}' not registered", original); + } + m_deprecated_names[alias] = original; + } + +private: std::unordered_map> m_creators; //! Map of synonyms to canonical names diff --git a/include/cantera/tpx/utils.h b/include/cantera/tpx/utils.h index aad9c42c5d..08b170523f 100644 --- a/include/cantera/tpx/utils.h +++ b/include/cantera/tpx/utils.h @@ -17,8 +17,8 @@ Substance* GetSub(int isub); * - methane * - hydrogen * - oxygen - * - hfc134a - * - carbondioxide + * - HFC-134a + * - carbon-dioxide * - heptane */ Substance* newSubstance(const std::string& name); diff --git a/interfaces/cython/cantera/cti2yaml.py b/interfaces/cython/cantera/cti2yaml.py index 4ef33129db..97f3869b91 100644 --- a/interfaces/cython/cantera/cti2yaml.py +++ b/interfaces/cython/cantera/cti2yaml.py @@ -1250,8 +1250,8 @@ class liquid_vapor(phase): 2: 'methane', 3: 'hydrogen', 4: 'oxygen', - 5: 'HFC134a', - 7: 'carbondioxide', + 5: 'HFC-134a', + 7: 'carbon-dioxide', 8: 'heptane' } diff --git a/interfaces/cython/cantera/ctml2yaml.py b/interfaces/cython/cantera/ctml2yaml.py index 10d6409ebc..4eff525587 100644 --- a/interfaces/cython/cantera/ctml2yaml.py +++ b/interfaces/cython/cantera/ctml2yaml.py @@ -366,8 +366,8 @@ class Phase: "2": "methane", "3": "hydrogen", "4": "oxygen", - "5": "HFC134a", - "7": "carbondioxide", + "5": "HFC-134a", + "7": "carbon-dioxide", "8": "heptane", } @@ -1399,13 +1399,14 @@ def debye_huckel( name = spec.attribs["name"] if name not in species_names: continue + debye_huckel = spec.attribs.get("Debye-Huckel", {}) if name in species_ionic_radii: - spec.attribs["ionic-radius"] = species_ionic_radii[name] + debye_huckel["ionic-radius"] = species_ionic_radii[name] if name in is_mods: - if "weak-acid-charge" not in spec.attribs: - spec.attribs["weak-acid-charge"] = is_mods[name] + if "weak-acid-charge" not in debye_huckel: + debye_huckel["weak-acid-charge"] = is_mods[name] else: - if is_mods[name] != spec.attribs["weak-acid-charge"]: + if is_mods[name] != debye_huckel["weak-acid-charge"]: warnings.warn( "The stoichIsMods node was specified at the phase and " "species level for species '{}'. The value specified " @@ -1413,16 +1414,18 @@ def debye_huckel( ) if name in etype_mods: etype = spec.electrolyte_species_type_mapping[etype_mods[name]] - if "electrolyte-species-type" not in spec.attribs: - spec.attribs["electrolyte-species-type"] = etype + if "electrolyte-species-type" not in debye_huckel: + debye_huckel["electrolyte-species-type"] = etype else: - if spec.attribs["electrolyte-species-type"] != etype: + if debye_huckel["electrolyte-species-type"] != etype: warnings.warn( "The electrolyteSpeciesType node was specified at the " "phase and species level for species '{}'. The value " "specified in the species node will be " "used".format(name) ) + if debye_huckel: + spec.attribs["Debye-Huckel"] = debye_huckel return activity_data @@ -1781,13 +1784,16 @@ def __init__(self, species_node: etree.Element): self.process_standard_state_node(species_node) electrolyte = species_node.findtext("electrolyteSpeciesType") + debye_huckel = {} if electrolyte is not None: electrolyte = self.electrolyte_species_type_mapping[electrolyte.strip()] - self.attribs["electrolyte-species-type"] = electrolyte + debye_huckel["electrolyte-species-type"] = electrolyte weak_acid_charge = species_node.find("stoichIsMods") if weak_acid_charge is not None: - self.attribs["weak-acid-charge"] = get_float_or_quantity(weak_acid_charge) + debye_huckel["weak-acid-charge"] = get_float_or_quantity(weak_acid_charge) + if debye_huckel: + self.attribs["Debye-Huckel"] = debye_huckel def hkft(self, species_node: etree.Element) -> Dict[str, "HKFT_THERMO_TYPE"]: """Process a species with HKFT thermo type. diff --git a/interfaces/cython/cantera/liquidvapor.py b/interfaces/cython/cantera/liquidvapor.py index ca3d5705b7..fbb1e40c9a 100644 --- a/interfaces/cython/cantera/liquidvapor.py +++ b/interfaces/cython/cantera/liquidvapor.py @@ -37,12 +37,12 @@ def Oxygen(): def Hfc134a(): """Create a `PureFluid` object using the equation of state for HFC-134a.""" - return PureFluid('liquidvapor.yaml', 'hfc134a') + return PureFluid('liquidvapor.yaml', 'HFC-134a') def CarbonDioxide(): """Create a `PureFluid` object using the equation of state for carbon dioxide.""" - return PureFluid('liquidvapor.yaml', 'carbondioxide') + return PureFluid('liquidvapor.yaml', 'carbon-dioxide') def Heptane(): diff --git a/interfaces/cython/cantera/test/test_purefluid.py b/interfaces/cython/cantera/test/test_purefluid.py index 84844d4b28..6371b24cc4 100644 --- a/interfaces/cython/cantera/test/test_purefluid.py +++ b/interfaces/cython/cantera/test/test_purefluid.py @@ -89,22 +89,31 @@ def test_set_minmax(self): def check_fd_properties(self, T1, P1, T2, P2, tol): # Properties which are computed as finite differences self.water.TP = T1, P1 + h1a = self.water.enthalpy_mass cp1 = self.water.cp_mass cv1 = self.water.cv_mass k1 = self.water.isothermal_compressibility alpha1 = self.water.thermal_expansion_coeff + h1b = self.water.enthalpy_mass self.water.TP = T2, P2 + h2a = self.water.enthalpy_mass cp2 = self.water.cp_mass cv2 = self.water.cv_mass k2 = self.water.isothermal_compressibility alpha2 = self.water.thermal_expansion_coeff + h2b = self.water.enthalpy_mass self.assertNear(cp1, cp2, tol) self.assertNear(cv1, cv2, tol) self.assertNear(k1, k2, tol) self.assertNear(alpha1, alpha2, tol) + # calculating these finite difference properties should not perturbe the + # state of the object + self.assertEqual(h1a, h1b) + self.assertEqual(h2a, h2b) + def test_properties_near_min(self): self.check_fd_properties(self.water.min_temp*(1+1e-5), 101325, self.water.min_temp*(1+1e-4), 101325, 1e-2) diff --git a/interfaces/matlab/toolbox/CarbonDioxide.m b/interfaces/matlab/toolbox/CarbonDioxide.m index 0223ab7395..4bdd62ffb7 100644 --- a/interfaces/matlab/toolbox/CarbonDioxide.m +++ b/interfaces/matlab/toolbox/CarbonDioxide.m @@ -17,4 +17,4 @@ % Instance of class :mat:func:`Solution` % -c = Solution('liquidvapor.yaml', 'carbondioxide'); +c = Solution('liquidvapor.yaml', 'carbon-dioxide'); diff --git a/interfaces/matlab/toolbox/HFC134a.m b/interfaces/matlab/toolbox/HFC134a.m index 1e0adf3e75..3ebb902d30 100644 --- a/interfaces/matlab/toolbox/HFC134a.m +++ b/interfaces/matlab/toolbox/HFC134a.m @@ -18,4 +18,4 @@ % Instance of class :mat:func:`Solution` % -h = Solution('liquidvapor.yaml', 'hfc134a'); +h = Solution('liquidvapor.yaml', 'HFC-134a'); diff --git a/src/base/AnyMap.cpp b/src/base/AnyMap.cpp index 15728cf352..b070038ed3 100644 --- a/src/base/AnyMap.cpp +++ b/src/base/AnyMap.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace ba = boost::algorithm; @@ -136,6 +137,8 @@ Type elementTypes(const YAML::Node& node) return types; } +Cantera::AnyValue Empty; + } // end anonymous namespace namespace YAML { // YAML converters @@ -188,7 +191,14 @@ struct convert { // Scalar nodes are int, doubles, or strings std::string nodestr = node.as(); if (isInt(nodestr)) { - target = intValue(nodestr); + try { + target = node.as(); + } catch (YAML::BadConversion&) { + // This exception is raised if the value doesn't fit in a + // long int, in which case we would rather store it + // (possibly inexactly) as a double. + target = node.as(); + } } else if (isFloat(nodestr)) { target = fpValue(nodestr); } else if (isBool(nodestr)) { @@ -245,13 +255,6 @@ struct convert { namespace Cantera { -class InputFile -{ -public: - std::string name; - std::string contents; -}; - std::map AnyValue::s_typenames = { {typeid(double).name(), "double"}, {typeid(long int).name(), "long int"}, @@ -269,6 +272,7 @@ AnyValue::AnyValue() , m_column(-1) , m_key() , m_value(new boost::any{}) + , m_equals(eq_comparer) {} AnyValue::~AnyValue() = default; @@ -276,18 +280,20 @@ AnyValue::~AnyValue() = default; AnyValue::AnyValue(AnyValue const& other) : m_line(other.m_line) , m_column(other.m_column) - , m_file(other.m_file) + , m_metadata(other.m_metadata) , m_key(other.m_key) , m_value(new boost::any{*other.m_value}) + , m_equals(other.m_equals) { } AnyValue::AnyValue(AnyValue&& other) : m_line(other.m_line) , m_column(other.m_column) - , m_file(std::move(other.m_file)) + , m_metadata(std::move(other.m_metadata)) , m_key(std::move(other.m_key)) , m_value(std::move(other.m_value)) + , m_equals(std::move(other.m_equals)) { } @@ -296,9 +302,10 @@ AnyValue& AnyValue::operator=(AnyValue const& other) { return *this; m_line = other.m_line; m_column = other.m_column; - m_file = other.m_file; + m_metadata = other.m_metadata; m_key = other.m_key; m_value.reset(new boost::any{*other.m_value}); + m_equals = other.m_equals; return *this; } @@ -307,12 +314,23 @@ AnyValue& AnyValue::operator=(AnyValue&& other) { return *this; m_line = other.m_line; m_column = other.m_column; - m_file = std::move(other.m_file); + m_metadata = std::move(other.m_metadata); m_key = std::move(other.m_key); m_value = std::move(other.m_value); + m_equals = std::move(other.m_equals); return *this; } +bool AnyValue::operator==(const AnyValue& other) const +{ + return m_equals(*m_value, *other.m_value); +} + +bool AnyValue::operator!=(const AnyValue& other) const +{ + return !m_equals(*m_value, *other.m_value); +} + AnyValue& AnyValue::operator[](const std::string& key) { return as()[key]; @@ -339,18 +357,27 @@ void AnyValue::setLoc(int line, int column) m_column = column; } -void AnyValue::setFile(shared_ptr& file) +const AnyValue& AnyValue::getMetadata(const std::string& key) const { - m_file = file; + if (m_metadata && m_metadata->hasKey(key)) { + return m_metadata->at(key); + } else { + return Empty; + } +} + +void AnyValue::propagateMetadata(shared_ptr& metadata) +{ + m_metadata = metadata; if (is()) { - as().setFile(m_file); + as().propagateMetadata(m_metadata); } else if (is>()) { for (auto& item : asVector()) { - item.setFile(m_file); + item.propagateMetadata(m_metadata); } } else if (is>()) { for (auto& item : asVector()) { - item.setFile(m_file); + item.propagateMetadata(m_metadata); } } } @@ -363,17 +390,27 @@ bool AnyValue::isScalar() const { return is() || is() || is() || is(); } -AnyValue::AnyValue(const std::string& value) : m_value(new boost::any{value}) {} +// Specializations for "std::string" and "const char*" -AnyValue::AnyValue(const char* value) : m_value(new boost::any{std::string(value)}) {} +AnyValue::AnyValue(const std::string& value) + : m_value(new boost::any{value}) + , m_equals(eq_comparer) +{} + +AnyValue::AnyValue(const char* value) + : m_value(new boost::any{std::string(value)}) + , m_equals(eq_comparer) +{} AnyValue &AnyValue::operator=(const std::string &value) { *m_value = value; + m_equals = eq_comparer; return *this; } AnyValue &AnyValue::operator=(const char *value) { *m_value = std::string(value); + m_equals = eq_comparer; return *this; } @@ -381,10 +418,40 @@ const std::string &AnyValue::asString() const { return as(); } -AnyValue::AnyValue(double value) : m_value(new boost::any{value}) {} +bool AnyValue::operator==(const std::string& other) const +{ + if (m_value->type() == typeid(std::string)) { + return boost::any_cast(*m_value) == other; + } else { + return false; + } +} + +bool AnyValue::operator!=(const std::string& other) const +{ + return !(*this == other); +} + +bool operator==(const std::string& lhs, const AnyValue& rhs) +{ + return rhs == lhs; +} + +bool operator!=(const std::string& lhs, const AnyValue& rhs) +{ + return rhs != lhs; +} + +// Specializations for "double" + +AnyValue::AnyValue(double value) + : m_value(new boost::any{value}) + , m_equals(eq_comparer) +{} AnyValue &AnyValue::operator=(double value) { *m_value = value; + m_equals = eq_comparer; return *this; } @@ -396,10 +463,42 @@ const double& AnyValue::asDouble() const { return as(); } -AnyValue::AnyValue(bool value) : m_value(new boost::any{value}) {} +bool AnyValue::operator==(const double& other) const +{ + if (m_value->type() == typeid(double)) { + return boost::any_cast(*m_value) == other; + } else if (m_value->type() == typeid(long int)) { + return boost::any_cast(*m_value) == other; + } else { + return false; + } +} + +bool AnyValue::operator!=(const double& other) const +{ + return !(*this == other); +} + +bool operator==(const double& lhs, const AnyValue& rhs) +{ + return rhs == lhs; +} + +bool operator!=(const double& lhs, const AnyValue& rhs) +{ + return rhs != lhs; +} + +// Specializations for "bool" + +AnyValue::AnyValue(bool value) + : m_value(new boost::any{value}) + , m_equals(eq_comparer) +{} AnyValue &AnyValue::operator=(bool value) { *m_value = value; + m_equals = eq_comparer; return *this; } @@ -411,15 +510,27 @@ const bool& AnyValue::asBool() const { return as(); } -AnyValue::AnyValue(long int value) : m_value(new boost::any{value}) {} +// Specializations for "long int" and "int" + +AnyValue::AnyValue(long int value) + : m_value(new boost::any{value}) + , m_equals(eq_comparer) +{} + +AnyValue::AnyValue(int value) + : m_value(new boost::any{static_cast(value)}) + , m_equals(eq_comparer) +{} AnyValue &AnyValue::operator=(long int value) { *m_value = value; + m_equals = eq_comparer; return *this; } AnyValue &AnyValue::operator=(int value) { *m_value = static_cast(value); + m_equals = eq_comparer; return *this; } @@ -431,15 +542,68 @@ const long int& AnyValue::asInt() const { return as(); } -AnyValue::AnyValue(const AnyMap& value) : m_value(new boost::any{value}) {} +bool AnyValue::operator==(const long int& other) const +{ + if (m_value->type() == typeid(long int)) { + return boost::any_cast(*m_value) == other; + } else if (m_value->type() == typeid(double)) { + return boost::any_cast(*m_value) == other; + } else { + return false; + } +} + +bool AnyValue::operator!=(const long int& other) const +{ + return !(*this == other); +} + +bool AnyValue::operator==(const int& other) const +{ + return *this == static_cast(other); +} + +bool AnyValue::operator!=(const int& other) const +{ + return *this != static_cast(other); +} + +bool operator==(const long int& lhs, const AnyValue& rhs) +{ + return rhs == lhs; +} + +bool operator!=(const long int& lhs, const AnyValue& rhs) +{ + return rhs != lhs; +} + +bool operator==(const int& lhs, const AnyValue& rhs) +{ + return rhs == lhs; +} + +bool operator!=(const int& lhs, const AnyValue& rhs) +{ + return rhs != lhs; +} + +// Specializations for "AnyMap" + +AnyValue::AnyValue(const AnyMap& value) + : m_value(new boost::any{value}) + , m_equals(eq_comparer) +{} AnyValue& AnyValue::operator=(const AnyMap& value) { *m_value = value; + m_equals = eq_comparer; return *this; } AnyValue& AnyValue::operator=(AnyMap&& value) { *m_value = std::move(value); + m_equals = eq_comparer; return *this; } @@ -472,18 +636,100 @@ std::unordered_map AnyValue::asMap(const std::string& name return mapped; } -AnyMap& AnyValue::getMapWhere(const std::string& key, const std::string& value) +const AnyMap& AnyValue::getMapWhere(const std::string& key, const std::string& value) const { - if (value == "") { - return asVector().at(0); + if (is>()) { + if (value == "") { + return asVector().at(0); + } + for (auto& item : asVector()) { + if (item.hasKey(key) && item[key] == value) { + return item; + } + } + throw InputFileError("AnyValue::getMapWhere", *this, + "List does not contain a map where '{}' = '{}'", key, value); + } else if (is()) { + if (value == "" || (hasKey(key) && as()[key] == value)) { + return as(); + } else { + throw InputFileError("AnyValue::getMapWhere", *this, + "Map does not contain a key where '{}' = '{}'", key, value); + } + } else { + throw InputFileError("AnyValue::getMapWhere", *this, + "Element is not a mapping or list of mappings"); } - for (auto& item : asVector()) { - if (item.hasKey(key) && item[key].asString() == value) { - return item; +} + +AnyMap& AnyValue::getMapWhere(const std::string& key, const std::string& value, + bool create) +{ + if (is>()) { + if (value == "") { + return asVector().at(0); + } + for (auto& item : asVector()) { + if (item.hasKey(key) && item[key] == value) { + return item; + } + } + if (create) { + // If the map wasn't found, insert it + auto& vec = asVector(); + AnyMap child; + child[key] = value; + vec.push_back(std::move(child)); + return vec.back(); + } else { + throw InputFileError("AnyValue::getMapWhere", *this, + "List does not contain a map where '{}' = '{}'", key, value); + } + } else if (is()) { + if (value == "" || (hasKey(key) && as()[key] == value)) { + return as(); + } else if (create) { + AnyMap newChild; + newChild[key] = value; + std::vector nodes{std::move(as()), std::move(newChild)}; + operator=(std::move(nodes)); + return asVector().back(); + } else { + throw InputFileError("AnyValue::getMapWhere", *this, + "Map does not contain a key where '{}' = '{}'", key, value); + } + } else if (is() && create) { + AnyMap child; + child[key] = value; + operator=(std::move(child)); + return as(); + } else { + throw InputFileError("AnyValue::getMapWhere", *this, + "Element is not a mapping or list of mappings"); + } +} + +bool AnyValue::hasMapWhere(const std::string& key, const std::string& value) const +{ + if (is>()) { + if (value == "") { + return true; } + for (auto& item : asVector()) { + if (item.hasKey(key) && item[key] == value) { + return true; + } + } + return false; + } else if (is()) { + if (value == "" || (hasKey(key) && as()[key] == value)) { + return true; + } else { + return false; + } + } else { + return false; } - throw InputFileError("AnyValue::getMapWhere", *this, - "List does not contain a map where '{}' = '{}'", key, value); } void AnyValue::applyUnits(const UnitSystem& units) @@ -564,6 +810,7 @@ const std::vector& AnyValue::asVector(size_t nMin, size_t nM // and an exception will be thrown. } const auto& vv = as>(); + m_equals = eq_comparer>; checkSize(vv, nMin, nMax); return vv; } @@ -588,6 +835,7 @@ const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) *m_value = v; } const auto& vv = as>(); + m_equals = eq_comparer>; checkSize(vv, nMin, nMax); return vv; } @@ -603,6 +851,7 @@ std::vector& AnyValue::asVector(size_t nMin, size_t nMax) *m_value = v; } auto& vv = as>(); + m_equals = eq_comparer>; checkSize(vv, nMin, nMax); return vv; } @@ -621,6 +870,7 @@ const std::vector& AnyValue::asVector(size_t nMin, size_t *m_value = v; } const auto& vv = as>(); + m_equals = eq_comparer>; checkSize(vv, nMin, nMax); return vv; } @@ -639,10 +889,40 @@ std::vector& AnyValue::asVector(size_t nMin, size_t nMax) *m_value = v; } auto& vv = as>(); + m_equals = eq_comparer>; checkSize(vv, nMin, nMax); return vv; } +template<> +const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) const +{ + if (is()) { + std::vector v; + v.push_back(std::move(as())); + *m_value = std::move(v); + } else if (is>() && asVector().empty()) { + *m_value = std::vector(); + } + const auto& vv = as>(); + checkSize(vv, nMin, nMax); + return vv; +} + +template<> +std::vector& AnyValue::asVector(size_t nMin, size_t nMax) +{ + if (is()) { + std::vector v; + v.push_back(std::move(as())); + *m_value = std::move(v); + } else if (is>() && asVector().empty()) { + *m_value = std::vector(); + } + auto& vv = as>(); + checkSize(vv, nMin, nMax); + return vv; +} // Methods of class AnyMap @@ -655,11 +935,11 @@ AnyValue& AnyMap::operator[](const std::string& key) // G++ 4.7 is dropped. AnyValue& value = m_data.insert({key, AnyValue()}).first->second; value.setKey(key); - if (m_file) { + if (m_metadata) { // Approximate location, useful mainly if this insertion is going to // immediately result in an error that needs to be reported. value.setLoc(m_line, m_column); - value.setFile(m_file); + value.propagateMetadata(m_metadata); } return value; } else { @@ -698,6 +978,11 @@ void AnyMap::erase(const std::string& key) m_data.erase(key); } +void AnyMap::clear() +{ + m_data.clear(); +} + std::string AnyMap::keys_str() const { fmt::memory_buffer b; @@ -719,26 +1004,33 @@ void AnyMap::setLoc(int line, int column) m_column = column; } -void AnyMap::setFile(shared_ptr& file) +const AnyValue& AnyMap::getMetadata(const std::string& key) const { - m_file = file; - for (auto& item : m_data) { - item.second.setFile(m_file); + if (m_metadata && m_metadata->hasKey(key)) { + return m_metadata->at(key); + } else { + return Empty; } } -void AnyMap::setFileName(const std::string& filename) +void AnyMap::propagateMetadata(shared_ptr& metadata) { - auto info = make_shared(); - info->name = filename; - setFile(info); + m_metadata = metadata; + for (auto& item : m_data) { + item.second.propagateMetadata(m_metadata); + } } -void AnyMap::setFileContents(const std::string& contents) +void AnyMap::setMetadata(const std::string& key, const AnyValue& value) { - auto info = make_shared(); - info->contents = contents; - setFile(info); + if (m_metadata) { + // Fork the metadata tree at this point to avoid affecting parent nodes + m_metadata = make_shared(*m_metadata); + } else { + m_metadata = make_shared(); + } + (*m_metadata)[key] = value; + propagateMetadata(m_metadata); } bool AnyMap::getBool(const std::string& key, bool default_) const @@ -788,6 +1080,53 @@ vector_fp AnyMap::convertVector(const std::string& key, const std::string& dest, return units().convert(at(key).asVector(nMin, nMax), dest); } +AnyMap::Iterator::Iterator( + const std::unordered_map::const_iterator& start, + const std::unordered_map::const_iterator& stop) +{ + m_iter = start; + m_stop = stop; + while (m_iter != m_stop + && ba::starts_with(m_iter->first, "__") + && ba::ends_with(m_iter->first, "__")) { + ++m_iter; + } +} + +AnyMap::Iterator& AnyMap::Iterator::operator++() +{ + ++m_iter; + while (m_iter != m_stop + && ba::starts_with(m_iter->first, "__") + && ba::ends_with(m_iter->first, "__")) { + ++m_iter; + } + return *this; +} + +bool AnyMap::operator==(const AnyMap& other) const +{ + // First, make sure that 'other' has all of the non-hidden keys that are in + // this map + for (auto& item : *this) { + if (!other.hasKey(item.first)) { + return false; + } + } + // Then check for equality, using the non-hidden keys from 'other' + for (auto & item : other) { + if (!hasKey(item.first) || item.second != at(item.first)) { + return false; + } + } + return true; +} + +bool AnyMap::operator!=(const AnyMap& other) const +{ + return m_data != other.m_data; +} + void AnyMap::applyUnits(const UnitSystem& units) { m_units = units; @@ -808,10 +1147,10 @@ AnyMap AnyMap::fromYamlString(const std::string& yaml) { } catch (YAML::Exception& err) { AnyMap fake; fake.setLoc(err.mark.line, err.mark.column); - fake.setFileContents(yaml); + fake.setMetadata("file-contents", AnyValue(yaml)); throw InputFileError("AnyMap::fromYamlString", fake, err.msg); } - amap.setFileContents(yaml); + amap.setMetadata("file-contents", AnyValue(yaml)); amap.applyUnits(UnitSystem()); return amap; } @@ -853,13 +1192,13 @@ AnyMap AnyMap::fromYamlFile(const std::string& name, try { YAML::Node node = YAML::LoadFile(fullName); cache_item.first = node.as(); - cache_item.first.setFileName(fullName); + cache_item.first.setMetadata("filename", AnyValue(fullName)); cache_item.first.applyUnits(UnitSystem()); } catch (YAML::Exception& err) { s_cache.erase(fullName); AnyMap fake; fake.setLoc(err.mark.line, err.mark.column); - fake.setFileName(fullName); + fake.setMetadata("filename", AnyValue(fullName)); throw InputFileError("AnyMap::fromYamlFile", fake, err.msg); } catch (CanteraError&) { s_cache.erase(fullName); @@ -875,40 +1214,41 @@ AnyMap AnyMap::fromYamlFile(const std::string& name, return cache_item.first; } -AnyMap::const_iterator begin(const AnyValue& v) { +AnyMap::Iterator begin(const AnyValue& v) { return v.as().begin(); } -AnyMap::const_iterator end(const AnyValue& v) { +AnyMap::Iterator end(const AnyValue& v) { return v.as().end(); } std::string InputFileError::formatError(const std::string& message, int lineno, int column, - const shared_ptr& file) + const shared_ptr& metadata) { - if (!file) { + if (!metadata) { return message; } + std::string filename = metadata->getString("filename", ""); fmt::memory_buffer b; format_to(b, "Error on line {} of", lineno+1); - if (file->name.empty()) { + if (filename.empty()) { format_to(b, " input string:\n"); } else { - format_to(b, " {}:\n", file->name); + format_to(b, " {}:\n", filename); } format_to(b, "{}\n", message); format_to(b, "| Line |\n"); - if (file->contents.empty()) { - std::ifstream infile(findInputFile(file->name)); + if (!metadata->hasKey("file-contents")) { + std::ifstream infile(findInputFile(filename)); std::stringstream buffer; buffer << infile.rdbuf(); - file->contents = buffer.str(); + (*metadata)["file-contents"] = buffer.str(); } std::string line; int i = 0; - std::stringstream contents(file->contents); + std::stringstream contents((*metadata)["file-contents"].asString()); while (std::getline(contents, line)) { if (lineno == i) { format_to(b, "> {: 5d} > {}\n", i+1, line); diff --git a/src/kinetics/FalloffFactory.cpp b/src/kinetics/FalloffFactory.cpp index 53003755c9..b2ec781226 100644 --- a/src/kinetics/FalloffFactory.cpp +++ b/src/kinetics/FalloffFactory.cpp @@ -17,7 +17,7 @@ std::mutex FalloffFactory::falloff_mutex; FalloffFactory::FalloffFactory() { reg("Lindemann", []() { return new Falloff(); }); - m_synonyms["Simple"] = "Lindemann"; + addAlias("Lindemann", "Simple"); reg("Troe", []() { return new Troe(); }); reg("SRI", []() { return new SRI(); }); } diff --git a/src/kinetics/KineticsFactory.cpp b/src/kinetics/KineticsFactory.cpp index 180f2e792d..b250d15044 100644 --- a/src/kinetics/KineticsFactory.cpp +++ b/src/kinetics/KineticsFactory.cpp @@ -40,10 +40,10 @@ Kinetics* KineticsFactory::newKinetics(XML_Node& phaseData, KineticsFactory::KineticsFactory() { reg("none", []() { return new Kinetics(); }); - reg("gaskinetics", []() { return new GasKinetics(); }); - m_synonyms["gas"] = "gaskinetics"; - reg("interface", []() { return new InterfaceKinetics(); }); - m_synonyms["surface"] = "interface"; + reg("gas", []() { return new GasKinetics(); }); + addAlias("gas", "gaskinetics"); + reg("surface", []() { return new InterfaceKinetics(); }); + addAlias("surface", "interface"); reg("edge", []() { return new EdgeKinetics(); }); } diff --git a/src/kinetics/Reaction.cpp b/src/kinetics/Reaction.cpp index d7bade039d..733adb800e 100644 --- a/src/kinetics/Reaction.cpp +++ b/src/kinetics/Reaction.cpp @@ -656,7 +656,7 @@ void setupFalloffReaction(FalloffReaction& R, const AnyMap& node, R.third_body.efficiencies[third_body.substr(2, third_body.size() - 3)] = 1.0; } - if (node["type"].asString() == "falloff") { + if (node["type"] == "falloff") { R.low_rate = readArrhenius(R, node["low-P-rate-constant"], kin, node.units(), 1); R.high_rate = readArrhenius(R, node["high-P-rate-constant"], kin, @@ -1057,7 +1057,7 @@ unique_ptr newReaction(const AnyMap& node, const Kinetics& kin) type = node["type"].asString(); } - if (kin.thermo().nDim() < 3) { + if (kin.thermo(kin.reactionPhaseIndex()).nDim() < 3) { // See if this is an electrochemical reaction Reaction testReaction(0); parseReactionEquation(testReaction, node["equation"], kin); diff --git a/src/thermo/DebyeHuckel.cpp b/src/thermo/DebyeHuckel.cpp index c2bf2fb3ce..eff489ec43 100644 --- a/src/thermo/DebyeHuckel.cpp +++ b/src/thermo/DebyeHuckel.cpp @@ -577,8 +577,7 @@ void DebyeHuckel::initThermo() auto& node = m_input["activity-data"].as(); setDebyeHuckelModel(node["model"].asString()); if (node.hasKey("A_Debye")) { - if (node["A_Debye"].is() - && node["A_Debye"].asString() == "variable") { + if (node["A_Debye"] == "variable") { setA_Debye(-1); } else { setA_Debye(node.convert("A_Debye", "kg^0.5/gmol^0.5")); @@ -748,7 +747,7 @@ bool DebyeHuckel::addSpecies(shared_ptr spec) m_tmpV.push_back(0.0); // NAN will be replaced with default value - m_Aionic.push_back(spec->input.convert("ionic-radius", "m", NAN)); + double Aionic = NAN; // Guess electrolyte species type based on charge properties int est = cEST_nonpolarNeutral; @@ -756,22 +755,28 @@ bool DebyeHuckel::addSpecies(shared_ptr spec) if (fabs(spec->charge) > 0.0001) { est = cEST_chargedSpecies; } - if (spec->input.hasKey("weak-acid-charge")) { - stoichCharge = spec->input["weak-acid-charge"].asDouble(); - if (fabs(stoichCharge - spec->charge) > 0.0001) { - est = cEST_weakAcidAssociated; + + if (spec->input.hasKey("Debye-Huckel")) { + auto& dhNode = spec->input["Debye-Huckel"].as(); + Aionic = dhNode.convert("ionic-radius", "m", NAN); + if (dhNode.hasKey("weak-acid-charge")) { + stoichCharge = dhNode["weak-acid-charge"].asDouble(); + if (fabs(stoichCharge - spec->charge) > 0.0001) { + est = cEST_weakAcidAssociated; + } + } + // Apply override of the electrolyte species type + if (dhNode.hasKey("electrolyte-species-type")) { + est = interp_est(dhNode["electrolyte-species-type"].asString()); } } - m_speciesCharge_Stoich.push_back(stoichCharge); if (m_electrolyteSpeciesType.size() == 0) { est = cEST_solvent; // species 0 is the solvent } - // Apply override of the electrolyte species type - if (spec->input.hasKey("electrolyte-species-type")) { - est = interp_est(spec->input["electrolyte-species-type"].asString()); - } + m_Aionic.push_back(Aionic); + m_speciesCharge_Stoich.push_back(stoichCharge); m_electrolyteSpeciesType.push_back(est); } return added; diff --git a/src/thermo/HMWSoln.cpp b/src/thermo/HMWSoln.cpp index df700c3a12..a7fb34359e 100644 --- a/src/thermo/HMWSoln.cpp +++ b/src/thermo/HMWSoln.cpp @@ -695,8 +695,7 @@ void HMWSoln::initThermo() nCoeffs = 5; } if (actData.hasKey("A_Debye")) { - if (actData["A_Debye"].is() - && actData["A_Debye"].asString() == "variable") { + if (actData["A_Debye"] == "variable") { setA_Debye(-1); } else { setA_Debye(actData.convert("A_Debye", "kg^0.5/gmol^0.5")); diff --git a/src/thermo/IdealSolidSolnPhase.cpp b/src/thermo/IdealSolidSolnPhase.cpp index f016313c96..befac7571a 100644 --- a/src/thermo/IdealSolidSolnPhase.cpp +++ b/src/thermo/IdealSolidSolnPhase.cpp @@ -376,12 +376,7 @@ bool IdealSolidSolnPhase::addSpecies(shared_ptr spec) m_pe.push_back(0.0);; m_pp.push_back(0.0); if (spec->input.hasKey("equation-of-state")) { - auto& eos = spec->input["equation-of-state"].as(); - if (eos.getString("model", "") != "constant-volume") { - throw CanteraError("IdealSolidSolnPhase::addSpecies", - "ideal-condensed model requires constant-volume " - "species model for species '{}'", spec->name); - } + auto& eos = spec->input["equation-of-state"].getMapWhere("model", "constant-volume"); double mv; if (eos.hasKey("density")) { mv = molecularWeight(m_kk-1) / eos.convert("density", "kg/m^3"); diff --git a/src/thermo/IonsFromNeutralVPSSTP.cpp b/src/thermo/IonsFromNeutralVPSSTP.cpp index b72a9b580f..037403fb1b 100644 --- a/src/thermo/IonsFromNeutralVPSSTP.cpp +++ b/src/thermo/IonsFromNeutralVPSSTP.cpp @@ -485,16 +485,23 @@ void IonsFromNeutralVPSSTP::initThermo() AnyMap infile; if (slash) { string fileName(neutralName.begin(), slash.begin()); - string node(slash.end(), neutralName.end()); + neutralName = string(slash.end(), neutralName.end()); infile = AnyMap::fromYamlFile(fileName, m_input.getString("__file__", "")); - AnyMap& phaseNode = infile["phases"].getMapWhere("name", node); - setNeutralMoleculePhase(newPhase(phaseNode, infile)); - } else { + } else if (m_input.hasKey("__file__")) { infile = AnyMap::fromYamlFile(m_input["__file__"].asString()); - AnyMap& phaseNode = infile["phases"].getMapWhere("name", neutralName); - setNeutralMoleculePhase(newPhase(phaseNode, infile)); + } else { + auto& text = m_input.getMetadata("file-contents"); + if (text.is()) { + infile = AnyMap::fromYamlString(text.asString()); + } else { + throw InputFileError("IonsFromNeutralVPSSTP::initThermo", + m_input["neutral-phase"], + "Unable to locate phase definition for neutral phase"); + } } + AnyMap& phaseNode = infile["phases"].getMapWhere("name", neutralName); + setNeutralMoleculePhase(newPhase(phaseNode, infile)); } if (!neutralMoleculePhase_) { @@ -629,7 +636,8 @@ bool IonsFromNeutralVPSSTP::addSpecies(shared_ptr spec) } if (spec->input.hasKey("equation-of-state")) { - auto& ss = spec->input["equation-of-state"].as(); + auto& ss = spec->input["equation-of-state"].getMapWhere( + "model", "ions-from-neutral-molecule"); if (ss.getBool("special-species", false)) { indexSpecialSpecies_ = m_kk - 1; } diff --git a/src/thermo/LatticePhase.cpp b/src/thermo/LatticePhase.cpp index b89e212c31..936c796dbf 100644 --- a/src/thermo/LatticePhase.cpp +++ b/src/thermo/LatticePhase.cpp @@ -244,12 +244,8 @@ bool LatticePhase::addSpecies(shared_ptr spec) m_s0_R.push_back(0.0); double mv = 1.0 / m_site_density; if (spec->input.hasKey("equation-of-state")) { - auto& eos = spec->input["equation-of-state"].as(); - if (eos.getString("model", "") != "constant-volume") { - throw CanteraError("LatticePhase::addSpecies", - "lattice model requires constant-volume species model " - "for species '{}'", spec->name); - } + auto& eos = spec->input["equation-of-state"].getMapWhere( + "model", "constant-volume"); if (eos.hasKey("density")) { mv = molecularWeight(m_kk-1) / eos.convert("density", "kg/m^3"); } else if (eos.hasKey("molar-density")) { @@ -273,7 +269,8 @@ void LatticePhase::setSiteDensity(double sitedens) if (species(k)->extra.hasKey("molar_volume")) { continue; } else if (species(k)->input.hasKey("equation-of-state")) { - auto& eos = species(k)->input["equation-of-state"]; + auto& eos = species(k)->input["equation-of-state"].getMapWhere( + "model", "constant-volume"); if (eos.hasKey("molar-volume") || eos.hasKey("density") || eos.hasKey("molar-density")) { continue; diff --git a/src/thermo/LatticeSolidPhase.cpp b/src/thermo/LatticeSolidPhase.cpp index 9f76d25973..5fafb82722 100644 --- a/src/thermo/LatticeSolidPhase.cpp +++ b/src/thermo/LatticeSolidPhase.cpp @@ -293,8 +293,20 @@ void LatticeSolidPhase::getGibbs_ref(doublereal* g) const void LatticeSolidPhase::initThermo() { - if (m_input.hasKey("composition") && m_input.hasKey("__file__")) { - AnyMap infile = AnyMap::fromYamlFile(m_input["__file__"].asString()); + if (m_input.hasKey("composition")) { + AnyMap infile; + if (m_input.hasKey("__file__")) { + infile = AnyMap::fromYamlFile(m_input["__file__"].asString()); + } else { + auto& text = m_input.getMetadata("file-contents"); + if (text.is()) { + infile = AnyMap::fromYamlString(text.asString()); + } else { + throw InputFileError("LatticeSolidPhase::initThermo", + m_input["composition"], + "Unable to locate phase definitions for component phases"); + } + } compositionMap composition = m_input["composition"].asMap(); for (auto& item : composition) { AnyMap& node = infile["phases"].getMapWhere("name", item.first); diff --git a/src/thermo/PDSSFactory.cpp b/src/thermo/PDSSFactory.cpp index e370eb92f6..328f13ea5a 100644 --- a/src/thermo/PDSSFactory.cpp +++ b/src/thermo/PDSSFactory.cpp @@ -20,19 +20,20 @@ std::mutex PDSSFactory::thermo_mutex; PDSSFactory::PDSSFactory() { reg("ideal-gas", []() { return new PDSS_IdealGas(); }); - reg("constant-incompressible", []() { return new PDSS_ConstVol(); }); - m_synonyms["constant_incompressible"] = "constant-incompressible"; - m_synonyms["constant-volume"] = "constant-incompressible"; - reg("water", []() { return new PDSS_Water(); }); - m_synonyms["waterPDSS"] = m_synonyms["waterIAPWS"] = "water"; - m_synonyms["liquid-water-IAPWS95"] = "water"; - reg("ions-from-neutral", []() { return new PDSS_IonsFromNeutral(); }); - m_synonyms["IonFromNeutral"] = "ions-from-neutral"; - m_synonyms["ions-from-neutral-molecule"] = "ions-from-neutral"; - reg("temperature_polynomial", []() { return new PDSS_SSVol(); }); - m_synonyms["molar-volume-temperature-polynomial"] = "temperature_polynomial"; - m_synonyms["density_temperature_polynomial"] = "temperature_polynomial"; - m_synonyms["density-temperature-polynomial"] = "temperature_polynomial"; + reg("constant-volume", []() { return new PDSS_ConstVol(); }); + addAlias("constant-volume", "constant_incompressible"); + addAlias("constant-volume", "constant-incompressible"); + reg("liquid-water-IAPWS95", []() { return new PDSS_Water(); }); + addAlias("liquid-water-IAPWS95", "waterPDSS"); + addAlias("liquid-water-IAPWS95", "waterIAPWS"); + addAlias("liquid-water-IAPWS95", "water"); + reg("ions-from-neutral-molecule", []() { return new PDSS_IonsFromNeutral(); }); + addAlias("ions-from-neutral-molecule", "IonFromNeutral"); + addAlias("ions-from-neutral-molecule", "ions-from-neutral"); + reg("molar-volume-temperature-polynomial", []() { return new PDSS_SSVol(); }); + addAlias("molar-volume-temperature-polynomial", "temperature_polynomial"); + reg("density-temperature-polynomial", []() { return new PDSS_SSVol(); }); + addAlias("density-temperature-polynomial", "density_temperature_polynomial"); reg("HKFT", []() { return new PDSS_HKFT(); }); } diff --git a/src/thermo/RedlichKwongMFTP.cpp b/src/thermo/RedlichKwongMFTP.cpp index 02b5c5c855..58ad30f6fe 100644 --- a/src/thermo/RedlichKwongMFTP.cpp +++ b/src/thermo/RedlichKwongMFTP.cpp @@ -645,12 +645,8 @@ void RedlichKwongMFTP::initThermo() // Read a and b coefficients from species 'input' information (i.e. as // specified in a YAML input file) if (item.second->input.hasKey("equation-of-state")) { - auto eos = item.second->input["equation-of-state"].as(); - if (eos.getString("model", "") != "Redlich-Kwong") { - throw InputFileError("RedlichKwongMFTP::initThermo", eos, - "Expected species equation of state to be 'Redlich-Kwong', " - "but got '{}' instead", eos.getString("model", "")); - } + auto eos = item.second->input["equation-of-state"].getMapWhere( + "model", "Redlich-Kwong"); double a0 = 0, a1 = 0; if (eos["a"].isScalar()) { a0 = eos.convert("a", "Pa*m^6/kmol^2*K^0.5"); diff --git a/src/thermo/Species.cpp b/src/thermo/Species.cpp index 4cc96cdf37..2c692fff75 100644 --- a/src/thermo/Species.cpp +++ b/src/thermo/Species.cpp @@ -56,13 +56,15 @@ shared_ptr newSpecies(const XML_Node& species_node) s->transport->validate(*s); } - // Extra data used for some electrolyte species + // Extra data used for electrolyte species in Debye-Huckel model if (species_node.hasChild("stoichIsMods")) { - s->input["weak-acid-charge"] = getFloat(species_node, "stoichIsMods"); + s->input["Debye-Huckel"]["weak-acid-charge"] = + getFloat(species_node, "stoichIsMods"); } if (species_node.hasChild("electrolyteSpeciesType")) { - s->input["electrolyte-species-type"] = species_node.child("electrolyteSpeciesType").value(); + s->input["Debye-Huckel"]["electrolyte-species-type"] = + species_node.child("electrolyteSpeciesType").value(); } // Extra data optionally used by LatticePhase @@ -75,7 +77,9 @@ shared_ptr newSpecies(const XML_Node& species_node) const XML_Node* thermo = species_node.findByName("thermo"); if (thermo && thermo->attrib("model") == "IonFromNeutral") { if (thermo->hasChild("specialSpecies")) { - s->input["equation-of-state"]["special-species"] = true; + auto& eos = s->input["equation-of-state"].getMapWhere( + "model", "ions-from-neutral-molecule", true); + eos["special-species"] = true; } } diff --git a/src/thermo/StoichSubstance.cpp b/src/thermo/StoichSubstance.cpp index 59962523f9..e6892a510a 100644 --- a/src/thermo/StoichSubstance.cpp +++ b/src/thermo/StoichSubstance.cpp @@ -128,12 +128,8 @@ void StoichSubstance::initThermo() } if (species(0)->input.hasKey("equation-of-state")) { - auto& eos = species(0)->input["equation-of-state"].as(); - if (eos.getString("model", "") != "constant-volume") { - throw InputFileError("StoichSubstance::initThermo", eos, - "fixed-stoichiometry model requires constant-volume species " - "model for species '{}'", speciesName(0)); - } + auto& eos = species(0)->input["equation-of-state"].getMapWhere( + "model", "constant-volume"); if (eos.hasKey("density")) { assignDensity(eos.convert("density", "kg/m^3")); } else if (eos.hasKey("molar-density")) { diff --git a/src/thermo/ThermoFactory.cpp b/src/thermo/ThermoFactory.cpp index 27818f15fb..a062d55219 100644 --- a/src/thermo/ThermoFactory.cpp +++ b/src/thermo/ThermoFactory.cpp @@ -49,51 +49,51 @@ std::mutex ThermoFactory::thermo_mutex; ThermoFactory::ThermoFactory() { - reg("IdealGas", []() { return new IdealGasPhase(); }); - m_synonyms["ideal-gas"] = "IdealGas"; - reg("Incompressible", []() { return new ConstDensityThermo(); }); - m_synonyms["constant-density"] = "Incompressible"; - reg("Surface", []() { return new SurfPhase(); }); - m_synonyms["ideal-surface"] = "Surface"; - reg("Edge", []() { return new EdgePhase(); }); - m_synonyms["edge"] = "Edge"; - reg("Metal", []() { return new MetalPhase(); }); - m_synonyms["electron-cloud"] = "Metal"; - reg("StoichSubstance", []() { return new StoichSubstance(); }); - m_synonyms["fixed-stoichiometry"] = "StoichSubstance"; - reg("PureFluid", []() { return new PureFluidPhase(); }); - m_synonyms["pure-fluid"] = "PureFluid"; - reg("LatticeSolid", []() { return new LatticeSolidPhase(); }); - m_synonyms["compound-lattice"] = "LatticeSolid"; - reg("Lattice", []() { return new LatticePhase(); }); - m_synonyms["lattice"] = "Lattice"; - reg("HMW", []() { return new HMWSoln(); }); - m_synonyms["HMW-electrolyte"] = "HMW"; - reg("IdealSolidSolution", []() { return new IdealSolidSolnPhase(); }); - m_synonyms["ideal-condensed"] = "IdealSolidSolution"; - reg("DebyeHuckel", []() { return new DebyeHuckel(); }); - m_synonyms["Debye-Huckel"] = "DebyeHuckel"; - reg("IdealMolalSolution", []() { return new IdealMolalSoln(); }); - m_synonyms["ideal-molal-solution"] = "IdealMolalSolution"; - reg("IdealGasVPSS", []() { return new IdealSolnGasVPSS(); }); - m_synonyms["IdealSolnVPSS"] = "IdealGasVPSS"; - m_synonyms["ideal-solution-VPSS"] = "IdealGasVPSS"; - m_synonyms["ideal-gas-VPSS"] = "IdealGasVPSS"; + reg("ideal-gas", []() { return new IdealGasPhase(); }); + addAlias("ideal-gas", "IdealGas"); + reg("constant-density", []() { return new ConstDensityThermo(); }); + addAlias("constant-density", "Incompressible"); + reg("ideal-surface", []() { return new SurfPhase(); }); + addAlias("ideal-surface", "Surface"); + reg("edge", []() { return new EdgePhase(); }); + addAlias("edge", "Edge"); + reg("electron-cloud", []() { return new MetalPhase(); }); + addAlias("electron-cloud", "Metal"); + reg("fixed-stoichiometry", []() { return new StoichSubstance(); }); + addAlias("fixed-stoichiometry", "StoichSubstance"); + reg("pure-fluid", []() { return new PureFluidPhase(); }); + addAlias("pure-fluid", "PureFluid"); + reg("compound-lattice", []() { return new LatticeSolidPhase(); }); + addAlias("compound-lattice", "LatticeSolid"); + reg("lattice", []() { return new LatticePhase(); }); + addAlias("lattice", "Lattice"); + reg("HMW-electrolyte", []() { return new HMWSoln(); }); + addAlias("HMW-electrolyte", "HMW"); + reg("ideal-condensed", []() { return new IdealSolidSolnPhase(); }); + addAlias("ideal-condensed", "IdealSolidSolution"); + reg("Debye-Huckel", []() { return new DebyeHuckel(); }); + addAlias("Debye-Huckel", "DebyeHuckel"); + reg("ideal-molal-solution", []() { return new IdealMolalSoln(); }); + addAlias("ideal-molal-solution", "IdealMolalSolution"); + reg("ideal-solution-VPSS", []() { return new IdealSolnGasVPSS(); }); + reg("ideal-gas-VPSS", []() { return new IdealSolnGasVPSS(); }); + addAlias("ideal-solution-VPSS", "IdealSolnVPSS"); + addAlias("ideal-gas-VPSS", "IdealGasVPSS"); reg("Margules", []() { return new MargulesVPSSTP(); }); - reg("IonsFromNeutralMolecule", []() { return new IonsFromNeutralVPSSTP(); }); - m_synonyms["ions-from-neutral-molecule"] = "IonsFromNeutralMolecule"; - reg("FixedChemPot", []() { return new FixedChemPotSSTP(); }); - m_synonyms["fixed-chemical-potential"] = "FixedChemPot"; + reg("ions-from-neutral-molecule", []() { return new IonsFromNeutralVPSSTP(); }); + addAlias("ions-from-neutral-molecule", "IonsFromNeutralMolecule"); + reg("fixed-chemical-potential", []() { return new FixedChemPotSSTP(); }); + addAlias("fixed-chemical-potential", "FixedChemPot"); reg("Redlich-Kister", []() { return new RedlichKisterVPSSTP(); }); - reg("RedlichKwong", []() { return new RedlichKwongMFTP(); }); - m_synonyms["RedlichKwongMFTP"] = "RedlichKwong"; - m_synonyms["Redlich-Kwong"] = "RedlichKwong"; - reg("MaskellSolidSolnPhase", []() { return new MaskellSolidSolnPhase(); }); - m_synonyms["Maskell-solid-solution"] = "MaskellSolidSolnPhase"; - reg("PureLiquidWater", []() { return new WaterSSTP(); }); - m_synonyms["liquid-water-IAPWS95"] = "PureLiquidWater"; - reg("BinarySolutionTabulatedThermo", []() { return new BinarySolutionTabulatedThermo(); }); - m_synonyms["binary-solution-tabulated"] = "BinarySolutionTabulatedThermo"; + reg("Redlich-Kwong", []() { return new RedlichKwongMFTP(); }); + addAlias("Redlich-Kwong", "RedlichKwongMFTP"); + addAlias("Redlich-Kwong", "RedlichKwong"); + reg("Maskell-solid-solution", []() { return new MaskellSolidSolnPhase(); }); + addAlias("Maskell-solid-solution", "MaskellSolidSolnPhase"); + reg("liquid-water-IAPWS95", []() { return new WaterSSTP(); }); + addAlias("liquid-water-IAPWS95", "PureLiquidWater"); + reg("binary-solution-tabulated", []() { return new BinarySolutionTabulatedThermo(); }); + addAlias("binary-solution-tabulated", "BinarySolutionTabulatedThermo"); } ThermoPhase* ThermoFactory::newThermoPhase(const std::string& model) @@ -433,7 +433,7 @@ void addSpecies(ThermoPhase& thermo, const AnyValue& names, const AnyValue& spec "Could not find a species named '{}'.", name); } } - } else if (names.is() && names.asString() == "all") { + } else if (names == "all") { // The keyword 'all' means to add all species from this source for (const auto& item : species.asVector()) { thermo.addSpecies(newSpecies(item)); @@ -554,13 +554,29 @@ void setupPhase(ThermoPhase& thermo, AnyMap& phaseNode, const AnyMap& rootNode) auto* vpssThermo = dynamic_cast(&thermo); if (vpssThermo) { for (size_t k = 0; k < thermo.nSpecies(); k++) { - string model; + unique_ptr pdss; if (thermo.species(k)->input.hasKey("equation-of-state")) { - model = thermo.species(k)->input["equation-of-state"]["model"].asString(); + // Use the first node which specifies a valid PDSS model + auto& eos = thermo.species(k)->input["equation-of-state"]; + bool found = false; + for (auto& node : eos.asVector()) { + string model = node["model"].asString(); + if (PDSSFactory::factory()->exists(model)) { + pdss.reset(newPDSS(model)); + pdss->setParameters(node); + found = true; + break; + } + } + if (!found) { + throw InputFileError("setupPhase", eos, + "Could not find an equation-of-state specification " + "which defines a known PDSS model."); + } } else { - model = "ideal-gas"; + pdss.reset(newPDSS("ideal-gas")); } - vpssThermo->installPDSS(k, unique_ptr(newPDSS(model))); + vpssThermo->installPDSS(k, std::move(pdss)); } } diff --git a/src/thermo/VPStandardStateTP.cpp b/src/thermo/VPStandardStateTP.cpp index a65317cfc5..7a2e1c0f47 100644 --- a/src/thermo/VPStandardStateTP.cpp +++ b/src/thermo/VPStandardStateTP.cpp @@ -234,9 +234,6 @@ void VPStandardStateTP::installPDSS(size_t k, unique_ptr&& pdss) pdss->setReferenceThermo(spec.thermo); spec.thermo->validate(spec.name); } - if (spec.input.hasKey("equation-of-state")) { - pdss->setParameters(spec.input["equation-of-state"].as()); - } m_minTemp = std::max(m_minTemp, pdss->minTemp()); m_maxTemp = std::min(m_maxTemp, pdss->maxTemp()); diff --git a/src/tpx/CarbonDioxide.h b/src/tpx/CarbonDioxide.h index afc01c66bc..bbcc165dba 100644 --- a/src/tpx/CarbonDioxide.h +++ b/src/tpx/CarbonDioxide.h @@ -17,7 +17,7 @@ class CarbonDioxide : public Substance { public: CarbonDioxide() { - m_name="CarbonDioxide"; + m_name="carbon-dioxide"; m_formula="CO2"; } diff --git a/src/tpx/Heptane.h b/src/tpx/Heptane.h index 3fe70febf2..c5b40d8b20 100644 --- a/src/tpx/Heptane.h +++ b/src/tpx/Heptane.h @@ -16,7 +16,7 @@ class Heptane : public Substance { public: Heptane() { - m_name = "Heptane"; + m_name = "heptane"; m_formula = "C7H16"; } diff --git a/src/tpx/Sub.cpp b/src/tpx/Sub.cpp index 65c5b3d363..718e02b251 100644 --- a/src/tpx/Sub.cpp +++ b/src/tpx/Sub.cpp @@ -86,6 +86,7 @@ double Substance::cv() double Substance::cp() { double Tsave = T, dt = 1.e-4*T; + double RhoSave = Rho; double T1 = std::max(Tmin(), Tsave - dt); double T2 = std::min(Tmax(), Tsave + dt); double p0 = P(); @@ -115,13 +116,14 @@ double Substance::cp() } double s2 = s(); - Set(PropertyPair::TP, Tsave, p0); + Set(PropertyPair::TV, Tsave, 1.0 / RhoSave); return T*(s2 - s1)/(T2-T1); } double Substance::thermalExpansionCoeff() { double Tsave = T, dt = 1.e-4*T; + double RhoSave = Rho; double T1 = std::max(Tmin(), Tsave - dt); double T2 = std::min(Tmax(), Tsave + dt); double p0 = P(); @@ -153,13 +155,14 @@ double Substance::thermalExpansionCoeff() } double v2 = v(); - Set(PropertyPair::TP, Tsave, p0); + Set(PropertyPair::TV, Tsave, 1.0 / RhoSave); return 2.0*(v2 - v1)/((v2 + v1)*(T2-T1)); } double Substance::isothermalCompressibility() { double Psave = P(), dp = 1.e-4*Psave; + double RhoSave = Rho; double x0 = x(); if (TwoPhase()) { @@ -191,7 +194,7 @@ double Substance::isothermalCompressibility() } double v2 = v(); - Set(PropertyPair::TP, T, Psave); + Set(PropertyPair::TV, T, 1.0 / RhoSave); return -(v2 - v1)/(v0*(P2-P1)); } diff --git a/src/tpx/utils.cpp b/src/tpx/utils.cpp index 8f5d0dd6b2..2df226d254 100644 --- a/src/tpx/utils.cpp +++ b/src/tpx/utils.cpp @@ -30,9 +30,9 @@ Substance* newSubstance(const std::string& name) return new hydrogen; } else if (lcname == "oxygen") { return new oxygen; - } else if (lcname == "hfc134a") { + } else if (lcname == "hfc-134a" || lcname == "hfc134a") { return new HFC134a; - } else if (lcname == "carbondioxide") { + } else if (lcname == "carbon-dioxide" || lcname == "carbondioxide") { return new CarbonDioxide; } else if (lcname == "heptane") { return new Heptane; diff --git a/src/transport/TransportFactory.cpp b/src/transport/TransportFactory.cpp index 690749fd09..d1defbd74f 100644 --- a/src/transport/TransportFactory.cpp +++ b/src/transport/TransportFactory.cpp @@ -47,21 +47,23 @@ class TransportDBError : public CanteraError TransportFactory::TransportFactory() { reg("", []() { return new Transport(); }); - m_synonyms["None"] = ""; - reg("UnityLewis", []() { return new UnityLewisTransport(); }); - m_synonyms["unity-Lewis-number"] = "UnityLewis"; - reg("Mix", []() { return new MixTransport(); }); - m_synonyms["mixture-averaged"] = "Mix"; - m_synonyms["CK_Mix"] = m_synonyms["mixture-averaged-CK"] = "Mix"; - reg("Multi", []() { return new MultiTransport(); }); - m_synonyms["multicomponent"] = "Multi"; - m_synonyms["CK_Multi"] = m_synonyms["multicomponent-CK"] = "Multi"; - reg("Ion", []() { return new IonGasTransport(); }); - m_synonyms["ionized-gas"] = "Ion"; - reg("Water", []() { return new WaterTransport(); }); - m_synonyms["water"] = "Water"; - reg("HighP", []() { return new HighPressureGasTransport(); }); - m_synonyms["high-pressure"] = "HighP"; + addAlias("", "None"); + reg("unity-Lewis-number", []() { return new UnityLewisTransport(); }); + addAlias("unity-Lewis-number", "UnityLewis"); + reg("mixture-averaged", []() { return new MixTransport(); }); + addAlias("mixture-averaged", "Mix"); + reg("mixture-averaged-CK", []() { return new MixTransport(); }); + addAlias("mixture-averaged-CK", "CK_Mix"); + reg("multicomponent", []() { return new MultiTransport(); }); + addAlias("multicomponent", "Multi"); + reg("multicomponent-CK", []() { return new MultiTransport(); }); + addAlias("multicomponent-CK", "CK_Multi"); + reg("ionized-gas", []() { return new IonGasTransport(); }); + addAlias("ionized-gas", "Ion"); + reg("water", []() { return new WaterTransport(); }); + addAlias("water", "Water"); + reg("high-pressure", []() { return new HighPressureGasTransport(); }); + addAlias("high-pressure", "HighP"); m_CK_mode["CK_Mix"] = m_CK_mode["mixture-averaged-CK"] = true; m_CK_mode["CK_Multi"] = m_CK_mode["multicomponent-CK"] = true; } diff --git a/test/data/thermo-models.yaml b/test/data/thermo-models.yaml index e625d315ca..541f8829bb 100644 --- a/test/data/thermo-models.yaml +++ b/test/data/thermo-models.yaml @@ -423,7 +423,8 @@ dh-electrolyte-species: equation-of-state: model: constant-volume molar-volume: 1.3 - ionic-radius: 4 Å + Debye-Huckel: + ionic-radius: 4 Å - name: Cl- composition: {Cl: 1, E: 1} thermo: @@ -434,7 +435,8 @@ dh-electrolyte-species: equation-of-state: model: constant-volume molar-volume: 1.3 - ionic-radius: 3.0 angstrom + Debye-Huckel: + ionic-radius: 3.0 angstrom - name: H+ composition: {H: 1, E: -1} thermo: @@ -445,7 +447,8 @@ dh-electrolyte-species: equation-of-state: model: constant-volume molar-volume: 0.0 - ionic-radius: 9e-10 m + Debye-Huckel: + ionic-radius: 9e-10 m - name: OH- composition: {O: 1, H: 1, E: 1} thermo: @@ -456,7 +459,8 @@ dh-electrolyte-species: equation-of-state: model: constant-volume molar-volume: 1.3 - ionic-radius: 3.5 angstrom + Debye-Huckel: + ionic-radius: 3.5 angstrom - name: NaCl(aq) composition: {Na: 1, Cl: 1} thermo: @@ -467,8 +471,9 @@ dh-electrolyte-species: equation-of-state: model: constant-volume molar-volume: 1.3 - electrolyte-species-type: weak-acid-associated - weak-acid-charge: -1.0 + Debye-Huckel: + electrolyte-species-type: weak-acid-associated + weak-acid-charge: -1.0 ions-from-neutral-species: - name: K+ diff --git a/test/general/test_containers.cpp b/test/general/test_containers.cpp index d7a3ea3000..2105f46aeb 100644 --- a/test/general/test_containers.cpp +++ b/test/general/test_containers.cpp @@ -24,6 +24,60 @@ TEST(AnyValue, is_moveable) { EXPECT_EQ(value2.asString(), "1"); } +TEST(AnyValue, getMapWhere_initial_list) +{ + AnyMap m = AnyMap::fromYamlString( + "data: [{a: foo, x: 2}, {a: bar, x: 3}]"); + + EXPECT_TRUE(m["data"].hasMapWhere("a", "foo")); + EXPECT_EQ(m["data"].getMapWhere("a", "bar")["x"].asInt(), 3); + + EXPECT_THROW(m["data"].getMapWhere("a", "baz"), CanteraError); + auto& newChild = m["data"].getMapWhere("a", "baz", true); + EXPECT_EQ(newChild.size(), (size_t) 1); + newChild["x"] = 4; + EXPECT_EQ(m["data"].getMapWhere("a", "baz")["x"].asInt(), 4); +} + +TEST(AnyValue, getMapWhere_initial_map) +{ + AnyMap m = AnyMap::fromYamlString( + "data: {a: foo, x: 2}"); + + EXPECT_TRUE(m["data"].hasMapWhere("a", "foo")); + EXPECT_EQ(m["data"].getMapWhere("a", "foo")["x"].asInt(), 2); + + EXPECT_THROW(m["data"].getMapWhere("a", "baz"), CanteraError); + auto& newChild = m["data"].getMapWhere("a", "baz", true); + EXPECT_EQ(newChild.size(), (size_t) 1); + newChild["x"] = 4; + EXPECT_EQ(m["data"].getMapWhere("a", "baz")["x"].asInt(), 4); + EXPECT_EQ(m["data"].getMapWhere("a", "foo")["x"].asInt(), 2); +} + +TEST(AnyValue, convert_vectorAnyMap) +{ + AnyMap m = AnyMap::fromYamlString( + "data: {a: foo, x: 2}"); + + auto& v = m["data"].asVector(); + EXPECT_EQ(v.size(), (size_t) 1); + EXPECT_EQ(v[0]["a"].asString(), "foo"); +} + +TEST(AnyValue, equality) { + AnyValue three(3); + EXPECT_EQ(three, 3); + EXPECT_EQ(3, three); + EXPECT_EQ(3.0, three); + EXPECT_NE(three, 4); + EXPECT_NE(three, "three"); + + AnyValue word("word"); + EXPECT_EQ(word, "word"); + EXPECT_EQ("word", word); +} + TEST(AnyMap, paths) { AnyMap m; m["simple"] = "qux"; @@ -44,6 +98,68 @@ TEST(AnyMap, paths) { EXPECT_THROW(m["missing"].asString(), std::exception); } +TEST(AnyMap, equality1) { + AnyMap m1; + m1["simple"] = "qux"; + m1["compound"]["first"] = "bar"; + m1["compound"]["second"] = 3.14; + AnyMap m2 = m1; + EXPECT_EQ(m1, m2); + m1["compound"]["second"] = 4.0; + EXPECT_NE(m1, m2); + m2["compound"]["second"] = 4.0; + EXPECT_EQ(m1, m2); + m2["foo"] = 5; + EXPECT_NE(m1, m2); +} + +TEST(AnyMap, equality2) { + // Build two identical maps + std::vector M(2); + for (auto& m : M) { + m["group"]["vector_double"] = vector_fp{1.1, 3.2, 2.4}; + m["group"]["vector_int"] = std::vector{3,5,7,9}; + m["group"]["changes"] = "a string"; + m["group"]["changes"] = 9; + m["group"]["vector_vector_double"] = std::vector{ + {1.2, 2.1}, {3.4, 4.3}, {5.6, 6.5} + }; + m["bool"] = true; + m["int"] = 33; + m["vector_any_int"] = std::vector{3, 9, -1}; + m["strings"] = std::vector{"spam", "eggs", "spam"}; + } + + EXPECT_EQ(M[0], M[1]); + + // Hidden keys shouldn't affect equality + M[0]["__secret__"] = true; + EXPECT_EQ(M[0], M[1]); + EXPECT_EQ(M[1], M[0]); + + M[0]["group"]["changes"] = 8; + EXPECT_NE(M[0], M[1]); + + M[0]["group"]["changes"] = 9.0; + M[0]["int"].asDouble(); + EXPECT_EQ(M[0], M[1]); + + // These conversions affect the type of the held value, but they should + // still be considered equal + M[0]["group"]["vector_int"].asVector(); + EXPECT_EQ(M[0], M[1]); + + M[0]["vector_any_int"].asVector(); + EXPECT_EQ(M[0], M[1]); + + M[1]["group"]["vector_double"].asVector(); + EXPECT_EQ(M[0], M[1]); + + M[0]["strings"].asVector(); + EXPECT_EQ(M[0], M[1]); + EXPECT_EQ(M[1], M[0]); +} + TEST(AnyMap, map_conversion) { AnyMap m; m["compound"]["first"] = "bar"; diff --git a/test/thermo/phaseConstructors.cpp b/test/thermo/phaseConstructors.cpp index 33d0467c81..81179f06f1 100644 --- a/test/thermo/phaseConstructors.cpp +++ b/test/thermo/phaseConstructors.cpp @@ -375,7 +375,7 @@ TEST(PureFluidFromScratch, CarbonDioxide) auto sCO2 = make_shared("CO2", parseCompString("C:1 O:2")); sCO2->thermo.reset(new ShomatePoly2(200, 6000, 101325, co2_shomate_coeffs)); p.addSpecies(sCO2); - p.setSubstance("carbondioxide"); + p.setSubstance("carbon-dioxide"); p.initThermo(); p.setState_Tsat(280, 0.5); EXPECT_NEAR(p.pressure(), 4160236.987, 1e-2); @@ -427,22 +427,22 @@ TEST(DebyeHuckel, fromScratch) auto sNa = make_species("Na+", "Na:1, E:-1", -240.34e6, 298.15, -103.98186, 333.15, -103.98186); sNa->charge = 1; - sNa->input["ionic-radius"] = 4.0e-10; + sNa->input["Debye-Huckel"]["ionic-radius"] = 4.0e-10; auto sCl = make_species("Cl-", "Cl:1, E:1", -167.08e6, 298.15, -74.20664, 333.15, -74.20664); sCl->charge = -1; - sCl->input["ionic-radius"] = 3.0e-10; + sCl->input["Debye-Huckel"]["ionic-radius"] = 3.0e-10; auto sH = make_species("H+", "H:1, E:-1", 0.0, 298.15, 0.0, 333.15, 0.0); sH->charge = 1; - sH->input["ionic-radius"] = 9.0e-10; + sH->input["Debye-Huckel"]["ionic-radius"] = 9.0e-10; auto sOH = make_species("OH-", "O:1, H:1, E:1", -230.015e6, 298.15, -91.50963, 333.15, -85); sOH->charge = -1; - sOH->input["ionic-radius"] = 3.5e-10; + sOH->input["Debye-Huckel"]["ionic-radius"] = 3.5e-10; auto sNaCl = make_species("NaCl(aq)", "Na:1, Cl:1", -96.03e6*4.184, 298.15, -174.5057463, 333.15, -174.5057463); - sNaCl->input["weak-acid-charge"] = -1.0; - sNaCl->input["electrolyte-species-type"] = "weakAcidAssociated"; + sNaCl->input["Debye-Huckel"]["weak-acid-charge"] = -1.0; + sNaCl->input["Debye-Huckel"]["electrolyte-species-type"] = "weakAcidAssociated"; for (auto& s : {sH2O, sNa, sCl, sH, sOH, sNaCl}) { p.addSpecies(s); } diff --git a/test/thermo/thermoFromYaml.cpp b/test/thermo/thermoFromYaml.cpp index 71a2366e0a..988bd79176 100644 --- a/test/thermo/thermoFromYaml.cpp +++ b/test/thermo/thermoFromYaml.cpp @@ -4,6 +4,7 @@ #include "cantera/thermo/MolalityVPSSTP.h" #include "cantera/thermo/IdealGasPhase.h" #include "cantera/thermo/SurfPhase.h" +#include using namespace Cantera; @@ -211,6 +212,28 @@ TEST(ThermoFromYaml, IonsFromNeutral) EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); } +TEST(ThermoFromYaml, IonsFromNeutral_fromString) +{ + // A little different because we can't re-read the input file to get the + // phase definition for the neutral phase + std::ifstream infile("../data/thermo-models.yaml"); + std::stringstream buffer; + buffer << infile.rdbuf(); + AnyMap input = AnyMap::fromYamlString(buffer.str()); + auto thermo = newPhase( + input["phases"].getMapWhere("name", "ions-from-neutral-molecule"), + input); + ASSERT_EQ((int) thermo->nSpecies(), 2); + vector_fp mu(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + + // Values for regression testing only -- same as "fromScratch" test + EXPECT_NEAR(thermo->density(), 1984.2507319669949, 1e-6); + EXPECT_NEAR(thermo->enthalpy_mass(), -14738312.44316336, 1e-6); + EXPECT_NEAR(mu[0], -4.66404010e+08, 1e1); + EXPECT_NEAR(mu[1], -2.88157316e+06, 1e-1); +} + TEST(ThermoFromYaml, IdealSolnGas_gas) { auto thermo = newThermo("thermo-models.yaml", "IdealSolnGas-gas"); @@ -371,6 +394,33 @@ TEST(ThermoFromYaml, Lattice) } } +TEST(ThermoFromYaml, Lattice_fromString) +{ + // A little different because we can't re-read the input file to get the + // phase definition for the neutral phase + std::ifstream infile("../data/thermo-models.yaml"); + std::stringstream buffer; + buffer << infile.rdbuf(); + AnyMap input = AnyMap::fromYamlString(buffer.str()); + auto thermo = newPhase( + input["phases"].getMapWhere("name", "Li7Si3_and_interstitials"), + input); + + // Regression test based on modified version of Li7Si3_ls.xml + EXPECT_NEAR(thermo->enthalpy_mass(), -2077955.0584538165, 1e-6); + double mu_ref[] = {-4.62717474e+08, -4.64248485e+07, 1.16370186e+05}; + double vol_ref[] = {0.095564748201438871, 0.2, 0.09557086}; + vector_fp mu(thermo->nSpecies()); + vector_fp vol(thermo->nSpecies()); + thermo->getChemPotentials(mu.data()); + thermo->getPartialMolarVolumes(vol.data()); + + for (size_t k = 0; k < thermo->nSpecies(); k++) { + EXPECT_NEAR(mu[k], mu_ref[k], 1e-7*fabs(mu_ref[k])); + EXPECT_NEAR(vol[k], vol_ref[k], 1e-7); + } +} + TEST(ThermoFromYaml, Metal) { auto thermo = newThermo("thermo-models.yaml", "Metal");