Skip to content

Commit

Permalink
Merge pull request #28 from open-atmos/parse_first_order_loss
Browse files Browse the repository at this point in the history
Parse first order loss
  • Loading branch information
K20shores authored Jan 19, 2024
2 parents 94db45a + dc129fe commit 80da0d7
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 2 deletions.
2 changes: 1 addition & 1 deletion examples/full_configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
{
"type": "FIRST_ORDER_LOSS",
"gas phase": "gas",
"products": [
"reactants": [
{
"species name": "C",
"coefficient": 1
Expand Down
3 changes: 2 additions & 1 deletion include/open_atmos/mechanism_configuration/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ namespace open_atmos
DuplicatePhasesDetected,
PhaseRequiresUnknownSpecies,
ReactionRequiresUnknownSpecies,
UnknownPhase
UnknownPhase,
TooManyReactionComponents
};
std::string configParseStatusToString(const ConfigParseStatus &status);

Expand Down
10 changes: 10 additions & 0 deletions include/open_atmos/mechanism_configuration/validation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ namespace open_atmos
const std::string Emission_key = "EMISSION";
// also scaling factor

// First Order Loss
const std::string FirstOrderLoss_key = "FIRST_ORDER_LOSS";
// also scaling factor

} keys;

struct Configuration
Expand Down Expand Up @@ -169,6 +173,12 @@ namespace open_atmos
const std::vector<std::string> optional_keys{ keys.name, keys.scaling_factor };
} emission;

struct FirstOrderLoss
{
const std::vector<std::string> required_keys{ keys.reactants, keys.type, keys.gas_phase };
const std::vector<std::string> optional_keys{ keys.name, keys.scaling_factor };
} first_order_loss;

struct Mechanism
{
const std::vector<std::string> required_keys{};
Expand Down
15 changes: 15 additions & 0 deletions include/open_atmos/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ namespace open_atmos
std::unordered_map<std::string, std::string> unknown_properties;
};

struct FirstOrderLoss
{
/// @brief Scaling factor to apply to user-provided rate constants
double scaling_factor_{ 1.0 };
/// @brief A list of reactants
std::vector<ReactionComponent> reactants;
/// @brief An identifier, optional, uniqueness not enforced
std::string name;
/// @brief An identifier indicating which gas phase this reaction takes place in
std::string gas_phase;
/// @brief Unknown properties, prefixed with two underscores (__)
std::unordered_map<std::string, std::string> unknown_properties;
};

struct Reactions
{
std::vector<types::Arrhenius> arrhenius;
Expand All @@ -191,6 +205,7 @@ namespace open_atmos
std::vector<types::Surface> surface;
std::vector<types::Photolysis> photolysis;
std::vector<types::Emission> emission;
std::vector<types::FirstOrderLoss> first_order_loss;
};

struct Mechanism
Expand Down
82 changes: 82 additions & 0 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace open_atmos
case ConfigParseStatus::PhaseRequiresUnknownSpecies: return "PhaseRequiresUnknownSpecies";
case ConfigParseStatus::ReactionRequiresUnknownSpecies: return "ReactionRequiresUnknownSpecies";
case ConfigParseStatus::UnknownPhase: return "UnknownPhase";
case ConfigParseStatus::TooManyReactionComponents: return "TooManyReactionComponents";
default: return "Unknown";
}
}
Expand Down Expand Up @@ -944,6 +945,77 @@ namespace open_atmos
return { status, emission };
}

std::pair<ConfigParseStatus, types::FirstOrderLoss> ParseFirstOrderLoss(const json& object, const std::vector<types::Species> existing_species, const std::vector<types::Phase> existing_phases)
{
ConfigParseStatus status = ConfigParseStatus::Success;
types::FirstOrderLoss first_order_loss;

status = ValidateSchema(object, validation::first_order_loss.required_keys, validation::first_order_loss.optional_keys);
if (status == ConfigParseStatus::Success)
{
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.scaling_factor))
{
first_order_loss.scaling_factor_ = object[validation::keys.scaling_factor].get<double>();
}

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

auto comments = GetComments(object, validation::first_order_loss.required_keys, validation::first_order_loss.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 : reactants)
{
requested_species.push_back(spec.species_name);
}

if (status == ConfigParseStatus::Success && RequiresUnknownSpecies(requested_species, existing_species))
{
status = ConfigParseStatus::ReactionRequiresUnknownSpecies;
}

std::string gas_phase = object[validation::keys.gas_phase].get<std::string>();
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;
}

if (status == ConfigParseStatus::Success && reactants.size() > 1)
{
status = ConfigParseStatus::TooManyReactionComponents;
}

first_order_loss.gas_phase = gas_phase;
first_order_loss.reactants = reactants;
first_order_loss.unknown_properties = unknown_properties;
}

return { status, first_order_loss };
}

