From e3da19494b17c477f9d6e32feb244dc5f2b06e2a Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Fri, 19 Jan 2024 14:55:52 -0600 Subject: [PATCH 1/6] parsing condensed phase photolysis --- examples/full_configuration.json | 4 - .../mechanism_configuration/validation.hpp | 13 ++ include/open_atmos/types.hpp | 29 ++++- src/parser.cpp | 118 ++++++++++++++++++ test/integration/test_json_parser.cpp | 1 + test/unit/CMakeLists.txt | 1 + .../test_parse_condensed_phase_photolysis.cpp | 79 ++++++++++++ .../bad_reaction_component.json | 48 +++++++ .../missing_aerosol_phase_water.json | 48 +++++++ .../missing_phase.json | 38 ++++++ .../more_than_one_reactant.json | 50 ++++++++ .../species_not_in_aerosol_phase.json | 51 ++++++++ .../unknown_species.json | 40 ++++++ .../condensed_phase_photolysis/valid.json | 68 ++++++++++ .../photolysis/more_than_one_reactant.json | 2 +- 15 files changed, 580 insertions(+), 10 deletions(-) create mode 100644 test/unit/test_parse_condensed_phase_photolysis.cpp create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/bad_reaction_component.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_aerosol_phase_water.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_phase.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/more_than_one_reactant.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/species_not_in_aerosol_phase.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/unknown_species.json create mode 100644 test/unit/unit_configs/reactions/condensed_phase_photolysis/valid.json diff --git a/examples/full_configuration.json b/examples/full_configuration.json index 7c94eaf..104ebbb 100644 --- a/examples/full_configuration.json +++ b/examples/full_configuration.json @@ -207,10 +207,6 @@ { "species name": "H2O2_aq", "coefficient": 1 - }, - { - "species name": "H2O_aq", - "coefficient": 1 } ], "products": [ diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index e9c7a6b..2736fc8 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -101,6 +101,13 @@ namespace open_atmos const std::string Photolysis_key = "PHOTOLYSIS"; const std::string scaling_factor = "scaling factor"; + // Condensed Phae Photolysis + const std::string CondensedPhasePhotolysis_key = "CONDENSED_PHASE_PHOTOLYSIS"; + // also + // scaling factor + // aerosol phase + // aerosol-phase water + // Emissions const std::string Emission_key = "EMISSION"; // also scaling factor @@ -185,6 +192,12 @@ namespace open_atmos const std::vector optional_keys{ keys.name, keys.scaling_factor }; } photolysis; + struct CondensedPhasePhotolysis + { + const std::vector required_keys{ keys.reactants, keys.products, keys.type, keys.aerosol_phase, keys.aerosol_phase_water }; + const std::vector optional_keys{ keys.name, keys.scaling_factor }; + } condensed_phase_photolysis; + struct Emission { const std::vector required_keys{ keys.products, keys.type, keys.gas_phase }; diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 7ff2b37..4b2c9ba 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -195,6 +195,24 @@ namespace open_atmos std::unordered_map unknown_properties; }; + struct CondensedPhasePhotolysis + { + /// @brief Scaling factor to apply to user-provided rate constants + double scaling_factor_{ 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 aerosol phase this reaction takes place in + std::string aerosol_phase; + /// @brief An identifier indicating the species label of aqueous phase water + std::string aerosol_phase_water; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + struct Emission { /// @brief Scaling factor to apply to user-provided rate constants @@ -226,14 +244,15 @@ namespace open_atmos struct Reactions { std::vector arrhenius; - std::vector condensed_phase_arrhenius; - std::vector troe; std::vector branched; - std::vector tunneling; - std::vector surface; - std::vector photolysis; + std::vector condensed_phase_arrhenius; + std::vector condensed_phase_photolysis; std::vector emission; std::vector first_order_loss; + std::vector photolysis; + std::vector surface; + std::vector troe; + std::vector tunneling; }; struct Mechanism diff --git a/src/parser.cpp b/src/parser.cpp index 615ebb0..7fe3aa3 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1078,6 +1078,114 @@ namespace open_atmos return { status, photolysis }; } + /// @brief Parses a photolysis reaction + /// @param object A json object that should have information containing arrhenius parameters + /// @param existing_species A list of species configured in a mechanism + /// @param existing_phases A list of phases configured in a mechanism + /// @return A pair indicating parsing success and a struct of Photolysis parameters + std::pair + ParseCondensedPhasePhotolysis(const json& object, const std::vector existing_species, const std::vector existing_phases) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::CondensedPhasePhotolysis condensed_phase_photolysis; + + status = ValidateSchema(object, validation::condensed_phase_photolysis.required_keys, validation::photolysis.optional_keys); + if (status == ConfigParseStatus::Success) + { + std::vector products{}; + for (const auto& reactant : object[validation::keys.products]) + { + auto product_parse = ParseReactionComponent(reactant); + 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.scaling_factor)) + { + condensed_phase_photolysis.scaling_factor_ = object[validation::keys.scaling_factor].get(); + } + + if (object.contains(validation::keys.name)) + { + condensed_phase_photolysis.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::condensed_phase_photolysis.required_keys, validation::photolysis.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + std::string aerosol_phase = object[validation::keys.aerosol_phase].get(); + std::string aerosol_phase_water = object[validation::keys.aerosol_phase_water].get(); + + 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); + } + requested_species.push_back(aerosol_phase_water); + + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species)) + { + status = ConfigParseStatus::ReactionRequiresUnknownSpecies; + } + + if (status == ConfigParseStatus::Success && reactants.size() > 1) + { + status = ConfigParseStatus::TooManyReactionComponents; + } + + auto phase_it = std::find_if( + existing_phases.begin(), existing_phases.end(), [&aerosol_phase](const types::Phase& phase) { return phase.name == aerosol_phase; }); + + if (phase_it != existing_phases.end()) + { + // check if all of the species for this reaction are actually in the aerosol phase + std::vector aerosol_phase_species = { (*phase_it).species.begin(), (*phase_it).species.end() }; + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, aerosol_phase_species)) + { + status = ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase; + } + } + else + { + status = ConfigParseStatus::UnknownPhase; + } + + condensed_phase_photolysis.aerosol_phase = aerosol_phase; + condensed_phase_photolysis.aerosol_phase_water = aerosol_phase_water; + condensed_phase_photolysis.products = products; + condensed_phase_photolysis.reactants = reactants; + condensed_phase_photolysis.unknown_properties = unknown_properties; + } + + return { status, condensed_phase_photolysis }; + } + /// @brief Parses a emission reaction /// @param object A json object that should have information containing arrhenius parameters /// @param existing_species A list of species configured in a mechanism @@ -1309,6 +1417,16 @@ namespace open_atmos } reactions.photolysis.push_back(photolysis_parse.second); } + else if (type == validation::keys.CondensedPhasePhotolysis_key) + { + auto condensed_phase_photolysis_parse = ParseCondensedPhasePhotolysis(object, existing_species, existing_phases); + status = condensed_phase_photolysis_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.condensed_phase_photolysis.push_back(condensed_phase_photolysis_parse.second); + } else if (type == validation::keys.Emission_key) { auto emission_parse = ParseEmission(object, existing_species, existing_phases); diff --git a/test/integration/test_json_parser.cpp b/test/integration/test_json_parser.cpp index d18f34c..09c419d 100644 --- a/test/integration/test_json_parser.cpp +++ b/test/integration/test_json_parser.cpp @@ -19,6 +19,7 @@ TEST(JsonParser, ParsesFullConfiguration) EXPECT_EQ(mechanism.reactions.tunneling.size(), 1); EXPECT_EQ(mechanism.reactions.surface.size(), 1); EXPECT_EQ(mechanism.reactions.photolysis.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis.size(), 1); EXPECT_EQ(mechanism.reactions.emission.size(), 1); EXPECT_EQ(mechanism.reactions.first_order_loss.size(), 1); } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index bc64576..fd94572 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -9,6 +9,7 @@ include(test_util) create_standard_test(NAME parse_arrhenius SOURCES test_parse_arrhenius.cpp) create_standard_test(NAME parse_branched SOURCES test_parse_branched.cpp) create_standard_test(NAME parse_condensed_phase_arrhenius SOURCES test_parse_condensed_phase_arrhenius.cpp) +create_standard_test(NAME parse_condensed_phase_photolysis SOURCES test_parse_condensed_phase_photolysis.cpp) create_standard_test(NAME parse_emission SOURCES test_parse_emission.cpp) create_standard_test(NAME parse_first_order_loss SOURCES test_parse_first_order_loss.cpp) create_standard_test(NAME parse_phases SOURCES test_parse_phases.cpp) diff --git a/test/unit/test_parse_condensed_phase_photolysis.cpp b/test/unit/test_parse_condensed_phase_photolysis.cpp new file mode 100644 index 0000000..acaf4eb --- /dev/null +++ b/test/unit/test_parse_condensed_phase_photolysis.cpp @@ -0,0 +1,79 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidCondensedPhasePhotolysisReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis.size(), 2); + + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].aerosol_phase, "aqueous aerosol"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].name, "my condensed phase photolysis"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].scaling_factor_, 12.3); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].reactants[0].species_name, "B"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].reactants[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].products.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].products[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].products[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[0].unknown_properties["__comment"], "\"hi\""); + + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].aerosol_phase, "aqueous aerosol"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].scaling_factor_, 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].reactants[0].species_name, "B"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].reactants[0].coefficient, 1.2); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].products.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].products[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.condensed_phase_photolysis[1].products[0].coefficient, 0.2); +} + +TEST(JsonParser, CondensedPhasePhotolysisDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, CondensedPhasePhotolysisDetectsBadReactionComponent) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/bad_reaction_component.json")); + EXPECT_EQ(status, ConfigParseStatus::InvalidKey); +} + +TEST(JsonParser, CondensedPhasePhotolysisDetectsUnknownPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/missing_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); +} + +TEST(JsonParser, CondensedPhasePhotolysisDoesNotAcceptMoreThanOneReactant) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/more_than_one_reactant.json")); + EXPECT_EQ(status, ConfigParseStatus::TooManyReactionComponents); +} + +TEST(JsonParser, CondensedPhasePhotolysisDetectsWhenRequestedSpeciesAreNotInAerosolPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/species_not_in_aerosol_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase); +} + +TEST(JsonParser, CondensedPhaseArrheniusDetectsUnknownAerosolPhaseWater) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_photolysis/missing_aerosol_phase_water.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/bad_reaction_component.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/bad_reaction_component.json new file mode 100644 index 0000000..de78eb2 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/bad_reaction_component.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0", + "name": "Bad reaction component", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A", + "Coefficient": 1.2 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_aerosol_phase_water.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_aerosol_phase_water.json new file mode 100644 index 0000000..659654d --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_aerosol_phase_water.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0", + "name": "Missing condensed phase arrhenius aerosol phase water", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_a", + "reactants": [ + { + "species name": "B", + "coefficient": 1.2 + } + ], + "products": [ + { + "species name": "C", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_phase.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_phase.json new file mode 100644 index 0000000..0de7a16 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/missing_phase.json @@ -0,0 +1,38 @@ +{ + "version": "1.0.0", + "name": "Missing phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "B", + "coefficient": 1.2 + } + ], + "products": [ + { + "species name": "C", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/more_than_one_reactant.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/more_than_one_reactant.json new file mode 100644 index 0000000..54530f1 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/more_than_one_reactant.json @@ -0,0 +1,50 @@ +{ + "version": "1.0.0", + "name": "more than one reactant", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A" + }, + { + "species name": "B" + } + ], + "products": [ + { + "species name": "C", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/species_not_in_aerosol_phase.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/species_not_in_aerosol_phase.json new file mode 100644 index 0000000..ad9ad21 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/species_not_in_aerosol_phase.json @@ -0,0 +1,51 @@ +{ + "version": "1.0.0", + "name": "Condensed phase photolysis using species not in its requested aerosol phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A", + "coefficient": 1 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 1.2 + }, + { + "species name": "C", + "coefficient": 0.3 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/unknown_species.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/unknown_species.json new file mode 100644 index 0000000..49a1124 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/unknown_species.json @@ -0,0 +1,40 @@ +{ + "version": "1.0.0", + "name": "Unknown species", + "species": [ + { + "name": "A" + }, + { + "name": "B" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A", + "coefficient": 1.2 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/condensed_phase_photolysis/valid.json b/test/unit/unit_configs/reactions/condensed_phase_photolysis/valid.json new file mode 100644 index 0000000..1a907f8 --- /dev/null +++ b/test/unit/unit_configs/reactions/condensed_phase_photolysis/valid.json @@ -0,0 +1,68 @@ +{ + "version": "1.0.0", + "name": "Valid surface", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "__comment": "hi", + "reactants": [ + { + "species name": "B", + "coefficient": 1 + } + ], + "products": [ + { + "species name": "C", + "coefficient": 1 + } + ], + "name": "my condensed phase photolysis", + "scaling factor": 12.3 + }, + { + "type": "CONDENSED_PHASE_PHOTOLYSIS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "B", + "coefficient": 1.2 + } + ], + "products": [ + { + "species name": "C", + "coefficient": 0.2 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/photolysis/more_than_one_reactant.json b/test/unit/unit_configs/reactions/photolysis/more_than_one_reactant.json index 5a77825..a036923 100644 --- a/test/unit/unit_configs/reactions/photolysis/more_than_one_reactant.json +++ b/test/unit/unit_configs/reactions/photolysis/more_than_one_reactant.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "name": "Valid surface", + "name": "more than one reactant", "species": [ { "name": "A" From 8d2617176af85fc8ef4d94ee24ca4acba0497147 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Fri, 19 Jan 2024 16:06:46 -0600 Subject: [PATCH 2/6] parsing henrys law --- .../mechanism_configuration/validation.hpp | 15 +++ include/open_atmos/types.hpp | 19 ++++ src/parser.cpp | 92 ++++++++++++++++ test/integration/test_json_parser.cpp | 11 +- test/unit/CMakeLists.txt | 1 + test/unit/test_parse_henrys_law.cpp | 59 ++++++++++ .../missing_aerosol_phase_water.json | 46 ++++++++ .../reactions/henrys_law/missing_phase.json | 27 +++++ .../species_not_in_aerosol_phase.json | 101 ++++++++++++++++++ .../reactions/henrys_law/unknown_species.json | 40 +++++++ .../reactions/henrys_law/valid.json | 50 +++++++++ .../reactions/photolysis/valid.json | 2 +- 12 files changed, 457 insertions(+), 6 deletions(-) create mode 100644 test/unit/test_parse_henrys_law.cpp create mode 100644 test/unit/unit_configs/reactions/henrys_law/missing_aerosol_phase_water.json create mode 100644 test/unit/unit_configs/reactions/henrys_law/missing_phase.json create mode 100644 test/unit/unit_configs/reactions/henrys_law/species_not_in_aerosol_phase.json create mode 100644 test/unit/unit_configs/reactions/henrys_law/unknown_species.json create mode 100644 test/unit/unit_configs/reactions/henrys_law/valid.json diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index e9c7a6b..5c8abb2 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -109,6 +109,15 @@ namespace open_atmos const std::string FirstOrderLoss_key = "FIRST_ORDER_LOSS"; // also scaling factor + // Henry's Law Phase Transfer + const std::string HenrysLaw_key = "HL_PHASE_TRANSFER"; + const std::string gas_phase_species = "gas-phase species"; + const std::string aerosol_phase_species = "aerosol-phase species"; + // also + // gas phase + // aerosol phase + // aerosol-phase water + } keys; struct Configuration @@ -197,6 +206,12 @@ namespace open_atmos const std::vector optional_keys{ keys.name, keys.scaling_factor }; } first_order_loss; + struct HenrysLaw + { + const std::vector required_keys{ keys.type, keys.gas_phase, keys.gas_phase_species, keys.aerosol_phase, keys.aerosol_phase_species, keys.aerosol_phase_water }; + const std::vector optional_keys{ keys.name }; + } henrys_law; + struct Mechanism { const std::vector required_keys{}; diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 7ff2b37..c85deb3 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -223,6 +223,24 @@ namespace open_atmos std::unordered_map unknown_properties; }; + struct HenrysLaw + { + /// @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 An identifier indicating which gas phase species this reaction involves + std::string gas_phase_species; + /// @brief An identifier indicating which aerosol phase this reaction takes place in + std::string aerosol_phase; + /// @brief An identifier indicating the species label of aqueous phase water + std::string aerosol_phase_water; + /// @brief An identifier indicating which aerosol phase species this reaction involves + std::string aerosol_phase_species; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + struct Reactions { std::vector arrhenius; @@ -234,6 +252,7 @@ namespace open_atmos std::vector photolysis; std::vector emission; std::vector first_order_loss; + std::vector henrys_law; }; struct Mechanism diff --git a/src/parser.cpp b/src/parser.cpp index 3489667..c047bf2 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1224,6 +1224,88 @@ namespace open_atmos return { status, first_order_loss }; } + /// @brief Parses a first order loss reaction + /// @param object A json object that should have information containing arrhenius parameters + /// @param existing_species A list of species configured in a mechanism + /// @param existing_phases A list of phases configured in a mechanism + /// @return A pair indicating parsing success and a struct of First Order Loss parameters + std::pair + ParseHenrysLaw(const json& object, const std::vector existing_species, const std::vector existing_phases) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::HenrysLaw henrys_law; + + status = ValidateSchema(object, validation::henrys_law.required_keys, validation::henrys_law.optional_keys); + if (status == ConfigParseStatus::Success) + { + std::string gas_phase = object[validation::keys.gas_phase].get(); + std::string gas_phase_species = object[validation::keys.gas_phase_species].get(); + std::string aerosol_phase = object[validation::keys.aerosol_phase].get(); + std::string aerosol_phase_species = object[validation::keys.aerosol_phase_species].get(); + std::string aerosol_phase_water = object[validation::keys.aerosol_phase_water].get(); + + if (object.contains(validation::keys.name)) + { + henrys_law.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::henrys_law.required_keys, validation::henrys_law.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; + requested_species.push_back(gas_phase_species); + requested_species.push_back(aerosol_phase_species); + requested_species.push_back(aerosol_phase_water); + + std::vector requested_aerosol_species; + requested_aerosol_species.push_back(aerosol_phase_species); + requested_aerosol_species.push_back(aerosol_phase_water); + + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species)) + { + status = ConfigParseStatus::ReactionRequiresUnknownSpecies; + } + + auto it = std::find_if(existing_phases.begin(), existing_phases.end(), [&gas_phase](const auto& phase) { return phase.name == gas_phase; }); + if (status == ConfigParseStatus::Success && it == existing_phases.end()) + { + status = ConfigParseStatus::UnknownPhase; + } + + auto phase_it = std::find_if( + existing_phases.begin(), existing_phases.end(), [&aerosol_phase](const types::Phase& phase) { return phase.name == aerosol_phase; }); + + if (phase_it != existing_phases.end()) + { + // check if all of the species for this reaction are actually in the aerosol phase + std::vector aerosol_phase_species = { (*phase_it).species.begin(), (*phase_it).species.end() }; + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_aerosol_species, aerosol_phase_species)) + { + status = ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase; + } + } + else + { + status = ConfigParseStatus::UnknownPhase; + } + + henrys_law.gas_phase = gas_phase; + henrys_law.gas_phase_species = gas_phase_species; + henrys_law.aerosol_phase = aerosol_phase; + henrys_law.aerosol_phase_species = aerosol_phase_species; + henrys_law.aerosol_phase_water = aerosol_phase_water; + henrys_law.unknown_properties = unknown_properties; + } + + return { status, henrys_law }; + } + /// @brief Parses all reactions /// @param objects A json object that should contain only valid reactions /// @param existing_species A list of spcecies configured for a mechanism @@ -1328,6 +1410,16 @@ namespace open_atmos } reactions.first_order_loss.push_back(first_order_loss_parse.second); } + else if (type == validation::keys.HenrysLaw_key) + { + auto henrys_law_parse = ParseHenrysLaw(object, existing_species, existing_phases); + status = henrys_law_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.henrys_law.push_back(henrys_law_parse.second); + } } return { status, reactions }; diff --git a/test/integration/test_json_parser.cpp b/test/integration/test_json_parser.cpp index d18f34c..b51d1a9 100644 --- a/test/integration/test_json_parser.cpp +++ b/test/integration/test_json_parser.cpp @@ -13,14 +13,15 @@ TEST(JsonParser, ParsesFullConfiguration) EXPECT_EQ(mechanism.species.size(), 11); EXPECT_EQ(mechanism.phases.size(), 4); EXPECT_EQ(mechanism.reactions.arrhenius.size(), 2); - EXPECT_EQ(mechanism.reactions.condensed_phase_arrhenius.size(), 2); - EXPECT_EQ(mechanism.reactions.troe.size(), 1); EXPECT_EQ(mechanism.reactions.branched.size(), 1); - EXPECT_EQ(mechanism.reactions.tunneling.size(), 1); - EXPECT_EQ(mechanism.reactions.surface.size(), 1); - EXPECT_EQ(mechanism.reactions.photolysis.size(), 1); + EXPECT_EQ(mechanism.reactions.condensed_phase_arrhenius.size(), 2); EXPECT_EQ(mechanism.reactions.emission.size(), 1); EXPECT_EQ(mechanism.reactions.first_order_loss.size(), 1); + EXPECT_EQ(mechanism.reactions.henrys_law.size(), 1); + EXPECT_EQ(mechanism.reactions.photolysis.size(), 1); + EXPECT_EQ(mechanism.reactions.surface.size(), 1); + EXPECT_EQ(mechanism.reactions.troe.size(), 1); + EXPECT_EQ(mechanism.reactions.tunneling.size(), 1); } TEST(JsonParser, ParserReportsBadFiles) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index bc64576..2f03a9d 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -11,6 +11,7 @@ create_standard_test(NAME parse_branched SOURCES test_parse_branched.cpp) create_standard_test(NAME parse_condensed_phase_arrhenius SOURCES test_parse_condensed_phase_arrhenius.cpp) create_standard_test(NAME parse_emission SOURCES test_parse_emission.cpp) create_standard_test(NAME parse_first_order_loss SOURCES test_parse_first_order_loss.cpp) +create_standard_test(NAME parse_henrys_law SOURCES test_parse_henrys_law.cpp) create_standard_test(NAME parse_phases SOURCES test_parse_phases.cpp) create_standard_test(NAME parse_photolysis SOURCES test_parse_photolysis.cpp) create_standard_test(NAME parse_species SOURCES test_parse_species.cpp) diff --git a/test/unit/test_parse_henrys_law.cpp b/test/unit/test_parse_henrys_law.cpp new file mode 100644 index 0000000..2e0aabd --- /dev/null +++ b/test/unit/test_parse_henrys_law.cpp @@ -0,0 +1,59 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidHenrysLawReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/henrys_law/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.henrys_law.size(), 2); + + EXPECT_EQ(mechanism.reactions.henrys_law[0].name, "my henry's law"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].gas_phase_species, "A"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].aerosol_phase, "aqueous aerosol"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].aerosol_phase_species, "B"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.henrys_law[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.henrys_law[0].unknown_properties["__comment"], "\"hi\""); + + EXPECT_EQ(mechanism.reactions.henrys_law[1].name, ""); + EXPECT_EQ(mechanism.reactions.henrys_law[1].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.henrys_law[1].gas_phase_species, "A"); + EXPECT_EQ(mechanism.reactions.henrys_law[1].aerosol_phase, "aqueous aerosol"); + EXPECT_EQ(mechanism.reactions.henrys_law[1].aerosol_phase_species, "B"); + EXPECT_EQ(mechanism.reactions.henrys_law[1].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.henrys_law[1].unknown_properties.size(), 0); +} + +TEST(JsonParser, HenrysLawDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/henrys_law/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, HenrysLawDetectsUnknownPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/henrys_law/missing_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); +} + +TEST(JsonParser, HenrysLawDetectsUnknownAerosolPhaseWater) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_arrhenius/missing_aerosol_phase_water.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, HenrysLawDetectsWhenRequestedSpeciesAreNotInAerosolPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/condensed_phase_arrhenius/species_not_in_aerosol_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase); +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/henrys_law/missing_aerosol_phase_water.json b/test/unit/unit_configs/reactions/henrys_law/missing_aerosol_phase_water.json new file mode 100644 index 0000000..f1e6976 --- /dev/null +++ b/test/unit/unit_configs/reactions/henrys_law/missing_aerosol_phase_water.json @@ -0,0 +1,46 @@ +{ + "version": "1.0.0", + "name": "Missing condensed phase arrhenius aerosol phase water", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_ARRHENIUS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_a", + "reactants": [ + { + "species name": "A" + } + ], + "products": [ + { + "species name": "C" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/henrys_law/missing_phase.json b/test/unit/unit_configs/reactions/henrys_law/missing_phase.json new file mode 100644 index 0000000..ca368c9 --- /dev/null +++ b/test/unit/unit_configs/reactions/henrys_law/missing_phase.json @@ -0,0 +1,27 @@ +{ + "version": "1.0.0", + "name": "Missing phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ ], + "reactions": [ + { + "type": "HL_PHASE_TRANSFER", + "gas phase": "gas", + "gas-phase species": "H2O2", + "aerosol phase": "aqueous aerosol", + "aerosol-phase species": "H2O2_aq", + "aerosol-phase water": "H2O_aq", + "name": "my henry's law" + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/henrys_law/species_not_in_aerosol_phase.json b/test/unit/unit_configs/reactions/henrys_law/species_not_in_aerosol_phase.json new file mode 100644 index 0000000..b7a8795 --- /dev/null +++ b/test/unit/unit_configs/reactions/henrys_law/species_not_in_aerosol_phase.json @@ -0,0 +1,101 @@ +{ + "version": "1.0.0", + "name": "Condensed phase arrhenius using species not in its requested aerosol phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aqueous aerosol", + "species": [ + "A", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "CONDENSED_PHASE_ARRHENIUS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A", + "coefficient": 1 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 1.2 + }, + { + "species name": "C", + "coefficient": 0.3 + } + ], + "A": 32.1, + "B": -2.3, + "C": 102.3, + "D": 63.4, + "E": -1.3, + "name": "my arrhenius", + "__solver_param": 0.1 + }, + { + "type": "CONDENSED_PHASE_ARRHENIUS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A", + "coefficient": 2 + }, + { + "species name": "B", + "coefficient": 0.1 + } + ], + "products": [ + { + "species name": "C", + "coefficient": 0.5, + "__optional thing": "hello" + } + ], + "A": 3.1, + "B": -0.3, + "C": 12.3, + "D": 6.4, + "E": -0.3, + "name": "my arrhenius2" + }, + { + "type": "CONDENSED_PHASE_ARRHENIUS", + "aerosol phase": "aqueous aerosol", + "aerosol-phase water": "H2O_aq", + "reactants": [ + { + "species name": "A" + } + ], + "products": [ + { + "species name": "C" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/henrys_law/unknown_species.json b/test/unit/unit_configs/reactions/henrys_law/unknown_species.json new file mode 100644 index 0000000..b3a1f90 --- /dev/null +++ b/test/unit/unit_configs/reactions/henrys_law/unknown_species.json @@ -0,0 +1,40 @@ +{ + "version": "1.0.0", + "name": "Unknown species", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A" + ] + }, + { + "name": "aqueous aerosol", + "species": [ + "B", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "HL_PHASE_TRANSFER", + "gas phase": "gas", + "gas-phase species": "C", + "aerosol phase": "aqueous aerosol", + "aerosol-phase species": "B", + "aerosol-phase water": "H2O_aq" + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/henrys_law/valid.json b/test/unit/unit_configs/reactions/henrys_law/valid.json new file mode 100644 index 0000000..92001f3 --- /dev/null +++ b/test/unit/unit_configs/reactions/henrys_law/valid.json @@ -0,0 +1,50 @@ +{ + "version": "1.0.0", + "name": "Valid surface", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A" + ] + }, + { + "name": "aqueous aerosol", + "species": [ + "B", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "HL_PHASE_TRANSFER", + "gas phase": "gas", + "gas-phase species": "A", + "aerosol phase": "aqueous aerosol", + "aerosol-phase species": "B", + "aerosol-phase water": "H2O_aq", + "name": "my henry's law", + "__comment": "hi" + }, + { + "type": "HL_PHASE_TRANSFER", + "gas phase": "gas", + "gas-phase species": "A", + "aerosol phase": "aqueous aerosol", + "aerosol-phase species": "B", + "aerosol-phase water": "H2O_aq" + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/photolysis/valid.json b/test/unit/unit_configs/reactions/photolysis/valid.json index 3f1c047..91c2de4 100644 --- a/test/unit/unit_configs/reactions/photolysis/valid.json +++ b/test/unit/unit_configs/reactions/photolysis/valid.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "name": "Valid surface", + "name": "Valid photolysis", "species": [ { "name": "A" From 027a7ed4b420969653badadaf2e158bc2c6b7236 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Fri, 19 Jan 2024 16:31:05 -0600 Subject: [PATCH 3/6] start wet deposition --- .../mechanism_configuration/validation.hpp | 13 +++++ include/open_atmos/types.hpp | 15 ++++++ src/parser.cpp | 10 ++++ test/unit/CMakeLists.txt | 1 + test/unit/test_parse_wet_deposition.cpp | 47 +++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 test/unit/test_parse_wet_deposition.cpp diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index e9c7a6b..e14369d 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -109,6 +109,13 @@ namespace open_atmos const std::string FirstOrderLoss_key = "FIRST_ORDER_LOSS"; // also scaling factor + // Wet Deposition + const std::string WetDeposition_key = "WET_DEPOSITION"; + // also + // scaling factor + // aerosol phase + // gas phase + } keys; struct Configuration @@ -197,6 +204,12 @@ namespace open_atmos const std::vector optional_keys{ keys.name, keys.scaling_factor }; } first_order_loss; + struct WetDeposition + { + const std::vector required_keys{ keys.gas_phase, keys.aerosol_phase, keys.type }; + const std::vector optional_keys{ keys.name, keys.scaling_factor }; + } wet_deposition; + struct Mechanism { const std::vector required_keys{}; diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 7ff2b37..bb6fd0b 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -223,6 +223,20 @@ namespace open_atmos std::unordered_map unknown_properties; }; + struct WetDeposition + { + /// @brief Scaling factor to apply to user-provided rate constants + double scaling_factor_{ 1.0 }; + /// @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 An identifier indicating which aerosol phase this reaction takes place in + std::string aerosol_phase; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + struct Reactions { std::vector arrhenius; @@ -234,6 +248,7 @@ namespace open_atmos std::vector photolysis; std::vector emission; std::vector first_order_loss; + std::vector wet_deposition; }; struct Mechanism diff --git a/src/parser.cpp b/src/parser.cpp index 3489667..96c7763 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1328,6 +1328,16 @@ namespace open_atmos } reactions.first_order_loss.push_back(first_order_loss_parse.second); } + else if (type == validation::keys.WetDeposition_key) + { + auto wet_deposition_parse = ParseWetDeposition(object, existing_species, existing_phases); + status = wet_deposition_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.wet_deposition.push_back(wet_deposition_parse.second); + } } return { status, reactions }; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index bc64576..eb0ffe4 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -17,6 +17,7 @@ create_standard_test(NAME parse_species SOURCES test_parse_species.cpp) create_standard_test(NAME parse_surface SOURCES test_parse_surface.cpp) create_standard_test(NAME parse_troe SOURCES test_parse_troe.cpp) create_standard_test(NAME parse_tunneling SOURCES test_parse_tunneling.cpp) +create_standard_test(NAME parse_wet_deposition SOURCES test_parse_wet_deposition.cpp) ################################################################################ # Copy test data diff --git a/test/unit/test_parse_wet_deposition.cpp b/test/unit/test_parse_wet_deposition.cpp new file mode 100644 index 0000000..a5b18b6 --- /dev/null +++ b/test/unit/test_parse_wet_deposition.cpp @@ -0,0 +1,47 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidWetDepositionReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.wet_deposition.size(), 1); + + EXPECT_EQ(mechanism.reactions.wet_deposition[0].name, "my arrhenius"); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].unknown_properties["__solver_param"], "0.1"); +} + +TEST(JsonParser, WetDepositionDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, WetDepositionDetectsMutuallyExclusiveOptions) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/mutually_exclusive.json")); + EXPECT_EQ(status, ConfigParseStatus::MutuallyExclusiveOption); +} + +TEST(JsonParser, WetDepositionDetectsBadReactionComponent) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/bad_reaction_component.json")); + EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +} + +TEST(JsonParser, WetDepositionDetectsUnknownPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/missing_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); +} \ No newline at end of file From bd362e65e8fdd2511b405a2af5a83f71c46532eb Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Mon, 22 Jan 2024 08:55:05 -0600 Subject: [PATCH 4/6] parsing wet deposition --- examples/full_configuration.json | 1 - .../mechanism_configuration/validation.hpp | 3 +- include/open_atmos/types.hpp | 10 ++-- src/parser.cpp | 55 ++++++++++++++++++- test/unit/test_parse_emission.cpp | 4 +- test/unit/test_parse_first_order_loss.cpp | 4 +- test/unit/test_parse_photolysis.cpp | 4 +- test/unit/test_parse_wet_deposition.cpp | 32 +++-------- .../wet_deposition/missing_phase.json | 24 ++++++++ .../reactions/wet_deposition/valid.json | 39 +++++++++++++ 10 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 test/unit/unit_configs/reactions/wet_deposition/missing_phase.json create mode 100644 test/unit/unit_configs/reactions/wet_deposition/valid.json diff --git a/examples/full_configuration.json b/examples/full_configuration.json index 7c94eaf..04b69f8 100644 --- a/examples/full_configuration.json +++ b/examples/full_configuration.json @@ -362,7 +362,6 @@ }, { "type": "WET_DEPOSITION", - "gas phase": "gas", "aerosol phase": "cloud", "name": "rxn cloud", "scaling factor": 12.3 diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index e14369d..12bb7c8 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -114,7 +114,6 @@ namespace open_atmos // also // scaling factor // aerosol phase - // gas phase } keys; @@ -206,7 +205,7 @@ namespace open_atmos struct WetDeposition { - const std::vector required_keys{ keys.gas_phase, keys.aerosol_phase, keys.type }; + const std::vector required_keys{ keys.aerosol_phase, keys.type }; const std::vector optional_keys{ keys.name, keys.scaling_factor }; } wet_deposition; diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index bb6fd0b..fbfda1e 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -182,7 +182,7 @@ namespace open_atmos struct Photolysis { /// @brief Scaling factor to apply to user-provided rate constants - double scaling_factor_{ 1.0 }; + double scaling_factor{ 1.0 }; /// @brief A list of reactants std::vector reactants; /// @brief A list of products @@ -198,7 +198,7 @@ namespace open_atmos struct Emission { /// @brief Scaling factor to apply to user-provided rate constants - double scaling_factor_{ 1.0 }; + double scaling_factor{ 1.0 }; /// @brief A list of products std::vector products; /// @brief An identifier, optional, uniqueness not enforced @@ -212,7 +212,7 @@ namespace open_atmos struct FirstOrderLoss { /// @brief Scaling factor to apply to user-provided rate constants - double scaling_factor_{ 1.0 }; + double scaling_factor{ 1.0 }; /// @brief A list of reactants std::vector reactants; /// @brief An identifier, optional, uniqueness not enforced @@ -226,11 +226,9 @@ namespace open_atmos struct WetDeposition { /// @brief Scaling factor to apply to user-provided rate constants - double scaling_factor_{ 1.0 }; + double scaling_factor{ 1.0 }; /// @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 An identifier indicating which aerosol phase this reaction takes place in std::string aerosol_phase; /// @brief Unknown properties, prefixed with two underscores (__) diff --git a/src/parser.cpp b/src/parser.cpp index 96c7763..0c378ca 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1024,7 +1024,7 @@ namespace open_atmos if (object.contains(validation::keys.scaling_factor)) { - photolysis.scaling_factor_ = object[validation::keys.scaling_factor].get(); + photolysis.scaling_factor = object[validation::keys.scaling_factor].get(); } if (object.contains(validation::keys.name)) @@ -1105,7 +1105,7 @@ namespace open_atmos if (object.contains(validation::keys.scaling_factor)) { - emission.scaling_factor_ = object[validation::keys.scaling_factor].get(); + emission.scaling_factor = object[validation::keys.scaling_factor].get(); } if (object.contains(validation::keys.name)) @@ -1176,7 +1176,7 @@ namespace open_atmos if (object.contains(validation::keys.scaling_factor)) { - first_order_loss.scaling_factor_ = object[validation::keys.scaling_factor].get(); + first_order_loss.scaling_factor = object[validation::keys.scaling_factor].get(); } if (object.contains(validation::keys.name)) @@ -1224,6 +1224,55 @@ namespace open_atmos return { status, first_order_loss }; } + /// @brief Parses a wet deposition reaction + /// @param object A json object that should have information containing arrhenius parameters + /// @param existing_species A list of species configured in a mechanism + /// @param existing_phases A list of phases configured in a mechanism + /// @return A pair indicating parsing success and a struct of First Order Loss parameters + std::pair + ParseWetDeposition(const json& object, const std::vector existing_species, const std::vector existing_phases) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::WetDeposition wet_deposition; + + status = ValidateSchema(object, validation::wet_deposition.required_keys, validation::wet_deposition.optional_keys); + if (status == ConfigParseStatus::Success) + { + if (object.contains(validation::keys.scaling_factor)) + { + wet_deposition.scaling_factor = object[validation::keys.scaling_factor].get(); + } + + if (object.contains(validation::keys.name)) + { + wet_deposition.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::wet_deposition.required_keys, validation::wet_deposition.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + std::string aerosol_phase = object[validation::keys.aerosol_phase].get(); + + // check if aerosol phase exists + auto it = std::find_if(existing_phases.begin(), existing_phases.end(), [&aerosol_phase](const auto& phase) { return phase.name == aerosol_phase; }); + if (status == ConfigParseStatus::Success && it == existing_phases.end()) + { + status = ConfigParseStatus::UnknownPhase; + } + + wet_deposition.aerosol_phase = aerosol_phase; + wet_deposition.unknown_properties = unknown_properties; + } + + return { status, wet_deposition }; + } + /// @brief Parses all reactions /// @param objects A json object that should contain only valid reactions /// @param existing_species A list of spcecies configured for a mechanism diff --git a/test/unit/test_parse_emission.cpp b/test/unit/test_parse_emission.cpp index fbe6424..0ce87ab 100644 --- a/test/unit/test_parse_emission.cpp +++ b/test/unit/test_parse_emission.cpp @@ -14,7 +14,7 @@ TEST(JsonParser, CanParseValidEmissionReaction) EXPECT_EQ(mechanism.reactions.emission[0].gas_phase, "gas"); EXPECT_EQ(mechanism.reactions.emission[0].name, "my emission"); - EXPECT_EQ(mechanism.reactions.emission[0].scaling_factor_, 12.3); + EXPECT_EQ(mechanism.reactions.emission[0].scaling_factor, 12.3); EXPECT_EQ(mechanism.reactions.emission[0].products.size(), 1); EXPECT_EQ(mechanism.reactions.emission[0].products[0].species_name, "B"); EXPECT_EQ(mechanism.reactions.emission[0].products[0].coefficient, 1); @@ -22,7 +22,7 @@ TEST(JsonParser, CanParseValidEmissionReaction) EXPECT_EQ(mechanism.reactions.emission[0].unknown_properties["__comment"], "\"Dr. Pepper outranks any other soda\""); EXPECT_EQ(mechanism.reactions.emission[1].gas_phase, "gas"); - EXPECT_EQ(mechanism.reactions.emission[1].scaling_factor_, 1); + EXPECT_EQ(mechanism.reactions.emission[1].scaling_factor, 1); EXPECT_EQ(mechanism.reactions.emission[1].products.size(), 1); EXPECT_EQ(mechanism.reactions.emission[1].products[0].species_name, "B"); EXPECT_EQ(mechanism.reactions.emission[1].products[0].coefficient, 1); diff --git a/test/unit/test_parse_first_order_loss.cpp b/test/unit/test_parse_first_order_loss.cpp index 30de586..f6226f6 100644 --- a/test/unit/test_parse_first_order_loss.cpp +++ b/test/unit/test_parse_first_order_loss.cpp @@ -14,7 +14,7 @@ TEST(JsonParser, CanParseValidFirstOrderLossReaction) EXPECT_EQ(mechanism.reactions.first_order_loss[0].gas_phase, "gas"); EXPECT_EQ(mechanism.reactions.first_order_loss[0].name, "my first order loss"); - EXPECT_EQ(mechanism.reactions.first_order_loss[0].scaling_factor_, 12.3); + EXPECT_EQ(mechanism.reactions.first_order_loss[0].scaling_factor, 12.3); EXPECT_EQ(mechanism.reactions.first_order_loss[0].reactants.size(), 1); EXPECT_EQ(mechanism.reactions.first_order_loss[0].reactants[0].species_name, "C"); EXPECT_EQ(mechanism.reactions.first_order_loss[0].reactants[0].coefficient, 1); @@ -22,7 +22,7 @@ TEST(JsonParser, CanParseValidFirstOrderLossReaction) EXPECT_EQ(mechanism.reactions.first_order_loss[0].unknown_properties["__comment"], "\"Strawberries are the superior fruit\""); EXPECT_EQ(mechanism.reactions.first_order_loss[1].gas_phase, "gas"); - EXPECT_EQ(mechanism.reactions.first_order_loss[1].scaling_factor_, 1); + EXPECT_EQ(mechanism.reactions.first_order_loss[1].scaling_factor, 1); EXPECT_EQ(mechanism.reactions.first_order_loss[1].reactants.size(), 1); EXPECT_EQ(mechanism.reactions.first_order_loss[1].reactants[0].species_name, "C"); EXPECT_EQ(mechanism.reactions.first_order_loss[1].reactants[0].coefficient, 1); diff --git a/test/unit/test_parse_photolysis.cpp b/test/unit/test_parse_photolysis.cpp index eff3e9d..1089126 100644 --- a/test/unit/test_parse_photolysis.cpp +++ b/test/unit/test_parse_photolysis.cpp @@ -14,7 +14,7 @@ TEST(JsonParser, CanParseValidPhotolysisReaction) EXPECT_EQ(mechanism.reactions.photolysis[0].gas_phase, "gas"); EXPECT_EQ(mechanism.reactions.photolysis[0].name, "my photolysis"); - EXPECT_EQ(mechanism.reactions.photolysis[0].scaling_factor_, 12.3); + EXPECT_EQ(mechanism.reactions.photolysis[0].scaling_factor, 12.3); EXPECT_EQ(mechanism.reactions.photolysis[0].reactants.size(), 1); EXPECT_EQ(mechanism.reactions.photolysis[0].reactants[0].species_name, "B"); EXPECT_EQ(mechanism.reactions.photolysis[0].reactants[0].coefficient, 1); @@ -25,7 +25,7 @@ TEST(JsonParser, CanParseValidPhotolysisReaction) EXPECT_EQ(mechanism.reactions.photolysis[0].unknown_properties["__comment"], "\"hi\""); EXPECT_EQ(mechanism.reactions.photolysis[1].gas_phase, "gas"); - EXPECT_EQ(mechanism.reactions.photolysis[1].scaling_factor_, 1); + EXPECT_EQ(mechanism.reactions.photolysis[1].scaling_factor, 1); EXPECT_EQ(mechanism.reactions.photolysis[1].reactants.size(), 1); EXPECT_EQ(mechanism.reactions.photolysis[1].reactants[0].species_name, "B"); EXPECT_EQ(mechanism.reactions.photolysis[1].reactants[0].coefficient, 1.2); diff --git a/test/unit/test_parse_wet_deposition.cpp b/test/unit/test_parse_wet_deposition.cpp index a5b18b6..3b1bd1e 100644 --- a/test/unit/test_parse_wet_deposition.cpp +++ b/test/unit/test_parse_wet_deposition.cpp @@ -10,33 +10,17 @@ TEST(JsonParser, CanParseValidWetDepositionReaction) auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/valid.json")); EXPECT_EQ(status, ConfigParseStatus::Success); - EXPECT_EQ(mechanism.reactions.wet_deposition.size(), 1); + EXPECT_EQ(mechanism.reactions.wet_deposition.size(), 2); - EXPECT_EQ(mechanism.reactions.wet_deposition[0].name, "my arrhenius"); - EXPECT_EQ(mechanism.reactions.wet_deposition[0].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].name, "rxn cloud"); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].aerosol_phase, "cloud"); + EXPECT_EQ(mechanism.reactions.wet_deposition[0].scaling_factor, 12.3); EXPECT_EQ(mechanism.reactions.wet_deposition[0].unknown_properties.size(), 1); - EXPECT_EQ(mechanism.reactions.wet_deposition[0].unknown_properties["__solver_param"], "0.1"); -} - -TEST(JsonParser, WetDepositionDetectsUnknownSpecies) -{ - JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/unknown_species.json")); - EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); -} + EXPECT_EQ(mechanism.reactions.wet_deposition[0].unknown_properties["__comment"], "\"Tuxedo cats are the best\""); -TEST(JsonParser, WetDepositionDetectsMutuallyExclusiveOptions) -{ - JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/mutually_exclusive.json")); - EXPECT_EQ(status, ConfigParseStatus::MutuallyExclusiveOption); -} - -TEST(JsonParser, WetDepositionDetectsBadReactionComponent) -{ - JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/wet_deposition/bad_reaction_component.json")); - EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); + EXPECT_EQ(mechanism.reactions.wet_deposition[1].name, "rxn cloud2"); + EXPECT_EQ(mechanism.reactions.wet_deposition[1].aerosol_phase, "cloud"); + EXPECT_EQ(mechanism.reactions.wet_deposition[1].scaling_factor, 1); } TEST(JsonParser, WetDepositionDetectsUnknownPhase) diff --git a/test/unit/unit_configs/reactions/wet_deposition/missing_phase.json b/test/unit/unit_configs/reactions/wet_deposition/missing_phase.json new file mode 100644 index 0000000..bc25732 --- /dev/null +++ b/test/unit/unit_configs/reactions/wet_deposition/missing_phase.json @@ -0,0 +1,24 @@ +{ + "version": "1.0.0", + "name": "Missing phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + ], + "reactions": [ + { + "type": "WET_DEPOSITION", + "aerosol phase": "cloud", + "name": "rxn cloud" + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/wet_deposition/valid.json b/test/unit/unit_configs/reactions/wet_deposition/valid.json new file mode 100644 index 0000000..0ad861c --- /dev/null +++ b/test/unit/unit_configs/reactions/wet_deposition/valid.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0", + "name": "Valid wet deposition", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "cloud", + "species": [ + "A", + "B", + "C" + ] + } + ], + "reactions": [ + { + "type": "WET_DEPOSITION", + "aerosol phase": "cloud", + "name": "rxn cloud", + "scaling factor": 12.3, + "__comment": "Tuxedo cats are the best" + }, + { + "type": "WET_DEPOSITION", + "aerosol phase": "cloud", + "name": "rxn cloud2" + } + ] +} \ No newline at end of file From 4845bfb596b108c167cf3f45f6722817008ed941 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Mon, 22 Jan 2024 14:51:14 -0600 Subject: [PATCH 5/6] parsed valid aqueous_equilibrium --- examples/full_configuration.json | 5 +- .../mechanism_configuration/validation.hpp | 30 +++- include/open_atmos/types.hpp | 29 ++++ src/parser.cpp | 141 +++++++++++++++++- test/integration/test_json_parser.cpp | 1 + test/unit/CMakeLists.txt | 1 + test/unit/test_parse_aqueous_equilibrium.cpp | 75 ++++++++++ .../bad_reaction_component.json | 37 +++++ .../aqueous_equilibrium/missing_phase.json | 32 ++++ .../mutually_exclusive.json | 39 +++++ .../aqueous_equilibrium/unknown_species.json | 37 +++++ .../reactions/aqueous_equilibrium/valid.json | 83 +++++++++++ 12 files changed, 502 insertions(+), 8 deletions(-) create mode 100644 test/unit/test_parse_aqueous_equilibrium.cpp create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json diff --git a/examples/full_configuration.json b/examples/full_configuration.json index 7c94eaf..ab62899 100644 --- a/examples/full_configuration.json +++ b/examples/full_configuration.json @@ -126,7 +126,10 @@ "A": 1.14e-2, "C": 2300.0, "k_reverse": 0.32, - "ion pair": "B-C", + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, "reactants": [ { "species name": "A", diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index e9c7a6b..ccb8ab6 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -109,13 +109,26 @@ namespace open_atmos const std::string FirstOrderLoss_key = "FIRST_ORDER_LOSS"; // also scaling factor + // Aqueous Equilibrium + const std::string AqueousPhaseEquilibrium_key = "AQUEOUS_EQUILIBRIUM"; + // also + // aerosol phase + // aerosol-phase water + // A + // C + const std::string k_reverse = "k_reverse"; + const std::string ion_pair = "ion pair"; + // ion pair + const std::string first = "first"; + const std::string second = "second"; + } keys; - struct Configuration + struct Mechanism { const std::vector required_keys{ keys.version, keys.species, keys.phases, keys.reactions }; const std::vector optional_keys{ keys.name }; - } configuration; + } mechanism; struct Species { @@ -197,11 +210,16 @@ namespace open_atmos const std::vector optional_keys{ keys.name, keys.scaling_factor }; } first_order_loss; - struct Mechanism + struct AqueousEquilibrium { - const std::vector required_keys{}; - const std::vector optional_keys{}; - } mechanism; + const std::vector required_keys{ keys.type, keys.reactants, keys.products, keys.aerosol_phase, keys.aerosol_phase_water, keys.k_reverse }; + const std::vector optional_keys{ keys.name, keys.A, keys.C, keys.ion_pair }; + } aqueous_equilibrium; + struct IonPair + { + const std::vector required_keys{ keys.first, keys.second }; + const std::vector optional_keys{}; + } ion_pair; } // namespace validation } // namespace open_atmos \ No newline at end of file diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 7ff2b37..d6db58a 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -6,6 +6,8 @@ #include #include +#include +#include namespace open_atmos { @@ -223,6 +225,32 @@ namespace open_atmos std::unordered_map unknown_properties; }; + struct AqueousEquilibrium + { + /// @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 An identifier indicating which aerosol phase this reaction takes place in + std::string aerosol_phase; + /// @brief An identifier indicating the species label of aqueous phase water + std::string aerosol_phase_water; + /// @brief A list of reactants + std::vector reactants; + /// @brief A list of products + std::vector products; + /// @brief Optional ion pairs + std::array ion_pair; + /// @brief Pre-exponential factor (s-1) + double A{ 1 }; + /// @brief A constant + double C{ 0 }; + /// @brief Reverse reation rate constant (s-1) + double k_reverse{ 0 }; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + struct Reactions { std::vector arrhenius; @@ -234,6 +262,7 @@ namespace open_atmos std::vector photolysis; std::vector emission; std::vector first_order_loss; + std::vector aqueous_equilibrium; }; struct Mechanism diff --git a/src/parser.cpp b/src/parser.cpp index 3489667..cd4d21a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1224,6 +1224,135 @@ namespace open_atmos return { status, first_order_loss }; } + /// @brief Parses an aqueous equilibrium reaction + /// @param object A json object that should have information containing arrhenius parameters + /// @param existing_species A list of species configured in a mechanism + /// @param existing_phases A list of phases configured in a mechanism + /// @return A pair indicating parsing success and a struct of Condensed Phase Arrhenius parameters + std::pair ParseAqueousEquilibrium( + const json& object, + const std::vector& existing_species, + const std::vector& existing_phases) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::AqueousEquilibrium aqueous_equilibrium; + + status = ValidateSchema(object, validation::aqueous_equilibrium.required_keys, validation::aqueous_equilibrium.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.A)) + { + aqueous_equilibrium.A = object[validation::keys.A].get(); + } + if (object.contains(validation::keys.C)) + { + aqueous_equilibrium.C = object[validation::keys.C].get(); + } + + aqueous_equilibrium.k_reverse = object[validation::keys.k_reverse].get(); + + if (object.contains(validation::keys.name)) + { + aqueous_equilibrium.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::aqueous_equilibrium.required_keys, validation::aqueous_equilibrium.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + std::string aerosol_phase = object[validation::keys.aerosol_phase].get(); + std::string aerosol_phase_water = object[validation::keys.aerosol_phase_water].get(); + + 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); + } + requested_species.push_back(aerosol_phase_water); + + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species)) + { + status = ConfigParseStatus::ReactionRequiresUnknownSpecies; + } + + auto phase_it = std::find_if( + existing_phases.begin(), existing_phases.end(), [&aerosol_phase](const types::Phase& phase) { return phase.name == aerosol_phase; }); + + if (phase_it != existing_phases.end()) + { + // check if all of the species for this reaction are actually in the aerosol phase + std::vector aerosol_phase_species = { (*phase_it).species.begin(), (*phase_it).species.end() }; + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, aerosol_phase_species)) + { + status = ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase; + } + } + else + { + status = ConfigParseStatus::UnknownPhase; + } + + const auto& ion_pair_object = object[validation::keys.ion_pair]; + status = ValidateSchema(ion_pair_object, validation::ion_pair.required_keys, validation::ion_pair.optional_keys); + if (status == ConfigParseStatus::Success) { + auto first_parse = ParseReactionComponent(ion_pair_object[validation::keys.first]); + if (first_parse.first != ConfigParseStatus::Success) { + status = first_parse.first; + } + else { + aqueous_equilibrium.ion_pair[0] = first_parse.second; + } + auto second_parse = ParseReactionComponent(ion_pair_object[validation::keys.second]); + if (second_parse.first != ConfigParseStatus::Success) { + status = second_parse.first; + } + else { + aqueous_equilibrium.ion_pair[1] = second_parse.second; + } + } + + aqueous_equilibrium.aerosol_phase = aerosol_phase; + aqueous_equilibrium.aerosol_phase_water = aerosol_phase_water; + aqueous_equilibrium.products = products; + aqueous_equilibrium.reactants = reactants; + aqueous_equilibrium.unknown_properties = unknown_properties; + } + + return { status, aqueous_equilibrium }; + } /// @brief Parses all reactions /// @param objects A json object that should contain only valid reactions /// @param existing_species A list of spcecies configured for a mechanism @@ -1328,6 +1457,16 @@ namespace open_atmos } reactions.first_order_loss.push_back(first_order_loss_parse.second); } + else if (type == validation::keys.AqueousPhaseEquilibrium_key) + { + auto aqueous_equilibrium_parse = ParseAqueousEquilibrium(object, existing_species, existing_phases); + status = aqueous_equilibrium_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.aqueous_equilibrium.push_back(aqueous_equilibrium_parse.second); + } } return { status, reactions }; @@ -1369,7 +1508,7 @@ namespace open_atmos ConfigParseStatus status; types::Mechanism mechanism; - status = ValidateSchema(object, validation::configuration.required_keys, validation::configuration.optional_keys); + status = ValidateSchema(object, validation::mechanism.required_keys, validation::mechanism.optional_keys); if (status != ConfigParseStatus::Success) { diff --git a/test/integration/test_json_parser.cpp b/test/integration/test_json_parser.cpp index d18f34c..4d9e034 100644 --- a/test/integration/test_json_parser.cpp +++ b/test/integration/test_json_parser.cpp @@ -21,6 +21,7 @@ TEST(JsonParser, ParsesFullConfiguration) EXPECT_EQ(mechanism.reactions.photolysis.size(), 1); EXPECT_EQ(mechanism.reactions.emission.size(), 1); EXPECT_EQ(mechanism.reactions.first_order_loss.size(), 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium.size(), 1); } TEST(JsonParser, ParserReportsBadFiles) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index bc64576..87c7d64 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -17,6 +17,7 @@ create_standard_test(NAME parse_species SOURCES test_parse_species.cpp) create_standard_test(NAME parse_surface SOURCES test_parse_surface.cpp) create_standard_test(NAME parse_troe SOURCES test_parse_troe.cpp) create_standard_test(NAME parse_tunneling SOURCES test_parse_tunneling.cpp) +create_standard_test(NAME parse_aqueous_equilibrium SOURCES test_parse_aqueous_equilibrium.cpp) ################################################################################ # Copy test data diff --git a/test/unit/test_parse_aqueous_equilibrium.cpp b/test/unit/test_parse_aqueous_equilibrium.cpp new file mode 100644 index 0000000..e105c05 --- /dev/null +++ b/test/unit/test_parse_aqueous_equilibrium.cpp @@ -0,0 +1,75 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidAqueousEquilibriumReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium.size(), 2); + + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].name, "my aqueous eq"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].aerosol_phase, "aerosol"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].A, 1.14e-2); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].C, 2300.0); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].k_reverse, 0.32); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].reactants[0].coefficient, 2); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products.size(), 2); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[0].species_name, "B"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[1].species_name, "C"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[1].coefficient, 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair[1].species_name, "B"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].unknown_properties["__comment"], "\"GIF is pronounced with a hard g\""); + + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].aerosol_phase, "aerosol"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].aerosol_phase_water, "H2O_aq"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].A, 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].C, 0); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].k_reverse, 0.32); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].reactants[0].coefficient, 2); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products.size(), 2); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products[0].species_name, "B"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products[1].species_name, "C"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products[1].coefficient, 1); +} + +// TEST(JsonParser, AqueousEquilibriumDetectsUnknownSpecies) +// { +// JsonParser parser; +// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/unknown_species.json")); +// EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +// } + +// TEST(JsonParser, AqueousEquilibriumDetectsMutuallyExclusiveOptions) +// { +// JsonParser parser; +// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json")); +// EXPECT_EQ(status, ConfigParseStatus::MutuallyExclusiveOption); +// } + +// TEST(JsonParser, AqueousEquilibriumDetectsBadReactionComponent) +// { +// JsonParser parser; +// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json")); +// EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +// } + +// TEST(JsonParser, AqueousEquilibriumDetectsUnknownPhase) +// { +// JsonParser parser; +// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/missing_phase.json")); +// EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); +// } \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json new file mode 100644 index 0000000..97197b6 --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/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": "ARRHENIUS", + "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/aqueous_equilibrium/missing_phase.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json new file mode 100644 index 0000000..1bd0485 --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json @@ -0,0 +1,32 @@ +{ + "version": "1.0.0", + "name": "Missing phase", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ ], + "reactions": [ + { + "type": "ARRHENIUS", + "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/aqueous_equilibrium/mutually_exclusive.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json new file mode 100644 index 0000000..61db970 --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json @@ -0,0 +1,39 @@ +{ + "version": "1.0.0", + "name": "Mutually Exclusive", + "species": [ + { + "name": "A" + }, + { + "name": "B" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ + { + "type": "ARRHENIUS", + "gas phase": "gas", + "reactants": [ + { + "species name": "A" + } + ], + "products": [ + { + "species name": "B" + } + ], + "C": 10, + "Ea": 0.5 + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json new file mode 100644 index 0000000..a92afa3 --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json @@ -0,0 +1,37 @@ +{ + "version": "1.0.0", + "name": "Unknown species", + "species": [ + { + "name": "A" + }, + { + "name": "B" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ + { + "type": "ARRHENIUS", + "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/aqueous_equilibrium/valid.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json new file mode 100644 index 0000000..c63a7dc --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json @@ -0,0 +1,83 @@ +{ + "version": "1.0.0", + "name": "Valid aqueous equilibrium", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aerosol", + "species": [ + "A", + "B", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "A": 1.14e-2, + "C": 2300.0, + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, + "reactants": [ + { + "species name": "A", + "coefficient": 2 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 1 + }, + { + "species name": "C", + "coefficient": 1 + } + ], + "name": "my aqueous eq", + "__comment": "GIF is pronounced with a hard g" + }, + { + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, + "reactants": [ + { + "species name": "A", + "coefficient": 2 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 1 + }, + { + "species name": "C", + "coefficient": 1 + } + ] + } + ] +} \ No newline at end of file From b7be48260de46552b6a87200809dfa70ab665d60 Mon Sep 17 00:00:00 2001 From: Kyle Shores Date: Tue, 23 Jan 2024 09:48:08 -0600 Subject: [PATCH 6/6] dropping to c++17, using optional for ion pair --- examples/full_configuration.json | 6 +- .../mechanism_configuration/parser.hpp | 3 +- include/open_atmos/types.hpp | 4 +- src/CMakeLists.txt | 2 +- src/parser.cpp | 24 ++++---- test/unit/test_parse_aqueous_equilibrium.cpp | 53 ++++++++--------- .../bad_reaction_component.json | 32 +++++++++-- .../aqueous_equilibrium/invalid_ion_pair.json | 57 +++++++++++++++++++ .../aqueous_equilibrium/missing_phase.json | 23 ++++++-- .../mutually_exclusive.json | 39 ------------- .../aqueous_equilibrium/unknown_species.json | 22 +++++-- .../reactions/aqueous_equilibrium/valid.json | 8 +-- 12 files changed, 172 insertions(+), 101 deletions(-) create mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/invalid_ion_pair.json delete mode 100644 test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json diff --git a/examples/full_configuration.json b/examples/full_configuration.json index ab62899..47358e0 100644 --- a/examples/full_configuration.json +++ b/examples/full_configuration.json @@ -76,7 +76,10 @@ "species": [ "H2O2_aq", "H2O_aq", - "ethanol_aq" + "ethanol_aq", + "A", + "B", + "C" ] }, { @@ -120,7 +123,6 @@ }, { "type": "AQUEOUS_EQUILIBRIUM", - "gas phase": "gas", "aerosol phase": "aqueous aerosol", "aerosol-phase water": "H2O_aq", "A": 1.14e-2, diff --git a/include/open_atmos/mechanism_configuration/parser.hpp b/include/open_atmos/mechanism_configuration/parser.hpp index aba0668..86e2a80 100644 --- a/include/open_atmos/mechanism_configuration/parser.hpp +++ b/include/open_atmos/mechanism_configuration/parser.hpp @@ -35,7 +35,8 @@ namespace open_atmos ReactionRequiresUnknownSpecies, UnknownPhase, RequestedAerosolSpeciesNotIncludedInAerosolPhase, - TooManyReactionComponents + TooManyReactionComponents, + InvalidIonPair }; std::string configParseStatusToString(const ConfigParseStatus &status); diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index d6db58a..1496490 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -8,6 +8,8 @@ #include #include #include +#include + namespace open_atmos { @@ -240,7 +242,7 @@ namespace open_atmos /// @brief A list of products std::vector products; /// @brief Optional ion pairs - std::array ion_pair; + std::optional> ion_pair; /// @brief Pre-exponential factor (s-1) double A{ 1 }; /// @brief A constant diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 861f1b9..7d6e7e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,7 +5,7 @@ configure_file(version.hpp.in ${PROJECT_SOURCE_DIR}/include/open_atmos/mechanism add_library(mechanism_configuration) add_library(open_atmos::mechanism_configuration ALIAS mechanism_configuration) -target_compile_features(mechanism_configuration PUBLIC cxx_std_20) +target_compile_features(mechanism_configuration PUBLIC cxx_std_17) target_sources(mechanism_configuration PRIVATE diff --git a/src/parser.cpp b/src/parser.cpp index cd4d21a..c6e753b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -32,6 +32,7 @@ namespace open_atmos case ConfigParseStatus::UnknownPhase: return "UnknownPhase"; case ConfigParseStatus::RequestedAerosolSpeciesNotIncludedInAerosolPhase: return "RequestedAerosolSpeciesNotIncludedInAerosolPhase"; case ConfigParseStatus::TooManyReactionComponents: return "TooManyReactionComponents"; + case ConfigParseStatus::InvalidIonPair: return "InvalidIonPair"; default: return "Unknown"; } } @@ -137,7 +138,7 @@ namespace open_atmos // now, anything left must be standard comment starting with __ for (auto& key : remaining) { - if (!key.starts_with("__")) + if (key.find("__") == std::string::npos) { std::cerr << "Non-standard key '" << key << "' found in object" << object << std::endl; @@ -1325,22 +1326,21 @@ namespace open_atmos status = ConfigParseStatus::UnknownPhase; } - const auto& ion_pair_object = object[validation::keys.ion_pair]; - status = ValidateSchema(ion_pair_object, validation::ion_pair.required_keys, validation::ion_pair.optional_keys); - if (status == ConfigParseStatus::Success) { + if (status == ConfigParseStatus::Success && object.contains(validation::keys.ion_pair)) { + const auto& ion_pair_object = object[validation::keys.ion_pair]; + status = ValidateSchema(ion_pair_object, validation::ion_pair.required_keys, validation::ion_pair.optional_keys); auto first_parse = ParseReactionComponent(ion_pair_object[validation::keys.first]); if (first_parse.first != ConfigParseStatus::Success) { status = first_parse.first; } else { - aqueous_equilibrium.ion_pair[0] = first_parse.second; - } - auto second_parse = ParseReactionComponent(ion_pair_object[validation::keys.second]); - if (second_parse.first != ConfigParseStatus::Success) { - status = second_parse.first; - } - else { - aqueous_equilibrium.ion_pair[1] = second_parse.second; + auto second_parse = ParseReactionComponent(ion_pair_object[validation::keys.second]); + if (second_parse.first != ConfigParseStatus::Success) { + status = second_parse.first; + } + else { + aqueous_equilibrium.ion_pair = std::array{first_parse.second, second_parse.second}; + } } } diff --git a/test/unit/test_parse_aqueous_equilibrium.cpp b/test/unit/test_parse_aqueous_equilibrium.cpp index e105c05..b508dfc 100644 --- a/test/unit/test_parse_aqueous_equilibrium.cpp +++ b/test/unit/test_parse_aqueous_equilibrium.cpp @@ -26,8 +26,8 @@ TEST(JsonParser, CanParseValidAqueousEquilibriumReaction) EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[0].coefficient, 1); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[1].species_name, "C"); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].products[1].coefficient, 1); - EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair[0].species_name, "A"); - EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair[1].species_name, "B"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair.value()[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].ion_pair.value()[1].species_name, "B"); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].unknown_properties.size(), 1); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[0].unknown_properties["__comment"], "\"GIF is pronounced with a hard g\""); @@ -35,6 +35,7 @@ TEST(JsonParser, CanParseValidAqueousEquilibriumReaction) EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].aerosol_phase_water, "H2O_aq"); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].A, 1); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].C, 0); + EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].ion_pair.has_value(), false); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].k_reverse, 0.32); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].reactants.size(), 1); EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].reactants[0].species_name, "A"); @@ -46,30 +47,30 @@ TEST(JsonParser, CanParseValidAqueousEquilibriumReaction) EXPECT_EQ(mechanism.reactions.aqueous_equilibrium[1].products[1].coefficient, 1); } -// TEST(JsonParser, AqueousEquilibriumDetectsUnknownSpecies) -// { -// JsonParser parser; -// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/unknown_species.json")); -// EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); -// } +TEST(JsonParser, AqueousEquilibriumDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} -// TEST(JsonParser, AqueousEquilibriumDetectsMutuallyExclusiveOptions) -// { -// JsonParser parser; -// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json")); -// EXPECT_EQ(status, ConfigParseStatus::MutuallyExclusiveOption); -// } +TEST(JsonParser, AqueousEquilibriumDetectsBadReactionComponent) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json")); + EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +} -// TEST(JsonParser, AqueousEquilibriumDetectsBadReactionComponent) -// { -// JsonParser parser; -// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json")); -// EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); -// } +TEST(JsonParser, AqueousEquilibriumDetectsUnknownPhase) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/missing_phase.json")); + EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); +} -// TEST(JsonParser, AqueousEquilibriumDetectsUnknownPhase) -// { -// JsonParser parser; -// auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/missing_phase.json")); -// EXPECT_EQ(status, ConfigParseStatus::UnknownPhase); -// } \ No newline at end of file +TEST(JsonParser, AqueousEquilibriumDetectsInvalidIonPair) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/aqueous_equilibrium/invalid_ion_pair.json")); + EXPECT_EQ(status, ConfigParseStatus::InvalidIonPair); +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json index 97197b6..03f3729 100644 --- a/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/bad_reaction_component.json @@ -7,29 +7,49 @@ }, { "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" } ], "phases": [ { - "name": "gas", + "name": "aerosol", "species": [ "A", - "B" + "B", + "C", + "H2O_aq" ] } ], "reactions": [ { - "type": "ARRHENIUS", - "gas phase": "gas", + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, "reactants": [ { - "Species name": "A" + "Species name": "A", + "coefficient": 2 } ], "products": [ { - "species name": "B" + "Species name": "B", + "coefficient": 1 + }, + { + "Species name": "C", + "coefficient": 1 } ] } diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/invalid_ion_pair.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/invalid_ion_pair.json new file mode 100644 index 0000000..936b7a4 --- /dev/null +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/invalid_ion_pair.json @@ -0,0 +1,57 @@ +{ + "version": "1.0.0", + "name": "Valid aqueous equilibrium", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + }, + { + "name": "H2O_aq" + } + ], + "phases": [ + { + "name": "aerosol", + "species": [ + "A", + "B", + "C", + "H2O_aq" + ] + } + ], + "reactions": [ + { + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, + "reactants": [ + { + "species name": "A", + "coefficient": 2 + } + ], + "products": [ + { + "species name": "B", + "coefficient": 1 + }, + { + "species name": "C", + "coefficient": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json index 1bd0485..93d295b 100644 --- a/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/missing_phase.json @@ -10,21 +10,36 @@ }, { "name": "C" + }, + { + "name": "H2O_aq" } ], "phases": [ ], "reactions": [ { - "type": "ARRHENIUS", - "gas phase": "gas", + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, "reactants": [ { - "species name": "A" + "species name": "A", + "coefficient": 2 } ], "products": [ { - "species name": "C" + "species name": "B", + "coefficient": 1 + }, + { + "species name": "C", + "coefficient": 1 } ] } diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json deleted file mode 100644 index 61db970..0000000 --- a/test/unit/unit_configs/reactions/aqueous_equilibrium/mutually_exclusive.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "version": "1.0.0", - "name": "Mutually Exclusive", - "species": [ - { - "name": "A" - }, - { - "name": "B" - } - ], - "phases": [ - { - "name": "gas", - "species": [ - "A", - "B" - ] - } - ], - "reactions": [ - { - "type": "ARRHENIUS", - "gas phase": "gas", - "reactants": [ - { - "species name": "A" - } - ], - "products": [ - { - "species name": "B" - } - ], - "C": 10, - "Ea": 0.5 - } - ] -} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json index a92afa3..97d7a81 100644 --- a/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/unknown_species.json @@ -11,7 +11,7 @@ ], "phases": [ { - "name": "gas", + "name": "aerosol", "species": [ "A", "B" @@ -20,16 +20,28 @@ ], "reactions": [ { - "type": "ARRHENIUS", - "gas phase": "gas", + "type": "AQUEOUS_EQUILIBRIUM", + "aerosol phase": "aerosol", + "aerosol-phase water": "H2O_aq", + "k_reverse": 0.32, + "ion pair": { + "first": { "species name": "A"}, + "second": { "species name": "B"} + }, "reactants": [ { - "species name": "A" + "species name": "A", + "coefficient": 2 } ], "products": [ { - "species name": "C" + "species name": "B", + "coefficient": 1 + }, + { + "species name": "C", + "coefficient": 1 } ] } diff --git a/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json b/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json index c63a7dc..758b8ac 100644 --- a/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json +++ b/test/unit/unit_configs/reactions/aqueous_equilibrium/valid.json @@ -8,6 +8,9 @@ { "name": "B" }, + { + "name": "C" + }, { "name": "H2O_aq" } @@ -18,6 +21,7 @@ "species": [ "A", "B", + "C", "H2O_aq" ] } @@ -58,10 +62,6 @@ "aerosol phase": "aerosol", "aerosol-phase water": "H2O_aq", "k_reverse": 0.32, - "ion pair": { - "first": { "species name": "A"}, - "second": { "species name": "B"} - }, "reactants": [ { "species name": "A",