From 960eb693a02a69fa650340ac7565a9dcb61f60a3 Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 13:27:05 -0600 Subject: [PATCH 1/7] organize types --- include/mechanism_configuration/mechanism.hpp | 6 +- .../mechanism_configuration.hpp | 6 +- .../{aerosol_types.hpp => types/aerosol.hpp} | 2 +- .../types/emissions.hpp | 96 ++++++++++++++ .../{types.hpp => types/reactions.hpp} | 123 ------------------ .../mechanism_configuration/types/species.hpp | 50 +++++++ src/detail/v0/parser_types.hpp | 2 +- src/detail/v1/aerosol_type_parsers.hpp | 5 +- src/detail/v1/emissions_parser.hpp | 2 +- src/detail/v1/reaction_parsers.hpp | 3 +- src/detail/v1/type_parsers.hpp | 3 +- src/detail/v1/type_schema.hpp | 2 +- src/detail/v1/utils.hpp | 2 +- src/v1/reactions/arrhenius.cpp | 3 +- src/v1/reactions/branched.cpp | 3 +- src/v1/reactions/emission.cpp | 3 +- src/v1/reactions/first_order_loss.cpp | 3 +- src/v1/reactions/lambda_rate_constant.cpp | 3 +- src/v1/reactions/photolysis.cpp | 3 +- src/v1/reactions/surface.cpp | 3 +- src/v1/reactions/taylor_series.cpp | 3 +- .../reactions/ternary_chemical_activation.cpp | 3 +- src/v1/reactions/troe.cpp | 3 +- src/v1/reactions/tunneling.cpp | 3 +- src/v1/reactions/user_defined.cpp | 3 +- 25 files changed, 190 insertions(+), 148 deletions(-) rename include/mechanism_configuration/{aerosol_types.hpp => types/aerosol.hpp} (99%) create mode 100644 include/mechanism_configuration/types/emissions.hpp rename include/mechanism_configuration/{types.hpp => types/reactions.hpp} (79%) create mode 100644 include/mechanism_configuration/types/species.hpp diff --git a/include/mechanism_configuration/mechanism.hpp b/include/mechanism_configuration/mechanism.hpp index 87b84416..5e8dd627 100644 --- a/include/mechanism_configuration/mechanism.hpp +++ b/include/mechanism_configuration/mechanism.hpp @@ -4,8 +4,10 @@ #pragma once -#include -#include +#include +#include +#include +#include #include #include diff --git a/include/mechanism_configuration/mechanism_configuration.hpp b/include/mechanism_configuration/mechanism_configuration.hpp index d3fe961b..06bf87fe 100644 --- a/include/mechanism_configuration/mechanism_configuration.hpp +++ b/include/mechanism_configuration/mechanism_configuration.hpp @@ -4,10 +4,12 @@ #pragma once -#include #include #include #include -#include +#include +#include +#include +#include #include #include diff --git a/include/mechanism_configuration/aerosol_types.hpp b/include/mechanism_configuration/types/aerosol.hpp similarity index 99% rename from include/mechanism_configuration/aerosol_types.hpp rename to include/mechanism_configuration/types/aerosol.hpp index 0574163f..ee75878f 100644 --- a/include/mechanism_configuration/aerosol_types.hpp +++ b/include/mechanism_configuration/types/aerosol.hpp @@ -4,7 +4,7 @@ #pragma once -#include +#include #include #include diff --git a/include/mechanism_configuration/types/emissions.hpp b/include/mechanism_configuration/types/emissions.hpp new file mode 100644 index 00000000..5ffe2771 --- /dev/null +++ b/include/mechanism_configuration/types/emissions.hpp @@ -0,0 +1,96 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace mechanism_configuration::types +{ + + struct SpeciesMapping + { + std::string inventory_species; + std::string mechanism_species; + double scaling_factor{ 1.0 }; + }; + + struct SpeciesMap + { + std::string name; + std::vector mappings; + }; + + struct Inventory + { + std::string name; + std::string directory; + std::string file_pattern; + std::string convention; + }; + + enum class SourceMode + { + Offline, + }; + + enum class SourceType + { + Anthropogenic, + Fire, + Biogenic, + Dust, + SeaSalt, + Lightning, + }; + + enum class TemporalInterpolation + { + Linear, + Nearest, + None, + }; + + enum class VerticalInjection + { + Surface, + }; + + struct SourceDescriptor + { + std::string name; + SourceMode mode{ SourceMode::Offline }; + SourceType type{ SourceType::Anthropogenic }; + std::string inventory; + std::string species_map; + TemporalInterpolation temporal_interpolation{ TemporalInterpolation::Linear }; + VerticalInjection vertical_injection{ VerticalInjection::Surface }; + int category{ 0 }; + int hierarchy{ 1 }; + double scaling_factor{ 1.0 }; + std::string sector; + std::unordered_map unknown_properties; + }; + + enum class RegriddingType + { + None, + }; + + struct Regridding + { + RegriddingType type{ RegriddingType::None }; + }; + + struct EmissionsConfig + { + std::vector inventories; + std::vector species_maps; + Regridding regridding; + std::vector sources; + }; + +} // namespace mechanism_configuration::types diff --git a/include/mechanism_configuration/types.hpp b/include/mechanism_configuration/types/reactions.hpp similarity index 79% rename from include/mechanism_configuration/types.hpp rename to include/mechanism_configuration/types/reactions.hpp index d01ff133..b2586ef8 100644 --- a/include/mechanism_configuration/types.hpp +++ b/include/mechanism_configuration/types/reactions.hpp @@ -4,10 +4,6 @@ #pragma once -#include - -#include -#include #include #include #include @@ -15,41 +11,6 @@ namespace mechanism_configuration::types { - struct Species - { - std::string name; - std::optional absolute_tolerance; - std::optional diffusion_coefficient; - std::optional molecular_weight; - std::optional henrys_law_constant_298; - std::optional henrys_law_constant_exponential_factor; - std::optional n_star; - std::optional density; - std::optional tracer_type; - std::optional constant_concentration; - std::optional constant_mixing_ratio; - std::optional is_third_body; - /// @brief Unknown properties, prefixed with two underscores (__) - std::unordered_map unknown_properties; - }; - - struct PhaseSpecies - { - std::string name; - std::optional diffusion_coefficient; - std::optional density; - /// @brief Unknown properties, prefixed with two underscores (__) - std::unordered_map unknown_properties; - }; - - struct Phase - { - std::string name; - std::vector species; - /// @brief Unknown properties, prefixed with two underscores (__) - std::unordered_map unknown_properties; - }; - struct ReactionComponent { std::string name; @@ -327,88 +288,4 @@ namespace mechanism_configuration::types std::vector lambda_rate_constant; }; - // ── Emissions configuration types ───────────────────────────────────────── - - struct SpeciesMapping - { - std::string inventory_species; - std::string mechanism_species; - double scaling_factor{ 1.0 }; - }; - - struct SpeciesMap - { - std::string name; - std::vector mappings; - }; - - struct Inventory - { - std::string name; - std::string directory; - std::string file_pattern; - std::string convention; - }; - - enum class SourceMode - { - Offline, - }; - - enum class SourceType - { - Anthropogenic, - Fire, - Biogenic, - Dust, - SeaSalt, - Lightning, - }; - - enum class TemporalInterpolation - { - Linear, - Nearest, - None, - }; - - enum class VerticalInjection - { - Surface, - }; - - struct SourceDescriptor - { - std::string name; - SourceMode mode{ SourceMode::Offline }; - SourceType type{ SourceType::Anthropogenic }; - std::string inventory; - std::string species_map; - TemporalInterpolation temporal_interpolation{ TemporalInterpolation::Linear }; - VerticalInjection vertical_injection{ VerticalInjection::Surface }; - int category{ 0 }; - int hierarchy{ 1 }; - double scaling_factor{ 1.0 }; - std::string sector; - std::unordered_map unknown_properties; - }; - - enum class RegriddingType - { - None, - }; - - struct Regridding - { - RegriddingType type{ RegriddingType::None }; - }; - - struct EmissionsConfig - { - std::vector inventories; - std::vector species_maps; - Regridding regridding; - std::vector sources; - }; - } // namespace mechanism_configuration::types diff --git a/include/mechanism_configuration/types/species.hpp b/include/mechanism_configuration/types/species.hpp new file mode 100644 index 00000000..89a637cb --- /dev/null +++ b/include/mechanism_configuration/types/species.hpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +namespace mechanism_configuration::types +{ + + struct Species + { + std::string name; + std::optional absolute_tolerance; + std::optional diffusion_coefficient; + std::optional molecular_weight; + std::optional henrys_law_constant_298; + std::optional henrys_law_constant_exponential_factor; + std::optional n_star; + std::optional density; + std::optional tracer_type; + std::optional constant_concentration; + std::optional constant_mixing_ratio; + std::optional is_third_body; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + + struct PhaseSpecies + { + std::string name; + std::optional diffusion_coefficient; + std::optional density; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + + struct Phase + { + std::string name; + std::vector species; + /// @brief Unknown properties, prefixed with two underscores (__) + std::unordered_map unknown_properties; + }; + +} // namespace mechanism_configuration::types diff --git a/src/detail/v0/parser_types.hpp b/src/detail/v0/parser_types.hpp index 353f52ca..32924d43 100644 --- a/src/detail/v0/parser_types.hpp +++ b/src/detail/v0/parser_types.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include diff --git a/src/detail/v1/aerosol_type_parsers.hpp b/src/detail/v1/aerosol_type_parsers.hpp index 694a0b0e..98049e1d 100644 --- a/src/detail/v1/aerosol_type_parsers.hpp +++ b/src/detail/v1/aerosol_type_parsers.hpp @@ -4,8 +4,9 @@ #pragma once -#include -#include +#include +#include +#include #include diff --git a/src/detail/v1/emissions_parser.hpp b/src/detail/v1/emissions_parser.hpp index 038ca30e..f3bc1379 100644 --- a/src/detail/v1/emissions_parser.hpp +++ b/src/detail/v1/emissions_parser.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include diff --git a/src/detail/v1/reaction_parsers.hpp b/src/detail/v1/reaction_parsers.hpp index 41455c5f..d9832e49 100644 --- a/src/detail/v1/reaction_parsers.hpp +++ b/src/detail/v1/reaction_parsers.hpp @@ -5,7 +5,8 @@ #pragma once #include -#include +#include +#include #include #include diff --git a/src/detail/v1/type_parsers.hpp b/src/detail/v1/type_parsers.hpp index 51ef29e6..3182d6a8 100644 --- a/src/detail/v1/type_parsers.hpp +++ b/src/detail/v1/type_parsers.hpp @@ -5,7 +5,8 @@ #pragma once #include -#include +#include +#include #include diff --git a/src/detail/v1/type_schema.hpp b/src/detail/v1/type_schema.hpp index 7d5e9c6a..7b0bf134 100644 --- a/src/detail/v1/type_schema.hpp +++ b/src/detail/v1/type_schema.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include diff --git a/src/detail/v1/utils.hpp b/src/detail/v1/utils.hpp index 69ac0a48..383fd2e2 100644 --- a/src/detail/v1/utils.hpp +++ b/src/detail/v1/utils.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include #include diff --git a/src/v1/reactions/arrhenius.cpp b/src/v1/reactions/arrhenius.cpp index e35ec51a..95806229 100644 --- a/src/v1/reactions/arrhenius.cpp +++ b/src/v1/reactions/arrhenius.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/branched.cpp b/src/v1/reactions/branched.cpp index a6a9a13c..424afd99 100644 --- a/src/v1/reactions/branched.cpp +++ b/src/v1/reactions/branched.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/emission.cpp b/src/v1/reactions/emission.cpp index c5e94aad..5aab25bb 100644 --- a/src/v1/reactions/emission.cpp +++ b/src/v1/reactions/emission.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/first_order_loss.cpp b/src/v1/reactions/first_order_loss.cpp index 8a1d206f..e76a73a0 100644 --- a/src/v1/reactions/first_order_loss.cpp +++ b/src/v1/reactions/first_order_loss.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/lambda_rate_constant.cpp b/src/v1/reactions/lambda_rate_constant.cpp index 466b5ebe..549a9eb6 100644 --- a/src/v1/reactions/lambda_rate_constant.cpp +++ b/src/v1/reactions/lambda_rate_constant.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/photolysis.cpp b/src/v1/reactions/photolysis.cpp index 7a020b8e..42419285 100644 --- a/src/v1/reactions/photolysis.cpp +++ b/src/v1/reactions/photolysis.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/surface.cpp b/src/v1/reactions/surface.cpp index 7927f5dd..eab12bec 100644 --- a/src/v1/reactions/surface.cpp +++ b/src/v1/reactions/surface.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/taylor_series.cpp b/src/v1/reactions/taylor_series.cpp index 7a8f821d..a119228e 100644 --- a/src/v1/reactions/taylor_series.cpp +++ b/src/v1/reactions/taylor_series.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/ternary_chemical_activation.cpp b/src/v1/reactions/ternary_chemical_activation.cpp index 8c3733f5..e059ac9f 100644 --- a/src/v1/reactions/ternary_chemical_activation.cpp +++ b/src/v1/reactions/ternary_chemical_activation.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/troe.cpp b/src/v1/reactions/troe.cpp index d154fcb7..95d5a26f 100644 --- a/src/v1/reactions/troe.cpp +++ b/src/v1/reactions/troe.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/tunneling.cpp b/src/v1/reactions/tunneling.cpp index 37a0bb5a..d4385709 100644 --- a/src/v1/reactions/tunneling.cpp +++ b/src/v1/reactions/tunneling.cpp @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 #include -#include +#include +#include #include #include diff --git a/src/v1/reactions/user_defined.cpp b/src/v1/reactions/user_defined.cpp index e76a9cb3..790cb9cf 100644 --- a/src/v1/reactions/user_defined.cpp +++ b/src/v1/reactions/user_defined.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include #include From fb89a4496558f54ce98147caef367b87121ee675 Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 13:40:01 -0600 Subject: [PATCH 2/7] move test utils functions under test --- CITATION.cff | 3 +++ cmake/test_util.cmake | 5 +++-- include/mechanism_configuration/errors.hpp | 7 ------- test/integration/test_parser.cpp | 2 ++ test/integration/test_v0_parser.cpp | 1 + test/integration/test_v1_parser.cpp | 1 + .../test_v1_read_from_file_configs.cpp | 2 ++ .../unit/emissions/v1/test_parse_emissions.cpp | 1 + test/unit/test_validate.cpp | 2 ++ test/unit/v0/test_arrhenius_config.cpp | 1 + test/unit/v0/test_branched_config.cpp | 1 + test/unit/v0/test_emission_config.cpp | 1 + test/unit/v0/test_first_order_loss_config.cpp | 1 + test/unit/v0/test_photolysis_config.cpp | 1 + test/unit/v0/test_surface_config.cpp | 1 + ...test_ternary_chemical_activation_config.cpp | 1 + test/unit/v0/test_troe_config.cpp | 1 + test/unit/v0/test_tunneling_config.cpp | 1 + test/unit/v0/test_user_defined_config.cpp | 1 + .../test_parse_from_file_configs.cpp | 2 ++ .../unit/v1/reactions/test_parse_arrhenius.cpp | 2 ++ test/unit/v1/reactions/test_parse_branched.cpp | 2 ++ test/unit/v1/reactions/test_parse_emission.cpp | 2 ++ .../reactions/test_parse_first_order_loss.cpp | 2 ++ .../test_parse_lambda_rate_constant.cpp | 2 ++ .../v1/reactions/test_parse_photolysis.cpp | 2 ++ test/unit/v1/reactions/test_parse_surface.cpp | 2 ++ .../v1/reactions/test_parse_taylor_series.cpp | 2 ++ .../test_parse_ternary_chemical_activation.cpp | 1 + test/unit/v1/reactions/test_parse_troe.cpp | 2 ++ .../unit/v1/reactions/test_parse_tunneling.cpp | 2 ++ .../v1/reactions/test_parse_user_defined.cpp | 2 ++ test/unit/v1/test_parse_aerosol.cpp | 2 ++ test/unit/v1/test_parse_phases.cpp | 2 ++ test/unit/v1/test_parse_species.cpp | 2 ++ test/utils/print.hpp | 18 ++++++++++++++++++ 36 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 test/utils/print.hpp diff --git a/CITATION.cff b/CITATION.cff index f756f514..5107c7f4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -12,5 +12,8 @@ authors: given-names: Jiwon - family-names: Thind given-names: Montek + - family-names: Meech + given-names: Scott + license: Apache-2.0 url: "https://github.com/NCAR/MechanismConfiguration" \ No newline at end of file diff --git a/cmake/test_util.cmake b/cmake/test_util.cmake index 828d371c..1004f77e 100644 --- a/cmake/test_util.cmake +++ b/cmake/test_util.cmake @@ -13,9 +13,10 @@ function(create_standard_test) add_executable(test_${TEST_NAME} ${TEST_SOURCES}) target_link_libraries(test_${TEST_NAME} PUBLIC musica::mechanism_configuration GTest::gtest_main) - target_include_directories(test_${TEST_NAME} - PUBLIC + target_include_directories(test_${TEST_NAME} + PUBLIC ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/test ) diff --git a/include/mechanism_configuration/errors.hpp b/include/mechanism_configuration/errors.hpp index 97cea53c..23a91177 100644 --- a/include/mechanism_configuration/errors.hpp +++ b/include/mechanism_configuration/errors.hpp @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include @@ -67,11 +66,5 @@ namespace mechanism_configuration std::string ErrorCodeToString(const ErrorCode& status); - // For Google Test printing - inline void PrintTo(const ErrorCode& status, std::ostream* os) - { - *os << ErrorCodeToString(status); - } - using Errors = std::vector>; } // namespace mechanism_configuration \ No newline at end of file diff --git a/test/integration/test_parser.cpp b/test/integration/test_parser.cpp index 9616d497..d9720cea 100644 --- a/test/integration/test_parser.cpp +++ b/test/integration/test_parser.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/integration/test_v0_parser.cpp b/test/integration/test_v0_parser.cpp index 2a0279a7..9b7a011e 100644 --- a/test/integration/test_v0_parser.cpp +++ b/test/integration/test_v0_parser.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/integration/test_v1_parser.cpp b/test/integration/test_v1_parser.cpp index 5a23a58c..355e0bec 100644 --- a/test/integration/test_v1_parser.cpp +++ b/test/integration/test_v1_parser.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "detail/v1/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/integration/test_v1_read_from_file_configs.cpp b/test/integration/test_v1_read_from_file_configs.cpp index 86c926aa..5e62ef14 100644 --- a/test/integration/test_v1_read_from_file_configs.cpp +++ b/test/integration/test_v1_read_from_file_configs.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/emissions/v1/test_parse_emissions.cpp b/test/unit/emissions/v1/test_parse_emissions.cpp index 152eba03..60610324 100644 --- a/test/unit/emissions/v1/test_parse_emissions.cpp +++ b/test/unit/emissions/v1/test_parse_emissions.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "detail/v1/parser.hpp" +#include "utils/print.hpp" #include #include diff --git a/test/unit/test_validate.cpp b/test/unit/test_validate.cpp index d13d3cc1..a648fe40 100644 --- a/test/unit/test_validate.cpp +++ b/test/unit/test_validate.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v0/test_arrhenius_config.cpp b/test/unit/v0/test_arrhenius_config.cpp index f32fa65d..2542f071 100644 --- a/test/unit/v0/test_arrhenius_config.cpp +++ b/test/unit/v0/test_arrhenius_config.cpp @@ -5,6 +5,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_branched_config.cpp b/test/unit/v0/test_branched_config.cpp index 574f5df0..8f0281a3 100644 --- a/test/unit/v0/test_branched_config.cpp +++ b/test/unit/v0/test_branched_config.cpp @@ -5,6 +5,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_emission_config.cpp b/test/unit/v0/test_emission_config.cpp index 2cf2ea10..3614259a 100644 --- a/test/unit/v0/test_emission_config.cpp +++ b/test/unit/v0/test_emission_config.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_first_order_loss_config.cpp b/test/unit/v0/test_first_order_loss_config.cpp index 5e3a60d9..9d10be7c 100644 --- a/test/unit/v0/test_first_order_loss_config.cpp +++ b/test/unit/v0/test_first_order_loss_config.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_photolysis_config.cpp b/test/unit/v0/test_photolysis_config.cpp index 52585318..e4f95812 100644 --- a/test/unit/v0/test_photolysis_config.cpp +++ b/test/unit/v0/test_photolysis_config.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_surface_config.cpp b/test/unit/v0/test_surface_config.cpp index bff6b80d..eb39ca4e 100644 --- a/test/unit/v0/test_surface_config.cpp +++ b/test/unit/v0/test_surface_config.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_ternary_chemical_activation_config.cpp b/test/unit/v0/test_ternary_chemical_activation_config.cpp index c85edf72..40f7ba2a 100644 --- a/test/unit/v0/test_ternary_chemical_activation_config.cpp +++ b/test/unit/v0/test_ternary_chemical_activation_config.cpp @@ -5,6 +5,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_troe_config.cpp b/test/unit/v0/test_troe_config.cpp index e26c8b74..f7355f85 100644 --- a/test/unit/v0/test_troe_config.cpp +++ b/test/unit/v0/test_troe_config.cpp @@ -5,6 +5,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_tunneling_config.cpp b/test/unit/v0/test_tunneling_config.cpp index c2b86f63..ed93c767 100644 --- a/test/unit/v0/test_tunneling_config.cpp +++ b/test/unit/v0/test_tunneling_config.cpp @@ -5,6 +5,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v0/test_user_defined_config.cpp b/test/unit/v0/test_user_defined_config.cpp index 3bebd65a..785ec52e 100644 --- a/test/unit/v0/test_user_defined_config.cpp +++ b/test/unit/v0/test_user_defined_config.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/v0/parser.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v1/file_configs/test_parse_from_file_configs.cpp b/test/unit/v1/file_configs/test_parse_from_file_configs.cpp index dd0be2c9..108ef591 100644 --- a/test/unit/v1/file_configs/test_parse_from_file_configs.cpp +++ b/test/unit/v1/file_configs/test_parse_from_file_configs.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_arrhenius.cpp b/test/unit/v1/reactions/test_parse_arrhenius.cpp index 44261204..56fecb35 100644 --- a/test/unit/v1/reactions/test_parse_arrhenius.cpp +++ b/test/unit/v1/reactions/test_parse_arrhenius.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_branched.cpp b/test/unit/v1/reactions/test_parse_branched.cpp index ea201cbf..586b8285 100644 --- a/test/unit/v1/reactions/test_parse_branched.cpp +++ b/test/unit/v1/reactions/test_parse_branched.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_emission.cpp b/test/unit/v1/reactions/test_parse_emission.cpp index 6f1cde84..b60d5c31 100644 --- a/test/unit/v1/reactions/test_parse_emission.cpp +++ b/test/unit/v1/reactions/test_parse_emission.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_first_order_loss.cpp b/test/unit/v1/reactions/test_parse_first_order_loss.cpp index de8f2238..e05fcf49 100644 --- a/test/unit/v1/reactions/test_parse_first_order_loss.cpp +++ b/test/unit/v1/reactions/test_parse_first_order_loss.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_lambda_rate_constant.cpp b/test/unit/v1/reactions/test_parse_lambda_rate_constant.cpp index b39a32a8..7dae8ad7 100644 --- a/test/unit/v1/reactions/test_parse_lambda_rate_constant.cpp +++ b/test/unit/v1/reactions/test_parse_lambda_rate_constant.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_photolysis.cpp b/test/unit/v1/reactions/test_parse_photolysis.cpp index c36e91cc..129a8479 100644 --- a/test/unit/v1/reactions/test_parse_photolysis.cpp +++ b/test/unit/v1/reactions/test_parse_photolysis.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_surface.cpp b/test/unit/v1/reactions/test_parse_surface.cpp index 57262b67..c78d1828 100644 --- a/test/unit/v1/reactions/test_parse_surface.cpp +++ b/test/unit/v1/reactions/test_parse_surface.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_taylor_series.cpp b/test/unit/v1/reactions/test_parse_taylor_series.cpp index 0f9c28b7..ab36a3a7 100644 --- a/test/unit/v1/reactions/test_parse_taylor_series.cpp +++ b/test/unit/v1/reactions/test_parse_taylor_series.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_ternary_chemical_activation.cpp b/test/unit/v1/reactions/test_parse_ternary_chemical_activation.cpp index df198197..a91ef2c5 100644 --- a/test/unit/v1/reactions/test_parse_ternary_chemical_activation.cpp +++ b/test/unit/v1/reactions/test_parse_ternary_chemical_activation.cpp @@ -4,6 +4,7 @@ #include "detail/constants.hpp" #include "detail/conversions.hpp" +#include "utils/print.hpp" #include diff --git a/test/unit/v1/reactions/test_parse_troe.cpp b/test/unit/v1/reactions/test_parse_troe.cpp index 8054e9d4..b6777e7a 100644 --- a/test/unit/v1/reactions/test_parse_troe.cpp +++ b/test/unit/v1/reactions/test_parse_troe.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_tunneling.cpp b/test/unit/v1/reactions/test_parse_tunneling.cpp index ec142d1f..c1d60e8e 100644 --- a/test/unit/v1/reactions/test_parse_tunneling.cpp +++ b/test/unit/v1/reactions/test_parse_tunneling.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/reactions/test_parse_user_defined.cpp b/test/unit/v1/reactions/test_parse_user_defined.cpp index 98bc7e2b..648f398c 100644 --- a/test/unit/v1/reactions/test_parse_user_defined.cpp +++ b/test/unit/v1/reactions/test_parse_user_defined.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/test_parse_aerosol.cpp b/test/unit/v1/test_parse_aerosol.cpp index b448f619..2102e135 100644 --- a/test/unit/v1/test_parse_aerosol.cpp +++ b/test/unit/v1/test_parse_aerosol.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/test_parse_phases.cpp b/test/unit/v1/test_parse_phases.cpp index 57364e5c..6018e4e4 100644 --- a/test/unit/v1/test_parse_phases.cpp +++ b/test/unit/v1/test_parse_phases.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/unit/v1/test_parse_species.cpp b/test/unit/v1/test_parse_species.cpp index e3827a4b..c105fdf4 100644 --- a/test/unit/v1/test_parse_species.cpp +++ b/test/unit/v1/test_parse_species.cpp @@ -2,6 +2,8 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 +#include "utils/print.hpp" + #include #include diff --git a/test/utils/print.hpp b/test/utils/print.hpp new file mode 100644 index 00000000..0fa4dcff --- /dev/null +++ b/test/utils/print.hpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include + +namespace mechanism_configuration +{ + // For Google Test printing + inline void PrintTo(const ErrorCode& status, std::ostream* os) + { + *os << ErrorCodeToString(status); + } +} // namespace mechanism_configuration From ee224a2c10b73e0509bc41d715f2a304f4db7df5 Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 13:46:50 -0600 Subject: [PATCH 3/7] move version out of mechansim definition --- include/mechanism_configuration/mechanism.hpp | 61 +--------------- .../mechanism_version.hpp | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+), 60 deletions(-) create mode 100644 include/mechanism_configuration/mechanism_version.hpp diff --git a/include/mechanism_configuration/mechanism.hpp b/include/mechanism_configuration/mechanism.hpp index 5e8dd627..d3ce69ee 100644 --- a/include/mechanism_configuration/mechanism.hpp +++ b/include/mechanism_configuration/mechanism.hpp @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -17,66 +18,6 @@ namespace mechanism_configuration { -// glibc defines `major` and `minor` as macros, which conflict with the `Version` struct fields. -// Temporarily undefine the macros before the struct definition and restore them afterward. -// https://stackoverflow.com/a/22253389/5217293 -#pragma push_macro("major") -#undef major -#pragma push_macro("minor") -#undef minor - - struct Version - { - unsigned int major; - unsigned int minor; - unsigned int patch; - - Version() - : major(0), - minor(0), - patch(0) - { - } - - Version(unsigned int major, unsigned int minor, unsigned int patch) - : major(major), - minor(minor), - patch(patch) - { - } - - Version(std::string version) - { - std::string delimiter = "."; - size_t pos = 0; - int i = 0; - while ((pos = version.find(delimiter)) != std::string::npos) - { - std::string token = version.substr(0, pos); - switch (i) - { - case 0: major = std::stoi(token); break; - case 1: minor = std::stoi(token); break; - } - version.erase(0, pos + delimiter.length()); - i++; - } - // The remaining part is the patch version - if (!version.empty()) - { - patch = std::stoi(version); - } - } - - std::string to_string() const - { - return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); - } - }; - -#pragma pop_macro("minor") -#pragma pop_macro("major") - /// @brief Represents a full mechanism definition struct Mechanism { diff --git a/include/mechanism_configuration/mechanism_version.hpp b/include/mechanism_configuration/mechanism_version.hpp new file mode 100644 index 00000000..021abab9 --- /dev/null +++ b/include/mechanism_configuration/mechanism_version.hpp @@ -0,0 +1,71 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace mechanism_configuration +{ +// glibc defines `major` and `minor` as macros, which conflict with the `Version` struct fields. +// Temporarily undefine the macros before the struct definition and restore them afterward. +// https://stackoverflow.com/a/22253389/5217293 +#pragma push_macro("major") +#undef major +#pragma push_macro("minor") +#undef minor + + struct Version + { + unsigned int major; + unsigned int minor; + unsigned int patch; + + Version() + : major(0), + minor(0), + patch(0) + { + } + + Version(unsigned int major, unsigned int minor, unsigned int patch) + : major(major), + minor(minor), + patch(patch) + { + } + + Version(std::string version) + { + std::string delimiter = "."; + size_t pos = 0; + int i = 0; + while ((pos = version.find(delimiter)) != std::string::npos) + { + std::string token = version.substr(0, pos); + switch (i) + { + case 0: major = std::stoi(token); break; + case 1: minor = std::stoi(token); break; + } + version.erase(0, pos + delimiter.length()); + i++; + } + // The remaining part is the patch version + if (!version.empty()) + { + patch = std::stoi(version); + } + } + + std::string to_string() const + { + return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); + } + }; + +#pragma pop_macro("minor") +#pragma pop_macro("major") + +} // namespace mechanism_configuration From dd35e2b8ab1d918f774250cc8932c610b508e56a Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 14:02:30 -0600 Subject: [PATCH 4/7] make aerosol as an optional mechanism --- include/mechanism_configuration/mechanism.hpp | 20 ++++++++++-------- src/validate.cpp | 5 ++++- test/integration/test_parser.cpp | 16 +++++++------- test/unit/test_validate.cpp | 21 ++++++++++--------- test/unit/v1/test_parse_aerosol.cpp | 3 ++- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/include/mechanism_configuration/mechanism.hpp b/include/mechanism_configuration/mechanism.hpp index d3ce69ee..41924b67 100644 --- a/include/mechanism_configuration/mechanism.hpp +++ b/include/mechanism_configuration/mechanism.hpp @@ -10,11 +10,9 @@ #include #include -#include -#include #include #include -#include +#include namespace mechanism_configuration { @@ -23,19 +21,23 @@ namespace mechanism_configuration { /// @brief Mechanism name (optional) std::string name; + /// @brief Version of the mechanism configuration format used, in major.minor.patch format + Version version; + /// @brief Relative tolerance for solver (optional, default: 1e-6) + double relative_tolerance{ 1e-6 }; + /// @brief Species list std::vector species; /// @brief Phases list std::vector phases; /// @brief Represents a collection of different reaction types, each stored in a vector /// corresponding to a specific mechanism + /// @note Not wrapped in optional: gas-phase chemistry is the default and requires reactions + /// to be present. Reactions may be empty when other chemistry (aerosol, etc.) is used + /// instead, but the field itself is always present. types::Reactions reactions; - /// @brief Aerosol representations, processes, and constraints - types::Aerosol aerosol; - /// @brief Version of the mechanism configuration format used, in major.minor.patch format - Version version; - /// @brief Relative tolerance for solver (optional, default: 1e-6) - double relative_tolerance{ 1e-6 }; + /// @brief Aerosol representations, processes, and constraints (optional) + std::optional aerosol; /// @brief Emissions configuration (optional) std::optional emissions; }; diff --git a/src/validate.cpp b/src/validate.cpp index db205b71..8318617c 100644 --- a/src/validate.cpp +++ b/src/validate.cpp @@ -311,7 +311,10 @@ namespace mechanism_configuration { Errors errors; - const auto& aerosol = mechanism.aerosol; + if (!mechanism.aerosol) + return errors; + + const auto& aerosol = *mechanism.aerosol; if (aerosol.representations.empty() && aerosol.processes.empty() && aerosol.constraints.empty()) return errors; diff --git a/test/integration/test_parser.cpp b/test/integration/test_parser.cpp index d9720cea..ea7e4a32 100644 --- a/test/integration/test_parser.cpp +++ b/test/integration/test_parser.cpp @@ -88,31 +88,33 @@ TEST(Parse, ParsesCamCloudChemistryAerosolConfiguration) EXPECT_EQ(mechanism.species.size(), 10u); EXPECT_EQ(mechanism.phases.size(), 2u); + ASSERT_TRUE(mechanism.aerosol.has_value()); + // One UNIFORM_SECTION representation. - ASSERT_EQ(mechanism.aerosol.representations.size(), 1u); - const auto& cloud = std::get(mechanism.aerosol.representations[0]); + ASSERT_EQ(mechanism.aerosol->representations.size(), 1u); + const auto& cloud = std::get(mechanism.aerosol->representations[0]); EXPECT_EQ(cloud.name, "CLOUD"); // Processes: one reversible reaction followed by one dissolved reaction. - ASSERT_EQ(mechanism.aerosol.processes.size(), 2u); - const auto& reversible = std::get(mechanism.aerosol.processes[0]); + ASSERT_EQ(mechanism.aerosol->processes.size(), 2u); + const auto& reversible = std::get(mechanism.aerosol->processes[0]); ASSERT_EQ(reversible.reactants.size(), 2u); EXPECT_EQ(reversible.reactants[0].name, "HSO3m"); // components keyed on "name" ASSERT_TRUE(reversible.equilibrium_constant.has_value()); EXPECT_DOUBLE_EQ(reversible.equilibrium_constant->A, 1725.0); // Constraints: 3 Henry's-law equilibria + 3 dissolved equilibria + 4 linear constraints. - ASSERT_EQ(mechanism.aerosol.constraints.size(), 10u); + ASSERT_EQ(mechanism.aerosol->constraints.size(), 10u); // The first constraint is the SO2 Henry's-law equilibrium; its solvent properties are sourced // from the species/phase definitions rather than the process block. - const auto& so2_equilibrium = std::get(mechanism.aerosol.constraints[0]); + const auto& so2_equilibrium = std::get(mechanism.aerosol->constraints[0]); EXPECT_EQ(so2_equilibrium.solvent, "H2O"); EXPECT_DOUBLE_EQ(so2_equilibrium.solvent_molecular_weight, 0.01801); // from the species section EXPECT_DOUBLE_EQ(so2_equilibrium.solvent_density, 997.0); // from the AQUEOUS phase // The first linear constraint's terms are keyed on "name". - const auto& linear = std::get(mechanism.aerosol.constraints[6]); + const auto& linear = std::get(mechanism.aerosol->constraints[6]); ASSERT_FALSE(linear.terms.empty()); EXPECT_EQ(linear.terms[0].phase, "gas"); EXPECT_EQ(linear.terms[0].name, "SO2"); diff --git a/test/unit/test_validate.cpp b/test/unit/test_validate.cpp index a648fe40..5cea810a 100644 --- a/test/unit/test_validate.cpp +++ b/test/unit/test_validate.cpp @@ -202,7 +202,8 @@ namespace section.phases = { "aqueous" }; section.min_radius = 1.0e-6; section.max_radius = 1.0e-5; - m.aerosol.representations = { section }; + m.aerosol = types::Aerosol{}; + m.aerosol->representations = { section }; return m; } @@ -238,7 +239,7 @@ TEST(ValidateAerosol, IgnoresMechanismWithoutAerosolSection) TEST(ValidateAerosol, AcceptsValidProcessesAndConstraints) { Mechanism m = AerosolBaseMechanism(); - m.aerosol.processes = { ValidPhaseTransfer() }; + m.aerosol->processes = { ValidPhaseTransfer() }; types::DissolvedReaction reaction; reaction.phase = "aqueous"; @@ -246,9 +247,9 @@ TEST(ValidateAerosol, AcceptsValidProcessesAndConstraints) reaction.reactants = { component("A") }; reaction.products = { component("A") }; reaction.rate_constants = { { "cloud", types::ArrheniusReferenceTemperature{} } }; // keyed by a declared representation - m.aerosol.processes.push_back(reaction); + m.aerosol->processes.push_back(reaction); - m.aerosol.constraints = { ValidEquilibrium() }; + m.aerosol->constraints = { ValidEquilibrium() }; EXPECT_TRUE(ValidateAerosolModel(m).empty()); } @@ -261,7 +262,7 @@ TEST(ValidateAerosol, DetectsRepresentationReferencingUnknownPhase) mode.phases = { "nonexistent" }; // no such phase mode.geometric_mean_radius = 1.0e-6; mode.geometric_standard_deviation = 1.6; - m.aerosol.representations.push_back(mode); + m.aerosol->representations.push_back(mode); EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::UnknownPhase)); } @@ -273,7 +274,7 @@ TEST(ValidateAerosol, DetectsSpeciesNotRegisteredInCondensedPhase) reaction.phase = "aqueous"; reaction.solvent = "H2O"; reaction.reactants = { component("Q") }; // Q is not registered in the aqueous phase - m.aerosol.processes = { reaction }; + m.aerosol->processes = { reaction }; EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::RequestedSpeciesNotRegisteredInPhase)); } @@ -287,7 +288,7 @@ TEST(ValidateAerosol, DetectsRateConstantKeyedByUnknownRepresentation) reaction.reactants = { component("A") }; reaction.products = { component("A") }; reaction.rate_constants = { { "not_a_representation", types::ArrheniusReferenceTemperature{} } }; - m.aerosol.processes = { reaction }; + m.aerosol->processes = { reaction }; EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::UnknownAerosolRepresentation)); } @@ -296,7 +297,7 @@ TEST(ValidateAerosol, DetectsMissingGasDiffusionCoefficient) { Mechanism m = AerosolBaseMechanism(); m.phases[0].species[0].diffusion_coefficient = std::nullopt; // Remove gas-phase A diffusion coefficient - m.aerosol.processes = { ValidPhaseTransfer() }; + m.aerosol->processes = { ValidPhaseTransfer() }; EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::RequiredKeyNotFound)); } @@ -305,7 +306,7 @@ TEST(ValidateAerosol, DetectsMissingSolventDensity) { Mechanism m = AerosolBaseMechanism(); m.phases[1].species[1].density = std::nullopt; // Remove aqueous H2O density - m.aerosol.constraints = { ValidEquilibrium() }; + m.aerosol->constraints = { ValidEquilibrium() }; EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::RequiredKeyNotFound)); } @@ -314,7 +315,7 @@ TEST(ValidateAerosol, DetectsMissingSolventMolecularWeight) { Mechanism m = AerosolBaseMechanism(); m.species[1].molecular_weight = std::nullopt; // Remove H2O molecular weight - m.aerosol.constraints = { ValidEquilibrium() }; + m.aerosol->constraints = { ValidEquilibrium() }; EXPECT_TRUE(HasCode(ValidateAerosolModel(m), ErrorCode::RequiredKeyNotFound)); } diff --git a/test/unit/v1/test_parse_aerosol.cpp b/test/unit/v1/test_parse_aerosol.cpp index 2102e135..d88644b2 100644 --- a/test/unit/v1/test_parse_aerosol.cpp +++ b/test/unit/v1/test_parse_aerosol.cpp @@ -36,7 +36,8 @@ TEST(ParseAerosol, ParsesValidAerosolConfiguration) ASSERT_TRUE(parsed); const Mechanism& mechanism = *parsed; - const auto& aerosol = mechanism.aerosol; + ASSERT_TRUE(mechanism.aerosol.has_value()); + const auto& aerosol = *mechanism.aerosol; // Two SINGLE_MOMENT_MODE representations. ASSERT_EQ(aerosol.representations.size(), 2u); From 6f64b5e161d3ab8c1929cd790d0fed463aefc99a Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 15:13:17 -0600 Subject: [PATCH 5/7] all caps --- src/detail/constants.hpp | 5 ++--- src/detail/conversions.hpp | 2 +- src/v0/arrhenius_parser.cpp | 4 ++-- src/v0/branched_parser.cpp | 2 +- src/v0/ternary_chemical_activation_parser.cpp | 4 ++-- src/v0/troe_parser.cpp | 4 ++-- src/v0/tunneling_parser.cpp | 2 +- src/v1/reactions/arrhenius.cpp | 2 +- src/v1/reactions/taylor_series.cpp | 2 +- test/unit/v0/test_arrhenius_config.cpp | 8 ++++---- test/unit/v0/test_branched_config.cpp | 4 ++-- test/unit/v0/test_ternary_chemical_activation_config.cpp | 8 ++++---- test/unit/v0/test_troe_config.cpp | 8 ++++---- test/unit/v0/test_tunneling_config.cpp | 4 ++-- 14 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/detail/constants.hpp b/src/detail/constants.hpp index 50c8751c..b660da81 100644 --- a/src/detail/constants.hpp +++ b/src/detail/constants.hpp @@ -8,8 +8,7 @@ namespace mechanism_configuration { 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} + static constexpr double BOLTZMANN = 1.380649e-23; // J K^{-1} + static constexpr double AVOGADRO = 6.02214076e23; // # mol^{-1} } // namespace constants } // namespace mechanism_configuration \ No newline at end of file diff --git a/src/detail/conversions.hpp b/src/detail/conversions.hpp index 7eb5c9d8..005c0d4c 100644 --- a/src/detail/conversions.hpp +++ b/src/detail/conversions.hpp @@ -10,6 +10,6 @@ namespace mechanism_configuration { namespace conversions { - constexpr double MolesM3ToMoleculesCm3 = 1.0e-6 * constants::avogadro; + constexpr double MOLES_M3_TO_MOLECULES_CM3 = 1.0e-6 * constants::AVOGADRO; } // namespace conversions } // namespace mechanism_configuration \ No newline at end of file diff --git a/src/v0/arrhenius_parser.cpp b/src/v0/arrhenius_parser.cpp index bba3b6e6..177b314f 100644 --- a/src/v0/arrhenius_parser.cpp +++ b/src/v0/arrhenius_parser.cpp @@ -40,7 +40,7 @@ namespace mechanism_configuration::v0 { total_moles += reactant.coefficient; } - parameters.A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles - 1); + parameters.A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles - 1); if (object[keys::B]) { parameters.B = object[keys::B].as(); @@ -69,7 +69,7 @@ namespace mechanism_configuration::v0 else { // Calculate 'C' using 'Ea' - parameters.C = -1 * object[keys::Ea].as() / constants::boltzmann; + parameters.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; } } diff --git a/src/v0/branched_parser.cpp b/src/v0/branched_parser.cpp index 2f1963f5..d5223215 100644 --- a/src/v0/branched_parser.cpp +++ b/src/v0/branched_parser.cpp @@ -44,7 +44,7 @@ namespace mechanism_configuration::v0 { total_moles += reactant.coefficient; } - parameters.X *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles - 1); + parameters.X *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles - 1); parameters.Y = object[keys::Y].as(); parameters.a0 = object[keys::A0].as(); parameters.n = object[keys::n].as(); diff --git a/src/v0/ternary_chemical_activation_parser.cpp b/src/v0/ternary_chemical_activation_parser.cpp index 89e5d56b..2d6ad292 100644 --- a/src/v0/ternary_chemical_activation_parser.cpp +++ b/src/v0/ternary_chemical_activation_parser.cpp @@ -43,7 +43,7 @@ namespace mechanism_configuration::v0 { total_moles += reactant.coefficient; } - parameters.k0_A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles); + parameters.k0_A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles); if (object[keys::K0_B]) { parameters.k0_B = object[keys::K0_B].as(); @@ -57,7 +57,7 @@ namespace mechanism_configuration::v0 parameters.kinf_A = object[keys::KINF_A].as(); } // Account for terms in denominator and exponent that include [M] but not other reactants - parameters.kinf_A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles - 1); + parameters.kinf_A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles - 1); if (object[keys::KINF_B]) { parameters.kinf_B = object[keys::KINF_B].as(); diff --git a/src/v0/troe_parser.cpp b/src/v0/troe_parser.cpp index fc2ad5a6..32855773 100644 --- a/src/v0/troe_parser.cpp +++ b/src/v0/troe_parser.cpp @@ -43,7 +43,7 @@ namespace mechanism_configuration::v0 { total_moles += reactant.coefficient; } - parameters.k0_A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles); + parameters.k0_A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles); if (object[keys::K0_B]) { parameters.k0_B = object[keys::K0_B].as(); @@ -57,7 +57,7 @@ namespace mechanism_configuration::v0 parameters.kinf_A = object[keys::KINF_A].as(); } // Account for terms in denominator and exponent that include [M] but not other reactants - parameters.kinf_A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles - 1); + parameters.kinf_A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles - 1); if (object[keys::KINF_B]) { parameters.kinf_B = object[keys::KINF_B].as(); diff --git a/src/v0/tunneling_parser.cpp b/src/v0/tunneling_parser.cpp index 423b8a73..79630a70 100644 --- a/src/v0/tunneling_parser.cpp +++ b/src/v0/tunneling_parser.cpp @@ -41,7 +41,7 @@ namespace mechanism_configuration::v0 { total_moles += reactant.coefficient; } - parameters.A *= std::pow(conversions::MolesM3ToMoleculesCm3, total_moles - 1); + parameters.A *= std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, total_moles - 1); if (object[keys::B]) { parameters.B = object[keys::B].as(); diff --git a/src/v1/reactions/arrhenius.cpp b/src/v1/reactions/arrhenius.cpp index 95806229..d4ca6f36 100644 --- a/src/v1/reactions/arrhenius.cpp +++ b/src/v1/reactions/arrhenius.cpp @@ -109,7 +109,7 @@ namespace mechanism_configuration } if (object[keys::Ea]) { - arrhenius.C = -1 * object[keys::Ea].as() / constants::boltzmann; + arrhenius.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; } if (object[keys::name]) { diff --git a/src/v1/reactions/taylor_series.cpp b/src/v1/reactions/taylor_series.cpp index a119228e..3d27fc36 100644 --- a/src/v1/reactions/taylor_series.cpp +++ b/src/v1/reactions/taylor_series.cpp @@ -110,7 +110,7 @@ namespace mechanism_configuration } if (object[keys::Ea]) { - taylor_series.C = -1 * object[keys::Ea].as() / constants::boltzmann; + taylor_series.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; } if (object[keys::taylor_coefficients]) { diff --git a/test/unit/v0/test_arrhenius_config.cpp b/test/unit/v0/test_arrhenius_config.cpp index 2542f071..7970eb1a 100644 --- a/test/unit/v0/test_arrhenius_config.cpp +++ b/test/unit/v0/test_arrhenius_config.cpp @@ -72,7 +72,7 @@ TEST(ArrheniusConfig, ParseConfig) EXPECT_EQ(mechanism.reactions.arrhenius[0].products[1].name, "baz"); EXPECT_EQ(mechanism.reactions.arrhenius[0].products[1].coefficient, 3.2); EXPECT_EQ( - mechanism.reactions.arrhenius[0].A, 1.0 * conversions::MolesM3ToMoleculesCm3 * conversions::MolesM3ToMoleculesCm3); + mechanism.reactions.arrhenius[0].A, 1.0 * conversions::MOLES_M3_TO_MOLECULES_CM3 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(mechanism.reactions.arrhenius[0].B, 0.0); EXPECT_EQ(mechanism.reactions.arrhenius[0].C, 0.0); EXPECT_EQ(mechanism.reactions.arrhenius[0].D, 300); @@ -89,7 +89,7 @@ TEST(ArrheniusConfig, ParseConfig) EXPECT_EQ(mechanism.reactions.arrhenius[1].products[0].coefficient, 0.5); EXPECT_EQ(mechanism.reactions.arrhenius[1].products[1].name, "foo"); EXPECT_EQ(mechanism.reactions.arrhenius[1].products[1].coefficient, 1.0); - EXPECT_EQ(mechanism.reactions.arrhenius[1].A, 32.1 * conversions::MolesM3ToMoleculesCm3); + EXPECT_EQ(mechanism.reactions.arrhenius[1].A, 32.1 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(mechanism.reactions.arrhenius[1].B, -2.3); EXPECT_EQ(mechanism.reactions.arrhenius[1].C, 102.3); EXPECT_EQ(mechanism.reactions.arrhenius[1].D, 63.4); @@ -106,9 +106,9 @@ TEST(ArrheniusConfig, ParseConfig) EXPECT_EQ(mechanism.reactions.arrhenius[2].products[0].coefficient, 0.5); EXPECT_EQ(mechanism.reactions.arrhenius[2].products[1].name, "foo"); EXPECT_EQ(mechanism.reactions.arrhenius[2].products[1].coefficient, 1.0); - EXPECT_EQ(mechanism.reactions.arrhenius[2].A, 32.1 * conversions::MolesM3ToMoleculesCm3); + EXPECT_EQ(mechanism.reactions.arrhenius[2].A, 32.1 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(mechanism.reactions.arrhenius[2].B, -2.3); - EXPECT_EQ(mechanism.reactions.arrhenius[2].C, -1 * 2e23 / constants::boltzmann); + EXPECT_EQ(mechanism.reactions.arrhenius[2].C, -1 * 2e23 / constants::BOLTZMANN); EXPECT_EQ(mechanism.reactions.arrhenius[2].D, 63.4); EXPECT_EQ(mechanism.reactions.arrhenius[2].E, -1.3); } diff --git a/test/unit/v0/test_branched_config.cpp b/test/unit/v0/test_branched_config.cpp index 8f0281a3..0664d771 100644 --- a/test/unit/v0/test_branched_config.cpp +++ b/test/unit/v0/test_branched_config.cpp @@ -82,7 +82,7 @@ TEST(BranchedConfig, ParseConfig) EXPECT_EQ(process_vector[0].reactants[0].coefficient, 1.0); EXPECT_EQ(process_vector[0].reactants[1].name, "quz"); EXPECT_EQ(process_vector[0].reactants[1].coefficient, 2.0); - EXPECT_EQ(process_vector[0].X, 12.3 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[0].X, 12.3 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[0].Y, 42.3); EXPECT_EQ(process_vector[0].a0, 1.0e-5); EXPECT_EQ(process_vector[0].n, 3); @@ -96,7 +96,7 @@ TEST(BranchedConfig, ParseConfig) EXPECT_EQ(process_vector[0].nitrate_products[0].coefficient, 1.0); // second reaction - EXPECT_EQ(process_vector[1].X, 0.32 * conversions::MolesM3ToMoleculesCm3); + EXPECT_EQ(process_vector[1].X, 0.32 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(process_vector[1].Y, 2.3e8); EXPECT_EQ(process_vector[1].a0, 0.423); EXPECT_EQ(process_vector[1].alkoxy_products.size(), 1); diff --git a/test/unit/v0/test_ternary_chemical_activation_config.cpp b/test/unit/v0/test_ternary_chemical_activation_config.cpp index 40f7ba2a..399e86b1 100644 --- a/test/unit/v0/test_ternary_chemical_activation_config.cpp +++ b/test/unit/v0/test_ternary_chemical_activation_config.cpp @@ -66,10 +66,10 @@ TEST(TernaryChemicalActivationConfig, ParseConfig) EXPECT_EQ(process_vector[0].products[0].coefficient, 1.0); EXPECT_EQ(process_vector[0].products[1].name, "baz"); EXPECT_EQ(process_vector[0].products[1].coefficient, 3.2); - EXPECT_EQ(process_vector[0].k0_A, 1.0 * std::pow(conversions::MolesM3ToMoleculesCm3, 3)); + EXPECT_EQ(process_vector[0].k0_A, 1.0 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 3)); EXPECT_EQ(process_vector[0].k0_B, 0.0); EXPECT_EQ(process_vector[0].k0_C, 0.0); - EXPECT_EQ(process_vector[0].kinf_A, 1.0 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[0].kinf_A, 1.0 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[0].kinf_B, 0.0); EXPECT_EQ(process_vector[0].kinf_C, 0.0); EXPECT_EQ(process_vector[0].Fc, 0.6); @@ -86,10 +86,10 @@ TEST(TernaryChemicalActivationConfig, ParseConfig) EXPECT_EQ(process_vector[1].products[0].coefficient, 0.5); EXPECT_EQ(process_vector[1].products[1].name, "foo"); EXPECT_EQ(process_vector[1].products[1].coefficient, 1.0); - EXPECT_EQ(process_vector[1].k0_A, 32.1 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[1].k0_A, 32.1 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[1].k0_B, -2.3); EXPECT_EQ(process_vector[1].k0_C, 102.3); - EXPECT_EQ(process_vector[1].kinf_A, 63.4 * std::pow(conversions::MolesM3ToMoleculesCm3, 1)); + EXPECT_EQ(process_vector[1].kinf_A, 63.4 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 1)); EXPECT_EQ(process_vector[1].kinf_B, -1.3); EXPECT_EQ(process_vector[1].kinf_C, 908.5); EXPECT_EQ(process_vector[1].Fc, 1.3); diff --git a/test/unit/v0/test_troe_config.cpp b/test/unit/v0/test_troe_config.cpp index f7355f85..22f0c0bb 100644 --- a/test/unit/v0/test_troe_config.cpp +++ b/test/unit/v0/test_troe_config.cpp @@ -66,10 +66,10 @@ TEST(TroeConfig, ParseConfig) EXPECT_EQ(process_vector[0].products[0].coefficient, 1.0); EXPECT_EQ(process_vector[0].products[1].name, "baz"); EXPECT_EQ(process_vector[0].products[1].coefficient, 3.2); - EXPECT_EQ(process_vector[0].k0_A, 1.0 * std::pow(conversions::MolesM3ToMoleculesCm3, 3)); + EXPECT_EQ(process_vector[0].k0_A, 1.0 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 3)); EXPECT_EQ(process_vector[0].k0_B, 0.0); EXPECT_EQ(process_vector[0].k0_C, 0.0); - EXPECT_EQ(process_vector[0].kinf_A, 1.0 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[0].kinf_A, 1.0 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[0].kinf_B, 0.0); EXPECT_EQ(process_vector[0].kinf_C, 0.0); EXPECT_EQ(process_vector[0].Fc, 0.6); @@ -86,10 +86,10 @@ TEST(TroeConfig, ParseConfig) EXPECT_EQ(process_vector[1].products[0].coefficient, 0.5); EXPECT_EQ(process_vector[1].products[1].name, "foo"); EXPECT_EQ(process_vector[1].products[1].coefficient, 1.0); - EXPECT_EQ(process_vector[1].k0_A, 32.1 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[1].k0_A, 32.1 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[1].k0_B, -2.3); EXPECT_EQ(process_vector[1].k0_C, 102.3); - EXPECT_EQ(process_vector[1].kinf_A, 63.4 * conversions::MolesM3ToMoleculesCm3); + EXPECT_EQ(process_vector[1].kinf_A, 63.4 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(process_vector[1].kinf_B, -1.3); EXPECT_EQ(process_vector[1].kinf_C, 908.5); EXPECT_EQ(process_vector[1].Fc, 1.3); diff --git a/test/unit/v0/test_tunneling_config.cpp b/test/unit/v0/test_tunneling_config.cpp index ed93c767..bc63c708 100644 --- a/test/unit/v0/test_tunneling_config.cpp +++ b/test/unit/v0/test_tunneling_config.cpp @@ -66,7 +66,7 @@ TEST(TunnelingConfig, ParseConfig) EXPECT_EQ(process_vector[0].products[0].coefficient, 1.0); EXPECT_EQ(process_vector[0].products[1].name, "baz"); EXPECT_EQ(process_vector[0].products[1].coefficient, 3.2); - EXPECT_EQ(process_vector[0].A, 1.0 * std::pow(conversions::MolesM3ToMoleculesCm3, 2)); + EXPECT_EQ(process_vector[0].A, 1.0 * std::pow(conversions::MOLES_M3_TO_MOLECULES_CM3, 2)); EXPECT_EQ(process_vector[0].B, 0.0); EXPECT_EQ(process_vector[0].C, 0.0); } @@ -81,7 +81,7 @@ TEST(TunnelingConfig, ParseConfig) EXPECT_EQ(process_vector[1].products[0].coefficient, 0.5); EXPECT_EQ(process_vector[1].products[1].name, "foo"); EXPECT_EQ(process_vector[1].products[1].coefficient, 1.0); - EXPECT_EQ(process_vector[1].A, 32.1 * conversions::MolesM3ToMoleculesCm3); + EXPECT_EQ(process_vector[1].A, 32.1 * conversions::MOLES_M3_TO_MOLECULES_CM3); EXPECT_EQ(process_vector[1].B, -2.3); EXPECT_EQ(process_vector[1].C, 102.3); } From 9e586af3ea5487e4c2a3827e3ca4588b5b91bb0d Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Tue, 7 Jul 2026 19:35:05 -0600 Subject: [PATCH 6/7] reorgnaize --- include/mechanism_configuration/validate.hpp | 9 +- src/CMakeLists.txt | 2 +- src/detail/{check_schema.hpp => schema.hpp} | 0 .../v1/{aerosol_keys.hpp => aerosol/keys.hpp} | 1 + .../parsers.hpp} | 0 .../schema.hpp} | 0 src/detail/v1/aerosol/utils.hpp | 40 +++ src/detail/v1/emissions/keys.hpp | 66 ++++ src/detail/v1/emissions/parsers.hpp | 20 ++ .../schema.hpp} | 8 - src/detail/v1/keys.hpp | 196 +---------- src/detail/v1/reactions/keys.hpp | 122 +++++++ .../parsers.hpp} | 25 +- .../{type_schema.hpp => reactions/schema.hpp} | 17 +- src/detail/v1/species/keys.hpp | 25 ++ src/detail/v1/species/parsers.hpp | 30 ++ src/detail/v1/species/schema.hpp | 31 ++ src/detail/v1/type_parsers.hpp | 52 --- src/detail/v1/utils.hpp | 37 +- src/{check_schema.cpp => schema.cpp} | 2 +- src/v0/arrhenius_parser.cpp | 2 +- src/v0/branched_parser.cpp | 2 +- src/v0/emission_parser.cpp | 2 +- src/v0/first_order_loss_parser.cpp | 2 +- src/v0/parser.cpp | 2 +- src/v0/photolysis_parser.cpp | 2 +- src/v0/species_parser.cpp | 2 +- src/v0/surface_parser.cpp | 2 +- src/v0/ternary_chemical_activation_parser.cpp | 2 +- src/v0/troe_parser.cpp | 2 +- src/v0/tunneling_parser.cpp | 2 +- src/v0/user_defined_reaction_parser.cpp | 2 +- src/v1/CMakeLists.txt | 10 +- src/v1/aerosol/CMakeLists.txt | 6 + src/v1/aerosol/parsers.cpp | 316 +++++++++++++++++ src/v1/aerosol/schema.cpp | 294 ++++++++++++++++ src/v1/aerosol/utils.cpp | 59 ++++ src/v1/aerosol_type_parsers.cpp | 323 ------------------ src/v1/aerosol_type_schema.cpp | 297 ---------------- src/v1/emissions/CMakeLists.txt | 5 + src/v1/emissions/parsers.cpp | 113 ++++++ .../schema.cpp} | 100 +----- src/v1/parser.cpp | 36 +- src/v1/reactions/CMakeLists.txt | 2 + src/v1/reactions/arrhenius.cpp | 189 +++++----- src/v1/reactions/branched.cpp | 161 +++++---- src/v1/reactions/emission.cpp | 103 +++--- src/v1/reactions/first_order_loss.cpp | 171 +++++----- src/v1/reactions/lambda_rate_constant.cpp | 117 ++++--- src/v1/reactions/parsers.cpp | 68 ++++ src/v1/reactions/photolysis.cpp | 185 +++++----- src/v1/reactions/schema.cpp | 95 ++++++ src/v1/reactions/surface.cpp | 197 ++++++----- src/v1/reactions/taylor_series.cpp | 211 ++++++------ .../reactions/ternary_chemical_activation.cpp | 177 +++++----- src/v1/reactions/troe.cpp | 177 +++++----- src/v1/reactions/tunneling.cpp | 135 ++++---- src/v1/reactions/user_defined.cpp | 119 ++++--- src/v1/species/CMakeLists.txt | 5 + src/v1/species/parsers.cpp | 99 ++++++ src/v1/species/schema.cpp | 76 +++++ src/v1/type_parsers.cpp | 156 --------- src/v1/type_schema.cpp | 154 --------- src/v1/utils.cpp | 148 +++----- src/validate.cpp | 71 ++-- 65 files changed, 2602 insertions(+), 2480 deletions(-) rename src/detail/{check_schema.hpp => schema.hpp} (100%) rename src/detail/v1/{aerosol_keys.hpp => aerosol/keys.hpp} (98%) rename src/detail/v1/{aerosol_type_parsers.hpp => aerosol/parsers.hpp} (100%) rename src/detail/v1/{aerosol_type_schema.hpp => aerosol/schema.hpp} (100%) create mode 100644 src/detail/v1/aerosol/utils.hpp create mode 100644 src/detail/v1/emissions/keys.hpp create mode 100644 src/detail/v1/emissions/parsers.hpp rename src/detail/v1/{emissions_parser.hpp => emissions/schema.hpp} (70%) create mode 100644 src/detail/v1/reactions/keys.hpp rename src/detail/v1/{reaction_parsers.hpp => reactions/parsers.hpp} (83%) rename src/detail/v1/{type_schema.hpp => reactions/schema.hpp} (66%) create mode 100644 src/detail/v1/species/keys.hpp create mode 100644 src/detail/v1/species/parsers.hpp create mode 100644 src/detail/v1/species/schema.hpp delete mode 100644 src/detail/v1/type_parsers.hpp rename src/{check_schema.cpp => schema.cpp} (99%) create mode 100644 src/v1/aerosol/CMakeLists.txt create mode 100644 src/v1/aerosol/parsers.cpp create mode 100644 src/v1/aerosol/schema.cpp create mode 100644 src/v1/aerosol/utils.cpp delete mode 100644 src/v1/aerosol_type_parsers.cpp delete mode 100644 src/v1/aerosol_type_schema.cpp create mode 100644 src/v1/emissions/CMakeLists.txt create mode 100644 src/v1/emissions/parsers.cpp rename src/v1/{emissions_parser.cpp => emissions/schema.cpp} (66%) create mode 100644 src/v1/reactions/parsers.cpp create mode 100644 src/v1/reactions/schema.cpp create mode 100644 src/v1/species/CMakeLists.txt create mode 100644 src/v1/species/parsers.cpp create mode 100644 src/v1/species/schema.cpp delete mode 100644 src/v1/type_parsers.cpp delete mode 100644 src/v1/type_schema.cpp diff --git a/include/mechanism_configuration/validate.hpp b/include/mechanism_configuration/validate.hpp index 217738d1..a826e50a 100644 --- a/include/mechanism_configuration/validate.hpp +++ b/include/mechanism_configuration/validate.hpp @@ -104,9 +104,14 @@ namespace mechanism_configuration /// has no aerosol section. Errors ValidateAerosolModel(const Mechanism& mechanism); + /// @brief Validates a Mechanism's emissions section by converting it to a location-free + /// semantics::Input and running ValidateSemantics. Returns no errors if the mechanism + /// has no emissions section. + Errors ValidateEmissionsModel(const Mechanism& mechanism); + /// @brief Validates the semantic invariants of a canonical Mechanism, regardless of whether it - /// was parsed or constructed in code. Combines ValidateGasModel and - /// ValidateAerosolModel, returning all validation errors. + /// was parsed or constructed in code. Combines ValidateGasModel, ValidateAerosolModel, + /// and ValidateEmissionsModel, returning all validation errors. /// /// Excludes structural/deserialization validation, which is handled by version-specific parsers. Errors Validate(const Mechanism& mechanism); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38930860..6accee18 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,8 +29,8 @@ target_sources(mechanism_configuration PRIVATE errors.cpp parse.cpp + schema.cpp validate.cpp - check_schema.cpp ) target_include_directories(mechanism_configuration diff --git a/src/detail/check_schema.hpp b/src/detail/schema.hpp similarity index 100% rename from src/detail/check_schema.hpp rename to src/detail/schema.hpp diff --git a/src/detail/v1/aerosol_keys.hpp b/src/detail/v1/aerosol/keys.hpp similarity index 98% rename from src/detail/v1/aerosol_keys.hpp rename to src/detail/v1/aerosol/keys.hpp index b7685249..77c3d759 100644 --- a/src/detail/v1/aerosol_keys.hpp +++ b/src/detail/v1/aerosol/keys.hpp @@ -49,6 +49,7 @@ namespace mechanism_configuration::v1::keys // Processes // ---------------------------------------- inline constexpr std::string_view solvent = "solvent"; + inline constexpr std::string_view condensed_phase_species = "condensed-phase species"; // HenryLawPhaseTransfer // also: gas_phase, gas_phase_species, condensed_phase, condensed_phase_species, solvent, diff --git a/src/detail/v1/aerosol_type_parsers.hpp b/src/detail/v1/aerosol/parsers.hpp similarity index 100% rename from src/detail/v1/aerosol_type_parsers.hpp rename to src/detail/v1/aerosol/parsers.hpp diff --git a/src/detail/v1/aerosol_type_schema.hpp b/src/detail/v1/aerosol/schema.hpp similarity index 100% rename from src/detail/v1/aerosol_type_schema.hpp rename to src/detail/v1/aerosol/schema.hpp diff --git a/src/detail/v1/aerosol/utils.hpp b/src/detail/v1/aerosol/utils.hpp new file mode 100644 index 00000000..450a02e8 --- /dev/null +++ b/src/detail/v1/aerosol/utils.hpp @@ -0,0 +1,40 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include +#include +#include + +namespace mechanism_configuration::v1 +{ + /// @brief Looks up the diffusion coefficient defined for a species within a phase. + /// @param phases Parsed phases to search + /// @param phase_name Name of the phase that should contain the species + /// @param species_name Name of the species whose diffusion coefficient is requested + /// @return The diffusion coefficient, or nullopt if the phase/species is not found or the + /// species has no diffusion coefficient defined. + std::optional FindPhaseSpeciesDiffusionCoefficient( + const std::vector& phases, + const std::string& phase_name, + const std::string& species_name); + + /// @brief Looks up the density defined for a species within a phase. + /// @return The density, or nullopt if the phase/species is not found or the species has no + /// density defined. + std::optional FindPhaseSpeciesDensity( + const std::vector& phases, + const std::string& phase_name, + const std::string& species_name); + + /// @brief Looks up the molecular weight defined for a top-level species. + /// @return The molecular weight, or nullopt if the species is not found or has none defined. + std::optional FindSpeciesMolecularWeight( + const std::vector& species, + const std::string& species_name); + +} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/emissions/keys.hpp b/src/detail/v1/emissions/keys.hpp new file mode 100644 index 00000000..dc38d8e8 --- /dev/null +++ b/src/detail/v1/emissions/keys.hpp @@ -0,0 +1,66 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace mechanism_configuration::v1::keys +{ + // ── Emissions section ────────────────────────────────────────────────────── + inline constexpr std::string_view emissions = "emissions"; + + // Emissions top-level sections + inline constexpr std::string_view inventories = "inventories"; + inline constexpr std::string_view species_maps = "species maps"; + inline constexpr std::string_view regridding = "regridding"; + inline constexpr std::string_view sources = "sources"; + + // Inventory entry + inline constexpr std::string_view directory = "directory"; + inline constexpr std::string_view file_pattern = "file pattern"; + inline constexpr std::string_view convention = "convention"; + + // Species map entry + inline constexpr std::string_view mappings = "mappings"; + inline constexpr std::string_view inventory_species = "inventory species"; + inline constexpr std::string_view mechanism_species = "mechanism species"; + // scaling_factor is shared, see detail/v1/keys.hpp + + // Regridding values + inline constexpr std::string_view regridding_none = "none"; + inline constexpr std::string_view regridding_scrip = "scrip"; + + // Source descriptor entry + inline constexpr std::string_view mode = "mode"; + inline constexpr std::string_view inventory = "inventory"; + inline constexpr std::string_view species_map = "species map"; + inline constexpr std::string_view temporal_interpolation = "temporal interpolation"; + inline constexpr std::string_view vertical_injection = "vertical injection"; + inline constexpr std::string_view category = "category"; + inline constexpr std::string_view hierarchy = "hierarchy"; + inline constexpr std::string_view sector = "sector"; + + // Mode values + inline constexpr std::string_view mode_offline = "offline"; + inline constexpr std::string_view mode_online = "online"; + + // Source type values + inline constexpr std::string_view type_anthropogenic = "anthropogenic"; + inline constexpr std::string_view type_fire = "fire"; + inline constexpr std::string_view type_biogenic = "biogenic"; + inline constexpr std::string_view type_dust = "dust"; + inline constexpr std::string_view type_sea_salt = "sea salt"; + inline constexpr std::string_view type_lightning = "lightning"; + + // Temporal interpolation values + inline constexpr std::string_view interp_linear = "linear"; + inline constexpr std::string_view interp_nearest = "nearest"; + inline constexpr std::string_view interp_none = "none"; + + // Vertical injection values + inline constexpr std::string_view inject_surface = "surface"; + inline constexpr std::string_view inject_plume = "plume"; + +} // namespace mechanism_configuration::v1::keys diff --git a/src/detail/v1/emissions/parsers.hpp b/src/detail/v1/emissions/parsers.hpp new file mode 100644 index 00000000..e443c50c --- /dev/null +++ b/src/detail/v1/emissions/parsers.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include + +namespace mechanism_configuration::v1 +{ + /// @brief Parses a YAML node into an EmissionsConfig. + /// The input must be validated using CheckEmissionsSchema(). + /// This function assumes the structure and types are correct. + /// @param emissions_node YAML node for the `emissions` key + /// @return The parsed EmissionsConfig + types::EmissionsConfig ParseEmissions(const YAML::Node& emissions_node); + +} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/emissions_parser.hpp b/src/detail/v1/emissions/schema.hpp similarity index 70% rename from src/detail/v1/emissions_parser.hpp rename to src/detail/v1/emissions/schema.hpp index f3bc1379..70ce9a31 100644 --- a/src/detail/v1/emissions_parser.hpp +++ b/src/detail/v1/emissions/schema.hpp @@ -5,7 +5,6 @@ #pragma once #include -#include #include @@ -22,11 +21,4 @@ namespace mechanism_configuration::v1 /// @return List of structural errors, or empty if the section conforms Errors CheckEmissionsSchema(const YAML::Node& emissions_node); - /// @brief Parses a YAML node into an EmissionsConfig. - /// The input must be validated using CheckEmissionsSchema(). - /// This function assumes the structure and types are correct. - /// @param emissions_node YAML node for the `emissions` key - /// @return The parsed EmissionsConfig - types::EmissionsConfig ParseEmissionsSection(const YAML::Node& emissions_node); - } // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/keys.hpp b/src/detail/v1/keys.hpp index 05df65a2..cbb50d25 100644 --- a/src/detail/v1/keys.hpp +++ b/src/detail/v1/keys.hpp @@ -6,6 +6,8 @@ #include +// Keys shared across two or more v1 domains (species, reactions, aerosol, emissions). +// Domain-exclusive keys live in their own domain's keys.hpp. namespace mechanism_configuration::v1::keys { // Shared, but also Mechanism @@ -18,24 +20,9 @@ namespace mechanism_configuration::v1::keys // Configuration inline constexpr std::string_view species = "species"; inline constexpr std::string_view phases = "phases"; - inline constexpr std::string_view models = "models"; inline constexpr std::string_view reactions = "reactions"; - // Species - inline constexpr std::string_view absolute_tolerance = "absolute tolerance"; - inline constexpr std::string_view diffusion_coefficient = "diffusion coefficient [m2 s-1]"; - inline constexpr std::string_view molecular_weight = "molecular weight [kg mol-1]"; - inline constexpr std::string_view henrys_law_constant_298 = "HLC(298K) [mol m-3 Pa-1]"; - inline constexpr std::string_view henrys_law_constant_exponential_factor = "HLC exponential factor [K]"; - inline constexpr std::string_view n_star = "N star"; - inline constexpr std::string_view density = "density [kg m-3]"; - inline constexpr std::string_view tracer_type = "tracer type"; - inline constexpr std::string_view constant_concentration = "constant concentration [mol m-3]"; - inline constexpr std::string_view constant_mixing_ratio = "constant mixing ratio [mol mol-1]"; - inline constexpr std::string_view is_third_body = "is third body"; - inline constexpr std::string_view third_body = "THIRD_BODY"; - - // Reactions + // Reactants and products (used by reactions and aerosol processes/constraints) inline constexpr std::string_view reactants = "reactants"; inline constexpr std::string_view products = "products"; inline constexpr std::string_view type = "type"; @@ -45,12 +32,7 @@ namespace mechanism_configuration::v1::keys inline constexpr std::string_view coefficient = "coefficient"; // also name - // ---------------------------------------- - // Reaction types - // ---------------------------------------- - - // Arrhenius - inline constexpr std::string_view Arrhenius_key = "ARRHENIUS"; + // Rate-constant parameters shared by ARRHENIUS-family reaction types and aerosol rate constants inline constexpr std::string_view A = "A"; inline constexpr std::string_view B = "B"; inline constexpr std::string_view C = "C"; @@ -58,177 +40,11 @@ namespace mechanism_configuration::v1::keys inline constexpr std::string_view E = "E"; inline constexpr std::string_view Ea = "Ea"; - // TaylorSeries - inline constexpr std::string_view TaylorSeries_key = "TAYLOR_SERIES"; - inline constexpr std::string_view taylor_coefficients = "taylor coefficients"; - // also these - // A - // B - // C - // D - // E - // Ea - - // Condensed Phase Arrhenius - inline constexpr std::string_view CondensedPhaseArrhenius_key = "CONDENSED_PHASE_ARRHENIUS"; - // also these - // condensed phase - // A - // B - // C - // D - // E - // Ea - - // Troe - inline constexpr std::string_view Troe_key = "TROE"; - inline constexpr std::string_view k0_A = "k0_A"; - inline constexpr std::string_view k0_B = "k0_B"; - inline constexpr std::string_view k0_C = "k0_C"; - inline constexpr std::string_view kinf_A = "kinf_A"; - inline constexpr std::string_view kinf_B = "kinf_B"; - inline constexpr std::string_view kinf_C = "kinf_C"; - inline constexpr std::string_view Fc = "Fc"; - inline constexpr std::string_view N = "N"; - - // Ternary Chemical Activation - inline constexpr std::string_view TernaryChemicalActivation_key = "TERNARY_CHEMICAL_ACTIVATION"; - // also k0_A - // k0_B - // k0_C - // kinf_A - // kinf_B - // kinf_C - // Fc - // N - - // Branched - inline constexpr std::string_view Branched_key = "BRANCHED_NO_RO2"; - inline constexpr std::string_view X = "X"; - inline constexpr std::string_view Y = "Y"; - inline constexpr std::string_view a0 = "a0"; - inline constexpr std::string_view n = "n"; - inline constexpr std::string_view nitrate_products = "nitrate products"; - inline constexpr std::string_view alkoxy_products = "alkoxy products"; - - // Tunneling - inline constexpr std::string_view Tunneling_key = "TUNNELING"; - // also these, but they are defined above - // A - // B - // C - - // Surface - inline constexpr std::string_view Surface_key = "SURFACE"; - inline constexpr std::string_view reaction_probability = "reaction probability"; + // Phase-transfer fields shared by the SURFACE reaction and aerosol phase transfer/equilibrium inline constexpr std::string_view gas_phase_species = "gas-phase species"; - inline constexpr std::string_view gas_phase_products = "gas-phase products"; inline constexpr std::string_view condensed_phase = "condensed phase"; - // Photolysis - inline constexpr std::string_view Photolysis_key = "PHOTOLYSIS"; + // Scaling factor shared across several reaction types and emissions species maps inline constexpr std::string_view scaling_factor = "scaling factor"; - // Condensed Phae Photolysis - inline constexpr std::string_view CondensedPhasePhotolysis_key = "CONDENSED_PHASE_PHOTOLYSIS"; - // also - // scaling factor - // condensed phase - - // Emissions - inline constexpr std::string_view Emission_key = "EMISSION"; - // also scaling factor - - // First Order Loss - inline constexpr std::string_view FirstOrderLoss_key = "FIRST_ORDER_LOSS"; - // also scaling factor - - // Simpol Phase Transfer - inline constexpr std::string_view SimpolPhaseTransfer_key = "SIMPOL_PHASE_TRANSFER"; - inline constexpr std::string_view condensed_phase_species = "condensed-phase species"; - // also - // gas phase - // gas-phase species - // condensed phase - // condensed-phase species - // B - - // Wet Deposition - inline constexpr std::string_view WetDeposition_key = "WET_DEPOSITION"; - // also - // scaling factor - // condensed phase - - // User Defined - inline constexpr std::string_view UserDefined_key = "USER_DEFINED"; - // also - // gas phase - // reactants - // products - // scaling factor - - // Lambda Rate Constant - inline constexpr std::string_view LambdaRateConstant_key = "LAMBDA_RATE_CONSTANT"; - inline constexpr std::string_view lambda_function = "lambda function"; - // also - // gas phase - // reactants - // products - // name - - // ── Emissions section ────────────────────────────────────────────────────── - inline constexpr std::string_view emissions = "emissions"; - - // Emissions top-level sections - inline constexpr std::string_view inventories = "inventories"; - inline constexpr std::string_view species_maps = "species maps"; - inline constexpr std::string_view regridding = "regridding"; - inline constexpr std::string_view sources = "sources"; - - // Inventory entry - inline constexpr std::string_view directory = "directory"; - inline constexpr std::string_view file_pattern = "file pattern"; - inline constexpr std::string_view convention = "convention"; - - // Species map entry - inline constexpr std::string_view mappings = "mappings"; - inline constexpr std::string_view inventory_species = "inventory species"; - inline constexpr std::string_view mechanism_species = "mechanism species"; - // scaling_factor already defined above - - // Regridding values - inline constexpr std::string_view regridding_none = "none"; - inline constexpr std::string_view regridding_scrip = "scrip"; - - // Source descriptor entry - inline constexpr std::string_view mode = "mode"; - inline constexpr std::string_view inventory = "inventory"; - inline constexpr std::string_view species_map = "species map"; - inline constexpr std::string_view temporal_interpolation = "temporal interpolation"; - inline constexpr std::string_view vertical_injection = "vertical injection"; - inline constexpr std::string_view category = "category"; - inline constexpr std::string_view hierarchy = "hierarchy"; - inline constexpr std::string_view sector = "sector"; - - // Mode values - inline constexpr std::string_view mode_offline = "offline"; - inline constexpr std::string_view mode_online = "online"; - - // Source type values - inline constexpr std::string_view type_anthropogenic = "anthropogenic"; - inline constexpr std::string_view type_fire = "fire"; - inline constexpr std::string_view type_biogenic = "biogenic"; - inline constexpr std::string_view type_dust = "dust"; - inline constexpr std::string_view type_sea_salt = "sea salt"; - inline constexpr std::string_view type_lightning = "lightning"; - - // Temporal interpolation values - inline constexpr std::string_view interp_linear = "linear"; - inline constexpr std::string_view interp_nearest = "nearest"; - inline constexpr std::string_view interp_none = "none"; - - // Vertical injection values - inline constexpr std::string_view inject_surface = "surface"; - inline constexpr std::string_view inject_plume = "plume"; - } // namespace mechanism_configuration::v1::keys diff --git a/src/detail/v1/reactions/keys.hpp b/src/detail/v1/reactions/keys.hpp new file mode 100644 index 00000000..03e5ab28 --- /dev/null +++ b/src/detail/v1/reactions/keys.hpp @@ -0,0 +1,122 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace mechanism_configuration::v1::keys +{ + // ---------------------------------------- + // Reaction types + // ---------------------------------------- + + // Arrhenius + inline constexpr std::string_view Arrhenius_key = "ARRHENIUS"; + // also A, B, C, D, E, Ea (shared, see detail/v1/keys.hpp) + + // TaylorSeries + inline constexpr std::string_view TaylorSeries_key = "TAYLOR_SERIES"; + inline constexpr std::string_view taylor_coefficients = "taylor coefficients"; + // also A, B, C, D, E, Ea (shared) + + // Condensed Phase Arrhenius + inline constexpr std::string_view CondensedPhaseArrhenius_key = "CONDENSED_PHASE_ARRHENIUS"; + // also condensed phase (shared), A, B, C, D, E, Ea (shared) + + // Troe + inline constexpr std::string_view Troe_key = "TROE"; + inline constexpr std::string_view k0_A = "k0_A"; + inline constexpr std::string_view k0_B = "k0_B"; + inline constexpr std::string_view k0_C = "k0_C"; + inline constexpr std::string_view kinf_A = "kinf_A"; + inline constexpr std::string_view kinf_B = "kinf_B"; + inline constexpr std::string_view kinf_C = "kinf_C"; + inline constexpr std::string_view Fc = "Fc"; + inline constexpr std::string_view N = "N"; + + // Ternary Chemical Activation + inline constexpr std::string_view TernaryChemicalActivation_key = "TERNARY_CHEMICAL_ACTIVATION"; + // also k0_A + // k0_B + // k0_C + // kinf_A + // kinf_B + // kinf_C + // Fc + // N + + // Branched + inline constexpr std::string_view Branched_key = "BRANCHED_NO_RO2"; + inline constexpr std::string_view X = "X"; + inline constexpr std::string_view Y = "Y"; + inline constexpr std::string_view a0 = "a0"; + inline constexpr std::string_view n = "n"; + inline constexpr std::string_view nitrate_products = "nitrate products"; + inline constexpr std::string_view alkoxy_products = "alkoxy products"; + + // Tunneling + inline constexpr std::string_view Tunneling_key = "TUNNELING"; + // also these, but they are defined above (shared) + // A + // B + // C + + // Surface + inline constexpr std::string_view Surface_key = "SURFACE"; + inline constexpr std::string_view reaction_probability = "reaction probability"; + inline constexpr std::string_view gas_phase_products = "gas-phase products"; + // also gas_phase_species, condensed_phase (shared) + + // Photolysis + inline constexpr std::string_view Photolysis_key = "PHOTOLYSIS"; + // also scaling factor (shared) + + // Condensed Phase Photolysis + inline constexpr std::string_view CondensedPhasePhotolysis_key = "CONDENSED_PHASE_PHOTOLYSIS"; + // also + // scaling factor (shared) + // condensed phase (shared) + + // Emission + inline constexpr std::string_view Emission_key = "EMISSION"; + // also scaling factor (shared) + + // First Order Loss + inline constexpr std::string_view FirstOrderLoss_key = "FIRST_ORDER_LOSS"; + // also scaling factor (shared) + + // Simpol Phase Transfer + inline constexpr std::string_view SimpolPhaseTransfer_key = "SIMPOL_PHASE_TRANSFER"; + // also + // gas phase (shared) + // gas-phase species (shared) + // condensed phase (shared) + // condensed-phase species (see detail/v1/aerosol/keys.hpp) + // B (shared) + + // Wet Deposition + inline constexpr std::string_view WetDeposition_key = "WET_DEPOSITION"; + // also + // scaling factor (shared) + // condensed phase (shared) + + // User Defined + inline constexpr std::string_view UserDefined_key = "USER_DEFINED"; + // also + // gas phase (shared) + // reactants (shared) + // products (shared) + // scaling factor (shared) + + // Lambda Rate Constant + inline constexpr std::string_view LambdaRateConstant_key = "LAMBDA_RATE_CONSTANT"; + inline constexpr std::string_view lambda_function = "lambda function"; + // also + // gas phase (shared) + // reactants (shared) + // products (shared) + // name (shared) + +} // namespace mechanism_configuration::v1::keys diff --git a/src/detail/v1/reaction_parsers.hpp b/src/detail/v1/reactions/parsers.hpp similarity index 83% rename from src/detail/v1/reaction_parsers.hpp rename to src/detail/v1/reactions/parsers.hpp index d9832e49..0a48609f 100644 --- a/src/detail/v1/reaction_parsers.hpp +++ b/src/detail/v1/reactions/parsers.hpp @@ -8,13 +8,36 @@ #include #include -#include +#include #include +#include +#include +#include #include namespace mechanism_configuration::v1 { + /// @brief Parses a YAML node into reaction components + /// @param object YAML node representing ReactionComponents + /// @param key Key of the sequence to parse + /// @return Vector of `types::ReactionComponent` with names, optional coefficients, and comments + std::vector ParseReactionComponents(const YAML::Node& object, std::string_view key); + + /// @brief Parses a single reaction component from a YAML node. + /// The parser performs no validation or error checking. + /// @param object YAML node representing ReactionComponents + /// @param key Key identifying the reaction component + /// @return The parsed `types::ReactionComponent`, or a default-constructed one if none found + types::ReactionComponent ParseReactionComponent(const YAML::Node& object, std::string_view key); + + /// @brief Parses a collection of YAML nodes into reaction objects + /// Iterates over the given YAML nodes, identifies the parser for each reaction type, + /// and populates a `types::Reactions` container with the parsed reactions. + /// @param objects YAML node containing multiple reaction definitions + /// @return A `types::Reactions` object with all successfully parsed reactions + types::Reactions ParseReactions(const YAML::Node& objects); + /// @brief Abstract interface for reaction parsers class IReactionParser { diff --git a/src/detail/v1/type_schema.hpp b/src/detail/v1/reactions/schema.hpp similarity index 66% rename from src/detail/v1/type_schema.hpp rename to src/detail/v1/reactions/schema.hpp index 7b0bf134..01a1f538 100644 --- a/src/detail/v1/type_schema.hpp +++ b/src/detail/v1/reactions/schema.hpp @@ -9,23 +9,14 @@ #include +#include + namespace mechanism_configuration::v1 { - // Structural (schema) validation of v1 YAML. These check required/optional keys and value + // Structural (schema) validation of v1 reactions. These check required/optional keys and value // shape only; semantic invariants (duplicate names, unknown species, phase membership) are // checked separately by the version-neutral ValidateSemantics on the built Mechanism. - /// @brief Schema-validates each species entry's keys. - /// @param species_list YAML node containing species entries - /// @return List of structural errors, or empty if all entries conform - Errors CheckSpeciesSchema(const YAML::Node& species_list); - - /// @brief Schema-validates each phase and its phase-species entries. - /// @param phases_list YAML node containing the list of phase entries - /// @param existing_species Unused; retained for call-site compatibility - /// @return List of structural errors, or empty if all entries conform - Errors CheckPhasesSchema(const YAML::Node& phases_list, const std::vector& existing_species); - /// @brief Schema-validates a sequence of reaction components (reactants or products), /// requiring exactly one of `name` / `species name` plus an optional coefficient. /// @param object YAML node representing a sequence of reactants or products @@ -43,4 +34,4 @@ namespace mechanism_configuration::v1 const std::vector& existing_species, const std::vector& existing_phases); -} // namespace mechanism_configuration::v1 \ No newline at end of file +} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/species/keys.hpp b/src/detail/v1/species/keys.hpp new file mode 100644 index 00000000..c4ea5fb5 --- /dev/null +++ b/src/detail/v1/species/keys.hpp @@ -0,0 +1,25 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace mechanism_configuration::v1::keys +{ + // Species + inline constexpr std::string_view absolute_tolerance = "absolute tolerance"; + inline constexpr std::string_view diffusion_coefficient = "diffusion coefficient [m2 s-1]"; + inline constexpr std::string_view molecular_weight = "molecular weight [kg mol-1]"; + inline constexpr std::string_view henrys_law_constant_298 = "HLC(298K) [mol m-3 Pa-1]"; + inline constexpr std::string_view henrys_law_constant_exponential_factor = "HLC exponential factor [K]"; + inline constexpr std::string_view n_star = "N star"; + inline constexpr std::string_view density = "density [kg m-3]"; + inline constexpr std::string_view tracer_type = "tracer type"; + inline constexpr std::string_view constant_concentration = "constant concentration [mol m-3]"; + inline constexpr std::string_view constant_mixing_ratio = "constant mixing ratio [mol mol-1]"; + inline constexpr std::string_view is_third_body = "is third body"; + inline constexpr std::string_view third_body = "THIRD_BODY"; + +} // namespace mechanism_configuration::v1::keys diff --git a/src/detail/v1/species/parsers.hpp b/src/detail/v1/species/parsers.hpp new file mode 100644 index 00000000..e0f81e97 --- /dev/null +++ b/src/detail/v1/species/parsers.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include + +#include + +namespace mechanism_configuration::v1 +{ + /// @brief Parses a YAML node into a vector of Species + /// The input must be validated using CheckSpeciesSchema(). + /// This function assumes the structure and types are correct. + /// @param objects YAML node representing species list + /// @return A vector of parsed species + std::vector ParseSpecies(const YAML::Node& objects); + + /// @brief Parses a YAML node into a vector of Phases + /// Extracts each phase's name and its associated species (including optional properties). + /// Assumes the input YAML has already been validated for required structure and keys. + /// @param objects YAML node representing phase list + /// @return A vector of parsed Phases + std::vector ParsePhases(const YAML::Node& objects); + +} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/species/schema.hpp b/src/detail/v1/species/schema.hpp new file mode 100644 index 00000000..dfb0767f --- /dev/null +++ b/src/detail/v1/species/schema.hpp @@ -0,0 +1,31 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include + +#include + +namespace mechanism_configuration::v1 +{ + // Structural (schema) validation of v1 species and phases. These check required/optional keys + // and value shape only; semantic invariants (duplicate names, unknown species, phase membership) + // are checked separately by the version-neutral ValidateSemantics on the built Mechanism. + + /// @brief Schema-validates each species entry's keys. + /// @param species_list YAML node containing species entries + /// @return List of structural errors, or empty if all entries conform + Errors CheckSpeciesSchema(const YAML::Node& species_list); + + /// @brief Schema-validates each phase and its phase-species entries. + /// @param phases_list YAML node containing the list of phase entries + /// @param existing_species Unused; retained for call-site compatibility + /// @return List of structural errors, or empty if all entries conform + Errors CheckPhasesSchema(const YAML::Node& phases_list, const std::vector& existing_species); + +} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/type_parsers.hpp b/src/detail/v1/type_parsers.hpp deleted file mode 100644 index 3182d6a8..00000000 --- a/src/detail/v1/type_parsers.hpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (C) 2023–2026 University Corporation for Atmospheric Research -// University of Illinois at Urbana-Champaign -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include -#include - -#include - -#include -#include - -namespace mechanism_configuration::v1 -{ - /// @brief Parses a YAML node into a vector of Species - /// The input must be validated using CheckSpeciesSchema(). - /// This function assumes the structure and types are correct. - /// @param objects YAML node representing species list - /// @return A vector of parsed species - std::vector ParseSpecies(const YAML::Node& objects); - - /// @brief Parses a YAML node into a vector of Phases - /// Extracts each phase's name and its associated species (including optional properties). - /// Assumes the input YAML has already been validated for required structure and keys. - /// @param objects YAML node representing phase list - /// @return A vector of parsed Phases - std::vector ParsePhases(const YAML::Node& objects); - - /// @brief Parses a YAML node into reaction components - /// @param object YAML node representing ReactionComponents - /// @param key Key of the sequence to parse - /// @return Vector of `types::ReactionComponent` with names, optional coefficients, and comments - std::vector ParseReactionComponents(const YAML::Node& object, std::string_view key); - - /// @brief Parses a single reaction component from a YAML node. - /// The parser performs no validation or error checking. - /// @param object YAML node representing ReactionComponents - /// @param key Key identifying the reaction component - /// @return The parsed `types::ReactionComponent`, or a default-constructed one if none found - types::ReactionComponent ParseReactionComponent(const YAML::Node& object, std::string_view key); - - /// @brief Parses a collection of YAML nodes into reaction objects - /// Iterates over the given YAML nodes, identifies the parser for each reaction type, - /// and populates a `types::Reactions` container with the parsed reactions. - /// @param objects YAML node containing multiple reaction definitions - /// @return A `types::Reactions` object with all successfully parsed reactions - types::Reactions ParseReactions(const YAML::Node& objects); - -} // namespace mechanism_configuration::v1 diff --git a/src/detail/v1/utils.hpp b/src/detail/v1/utils.hpp index 383fd2e2..f35e995a 100644 --- a/src/detail/v1/utils.hpp +++ b/src/detail/v1/utils.hpp @@ -5,18 +5,15 @@ #pragma once #include -#include #include #include #include #include -#include #include #include #include -#include namespace mechanism_configuration::v1 { @@ -32,34 +29,10 @@ namespace mechanism_configuration::v1 std::unordered_map GetComments(const YAML::Node& object); - /// @brief Reads a reaction component's species reference, accepting either the - /// canonical `name` key or the legacy `species name` alias (v1 files). - /// @note Assumes the component has already been validated to contain exactly one of them. - std::string GetReactionComponentName(const YAML::Node& component); - - /// @brief Looks up the diffusion coefficient defined for a species within a phase. - /// @param phases Parsed phases to search - /// @param phase_name Name of the phase that should contain the species - /// @param species_name Name of the species whose diffusion coefficient is requested - /// @return The diffusion coefficient, or nullopt if the phase/species is not found or the - /// species has no diffusion coefficient defined. - std::optional FindPhaseSpeciesDiffusionCoefficient( - const std::vector& phases, - const std::string& phase_name, - const std::string& species_name); - - /// @brief Looks up the density defined for a species within a phase. - /// @return The density, or nullopt if the phase/species is not found or the species has no - /// density defined. - std::optional FindPhaseSpeciesDensity( - const std::vector& phases, - const std::string& phase_name, - const std::string& species_name); - - /// @brief Looks up the molecular weight defined for a top-level species. - /// @return The molecular weight, or nullopt if the species is not found or has none defined. - std::optional FindSpeciesMolecularWeight( - const std::vector& species, - const std::string& species_name); + /// @brief Reads a named entry's name, accepting either a bare-string shorthand or an object + /// keyed by the canonical `name` or the legacy `species name` alias (v1 files). Used for + /// species, phase-species, and reaction component entries. + /// @note Assumes the entry has already been validated to contain exactly one of them. + std::string GetComponentName(const YAML::Node& component); } // namespace mechanism_configuration::v1 diff --git a/src/check_schema.cpp b/src/schema.cpp similarity index 99% rename from src/check_schema.cpp rename to src/schema.cpp index a699153e..3ba3b2a1 100644 --- a/src/check_schema.cpp +++ b/src/schema.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/error_format.hpp" diff --git a/src/v0/arrhenius_parser.cpp b/src/v0/arrhenius_parser.cpp index 177b314f..5be07f80 100644 --- a/src/v0/arrhenius_parser.cpp +++ b/src/v0/arrhenius_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/branched_parser.cpp b/src/v0/branched_parser.cpp index d5223215..3fb0588a 100644 --- a/src/v0/branched_parser.cpp +++ b/src/v0/branched_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" diff --git a/src/v0/emission_parser.cpp b/src/v0/emission_parser.cpp index 9efdaa2f..c261c182 100644 --- a/src/v0/emission_parser.cpp +++ b/src/v0/emission_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/first_order_loss_parser.cpp b/src/v0/first_order_loss_parser.cpp index 66781f39..92689dfd 100644 --- a/src/v0/first_order_loss_parser.cpp +++ b/src/v0/first_order_loss_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/parser.cpp b/src/v0/parser.cpp index 22e677b9..bdaf3257 100644 --- a/src/v0/parser.cpp +++ b/src/v0/parser.cpp @@ -4,7 +4,7 @@ #include "detail/v0/parser.hpp" -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" diff --git a/src/v0/photolysis_parser.cpp b/src/v0/photolysis_parser.cpp index c24e790f..2b2f2f11 100644 --- a/src/v0/photolysis_parser.cpp +++ b/src/v0/photolysis_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/species_parser.cpp b/src/v0/species_parser.cpp index 2484f1fe..ca10a3df 100644 --- a/src/v0/species_parser.cpp +++ b/src/v0/species_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser_types.hpp" diff --git a/src/v0/surface_parser.cpp b/src/v0/surface_parser.cpp index 3e9eec75..f5eb386e 100644 --- a/src/v0/surface_parser.cpp +++ b/src/v0/surface_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/ternary_chemical_activation_parser.cpp b/src/v0/ternary_chemical_activation_parser.cpp index 2d6ad292..6fb03182 100644 --- a/src/v0/ternary_chemical_activation_parser.cpp +++ b/src/v0/ternary_chemical_activation_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" diff --git a/src/v0/troe_parser.cpp b/src/v0/troe_parser.cpp index 32855773..a1917d15 100644 --- a/src/v0/troe_parser.cpp +++ b/src/v0/troe_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" diff --git a/src/v0/tunneling_parser.cpp b/src/v0/tunneling_parser.cpp index 79630a70..be080293 100644 --- a/src/v0/tunneling_parser.cpp +++ b/src/v0/tunneling_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/conversions.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v0/user_defined_reaction_parser.cpp b/src/v0/user_defined_reaction_parser.cpp index c26a4f04..5f5a5b36 100644 --- a/src/v0/user_defined_reaction_parser.cpp +++ b/src/v0/user_defined_reaction_parser.cpp @@ -2,7 +2,7 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/constants.hpp" #include "detail/v0/keys.hpp" #include "detail/v0/parser.hpp" diff --git a/src/v1/CMakeLists.txt b/src/v1/CMakeLists.txt index e9972758..4ed15421 100644 --- a/src/v1/CMakeLists.txt +++ b/src/v1/CMakeLists.txt @@ -1,12 +1,10 @@ target_sources(mechanism_configuration PRIVATE - emissions_parser.cpp parser.cpp - type_parsers.cpp - type_schema.cpp utils.cpp - aerosol_type_parsers.cpp - aerosol_type_schema.cpp ) -add_subdirectory(reactions) \ No newline at end of file +add_subdirectory(aerosol) +add_subdirectory(emissions) +add_subdirectory(reactions) +add_subdirectory(species) diff --git a/src/v1/aerosol/CMakeLists.txt b/src/v1/aerosol/CMakeLists.txt new file mode 100644 index 00000000..5e3c02e3 --- /dev/null +++ b/src/v1/aerosol/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(mechanism_configuration + PRIVATE + parsers.cpp + schema.cpp + utils.cpp +) diff --git a/src/v1/aerosol/parsers.cpp b/src/v1/aerosol/parsers.cpp new file mode 100644 index 00000000..acd0fb76 --- /dev/null +++ b/src/v1/aerosol/parsers.cpp @@ -0,0 +1,316 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/aerosol/parsers.hpp" + +#include "detail/v1/aerosol/keys.hpp" +#include "detail/v1/aerosol/utils.hpp" +#include "detail/v1/keys.hpp" +#include "detail/v1/reactions/parsers.hpp" + +namespace mechanism_configuration::v1 +{ + // ---------------------------------------- + // Rate constants parser + // ---------------------------------------- + + types::ArrheniusReferenceTemperature ParseArrheniusReferenceTemperature(const YAML::Node& object) + { + types::ArrheniusReferenceTemperature rate_constant; + + rate_constant.A = object[keys::A].as(); + if (object[keys::henry_law_C]) + rate_constant.C = object[keys::henry_law_C].as(); + if (object[keys::reference_temperature]) + rate_constant.T0 = object[keys::reference_temperature].as(); + + return rate_constant; + } + + types::Arrhenius ParseArrhenius(const YAML::Node& object) + { + types::Arrhenius rate_constant; + + rate_constant.A = object[keys::A].as(); + if (object[keys::B]) + rate_constant.B = object[keys::B].as(); + if (object[keys::C]) + rate_constant.C = object[keys::C].as(); + if (object[keys::D]) + rate_constant.D = object[keys::D].as(); + if (object[keys::E]) + rate_constant.E = object[keys::E].as(); + + return rate_constant; + } + + types::RateConstant ParseRateConstant(const YAML::Node& object) + { + if (object[keys::type] && object[keys::type].as() == keys::ArrheniusReferenceTemperature_key) + return ParseArrheniusReferenceTemperature(object); + + // ARRHENIUS is the default when no (recognized) type is given. + return ParseArrhenius(object); + } + + types::HenryLawConstant ParseHenryLawConstant(const YAML::Node& object) + { + types::HenryLawConstant henry_law_constant; + + henry_law_constant.HLC_ref = object[keys::HLC_ref].as(); + if (object[keys::henry_law_C]) + henry_law_constant.C = object[keys::henry_law_C].as(); + if (object[keys::reference_temperature]) + henry_law_constant.T0 = object[keys::reference_temperature].as(); + + return henry_law_constant; + } + + std::map ParseRateConstantMap(const YAML::Node& object) + { + std::map rate_constants; + + // Each key is an aerosol representation name (e.g. "CLOUD"); each value is a rate-constant + // block whose own `type` selects the RateConstant variant alternative. + for (const auto& entry : object) + { + rate_constants.emplace(entry.first.as(), ParseRateConstant(entry.second)); + } + + return rate_constants; + } + + // ---------------------------------------- + // Representation parsers + // ---------------------------------------- + + namespace + { + std::vector ParsePhaseNames(const YAML::Node& object) + { + std::vector phases; + for (const auto& phase : object[keys::phases]) + phases.push_back(phase.as()); + return phases; + } + } // namespace + + types::UniformSection ParseUniformSection(const YAML::Node& object) + { + types::UniformSection uniform_section; + + uniform_section.name = object[keys::name].as(); + uniform_section.phases = ParsePhaseNames(object); + uniform_section.min_radius = object[keys::minimum_radius].as(); + uniform_section.max_radius = object[keys::maximum_radius].as(); + + return uniform_section; + } + + types::SingleMomentMode ParseSingleMomentMode(const YAML::Node& object) + { + types::SingleMomentMode single_moment_mode; + + single_moment_mode.name = object[keys::name].as(); + single_moment_mode.phases = ParsePhaseNames(object); + single_moment_mode.geometric_mean_radius = object[keys::geometric_mean_radius].as(); + single_moment_mode.geometric_standard_deviation = object[keys::geometric_standard_deviation].as(); + + return single_moment_mode; + } + + types::TwoMomentMode ParseTwoMomentMode(const YAML::Node& object) + { + types::TwoMomentMode two_moment_mode; + + two_moment_mode.name = object[keys::name].as(); + two_moment_mode.phases = ParsePhaseNames(object); + two_moment_mode.geometric_standard_deviation = object[keys::geometric_standard_deviation].as(); + + return two_moment_mode; + } + + // ---------------------------------------- + // Process parsers + // ---------------------------------------- + + types::HenryLawPhaseTransfer ParseHenryLawPhaseTransfer(const YAML::Node& object, const std::vector& phases) + { + types::HenryLawPhaseTransfer transfer; + + transfer.gas_phase = object[keys::gas_phase].as(); + transfer.gas_species = object[keys::gas_phase_species].as(); + transfer.condensed_phase = object[keys::condensed_phase].as(); + transfer.condensed_species = object[keys::condensed_phase_species].as(); + transfer.solvent = object[keys::solvent].as(); + transfer.henry_law_constant = ParseHenryLawConstant(object[keys::henry_law_constant]); + // Sourced from the gas-phase species' definition; presence is enforced by ValidateAerosolModel, + // which runs before this result is returned, so a missing value defaults harmlessly here. + transfer.diffusion_coefficient = + FindPhaseSpeciesDiffusionCoefficient(phases, transfer.gas_phase, transfer.gas_species).value_or(0.0); + transfer.accommodation_coefficient = object[keys::accommodation_coefficient].as(); + + return transfer; + } + + types::DissolvedReaction ParseDissolvedReaction(const YAML::Node& object) + { + types::DissolvedReaction reaction; + + reaction.phase = object[keys::condensed_phase].as(); + reaction.solvent = object[keys::solvent].as(); + reaction.reactants = ParseReactionComponents(object, keys::reactants); + reaction.products = ParseReactionComponents(object, keys::products); + reaction.rate_constants = ParseRateConstantMap(object[keys::rate_constants]); + + return reaction; + } + + types::DissolvedReversibleReaction ParseDissolvedReversibleReaction(const YAML::Node& object) + { + types::DissolvedReversibleReaction reaction; + + reaction.phase = object[keys::condensed_phase].as(); + reaction.solvent = object[keys::solvent].as(); + reaction.reactants = ParseReactionComponents(object, keys::reactants); + reaction.products = ParseReactionComponents(object, keys::products); + + // Any two of {forward, reverse, equilibrium} may be supplied; the third is derived + // downstream, so each is parsed only when present. + if (object[keys::forward_rate_constants]) + reaction.forward_rate_constants = ParseRateConstantMap(object[keys::forward_rate_constants]); + if (object[keys::reverse_rate_constants]) + reaction.reverse_rate_constants = ParseRateConstantMap(object[keys::reverse_rate_constants]); + if (object[keys::equilibrium_constant]) + reaction.equilibrium_constant = ParseArrheniusReferenceTemperature(object[keys::equilibrium_constant]); + + return reaction; + } + + // ---------------------------------------- + // Constraint parsers + // ---------------------------------------- + + types::HenryLawEquilibrium ParseHenryLawEquilibrium( + const YAML::Node& object, + const std::vector& species, + const std::vector& phases) + { + types::HenryLawEquilibrium equilibrium; + + equilibrium.gas_phase = object[keys::gas_phase].as(); + equilibrium.gas_species = object[keys::gas_phase_species].as(); + equilibrium.condensed_phase = object[keys::condensed_phase].as(); + equilibrium.condensed_species = object[keys::condensed_phase_species].as(); + equilibrium.solvent = object[keys::solvent].as(); + equilibrium.henry_law_constant = ParseHenryLawConstant(object[keys::henry_law_constant]); + + // Sourced from the solvent species' definition: molecular weight from the species section, + // density from the condensed phase. Presence is enforced by ValidateAerosolModel, which runs + // before this result is returned, so a missing value defaults harmlessly here. + equilibrium.solvent_molecular_weight = FindSpeciesMolecularWeight(species, equilibrium.solvent).value_or(0.0); + equilibrium.solvent_density = + FindPhaseSpeciesDensity(phases, equilibrium.condensed_phase, equilibrium.solvent).value_or(0.0); + + return equilibrium; + } + + types::DissolvedEquilibrium ParseDissolvedEquilibrium(const YAML::Node& object) + { + types::DissolvedEquilibrium equilibrium; + + equilibrium.phase = object[keys::condensed_phase].as(); + equilibrium.algebraic_species = object[keys::algebraic_species].as(); + equilibrium.solvent = object[keys::solvent].as(); + equilibrium.reactants = ParseReactionComponents(object, keys::reactants); + equilibrium.products = ParseReactionComponents(object, keys::products); + equilibrium.equilibrium_constant = ParseArrheniusReferenceTemperature(object[keys::equilibrium_constant]); + + return equilibrium; + } + + types::LinearConstraint ParseLinearConstraint(const YAML::Node& object) + { + types::LinearConstraint constraint; + + constraint.algebraic_phase = object[keys::algebraic_phase].as(); + constraint.algebraic_species = object[keys::algebraic_species].as(); + + for (const auto& term_node : object[keys::terms]) + { + types::LinearConstraintTerm term; + term.phase = term_node[keys::phase].as(); + term.name = term_node[keys::name].as(); + term.coefficient = term_node[keys::coefficient].as(); + constraint.terms.push_back(term); + } + + // RHS constant: diagnose-from-state, else a fixed value, else the default FixedConstant{0}. + if (object[keys::diagnose_from_state] && object[keys::diagnose_from_state].as()) + constraint.constant = types::DiagnoseFromState{}; + else if (object[keys::constant]) + constraint.constant = types::FixedConstant{ object[keys::constant].as() }; + + return constraint; + } + + // ---------------------------------------- + // Container parsers + // ---------------------------------------- + + std::vector ParseAerosolRepresentations(const YAML::Node& objects) + { + std::vector representations; + + for (const auto& object : objects) + { + const auto type = object[keys::type].as(); + + if (type == keys::UniformSection_key) + representations.emplace_back(ParseUniformSection(object)); + else if (type == keys::SingleMomentMode_key) + representations.emplace_back(ParseSingleMomentMode(object)); + else if (type == keys::TwoMomentMode_key) + representations.emplace_back(ParseTwoMomentMode(object)); + } + + return representations; + } + + types::Aerosol + ParseAerosol(const YAML::Node& object, const std::vector& species, const std::vector& phases) + { + types::Aerosol aerosol; + + if (object[keys::aerosol_representations]) + aerosol.representations = ParseAerosolRepresentations(object[keys::aerosol_representations]); + + if (object[keys::aerosol_processes]) + { + // Aerosol processes section mixes process and constraint entries. + for (const auto& entry : object[keys::aerosol_processes]) + { + const auto type = entry[keys::type].as(); + + // Processes + if (type == keys::HenryLawPhaseTransfer_key) + aerosol.processes.emplace_back(ParseHenryLawPhaseTransfer(entry, phases)); + else if (type == keys::DissolvedReaction_key) + aerosol.processes.emplace_back(ParseDissolvedReaction(entry)); + else if (type == keys::DissolvedReversibleReaction_key) + aerosol.processes.emplace_back(ParseDissolvedReversibleReaction(entry)); + // Constraints + else if (type == keys::HenryLawEquilibrium_key) + aerosol.constraints.emplace_back(ParseHenryLawEquilibrium(entry, species, phases)); + else if (type == keys::DissolvedEquilibrium_key) + aerosol.constraints.emplace_back(ParseDissolvedEquilibrium(entry)); + else if (type == keys::LinearConstraint_key) + aerosol.constraints.emplace_back(ParseLinearConstraint(entry)); + } + } + + return aerosol; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/aerosol/schema.cpp b/src/v1/aerosol/schema.cpp new file mode 100644 index 00000000..82eff72e --- /dev/null +++ b/src/v1/aerosol/schema.cpp @@ -0,0 +1,294 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/aerosol/schema.hpp" + +#include "detail/error_format.hpp" +#include "detail/schema.hpp" +#include "detail/v1/aerosol/keys.hpp" +#include "detail/v1/keys.hpp" +#include "detail/v1/reactions/schema.hpp" +#include "detail/v1/utils.hpp" + +#include + +#include +#include +#include + +namespace mechanism_configuration::v1 +{ + namespace + { + Errors CheckArrheniusReferenceTemperatureSchema(const YAML::Node& object) + { + const std::vector required_keys = { keys::A, keys::henry_law_C }; + const std::vector optional_keys = { keys::type, keys::reference_temperature }; + return CheckSchema(object, required_keys, optional_keys); + } + + Errors CheckArrheniusSchema(const YAML::Node& object) + { + const std::vector required_keys = { keys::A, keys::C }; + const std::vector optional_keys = { keys::type }; + return CheckSchema(object, required_keys, optional_keys); + } + + Errors CheckRateConstantSchema(const YAML::Node& object) + { + if (object[keys::type] && object[keys::type].as() == keys::ArrheniusReferenceTemperature_key) + return CheckArrheniusReferenceTemperatureSchema(object); + return CheckArrheniusSchema(object); + } + + Errors CheckHenryLawConstantSchema(const YAML::Node& object) + { + const std::vector required_keys = { keys::HLC_ref, keys::henry_law_C }; + const std::vector optional_keys = { keys::reference_temperature }; + return CheckSchema(object, required_keys, optional_keys); + } + + // A per-representation rate-constant map. Each value is a typed rate-constant block. + Errors CheckRateConstantMapSchema(const YAML::Node& object) + { + Errors errors; + for (const auto& entry : object) + { + auto entry_errors = CheckRateConstantSchema(entry.second); + errors.insert(errors.end(), entry_errors.begin(), entry_errors.end()); + } + return errors; + } + + // Each linear-constraint term references a species in a phase with a coefficient. + Errors CheckLinearConstraintTermsSchema(const YAML::Node& object) + { + Errors errors; + const std::vector required_keys = { keys::phase, keys::name, keys::coefficient }; + const std::vector optional_keys = {}; + for (const auto& term : object) + { + auto term_errors = CheckSchema(term, required_keys, optional_keys); + errors.insert(errors.end(), term_errors.begin(), term_errors.end()); + } + return errors; + } + } // namespace + + Errors CheckAerosolRepresentationsSchema(const YAML::Node& representations_list) + { + Errors errors; + + for (const auto& object : AsSequence(representations_list)) + { + // Every representation needs a type to dispatch the remaining required keys on. + if (!object[keys::type]) + { + ErrorLocation error_location{ object.Mark().line, object.Mark().column }; + errors.push_back({ ErrorCode::RequiredKeyNotFound, + mc_fmt::format("{} error: Missing 'type' object in aerosol representation.", error_location) }); + continue; + } + + // Keys shared by every representation type. + std::vector required_keys = { keys::type, keys::name, keys::phases }; + std::vector optional_keys = {}; + + const std::string type = object[keys::type].as(); + if (type == keys::UniformSection_key) + { + required_keys.push_back(keys::minimum_radius); + required_keys.push_back(keys::maximum_radius); + } + else if (type == keys::SingleMomentMode_key) + { + required_keys.push_back(keys::geometric_mean_radius); + required_keys.push_back(keys::geometric_standard_deviation); + } + else if (type == keys::TwoMomentMode_key) + { + required_keys.push_back(keys::geometric_standard_deviation); + } + else + { + const auto& node = object[keys::type]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + errors.push_back( + { ErrorCode::UnknownType, + mc_fmt::format("{} error: Unknown aerosol representation type '{}' found.", error_location, type) }); + continue; + } + + auto schema_errors = CheckSchema(object, required_keys, optional_keys); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + return errors; + } + + Errors CheckAerosolProcessesSchema(const YAML::Node& processes_list) + { + Errors errors; + + for (const auto& object : AsSequence(processes_list)) + { + // Every entry needs a type to dispatch the remaining required keys on. + if (!object[keys::type]) + { + ErrorLocation error_location{ object.Mark().line, object.Mark().column }; + errors.push_back({ ErrorCode::RequiredKeyNotFound, + mc_fmt::format("{} error: Missing 'type' object in aerosol process.", error_location) }); + continue; + } + + std::vector required_keys; + std::vector optional_keys; + Errors nested_errors; + + const std::string type = object[keys::type].as(); + if (type == keys::HenryLawPhaseTransfer_key) + { + // The diffusion coefficient is not given here. It is sourced from the gas-phase + // species' definition in the phases section (see ValidateAerosolModel). + required_keys = { keys::type, + keys::gas_phase, + keys::gas_phase_species, + keys::condensed_phase, + keys::condensed_phase_species, + keys::solvent, + keys::henry_law_constant, + keys::accommodation_coefficient }; + if (object[keys::henry_law_constant]) + nested_errors = CheckHenryLawConstantSchema(object[keys::henry_law_constant]); + } + else if (type == keys::DissolvedReaction_key) + { + required_keys = { keys::type, keys::condensed_phase, keys::solvent, + keys::reactants, keys::products, keys::rate_constants }; + if (object[keys::reactants]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::products]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::products]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::rate_constants]) + { + auto e = CheckRateConstantMapSchema(object[keys::rate_constants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + } + else if (type == keys::DissolvedReversibleReaction_key) + { + required_keys = { keys::type, keys::condensed_phase, keys::solvent, keys::reactants, keys::products }; + optional_keys = { keys::forward_rate_constants, keys::reverse_rate_constants, keys::equilibrium_constant }; + if (object[keys::reactants]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::products]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::products]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::forward_rate_constants]) + { + auto e = CheckRateConstantMapSchema(object[keys::forward_rate_constants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::reverse_rate_constants]) + { + auto e = CheckRateConstantMapSchema(object[keys::reverse_rate_constants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::equilibrium_constant]) + { + auto e = CheckArrheniusReferenceTemperatureSchema(object[keys::equilibrium_constant]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + } + else if (type == keys::HenryLawEquilibrium_key) + { + // The solvent's molecular weight and density are not given here; they are sourced from + // the solvent species' definition (see ValidateAerosolModel). + required_keys = { keys::type, + keys::gas_phase, + keys::gas_phase_species, + keys::condensed_phase, + keys::condensed_phase_species, + keys::solvent, + keys::henry_law_constant }; + if (object[keys::henry_law_constant]) + nested_errors = CheckHenryLawConstantSchema(object[keys::henry_law_constant]); + } + else if (type == keys::DissolvedEquilibrium_key) + { + required_keys = { keys::type, + keys::condensed_phase, + keys::solvent, + keys::reactants, + keys::products, + keys::algebraic_species, + keys::equilibrium_constant }; + if (object[keys::reactants]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::products]) + { + auto e = CheckReactantsOrProductsSchema(object[keys::products]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + if (object[keys::equilibrium_constant]) + { + auto e = CheckArrheniusReferenceTemperatureSchema(object[keys::equilibrium_constant]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + } + else if (type == keys::LinearConstraint_key) + { + required_keys = { keys::type, keys::algebraic_phase, keys::algebraic_species, keys::terms }; + optional_keys = { keys::constant, keys::diagnose_from_state }; + if (object[keys::terms]) + { + auto e = CheckLinearConstraintTermsSchema(object[keys::terms]); + nested_errors.insert(nested_errors.end(), e.begin(), e.end()); + } + // The RHS constant is given either as a fixed value or diagnosed from state, not both. + if (object[keys::constant] && object[keys::diagnose_from_state]) + { + const auto& node = object[keys::diagnose_from_state]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + nested_errors.push_back({ ErrorCode::MutuallyExclusiveOption, + mc_fmt::format( + "{} error: Mutually exclusive option of '{}' and '{}' found in '{}'.", + error_location, + keys::constant, + keys::diagnose_from_state, + type) }); + } + } + else + { + const auto& node = object[keys::type]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + errors.push_back({ ErrorCode::UnknownType, + mc_fmt::format("{} error: Unknown aerosol process type '{}' found.", error_location, type) }); + continue; + } + + auto schema_errors = CheckSchema(object, required_keys, optional_keys); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + errors.insert(errors.end(), nested_errors.begin(), nested_errors.end()); + } + + return errors; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/aerosol/utils.cpp b/src/v1/aerosol/utils.cpp new file mode 100644 index 00000000..b878b7c9 --- /dev/null +++ b/src/v1/aerosol/utils.cpp @@ -0,0 +1,59 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/aerosol/utils.hpp" + +#include + +namespace mechanism_configuration::v1 +{ + std::optional FindPhaseSpeciesDiffusionCoefficient( + const std::vector& phases, + const std::string& phase_name, + const std::string& species_name) + { + for (const auto& phase : phases) + { + if (phase.name != phase_name) + continue; + for (const auto& species : phase.species) + { + if (species.name == species_name) + return species.diffusion_coefficient; + } + } + return std::nullopt; + } + + std::optional FindPhaseSpeciesDensity( + const std::vector& phases, + const std::string& phase_name, + const std::string& species_name) + { + for (const auto& phase : phases) + { + if (phase.name != phase_name) + continue; + for (const auto& species : phase.species) + { + if (species.name == species_name) + return species.density; + } + } + return std::nullopt; + } + + std::optional FindSpeciesMolecularWeight( + const std::vector& species, + const std::string& species_name) + { + for (const auto& s : species) + { + if (s.name == species_name) + return s.molecular_weight; + } + return std::nullopt; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/aerosol_type_parsers.cpp b/src/v1/aerosol_type_parsers.cpp deleted file mode 100644 index 589ed655..00000000 --- a/src/v1/aerosol_type_parsers.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright (C) 2023–2026 University Corporation for Atmospheric Research -// University of Illinois at Urbana-Champaign -// SPDX-License-Identifier: Apache-2.0 - -#include "detail/v1/aerosol_type_parsers.hpp" - -#include "detail/v1/aerosol_keys.hpp" -#include "detail/v1/keys.hpp" -#include "detail/v1/type_parsers.hpp" -#include "detail/v1/utils.hpp" - -namespace mechanism_configuration -{ - namespace v1 - { - // ---------------------------------------- - // Rate constants parser - // ---------------------------------------- - - types::ArrheniusReferenceTemperature ParseArrheniusReferenceTemperature(const YAML::Node& object) - { - types::ArrheniusReferenceTemperature rate_constant; - - rate_constant.A = object[keys::A].as(); - if (object[keys::henry_law_C]) - rate_constant.C = object[keys::henry_law_C].as(); - if (object[keys::reference_temperature]) - rate_constant.T0 = object[keys::reference_temperature].as(); - - return rate_constant; - } - - types::Arrhenius ParseArrhenius(const YAML::Node& object) - { - types::Arrhenius rate_constant; - - rate_constant.A = object[keys::A].as(); - if (object[keys::B]) - rate_constant.B = object[keys::B].as(); - if (object[keys::C]) - rate_constant.C = object[keys::C].as(); - if (object[keys::D]) - rate_constant.D = object[keys::D].as(); - if (object[keys::E]) - rate_constant.E = object[keys::E].as(); - - return rate_constant; - } - - types::RateConstant ParseRateConstant(const YAML::Node& object) - { - if (object[keys::type] && object[keys::type].as() == keys::ArrheniusReferenceTemperature_key) - return ParseArrheniusReferenceTemperature(object); - - // ARRHENIUS is the default when no (recognized) type is given. - return ParseArrhenius(object); - } - - types::HenryLawConstant ParseHenryLawConstant(const YAML::Node& object) - { - types::HenryLawConstant henry_law_constant; - - henry_law_constant.HLC_ref = object[keys::HLC_ref].as(); - if (object[keys::henry_law_C]) - henry_law_constant.C = object[keys::henry_law_C].as(); - if (object[keys::reference_temperature]) - henry_law_constant.T0 = object[keys::reference_temperature].as(); - - return henry_law_constant; - } - - std::map ParseRateConstantMap(const YAML::Node& object) - { - std::map rate_constants; - - // Each key is an aerosol representation name (e.g. "CLOUD"); each value is a rate-constant - // block whose own `type` selects the RateConstant variant alternative. - for (const auto& entry : object) - { - rate_constants.emplace(entry.first.as(), ParseRateConstant(entry.second)); - } - - return rate_constants; - } - - // ---------------------------------------- - // Representation parsers - // ---------------------------------------- - - namespace - { - std::vector ParsePhaseNames(const YAML::Node& object) - { - std::vector phases; - for (const auto& phase : object[keys::phases]) - phases.push_back(phase.as()); - return phases; - } - } // namespace - - types::UniformSection ParseUniformSection(const YAML::Node& object) - { - types::UniformSection uniform_section; - - uniform_section.name = object[keys::name].as(); - uniform_section.phases = ParsePhaseNames(object); - uniform_section.min_radius = object[keys::minimum_radius].as(); - uniform_section.max_radius = object[keys::maximum_radius].as(); - - return uniform_section; - } - - types::SingleMomentMode ParseSingleMomentMode(const YAML::Node& object) - { - types::SingleMomentMode single_moment_mode; - - single_moment_mode.name = object[keys::name].as(); - single_moment_mode.phases = ParsePhaseNames(object); - single_moment_mode.geometric_mean_radius = object[keys::geometric_mean_radius].as(); - single_moment_mode.geometric_standard_deviation = object[keys::geometric_standard_deviation].as(); - - return single_moment_mode; - } - - types::TwoMomentMode ParseTwoMomentMode(const YAML::Node& object) - { - types::TwoMomentMode two_moment_mode; - - two_moment_mode.name = object[keys::name].as(); - two_moment_mode.phases = ParsePhaseNames(object); - two_moment_mode.geometric_standard_deviation = object[keys::geometric_standard_deviation].as(); - - return two_moment_mode; - } - - // ---------------------------------------- - // Process parsers - // ---------------------------------------- - - types::HenryLawPhaseTransfer ParseHenryLawPhaseTransfer( - const YAML::Node& object, - const std::vector& phases) - { - types::HenryLawPhaseTransfer transfer; - - transfer.gas_phase = object[keys::gas_phase].as(); - transfer.gas_species = object[keys::gas_phase_species].as(); - transfer.condensed_phase = object[keys::condensed_phase].as(); - transfer.condensed_species = object[keys::condensed_phase_species].as(); - transfer.solvent = object[keys::solvent].as(); - transfer.henry_law_constant = ParseHenryLawConstant(object[keys::henry_law_constant]); - // Sourced from the gas-phase species' definition; presence is enforced by ValidateAerosolModel, - // which runs before this result is returned, so a missing value defaults harmlessly here. - transfer.diffusion_coefficient = - FindPhaseSpeciesDiffusionCoefficient(phases, transfer.gas_phase, transfer.gas_species).value_or(0.0); - transfer.accommodation_coefficient = object[keys::accommodation_coefficient].as(); - - return transfer; - } - - types::DissolvedReaction ParseDissolvedReaction(const YAML::Node& object) - { - types::DissolvedReaction reaction; - - reaction.phase = object[keys::condensed_phase].as(); - reaction.solvent = object[keys::solvent].as(); - reaction.reactants = ParseReactionComponents(object, keys::reactants); - reaction.products = ParseReactionComponents(object, keys::products); - reaction.rate_constants = ParseRateConstantMap(object[keys::rate_constants]); - - return reaction; - } - - types::DissolvedReversibleReaction ParseDissolvedReversibleReaction(const YAML::Node& object) - { - types::DissolvedReversibleReaction reaction; - - reaction.phase = object[keys::condensed_phase].as(); - reaction.solvent = object[keys::solvent].as(); - reaction.reactants = ParseReactionComponents(object, keys::reactants); - reaction.products = ParseReactionComponents(object, keys::products); - - // Any two of {forward, reverse, equilibrium} may be supplied; the third is derived - // downstream, so each is parsed only when present. - if (object[keys::forward_rate_constants]) - reaction.forward_rate_constants = ParseRateConstantMap(object[keys::forward_rate_constants]); - if (object[keys::reverse_rate_constants]) - reaction.reverse_rate_constants = ParseRateConstantMap(object[keys::reverse_rate_constants]); - if (object[keys::equilibrium_constant]) - reaction.equilibrium_constant = ParseArrheniusReferenceTemperature(object[keys::equilibrium_constant]); - - return reaction; - } - - // ---------------------------------------- - // Constraint parsers - // ---------------------------------------- - - types::HenryLawEquilibrium ParseHenryLawEquilibrium( - const YAML::Node& object, - const std::vector& species, - const std::vector& phases) - { - types::HenryLawEquilibrium equilibrium; - - equilibrium.gas_phase = object[keys::gas_phase].as(); - equilibrium.gas_species = object[keys::gas_phase_species].as(); - equilibrium.condensed_phase = object[keys::condensed_phase].as(); - equilibrium.condensed_species = object[keys::condensed_phase_species].as(); - equilibrium.solvent = object[keys::solvent].as(); - equilibrium.henry_law_constant = ParseHenryLawConstant(object[keys::henry_law_constant]); - - // Sourced from the solvent species' definition: molecular weight from the species section, - // density from the condensed phase. Presence is enforced by ValidateAerosolModel, which runs - // before this result is returned, so a missing value defaults harmlessly here. - equilibrium.solvent_molecular_weight = FindSpeciesMolecularWeight(species, equilibrium.solvent).value_or(0.0); - equilibrium.solvent_density = - FindPhaseSpeciesDensity(phases, equilibrium.condensed_phase, equilibrium.solvent).value_or(0.0); - - return equilibrium; - } - - types::DissolvedEquilibrium ParseDissolvedEquilibrium(const YAML::Node& object) - { - types::DissolvedEquilibrium equilibrium; - - equilibrium.phase = object[keys::condensed_phase].as(); - equilibrium.algebraic_species = object[keys::algebraic_species].as(); - equilibrium.solvent = object[keys::solvent].as(); - equilibrium.reactants = ParseReactionComponents(object, keys::reactants); - equilibrium.products = ParseReactionComponents(object, keys::products); - equilibrium.equilibrium_constant = ParseArrheniusReferenceTemperature(object[keys::equilibrium_constant]); - - return equilibrium; - } - - types::LinearConstraint ParseLinearConstraint(const YAML::Node& object) - { - types::LinearConstraint constraint; - - constraint.algebraic_phase = object[keys::algebraic_phase].as(); - constraint.algebraic_species = object[keys::algebraic_species].as(); - - for (const auto& term_node : object[keys::terms]) - { - types::LinearConstraintTerm term; - term.phase = term_node[keys::phase].as(); - term.name = term_node[keys::name].as(); - term.coefficient = term_node[keys::coefficient].as(); - constraint.terms.push_back(term); - } - - // RHS constant: diagnose-from-state, else a fixed value, else the default FixedConstant{0}. - if (object[keys::diagnose_from_state] && object[keys::diagnose_from_state].as()) - constraint.constant = types::DiagnoseFromState{}; - else if (object[keys::constant]) - constraint.constant = types::FixedConstant{ object[keys::constant].as() }; - - return constraint; - } - - // ---------------------------------------- - // Container parsers - // ---------------------------------------- - - std::vector ParseAerosolRepresentations(const YAML::Node& objects) - { - std::vector representations; - - for (const auto& object : objects) - { - const auto type = object[keys::type].as(); - - if (type == keys::UniformSection_key) - representations.emplace_back(ParseUniformSection(object)); - else if (type == keys::SingleMomentMode_key) - representations.emplace_back(ParseSingleMomentMode(object)); - else if (type == keys::TwoMomentMode_key) - representations.emplace_back(ParseTwoMomentMode(object)); - } - - return representations; - } - - types::Aerosol ParseAerosol( - const YAML::Node& object, - const std::vector& species, - const std::vector& phases) - { - types::Aerosol aerosol; - - if (object[keys::aerosol_representations]) - aerosol.representations = ParseAerosolRepresentations(object[keys::aerosol_representations]); - - if (object[keys::aerosol_processes]) - { - // Aerosol processes section mixes process and constraint entries. - for (const auto& entry : object[keys::aerosol_processes]) - { - const auto type = entry[keys::type].as(); - - // Processes - if (type == keys::HenryLawPhaseTransfer_key) - aerosol.processes.emplace_back(ParseHenryLawPhaseTransfer(entry, phases)); - else if (type == keys::DissolvedReaction_key) - aerosol.processes.emplace_back(ParseDissolvedReaction(entry)); - else if (type == keys::DissolvedReversibleReaction_key) - aerosol.processes.emplace_back(ParseDissolvedReversibleReaction(entry)); - // Constraints - else if (type == keys::HenryLawEquilibrium_key) - aerosol.constraints.emplace_back(ParseHenryLawEquilibrium(entry, species, phases)); - else if (type == keys::DissolvedEquilibrium_key) - aerosol.constraints.emplace_back(ParseDissolvedEquilibrium(entry)); - else if (type == keys::LinearConstraint_key) - aerosol.constraints.emplace_back(ParseLinearConstraint(entry)); - } - } - - return aerosol; - } - - } // namespace v1 -} // namespace mechanism_configuration diff --git a/src/v1/aerosol_type_schema.cpp b/src/v1/aerosol_type_schema.cpp deleted file mode 100644 index e442ea47..00000000 --- a/src/v1/aerosol_type_schema.cpp +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (C) 2023–2026 University Corporation for Atmospheric Research -// University of Illinois at Urbana-Champaign -// SPDX-License-Identifier: Apache-2.0 - -#include "detail/v1/aerosol_type_schema.hpp" - -#include "detail/check_schema.hpp" -#include "detail/error_format.hpp" -#include "detail/v1/aerosol_keys.hpp" -#include "detail/v1/keys.hpp" -#include "detail/v1/type_schema.hpp" -#include "detail/v1/utils.hpp" - -#include - -#include -#include -#include - -namespace mechanism_configuration -{ - namespace v1 - { - namespace - { - Errors CheckArrheniusReferenceTemperatureSchema(const YAML::Node& object) - { - const std::vector required_keys = { keys::A, keys::henry_law_C }; - const std::vector optional_keys = { keys::type, keys::reference_temperature }; - return CheckSchema(object, required_keys, optional_keys); - } - - Errors CheckArrheniusSchema(const YAML::Node& object) - { - const std::vector required_keys = { keys::A, keys::C }; - const std::vector optional_keys = { keys::type }; - return CheckSchema(object, required_keys, optional_keys); - } - - Errors CheckRateConstantSchema(const YAML::Node& object) - { - if (object[keys::type] && object[keys::type].as() == keys::ArrheniusReferenceTemperature_key) - return CheckArrheniusReferenceTemperatureSchema(object); - return CheckArrheniusSchema(object); - } - - Errors CheckHenryLawConstantSchema(const YAML::Node& object) - { - const std::vector required_keys = { keys::HLC_ref, keys::henry_law_C }; - const std::vector optional_keys = { keys::reference_temperature }; - return CheckSchema(object, required_keys, optional_keys); - } - - // A per-representation rate-constant map. Each value is a typed rate-constant block. - Errors CheckRateConstantMapSchema(const YAML::Node& object) - { - Errors errors; - for (const auto& entry : object) - { - auto entry_errors = CheckRateConstantSchema(entry.second); - errors.insert(errors.end(), entry_errors.begin(), entry_errors.end()); - } - return errors; - } - - // Each linear-constraint term references a species in a phase with a coefficient. - Errors CheckLinearConstraintTermsSchema(const YAML::Node& object) - { - Errors errors; - const std::vector required_keys = { keys::phase, keys::name, keys::coefficient }; - const std::vector optional_keys = {}; - for (const auto& term : object) - { - auto term_errors = CheckSchema(term, required_keys, optional_keys); - errors.insert(errors.end(), term_errors.begin(), term_errors.end()); - } - return errors; - } - } // namespace - - Errors CheckAerosolRepresentationsSchema(const YAML::Node& representations_list) - { - Errors errors; - - for (const auto& object : AsSequence(representations_list)) - { - // Every representation needs a type to dispatch the remaining required keys on. - if (!object[keys::type]) - { - ErrorLocation error_location{ object.Mark().line, object.Mark().column }; - errors.push_back({ ErrorCode::RequiredKeyNotFound, - mc_fmt::format("{} error: Missing 'type' object in aerosol representation.", error_location) }); - continue; - } - - // Keys shared by every representation type. - std::vector required_keys = { keys::type, keys::name, keys::phases }; - std::vector optional_keys = {}; - - const std::string type = object[keys::type].as(); - if (type == keys::UniformSection_key) - { - required_keys.push_back(keys::minimum_radius); - required_keys.push_back(keys::maximum_radius); - } - else if (type == keys::SingleMomentMode_key) - { - required_keys.push_back(keys::geometric_mean_radius); - required_keys.push_back(keys::geometric_standard_deviation); - } - else if (type == keys::TwoMomentMode_key) - { - required_keys.push_back(keys::geometric_standard_deviation); - } - else - { - const auto& node = object[keys::type]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - errors.push_back( - { ErrorCode::UnknownType, - mc_fmt::format("{} error: Unknown aerosol representation type '{}' found.", error_location, type) }); - continue; - } - - auto schema_errors = CheckSchema(object, required_keys, optional_keys); - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - return errors; - } - - Errors CheckAerosolProcessesSchema(const YAML::Node& processes_list) - { - Errors errors; - - for (const auto& object : AsSequence(processes_list)) - { - // Every entry needs a type to dispatch the remaining required keys on. - if (!object[keys::type]) - { - ErrorLocation error_location{ object.Mark().line, object.Mark().column }; - errors.push_back({ ErrorCode::RequiredKeyNotFound, - mc_fmt::format("{} error: Missing 'type' object in aerosol process.", error_location) }); - continue; - } - - std::vector required_keys; - std::vector optional_keys; - Errors nested_errors; - - const std::string type = object[keys::type].as(); - if (type == keys::HenryLawPhaseTransfer_key) - { - // The diffusion coefficient is not given here. It is sourced from the gas-phase - // species' definition in the phases section (see ValidateAerosolModel). - required_keys = { keys::type, - keys::gas_phase, - keys::gas_phase_species, - keys::condensed_phase, - keys::condensed_phase_species, - keys::solvent, - keys::henry_law_constant, - keys::accommodation_coefficient }; - if (object[keys::henry_law_constant]) - nested_errors = CheckHenryLawConstantSchema(object[keys::henry_law_constant]); - } - else if (type == keys::DissolvedReaction_key) - { - required_keys = { keys::type, keys::condensed_phase, keys::solvent, - keys::reactants, keys::products, keys::rate_constants }; - if (object[keys::reactants]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::products]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::products]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::rate_constants]) - { - auto e = CheckRateConstantMapSchema(object[keys::rate_constants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - } - else if (type == keys::DissolvedReversibleReaction_key) - { - required_keys = { keys::type, keys::condensed_phase, keys::solvent, keys::reactants, keys::products }; - optional_keys = { keys::forward_rate_constants, keys::reverse_rate_constants, keys::equilibrium_constant }; - if (object[keys::reactants]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::products]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::products]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::forward_rate_constants]) - { - auto e = CheckRateConstantMapSchema(object[keys::forward_rate_constants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::reverse_rate_constants]) - { - auto e = CheckRateConstantMapSchema(object[keys::reverse_rate_constants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::equilibrium_constant]) - { - auto e = CheckArrheniusReferenceTemperatureSchema(object[keys::equilibrium_constant]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - } - else if (type == keys::HenryLawEquilibrium_key) - { - // The solvent's molecular weight and density are not given here; they are sourced from - // the solvent species' definition (see ValidateAerosolModel). - required_keys = { keys::type, - keys::gas_phase, - keys::gas_phase_species, - keys::condensed_phase, - keys::condensed_phase_species, - keys::solvent, - keys::henry_law_constant }; - if (object[keys::henry_law_constant]) - nested_errors = CheckHenryLawConstantSchema(object[keys::henry_law_constant]); - } - else if (type == keys::DissolvedEquilibrium_key) - { - required_keys = { keys::type, - keys::condensed_phase, - keys::solvent, - keys::reactants, - keys::products, - keys::algebraic_species, - keys::equilibrium_constant }; - if (object[keys::reactants]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::reactants]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::products]) - { - auto e = CheckReactantsOrProductsSchema(object[keys::products]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - if (object[keys::equilibrium_constant]) - { - auto e = CheckArrheniusReferenceTemperatureSchema(object[keys::equilibrium_constant]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - } - else if (type == keys::LinearConstraint_key) - { - required_keys = { keys::type, keys::algebraic_phase, keys::algebraic_species, keys::terms }; - optional_keys = { keys::constant, keys::diagnose_from_state }; - if (object[keys::terms]) - { - auto e = CheckLinearConstraintTermsSchema(object[keys::terms]); - nested_errors.insert(nested_errors.end(), e.begin(), e.end()); - } - // The RHS constant is given either as a fixed value or diagnosed from state, not both. - if (object[keys::constant] && object[keys::diagnose_from_state]) - { - const auto& node = object[keys::diagnose_from_state]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - nested_errors.push_back({ ErrorCode::MutuallyExclusiveOption, - mc_fmt::format( - "{} error: Mutually exclusive option of '{}' and '{}' found in '{}'.", - error_location, - keys::constant, - keys::diagnose_from_state, - type) }); - } - } - else - { - const auto& node = object[keys::type]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - errors.push_back({ ErrorCode::UnknownType, - mc_fmt::format("{} error: Unknown aerosol process type '{}' found.", error_location, type) }); - continue; - } - - auto schema_errors = CheckSchema(object, required_keys, optional_keys); - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - errors.insert(errors.end(), nested_errors.begin(), nested_errors.end()); - } - - return errors; - } - - } // namespace v1 -} // namespace mechanism_configuration diff --git a/src/v1/emissions/CMakeLists.txt b/src/v1/emissions/CMakeLists.txt new file mode 100644 index 00000000..029e1c1d --- /dev/null +++ b/src/v1/emissions/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(mechanism_configuration + PRIVATE + parsers.cpp + schema.cpp +) diff --git a/src/v1/emissions/parsers.cpp b/src/v1/emissions/parsers.cpp new file mode 100644 index 00000000..499a3e20 --- /dev/null +++ b/src/v1/emissions/parsers.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/emissions/parsers.hpp" + +#include "detail/v1/emissions/keys.hpp" +#include "detail/v1/keys.hpp" +#include "detail/v1/utils.hpp" + +#include + +namespace mechanism_configuration::v1 +{ + namespace + { + types::SourceType ParseSourceType(const std::string& s) + { + if (s == std::string(keys::type_fire)) + return types::SourceType::Fire; + if (s == std::string(keys::type_biogenic)) + return types::SourceType::Biogenic; + if (s == std::string(keys::type_dust)) + return types::SourceType::Dust; + if (s == std::string(keys::type_sea_salt)) + return types::SourceType::SeaSalt; + if (s == std::string(keys::type_lightning)) + return types::SourceType::Lightning; + return types::SourceType::Anthropogenic; + } + + types::TemporalInterpolation ParseTemporalInterpolation(const std::string& s) + { + if (s == std::string(keys::interp_nearest)) + return types::TemporalInterpolation::Nearest; + if (s == std::string(keys::interp_none)) + return types::TemporalInterpolation::None; + return types::TemporalInterpolation::Linear; + } + } // namespace + + types::EmissionsConfig ParseEmissions(const YAML::Node& node) + { + types::EmissionsConfig config; + + if (node[std::string(keys::inventories)]) + { + for (const auto& item : node[std::string(keys::inventories)]) + { + types::Inventory inv; + inv.name = item[std::string(keys::name)].as(); + inv.directory = item[std::string(keys::directory)].as(); + inv.file_pattern = item[std::string(keys::file_pattern)].as(); + inv.convention = item[std::string(keys::convention)].as(); + config.inventories.push_back(std::move(inv)); + } + } + + if (node[std::string(keys::species_maps)]) + { + for (const auto& item : node[std::string(keys::species_maps)]) + { + types::SpeciesMap smap; + smap.name = item[std::string(keys::name)].as(); + for (const auto& mapping_node : item[std::string(keys::mappings)]) + { + types::SpeciesMapping m; + m.inventory_species = mapping_node[std::string(keys::inventory_species)].as(); + m.mechanism_species = mapping_node[std::string(keys::mechanism_species)].as(); + if (mapping_node[std::string(keys::scaling_factor)]) + m.scaling_factor = mapping_node[std::string(keys::scaling_factor)].as(); + smap.mappings.push_back(std::move(m)); + } + config.species_maps.push_back(std::move(smap)); + } + } + + if (node[std::string(keys::regridding)] && node[std::string(keys::regridding)][std::string(keys::type)]) + { + config.regridding.type = types::RegriddingType::None; + } + + if (node[std::string(keys::sources)]) + { + for (const auto& s : node[std::string(keys::sources)]) + { + types::SourceDescriptor src; + src.name = s[std::string(keys::name)].as(); + src.type = ParseSourceType(s[std::string(keys::type)].as()); + src.inventory = s[std::string(keys::inventory)].as(); + src.species_map = s[std::string(keys::species_map)].as(); + + if (s[std::string(keys::temporal_interpolation)]) + src.temporal_interpolation = + ParseTemporalInterpolation(s[std::string(keys::temporal_interpolation)].as()); + + if (s[std::string(keys::category)]) + src.category = s[std::string(keys::category)].as(); + if (s[std::string(keys::hierarchy)]) + src.hierarchy = s[std::string(keys::hierarchy)].as(); + if (s[std::string(keys::scaling_factor)]) + src.scaling_factor = s[std::string(keys::scaling_factor)].as(); + if (s[std::string(keys::sector)]) + src.sector = s[std::string(keys::sector)].as(); + + src.unknown_properties = GetComments(s); + config.sources.push_back(std::move(src)); + } + } + + return config; + } +} // namespace mechanism_configuration::v1 diff --git a/src/v1/emissions_parser.cpp b/src/v1/emissions/schema.cpp similarity index 66% rename from src/v1/emissions_parser.cpp rename to src/v1/emissions/schema.cpp index 4ffdbb0e..2bd164cc 100644 --- a/src/v1/emissions_parser.cpp +++ b/src/v1/emissions/schema.cpp @@ -2,10 +2,11 @@ // University of Illinois at Urbana-Champaign // SPDX-License-Identifier: Apache-2.0 -#include "detail/v1/emissions_parser.hpp" +#include "detail/v1/emissions/schema.hpp" -#include "detail/check_schema.hpp" #include "detail/error_format.hpp" +#include "detail/schema.hpp" +#include "detail/v1/emissions/keys.hpp" #include "detail/v1/keys.hpp" #include "detail/v1/utils.hpp" @@ -20,30 +21,6 @@ namespace mechanism_configuration::v1 { namespace { - types::SourceType ParseSourceType(const std::string& s) - { - if (s == std::string(keys::type_fire)) - return types::SourceType::Fire; - if (s == std::string(keys::type_biogenic)) - return types::SourceType::Biogenic; - if (s == std::string(keys::type_dust)) - return types::SourceType::Dust; - if (s == std::string(keys::type_sea_salt)) - return types::SourceType::SeaSalt; - if (s == std::string(keys::type_lightning)) - return types::SourceType::Lightning; - return types::SourceType::Anthropogenic; - } - - types::TemporalInterpolation ParseTemporalInterpolation(const std::string& s) - { - if (s == std::string(keys::interp_nearest)) - return types::TemporalInterpolation::Nearest; - if (s == std::string(keys::interp_none)) - return types::TemporalInterpolation::None; - return types::TemporalInterpolation::Linear; - } - Errors CheckInventoriesSchema(const YAML::Node& inventories_node) { Errors errors; @@ -226,75 +203,4 @@ namespace mechanism_configuration::v1 return errors; } - types::EmissionsConfig ParseEmissionsSection(const YAML::Node& node) - { - types::EmissionsConfig config; - - if (node[std::string(keys::inventories)]) - { - for (const auto& item : node[std::string(keys::inventories)]) - { - types::Inventory inv; - inv.name = item[std::string(keys::name)].as(); - inv.directory = item[std::string(keys::directory)].as(); - inv.file_pattern = item[std::string(keys::file_pattern)].as(); - inv.convention = item[std::string(keys::convention)].as(); - config.inventories.push_back(std::move(inv)); - } - } - - if (node[std::string(keys::species_maps)]) - { - for (const auto& item : node[std::string(keys::species_maps)]) - { - types::SpeciesMap smap; - smap.name = item[std::string(keys::name)].as(); - for (const auto& mapping_node : item[std::string(keys::mappings)]) - { - types::SpeciesMapping m; - m.inventory_species = mapping_node[std::string(keys::inventory_species)].as(); - m.mechanism_species = mapping_node[std::string(keys::mechanism_species)].as(); - if (mapping_node[std::string(keys::scaling_factor)]) - m.scaling_factor = mapping_node[std::string(keys::scaling_factor)].as(); - smap.mappings.push_back(std::move(m)); - } - config.species_maps.push_back(std::move(smap)); - } - } - - if (node[std::string(keys::regridding)] && node[std::string(keys::regridding)][std::string(keys::type)]) - { - config.regridding.type = types::RegriddingType::None; - } - - if (node[std::string(keys::sources)]) - { - for (const auto& s : node[std::string(keys::sources)]) - { - types::SourceDescriptor src; - src.name = s[std::string(keys::name)].as(); - src.type = ParseSourceType(s[std::string(keys::type)].as()); - src.inventory = s[std::string(keys::inventory)].as(); - src.species_map = s[std::string(keys::species_map)].as(); - - if (s[std::string(keys::temporal_interpolation)]) - src.temporal_interpolation = - ParseTemporalInterpolation(s[std::string(keys::temporal_interpolation)].as()); - - if (s[std::string(keys::category)]) - src.category = s[std::string(keys::category)].as(); - if (s[std::string(keys::hierarchy)]) - src.hierarchy = s[std::string(keys::hierarchy)].as(); - if (s[std::string(keys::scaling_factor)]) - src.scaling_factor = s[std::string(keys::scaling_factor)].as(); - if (s[std::string(keys::sector)]) - src.sector = s[std::string(keys::sector)].as(); - - src.unknown_properties = GetComments(s); - config.sources.push_back(std::move(src)); - } - } - - return config; - } } // namespace mechanism_configuration::v1 diff --git a/src/v1/parser.cpp b/src/v1/parser.cpp index 3f8f2d87..0bcd1327 100644 --- a/src/v1/parser.cpp +++ b/src/v1/parser.cpp @@ -4,15 +4,19 @@ #include "detail/v1/parser.hpp" -#include "detail/check_schema.hpp" #include "detail/error_format.hpp" -#include "detail/v1/aerosol_keys.hpp" -#include "detail/v1/aerosol_type_parsers.hpp" -#include "detail/v1/aerosol_type_schema.hpp" -#include "detail/v1/emissions_parser.hpp" +#include "detail/schema.hpp" +#include "detail/v1/aerosol/keys.hpp" +#include "detail/v1/aerosol/parsers.hpp" +#include "detail/v1/aerosol/schema.hpp" +#include "detail/v1/emissions/keys.hpp" +#include "detail/v1/emissions/parsers.hpp" +#include "detail/v1/emissions/schema.hpp" #include "detail/v1/keys.hpp" -#include "detail/v1/type_parsers.hpp" -#include "detail/v1/type_schema.hpp" +#include "detail/v1/reactions/parsers.hpp" +#include "detail/v1/reactions/schema.hpp" +#include "detail/v1/species/parsers.hpp" +#include "detail/v1/species/schema.hpp" #include "detail/v1/utils.hpp" #include @@ -52,7 +56,7 @@ namespace mechanism_configuration::v1 if (!reaction[k]) return; for (const auto& item : AsSequence(reaction[k])) - out.push_back({ GetReactionComponentName(item), LocationOf(item) }); + out.push_back({ GetComponentName(item), LocationOf(item) }); } } // namespace @@ -62,7 +66,7 @@ namespace mechanism_configuration::v1 if (object[std::string(keys::species)]) for (const auto& s : object[std::string(keys::species)]) - input.species.push_back({ GetReactionComponentName(s), LocationOf(s) }); + input.species.push_back({ GetComponentName(s), LocationOf(s) }); if (object[std::string(keys::phases)]) for (const auto& phase : object[std::string(keys::phases)]) @@ -72,7 +76,7 @@ namespace mechanism_configuration::v1 pr.location = LocationOf(phase); if (phase[std::string(keys::species)]) for (const auto& ps : phase[std::string(keys::species)]) - pr.species.push_back({ GetReactionComponentName(ps), LocationOf(ps) }); + pr.species.push_back({ GetComponentName(ps), LocationOf(ps) }); input.phases.push_back(std::move(pr)); } @@ -463,10 +467,6 @@ namespace mechanism_configuration::v1 return std::unexpected(std::move(aerosol_errors)); } - if (object[std::string(keys::emissions)]) - { - mechanism.emissions = ParseEmissionsSection(object[std::string(keys::emissions)]); - } return mechanism; } catch (const std::exception& e) @@ -485,6 +485,10 @@ namespace mechanism_configuration::v1 mechanism.species = ParseSpecies(object[keys::species]); mechanism.phases = ParsePhases(object[keys::phases]); + if (object[keys::name]) + { + mechanism.name = object[keys::name].as(); + } if (object[keys::reactions]) { mechanism.reactions = ParseReactions(object[keys::reactions]); @@ -493,9 +497,9 @@ namespace mechanism_configuration::v1 { mechanism.aerosol = ParseAerosol(object, mechanism.species, mechanism.phases); } - if (object[keys::name]) + if (object[std::string(keys::emissions)]) { - mechanism.name = object[keys::name].as(); + mechanism.emissions = ParseEmissions(object[std::string(keys::emissions)]); } return mechanism; diff --git a/src/v1/reactions/CMakeLists.txt b/src/v1/reactions/CMakeLists.txt index 23f955b9..7fb32220 100644 --- a/src/v1/reactions/CMakeLists.txt +++ b/src/v1/reactions/CMakeLists.txt @@ -4,7 +4,9 @@ target_sources(mechanism_configuration branched.cpp emission.cpp first_order_loss.cpp + parsers.cpp photolysis.cpp + schema.cpp surface.cpp taylor_series.cpp ternary_chemical_activation.cpp diff --git a/src/v1/reactions/arrhenius.cpp b/src/v1/reactions/arrhenius.cpp index d4ca6f36..2e7882a6 100644 --- a/src/v1/reactions/arrhenius.cpp +++ b/src/v1/reactions/arrhenius.cpp @@ -6,118 +6,115 @@ #include #include -#include #include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Arrhenius reaction entry + /// Performs schema validation, checks for mutually exclusive parameters (`Ea` vs `C`), + /// and collects any structural errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors ArrheniusParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Arrhenius reaction entry - /// Performs schema validation, checks for mutually exclusive parameters (`Ea` vs `C`), - /// and collects any structural errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors ArrheniusParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::A, keys::B, keys::C, keys::D, keys::E, keys::Ea, keys::name }; - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::A, keys::B, keys::C, keys::D, keys::E, keys::Ea, keys::name }; + Errors errors; - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + return errors; + } - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - if (object[keys::Ea] && object[keys::C]) - { - const auto& node = object[keys::Ea]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - std::string message = mc_fmt::format( - "{} error: Mutually exclusive option of 'Ea' and 'C' found in '{}' reaction.", - error_location, - object[keys::type].as()); + if (object[keys::Ea] && object[keys::C]) + { + const auto& node = object[keys::Ea]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - errors.push_back({ ErrorCode::MutuallyExclusiveOption, message }); - } + std::string message = mc_fmt::format( + "{} error: Mutually exclusive option of 'Ea' and 'C' found in '{}' reaction.", + error_location, + object[keys::type].as()); - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. - return errors; + errors.push_back({ ErrorCode::MutuallyExclusiveOption, message }); } - /// @brief Parses a YAML-defined Arrhenius reaction and appends it to the reaction list. - /// Extracts reactants, products, kinetic parameters (A–E, Ea), gas phase, - /// optional metadata (name, comments), and constructs a `types::Arrhenius` object. - /// @param object The YAML node representing the reaction - /// @param reactions The reactions container to append the parsed reaction to - void ArrheniusParser::Parse(const YAML::Node& object, types::Reactions& reactions) - { - types::Arrhenius arrhenius; + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } - arrhenius.gas_phase = object[keys::gas_phase].as(); - arrhenius.reactants = ParseReactionComponents(object, keys::reactants); - arrhenius.products = ParseReactionComponents(object, keys::products); - arrhenius.unknown_properties = GetComments(object); + /// @brief Parses a YAML-defined Arrhenius reaction and appends it to the reaction list. + /// Extracts reactants, products, kinetic parameters (A–E, Ea), gas phase, + /// optional metadata (name, comments), and constructs a `types::Arrhenius` object. + /// @param object The YAML node representing the reaction + /// @param reactions The reactions container to append the parsed reaction to + void ArrheniusParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Arrhenius arrhenius; - if (object[keys::A]) - { - arrhenius.A = object[keys::A].as(); - } - if (object[keys::B]) - { - arrhenius.B = object[keys::B].as(); - } - if (object[keys::C]) - { - arrhenius.C = object[keys::C].as(); - } - if (object[keys::D]) - { - arrhenius.D = object[keys::D].as(); - } - if (object[keys::E]) - { - arrhenius.E = object[keys::E].as(); - } - if (object[keys::Ea]) - { - arrhenius.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; - } - if (object[keys::name]) - { - arrhenius.name = object[keys::name].as(); - } + arrhenius.gas_phase = object[keys::gas_phase].as(); + arrhenius.reactants = ParseReactionComponents(object, keys::reactants); + arrhenius.products = ParseReactionComponents(object, keys::products); + arrhenius.unknown_properties = GetComments(object); - reactions.arrhenius.emplace_back(std::move(arrhenius)); + if (object[keys::A]) + { + arrhenius.A = object[keys::A].as(); } + if (object[keys::B]) + { + arrhenius.B = object[keys::B].as(); + } + if (object[keys::C]) + { + arrhenius.C = object[keys::C].as(); + } + if (object[keys::D]) + { + arrhenius.D = object[keys::D].as(); + } + if (object[keys::E]) + { + arrhenius.E = object[keys::E].as(); + } + if (object[keys::Ea]) + { + arrhenius.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; + } + if (object[keys::name]) + { + arrhenius.name = object[keys::name].as(); + } + + reactions.arrhenius.emplace_back(std::move(arrhenius)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/branched.cpp b/src/v1/reactions/branched.cpp index 424afd99..efdb28ca 100644 --- a/src/v1/reactions/branched.cpp +++ b/src/v1/reactions/branched.cpp @@ -7,101 +7,98 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Branched reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors BranchedParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Branched reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors BranchedParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { - keys::type, keys::gas_phase, keys::reactants, keys::alkoxy_products, keys::nitrate_products - }; - std::vector optional_keys = { keys::name, keys::X, keys::Y, keys::a0, keys::n }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } + std::vector required_keys = { + keys::type, keys::gas_phase, keys::reactants, keys::alkoxy_products, keys::nitrate_products + }; + std::vector optional_keys = { keys::name, keys::X, keys::Y, keys::a0, keys::n }; - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + Errors errors; - // Alkoxy products - schema_errors = CheckReactantsOrProductsSchema(object[keys::alkoxy_products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + return errors; + } - // Nitrate products - schema_errors = CheckReactantsOrProductsSchema(object[keys::nitrate_products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. - return errors; + // Alkoxy products + schema_errors = CheckReactantsOrProductsSchema(object[keys::alkoxy_products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); } - void BranchedParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Nitrate products + schema_errors = CheckReactantsOrProductsSchema(object[keys::nitrate_products]); + if (!schema_errors.empty()) { - types::Branched branched; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - branched.gas_phase = object[keys::gas_phase].as(); - branched.reactants = ParseReactionComponents(object, keys::reactants); - branched.alkoxy_products = ParseReactionComponents(object, keys::alkoxy_products); - branched.nitrate_products = ParseReactionComponents(object, keys::nitrate_products); - branched.unknown_properties = GetComments(object); + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } - if (object[keys::X]) - { - branched.X = object[keys::X].as(); - } - if (object[keys::Y]) - { - branched.Y = object[keys::Y].as(); - } - if (object[keys::a0]) - { - branched.a0 = object[keys::a0].as(); - } - if (object[keys::n]) - { - branched.n = object[keys::n].as(); - } - if (object[keys::name]) - { - branched.name = object[keys::name].as(); - } + void BranchedParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Branched branched; - reactions.branched.emplace_back(std::move(branched)); + branched.gas_phase = object[keys::gas_phase].as(); + branched.reactants = ParseReactionComponents(object, keys::reactants); + branched.alkoxy_products = ParseReactionComponents(object, keys::alkoxy_products); + branched.nitrate_products = ParseReactionComponents(object, keys::nitrate_products); + branched.unknown_properties = GetComments(object); + + if (object[keys::X]) + { + branched.X = object[keys::X].as(); + } + if (object[keys::Y]) + { + branched.Y = object[keys::Y].as(); + } + if (object[keys::a0]) + { + branched.a0 = object[keys::a0].as(); } + if (object[keys::n]) + { + branched.n = object[keys::n].as(); + } + if (object[keys::name]) + { + branched.name = object[keys::name].as(); + } + + reactions.branched.emplace_back(std::move(branched)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/emission.cpp b/src/v1/reactions/emission.cpp index 5aab25bb..5a725183 100644 --- a/src/v1/reactions/emission.cpp +++ b/src/v1/reactions/emission.cpp @@ -7,71 +7,68 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Emission reaction entry. + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors EmissionParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Emission reaction entry. - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors EmissionParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::scaling_factor }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } + std::vector required_keys = { keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::scaling_factor }; - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + Errors errors; - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void EmissionParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) { - types::Emission emission; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - emission.gas_phase = object[keys::gas_phase].as(); - emission.products = ParseReactionComponents(object, keys::products); - emission.unknown_properties = GetComments(object); + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } - if (object[keys::scaling_factor]) - { - emission.scaling_factor = object[keys::scaling_factor].as(); - } - if (object[keys::name]) - { - emission.name = object[keys::name].as(); - } + void EmissionParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Emission emission; - reactions.emission.emplace_back(std::move(emission)); + emission.gas_phase = object[keys::gas_phase].as(); + emission.products = ParseReactionComponents(object, keys::products); + emission.unknown_properties = GetComments(object); + + if (object[keys::scaling_factor]) + { + emission.scaling_factor = object[keys::scaling_factor].as(); } + if (object[keys::name]) + { + emission.name = object[keys::name].as(); + } + + reactions.emission.emplace_back(std::move(emission)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/first_order_loss.cpp b/src/v1/reactions/first_order_loss.cpp index e76a73a0..85cc95de 100644 --- a/src/v1/reactions/first_order_loss.cpp +++ b/src/v1/reactions/first_order_loss.cpp @@ -6,102 +6,99 @@ #include #include -#include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined First order loss reaction entry. + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors FirstOrderLossParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined First order loss reaction entry. - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors FirstOrderLossParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) + std::vector required_keys = { keys::reactants, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::scaling_factor, keys::products }; + + Errors errors; + + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) { - std::vector required_keys = { keys::reactants, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::scaling_factor, keys::products }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - if (!errors.empty()) - return errors; - - std::vector> species_node_pairs; - - for (const auto& obj : object[keys::reactants]) - { - types::ReactionComponent component; - component.name = GetReactionComponentName(obj); - species_node_pairs.emplace_back(component, obj); - } - - if (species_node_pairs.size() > 1) - { - const auto& node = object[keys::reactants]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - - std::string message = mc_fmt::format( - "{} error: '{}' reaction requires one reactant, but {} were provided.", - error_location, - object[keys::type].as(), - species_node_pairs.size()); - - errors.push_back({ ErrorCode::TooManyReactionComponents, message }); - } - - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void FirstOrderLossParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) { - types::FirstOrderLoss first_order_loss; - - first_order_loss.gas_phase = object[keys::gas_phase].as(); - first_order_loss.reactants = ParseReactionComponent(object, keys::reactants); - if (object[keys::products]) - { - first_order_loss.products = ParseReactionComponents(object, keys::products); - } - first_order_loss.unknown_properties = GetComments(object); - - if (object[keys::scaling_factor]) - { - first_order_loss.scaling_factor = object[keys::scaling_factor].as(); - } - if (object[keys::name]) - { - first_order_loss.name = object[keys::name].as(); - } - - reactions.first_order_loss.emplace_back(std::move(first_order_loss)); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + if (!errors.empty()) + return errors; + + std::vector> species_node_pairs; + + for (const auto& obj : object[keys::reactants]) + { + types::ReactionComponent component; + component.name = GetComponentName(obj); + species_node_pairs.emplace_back(component, obj); + } + + if (species_node_pairs.size() > 1) + { + const auto& node = object[keys::reactants]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + + std::string message = mc_fmt::format( + "{} error: '{}' reaction requires one reactant, but {} were provided.", + error_location, + object[keys::type].as(), + species_node_pairs.size()); + + errors.push_back({ ErrorCode::TooManyReactionComponents, message }); } - } // namespace v1 -} // namespace mechanism_configuration + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } + + void FirstOrderLossParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::FirstOrderLoss first_order_loss; + + first_order_loss.gas_phase = object[keys::gas_phase].as(); + first_order_loss.reactants = ParseReactionComponent(object, keys::reactants); + if (object[keys::products]) + { + first_order_loss.products = ParseReactionComponents(object, keys::products); + } + first_order_loss.unknown_properties = GetComments(object); + + if (object[keys::scaling_factor]) + { + first_order_loss.scaling_factor = object[keys::scaling_factor].as(); + } + if (object[keys::name]) + { + first_order_loss.name = object[keys::name].as(); + } + + reactions.first_order_loss.emplace_back(std::move(first_order_loss)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/lambda_rate_constant.cpp b/src/v1/reactions/lambda_rate_constant.cpp index 549a9eb6..ea5fbcd3 100644 --- a/src/v1/reactions/lambda_rate_constant.cpp +++ b/src/v1/reactions/lambda_rate_constant.cpp @@ -7,77 +7,74 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Lambda Rate Constant reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors LambdaRateConstantParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Lambda Rate Constant reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors LambdaRateConstantParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { - keys::reactants, keys::products, keys::type, keys::gas_phase, keys::lambda_function - }; - std::vector optional_keys = { keys::name }; - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Semantic checks are performed by the version-neutral ValidateSemantics. + std::vector required_keys = { + keys::reactants, keys::products, keys::type, keys::gas_phase, keys::lambda_function + }; + std::vector optional_keys = { keys::name }; + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void LambdaRateConstantParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) { - types::LambdaRateConstant lambda_rate_constant; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - lambda_rate_constant.reactants = ParseReactionComponents(object, keys::reactants); - lambda_rate_constant.products = ParseReactionComponents(object, keys::products); - lambda_rate_constant.gas_phase = object[keys::gas_phase].as(); - lambda_rate_constant.lambda_function = object[keys::lambda_function].as(); - lambda_rate_constant.unknown_properties = GetComments(object); + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Semantic checks are performed by the version-neutral ValidateSemantics. - if (object[keys::name]) - { - lambda_rate_constant.name = object[keys::name].as(); - } + return errors; + } + + void LambdaRateConstantParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::LambdaRateConstant lambda_rate_constant; - reactions.lambda_rate_constant.emplace_back(std::move(lambda_rate_constant)); + lambda_rate_constant.reactants = ParseReactionComponents(object, keys::reactants); + lambda_rate_constant.products = ParseReactionComponents(object, keys::products); + lambda_rate_constant.gas_phase = object[keys::gas_phase].as(); + lambda_rate_constant.lambda_function = object[keys::lambda_function].as(); + lambda_rate_constant.unknown_properties = GetComments(object); + + if (object[keys::name]) + { + lambda_rate_constant.name = object[keys::name].as(); } - } // namespace v1 -} // namespace mechanism_configuration + reactions.lambda_rate_constant.emplace_back(std::move(lambda_rate_constant)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/parsers.cpp b/src/v1/reactions/parsers.cpp new file mode 100644 index 00000000..25a64841 --- /dev/null +++ b/src/v1/reactions/parsers.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/reactions/parsers.hpp" + +#include "detail/v1/keys.hpp" +#include "detail/v1/utils.hpp" + +#include + +namespace mechanism_configuration::v1 +{ + std::vector ParseReactionComponents(const YAML::Node& object, std::string_view key) + { + std::vector component_list; + for (const auto& elem : AsSequence(object[key])) + { + types::ReactionComponent component; + component.name = GetComponentName(elem); + + // A bare-string component carries only its name; coefficients/comments + // are only present on the object form. + if (!elem.IsScalar()) + { + component.unknown_properties = GetComments(elem); + if (elem[keys::coefficient]) + { + component.coefficient = elem[keys::coefficient].as(); + } + } + + component_list.emplace_back(std::move(component)); + } + + return component_list; + }; + + types::ReactionComponent ParseReactionComponent(const YAML::Node& object, std::string_view key) + { + auto reaction_components = ParseReactionComponents(object, key); + + if (reaction_components.empty()) + { + return types::ReactionComponent(); + } + + return std::move(reaction_components.front()); + }; + + types::Reactions ParseReactions(const YAML::Node& objects) + { + auto& parsers = GetReactionParserMap(); + types::Reactions reactions; + + for (const auto& object : objects) + { + auto it = parsers.find(object[keys::type].as()); + if (it != parsers.end()) + { + it->second->Parse(object, reactions); + } + } + + return reactions; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/photolysis.cpp b/src/v1/reactions/photolysis.cpp index 42419285..842ea9b4 100644 --- a/src/v1/reactions/photolysis.cpp +++ b/src/v1/reactions/photolysis.cpp @@ -6,109 +6,106 @@ #include #include -#include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Photolysis reaction entry. + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors PhotolysisParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Photolysis reaction entry. - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors PhotolysisParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::scaling_factor }; + + Errors errors; + + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::scaling_factor }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - if (!errors.empty()) - return errors; - - // Reactants must belong to the reaction's phase; products may reference any phase. - std::vector> reactant_node_pairs; - for (const auto& obj : object[keys::reactants]) - { - types::ReactionComponent component; - component.name = GetReactionComponentName(obj); - reactant_node_pairs.emplace_back(component, obj); - } - std::vector> species_node_pairs = reactant_node_pairs; - - // Checks the number of reactants - // This must be done before collecting errors from the products - if (species_node_pairs.size() > 1) - { - const auto& node = object[keys::reactants]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - - std::string message = mc_fmt::format( - "{} error: '{}' reaction requires one reactant, but {} were provided.", - error_location, - object[keys::type].as(), - species_node_pairs.size()); - - errors.push_back({ ErrorCode::TooManyReactionComponents, message }); - } - - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void PhotolysisParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) { - types::Photolysis photolysis; - - photolysis.gas_phase = object[keys::gas_phase].as(); - photolysis.reactants = ParseReactionComponent(object, keys::reactants); - photolysis.products = ParseReactionComponents(object, keys::products); - photolysis.unknown_properties = GetComments(object); - - if (object[keys::scaling_factor]) - { - photolysis.scaling_factor = object[keys::scaling_factor].as(); - } - if (object[keys::name]) - { - photolysis.name = object[keys::name].as(); - } - - reactions.photolysis.emplace_back(std::move(photolysis)); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); } - } // namespace v1 -} // namespace mechanism_configuration + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + if (!errors.empty()) + return errors; + + // Reactants must belong to the reaction's phase; products may reference any phase. + std::vector> reactant_node_pairs; + for (const auto& obj : object[keys::reactants]) + { + types::ReactionComponent component; + component.name = GetComponentName(obj); + reactant_node_pairs.emplace_back(component, obj); + } + std::vector> species_node_pairs = reactant_node_pairs; + + // Checks the number of reactants + // This must be done before collecting errors from the products + if (species_node_pairs.size() > 1) + { + const auto& node = object[keys::reactants]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + + std::string message = mc_fmt::format( + "{} error: '{}' reaction requires one reactant, but {} were provided.", + error_location, + object[keys::type].as(), + species_node_pairs.size()); + + errors.push_back({ ErrorCode::TooManyReactionComponents, message }); + } + + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } + + void PhotolysisParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Photolysis photolysis; + + photolysis.gas_phase = object[keys::gas_phase].as(); + photolysis.reactants = ParseReactionComponent(object, keys::reactants); + photolysis.products = ParseReactionComponents(object, keys::products); + photolysis.unknown_properties = GetComments(object); + + if (object[keys::scaling_factor]) + { + photolysis.scaling_factor = object[keys::scaling_factor].as(); + } + if (object[keys::name]) + { + photolysis.name = object[keys::name].as(); + } + + reactions.photolysis.emplace_back(std::move(photolysis)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/schema.cpp b/src/v1/reactions/schema.cpp new file mode 100644 index 00000000..00f1ffda --- /dev/null +++ b/src/v1/reactions/schema.cpp @@ -0,0 +1,95 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/reactions/schema.hpp" + +#include "detail/error_format.hpp" +#include "detail/schema.hpp" +#include "detail/v1/keys.hpp" +#include "detail/v1/reactions/parsers.hpp" +#include "detail/v1/utils.hpp" + +#include + +#include +#include + +namespace mechanism_configuration::v1 +{ + Errors CheckReactantsOrProductsSchema(const YAML::Node& list) + { + const std::vector required_keys = {}; + const std::vector optional_keys = { keys::coefficient }; + // A component's species reference may use the canonical `name` or the legacy + // `species name` alias, but exactly one of them. + const std::vector> exactly_one_of = { { keys::name, keys::species_name } }; + + Errors errors; + + for (const auto& object : list) + { + auto schema_errors = CheckSchema(object, required_keys, optional_keys, exactly_one_of); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + } + + return errors; + } + + Errors CheckReactionsSchema( + const YAML::Node& reactions_list, + const std::vector& existing_species, + const std::vector& existing_phases) + { + Errors errors; + + auto& parsers = GetReactionParserMap(); + + std::vector> valid_reactions; + + for (const auto& object : reactions_list) + { + if (!object[keys::type]) + { + ErrorLocation error_location{ object.Mark().line, object.Mark().column }; + std::string message = mc_fmt::format("{} error: Missing 'type' object in reaction.", error_location); + errors.push_back({ ErrorCode::RequiredKeyNotFound, message }); + continue; + } + + std::string type = object[keys::type].as(); + + auto it = parsers.find(type); + if (it == parsers.end()) + { + const auto& node = object[keys::type]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + + std::string message = mc_fmt::format("{} error: Unknown reaction type '{}' found.", error_location, type); + + errors.push_back({ ErrorCode::UnknownType, message }); + + continue; + } + valid_reactions.emplace_back(object, it->second.get()); + } + + if (!errors.empty()) + return errors; + + for (const auto& [reaction_node, parser] : valid_reactions) + { + auto schema_errors = parser->CheckSchema(reaction_node, existing_species, existing_phases); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + } + + return errors; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/surface.cpp b/src/v1/reactions/surface.cpp index eab12bec..9e6c5365 100644 --- a/src/v1/reactions/surface.cpp +++ b/src/v1/reactions/surface.cpp @@ -6,114 +6,111 @@ #include #include -#include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Surface reaction entry. + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors SurfaceParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Surface reaction entry. - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors SurfaceParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) + std::vector required_keys = { + keys::gas_phase_products, keys::gas_phase_species, keys::type, keys::gas_phase + }; + std::vector optional_keys = { keys::name, keys::reaction_probability, keys::condensed_phase }; + + Errors errors; + + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) { - std::vector required_keys = { - keys::gas_phase_products, keys::gas_phase_species, keys::type, keys::gas_phase - }; - std::vector optional_keys = { keys::name, keys::reaction_probability, keys::condensed_phase }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Gas phase species reactant - schema_errors = CheckReactantsOrProductsSchema(object[keys::gas_phase_species]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::gas_phase_products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - if (!errors.empty()) - return errors; - - // The gas-phase species (reactant) must belong to the reaction's phase; gas-phase - // products may reference any phase, so only the reactant is phase-checked below. - std::vector> reactant_node_pairs; - for (const auto& obj : object[keys::gas_phase_species]) - { - types::ReactionComponent component; - component.name = GetReactionComponentName(obj); - reactant_node_pairs.emplace_back(component, obj); - } - - // Checks the number of reactants - // This must be done before collecting errors from the products - if (reactant_node_pairs.size() > 1) - { - const auto& node = object[keys::gas_phase_species]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - - std::string message = mc_fmt::format( - "{} error: '{}' reaction requires one reactant, but {} were provided.", - error_location, - object[keys::type].as(), - reactant_node_pairs.size()); - - errors.push_back({ ErrorCode::TooManyReactionComponents, message }); - } - - // Semantic checks (species existence, phase membership) are performed by the - // version-neutral ValidateSemantics over the canonical Mechanism. + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void SurfaceParser::Parse(const YAML::Node& object, types::Reactions& reactions) + + // Gas phase species reactant + schema_errors = CheckReactantsOrProductsSchema(object[keys::gas_phase_species]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::gas_phase_products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + if (!errors.empty()) + return errors; + + // The gas-phase species (reactant) must belong to the reaction's phase; gas-phase + // products may reference any phase, so only the reactant is phase-checked below. + std::vector> reactant_node_pairs; + for (const auto& obj : object[keys::gas_phase_species]) { - types::Surface surface; - - surface.gas_phase = object[keys::gas_phase].as(); - if (object[keys::condensed_phase]) - { - surface.condensed_phase = object[keys::condensed_phase].as(); - } - surface.gas_phase_species = ParseReactionComponent(object, keys::gas_phase_species); - surface.gas_phase_products = ParseReactionComponents(object, keys::gas_phase_products); - surface.unknown_properties = GetComments(object); - - if (object[keys::reaction_probability]) - { - surface.reaction_probability = object[keys::reaction_probability].as(); - } - if (object[keys::name]) - { - surface.name = object[keys::name].as(); - } - - reactions.surface.emplace_back(std::move(surface)); + types::ReactionComponent component; + component.name = GetComponentName(obj); + reactant_node_pairs.emplace_back(component, obj); } - } // namespace v1 -} // namespace mechanism_configuration + // Checks the number of reactants + // This must be done before collecting errors from the products + if (reactant_node_pairs.size() > 1) + { + const auto& node = object[keys::gas_phase_species]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + + std::string message = mc_fmt::format( + "{} error: '{}' reaction requires one reactant, but {} were provided.", + error_location, + object[keys::type].as(), + reactant_node_pairs.size()); + + errors.push_back({ ErrorCode::TooManyReactionComponents, message }); + } + + // Semantic checks (species existence, phase membership) are performed by the + // version-neutral ValidateSemantics over the canonical Mechanism. + return errors; + } + void SurfaceParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Surface surface; + + surface.gas_phase = object[keys::gas_phase].as(); + if (object[keys::condensed_phase]) + { + surface.condensed_phase = object[keys::condensed_phase].as(); + } + surface.gas_phase_species = ParseReactionComponent(object, keys::gas_phase_species); + surface.gas_phase_products = ParseReactionComponents(object, keys::gas_phase_products); + surface.unknown_properties = GetComments(object); + + if (object[keys::reaction_probability]) + { + surface.reaction_probability = object[keys::reaction_probability].as(); + } + if (object[keys::name]) + { + surface.name = object[keys::name].as(); + } + + reactions.surface.emplace_back(std::move(surface)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/taylor_series.cpp b/src/v1/reactions/taylor_series.cpp index 3d27fc36..be8653e9 100644 --- a/src/v1/reactions/taylor_series.cpp +++ b/src/v1/reactions/taylor_series.cpp @@ -6,123 +6,120 @@ #include #include -#include #include #include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Taylor Series reaction entry + /// Performs schema validation, checks for mutually exclusive parameters (`Ea` vs `C`), + /// and collects any structural errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors TaylorSeriesParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Taylor Series reaction entry - /// Performs schema validation, checks for mutually exclusive parameters (`Ea` vs `C`), - /// and collects any structural errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors TaylorSeriesParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::A, keys::B, keys::C, keys::D, - keys::E, keys::Ea, keys::name, keys::taylor_coefficients }; - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - if (object[keys::Ea] && object[keys::C]) - { - const auto& node = object[keys::Ea]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - - std::string message = mc_fmt::format( - "{} error: Mutually exclusive option of 'Ea' and 'C' found in '{}' reaction.", - error_location, - object[keys::type].as()); - - errors.push_back({ ErrorCode::MutuallyExclusiveOption, message }); - } - - // Semantic checks are performed by the version-neutral ValidateSemantics. + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::A, keys::B, keys::C, keys::D, + keys::E, keys::Ea, keys::name, keys::taylor_coefficients }; + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - /// @brief Parses a YAML-defined Taylor Series reaction and appends it to the reaction list. - /// Extracts reactants, products, kinetic parameters (A–E, Ea), gas phase, - /// optional metadata (name, comments), and constructs a `types::TaylorSeries` object. - /// @param object The YAML node representing the reaction - /// @param reactions The reactions container to append the parsed reaction to - void TaylorSeriesParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) { - types::TaylorSeries taylor_series; - - taylor_series.gas_phase = object[keys::gas_phase].as(); - taylor_series.reactants = ParseReactionComponents(object, keys::reactants); - taylor_series.products = ParseReactionComponents(object, keys::products); - taylor_series.unknown_properties = GetComments(object); - - if (object[keys::A]) - { - taylor_series.A = object[keys::A].as(); - } - if (object[keys::B]) - { - taylor_series.B = object[keys::B].as(); - } - if (object[keys::C]) - { - taylor_series.C = object[keys::C].as(); - } - if (object[keys::D]) - { - taylor_series.D = object[keys::D].as(); - } - if (object[keys::E]) - { - taylor_series.E = object[keys::E].as(); - } - if (object[keys::Ea]) - { - taylor_series.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; - } - if (object[keys::taylor_coefficients]) - { - taylor_series.taylor_coefficients = object[keys::taylor_coefficients].as>(); - } - if (object[keys::name]) - { - taylor_series.name = object[keys::name].as(); - } - - reactions.taylor_series.emplace_back(std::move(taylor_series)); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); } - } // namespace v1 -} // namespace mechanism_configuration + if (object[keys::Ea] && object[keys::C]) + { + const auto& node = object[keys::Ea]; + ErrorLocation error_location{ node.Mark().line, node.Mark().column }; + + std::string message = mc_fmt::format( + "{} error: Mutually exclusive option of 'Ea' and 'C' found in '{}' reaction.", + error_location, + object[keys::type].as()); + + errors.push_back({ ErrorCode::MutuallyExclusiveOption, message }); + } + + // Semantic checks are performed by the version-neutral ValidateSemantics. + + return errors; + } + + /// @brief Parses a YAML-defined Taylor Series reaction and appends it to the reaction list. + /// Extracts reactants, products, kinetic parameters (A–E, Ea), gas phase, + /// optional metadata (name, comments), and constructs a `types::TaylorSeries` object. + /// @param object The YAML node representing the reaction + /// @param reactions The reactions container to append the parsed reaction to + void TaylorSeriesParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::TaylorSeries taylor_series; + + taylor_series.gas_phase = object[keys::gas_phase].as(); + taylor_series.reactants = ParseReactionComponents(object, keys::reactants); + taylor_series.products = ParseReactionComponents(object, keys::products); + taylor_series.unknown_properties = GetComments(object); + + if (object[keys::A]) + { + taylor_series.A = object[keys::A].as(); + } + if (object[keys::B]) + { + taylor_series.B = object[keys::B].as(); + } + if (object[keys::C]) + { + taylor_series.C = object[keys::C].as(); + } + if (object[keys::D]) + { + taylor_series.D = object[keys::D].as(); + } + if (object[keys::E]) + { + taylor_series.E = object[keys::E].as(); + } + if (object[keys::Ea]) + { + taylor_series.C = -1 * object[keys::Ea].as() / constants::BOLTZMANN; + } + if (object[keys::taylor_coefficients]) + { + taylor_series.taylor_coefficients = object[keys::taylor_coefficients].as>(); + } + if (object[keys::name]) + { + taylor_series.name = object[keys::name].as(); + } + + reactions.taylor_series.emplace_back(std::move(taylor_series)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/ternary_chemical_activation.cpp b/src/v1/reactions/ternary_chemical_activation.cpp index e059ac9f..4997d3a0 100644 --- a/src/v1/reactions/ternary_chemical_activation.cpp +++ b/src/v1/reactions/ternary_chemical_activation.cpp @@ -6,107 +6,104 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Ternary ChemicalActivation reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors TernaryChemicalActivationParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Ternary ChemicalActivation reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors TernaryChemicalActivationParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::k0_A, keys::k0_B, keys::k0_C, keys::kinf_A, - keys::kinf_B, keys::kinf_C, keys::Fc, keys::N }; - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Semantic checks are performed by the version-neutral ValidateSemantics. + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::k0_A, keys::k0_B, keys::k0_C, keys::kinf_A, + keys::kinf_B, keys::kinf_C, keys::Fc, keys::N }; + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void TernaryChemicalActivationParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) { - types::TernaryChemicalActivation ternary; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Semantic checks are performed by the version-neutral ValidateSemantics. + + return errors; + } - ternary.gas_phase = object[keys::gas_phase].as(); - ternary.reactants = ParseReactionComponents(object, keys::reactants); - ternary.products = ParseReactionComponents(object, keys::products); - ternary.unknown_properties = GetComments(object); + void TernaryChemicalActivationParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::TernaryChemicalActivation ternary; - if (object[keys::k0_A]) - { - ternary.k0_A = object[keys::k0_A].as(); - } - if (object[keys::k0_B]) - { - ternary.k0_B = object[keys::k0_B].as(); - } - if (object[keys::k0_C]) - { - ternary.k0_C = object[keys::k0_C].as(); - } - if (object[keys::kinf_A]) - { - ternary.kinf_A = object[keys::kinf_A].as(); - } - if (object[keys::kinf_B]) - { - ternary.kinf_B = object[keys::kinf_B].as(); - } - if (object[keys::kinf_C]) - { - ternary.kinf_C = object[keys::kinf_C].as(); - } - if (object[keys::Fc]) - { - ternary.Fc = object[keys::Fc].as(); - } - if (object[keys::N]) - { - ternary.N = object[keys::N].as(); - } - if (object[keys::name]) - { - ternary.name = object[keys::name].as(); - } + ternary.gas_phase = object[keys::gas_phase].as(); + ternary.reactants = ParseReactionComponents(object, keys::reactants); + ternary.products = ParseReactionComponents(object, keys::products); + ternary.unknown_properties = GetComments(object); - reactions.ternary_chemical_activation.emplace_back(std::move(ternary)); + if (object[keys::k0_A]) + { + ternary.k0_A = object[keys::k0_A].as(); + } + if (object[keys::k0_B]) + { + ternary.k0_B = object[keys::k0_B].as(); } + if (object[keys::k0_C]) + { + ternary.k0_C = object[keys::k0_C].as(); + } + if (object[keys::kinf_A]) + { + ternary.kinf_A = object[keys::kinf_A].as(); + } + if (object[keys::kinf_B]) + { + ternary.kinf_B = object[keys::kinf_B].as(); + } + if (object[keys::kinf_C]) + { + ternary.kinf_C = object[keys::kinf_C].as(); + } + if (object[keys::Fc]) + { + ternary.Fc = object[keys::Fc].as(); + } + if (object[keys::N]) + { + ternary.N = object[keys::N].as(); + } + if (object[keys::name]) + { + ternary.name = object[keys::name].as(); + } + + reactions.ternary_chemical_activation.emplace_back(std::move(ternary)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/troe.cpp b/src/v1/reactions/troe.cpp index 95d5a26f..257f927d 100644 --- a/src/v1/reactions/troe.cpp +++ b/src/v1/reactions/troe.cpp @@ -6,107 +6,104 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Troe reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors TroeParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Troe reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors TroeParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::k0_A, keys::k0_B, keys::k0_C, keys::kinf_A, - keys::kinf_B, keys::kinf_C, keys::Fc, keys::N }; - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Semantic checks are performed by the version-neutral ValidateSemantics. + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::k0_A, keys::k0_B, keys::k0_C, keys::kinf_A, + keys::kinf_B, keys::kinf_C, keys::Fc, keys::N }; + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void TroeParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) { - types::Troe troe; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + + // Semantic checks are performed by the version-neutral ValidateSemantics. + + return errors; + } - troe.gas_phase = object[keys::gas_phase].as(); - troe.reactants = ParseReactionComponents(object, keys::reactants); - troe.products = ParseReactionComponents(object, keys::products); - troe.unknown_properties = GetComments(object); + void TroeParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Troe troe; - if (object[keys::k0_A]) - { - troe.k0_A = object[keys::k0_A].as(); - } - if (object[keys::k0_B]) - { - troe.k0_B = object[keys::k0_B].as(); - } - if (object[keys::k0_C]) - { - troe.k0_C = object[keys::k0_C].as(); - } - if (object[keys::kinf_A]) - { - troe.kinf_A = object[keys::kinf_A].as(); - } - if (object[keys::kinf_B]) - { - troe.kinf_B = object[keys::kinf_B].as(); - } - if (object[keys::kinf_C]) - { - troe.kinf_C = object[keys::kinf_C].as(); - } - if (object[keys::Fc]) - { - troe.Fc = object[keys::Fc].as(); - } - if (object[keys::N]) - { - troe.N = object[keys::N].as(); - } - if (object[keys::name]) - { - troe.name = object[keys::name].as(); - } + troe.gas_phase = object[keys::gas_phase].as(); + troe.reactants = ParseReactionComponents(object, keys::reactants); + troe.products = ParseReactionComponents(object, keys::products); + troe.unknown_properties = GetComments(object); - reactions.troe.emplace_back(std::move(troe)); + if (object[keys::k0_A]) + { + troe.k0_A = object[keys::k0_A].as(); + } + if (object[keys::k0_B]) + { + troe.k0_B = object[keys::k0_B].as(); } + if (object[keys::k0_C]) + { + troe.k0_C = object[keys::k0_C].as(); + } + if (object[keys::kinf_A]) + { + troe.kinf_A = object[keys::kinf_A].as(); + } + if (object[keys::kinf_B]) + { + troe.kinf_B = object[keys::kinf_B].as(); + } + if (object[keys::kinf_C]) + { + troe.kinf_C = object[keys::kinf_C].as(); + } + if (object[keys::Fc]) + { + troe.Fc = object[keys::Fc].as(); + } + if (object[keys::N]) + { + troe.N = object[keys::N].as(); + } + if (object[keys::name]) + { + troe.name = object[keys::name].as(); + } + + reactions.troe.emplace_back(std::move(troe)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/tunneling.cpp b/src/v1/reactions/tunneling.cpp index d4385709..3baf4e5c 100644 --- a/src/v1/reactions/tunneling.cpp +++ b/src/v1/reactions/tunneling.cpp @@ -6,87 +6,84 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined Tunneling reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors TunnelingParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined Tunneling reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors TunnelingParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::A, keys::B, keys::C }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::A, keys::B, keys::C }; - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Semantic checks are performed by the version-neutral ValidateSemantics. + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void TunnelingParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) { - types::Tunneling tunneling; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - tunneling.gas_phase = object[keys::gas_phase].as(); - tunneling.reactants = ParseReactionComponents(object, keys::reactants); - tunneling.products = ParseReactionComponents(object, keys::products); - tunneling.unknown_properties = GetComments(object); + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - if (object[keys::A]) - { - tunneling.A = object[keys::A].as(); - } - if (object[keys::B]) - { - tunneling.B = object[keys::B].as(); - } - if (object[keys::C]) - { - tunneling.C = object[keys::C].as(); - } - if (object[keys::name]) - { - tunneling.name = object[keys::name].as(); - } + // Semantic checks are performed by the version-neutral ValidateSemantics. - reactions.tunneling.emplace_back(std::move(tunneling)); + return errors; + } + + void TunnelingParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::Tunneling tunneling; + + tunneling.gas_phase = object[keys::gas_phase].as(); + tunneling.reactants = ParseReactionComponents(object, keys::reactants); + tunneling.products = ParseReactionComponents(object, keys::products); + tunneling.unknown_properties = GetComments(object); + + if (object[keys::A]) + { + tunneling.A = object[keys::A].as(); + } + if (object[keys::B]) + { + tunneling.B = object[keys::B].as(); } + if (object[keys::C]) + { + tunneling.C = object[keys::C].as(); + } + if (object[keys::name]) + { + tunneling.name = object[keys::name].as(); + } + + reactions.tunneling.emplace_back(std::move(tunneling)); + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/v1/reactions/user_defined.cpp b/src/v1/reactions/user_defined.cpp index 790cb9cf..5944e012 100644 --- a/src/v1/reactions/user_defined.cpp +++ b/src/v1/reactions/user_defined.cpp @@ -7,80 +7,77 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + /// @brief Checks the structural schema of a YAML-defined User-defined reaction entry + /// Performs structural (schema) validation only; + /// and collects any errors found. + /// @param object The YAML node representing the reaction + /// @param existing_species Unused; semantic checks live in ValidateSemantics + /// @param existing_phases Unused; semantic checks live in ValidateSemantics + /// @return A list of validation errors, if any + Errors UserDefinedParser::CheckSchema( + const YAML::Node& object, + const std::vector& existing_species, + const std::vector& existing_phases) { - /// @brief Checks the structural schema of a YAML-defined User-defined reaction entry - /// Performs structural (schema) validation only; - /// and collects any errors found. - /// @param object The YAML node representing the reaction - /// @param existing_species Unused; semantic checks live in ValidateSemantics - /// @param existing_phases Unused; semantic checks live in ValidateSemantics - /// @return A list of validation errors, if any - Errors UserDefinedParser::CheckSchema( - const YAML::Node& object, - const std::vector& existing_species, - const std::vector& existing_phases) - { - std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; - std::vector optional_keys = { keys::name, keys::scaling_factor }; - - Errors errors; - - auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - return errors; - } - - // Reactants - schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - - // Products - schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } + std::vector required_keys = { keys::reactants, keys::products, keys::type, keys::gas_phase }; + std::vector optional_keys = { keys::name, keys::scaling_factor }; - // Semantic checks are performed by the version-neutral ValidateSemantics. + Errors errors; + auto schema_errors = mechanism_configuration::CheckSchema(object, required_keys, optional_keys); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); return errors; } - void UserDefinedParser::Parse(const YAML::Node& object, types::Reactions& reactions) + // Reactants + schema_errors = CheckReactantsOrProductsSchema(object[keys::reactants]); + if (!schema_errors.empty()) { - types::UserDefined user_defined; + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - user_defined.reactants = ParseReactionComponents(object, keys::reactants); - user_defined.products = ParseReactionComponents(object, keys::products); - user_defined.gas_phase = object[keys::gas_phase].as(); - user_defined.unknown_properties = GetComments(object); + // Products + schema_errors = CheckReactantsOrProductsSchema(object[keys::products]); + if (!schema_errors.empty()) + { + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } - if (object[keys::scaling_factor]) - { - user_defined.scaling_factor = object[keys::scaling_factor].as(); - } + // Semantic checks are performed by the version-neutral ValidateSemantics. - if (object[keys::name]) - { - user_defined.name = object[keys::name].as(); - } + return errors; + } - reactions.user_defined.emplace_back(std::move(user_defined)); + void UserDefinedParser::Parse(const YAML::Node& object, types::Reactions& reactions) + { + types::UserDefined user_defined; + + user_defined.reactants = ParseReactionComponents(object, keys::reactants); + user_defined.products = ParseReactionComponents(object, keys::products); + user_defined.gas_phase = object[keys::gas_phase].as(); + user_defined.unknown_properties = GetComments(object); + + if (object[keys::scaling_factor]) + { + user_defined.scaling_factor = object[keys::scaling_factor].as(); } - } // namespace v1 -} // namespace mechanism_configuration + if (object[keys::name]) + { + user_defined.name = object[keys::name].as(); + } + + reactions.user_defined.emplace_back(std::move(user_defined)); + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/species/CMakeLists.txt b/src/v1/species/CMakeLists.txt new file mode 100644 index 00000000..029e1c1d --- /dev/null +++ b/src/v1/species/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(mechanism_configuration + PRIVATE + parsers.cpp + schema.cpp +) diff --git a/src/v1/species/parsers.cpp b/src/v1/species/parsers.cpp new file mode 100644 index 00000000..7b0335bd --- /dev/null +++ b/src/v1/species/parsers.cpp @@ -0,0 +1,99 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/species/parsers.hpp" + +#include "detail/v1/keys.hpp" +#include "detail/v1/species/keys.hpp" +#include "detail/v1/utils.hpp" + +#include + +namespace mechanism_configuration::v1 +{ + std::vector ParseSpecies(const YAML::Node& objects) + { + std::vector all_species; + for (const auto& object : objects) + { + types::Species species; + + species.name = object[keys::name].as(); + + if (object[keys::tracer_type]) + species.tracer_type = object[keys::tracer_type].as(); + if (object[keys::absolute_tolerance]) + species.absolute_tolerance = object[keys::absolute_tolerance].as(); + if (object[keys::diffusion_coefficient]) + species.diffusion_coefficient = object[keys::diffusion_coefficient].as(); + if (object[keys::molecular_weight]) + species.molecular_weight = object[keys::molecular_weight].as(); + if (object[keys::henrys_law_constant_298]) + species.henrys_law_constant_298 = object[keys::henrys_law_constant_298].as(); + if (object[keys::henrys_law_constant_exponential_factor]) + species.henrys_law_constant_exponential_factor = object[keys::henrys_law_constant_exponential_factor].as(); + if (object[keys::n_star]) + species.n_star = object[keys::n_star].as(); + if (object[keys::density]) + species.density = object[keys::density].as(); + if (object[keys::constant_concentration]) + species.constant_concentration = object[keys::constant_concentration].as(); + if (object[keys::constant_mixing_ratio]) + species.constant_mixing_ratio = object[keys::constant_mixing_ratio].as(); + if (object[keys::is_third_body]) + species.is_third_body = object[keys::is_third_body].as(); + + species.unknown_properties = GetComments(object); + + all_species.push_back(species); + } + return all_species; + } + + std::vector ParsePhases(const YAML::Node& objects) + { + std::vector all_phases; + for (const auto& object : objects) + { + types::Phase phase; + phase.name = object[keys::name].as(); + + std::vector species; + + for (const auto& spec : object[keys::species]) + { + types::PhaseSpecies phase_species; + + if (spec.IsScalar()) + { + // Shorthand: a bare string is the species name. + phase_species.name = spec.as(); + } + else + { + // Object form: a name plus optional properties. + phase_species.name = spec[keys::name].as(); + if (spec[keys::diffusion_coefficient]) + { + phase_species.diffusion_coefficient = spec[keys::diffusion_coefficient].as(); + } + if (spec[keys::density]) + { + phase_species.density = spec[keys::density].as(); + } + phase_species.unknown_properties = GetComments(spec); + } + + species.emplace_back(phase_species); + } + + phase.species = species; + phase.unknown_properties = GetComments(object); + all_phases.emplace_back(phase); + } + + return all_phases; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/species/schema.cpp b/src/v1/species/schema.cpp new file mode 100644 index 00000000..fa03c0bd --- /dev/null +++ b/src/v1/species/schema.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2023–2026 University Corporation for Atmospheric Research +// University of Illinois at Urbana-Champaign +// SPDX-License-Identifier: Apache-2.0 + +#include "detail/v1/species/schema.hpp" + +#include "detail/error_format.hpp" +#include "detail/schema.hpp" +#include "detail/v1/keys.hpp" +#include "detail/v1/species/keys.hpp" +#include "detail/v1/utils.hpp" + +#include + +#include +#include + +namespace mechanism_configuration::v1 +{ + Errors CheckSpeciesSchema(const YAML::Node& species_list) + { + const std::vector required_keys = { keys::name }; + const std::vector optional_keys = { keys::absolute_tolerance, + keys::diffusion_coefficient, + keys::molecular_weight, + keys::henrys_law_constant_298, + keys::henrys_law_constant_exponential_factor, + keys::n_star, + keys::density, + keys::tracer_type, + keys::constant_concentration, + keys::constant_mixing_ratio, + keys::is_third_body }; + // Structural validation only. Duplicate-species detection (a semantic check) is performed + // by the version-neutral ValidateSemantics. + Errors errors; + for (const auto& object : species_list) + { + auto schema_errors = CheckSchema(object, required_keys, optional_keys); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + } + return errors; + } + + Errors CheckPhasesSchema(const YAML::Node& phases_list, const std::vector& existing_species) + { + // Phase + const std::vector required_keys = { keys::name, keys::species }; + const std::vector optional_keys = {}; + // PhaseSpecies + const std::vector species_required_keys = { keys::name }; + const std::vector species_optional_keys = { keys::diffusion_coefficient, keys::density }; + + // Structural validation only. Duplicate detection, phase-species existence, and + // phase-membership (semantic checks) are performed by the version-neutral + // ValidateSemantics. existing_species is intentionally unused here. + (void)existing_species; + Errors errors; + for (const auto& object : AsSequence(phases_list)) + { + auto schema_errors = CheckSchema(object, required_keys, optional_keys); + errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); + + for (const auto& spec : object[keys::species]) + { + // A bare string is shorthand for a species name and needs no schema validation. + if (spec.IsScalar()) + continue; + auto species_schema_errors = CheckSchema(spec, species_required_keys, species_optional_keys); + errors.insert(errors.end(), species_schema_errors.begin(), species_schema_errors.end()); + } + } + return errors; + } + +} // namespace mechanism_configuration::v1 diff --git a/src/v1/type_parsers.cpp b/src/v1/type_parsers.cpp deleted file mode 100644 index 3442f4c6..00000000 --- a/src/v1/type_parsers.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (C) 2023–2026 University Corporation for Atmospheric Research -// University of Illinois at Urbana-Champaign -// SPDX-License-Identifier: Apache-2.0 - -#include "detail/v1/type_parsers.hpp" - -#include "detail/v1/keys.hpp" -#include "detail/v1/reaction_parsers.hpp" -#include "detail/v1/utils.hpp" - -#include - -namespace mechanism_configuration -{ - namespace v1 - { - std::vector ParseSpecies(const YAML::Node& objects) - { - std::vector all_species; - for (const auto& object : objects) - { - types::Species species; - - species.name = object[keys::name].as(); - - if (object[keys::tracer_type]) - species.tracer_type = object[keys::tracer_type].as(); - if (object[keys::absolute_tolerance]) - species.absolute_tolerance = object[keys::absolute_tolerance].as(); - if (object[keys::diffusion_coefficient]) - species.diffusion_coefficient = object[keys::diffusion_coefficient].as(); - if (object[keys::molecular_weight]) - species.molecular_weight = object[keys::molecular_weight].as(); - if (object[keys::henrys_law_constant_298]) - species.henrys_law_constant_298 = object[keys::henrys_law_constant_298].as(); - if (object[keys::henrys_law_constant_exponential_factor]) - species.henrys_law_constant_exponential_factor = object[keys::henrys_law_constant_exponential_factor].as(); - if (object[keys::n_star]) - species.n_star = object[keys::n_star].as(); - if (object[keys::density]) - species.density = object[keys::density].as(); - if (object[keys::constant_concentration]) - species.constant_concentration = object[keys::constant_concentration].as(); - if (object[keys::constant_mixing_ratio]) - species.constant_mixing_ratio = object[keys::constant_mixing_ratio].as(); - if (object[keys::is_third_body]) - species.is_third_body = object[keys::is_third_body].as(); - - species.unknown_properties = GetComments(object); - - all_species.push_back(species); - } - return all_species; - } - - std::vector ParsePhases(const YAML::Node& objects) - { - std::vector all_phases; - for (const auto& object : objects) - { - types::Phase phase; - phase.name = object[keys::name].as(); - - std::vector species; - - for (const auto& spec : object[keys::species]) - { - types::PhaseSpecies phase_species; - - if (spec.IsScalar()) - { - // Shorthand: a bare string is the species name. - phase_species.name = spec.as(); - } - else - { - // Object form: a name plus optional properties. - phase_species.name = spec[keys::name].as(); - if (spec[keys::diffusion_coefficient]) - { - phase_species.diffusion_coefficient = spec[keys::diffusion_coefficient].as(); - } - if (spec[keys::density]) - { - phase_species.density = spec[keys::density].as(); - } - phase_species.unknown_properties = GetComments(spec); - } - - species.emplace_back(phase_species); - } - - phase.species = species; - phase.unknown_properties = GetComments(object); - all_phases.emplace_back(phase); - } - - return all_phases; - } - - std::vector ParseReactionComponents(const YAML::Node& object, std::string_view key) - { - std::vector component_list; - for (const auto& elem : AsSequence(object[key])) - { - types::ReactionComponent component; - component.name = GetReactionComponentName(elem); - - // A bare-string component carries only its name; coefficients/comments - // are only present on the object form. - if (!elem.IsScalar()) - { - component.unknown_properties = GetComments(elem); - if (elem[keys::coefficient]) - { - component.coefficient = elem[keys::coefficient].as(); - } - } - - component_list.emplace_back(std::move(component)); - } - - return component_list; - }; - - types::ReactionComponent ParseReactionComponent(const YAML::Node& object, std::string_view key) - { - auto reaction_components = ParseReactionComponents(object, key); - - if (reaction_components.empty()) - { - return types::ReactionComponent(); - } - - return std::move(reaction_components.front()); - }; - - types::Reactions ParseReactions(const YAML::Node& objects) - { - auto& parsers = GetReactionParserMap(); - types::Reactions reactions; - - for (const auto& object : objects) - { - auto it = parsers.find(object[keys::type].as()); - if (it != parsers.end()) - { - it->second->Parse(object, reactions); - } - } - - return reactions; - } - - } // namespace v1 -} // namespace mechanism_configuration diff --git a/src/v1/type_schema.cpp b/src/v1/type_schema.cpp deleted file mode 100644 index ffe99a54..00000000 --- a/src/v1/type_schema.cpp +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (C) 2023–2026 University Corporation for Atmospheric Research -// University of Illinois at Urbana-Champaign -// SPDX-License-Identifier: Apache-2.0 - -#include "detail/v1/type_schema.hpp" - -#include "detail/check_schema.hpp" -#include "detail/error_format.hpp" -#include "detail/v1/keys.hpp" -#include "detail/v1/reaction_parsers.hpp" -#include "detail/v1/utils.hpp" - -#include - -#include -#include - -namespace mechanism_configuration -{ - namespace v1 - { - Errors CheckSpeciesSchema(const YAML::Node& species_list) - { - const std::vector required_keys = { keys::name }; - const std::vector optional_keys = { keys::absolute_tolerance, - keys::diffusion_coefficient, - keys::molecular_weight, - keys::henrys_law_constant_298, - keys::henrys_law_constant_exponential_factor, - keys::n_star, - keys::density, - keys::tracer_type, - keys::constant_concentration, - keys::constant_mixing_ratio, - keys::is_third_body }; - // Structural validation only. Duplicate-species detection (a semantic check) is performed - // by the version-neutral ValidateSemantics. - Errors errors; - for (const auto& object : species_list) - { - auto schema_errors = CheckSchema(object, required_keys, optional_keys); - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - return errors; - } - - Errors CheckPhasesSchema(const YAML::Node& phases_list, const std::vector& existing_species) - { - // Phase - const std::vector required_keys = { keys::name, keys::species }; - const std::vector optional_keys = {}; - // PhaseSpecies - const std::vector species_required_keys = { keys::name }; - const std::vector species_optional_keys = { keys::diffusion_coefficient, keys::density }; - - // Structural validation only. Duplicate detection, phase-species existence, and - // phase-membership (semantic checks) are performed by the version-neutral - // ValidateSemantics. existing_species is intentionally unused here. - (void)existing_species; - Errors errors; - for (const auto& object : AsSequence(phases_list)) - { - auto schema_errors = CheckSchema(object, required_keys, optional_keys); - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - - for (const auto& spec : object[keys::species]) - { - // A bare string is shorthand for a species name and needs no schema validation. - if (spec.IsScalar()) - continue; - auto species_schema_errors = CheckSchema(spec, species_required_keys, species_optional_keys); - errors.insert(errors.end(), species_schema_errors.begin(), species_schema_errors.end()); - } - } - return errors; - } - - Errors CheckReactantsOrProductsSchema(const YAML::Node& list) - { - const std::vector required_keys = {}; - const std::vector optional_keys = { keys::coefficient }; - // A component's species reference may use the canonical `name` or the legacy - // `species name` alias, but exactly one of them. - const std::vector> exactly_one_of = { { keys::name, keys::species_name } }; - - Errors errors; - - for (const auto& object : list) - { - auto schema_errors = CheckSchema(object, required_keys, optional_keys, exactly_one_of); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - } - - return errors; - } - - Errors CheckReactionsSchema( - const YAML::Node& reactions_list, - const std::vector& existing_species, - const std::vector& existing_phases) - { - Errors errors; - - auto& parsers = GetReactionParserMap(); - - std::vector> valid_reactions; - - for (const auto& object : reactions_list) - { - if (!object[keys::type]) - { - ErrorLocation error_location{ object.Mark().line, object.Mark().column }; - std::string message = mc_fmt::format("{} error: Missing 'type' object in reaction.", error_location); - errors.push_back({ ErrorCode::RequiredKeyNotFound, message }); - continue; - } - - std::string type = object[keys::type].as(); - - auto it = parsers.find(type); - if (it == parsers.end()) - { - const auto& node = object[keys::type]; - ErrorLocation error_location{ node.Mark().line, node.Mark().column }; - - std::string message = mc_fmt::format("{} error: Unknown reaction type '{}' found.", error_location, type); - - errors.push_back({ ErrorCode::UnknownType, message }); - - continue; - } - valid_reactions.emplace_back(object, it->second.get()); - } - - if (!errors.empty()) - return errors; - - for (const auto& [reaction_node, parser] : valid_reactions) - { - auto schema_errors = parser->CheckSchema(reaction_node, existing_species, existing_phases); - if (!schema_errors.empty()) - { - errors.insert(errors.end(), schema_errors.begin(), schema_errors.end()); - } - } - - return errors; - } - - } // namespace v1 -} // namespace mechanism_configuration \ No newline at end of file diff --git a/src/v1/utils.cpp b/src/v1/utils.cpp index fd1972d7..74f2bf9d 100644 --- a/src/v1/utils.cpp +++ b/src/v1/utils.cpp @@ -4,132 +4,80 @@ #include "detail/v1/utils.hpp" -#include "detail/check_schema.hpp" +#include "detail/schema.hpp" #include "detail/v1/keys.hpp" #include #include #include -#include #include -namespace mechanism_configuration +namespace mechanism_configuration::v1 { - namespace v1 + YAML::Node AsSequence(const YAML::Node& node) { - YAML::Node AsSequence(const YAML::Node& node) - { - if (node.IsSequence()) - return node; + if (node.IsSequence()) + return node; - YAML::Node sequence; - sequence.push_back(node); + YAML::Node sequence; + sequence.push_back(node); - return sequence; - } + return sequence; + } - ErrorLocation LocationOf(const YAML::Node& node) - { - return ErrorLocation{ node.Mark().line, node.Mark().column }; - } + ErrorLocation LocationOf(const YAML::Node& node) + { + return ErrorLocation{ node.Mark().line, node.Mark().column }; + } - std::string GetReactionComponentName(const YAML::Node& component) + std::string GetComponentName(const YAML::Node& component) + { + // A component may be given as a bare string (shorthand for its name), + // or as an object keyed by the canonical `name` or the legacy `species name`. + if (component.IsScalar()) + return component.as(); + if (component[keys::name]) + return component[keys::name].as(); + return component[keys::species_name].as(); + } + + void AppendFilePath(const std::string& config_path, Errors& errors) + { + for (auto& error : errors) { - // A component may be given as a bare string (shorthand for its name), - // or as an object keyed by the canonical `name` or the legacy `species name`. - if (component.IsScalar()) - return component.as(); - if (component[keys::name]) - return component[keys::name].as(); - return component[keys::species_name].as(); + error.second = config_path + ":" + error.second; } + } - void AppendFilePath(const std::string& config_path, Errors& errors) - { - for (auto& error : errors) - { - error.second = config_path + ":" + error.second; - } - } + std::unordered_map GetComments(const YAML::Node& object) + { + std::unordered_map unknown_properties; + const std::string comment_start = "__"; - std::unordered_map GetComments(const YAML::Node& object) + for (const auto& key : object) { - std::unordered_map unknown_properties; - const std::string comment_start = "__"; - - for (const auto& key : object) - { - std::string key_str = key.first.as(); - - // Check if the key starts with the comment prefix - if (key_str.compare(0, comment_start.size(), comment_start) == 0) - { - // Check if the value is a YAML node - if (key.second.IsScalar()) - { - unknown_properties[key_str] = key.second.as(); - } - else - { - std::stringstream ss; - ss << key.second; - unknown_properties[key_str] = ss.str(); - } - } - } - - // Return the map of extracted comments - return unknown_properties; - } + std::string key_str = key.first.as(); - std::optional FindPhaseSpeciesDiffusionCoefficient( - const std::vector& phases, - const std::string& phase_name, - const std::string& species_name) - { - for (const auto& phase : phases) + // Check if the key starts with the comment prefix + if (key_str.compare(0, comment_start.size(), comment_start) == 0) { - if (phase.name != phase_name) - continue; - for (const auto& species : phase.species) + // Check if the value is a YAML node + if (key.second.IsScalar()) { - if (species.name == species_name) - return species.diffusion_coefficient; + unknown_properties[key_str] = key.second.as(); } - } - return std::nullopt; - } - - std::optional FindPhaseSpeciesDensity( - const std::vector& phases, - const std::string& phase_name, - const std::string& species_name) - { - for (const auto& phase : phases) - { - if (phase.name != phase_name) - continue; - for (const auto& species : phase.species) + else { - if (species.name == species_name) - return species.density; + std::stringstream ss; + ss << key.second; + unknown_properties[key_str] = ss.str(); } } - return std::nullopt; } - std::optional FindSpeciesMolecularWeight( - const std::vector& species, - const std::string& species_name) - { - for (const auto& s : species) - { - if (s.name == species_name) - return s.molecular_weight; - } - return std::nullopt; - } + // Return the map of extracted comments + return unknown_properties; + } - } // namespace v1 -} // namespace mechanism_configuration +} // namespace mechanism_configuration::v1 diff --git a/src/validate.cpp b/src/validate.cpp index 8318617c..2d9dd2ba 100644 --- a/src/validate.cpp +++ b/src/validate.cpp @@ -274,36 +274,6 @@ namespace mechanism_configuration add("BRANCHED", x.gas_phase, Refs(x.reactants), Refs(products)); } - if (mechanism.emissions) - { - semantics::EmissionsRef emissions_ref; - - for (const auto& inv : mechanism.emissions->inventories) - emissions_ref.inventories.push_back({ inv.name, std::nullopt }); - - for (const auto& smap : mechanism.emissions->species_maps) - { - semantics::SpeciesMapRef smap_ref; - smap_ref.name = smap.name; - for (const auto& mapping : smap.mappings) - smap_ref.mappings.push_back({ mapping.inventory_species, mapping.mechanism_species, mapping.scaling_factor }); - emissions_ref.species_maps.push_back(std::move(smap_ref)); - } - - for (const auto& source : mechanism.emissions->sources) - { - semantics::SourceRef source_ref; - source_ref.name = source.name; - source_ref.inventory = { source.inventory, std::nullopt }; - source_ref.species_map = { source.species_map, std::nullopt }; - source_ref.category = source.category; - source_ref.hierarchy = source.hierarchy; - emissions_ref.sources.push_back(std::move(source_ref)); - } - - input.emissions = std::move(emissions_ref); - } - return ValidateSemantics(input); } @@ -492,11 +462,52 @@ namespace mechanism_configuration return errors; } + Errors ValidateEmissionsModel(const Mechanism& mechanism) + { + if (!mechanism.emissions) + return {}; + + semantics::Input input; + semantics::EmissionsRef emissions_ref; + + for (const auto& inv : mechanism.emissions->inventories) + emissions_ref.inventories.push_back({ inv.name, std::nullopt }); + + for (const auto& smap : mechanism.emissions->species_maps) + { + semantics::SpeciesMapRef smap_ref; + smap_ref.name = smap.name; + for (const auto& mapping : smap.mappings) + smap_ref.mappings.push_back({ mapping.inventory_species, mapping.mechanism_species, mapping.scaling_factor }); + emissions_ref.species_maps.push_back(std::move(smap_ref)); + } + + for (const auto& source : mechanism.emissions->sources) + { + semantics::SourceRef source_ref; + source_ref.name = source.name; + source_ref.inventory = { source.inventory, std::nullopt }; + source_ref.species_map = { source.species_map, std::nullopt }; + source_ref.category = source.category; + source_ref.hierarchy = source.hierarchy; + emissions_ref.sources.push_back(std::move(source_ref)); + } + + input.emissions = std::move(emissions_ref); + + return ValidateSemantics(input); + } + Errors Validate(const Mechanism& mechanism) { Errors errors = ValidateGasModel(mechanism); + Errors aerosol_errors = ValidateAerosolModel(mechanism); errors.insert(errors.end(), aerosol_errors.begin(), aerosol_errors.end()); + + Errors emissions_errors = ValidateEmissionsModel(mechanism); + errors.insert(errors.end(), emissions_errors.begin(), emissions_errors.end()); + return errors; } From c6b4fac775c2ad85ab7066e57022102ce006ec11 Mon Sep 17 00:00:00 2001 From: Jiwon Gim Date: Wed, 8 Jul 2026 09:09:51 -0600 Subject: [PATCH 7/7] comment clean up --- src/detail/v1/emissions/keys.hpp | 1 - src/detail/v1/reactions/keys.hpp | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/detail/v1/emissions/keys.hpp b/src/detail/v1/emissions/keys.hpp index dc38d8e8..7bd9222a 100644 --- a/src/detail/v1/emissions/keys.hpp +++ b/src/detail/v1/emissions/keys.hpp @@ -8,7 +8,6 @@ namespace mechanism_configuration::v1::keys { - // ── Emissions section ────────────────────────────────────────────────────── inline constexpr std::string_view emissions = "emissions"; // Emissions top-level sections diff --git a/src/detail/v1/reactions/keys.hpp b/src/detail/v1/reactions/keys.hpp index 03e5ab28..7911697a 100644 --- a/src/detail/v1/reactions/keys.hpp +++ b/src/detail/v1/reactions/keys.hpp @@ -8,10 +8,6 @@ namespace mechanism_configuration::v1::keys { - // ---------------------------------------- - // Reaction types - // ---------------------------------------- - // Arrhenius inline constexpr std::string_view Arrhenius_key = "ARRHENIUS"; // also A, B, C, D, E, Ea (shared, see detail/v1/keys.hpp)