Create a complete ThermoPhase object from a YAML input file
This commit is contained in:
parent
c8b737035d
commit
f4d45f8a85
8 changed files with 386 additions and 2 deletions
|
|
@ -59,12 +59,14 @@ public:
|
|||
template<class T>
|
||||
T& as();
|
||||
|
||||
const std::type_info& type();
|
||||
const std::type_info& type() const;
|
||||
std::string type_str() const;
|
||||
|
||||
template<class T>
|
||||
bool is() const;
|
||||
|
||||
explicit AnyValue(const std::string& value);
|
||||
explicit AnyValue(const char* value);
|
||||
AnyValue& operator=(const std::string& value);
|
||||
AnyValue& operator=(const char* value);
|
||||
const std::string& asString() const;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,17 @@ inline ThermoPhase* newThermoPhase(const std::string& model)
|
|||
*/
|
||||
ThermoPhase* newPhase(XML_Node& phase);
|
||||
|
||||
//! Create a new ThermoPhase object and initialize it
|
||||
/*!
|
||||
* @param phaseNode The node containing the phase definition (i.e. thermo
|
||||
* model, list of species, and initial state)
|
||||
* @param rootNode The root node of the tree containing the phase definition,
|
||||
* which will be used as the default location from which to read species
|
||||
* definitions.
|
||||
*/
|
||||
unique_ptr<ThermoPhase> newPhase(const AnyMap& phaseNode,
|
||||
const AnyMap& rootNode=AnyMap());
|
||||
|
||||
//! Create and Initialize a ThermoPhase object from an XML input file.
|
||||
/*!
|
||||
* This routine is a wrapper around the newPhase(XML_Node) routine which does
|
||||
|
|
@ -187,6 +198,18 @@ ThermoPhase* newPhase(const std::string& infile, std::string id="");
|
|||
*/
|
||||
void importPhase(XML_Node& phase, ThermoPhase* th);
|
||||
|
||||
//! Initialize a ThermoPhase object
|
||||
/*!
|
||||
* @param phase The ThermoPhase object to be initialized
|
||||
* @param phaseNode The node containing the phase definition (i.e. thermo
|
||||
* model, list of species, and initial state)
|
||||
* @param rootNode The root node of the tree containing the phase definition,
|
||||
* which will be used as the default location from which to read species
|
||||
* definitions.
|
||||
*/
|
||||
void setupPhase(ThermoPhase& phase, const AnyMap& phaseNode,
|
||||
const AnyMap& rootNode=AnyMap());
|
||||
|
||||
//! Add the elements given in an XML_Node tree to the specified phase
|
||||
void installElements(Phase& th, const XML_Node& phaseNode);
|
||||
|
||||
|
|
|
|||
2
interfaces/cython/.gitignore
vendored
2
interfaces/cython/.gitignore
vendored
|
|
@ -7,6 +7,8 @@ cantera/test/data/*.xml
|
|||
cantera/test/data/*.inp
|
||||
cantera/test/data/*.dat
|
||||
cantera/test/data/*.csv
|
||||
cantera/test/data/*.yaml
|
||||
cantera/test/data/*.yml
|
||||
setup.py
|
||||
scripts/ctml_writer.py
|
||||
scripts/ctml_writer
|
||||
|
|
|
|||
|
|
@ -287,12 +287,18 @@ bool AnyValue::hasKey(const std::string& key) const {
|
|||
|
||||
void AnyValue::setKey(const std::string &key) { m_key = key; }
|
||||
|
||||
const std::type_info &AnyValue::type() {
|
||||
const std::type_info &AnyValue::type() const {
|
||||
return m_value->type();
|
||||
}
|
||||
|
||||
std::string AnyValue::type_str() const {
|
||||
return demangle(type());
|
||||
}
|
||||
|
||||
AnyValue::AnyValue(const std::string& value) : m_value(new boost::any{value}) {}
|
||||
|
||||
AnyValue::AnyValue(const char* value) : m_value(new boost::any{std::string(value)}) {}
|
||||
|
||||
AnyValue &AnyValue::operator=(const std::string &value) {
|
||||
*m_value = value;
|
||||
return *this;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ std::mutex ThermoFactory::thermo_mutex;
|
|||
ThermoFactory::ThermoFactory()
|
||||
{
|
||||
reg("IdealGas", []() { return new IdealGasPhase(); });
|
||||
m_synonyms["ideal-gas"] = "IdealGas";
|
||||
reg("Incompressible", []() { return new ConstDensityThermo(); });
|
||||
reg("Surface", []() { return new SurfPhase(); });
|
||||
reg("Edge", []() { return new EdgePhase(); });
|
||||
|
|
@ -88,6 +89,13 @@ ThermoPhase* newPhase(XML_Node& xmlphase)
|
|||
return t.release();
|
||||
}
|
||||
|
||||
unique_ptr<ThermoPhase> newPhase(const AnyMap& phaseNode, const AnyMap& rootNode)
|
||||
{
|
||||
unique_ptr<ThermoPhase> t(newThermoPhase(phaseNode["thermo"].asString()));
|
||||
setupPhase(*t, phaseNode, rootNode);
|
||||
return t;
|
||||
}
|
||||
|
||||
ThermoPhase* newPhase(const std::string& infile, std::string id)
|
||||
{
|
||||
XML_Node* root = get_XML_File(infile);
|
||||
|
|
@ -353,6 +361,152 @@ void importPhase(XML_Node& phase, ThermoPhase* th)
|
|||
th->initThermoXML(phase, id);
|
||||
}
|
||||
|
||||
void addElements(ThermoPhase& thermo, const vector<string>& element_names,
|
||||
const unordered_map<string, const AnyMap*>& local_elements,
|
||||
bool allow_default)
|
||||
{
|
||||
for (const auto& symbol : element_names) {
|
||||
if (local_elements.count(symbol)) {
|
||||
auto& element = *local_elements.at(symbol);
|
||||
double weight = element["atomic-weight"].asDouble();
|
||||
long int number = element.getInt("atomic-number", 0);
|
||||
double e298 = element.getDouble("entropy298", ENTROPY298_UNKNOWN);
|
||||
thermo.addElement(symbol, weight, number, e298);
|
||||
} else if (allow_default) {
|
||||
thermo.addElement(symbol);
|
||||
} else {
|
||||
throw CanteraError("addElements", "Element '{}' not found", symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addSpecies(ThermoPhase& thermo, const AnyValue& names, const AnyValue& species)
|
||||
{
|
||||
if (names.is<vector<string>>()) {
|
||||
// 'names' is a list of species names which should be found in 'species'
|
||||
const auto& species_nodes = species.asMap("name");
|
||||
for (const auto& name : names.asVector<string>()) {
|
||||
thermo.addSpecies(newSpecies(*species_nodes.at(name)));
|
||||
}
|
||||
} else if (names.is<string>() && names.asString() == "all") {
|
||||
// The keyword 'all' means to add all species from this source
|
||||
for (const auto& item : species.asVector<AnyMap>()) {
|
||||
thermo.addSpecies(newSpecies(item));
|
||||
}
|
||||
} else {
|
||||
throw CanteraError("addSpecies",
|
||||
"Could not parse species declaration of type '{}'", names.type_str());
|
||||
}
|
||||
}
|
||||
|
||||
void setupPhase(ThermoPhase& thermo, const AnyMap& phaseNode,
|
||||
const AnyMap& rootNode)
|
||||
{
|
||||
thermo.setName(phaseNode["name"].asString());
|
||||
|
||||
// Add elements
|
||||
if (phaseNode.hasKey("elements")) {
|
||||
if (phaseNode.getBool("skip-undeclared-elements", false)) {
|
||||
thermo.ignoreUndefinedElements();
|
||||
} else {
|
||||
thermo.throwUndefinedElements();
|
||||
}
|
||||
|
||||
if (phaseNode["elements"].is<vector<string>>()) {
|
||||
// 'elements' is a list of element symbols
|
||||
if (rootNode.hasKey("elements")) {
|
||||
addElements(thermo, phaseNode["elements"].asVector<string>(),
|
||||
rootNode["elements"].asMap("symbol"), true);
|
||||
} else {
|
||||
addElements(thermo, phaseNode["elements"].asVector<string>(),
|
||||
{}, true);
|
||||
}
|
||||
} else if (phaseNode["elements"].is<vector<AnyMap>>()) {
|
||||
// Each item in 'elements' is a map with one item, where the key is
|
||||
// a section in this file or another YAML file, and the value is a
|
||||
// list of element symbols to read from that section
|
||||
for (const auto& elemNode : phaseNode["elements"].asVector<AnyMap>()) {
|
||||
const string& source = elemNode.begin()->first;
|
||||
const auto& names = elemNode.begin()->second.asVector<string>();
|
||||
const auto& slash = boost::ifind_first(source, "/");
|
||||
if (slash) {
|
||||
std::string fileName(source.begin(), slash.begin());
|
||||
std::string node(slash.end(), source.end());
|
||||
const AnyMap elements = AnyMap::fromYamlFile(fileName);
|
||||
addElements(thermo, names,
|
||||
elements[node].asMap("symbol"), false);
|
||||
} else if (rootNode.hasKey(source)) {
|
||||
addElements(thermo, names,
|
||||
rootNode[source].asMap("symbol"), false);
|
||||
} else if (source == "default") {
|
||||
addElements(thermo, names, {}, true);
|
||||
} else {
|
||||
throw CanteraError("setupPhase",
|
||||
"Could not find elements section named '{}'", source);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw CanteraError("setupPhase",
|
||||
"Could not parse elements declaration of type '{}'",
|
||||
phaseNode["elements"].type_str());
|
||||
}
|
||||
} else {
|
||||
// If no elements list is provided, just add elements as-needed from the
|
||||
// default list.
|
||||
thermo.addUndefinedElements();
|
||||
}
|
||||
|
||||
// Add species
|
||||
if (phaseNode.hasKey("species")) {
|
||||
if (phaseNode["species"].is<vector<string>>()) {
|
||||
// 'species' is a list of species names to be added from the current
|
||||
// file's 'species' section
|
||||
addSpecies(thermo, phaseNode["species"], rootNode["species"]);
|
||||
} else if (phaseNode["species"].is<string>()) {
|
||||
// 'species' is a keyword applicable to the current file's 'species'
|
||||
// section
|
||||
addSpecies(thermo, phaseNode["species"], rootNode["species"]);
|
||||
} else if (phaseNode["species"].is<vector<AnyMap>>()) {
|
||||
// Each item in 'species' is a map with one item, where the key is
|
||||
// a section in this file or another YAML file, and the value is a
|
||||
// list of species names to read from that section
|
||||
for (const auto& speciesNode : phaseNode["species"].asVector<AnyMap>()) {
|
||||
const string& source = speciesNode.begin()->first;
|
||||
const auto& names = speciesNode.begin()->second;
|
||||
const auto& slash = boost::ifind_first(source, "/");
|
||||
if (slash) {
|
||||
// source is a different input file
|
||||
std::string fileName(source.begin(), slash.begin());
|
||||
std::string node(slash.end(), source.end());
|
||||
AnyMap species = AnyMap::fromYamlFile(fileName);
|
||||
addSpecies(thermo, names, species[node]);
|
||||
} else if (rootNode.hasKey(source)) {
|
||||
// source is in the current file
|
||||
addSpecies(thermo, names, rootNode[source]);
|
||||
} else {
|
||||
throw CanteraError("setupPhase",
|
||||
"Could not find species section named '{}'", source);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw CanteraError("setupPhase",
|
||||
"Could not parse species declaration of type '{}'",
|
||||
phaseNode["species"].type_str());
|
||||
}
|
||||
} else {
|
||||
// By default, add all species from the 'species' section
|
||||
addSpecies(thermo, AnyValue("all"), rootNode["species"]);
|
||||
}
|
||||
|
||||
thermo.initThermo();
|
||||
|
||||
if (phaseNode.hasKey("state")) {
|
||||
thermo.setState(phaseNode["state"].as<AnyMap>());
|
||||
} else {
|
||||
thermo.setState_TP(298.15, OneAtm);
|
||||
}
|
||||
}
|
||||
|
||||
void installElements(Phase& th, const XML_Node& phaseNode)
|
||||
{
|
||||
// get the declared element names
|
||||
|
|
|
|||
96
test/data/ideal-gas.yaml
Normal file
96
test/data/ideal-gas.yaml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
units: {length: cm, time: s, quantity: mol, molar-energy: cal/mol}
|
||||
|
||||
phases:
|
||||
- name: simple
|
||||
thermo: ideal-gas
|
||||
elements: [N, O]
|
||||
species: [O2, NO, N2]
|
||||
state: {T: 500.0, P: 10 atm, X: {O2: 0.21, N2: 0.79}}
|
||||
|
||||
- name: duplicate-species
|
||||
thermo: ideal-gas
|
||||
elements: [N, O]
|
||||
species: [O2, NO, N2, NO]
|
||||
state: {T: 300.0, P: 1 atm, X: {O2: 0.21, N2: 0.79}}
|
||||
|
||||
- name: element-override
|
||||
thermo: ideal-gas
|
||||
elements: [N, O, Ar]
|
||||
species: [O2, NO, N2, AR]
|
||||
note: replace Argon with custom element
|
||||
state: {T: 900.0, P: 5 atm, Y: {O2: 0.4, N2: 0.4, AR: 0.2}}
|
||||
|
||||
- name: element-remote
|
||||
thermo: ideal-gas
|
||||
elements:
|
||||
- default: [N, O]
|
||||
- species-elements.yaml/isotopes: [Ar]
|
||||
species: [O2, NO, N2, AR]
|
||||
note: replace Argon with custom element from a different file
|
||||
state: {T: 900.0, P: 5 atm, Y: {O2: 0.4, N2: 0.4, AR: 0.2}}
|
||||
|
||||
- name: species-remote
|
||||
thermo: ideal-gas
|
||||
species:
|
||||
- species: [O2, N2]
|
||||
- species-elements.yaml/species: [NO2, N2O]
|
||||
state: {T: 300.0, P: 1 atm, X: "N2O:0.3, O2:0.7"}
|
||||
|
||||
- name: species-all
|
||||
thermo: ideal-gas
|
||||
state: {T: 300.0, P: 1 atm, X: {AR: 0.2, O2: 0.7, N2: 0.1}}
|
||||
|
||||
|
||||
elements:
|
||||
- symbol: Ar
|
||||
atomic-weight: 36
|
||||
|
||||
|
||||
species:
|
||||
- name: O2
|
||||
composition: {O: 2}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [200.00, 1000.00, 3500.00]
|
||||
data:
|
||||
- [3.782456360E+00, -2.996734160E-03, 9.847302010E-06, -9.681295090E-09,
|
||||
3.243728370E-12, -1.063943560E+03, 3.657675730E+00]
|
||||
- [3.282537840E+00, 1.483087540E-03, -7.579666690E-07, 2.094705550E-10,
|
||||
-2.167177940E-14, -1.088457720E+03, 5.453231290E+00]
|
||||
note: "TPIS89"
|
||||
|
||||
- name: NO
|
||||
composition: {N: 1, O: 1}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [200.00, 1000.00, 6000.00]
|
||||
data:
|
||||
- [4.218476300E+00, -4.638976000E-03, 1.104102200E-05, -9.336135400E-09,
|
||||
2.803577000E-12, 9.844623000E+03, 2.280846400E+00]
|
||||
- [3.260605600E+00, 1.191104300E-03, -4.291704800E-07, 6.945766900E-11,
|
||||
-4.033609900E-15, 9.920974600E+03, 6.369302700E+00]
|
||||
note: "RUS 78"
|
||||
|
||||
- name: N2
|
||||
composition: {N: 2}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [300.00, 1000.00, 5000.00]
|
||||
data:
|
||||
- [3.298677000E+00, 1.408240400E-03, -3.963222000E-06, 5.641515000E-09,
|
||||
-2.444854000E-12, -1.020899900E+03, 3.950372000E+00]
|
||||
- [2.926640000E+00, 1.487976800E-03, -5.684760000E-07, 1.009703800E-10,
|
||||
-6.753351000E-15, -9.227977000E+02, 5.980528000E+00]
|
||||
note: "121286"
|
||||
|
||||
- name: AR
|
||||
composition: {Ar: 1}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [300.00, 1000.00, 5000.00]
|
||||
data:
|
||||
- [2.500000000E+00, 0.000000000E+00, 0.000000000E+00, 0.000000000E+00,
|
||||
0.000000000E+00, -7.453750000E+02, 4.366000000E+00]
|
||||
- [2.500000000E+00, 0.000000000E+00, 0.000000000E+00, 0.000000000E+00,
|
||||
0.000000000E+00, -7.453750000E+02, 4.366000000E+00]
|
||||
note: "120186"
|
||||
32
test/data/species-elements.yaml
Normal file
32
test/data/species-elements.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
units: {length: cm, time: s, quantity: mol, molar-energy: cal/mol}
|
||||
|
||||
isotopes:
|
||||
- symbol: Ar
|
||||
atomic-weight: 38
|
||||
- symbol: O18
|
||||
atomic-weight: 18
|
||||
|
||||
species:
|
||||
- name: NO2
|
||||
composition: {N: 1, O: 2}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [200.00, 1000.00, 6000.00]
|
||||
data:
|
||||
- [3.944031200E+00, -1.585429000E-03, 1.665781200E-05, -2.047542600E-08,
|
||||
7.835056400E-12, 2.896617900E+03, 6.311991700E+00]
|
||||
- [4.884754200E+00, 2.172395600E-03, -8.280690600E-07, 1.574751000E-10,
|
||||
-1.051089500E-14, 2.316498300E+03, -1.174169500E-01]
|
||||
note: "L 7/88"
|
||||
|
||||
- name: N2O
|
||||
composition: {N: 2, O: 1}
|
||||
thermo:
|
||||
model: NASA7
|
||||
temperature-ranges: [200.00, 1000.00, 6000.00]
|
||||
data:
|
||||
- [2.257150200E+00, 1.130472800E-02, -1.367131900E-05, 9.681980600E-09,
|
||||
-2.930718200E-12, 8.741774400E+03, 1.075799200E+01]
|
||||
- [4.823072900E+00, 2.627025100E-03, -9.585087400E-07, 1.600071200E-10,
|
||||
-9.775230300E-15, 8.073404800E+03, -2.201720700E+00]
|
||||
note: "L 7/88"
|
||||
69
test/thermo/thermoFromYaml.cpp
Normal file
69
test/thermo/thermoFromYaml.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "cantera/thermo/ThermoFactory.h"
|
||||
#include "cantera/thermo/Elements.h"
|
||||
|
||||
using namespace Cantera;
|
||||
|
||||
TEST(ThermoFromYaml, simpleIdealGas)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
auto thermo = newPhase(*phaseNodes.at("simple"), infile);
|
||||
EXPECT_EQ(thermo->nSpecies(), (size_t) 3);
|
||||
EXPECT_DOUBLE_EQ(thermo->density(), 7.031763356741983);
|
||||
EXPECT_DOUBLE_EQ(thermo->cp_mass(), 1037.7632754708304);
|
||||
}
|
||||
|
||||
TEST(ThermoFromYaml, failDuplicateSpecies)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
EXPECT_THROW(newPhase(*phaseNodes.at("duplicate-species"), infile),
|
||||
CanteraError);
|
||||
}
|
||||
|
||||
TEST(ThermoFromYaml, elementOverride)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
auto thermo = newPhase(*phaseNodes.at("element-override"), infile);
|
||||
EXPECT_EQ(thermo->nElements(), (size_t) 3);
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N"));
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O"));
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 36);
|
||||
}
|
||||
|
||||
TEST(ThermoFromYaml, elementFromDifferentFile)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
auto thermo = newPhase(*phaseNodes.at("element-remote"), infile);
|
||||
EXPECT_EQ(thermo->nElements(), (size_t) 3);
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(0), getElementWeight("N"));
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(1), getElementWeight("O"));
|
||||
EXPECT_DOUBLE_EQ(thermo->atomicWeight(2), 38);
|
||||
}
|
||||
|
||||
TEST(ThermoFromYaml, speciesFromDifferentFile)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
auto thermo = newPhase(*phaseNodes.at("species-remote"), infile);
|
||||
EXPECT_EQ(thermo->nElements(), (size_t) 2);
|
||||
EXPECT_EQ(thermo->nSpecies(), (size_t) 4);
|
||||
EXPECT_EQ(thermo->species(0)->composition["O"], 2);
|
||||
EXPECT_EQ(thermo->species(3)->composition["O"], 1);
|
||||
EXPECT_EQ(thermo->species(2)->name, "NO2");
|
||||
EXPECT_DOUBLE_EQ(thermo->moleFraction(3), 0.3);
|
||||
}
|
||||
|
||||
TEST(ThermoFromYaml, speciesAll)
|
||||
{
|
||||
AnyMap infile = AnyMap::fromYamlFile("ideal-gas.yaml");
|
||||
auto phaseNodes = infile["phases"].asMap("name");
|
||||
auto thermo = newPhase(*phaseNodes.at("species-all"), infile);
|
||||
EXPECT_EQ(thermo->nElements(), (size_t) 3);
|
||||
EXPECT_EQ(thermo->nSpecies(), (size_t) 4);
|
||||
EXPECT_EQ(thermo->species(1)->name, "NO");
|
||||
EXPECT_EQ(thermo->species(2)->name, "N2");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue