Skip to content

Commit

Permalink
Merge pull request #14 from open-atmos/parse_phases
Browse files Browse the repository at this point in the history
parsing phases
  • Loading branch information
mattldawson authored Jan 18, 2024
2 parents 76e9624 + fb6d30d commit 5b5db4a
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 15 deletions.
4 changes: 3 additions & 1 deletion include/open_atmos/mechanism_configuration/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ namespace open_atmos
RequiredKeyNotFound,
MutuallyExclusiveOption,
InvalidVersion,
DuplicateSpeciesDetected
DuplicateSpeciesDetected,
DuplicatePhasesDetected,
PhaseRequiresUnknownSpecies,
};
std::string configParseStatusToString(const ConfigParseStatus &status);

Expand Down
8 changes: 4 additions & 4 deletions include/open_atmos/mechanism_configuration/validation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ namespace open_atmos

struct Phase
{
const std::vector<std::string> required_keys {};
const std::vector<std::string> optional_keys {};
const std::vector<std::string> required_keys{ keys.name, keys.species };
const std::vector<std::string> optional_keys{};
} phase;

struct Mechanism
{
const std::vector<std::string> required_keys {};
const std::vector<std::string> optional_keys {};
const std::vector<std::string> required_keys{};
const std::vector<std::string> optional_keys{};
} mechanism;

} // namespace validation
Expand Down
5 changes: 4 additions & 1 deletion include/open_atmos/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ namespace open_atmos

struct Phase
{
std::string name;
std::vector<std::string> species;
std::unordered_map<std::string, std::string> unknown_properties;
};

struct Mechanism
{
std::string name; // optional
std::vector<types::Species> species;
std::unordered_map<std::string, types::Phase> phases;
std::vector<types::Phase> phases;
};

} // namespace types
Expand Down
89 changes: 84 additions & 5 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ 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";
default: return "Unknown";
}
}
Expand Down Expand Up @@ -140,13 +142,14 @@ namespace open_atmos
return ConfigParseStatus::Success;
}

bool ContainsOnlyUniqueSpecies(const std::vector<types::Species>& all_species)
template<typename T>
bool ContainsUniqueObjectsByName(const std::vector<T>& 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;
}
Expand All @@ -155,6 +158,21 @@ namespace open_atmos
return true;
}

bool PhaseRequiresUnknownSpecies(const std::vector<std::string> requested_species, const std::vector<types::Species>& 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<ConfigParseStatus, std::vector<types::Species>> ParseSpecies(const json& objects)
{
ConfigParseStatus status = ConfigParseStatus::Success;
Expand Down Expand Up @@ -197,12 +215,62 @@ namespace open_atmos
all_species.push_back(species);
}

if (!ContainsOnlyUniqueSpecies(all_species))
if (!ContainsUniqueObjectsByName<types::Species>(all_species))
status = ConfigParseStatus::DuplicateSpeciesDetected;

return { status, all_species };
}

std::pair<ConfigParseStatus, std::vector<types::Phase>> ParsePhases(const json& objects, const std::vector<types::Species> existing_species)
{
ConfigParseStatus status = ConfigParseStatus::Success;
std::vector<types::Phase> 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::string>();

std::vector<std::string> 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<std::string, std::string> 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 (PhaseRequiresUnknownSpecies(species, existing_species))
{
status = ConfigParseStatus::PhaseRequiresUnknownSpecies;
break;
}

all_phases.push_back(phase);
}

if (status == ConfigParseStatus::Success && !ContainsUniqueObjectsByName<types::Phase>(all_phases))
status = ConfigParseStatus::DuplicatePhasesDetected;

return { status, all_phases };
}

std::pair<ConfigParseStatus, types::Mechanism> JsonParser::Parse(const std::string& file_path)
{
return JsonParser::Parse(std::filesystem::path(file_path));
Expand Down Expand Up @@ -262,7 +330,18 @@ 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["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;
}

mechanism.species = species_parsing.second;
mechanism.phases = phases_parsing.second;

return { status, mechanism };
}
Expand Down
1 change: 1 addition & 0 deletions test/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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)

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

#include <open_atmos/mechanism_configuration/parser.hpp>

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);
}
8 changes: 4 additions & 4 deletions test/unit/test_parse_species.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -38,23 +38,23 @@ 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);
}

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);
}

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);
}
32 changes: 32 additions & 0 deletions test/unit/unit_configs/phases/duplicate_phases.json
Original file line number Diff line number Diff line change
@@ -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": [ ]
}
26 changes: 26 additions & 0 deletions test/unit/unit_configs/phases/invalid_key.json
Original file line number Diff line number Diff line change
@@ -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": [ ]
}
12 changes: 12 additions & 0 deletions test/unit/unit_configs/phases/missing_required_key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1.0.0",
"name": "Missing required phases key configuration",
"species": [
],
"phases": [
{
"name": "gas"
}
],
"reactions": [ ]
}
26 changes: 26 additions & 0 deletions test/unit/unit_configs/phases/unknown_species.json
Original file line number Diff line number Diff line change
@@ -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": [ ]
}
Loading

0 comments on commit 5b5db4a

Please sign in to comment.