diff --git a/examples/full_configuration.json b/examples/full_configuration.json index 492e8b4..8c60f92 100644 --- a/examples/full_configuration.json +++ b/examples/full_configuration.json @@ -12,6 +12,9 @@ { "name": "C" }, + { + "name": "M" + }, { "name": "H2O2", "HLC(298K) [mol m-3 Pa-1]": 1.011596348, diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index 6f5de43..dc611c0 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -51,6 +51,17 @@ namespace open_atmos const std::string E = "E"; const std::string Ea = "Ea"; + // Troe + const std::string Troe_key = "TROE"; + const std::string k0_A = "k0_A"; + const std::string k0_B = "k0_B"; + const std::string k0_C = "k0_C"; + const std::string kinf_A = "kinf_A"; + const std::string kinf_B = "kinf_B"; + const std::string kinf_C = "kinf_C"; + const std::string Fc = "Fc"; + const std::string N = "N"; + } keys; struct Configuration @@ -89,6 +100,12 @@ namespace open_atmos const std::vector optional_keys{ keys.A, keys.B, keys.C, keys.D, keys.E, keys.Ea, keys.name }; } arrhenius; + struct Troe + { + const std::vector required_keys{ keys.products, keys.reactants, keys.type, keys.gas_phase }; + const std::vector optional_keys{ keys.name, keys.k0_A, keys.k0_B, keys.k0_C, keys.kinf_A, keys.kinf_B, keys.kinf_C, keys.Fc, keys.N }; + } troe; + struct Mechanism { const std::vector required_keys{}; diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 2d3e422..176d7f5 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -14,9 +14,8 @@ namespace open_atmos struct Species { std::string name; - std::map optional_numerical_properties; - + /// @brief Unknown properties, prefixed with two underscores (__) std::unordered_map unknown_properties; }; @@ -24,6 +23,7 @@ namespace open_atmos { std::string name; std::vector species; + /// @brief Unknown properties, prefixed with two underscores (__) std::unordered_map unknown_properties; }; @@ -31,6 +31,7 @@ namespace open_atmos { std::string species_name; double coefficient; + /// @brief Unknown properties, prefixed with two underscores (__) std::unordered_map unknown_properties; }; @@ -47,7 +48,6 @@ namespace open_atmos double D{ 300 }; /// @brief A factor that determines pressure dependence [Pa-1] double E{ 0 }; - /// @brief A list of reactants std::vector reactants; /// @brief A list of products @@ -56,13 +56,44 @@ namespace open_atmos std::string name; /// @brief An identifier indicating which gas phase this reaction takes place in std::string gas_phase; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + struct Troe + { + /// @brief low-pressure pre-exponential factor + double k0_A = 1.0; + /// @brief low-pressure temperature-scaling parameter + double k0_B = 0.0; + /// @brief low-pressure exponential factor + double k0_C = 0.0; + /// @brief high-pressure pre-exponential factor + double kinf_A = 1.0; + /// @brief high-pressure temperature-scaling parameter + double kinf_B = 0.0; + /// @brief high-pressure exponential factor + double kinf_C = 0.0; + /// @brief Troe F_c parameter + double Fc = 0.6; + /// @brief Troe N parameter + double N = 1.0; + /// @brief A list of reactants + std::vector reactants; + /// @brief A list of products + std::vector products; + /// @brief An identifier, optional, uniqueness not enforced + std::string name; + /// @brief An identifier indicating which gas phase this reaction takes place in + std::string gas_phase; + /// @brief Unknown properties, prefixed with two underscores (__) std::unordered_map unknown_properties; }; struct Reactions { std::vector arrhenius; + std::vector troe; }; struct Mechanism diff --git a/src/parser.cpp b/src/parser.cpp index 3cb9aa9..d8d1d0c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -406,6 +406,109 @@ namespace open_atmos return { status, arrhenius }; } + std::pair ParseTroe(const json& object, const std::vector existing_species) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::Troe troe; + + status = ValidateSchema(object, validation::troe.required_keys, validation::troe.optional_keys); + if (status == ConfigParseStatus::Success) + { + std::vector products{}; + for (const auto& product : object[validation::keys.products]) + { + auto product_parse = ParseReactionComponent(product); + status = product_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + products.push_back(product_parse.second); + } + + std::vector reactants{}; + for (const auto& reactant : object[validation::keys.reactants]) + { + auto reactant_parse = ParseReactionComponent(reactant); + status = reactant_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactants.push_back(reactant_parse.second); + } + + if (object.contains(validation::keys.k0_A)) + { + troe.k0_A = object[validation::keys.k0_A].get(); + } + if (object.contains(validation::keys.k0_B)) + { + troe.k0_B = object[validation::keys.k0_B].get(); + } + if (object.contains(validation::keys.k0_C)) + { + troe.k0_C = object[validation::keys.k0_C].get(); + } + if (object.contains(validation::keys.kinf_A)) + { + troe.kinf_A = object[validation::keys.kinf_A].get(); + } + if (object.contains(validation::keys.kinf_B)) + { + troe.kinf_B = object[validation::keys.kinf_B].get(); + } + if (object.contains(validation::keys.kinf_C)) + { + troe.kinf_C = object[validation::keys.kinf_C].get(); + } + if (object.contains(validation::keys.Fc)) + { + troe.Fc = object[validation::keys.Fc].get(); + } + if (object.contains(validation::keys.N)) + { + troe.N = object[validation::keys.N].get(); + } + + if (object.contains(validation::keys.name)) + { + troe.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::troe.required_keys, validation::troe.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + std::vector requested_species; + for (const auto& spec : products) + { + requested_species.push_back(spec.species_name); + } + for (const auto& spec : reactants) + { + requested_species.push_back(spec.species_name); + } + + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species)) + { + status = ConfigParseStatus::ReactionRequiresUnknownSpecies; + } + + troe.gas_phase = object[validation::keys.gas_phase].get(); + troe.products = products; + troe.reactants = reactants; + troe.unknown_properties = unknown_properties; + } + + return { status, troe }; + } + std::pair ParseReactions(const json& objects, const std::vector existing_species) { ConfigParseStatus status = ConfigParseStatus::Success; @@ -424,6 +527,16 @@ namespace open_atmos } reactions.arrhenius.push_back(arrhenius_parse.second); } + else if (type == validation::keys.Troe_key) + { + auto troe_parse = ParseTroe(object, existing_species); + status = troe_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.troe.push_back(troe_parse.second); + } } return { status, reactions }; diff --git a/test/integration/test_json_parser.cpp b/test/integration/test_json_parser.cpp index c49b2de..ffdc9f3 100644 --- a/test/integration/test_json_parser.cpp +++ b/test/integration/test_json_parser.cpp @@ -10,8 +10,9 @@ TEST(JsonParser, ParsesFullConfiguration) auto [status, mechanism] = parser.Parse(std::string("examples/full_configuration.json")); EXPECT_EQ(status, ConfigParseStatus::Success); EXPECT_EQ(mechanism.name, "Full Configuration"); - EXPECT_EQ(mechanism.species.size(), 10); + EXPECT_EQ(mechanism.species.size(), 11); EXPECT_EQ(mechanism.reactions.arrhenius.size(), 1); + EXPECT_EQ(mechanism.reactions.troe.size(), 1); } TEST(JsonParser, ParserReportsBadFiles) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b0410c8..f5b8b7c 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -9,6 +9,7 @@ include(test_util) create_standard_test(NAME parse_species SOURCES test_parse_species.cpp) create_standard_test(NAME parse_phases SOURCES test_parse_phases.cpp) create_standard_test(NAME parse_arrhenius SOURCES test_parse_arrhenius.cpp) +create_standard_test(NAME parse_troe SOURCES test_parse_troe.cpp) ################################################################################ # Copy test data diff --git a/test/unit/test_parse_troe.cpp b/test/unit/test_parse_troe.cpp new file mode 100644 index 0000000..e3c0024 --- /dev/null +++ b/test/unit/test_parse_troe.cpp @@ -0,0 +1,68 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidTroeReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/troe/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.troe.size(), 2); + + EXPECT_EQ(mechanism.reactions.troe[0].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.troe[0].k0_A, 1.0); + EXPECT_EQ(mechanism.reactions.troe[0].k0_B, 0.0); + EXPECT_EQ(mechanism.reactions.troe[0].k0_C, 0.0); + EXPECT_EQ(mechanism.reactions.troe[0].kinf_A, 1.0); + EXPECT_EQ(mechanism.reactions.troe[0].kinf_B, 0.0); + EXPECT_EQ(mechanism.reactions.troe[0].kinf_C, 0.0); + EXPECT_EQ(mechanism.reactions.troe[0].Fc, 0.6); + EXPECT_EQ(mechanism.reactions.troe[0].N, 1.0); + EXPECT_EQ(mechanism.reactions.troe[0].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.troe[0].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.troe[0].reactants[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.troe[0].products.size(), 1); + EXPECT_EQ(mechanism.reactions.troe[0].products[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.troe[0].products[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.troe[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.troe[0].unknown_properties["__my object"], "{\"a\":1.0}"); + + EXPECT_EQ(mechanism.reactions.troe[1].name, "my troe"); + EXPECT_EQ(mechanism.reactions.troe[1].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.troe[1].k0_A, 32.1); + EXPECT_EQ(mechanism.reactions.troe[1].k0_B, -2.3); + EXPECT_EQ(mechanism.reactions.troe[1].k0_C, 102.3); + EXPECT_EQ(mechanism.reactions.troe[1].kinf_A, 63.4); + EXPECT_EQ(mechanism.reactions.troe[1].kinf_B, -1.3); + EXPECT_EQ(mechanism.reactions.troe[1].kinf_C, 908.5); + EXPECT_EQ(mechanism.reactions.troe[1].Fc, 1.3); + EXPECT_EQ(mechanism.reactions.troe[1].N, 32.1); + EXPECT_EQ(mechanism.reactions.troe[1].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.troe[1].reactants[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.troe[1].reactants[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.troe[1].products.size(), 2); + EXPECT_EQ(mechanism.reactions.troe[1].products[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.troe[1].products[0].coefficient, 0.2); + EXPECT_EQ(mechanism.reactions.troe[1].products[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.troe[1].products[0].unknown_properties["__optional thing"], "\"hello\""); + EXPECT_EQ(mechanism.reactions.troe[1].products[1].species_name, "B"); + EXPECT_EQ(mechanism.reactions.troe[1].products[1].coefficient, 1.2); + EXPECT_EQ(mechanism.reactions.troe[1].products[1].unknown_properties.size(), 0); +} + +TEST(JsonParser, TroeDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/troe/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, TroeDetectsBadReactionComponent) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/troe/bad_reaction_component.json")); + EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json b/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json index 859209d..97197b6 100644 --- a/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json +++ b/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "name": "Mutually Exclusive", + "name": "Bad reaction component", "species": [ { "name": "A" diff --git a/test/unit/unit_configs/reactions/troe/bad_reaction_component.json b/test/unit/unit_configs/reactions/troe/bad_reaction_component.json new file mode 100644 index 0000000..89830d2 --- /dev/null +++ b/test/unit/unit_configs/reactions/troe/bad_reaction_component.json @@ -0,0 +1,37 @@ +{ + "version": "1.0.0", + "name": "Bad reaction component", + "species": [ + { + "name": "A" + }, + { + "name": "B" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ + { + "type": "TROE", + "gas phase": "gas", + "reactants": [ + { + "Species name": "A" + } + ], + "products": [ + { + "species name": "B" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/troe/unknown_species.json b/test/unit/unit_configs/reactions/troe/unknown_species.json new file mode 100644 index 0000000..ac09162 --- /dev/null +++ b/test/unit/unit_configs/reactions/troe/unknown_species.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0", + "name": "Unknown species", + "species": [ + { + "name": "A" + }, + { + "name": "B" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ + { + "__my comment": {}, + "type": "TROE", + "gas phase": "gas", + "reactants": [ + { + "species name": "A" + } + ], + "products": [ + { + "species name": "C" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/troe/valid.json b/test/unit/unit_configs/reactions/troe/valid.json new file mode 100644 index 0000000..d695df6 --- /dev/null +++ b/test/unit/unit_configs/reactions/troe/valid.json @@ -0,0 +1,71 @@ +{ + "version": "1.0.0", + "name": "Valid troe", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B", + "C" + ] + } + ], + "reactions": [ + { + "__my object": {"a": 1.0}, + "type": "TROE", + "gas phase": "gas", + "reactants": [ + { + "species name": "A" + } + ], + "products": [ + { + "species name": "C" + } + ] + }, + { + "type": "TROE", + "gas phase": "gas", + "name": "my troe", + "reactants": [ + { + "species name": "C" + } + ], + "products": [ + { + "species name": "A", + "coefficient": 0.2, + "__optional thing": "hello" + }, + { + "species name": "B", + "coefficient": 1.2 + } + ], + "k0_A": 32.1, + "k0_B": -2.3, + "k0_C": 102.3, + "kinf_A": 63.4, + "kinf_B": -1.3, + "kinf_C": 908.5, + "Fc": 1.3, + "N": 32.1 + } + ] +} \ No newline at end of file