From ac7ad43cf8d893cafe6aaa0a32a3b8dfb3347235 Mon Sep 17 00:00:00 2001 From: Kevin Nobel Date: Tue, 17 Mar 2026 16:26:31 +0000 Subject: [PATCH 1/4] Implement encoding for currently known CAMS fields --- .../composition/compositionEncoding.h | 27 +++++++++++++++---- .../concepts/composition/compositionMatcher.h | 27 +++++++++++++++---- .../backend/concepts/level/levelMatcher.h | 3 ++- .../point-in-time/pointInTimeMatcher.h | 2 +- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h index 6a997cc1..941b49d6 100644 --- a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h +++ b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h @@ -134,6 +134,7 @@ template void CompositionOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { + using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; @@ -151,16 +152,32 @@ void CompositionOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t // ============================================================= // Variant-specific logic // ============================================================= - if constexpr (Variant == CompositionType::Chem) { + // if constexpr (Variant == CompositionType::Chem) { - // Structural validation - /// @todo [owner: dgov][scope: concept][reason: completeness][prio: low] + // // Structural validation + // /// @todo [owner: dgov][scope: concept][reason: completeness][prio: low] + + // // Deductions + // long constituentType = deductions::resolve_ConstituentType_or_throw(mars, par, opt); + + // // Encoding + // set_or_throw(out, "constituentType", constituentType); + // } + + if constexpr (Variant == CompositionType::AerosolOptical) { // Deductions - long constituentType = deductions::resolve_ConstituentType_or_throw(mars, par, opt); + const auto chem = get_or_throw(mars, "chem"); + const auto wavelengthInNanometers = get_or_throw(mars, "wavelength"); + const double wavelengthInMeters = static_cast(wavelengthInNanometers) / 1000000000.0; // Encoding - set_or_throw(out, "constituentType", constituentType); + set_or_throw(out, "enableChemSplit", true); + set_or_throw(out, "chemId", chem); + set_or_throw(out, "firstWavelength", wavelengthInMeters); + } + else { + MARS2GRIB_CONCEPT_THROW(composition, "Concept variant is not implemented!"); } } catch (...) { diff --git a/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h b/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h index 2a657c97..c481465a 100644 --- a/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h +++ b/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h @@ -7,22 +7,39 @@ #include "metkit/mars2grib/backend/concepts/composition/compositionEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" +#include "metkit/mars2grib/utils/mars2gribExceptions.h" +#include "metkit/mars2grib/utils/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t compositionMatcher(const MarsDict_t& mars, const OptDict_t& opt) { + using metkit::mars2grib::util::param_matcher::matchAny; + using metkit::mars2grib::util::param_matcher::range; + using metkit::mars2grib::utils::dict_traits::get_opt; + using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; + using metkit::mars2grib::utils::exceptions::Mars2GribMatcherException; - if (has(mars, "chem")) { - return static_cast(CompositionType::Chem); + const auto param = get_or_throw(mars, "param"); + + // TODO: This is the range for CAMS, there are some unmapped parameters that may need to be supported for ERA6, etc. + if (param < 400000 || param >= 500000) { + return compile_time_registry_engine::MISSING; } - if (has(mars, "wavelength")) { - return static_cast(CompositionType::Aerosol); + const auto chem = get_or_throw(mars, "chem"); + const auto hasWavelength = has(mars, "wavelength"); + + if (has(mars, "wavelength") && + ((chem == 922 && matchAny(param, 457000, 458000, 459000, 472000)) || (chem == 923 && param == 457000))) { + return static_cast(CompositionType::AerosolOptical); } - return compile_time_registry_engine::MISSING; + throw Mars2GribMatcherException( + "compositionMatcher: matching logic is not implemented for param=" + std::to_string(param) + + ", chem=" + std::to_string(chem) + ", hasWavelength=" + (hasWavelength ? "true" : "false"), + Here()); } } // namespace metkit::mars2grib::backend::concepts_ diff --git a/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h b/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h index a7941125..21186354 100644 --- a/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h +++ b/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h @@ -132,7 +132,8 @@ inline std::size_t matchSFC(const long param) { } // Chemical - if (matchAny(param, range(228080, 228085), range(233032, 233035), range(235062, 235064))) { + if (matchAny(param, range(228080, 228085), range(233032, 233035), range(235062, 235064), 457000, 458000, 459000, + 472000)) { return static_cast(LevelType::Surface); } diff --git a/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h b/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h index daa51a72..43c4d6ab 100644 --- a/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h +++ b/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h @@ -49,7 +49,7 @@ std::size_t pointInTimeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { } // Chemical products - if (matchAny(param, range(228083, 228085))) { + if (matchAny(param, range(228083, 228085), 457000, 458000, 459000, 472000)) { return static_cast(PointInTimeType::Default); } From aaf599966822e1e5353483b919e516bfe39d49dd Mon Sep 17 00:00:00 2001 From: Kevin Nobel Date: Mon, 30 Mar 2026 08:36:22 +0000 Subject: [PATCH 2/4] Add chemId deduction --- .../composition/compositionEncoding.h | 5 +- .../mars2grib/backend/deductions/chemId.h | 139 ++++++++++++++++++ .../docs/doxygen/mars2grib.config.in | 1 + 3 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 src/metkit/mars2grib/backend/deductions/chemId.h diff --git a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h index 941b49d6..43c4b873 100644 --- a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h +++ b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h @@ -48,6 +48,7 @@ #include "metkit/mars2grib/utils/generalUtils.h" // Deductions +#include "metkit/mars2grib/backend/deductions/chemId.h" #include "metkit/mars2grib/backend/deductions/constituentType.h" // Utils @@ -167,13 +168,13 @@ void CompositionOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t if constexpr (Variant == CompositionType::AerosolOptical) { // Deductions - const auto chem = get_or_throw(mars, "chem"); + const auto chemId = deductions::resolve_ChemId_or_throw(mars, par, opt); const auto wavelengthInNanometers = get_or_throw(mars, "wavelength"); const double wavelengthInMeters = static_cast(wavelengthInNanometers) / 1000000000.0; // Encoding set_or_throw(out, "enableChemSplit", true); - set_or_throw(out, "chemId", chem); + set_or_throw(out, "chemId", chemId); set_or_throw(out, "firstWavelength", wavelengthInMeters); } else { diff --git a/src/metkit/mars2grib/backend/deductions/chemId.h b/src/metkit/mars2grib/backend/deductions/chemId.h new file mode 100644 index 00000000..d08e2fcb --- /dev/null +++ b/src/metkit/mars2grib/backend/deductions/chemId.h @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2026- ECMWF and individual contributors. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +/// +/// @file chemId.h +/// @brief Deduction of the chemical identifier for composition encoding. +/// +/// This header defines deduction utilities used by the mars2grib backend +/// to resolve the **chemical identifier** from MARS metadata. +/// +/// The deduction retrieves the identifier directly from the MARS +/// dictionary and exposes it to the encoding layer without +/// transformation or interpretation. +/// +/// Deductions are responsible for: +/// - extracting values from MARS, parameter, and option dictionaries +/// - applying minimal, explicit deduction logic +/// - returning strongly typed values to concept operations +/// +/// Deductions: +/// - do NOT encode GRIB keys directly +/// - do NOT apply inference, defaulting, or consistency checks +/// - do NOT perform GRIB table validation +/// +/// Error handling follows a strict fail-fast strategy: +/// - missing or malformed inputs cause immediate failure +/// - errors are reported using domain-specific deduction exceptions +/// - original errors are preserved via nested exception propagation +/// +/// Logging follows the mars2grib deduction policy: +/// - RESOLVE: value derived via deduction logic from input dictionaries +/// - OVERRIDE: value provided by parameter dictionary overriding deduction logic +/// +/// @section References +/// Concept: +/// - @ref compositionEncoding.h +/// +/// @ingroup mars2grib_backend_deductions +/// +#pragma once + +// System includes +#include + +// Core deduction includes +#include "metkit/config/LibMetkit.h" +#include "metkit/mars2grib/utils/generalUtils.h" +#include "metkit/mars2grib/utils/logUtils.h" +#include "metkit/mars2grib/utils/mars2gribExceptions.h" + +namespace metkit::mars2grib::backend::deductions { + +/// +/// @brief Resolve the chemical identifier from input dictionaries. +/// +/// @section Deduction contract +/// - Reads: `mars["chem"]` +/// - Writes: none +/// - Side effects: logging (RESOLVE) +/// - Failure mode: throws +/// +/// This deduction resolves the chemical identifier by retrieving +/// the mandatory MARS key `chem` and returning its value as a `long`. +/// +/// No semantic interpretation, normalization, or validation is performed +/// beyond basic type conversion. The meaning of the chemical identifier is +/// defined by upstream metadata conventions. +/// +/// @tparam MarsDict_t +/// Type of the MARS dictionary. Must support keyed access to `chem` +/// and conversion to `long`. +/// +/// @tparam ParDict_t +/// Type of the parameter dictionary (unused by this deduction). +/// +/// @tparam OptDict_t +/// Type of the options dictionary (unused by this deduction). +/// +/// @param[in] mars +/// MARS dictionary from which the chemical identifier is resolved. +/// +/// @param[in] par +/// Parameter dictionary (unused). +/// +/// @param[in] opt +/// Options dictionary (unused). +/// +/// @return +/// The resolved chemical identifier. +/// +/// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException +/// If the key `chem` is missing, cannot be converted to `long`, +/// or if any unexpected error occurs during deduction. +/// +/// @note +/// This deduction performs presence-only validation and does not +/// consult chemical metadata or GRIB tables. +/// +template +long resolve_ChemId_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { + + using metkit::mars2grib::utils::dict_traits::get_or_throw; + using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; + + try { + + // Retrieve mandatory MARS chemical identifier + long chemId = get_or_throw(mars, "chem"); + + // Emit RESOLVE log entry + MARS2GRIB_LOG_RESOLVE([&]() { + std::string logMsg = "`chemId` resolved from input dictionaries: value='"; + logMsg += std::to_string(chemId); + logMsg += "'"; + return logMsg; + }()); + + // Success exit point + return chemId; + } + catch (...) { + + // Rethrow nested exceptions + std::throw_with_nested( + Mars2GribDeductionException("Failed to resolve `chemId` from input dictionaries", Here())); + }; + + // Remove compiler warning + mars2gribUnreachable(); +}; + +} // namespace metkit::mars2grib::backend::deductions diff --git a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in index 001b3d7b..5cdc9c73 100644 --- a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in +++ b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in @@ -1144,6 +1144,7 @@ INPUT = \@MARS2GRIB_SOURCE_DIRECTORY@/utils/configConverter.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/timeIncrementInSeconds.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/laplacianOperator.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/channel.h \ +@MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/chemId.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/level.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/instrumentType.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/derivedForecast.h \ From d44a061f604a5ef87ec9ceba8a8b587c27cd1860 Mon Sep 17 00:00:00 2001 From: Kevin Nobel Date: Mon, 30 Mar 2026 08:49:46 +0000 Subject: [PATCH 3/4] Add firstWavelength deduction --- .../composition/compositionEncoding.h | 8 +- .../backend/deductions/firstWavelength.h | 145 ++++++++++++++++++ .../docs/doxygen/mars2grib.config.in | 1 + 3 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 src/metkit/mars2grib/backend/deductions/firstWavelength.h diff --git a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h index 43c4b873..0435e1dd 100644 --- a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h +++ b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h @@ -50,6 +50,7 @@ // Deductions #include "metkit/mars2grib/backend/deductions/chemId.h" #include "metkit/mars2grib/backend/deductions/constituentType.h" +#include "metkit/mars2grib/backend/deductions/firstWavelength.h" // Utils #include "metkit/config/LibMetkit.h" @@ -168,14 +169,13 @@ void CompositionOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t if constexpr (Variant == CompositionType::AerosolOptical) { // Deductions - const auto chemId = deductions::resolve_ChemId_or_throw(mars, par, opt); - const auto wavelengthInNanometers = get_or_throw(mars, "wavelength"); - const double wavelengthInMeters = static_cast(wavelengthInNanometers) / 1000000000.0; + const auto chemId = deductions::resolve_ChemId_or_throw(mars, par, opt); + const auto firstWavelength = deductions::resolve_FirstWavelength_or_throw(mars, par, opt); // Encoding set_or_throw(out, "enableChemSplit", true); set_or_throw(out, "chemId", chemId); - set_or_throw(out, "firstWavelength", wavelengthInMeters); + set_or_throw(out, "firstWavelength", firstWavelength); } else { MARS2GRIB_CONCEPT_THROW(composition, "Concept variant is not implemented!"); diff --git a/src/metkit/mars2grib/backend/deductions/firstWavelength.h b/src/metkit/mars2grib/backend/deductions/firstWavelength.h new file mode 100644 index 00000000..dc8cb86e --- /dev/null +++ b/src/metkit/mars2grib/backend/deductions/firstWavelength.h @@ -0,0 +1,145 @@ +/* + * (C) Copyright 2026- ECMWF and individual contributors. + * + * This software is licensed under the terms of the Apache Licence Version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation nor + * does it submit to any jurisdiction. + */ + +/// +/// @file firstWavelength.h +/// @brief Deduction of the first wavelength in meters for composition encoding. +/// +/// This header defines deduction utilities used by the mars2grib backend +/// to resolve the **first wavelength** from MARS metadata. +/// +/// The deduction retrieves the wavelength from the MARS dictionary +/// (expressed in nanometers) and converts it to meters before exposing +/// the value to the encoding layer. +/// +/// Deductions are responsible for: +/// - extracting values from MARS, parameter, and option dictionaries +/// - applying explicit and minimal transformation logic +/// - returning strongly typed values to concept operations +/// +/// Deductions: +/// - do NOT encode GRIB keys directly +/// - do NOT apply inference, defaulting, or consistency checks +/// - do NOT perform GRIB table validation +/// +/// Error handling follows a strict fail-fast strategy: +/// - missing or malformed inputs cause immediate failure +/// - errors are reported using domain-specific deduction exceptions +/// - original errors are preserved via nested exception propagation +/// +/// Logging follows the mars2grib deduction policy: +/// - RESOLVE: value derived via deduction logic from input dictionaries +/// - OVERRIDE: value provided by parameter dictionary overriding deduction logic +/// +/// @section References +/// Concept: +/// - @ref compositionEncoding.h +/// +/// Related deductions: +/// - @ref chemId.h +/// +/// @ingroup mars2grib_backend_deductions +/// +#pragma once + +// System includes +#include + +// Core deduction includes +#include "metkit/config/LibMetkit.h" +#include "metkit/mars2grib/utils/generalUtils.h" +#include "metkit/mars2grib/utils/logUtils.h" +#include "metkit/mars2grib/utils/mars2gribExceptions.h" + +namespace metkit::mars2grib::backend::deductions { + +/// +/// @brief Resolve the first wavelength in meters from input dictionaries. +/// +/// @section Deduction contract +/// - Reads: `mars["wavelength"]` +/// - Writes: none +/// - Side effects: logging (RESOLVE) +/// - Failure mode: throws +/// +/// This deduction resolves the first wavelength by retrieving the +/// mandatory MARS key `wavelength` (expressed in nanometers) and +/// converting it to meters. +/// +/// The conversion follows: +/// \f$ \text{wavelength\_m} = \text{wavelength\_nm} \times 10^{-9} \f$ +/// +/// @tparam MarsDict_t +/// Type of the MARS dictionary. Must support keyed access to `wavelength` +/// and conversion to `long`. +/// +/// @tparam ParDict_t +/// Type of the parameter dictionary (unused by this deduction). +/// +/// @tparam OptDict_t +/// Type of the options dictionary (unused by this deduction). +/// +/// @param[in] mars +/// MARS dictionary from which the wavelength is resolved. +/// +/// @param[in] par +/// Parameter dictionary (unused). +/// +/// @param[in] opt +/// Options dictionary (unused). +/// +/// @return +/// The resolved wavelength in meters as a `double`. +/// +/// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException +/// If the key `wavelength` is missing, cannot be converted to `long`, +/// or if any unexpected error occurs during deduction. +/// +/// @note +/// This deduction assumes that the MARS `wavelength` value is expressed +/// in nanometers. Alternative units are not supported. +/// +template +double resolve_FirstWavelength_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { + + using metkit::mars2grib::utils::dict_traits::get_or_throw; + using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; + + try { + + // Retrieve mandatory MARS wavelength (in nanometers) + long wavelengthInNanometers = get_or_throw(mars, "wavelength"); + + // Convert nanometers to meters + double wavelengthInMeters = static_cast(wavelengthInNanometers) / 1000000000.0; + + // Emit RESOLVE log entry + MARS2GRIB_LOG_RESOLVE([&]() { + std::string logMsg = "`firstWavelength` deduced from mars dictionary: "; + logMsg += std::to_string(wavelengthInMeters) + " [meters]"; + logMsg += " (from " + std::to_string(wavelengthInNanometers) + " [nanometers])"; + return logMsg; + }()); + + // Success exit point + return wavelengthInMeters; + } + catch (...) { + + // Rethrow nested exceptions + std::throw_with_nested( + Mars2GribDeductionException("Failed to resolve `firstWavelength` from input dictionaries", Here())); + }; + + // Remove compiler warning + mars2gribUnreachable(); +}; + +} // namespace metkit::mars2grib::backend::deductions diff --git a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in index 5cdc9c73..d2b346b4 100644 --- a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in +++ b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in @@ -1145,6 +1145,7 @@ INPUT = \@MARS2GRIB_SOURCE_DIRECTORY@/utils/configConverter.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/laplacianOperator.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/channel.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/chemId.h \ +@MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/firstWavelength.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/level.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/instrumentType.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/derivedForecast.h \ From e5774b63764c8bef11de325334202bc6e8cb974d Mon Sep 17 00:00:00 2001 From: Kevin Nobel Date: Mon, 30 Mar 2026 09:06:20 +0000 Subject: [PATCH 4/4] Remove constituentType deduction --- .../composition/compositionEncoding.h | 1 - .../backend/deductions/constituentType.h | 155 ------------------ .../docs/doxygen/mars2grib.config.in | 1 - 3 files changed, 157 deletions(-) delete mode 100644 src/metkit/mars2grib/backend/deductions/constituentType.h diff --git a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h index 0435e1dd..101eb402 100644 --- a/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h +++ b/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h @@ -49,7 +49,6 @@ // Deductions #include "metkit/mars2grib/backend/deductions/chemId.h" -#include "metkit/mars2grib/backend/deductions/constituentType.h" #include "metkit/mars2grib/backend/deductions/firstWavelength.h" // Utils diff --git a/src/metkit/mars2grib/backend/deductions/constituentType.h b/src/metkit/mars2grib/backend/deductions/constituentType.h deleted file mode 100644 index 5d49b5f5..00000000 --- a/src/metkit/mars2grib/backend/deductions/constituentType.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * (C) Copyright 2025- ECMWF and individual contributors. - * - * This software is licensed under the terms of the Apache Licence Version 2.0 - * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation nor - * does it submit to any jurisdiction. - */ - -/// -/// @file constituentType.h -/// @brief Deduction of the constituent (chemical species) type identifier. -/// -/// This header defines deduction utilities used by the mars2grib backend -/// to resolve the **constituent / chemical species identifier** from -/// MARS metadata. -/// -/// The deduction retrieves the identifier directly from the MARS -/// dictionary and performs basic numeric validation before exposing -/// the value to the encoding layer. -/// -/// Deductions are responsible for: -/// - extracting values from MARS, parameter, and option dictionaries -/// - applying explicit and minimal validation logic -/// - returning strongly typed values to concept operations -/// -/// Deductions: -/// - do NOT encode GRIB keys directly -/// - do NOT apply semantic inference or defaulting -/// - do NOT perform GRIB table validation -/// -/// Error handling follows a strict fail-fast strategy: -/// - missing or invalid inputs cause immediate failure -/// - errors are reported using domain-specific deduction exceptions -/// - original errors are preserved via nested exception propagation -/// -/// Logging follows the mars2grib deduction policy: -/// - RESOLVE: value derived via deduction logic from input dictionaries -/// - OVERRIDE: value provided by parameter dictionary overriding deduction logic -/// -/// @section References -/// Concept: -/// - @ref compositionEncoding.h -/// -/// Related deductions: -/// - @ref paramId.h -/// -/// @ingroup mars2grib_backend_deductions -/// -#pragma once - -// System includes -#include - -// Core deduction includes -#include "metkit/config/LibMetkit.h" -#include "metkit/mars2grib/utils/generalUtils.h" -#include "metkit/mars2grib/utils/logUtils.h" -#include "metkit/mars2grib/utils/mars2gribExceptions.h" - -namespace metkit::mars2grib::backend::deductions { - -/// -/// @brief Resolve the constituent (chemical species) type identifier from input dictionaries. -/// -/// @section Deduction contract -/// - Reads: `mars["chem"]` -/// - Writes: none -/// - Side effects: logging (RESOLVE) -/// - Failure mode: throws -/// -/// This deduction resolves the constituent (chemical species) type -/// identifier by retrieving the mandatory MARS key `chem` and returning -/// its value as a `long`. -/// -/// A basic numeric validity check is applied. Only values in the -/// inclusive range `[0, 900]` are accepted. Values outside this range -/// result in a deduction failure. -/// -/// No semantic interpretation, normalization, or defaulting is applied. -/// The meaning of the identifier is defined by upstream MARS/GRIB -/// conventions. -/// -/// @tparam MarsDict_t -/// Type of the MARS dictionary. Must support keyed access to `chem` -/// and conversion to `long`. -/// -/// @tparam ParDict_t -/// Type of the parameter dictionary (unused by this deduction). -/// -/// @tparam OptDict_t -/// Type of the options dictionary (unused by this deduction). -/// -/// @param[in] mars -/// MARS dictionary from which the constituent type identifier is resolved. -/// -/// @param[in] par -/// Parameter dictionary (unused). -/// -/// @param[in] opt -/// Options dictionary (unused). -/// -/// @return -/// The resolved constituent (chemical species) type identifier. -/// -/// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException -/// If the key `chem` is missing, cannot be converted to `long`, if the -/// value is outside the accepted range, or if any unexpected error -/// occurs during deduction. -/// -/// @note -/// This deduction enforces conservative numeric validation and does -/// not consult chemical metadata tables or GRIB code tables. -/// -template -long resolve_ConstituentType_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { - - using metkit::mars2grib::utils::dict_traits::get_or_throw; - using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; - - try { - - // Retrieve mandatory MARS constituent type - long constituentType = get_or_throw(mars, "chem"); - - // Validate - if (constituentType < 0 || constituentType > 900) { - throw Mars2GribDeductionException( - "Invalid `constituentType`: value='" + std::to_string(constituentType) + "'", Here()); - } - - // Emit RESOLVE log entry - MARS2GRIB_LOG_RESOLVE([&]() { - std::string logMsg = "`constituentType` resolved from input dictionaries: value='"; - logMsg += std::to_string(constituentType); - logMsg += "'"; - return logMsg; - }()); - - // Success exit point - return constituentType; - } - catch (...) { - - // Rethrow nested exceptions - std::throw_with_nested( - Mars2GribDeductionException("Failed to resolve `constituentType` from input dictionaries", Here())); - }; - - // Remove compiler warning - mars2gribUnreachable(); -}; - -} // namespace metkit::mars2grib::backend::deductions diff --git a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in index d2b346b4..4e4bbd70 100644 --- a/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in +++ b/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in @@ -1137,7 +1137,6 @@ INPUT = \@MARS2GRIB_SOURCE_DIRECTORY@/utils/configConverter.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/timeSpanInSeconds.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/systemNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/resolution.h \ -@MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/constituentType.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/experiment.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/class.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/subSetTrunc.h \