Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
#include "metkit/mars2grib/utils/generalUtils.h"

// Deductions
#include "metkit/mars2grib/backend/deductions/constituentType.h"
#include "metkit/mars2grib/backend/deductions/chemId.h"
#include "metkit/mars2grib/backend/deductions/firstWavelength.h"

// Utils
#include "metkit/config/LibMetkit.h"
Expand Down Expand Up @@ -134,6 +135,7 @@ template <std::size_t Stage, std::size_t Section, CompositionType Variant, class
class OptDict_t, class OutDict_t>
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;

Expand All @@ -151,16 +153,31 @@ 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<long>(out, "constituentType", constituentType);
// }

if constexpr (Variant == CompositionType::AerosolOptical) {

// Deductions
long constituentType = deductions::resolve_ConstituentType_or_throw(mars, par, opt);
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<long>(out, "constituentType", constituentType);
set_or_throw(out, "enableChemSplit", true);
set_or_throw(out, "chemId", chemId);
set_or_throw(out, "firstWavelength", firstWavelength);
}
else {
MARS2GRIB_CONCEPT_THROW(composition, "Concept variant is not implemented!");
}
}
catch (...) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class MarsDict_t, class OptDict_t>
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<std::size_t>(CompositionType::Chem);
const auto param = get_or_throw<long>(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<std::size_t>(CompositionType::Aerosol);
const auto chem = get_or_throw<long>(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<std::size_t>(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_
3 changes: 2 additions & 1 deletion src/metkit/mars2grib/backend/concepts/level/levelMatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::size_t>(LevelType::Surface);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::size_t>(PointInTimeType::Default);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright 2025- ECMWF and individual contributors.
* (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.
Expand All @@ -9,29 +9,28 @@
*/

///
/// @file constituentType.h
/// @brief Deduction of the constituent (chemical species) type identifier.
/// @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 **constituent / chemical species identifier** from
/// MARS metadata.
/// to resolve the **chemical 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.
/// 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 explicit and minimal validation logic
/// - applying minimal, explicit deduction logic
/// - returning strongly typed values to concept operations
///
/// Deductions:
/// - do NOT encode GRIB keys directly
/// - do NOT apply semantic inference or defaulting
/// - do NOT apply inference, defaulting, or consistency checks
/// - do NOT perform GRIB table validation
///
/// Error handling follows a strict fail-fast strategy:
/// - missing or invalid inputs cause immediate failure
/// - missing or malformed inputs cause immediate failure
/// - errors are reported using domain-specific deduction exceptions
/// - original errors are preserved via nested exception propagation
///
Expand All @@ -43,9 +42,6 @@
/// Concept:
/// - @ref compositionEncoding.h
///
/// Related deductions:
/// - @ref paramId.h
///
/// @ingroup mars2grib_backend_deductions
///
#pragma once
Expand All @@ -62,25 +58,20 @@
namespace metkit::mars2grib::backend::deductions {

///
/// @brief Resolve the constituent (chemical species) type identifier from input dictionaries.
/// @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 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.
/// 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 defaulting is applied.
/// The meaning of the identifier is defined by upstream MARS/GRIB
/// conventions.
/// 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`
Expand All @@ -93,7 +84,7 @@ namespace metkit::mars2grib::backend::deductions {
/// Type of the options dictionary (unused by this deduction).
///
/// @param[in] mars
/// MARS dictionary from which the constituent type identifier is resolved.
/// MARS dictionary from which the chemical identifier is resolved.
///
/// @param[in] par
/// Parameter dictionary (unused).
Expand All @@ -102,50 +93,43 @@ namespace metkit::mars2grib::backend::deductions {
/// Options dictionary (unused).
///
/// @return
/// The resolved constituent (chemical species) type identifier.
/// The resolved chemical 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.
/// If the key `chem` is missing, cannot be converted to `long`,
/// 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.
/// This deduction performs presence-only validation and does not
/// consult chemical metadata or GRIB tables.
///
template <class MarsDict_t, class ParDict_t, class OptDict_t>
long resolve_ConstituentType_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) {
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 constituent type
long constituentType = get_or_throw<long>(mars, "chem");

// Validate
if (constituentType < 0 || constituentType > 900) {
throw Mars2GribDeductionException(
"Invalid `constituentType`: value='" + std::to_string(constituentType) + "'", Here());
}
// Retrieve mandatory MARS chemical identifier
long chemId = get_or_throw<long>(mars, "chem");

// Emit RESOLVE log entry
MARS2GRIB_LOG_RESOLVE([&]() {
std::string logMsg = "`constituentType` resolved from input dictionaries: value='";
logMsg += std::to_string(constituentType);
std::string logMsg = "`chemId` resolved from input dictionaries: value='";
logMsg += std::to_string(chemId);
logMsg += "'";
return logMsg;
}());

// Success exit point
return constituentType;
return chemId;
}
catch (...) {

// Rethrow nested exceptions
std::throw_with_nested(
Mars2GribDeductionException("Failed to resolve `constituentType` from input dictionaries", Here()));
Mars2GribDeductionException("Failed to resolve `chemId` from input dictionaries", Here()));
};

// Remove compiler warning
Expand Down
Loading
Loading