Skip to content

Commit

Permalink
Merge pull request #16 from open-atmos/parse_arrhenius
Browse files Browse the repository at this point in the history
Parse arrhenius
  • Loading branch information
mattldawson authored Jan 18, 2024
2 parents 5b5db4a + f351163 commit 6b714de
Show file tree
Hide file tree
Showing 12 changed files with 561 additions and 6 deletions.
14 changes: 14 additions & 0 deletions include/open_atmos/constants.hpp
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions include/open_atmos/mechanism_configuration/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace open_atmos
DuplicateSpeciesDetected,
DuplicatePhasesDetected,
PhaseRequiresUnknownSpecies,
ReactionRequiresUnknownSpecies,
};
std::string configParseStatusToString(const ConfigParseStatus &status);

Expand Down
32 changes: 32 additions & 0 deletions include/open_atmos/mechanism_configuration/validation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,6 +77,18 @@ namespace open_atmos
const std::vector<std::string> optional_keys{};
} phase;

struct ReactionComponent
{
const std::vector<std::string> required_keys{ keys.species_name };
const std::vector<std::string> optional_keys{ keys.coefficient };
} reaction_component;

struct Arrhenius
{
const std::vector<std::string> required_keys{ keys.products, keys.reactants, keys.type, keys.gas_phase };
const std::vector<std::string> optional_keys{ keys.A, keys.B, keys.C, keys.D, keys.E, keys.Ea, keys.name };
} arrhenius;

struct Mechanism
{
const std::vector<std::string> required_keys{};
Expand Down
42 changes: 41 additions & 1 deletion include/open_atmos/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,51 @@ namespace open_atmos
std::unordered_map<std::string, std::string> unknown_properties;
};

struct ReactionComponent
{
std::string species_name;
double coefficient;
std::unordered_map<std::string, std::string> 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<ReactionComponent> reactants;
/// @brief A list of products
std::vector<ReactionComponent> 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<std::string, std::string> unknown_properties;
};

struct Reactions
{
std::vector<types::Arrhenius> arrhenius;
};

struct Mechanism
{
std::string name; // optional
/// @brief An identifier, optional
std::string name;
std::vector<types::Species> species;
std::vector<types::Phase> phases;
Reactions reactions;
};

} // namespace types
Expand Down
172 changes: 168 additions & 4 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: Apache-2.0

#include <open_atmos/constants.hpp>
#include <open_atmos/mechanism_configuration/parser.hpp>
#include <open_atmos/mechanism_configuration/validation.hpp>
#include <open_atmos/mechanism_configuration/version.hpp>
Expand All @@ -27,6 +28,7 @@ namespace open_atmos
case ConfigParseStatus::DuplicateSpeciesDetected: return "DuplicateSpeciesDetected";
case ConfigParseStatus::DuplicatePhasesDetected: return "DuplicatePhasesDetected";
case ConfigParseStatus::PhaseRequiresUnknownSpecies: return "PhaseRequiresUnknownSpecies";
case ConfigParseStatus::ReactionRequiresUnknownSpecies: return "ReactionRequiresUnknownSpecies";
default: return "Unknown";
}
}
Expand Down Expand Up @@ -158,7 +160,7 @@ namespace open_atmos
return true;
}

bool PhaseRequiresUnknownSpecies(const std::vector<std::string> requested_species, const std::vector<types::Species>& existing_species)
bool RequiresUnknownSpecies(const std::vector<std::string> requested_species, const std::vector<types::Species>& existing_species)
{
for (const auto& spec : requested_species)
{
Expand Down Expand Up @@ -256,7 +258,7 @@ namespace open_atmos
phase.species = species;
phase.unknown_properties = unknown_properties;

if (PhaseRequiresUnknownSpecies(species, existing_species))
if (RequiresUnknownSpecies(species, existing_species))
{
status = ConfigParseStatus::PhaseRequiresUnknownSpecies;
break;
Expand All @@ -271,6 +273,157 @@ namespace open_atmos
return { status, all_phases };
}

std::pair<ConfigParseStatus, types::ReactionComponent> 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<std::string>();
double coefficient = 1;
if (object.contains(validation::keys.coefficient))
{
coefficient = object[validation::keys.coefficient].get<double>();
}

auto comments = GetComments(object, validation::reaction_component.required_keys, validation::reaction_component.optional_keys);

std::unordered_map<std::string, std::string> 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<ConfigParseStatus, types::Arrhenius> ParseArrhenius(const json& object, const std::vector<types::Species> 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<types::ReactionComponent> 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<types::ReactionComponent> 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<double>();
}
if (object.contains(validation::keys.B))
{
arrhenius.B = object[validation::keys.B].get<double>();
}
if (object.contains(validation::keys.C))
{
arrhenius.C = object[validation::keys.C].get<double>();
}
if (object.contains(validation::keys.D))
{
arrhenius.D = object[validation::keys.D].get<double>();
}
if (object.contains(validation::keys.E))
{
arrhenius.E = object[validation::keys.E].get<double>();
}
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<double>() / constants::boltzmann;
}

if (object.contains(validation::keys.name))
{
arrhenius.name = object[validation::keys.name].get<std::string>();
}

auto comments = GetComments(object, validation::arrhenius.required_keys, validation::arrhenius.optional_keys);

std::unordered_map<std::string, std::string> unknown_properties;
for (const auto& key : comments)
{
std::string val = object[key].dump();
unknown_properties[key] = val;
}

std::vector<std::string> 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<std::string>();
arrhenius.products = products;
arrhenius.reactants = reactants;
arrhenius.unknown_properties = unknown_properties;
}

return { status, arrhenius };
}

std::pair<ConfigParseStatus, types::Reactions> ParseReactions(const json& objects, const std::vector<types::Species> existing_species)
{
ConfigParseStatus status = ConfigParseStatus::Success;
types::Reactions reactions;

for (const auto& object : objects)
{
std::string type = object[validation::keys.type].get<std::string>();
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<ConfigParseStatus, types::Mechanism> JsonParser::Parse(const std::string& file_path)
{
return JsonParser::Parse(std::filesystem::path(file_path));
Expand Down Expand Up @@ -321,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)
{
Expand All @@ -331,7 +484,7 @@ namespace open_atmos
}

// parse all of the phases at once
auto phases_parsing = ParsePhases(object["phases"], species_parsing.second);
auto phases_parsing = ParsePhases(object[validation::keys.phases], species_parsing.second);

if (phases_parsing.first != ConfigParseStatus::Success)
{
Expand All @@ -340,8 +493,19 @@ namespace open_atmos
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 };
}
Expand Down
12 changes: 11 additions & 1 deletion test/integration/test_json_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include(test_util)

create_standard_test(NAME parse_species SOURCES test_parse_species.cpp)
create_standard_test(NAME parse_phases SOURCES test_parse_phases.cpp)
create_standard_test(NAME parse_arrhenius SOURCES test_parse_arrhenius.cpp)

################################################################################
# Copy test data
Expand Down
Loading

0 comments on commit 6b714de

Please sign in to comment.