std::pair<ConfigParseStatus, types::Reactions>
ParseReactions(const json& objects, const std::vector<types::Species>& existing_species, const std::vector<types::Phase>& existing_phases)
{
Expand Down Expand Up @@ -1023,6 +1095,16 @@ namespace open_atmos
}
reactions.emission.push_back(emission_parse.second);
}
else if (type == validation::keys.FirstOrderLoss_key)
{
auto first_order_loss_parse = ParseFirstOrderLoss(object, existing_species, existing_phases);
status = first_order_loss_parse.first;
if (status != ConfigParseStatus::Success)
{
break;
}
reactions.first_order_loss.push_back(first_order_loss_parse.second);
}
}

return { status, reactions };
Expand Down
1 change: 1 addition & 0 deletions test/integration/test_json_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ TEST(JsonParser, ParsesFullConfiguration)
EXPECT_EQ(mechanism.reactions.surface.size(), 1);
EXPECT_EQ(mechanism.reactions.photolysis.size(), 1);
EXPECT_EQ(mechanism.reactions.emission.size(), 1);
EXPECT_EQ(mechanism.reactions.first_order_loss.size(), 1);
}

TEST(JsonParser, ParserReportsBadFiles)
Expand Down
1 change: 1 addition & 0 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ create_standard_test(NAME parse_tunneling SOURCES test_parse_tunneling.cpp)
create_standard_test(NAME parse_surface SOURCES test_parse_surface.cpp)
create_standard_test(NAME parse_photolysis SOURCES test_parse_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)

################################################################################
# Copy test data
Expand Down
57 changes: 57 additions & 0 deletions test/unit/test_parse_first_order_loss.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <gtest/gtest.h>

#include <open_atmos/mechanism_configuration/parser.hpp>

using namespace open_atmos::mechanism_configuration;

TEST(JsonParser, CanParseValidFirstOrderLossReaction)
{
JsonParser parser;
auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/first_order_loss/valid.json"));
EXPECT_EQ(status, ConfigParseStatus::Success);

EXPECT_EQ(mechanism.reactions.first_order_loss.size(), 2);

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].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);
EXPECT_EQ(mechanism.reactions.first_order_loss[0].unknown_properties.size(), 1);
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].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);
}

TEST(JsonParser, FirstOrderLossDetectsUnknownSpecies)
{
JsonParser parser;
auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/first_order_loss/unknown_species.json"));
EXPECT_EQ(status, ConfigParseStatus::ReactionRequiresUnknownSpecies);
}

TEST(JsonParser, FirstOrderLossDetectsBadReactionComponent)
{
JsonParser parser;
auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/first_order_loss/bad_reaction_component.json"));
EXPECT_EQ(status, ConfigParseStatus::RequiredKeyNotFound);
}

TEST(JsonParser, FirstOrderLossDetectsUnknownPhase)
{
JsonParser parser;
auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/first_order_loss/missing_phase.json"));
EXPECT_EQ(status, ConfigParseStatus::UnknownPhase);
}

TEST(JsonParser, FirstOrderLossDetectsMoreThanOneSpecies)
{
JsonParser parser;
auto [status, mechanism] = parser.Parse(std::string("unit_configs/reactions/first_order_loss/too_many_reactants.json"));
EXPECT_EQ(status, ConfigParseStatus::TooManyReactionComponents);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"version": "1.0.0",
"name": "Bad reaction component",
"species": [
{
"name": "A"
},
{
"name": "B"
}
],
"phases": [
{
"name": "gas",
"species": [
"A",
"B"
]
}
],
"reactions": [
{
"type": "FIRST_ORDER_LOSS",
"gas phase": "gas",
"reactants": [
{
"Species name": "C"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"version": "1.0.0",
"name": "Missing phase",
"species": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
}
],
"phases": [ ],
"reactions": [
{
"type": "FIRST_ORDER_LOSS",
"gas phase": "gas",
"reactants": [
{
"species name": "C"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "1.0.0",
"name": "Too many reactants",
"species": [
{
"name": "A"
},
{
"name": "B"
},
{
"name": "C"
}
],
"phases": [
{
"name": "gas",
"species": [
"A",
"B",
"C"
]
}
],
"reactions": [
{
"type": "FIRST_ORDER_LOSS",
"gas phase": "gas",
"reactants": [
{
"species name": "C",
"coefficient": 1
},
{
"species name": "B",
"coefficient": 1
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"version": "1.0.0",
"name": "Unknown species",
"species": [
{
"name": "A"
},
{
"name": "B"
}
],
"phases": [
{
"name": "gas",
"species": [
"A",
"B"
]
}
],
"reactions": [
{
"type": "FIRST_ORDER_LOSS",
"gas phase": "gas",
"reactants": [
{
"species name": "C"
}
]
}
]
}
Loading

0 comments on commit 80da0d7

Please sign in to comment.