diff --git a/include/open_atmos/constants.hpp b/include/open_atmos/constants.hpp new file mode 100644 index 0000000..234cd5c --- /dev/null +++ b/include/open_atmos/constants.hpp @@ -0,0 +1,14 @@ +// Copyright (C) 2023-2024 National Center for Atmospheric Research, University of Illinois at Urbana-Champaign +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once +namespace open_atmos +{ + namespace constants + { + static constexpr double boltzmann = 1.380649e-23; // J K^{-1} + static constexpr double avogadro = 6.02214076e23; // # mol^{-1} + static constexpr double R = boltzmann * avogadro; // J K^{-1} mol^{-1} + } // namespace constants +} // namespace open_atmos \ No newline at end of file diff --git a/include/open_atmos/mechanism_configuration/parser.hpp b/include/open_atmos/mechanism_configuration/parser.hpp index 3cd6aa1..6d306ed 100644 --- a/include/open_atmos/mechanism_configuration/parser.hpp +++ b/include/open_atmos/mechanism_configuration/parser.hpp @@ -29,7 +29,10 @@ namespace open_atmos RequiredKeyNotFound, MutuallyExclusiveOption, InvalidVersion, - DuplicateSpeciesDetected + DuplicateSpeciesDetected, + DuplicatePhasesDetected, + PhaseRequiresUnknownSpecies, + ReactionRequiresUnknownSpecies, }; std::string configParseStatusToString(const ConfigParseStatus &status); diff --git a/include/open_atmos/mechanism_configuration/validation.hpp b/include/open_atmos/mechanism_configuration/validation.hpp index 3eb7289..6f5de43 100644 --- a/include/open_atmos/mechanism_configuration/validation.hpp +++ b/include/open_atmos/mechanism_configuration/validation.hpp @@ -31,6 +31,26 @@ namespace open_atmos const std::string phase = "phase"; const std::string n_star = "N star"; const std::string density = "density [kg m-3]"; + + // Reactions + const std::string reactants = "reactants"; + const std::string products = "products"; + const std::string type = "type"; + const std::string gas_phase = "gas phase"; + + // Reactant and product + const std::string species_name = "species name"; + const std::string coefficient = "coefficient"; + + // Arrhenius + const std::string Arrhenius_key = "ARRHENIUS"; + const std::string A = "A"; + const std::string B = "B"; + const std::string C = "C"; + const std::string D = "D"; + const std::string E = "E"; + const std::string Ea = "Ea"; + } keys; struct Configuration @@ -53,14 +73,26 @@ namespace open_atmos struct Phase { - const std::vector required_keys {}; - const std::vector optional_keys {}; + const std::vector required_keys{ keys.name, keys.species }; + const std::vector optional_keys{}; } phase; + struct ReactionComponent + { + const std::vector required_keys{ keys.species_name }; + const std::vector optional_keys{ keys.coefficient }; + } reaction_component; + + struct Arrhenius + { + const std::vector required_keys{ keys.products, keys.reactants, keys.type, keys.gas_phase }; + const std::vector optional_keys{ keys.A, keys.B, keys.C, keys.D, keys.E, keys.Ea, keys.name }; + } arrhenius; + struct Mechanism { - const std::vector required_keys {}; - const std::vector optional_keys {}; + const std::vector required_keys{}; + const std::vector optional_keys{}; } mechanism; } // namespace validation diff --git a/include/open_atmos/types.hpp b/include/open_atmos/types.hpp index 455b2c9..2d3e422 100644 --- a/include/open_atmos/types.hpp +++ b/include/open_atmos/types.hpp @@ -22,13 +22,56 @@ namespace open_atmos struct Phase { + std::string name; + std::vector species; + std::unordered_map unknown_properties; + }; + + struct ReactionComponent + { + std::string species_name; + double coefficient; + std::unordered_map unknown_properties; + }; + + struct Arrhenius + { + /// @brief Pre-exponential factor [(mol m−3)^(−(𝑛−1)) s−1] + double A{ 1 }; + /// @brief Unitless exponential factor + double B{ 0 }; + /// @brief Activation threshold, expected to be the negative activation energy divided by the boltzman constant + /// [-E_a / k_b), K] + double C{ 0 }; + /// @brief A factor that determines temperature dependence [K] + double D{ 300 }; + /// @brief A factor that determines pressure dependence [Pa-1] + double E{ 0 }; + + /// @brief A list of reactants + std::vector reactants; + /// @brief A list of products + std::vector products; + /// @brief An identifier, optional, uniqueness not enforced + std::string name; + /// @brief An identifier indicating which gas phase this reaction takes place in + std::string gas_phase; + + std::unordered_map unknown_properties; + }; + + struct Reactions + { + std::vector arrhenius; }; struct Mechanism { - std::string name; // optional + /// @brief An identifier, optional + std::string name; std::vector species; - std::unordered_map phases; + std::vector phases; + Reactions reactions; }; } // namespace types diff --git a/src/parser.cpp b/src/parser.cpp index 9178683..6bc9552 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 +#include #include #include #include @@ -25,6 +26,9 @@ namespace open_atmos case ConfigParseStatus::RequiredKeyNotFound: return "RequiredKeyNotFound"; case ConfigParseStatus::MutuallyExclusiveOption: return "MutuallyExclusiveOption"; case ConfigParseStatus::DuplicateSpeciesDetected: return "DuplicateSpeciesDetected"; + case ConfigParseStatus::DuplicatePhasesDetected: return "DuplicatePhasesDetected"; + case ConfigParseStatus::PhaseRequiresUnknownSpecies: return "PhaseRequiresUnknownSpecies"; + case ConfigParseStatus::ReactionRequiresUnknownSpecies: return "ReactionRequiresUnknownSpecies"; default: return "Unknown"; } } @@ -140,13 +144,14 @@ namespace open_atmos return ConfigParseStatus::Success; } - bool ContainsOnlyUniqueSpecies(const std::vector& all_species) + template + bool ContainsUniqueObjectsByName(const std::vector& collection) { - for (size_t i = 0; i < all_species.size(); ++i) + for (size_t i = 0; i < collection.size(); ++i) { - for (size_t j = i + 1; j < all_species.size(); ++j) + for (size_t j = i + 1; j < collection.size(); ++j) { - if (all_species[i].name == all_species[j].name) + if (collection[i].name == collection[j].name) { return false; } @@ -155,6 +160,21 @@ namespace open_atmos return true; } + bool RequiresUnknownSpecies(const std::vector requested_species, const std::vector& existing_species) + { + for (const auto& spec : requested_species) + { + auto it = + std::find_if(existing_species.begin(), existing_species.end(), [&spec](const types::Species& existing) { return existing.name == spec; }); + + if (it == existing_species.end()) + { + return true; + } + } + return false; + } + std::pair> ParseSpecies(const json& objects) { ConfigParseStatus status = ConfigParseStatus::Success; @@ -197,12 +217,213 @@ namespace open_atmos all_species.push_back(species); } - if (!ContainsOnlyUniqueSpecies(all_species)) + if (!ContainsUniqueObjectsByName(all_species)) status = ConfigParseStatus::DuplicateSpeciesDetected; return { status, all_species }; } + std::pair> ParsePhases(const json& objects, const std::vector existing_species) + { + ConfigParseStatus status = ConfigParseStatus::Success; + std::vector all_phases; + + for (const auto& object : objects) + { + types::Phase phase; + status = ValidateSchema(object, validation::phase.required_keys, validation::phase.optional_keys); + if (status != ConfigParseStatus::Success) + { + break; + } + + std::string name = object[validation::keys.name].get(); + + std::vector species{}; + for (const auto& spec : object[validation::keys.species]) + { + species.push_back(spec); + } + + auto comments = GetComments(object, validation::phase.required_keys, validation::phase.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + phase.name = name; + phase.species = species; + phase.unknown_properties = unknown_properties; + + if (RequiresUnknownSpecies(species, existing_species)) + { + status = ConfigParseStatus::PhaseRequiresUnknownSpecies; + break; + } + + all_phases.push_back(phase); + } + + if (status == ConfigParseStatus::Success && !ContainsUniqueObjectsByName(all_phases)) + status = ConfigParseStatus::DuplicatePhasesDetected; + + return { status, all_phases }; + } + + std::pair ParseReactionComponent(const json& object) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::ReactionComponent component; + + status = ValidateSchema(object, validation::reaction_component.required_keys, validation::reaction_component.optional_keys); + if (status == ConfigParseStatus::Success) + { + std::string species_name = object[validation::keys.species_name].get(); + double coefficient = 1; + if (object.contains(validation::keys.coefficient)) + { + coefficient = object[validation::keys.coefficient].get(); + } + + auto comments = GetComments(object, validation::reaction_component.required_keys, validation::reaction_component.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + component.species_name = species_name; + component.coefficient = coefficient; + component.unknown_properties = unknown_properties; + } + + return { status, component }; + } + + std::pair ParseArrhenius(const json& object, const std::vector existing_species) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::Arrhenius arrhenius; + + status = ValidateSchema(object, validation::arrhenius.required_keys, validation::arrhenius.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)) + { + arrhenius.A = object[validation::keys.A].get(); + } + if (object.contains(validation::keys.B)) + { + arrhenius.B = object[validation::keys.B].get(); + } + if (object.contains(validation::keys.C)) + { + arrhenius.C = object[validation::keys.C].get(); + } + if (object.contains(validation::keys.D)) + { + arrhenius.D = object[validation::keys.D].get(); + } + if (object.contains(validation::keys.E)) + { + arrhenius.E = object[validation::keys.E].get(); + } + if (object.contains(validation::keys.Ea)) + { + if (arrhenius.C != 0) + { + std::cerr << "Ea is specified when C is also specified for an Arrhenius reaction. Pick one." << std::endl; + status = ConfigParseStatus::MutuallyExclusiveOption; + } + // Calculate 'C' using 'Ea' + arrhenius.C = -1 * object[validation::keys.Ea].get() / constants::boltzmann; + } + + if (object.contains(validation::keys.name)) + { + arrhenius.name = object[validation::keys.name].get(); + } + + auto comments = GetComments(object, validation::arrhenius.required_keys, validation::arrhenius.optional_keys); + + std::unordered_map unknown_properties; + for (const auto& key : comments) + { + std::string val = object[key].dump(); + unknown_properties[key] = val; + } + + std::vector requested_species; + for(const auto& spec : products) { + requested_species.push_back(spec.species_name); + } + for(const auto& spec : reactants) { + requested_species.push_back(spec.species_name); + } + + if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species)) { + status = ConfigParseStatus::ReactionRequiresUnknownSpecies; + } + + arrhenius.gas_phase = object[validation::keys.gas_phase].get(); + arrhenius.products = products; + arrhenius.reactants = reactants; + arrhenius.unknown_properties = unknown_properties; + } + + return { status, arrhenius }; + } + + std::pair ParseReactions(const json& objects, const std::vector existing_species) + { + ConfigParseStatus status = ConfigParseStatus::Success; + types::Reactions reactions; + + for (const auto& object : objects) + { + std::string type = object[validation::keys.type].get(); + if (type == validation::keys.Arrhenius_key) + { + auto arrhenius_parse = ParseArrhenius(object, existing_species); + status = arrhenius_parse.first; + if (status != ConfigParseStatus::Success) + { + break; + } + reactions.arrhenius.push_back(arrhenius_parse.second); + } + } + + return { status, reactions }; + } + std::pair JsonParser::Parse(const std::string& file_path) { return JsonParser::Parse(std::filesystem::path(file_path)); @@ -253,7 +474,7 @@ namespace open_atmos mechanism.name = name; // parse all of the species at once - auto species_parsing = ParseSpecies(object["species"]); + auto species_parsing = ParseSpecies(object[validation::keys.species]); if (species_parsing.first != ConfigParseStatus::Success) { @@ -262,7 +483,29 @@ namespace open_atmos std::cerr << "[" << msg << "] Failed to parse the species." << std::endl; } + // parse all of the phases at once + auto phases_parsing = ParsePhases(object[validation::keys.phases], species_parsing.second); + + if (phases_parsing.first != ConfigParseStatus::Success) + { + status = phases_parsing.first; + std::string msg = configParseStatusToString(status); + std::cerr << "[" << msg << "] Failed to parse the phases." << std::endl; + } + + // parse all of the reactions at once + auto reactions_parsing = ParseReactions(object[validation::keys.reactions], species_parsing.second); + + if (reactions_parsing.first != ConfigParseStatus::Success) + { + status = reactions_parsing.first; + std::string msg = configParseStatusToString(status); + std::cerr << "[" << msg << "] Failed to parse the reactions." << std::endl; + } + mechanism.species = species_parsing.second; + mechanism.phases = phases_parsing.second; + mechanism.reactions = reactions_parsing.second; return { status, mechanism }; } diff --git a/test/integration/test_json_parser.cpp b/test/integration/test_json_parser.cpp index c29a40a..c49b2de 100644 --- a/test/integration/test_json_parser.cpp +++ b/test/integration/test_json_parser.cpp @@ -4,9 +4,19 @@ using namespace open_atmos::mechanism_configuration; -TEST(JsonParser, Returns) +TEST(JsonParser, ParsesFullConfiguration) { JsonParser parser; auto [status, mechanism] = parser.Parse(std::string("examples/full_configuration.json")); EXPECT_EQ(status, ConfigParseStatus::Success); + EXPECT_EQ(mechanism.name, "Full Configuration"); + EXPECT_EQ(mechanism.species.size(), 10); + EXPECT_EQ(mechanism.reactions.arrhenius.size(), 1); +} + +TEST(JsonParser, ParserReportsBadFiles) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("examples/_missing_configuration.json")); + EXPECT_EQ(status, ConfigParseStatus::InvalidFilePath); } \ No newline at end of file diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 113c111..b0410c8 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -7,6 +7,8 @@ include(test_util) # Tests create_standard_test(NAME parse_species SOURCES test_parse_species.cpp) +create_standard_test(NAME parse_phases SOURCES test_parse_phases.cpp) +create_standard_test(NAME parse_arrhenius SOURCES test_parse_arrhenius.cpp) ################################################################################ # Copy test data diff --git a/test/unit/test_parse_arrhenius.cpp b/test/unit/test_parse_arrhenius.cpp new file mode 100644 index 0000000..e26fba1 --- /dev/null +++ b/test/unit/test_parse_arrhenius.cpp @@ -0,0 +1,85 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidArrheniusReaction) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/arrhenius/valid.json")); + EXPECT_EQ(status, ConfigParseStatus::Success); + + EXPECT_EQ(mechanism.reactions.arrhenius.size(), 3); + + EXPECT_EQ(mechanism.reactions.arrhenius[0].name, "my arrhenius"); + EXPECT_EQ(mechanism.reactions.arrhenius[0].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.arrhenius[0].A, 32.1); + EXPECT_EQ(mechanism.reactions.arrhenius[0].B, -2.3); + EXPECT_EQ(mechanism.reactions.arrhenius[0].C, 102.3); + EXPECT_EQ(mechanism.reactions.arrhenius[0].D, 63.4); + EXPECT_EQ(mechanism.reactions.arrhenius[0].E, -1.3); + EXPECT_EQ(mechanism.reactions.arrhenius[0].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[0].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.arrhenius[0].reactants[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.arrhenius[0].products.size(), 2); + EXPECT_EQ(mechanism.reactions.arrhenius[0].products[0].species_name, "B"); + EXPECT_EQ(mechanism.reactions.arrhenius[0].products[0].coefficient, 1.2); + EXPECT_EQ(mechanism.reactions.arrhenius[0].products[1].species_name, "C"); + EXPECT_EQ(mechanism.reactions.arrhenius[0].products[1].coefficient, 0.3); + EXPECT_EQ(mechanism.reactions.arrhenius[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[0].unknown_properties["__solver_param"], "0.1"); + + EXPECT_EQ(mechanism.reactions.arrhenius[1].name, "my arrhenius2"); + EXPECT_EQ(mechanism.reactions.arrhenius[1].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.arrhenius[1].A, 3.1); + EXPECT_EQ(mechanism.reactions.arrhenius[1].B, -0.3); + EXPECT_EQ(mechanism.reactions.arrhenius[1].C, 12.3); + EXPECT_EQ(mechanism.reactions.arrhenius[1].D, 6.4); + EXPECT_EQ(mechanism.reactions.arrhenius[1].E, -0.3); + EXPECT_EQ(mechanism.reactions.arrhenius[1].reactants.size(), 2); + EXPECT_EQ(mechanism.reactions.arrhenius[1].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.arrhenius[1].reactants[0].coefficient, 2); + EXPECT_EQ(mechanism.reactions.arrhenius[1].reactants[1].species_name, "B"); + EXPECT_EQ(mechanism.reactions.arrhenius[1].reactants[1].coefficient, 0.1); + EXPECT_EQ(mechanism.reactions.arrhenius[1].products.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[1].products[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.arrhenius[1].products[0].coefficient, 0.5); + EXPECT_EQ(mechanism.reactions.arrhenius[1].products[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[1].products[0].unknown_properties["__optional thing"], "\"hello\""); + + EXPECT_EQ(mechanism.reactions.arrhenius[2].name, ""); + EXPECT_EQ(mechanism.reactions.arrhenius[2].gas_phase, "gas"); + EXPECT_EQ(mechanism.reactions.arrhenius[2].A, 1); + EXPECT_EQ(mechanism.reactions.arrhenius[2].B, 0); + EXPECT_EQ(mechanism.reactions.arrhenius[2].C, 0); + EXPECT_EQ(mechanism.reactions.arrhenius[2].D, 300); + EXPECT_EQ(mechanism.reactions.arrhenius[2].E, 0); + EXPECT_EQ(mechanism.reactions.arrhenius[2].reactants.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[2].reactants[0].species_name, "A"); + EXPECT_EQ(mechanism.reactions.arrhenius[2].reactants[0].coefficient, 1); + EXPECT_EQ(mechanism.reactions.arrhenius[2].products.size(), 1); + EXPECT_EQ(mechanism.reactions.arrhenius[2].products[0].species_name, "C"); + EXPECT_EQ(mechanism.reactions.arrhenius[2].products[0].coefficient, 1); +} + +TEST(JsonParser, ArrheniusDetectsUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/arrhenius/unknown_species.json")); + EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies); +} + +TEST(JsonParser, ArrheniusDetectsMutuallyExclusiveOptions) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/arrhenius/mutually_exclusive.json")); + EXPECT_EQ(status, ConfigParseStatus::MutuallyExclusiveOption); +} + +TEST(JsonParser, ArrheniusDetectsBadReactionComponent) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/arrhenius/bad_reaction_component.json")); + EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +} \ No newline at end of file diff --git a/test/unit/test_parse_phases.cpp b/test/unit/test_parse_phases.cpp new file mode 100644 index 0000000..2065294 --- /dev/null +++ b/test/unit/test_parse_phases.cpp @@ -0,0 +1,61 @@ +#include + +#include + +using namespace open_atmos::mechanism_configuration; + +TEST(JsonParser, CanParseValidPhases) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/phases/valid_phases.json")); + + EXPECT_EQ(status, ConfigParseStatus::Success); + EXPECT_EQ(mechanism.species.size(), 3); + EXPECT_EQ(mechanism.phases.size(), 2); + + EXPECT_EQ(mechanism.phases[0].name, "gas"); + EXPECT_EQ(mechanism.phases[0].species.size(), 2); + EXPECT_EQ(mechanism.phases[0].species[0], "A"); + EXPECT_EQ(mechanism.phases[0].species[1], "B"); + EXPECT_EQ(mechanism.phases[0].unknown_properties.size(), 1); + EXPECT_EQ(mechanism.phases[0].unknown_properties["__other"], "\"key\""); + + EXPECT_EQ(mechanism.phases[1].name, "aerosols"); + EXPECT_EQ(mechanism.phases[1].species.size(), 1); + EXPECT_EQ(mechanism.phases[1].species[0], "C"); + EXPECT_EQ(mechanism.phases[1].unknown_properties.size(), 2); + EXPECT_EQ(mechanism.phases[1].unknown_properties["__other1"], "\"key1\""); + EXPECT_EQ(mechanism.phases[1].unknown_properties["__other2"], "\"key2\""); +} + +TEST(JsonParser, DetectsDuplicatePhases) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/phases/duplicate_phases.json")); + + EXPECT_EQ(status, ConfigParseStatus::DuplicatePhasesDetected); +} + +TEST(JsonParser, DetectsMissingRequiredKeys) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/phases/missing_required_key.json")); + + EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); +} + +TEST(JsonParser, DetectsInvalidKeys) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/phases/invalid_key.json")); + + EXPECT_EQ(status, ConfigParseStatus::InvalidKey); +} + +TEST(JsonParser, DetectsPhaseRequestingUnknownSpecies) +{ + JsonParser parser; + auto [status, mechanism] = parser.Parse(std::string("unit_configs/phases/unknown_species.json")); + + EXPECT_EQ(status, ConfigParseStatus::PhaseRequiresUnknownSpecies); +} \ No newline at end of file diff --git a/test/unit/test_parse_species.cpp b/test/unit/test_parse_species.cpp index f9bb628..1fd250c 100644 --- a/test/unit/test_parse_species.cpp +++ b/test/unit/test_parse_species.cpp @@ -7,7 +7,7 @@ using namespace open_atmos::mechanism_configuration; TEST(JsonParser, CanParseValidSpecies) { JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/valid_species.json")); + auto [status, mechanism] = parser.Parse(std::string("unit_configs/species/valid_species.json")); EXPECT_EQ(status, ConfigParseStatus::Success); EXPECT_EQ(mechanism.species.size(), 3); @@ -38,7 +38,7 @@ TEST(JsonParser, CanParseValidSpecies) TEST(JsonParser, DetectsDuplicateSpecies) { JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/duplicate_species.json")); + auto [status, mechanism] = parser.Parse(std::string("unit_configs/species/duplicate_species.json")); EXPECT_EQ(status, ConfigParseStatus::DuplicateSpeciesDetected); } @@ -46,7 +46,7 @@ TEST(JsonParser, DetectsDuplicateSpecies) TEST(JsonParser, DetectsMissingRequiredKeys) { JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/missing_required_key.json")); + auto [status, mechanism] = parser.Parse(std::string("unit_configs/species/missing_required_key.json")); EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound); } @@ -54,7 +54,7 @@ TEST(JsonParser, DetectsMissingRequiredKeys) TEST(JsonParser, DetectsInvalidKeys) { JsonParser parser; - auto [status, mechanism] = parser.Parse(std::string("unit_configs/invalid_key.json")); + auto [status, mechanism] = parser.Parse(std::string("unit_configs/species/invalid_key.json")); EXPECT_EQ(status, ConfigParseStatus::InvalidKey); } \ No newline at end of file diff --git a/test/unit/unit_configs/phases/duplicate_phases.json b/test/unit/unit_configs/phases/duplicate_phases.json new file mode 100644 index 0000000..66da828 --- /dev/null +++ b/test/unit/unit_configs/phases/duplicate_phases.json @@ -0,0 +1,32 @@ +{ + "version": "1.0.0", + "name": "Duplicate phases configuration", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ] + }, + { + "name": "gas", + "species": [ + "A", + "B" + ] + } + ], + "reactions": [ ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/phases/invalid_key.json b/test/unit/unit_configs/phases/invalid_key.json new file mode 100644 index 0000000..0b57b70 --- /dev/null +++ b/test/unit/unit_configs/phases/invalid_key.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0", + "name": "Invalid key configuration", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ], + "other": "key" + } + ], + "reactions": [ ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/phases/missing_required_key.json b/test/unit/unit_configs/phases/missing_required_key.json new file mode 100644 index 0000000..5f2c793 --- /dev/null +++ b/test/unit/unit_configs/phases/missing_required_key.json @@ -0,0 +1,12 @@ +{ + "version": "1.0.0", + "name": "Missing required phases key configuration", + "species": [ + ], + "phases": [ + { + "name": "gas" + } + ], + "reactions": [ ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/phases/unknown_species.json b/test/unit/unit_configs/phases/unknown_species.json new file mode 100644 index 0000000..4a80e50 --- /dev/null +++ b/test/unit/unit_configs/phases/unknown_species.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0", + "name": "Unknown species configuration", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "D" + ], + "__other": "key" + } + ], + "reactions": [ ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/phases/valid_phases.json b/test/unit/unit_configs/phases/valid_phases.json new file mode 100644 index 0000000..d208cd5 --- /dev/null +++ b/test/unit/unit_configs/phases/valid_phases.json @@ -0,0 +1,34 @@ +{ + "version": "1.0.0", + "name": "Valid phases configuration", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B" + ], + "__other": "key" + }, + { + "name": "aerosols", + "species": [ + "C" + ], + "__other1": "key1", + "__other2": "key2" + } + ], + "reactions": [ ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json b/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json new file mode 100644 index 0000000..859209d --- /dev/null +++ b/test/unit/unit_configs/reactions/arrhenius/bad_reaction_component.json @@ -0,0 +1,37 @@ +{ + "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" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/unit/unit_configs/reactions/arrhenius/mutually_exclusive.json b/test/unit/unit_configs/reactions/arrhenius/mutually_exclusive.json new file mode 100644 index 0000000..61db970 --- /dev/null +++ b/test/unit/unit_configs/reactions/arrhenius/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/arrhenius/unknown_species.json b/test/unit/unit_configs/reactions/arrhenius/unknown_species.json new file mode 100644 index 0000000..a92afa3 --- /dev/null +++ b/test/unit/unit_configs/reactions/arrhenius/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/arrhenius/valid.json b/test/unit/unit_configs/reactions/arrhenius/valid.json new file mode 100644 index 0000000..2ef1013 --- /dev/null +++ b/test/unit/unit_configs/reactions/arrhenius/valid.json @@ -0,0 +1,95 @@ +{ + "version": "1.0.0", + "name": "Valid arrhenius", + "species": [ + { + "name": "A" + }, + { + "name": "B" + }, + { + "name": "C" + } + ], + "phases": [ + { + "name": "gas", + "species": [ + "A", + "B", + "C" + ] + } + ], + "reactions": [ + { + "type": "ARRHENIUS", + "gas phase": "gas", + "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": "ARRHENIUS", + "gas phase": "gas", + "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": "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/duplicate_species.json b/test/unit/unit_configs/species/duplicate_species.json similarity index 100% rename from test/unit/unit_configs/duplicate_species.json rename to test/unit/unit_configs/species/duplicate_species.json diff --git a/test/unit/unit_configs/invalid_key.json b/test/unit/unit_configs/species/invalid_key.json similarity index 100% rename from test/unit/unit_configs/invalid_key.json rename to test/unit/unit_configs/species/invalid_key.json diff --git a/test/unit/unit_configs/missing_required_key.json b/test/unit/unit_configs/species/missing_required_key.json similarity index 100% rename from test/unit/unit_configs/missing_required_key.json rename to test/unit/unit_configs/species/missing_required_key.json diff --git a/test/unit/unit_configs/valid_species.json b/test/unit/unit_configs/species/valid_species.json similarity index 100% rename from test/unit/unit_configs/valid_species.json rename to test/unit/unit_configs/species/valid_species.json