metkit-1.20.2/0000775000175000017500000000000015246725757013301 5ustar alastairalastairmetkit-1.20.2/ruff.toml0000664000175000017500000000005315246725757015136 0ustar alastairalastairtarget-version = "py310" line-length = 120 metkit-1.20.2/VERSION0000664000175000017500000000000715246725757014346 0ustar alastairalastair1.20.2 metkit-1.20.2/NOTICE0000664000175000017500000000063515246725757014211 0ustar alastairalastairmetkit ====== Copyright 1996- ECMWF This product is developed by the Development Section, European Centre for Medium-Range Weather Forecasts (ECMWF) - http://www.ecmwf.int Below is a list of software packages which are used inside: - No external package in currently included Please also read the cmake/NOTICE file bundled in the distribution packages, which lists the included third-party CMake macros. metkit-1.20.2/tests/0000775000175000017500000000000015246725757014443 5ustar alastairalastairmetkit-1.20.2/tests/pl.grib0000664000175000017500000000606015246725757015725 0ustar alastairalastairGRIBb! 0001H Ĵ0ĴC1-1-"dCg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777GRIBb! 0001H Ĵ0ĴC1-1-"dCg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777GRIBb! 0001H Ĵ0ĴC1-1-"dCg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777GRIBb! 0001H Ĵ0ĴC1-1-"dCg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777GRIBb! 0001H Ĵ0ĴC1-1-"dCg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777GRIBb! 0001H Ĵ0ĴC1-1-"dN Cg졀 I;2SfaNbZ'K=5D211-&FT"Arc0}~>yf.JT6 5E?zI$HH>8_+k`Wgcuvz!fS@ZQ`re`_`Z_YiO>~g}j;izŁ$|bvKuvz!}Lяz}Ux:8I`5?wn|Ez={vjmsy{.Z~~~%zmPmj c _dpj;o?rQyO}~|yut]syu,nkKk>oQtv{kTelˋX~wpkJh-gimBr0w~gwۆ7777metkit-1.20.2/tests/test_odbsplitter.cc0000664000175000017500000000752215246725757020352 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/io/BufferList.h" #include "eckit/io/FileHandle.h" #include "eckit/message/Reader.h" #include "eckit/testing/Test.h" #include using namespace eckit::testing; namespace metkit { namespace grib { namespace test { //---------------------------------------------------------------------------------------------------------------------- class NonSeekFileHandle : public eckit::FileHandle { using eckit::FileHandle::FileHandle; bool canSeek() const override { return false; } eckit::Offset seek(const eckit::Offset&) override { NOTIMP; } }; //---------------------------------------------------------------------------------------------------------------------- CASE("read multiple matched odb frames") { // This ODB is formed of two frames with matched metadata. We should read both frames into // one message. eckit::Buffer msgdata; { NonSeekFileHandle fh("multiodb.odb"); fh.openForRead(); eckit::AutoClose closer(fh); eckit::message::Reader reader(fh); eckit::message::Message msg; msg = reader.next(); ASSERT(msg); msgdata = eckit::Buffer{msg.data(), msg.length()}; // There is only one Message in the file msg = reader.next(); EXPECT(!msg); } // And read the data in in one blob eckit::Buffer comparedata; { NonSeekFileHandle fh("multiodb.odb"); fh.openForRead(); eckit::AutoClose closer(fh); size_t expected_size = fh.size(); comparedata.resize(expected_size); EXPECT(expected_size > 0); EXPECT(fh.read(comparedata.data(), expected_size) == expected_size); } EXPECT(comparedata.size() == msgdata.size()); EXPECT(::memcmp(comparedata.data(), msgdata.data(), comparedata.size()) == 0); eckit::Log::info() << "odb size: " << msgdata.size() << std::endl; } CASE("read multiple matched odb frames") { // This ODB file is formed of four frames. Two pairs of matched metadata. This should be deconstructed into // two messages. eckit::BufferList msgdata; { eckit::FileHandle fh("multiodb2.odb"); fh.openForRead(); eckit::AutoClose closer(fh); eckit::message::Reader reader(fh); eckit::message::Message msg; for (int i = 0; i < 2; ++i) { msg = reader.next(); ASSERT(msg); msgdata.append(eckit::Buffer{msg.data(), msg.length()}); } // There is only one Message in the file msg = reader.next(); EXPECT(!msg); } // And read the data in in one blob eckit::Buffer comparedata; { eckit::FileHandle fh("multiodb2.odb"); fh.openForRead(); eckit::AutoClose closer(fh); size_t expected_size = fh.size(); comparedata.resize(expected_size); EXPECT(expected_size > 0); EXPECT(fh.read(comparedata.data(), expected_size) == expected_size); } eckit::Buffer combineddata = msgdata.consolidate(); EXPECT(comparedata.size() == combineddata.size()); EXPECT(::memcmp(comparedata.data(), combineddata.data(), combineddata.size()) == 0); eckit::Log::info() << "odb size: " << combineddata.size() << std::endl; } //---------------------------------------------------------------------------------------------------------------------- } // namespace test } // namespace grib } // namespace metkit int main(int argc, char** argv) { return run_tests(argc, argv); } metkit-1.20.2/tests/netcdf/0000775000175000017500000000000015246725757015706 5ustar alastairalastairmetkit-1.20.2/tests/netcdf/signature_merge_1.sh0000775000175000017500000000214715246725757021651 0ustar alastairalastair#!/bin/bash set -eaux rm -f *.nc ncgen -b - <CCCCmetkit-1.20.2/tests/netcdf/simple_three_way_merge.sh0000775000175000017500000000155315246725757022770 0ustar alastairalastair#!/bin/bash set -eaux ncgen -b - < #include #include #include "eckit/testing/Test.h" #include "eckit/value/Value.h" #include "metkit/mars/TypeDate.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars::test { //----------------------------------------------------------------------------- CASE("test_list_types") { std::stringstream ss; TypesFactory::list(ss); std::cout << ss.str() << std::endl; EXPECT(ss.str() == std::string("[any,date,enum,expver,float,integer,lowercase,mixed,param,range,regex,time,to-by-" "list,to-by-list-float,to-by-list-quantile]")); } CASE("test_build") { eckit::ValueMap settings; settings["type"] = "date"; Type* t1(TypesFactory::build("abcd", eckit::Value(settings))); EXPECT(t1 != 0); t1->attach(); // Check that we have obtained the correct type EXPECT(dynamic_cast(t1) != 0); // Clean up, taking into account that ~Type is protected. t1->detach(); } } // namespace metkit::mars::test //----------------------------------------------------------------------------- int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_expand.cc0000664000175000017500000017475415246725757017312 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_expand.cc /// @date Jan 2016 /// @author Florian Rathgeber /// @author Emanuele Danovaro #include #include #include #include "eckit/filesystem/LocalPathName.h" #include "eckit/filesystem/StdDir.h" #include "eckit/types/Date.h" #include "eckit/utils/StringTools.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsParser.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" #include "eckit/testing/Test.h" #include "eckit/utils/Tokenizer.h" using namespace eckit::testing; namespace metkit::mars::test { namespace { using ExpectedVals = std::map>; /** * @brief We can specify a set of keys which should be ignored when not specified in the user request. * In case the user requests defines them, check them regardless. This is mean to ignore auto-injected * keys e.g. for resol. */ std::set ignore_{"repres", "resol"}; std::string date(long d) { if (d <= 0) { eckit::Date day(d); d = day.yyyymmdd(); } return std::to_string(d); } std::string date(std::string d) { if (d == "0" || d[0] == '-') { long dd = std::stol(d); eckit::Date day(dd); return std::to_string(day.yyyymmdd()); } return d; } struct ExpectedRequest { ExpectedRequest() = default; ExpectedRequest(std::string vrb, ExpectedVals vv, std::vector dd) : verb(vrb), vals(vv), dates(dd) {} ExpectedRequest(std::string vrb, ExpectedVals vv, std::vector dd) : verb(vrb), vals(vv) { dates.reserve(dd.size()); for (long d : dd) { dates.push_back(date(d)); } } std::string verb; ExpectedVals vals; std::vector dates; }; } // namespace //----------------------------------------------------------------------------- void expand(const MarsRequest& r, const ExpectedRequest& expected, std::set ignore) { try { EXPECT_EQUAL(expected.verb, r.verb()); for (const auto& e : expected.vals) { if (!r.has(e.first)) { if (ignore.find(e.first) != ignore.end()) { continue; } std::cerr << eckit::Colour::red << "Missing keyword: " << e.first << eckit::Colour::reset << std::endl; } EXPECT(r.has(e.first)); auto vv = r.values(e.first); if (e.first != "date") { // dates are verified at a later stage EXPECT_EQUAL(e.second.size(), vv.size()); } for (int i = 0; i < vv.size(); i++) { if (e.first == "grid") { EXPECT_EQUAL(eckit::StringTools::upper(e.second.at(i)), vv.at(i)); } else { if (e.first == "resol") { EXPECT_EQUAL(e.second.at(i), eckit::StringTools::lower(vv.at(i))); } else { EXPECT_EQUAL(e.second.at(i), vv.at(i)); } } } } if (expected.dates.size() > 0) { EXPECT(r.has("date")); auto dd = r.values("date"); EXPECT_EQUAL(expected.dates.size(), dd.size()); for (int i = 0; i < expected.dates.size(); i++) { EXPECT_EQUAL(date(expected.dates.at(i)), dd.at(i)); } } } catch (...) { std::cerr << "Error comparing " << r << " with " << expected.vals << " dates " << expected.dates << std::endl; throw; } } void expand(const MarsRequest& r, const std::string& verb, const ExpectedVals& expected, std::vector& dates, std::set ignore) { expand(r, {verb, expected, dates}, ignore); } void expand(const std::string& text, const std::string& verb, const ExpectedVals& expected, std::vector dates, bool strict = false) { MarsRequest r = MarsRequest::parse(text, strict); std::set ignore; std::string tt = eckit::StringTools::lower(text); for (const auto& i : ignore_) { auto idx = tt.find(i); if (idx == std::string::npos) { ignore.insert(i); } } expand(r, {verb, expected, dates}, ignore); } void parse(const std::string& req, ExpectedRequest& expected) { eckit::Tokenizer c(","); eckit::Tokenizer e("="); eckit::Tokenizer s("/"); eckit::StringList tokens; c(req, tokens); for (const auto& t : tokens) { auto tt = eckit::StringTools::trim(t); if (expected.verb.empty()) { expected.verb = eckit::StringTools::lower(tt); continue; } eckit::StringList kv; e(tt, kv); EXPECT_EQUAL(2, kv.size()); auto key = eckit::StringTools::lower(eckit::StringTools::trim(kv[0])); if (key == "date") { EXPECT_EQUAL(0, expected.dates.size()); } eckit::StringList vals; s(kv[1], vals); std::vector vv; for (auto v : vals) { auto val = eckit::StringTools::trim(v); if (key != "source" && key != "target" && key != "intgrid" && key != "grid") { val = eckit::StringTools::lower(val); } if (key == "date") { expected.dates.push_back(date(val)); } else { vv.push_back(val); } } if (key != "date") { expected.vals.emplace(key, vv); } } } void expand(const std::string& text, const std::string& expected, bool strict = false, std::vector dates = {}) { ExpectedRequest out; out.dates.reserve(dates.size()); for (long d : dates) { out.dates.push_back(date(d)); } parse(expected, out); MarsRequest r = MarsRequest::parse(text, strict); std::set ignore; std::string tt = eckit::StringTools::lower(text); for (const auto& i : ignore_) { auto idx = tt.find(i); if (idx == std::string::npos) { ignore.insert(i); } } expand(r, out, ignore); } void expand(const std::string& text, const std::vector& expected, bool strict = false, std::vector> dates = {}) { ExpectedVals out; std::istringstream in(text); std::vector reqs = MarsRequest::parse(in, strict); std::set ignore; std::string tt = eckit::StringTools::lower(text); for (const auto& i : ignore_) { auto idx = tt.find(i); if (idx == std::string::npos) { ignore.insert(i); } } ASSERT(dates.size() == 0 || dates.size() == expected.size()); EXPECT_EQUAL(expected.size(), reqs.size()); for (size_t i = 0; i < expected.size(); i++) { ExpectedRequest expectedReq; if (dates.size() != 0) { expectedReq.dates.reserve(dates.at(i).size()); for (long d : dates.at(i)) { expectedReq.dates.push_back(date(d)); } } parse(expected.at(i), expectedReq); expand(reqs.at(i), expectedReq, ignore); } } CASE("test_metkit_expand_1") { const char* text = "ret,date=-5/to/-1"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; expand(text, "retrieve", expected, {-5, -4, -3, -2, -1}); const char* text2 = "ret,date=-5/to/-1."; expand(text2, "retrieve", expected, {-5, -4, -3, -2, -1}); const char* expectedStr = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=OPER,EXPVER=0001,REPRES=SH,LEVTYPE=PL,LEVELIST=1000/850/700/500/400/" "300,PARAM=129,TIME=1200,STEP=0,DOMAIN=G"; expand(text, expectedStr, false, {-5, -4, -3, -2, -1}); } CASE("test_metkit_expand_2") { { const char* text = "ret,date=-1"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; expand(text, "retrieve", expected, {-1}); } { const char* text = "ret,levtype=ml"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1"}}, {"levtype", {"ml"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; expand(text, "retrieve", expected, {-1}); } } CASE("test_metkit_expand_3") { const char* text = "ret,date=-5/to/-1,grid=n640"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}, {"grid", {"N640"}}}; expand(text, "retrieve", expected, {-5, -4, -3, -2, -1}); } CASE("test_metkit_expand_4") { const char* text = "ret,date=-5/to/-1,grid=o640"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}, {"grid", {"O640"}}}; expand(text, "retrieve", expected, {-5, -4, -3, -2, -1}); } CASE("test_metkit_expand_5") { const char* text = "retrieve,class=od,date=20050601,diagnostic=1,expver=1,iteration=0,levelist=1,levtype=ml,param=155.129,stream=" "sens,time=1200,type=sg"; ExpectedVals expected{{"class", {"od"}}, {"diagnostic", {"1"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"iteration", {"0"}}, {"levelist", {"1"}}, {"levtype", {"ml"}}, {"param", {"129155"}}, {"step", {"0"}}, {"stream", {"sens"}}, {"time", {"1200"}}, {"type", {"sg"}}}; expand(text, "retrieve", expected, {20050601}); } CASE("test_metkit_expand_6") { const char* text = "retrieve,class=rd,expver=hl1m,stream=oper,date=20000801,time=0000,domain=g,type=fc,levtype=pl,step=24,param=" "129,levelist=1/to/31"; ExpectedVals expected{{"class", {"rd"}}, {"expver", {"hl1m"}}, {"stream", {"oper"}}, {"time", {"0000"}}, {"domain", {"g"}}, {"type", {"fc"}}, {"levtype", {"pl"}}, {"step", {"24"}}, {"param", {"129"}}}; std::vector levelist; for (int i = 1; i <= 31; i++) { levelist.push_back(std::to_string(i)); } expected["levelist"] = levelist; expand(text, "retrieve", expected, {20000801}); } CASE("test_metkit_expand_7") { const char* text = "retrieve,class=rd,expver=hl1m,stream=oper,date=20000801,time=0000,domain=g,type=fc,levtype=pl,step=24,param=" "129,levelist=0.01/0.7"; ExpectedVals expected{{"class", {"rd"}}, {"expver", {"hl1m"}}, {"stream", {"oper"}}, {"time", {"0000"}}, {"domain", {"g"}}, {"type", {"fc"}}, {"levtype", {"pl"}}, {"step", {"24"}}, {"param", {"129"}}, {"levelist", {".01", ".7"}}}; expand(text, "retrieve", expected, {20000801}); } CASE("test_metkit_expand_8") { const char* text = "retrieve,class=rd,expver=hl1m,stream=oper,date=20000801,time=0000,domain=g,type=fc,levtype=pl,step=24,param=" "129,levelist=0.1/to/0.7/by/0.2"; ExpectedVals expected{{"class", {"rd"}}, {"expver", {"hl1m"}}, {"stream", {"oper"}}, {"time", {"0000"}}, {"domain", {"g"}}, {"type", {"fc"}}, {"levtype", {"pl"}}, {"step", {"24"}}, {"param", {"129"}}, {"levelist", {".1", ".3", ".5", ".7"}}}; expand(text, "retrieve", expected, {20000801}); } CASE("test_metkit_expand_9_strict") { const char* text = "retrieve,class=rd,expver=hm1u,stream=weeh,time=0000,date=20210101,domain=g,hdate=20190101"; { std::istringstream in(text); MarsParser parser(in); MarsExpansion expand(false, false); std::vector v = expand.expand(parser.parse()); EXPECT_EQUAL(v.size(), 1); } { std::istringstream in(text); MarsParser parser(in); MarsExpansion expand(false, true); std::vector v = expand.expand(parser.parse()); EXPECT_EQUAL(v.size(), 1); } { const char* text = "retrieve,class=od,expver=1,date=20240304,time=0,type=fc,levtype=sfc,levelist=1/2/" "3,step=0,param=2t,target=out"; EXPECT_THROWS_AS(MarsRequest::parse(text, true), eckit::UserError); const char* expected = "RETRIEVE,CLASS=OD,TYPE=FC,STREAM=OPER,EXPVER=0001,REPRES=GG,LEVTYPE=SFC,PARAM=167,DATE=20240304,TIME=0000," "STEP=0,DOMAIN=G,TARGET=out"; expand(text, expected, false); } { const char* text = "retrieve,class=od,expver=1,stream=enfo,date=20240304,time=0,type=cf,levtype=sfc,levelist=1/2/" "3,step=0,param=2t,number=1/2/3,target=out"; EXPECT_THROWS_AS(MarsRequest::parse(text, true), eckit::UserError); const char* expected = "RETRIEVE,CLASS=OD,TYPE=CF,STREAM=ENFO,EXPVER=0001,REPRES=SH,LEVTYPE=SFC,PARAM=167,DATE=20240304,TIME=0000," "STEP=0,DOMAIN=G,TARGET=out"; expand(text, expected, false); } } CASE("test_metkit_expand_10_strict") { const char* text = "retrieve,class=rd,expver=hm1u,stream=wees,time=0000,date=20210101,domain=g,hdate=20190101"; { std::istringstream in(text); MarsParser parser(in); MarsExpansion expand(false, false); std::vector v = expand.expand(parser.parse()); EXPECT_EQUAL(v.size(), 1); } } CASE("test_metkit_expand_multirequest-1") { const std::string text = "ret,date=-5/to/-2.\nret,date=-1"; std::istringstream in(text); std::vector reqs = MarsRequest::parse(in, false); EXPECT_EQUAL(reqs.size(), 2); ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; expand(reqs.at(0), {"retrieve", expected, std::vector{-5, -4, -3, -2}}, ignore_); expand(reqs.at(1), {"retrieve", expected, std::vector{-1}}, ignore_); } CASE("test_metkit_expand_multirequest-2") { const std::string text = "ret,date=-5/to/-2.\nret,date=-1"; std::string expectedStr = "retrieve,class=od,domain=g,expver=0001,levelist=1000/850/700/500/400/" "300,levtype=pl,param=129,step=0,stream=oper,time=1200,type=an"; expand(text, {expectedStr, expectedStr}, false, {{-5, -4, -3, -2}, {-1}}); } CASE("test_metkit_expand_multirequest-3") { const char* text = "retrieve,accuracy=16,area=14.8/-19.6/-14.5/19.8,class=od,date=20230810,expver=1,grid=0.09/0.09,levelist=1/" "to/137,levtype=ml,number=-1,param=z,process=local,rotation=-78.8/-61.0,step=000,stream=scda,time=18," "type=an,target=\"reference.ect1qF.data\"\n" "ret,rotation=off,target=\"out\""; const char* expected1 = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=SCDA,EXPVER=0001,REPRES=SH,LEVTYPE=ML,LEVELIST=1/2/3/4/5/6/7/8/9/10/11/" "12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/" "47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/" "82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100/101/102/103/104/105/106/107/108/109/110/111/112/" "113/114/115/116/117/118/119/120/121/122/123/124/125/126/127/128/129/130/131/132/133/134/135/136/137," "PARAM=129,DATE=20230810,TIME=1800,STEP=0,DOMAIN=G,TARGET=reference.ect1qF.data,RESOL=AUTO,ACCURACY=16," "AREA=14.8/-19.6/-14.5/19.8,ROTATION=-78.8/-61,GRID=.09/.09,PROCESS=LOCAL"; const char* expected2 = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=SCDA,EXPVER=0001,REPRES=SH,LEVTYPE=ML,LEVELIST=1/2/3/4/5/6/7/8/9/10/11/" "12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/" "47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/" "82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100/101/102/103/104/105/106/107/108/109/110/111/112/" "113/114/115/116/117/118/119/120/121/122/123/124/125/126/127/128/129/130/131/132/133/134/135/136/137," "PARAM=129,DATE=20230810,TIME=1800,STEP=0,DOMAIN=G,TARGET=out,RESOL=AUTO,ACCURACY=16," "AREA=14.8/-19.6/-14.5/19.8,GRID=.09/.09,PROCESS=LOCAL"; expand(text, std::vector{expected1, expected2}); } void expandKeyThrows(const std::string& key, std::vector values) { static metkit::mars::MarsLanguage language("retrieve"); metkit::mars::Type* t = language.type(key); EXPECT_THROWS_AS(t->expand(values), eckit::BadValue); } void expandKey(const std::string& key, std::vector values, std::vector expected) { static metkit::mars::MarsLanguage language("retrieve"); metkit::mars::Type* t = language.type(key); t->expand(values); EXPECT_EQUAL(expected, values); } void quantileThrows(std::vector values) { expandKeyThrows("quantile", values); } void quantile(std::vector values, std::vector expected) { expandKey("quantile", values, expected); } CASE("test_metkit_expand_11_quantile") { quantileThrows({"-1:5"}); quantileThrows({"0:-5"}); quantileThrows({"6:5"}); quantileThrows({"0:12"}); quantile({"2:5"}, {"2:5"}); quantile({"0:2", "1:2", "2:2"}, {"0:2", "1:2", "2:2"}); quantile({"0:2", "1:3", "2:5"}, {"0:2", "1:3", "2:5"}); quantileThrows({"to", "5:10"}); quantileThrows({"3:5", "to"}); quantileThrows({"3:5", "to", "5:10"}); quantileThrows({"1:5", "to", "3:5", "by"}); quantile({"0:5", "to", "0:5"}, {"0:5"}); quantile({"3:3", "to", "3:3"}, {"3:3"}); quantile({"0:5", "to", "5:5"}, {"0:5", "1:5", "2:5", "3:5", "4:5", "5:5"}); quantile({"0:5", "to", "5:5", "by", "1"}, {"0:5", "1:5", "2:5", "3:5", "4:5", "5:5"}); quantile({"0:5", "to", "5:5", "by", "2"}, {"0:5", "2:5", "4:5"}); quantile({"0:5", "to", "5:5", "by", "3"}, {"0:5", "3:5"}); quantile({"0:5", "to", "5:5", "by", "5"}, {"0:5", "5:5"}); quantile({"0:5", "to", "5:5", "by", "6"}, {"0:5"}); quantile({"2:5", "to", "5:5", "by", "2"}, {"2:5", "4:5"}); quantile({"3:5", "to", "5:5", "by", "2"}, {"3:5", "5:5"}); quantile({"4:5", "to", "5:5", "by", "2"}, {"4:5"}); quantile({"0:10", "3:10", "to", "7:10", "by", "2", "10:10"}, {"0:10", "3:10", "5:10", "7:10", "10:10"}); quantile({"3:5", "to", "2:5"}, {"3:5", "2:5"}); } void timeThrows(std::vector values) { expandKeyThrows("time", values); } void time(std::vector values, std::vector expected) { expandKey("time", values, expected); } CASE("test_metkit_expand_12_time") { timeThrows({"87"}); timeThrows({"000012"}); timeThrows({"0:0:12"}); timeThrows({"12s"}); time({"0"}, {"0000"}); time({"0", "1", "6", "12", "18"}, {"0000", "0100", "0600", "1200", "1800"}); time({"0", "1", "12"}, {"0000", "0100", "1200"}); time({"0030"}, {"0030"}); time({"30m"}, {"0030"}); time({"00:30", "1:30", "02:50"}, {"0030", "0130", "0250"}); time({"0h", "3h", "120m", "170m"}, {"0000", "0300", "0200", "0250"}); timeThrows({"to", "5"}); timeThrows({"3", "to"}); timeThrows({"1", "to", "3", "by"}); time({"0", "to", "0"}, {"0000"}); time({"12", "to", "12"}, {"1200"}); time({"0", "to", "12"}, {"0000", "0600", "1200"}); time({"3", "to", "2"}, {"0300"}); time({"12", "to", "4"}, {"1200", "0600"}); time({"0", "to", "6", "by", "1"}, {"0000", "0100", "0200", "0300", "0400", "0500", "0600"}); time({"0", "to", "6", "by", "2"}, {"0000", "0200", "0400", "0600"}); time({"0", "to", "6", "by", "3"}, {"0000", "0300", "0600"}); time({"0", "to", "6", "by", "4"}, {"0000", "0400"}); time({"0", "to", "6", "by", "5"}, {"0000", "0500"}); time({"0", "to", "6", "by", "6"}, {"0000", "0600"}); time({"6", "to", "18"}, {"0600", "1200", "1800"}); time({"1", "to", "6", "by", "1"}, {"0100", "0200", "0300", "0400", "0500", "0600"}); time({"1", "to", "6", "by", "2"}, {"0100", "0300", "0500"}); time({"1", "to", "6", "by", "3"}, {"0100", "0400"}); time({"1", "to", "6", "by", "4"}, {"0100", "0500"}); time({"1", "to", "6", "by", "5"}, {"0100", "0600"}); time({"1", "to", "6", "by", "6"}, {"0100"}); time({"1", "to", "3h", "by", "30m"}, {"0100", "0130", "0200", "0230", "0300"}); } void stepThrows(std::vector values) { expandKeyThrows("step", values); } void step(std::string valuesStr, std::string expectedStr) { eckit::Tokenizer parse("/"); std::vector values; std::vector expected; parse(valuesStr, values); parse(expectedStr, expected); expandKey("step", values, expected); } CASE("test_metkit_expand_13_step") { step("0", "0"); step("1", "1"); step("24", "24"); step("144", "144"); step("012", "12"); step("12:30", "12h30m"); step("1:00", "1"); step("1:0:0", "1"); step("1h", "1"); step("60m", "1"); step("1h60m", "2"); step("0:5", "5m"); step("0:05", "5m"); step("0:05:0", "5m"); step("0:06", "6m"); step("0:10", "10m"); step("0:12", "12m"); step("0:15", "15m"); step("0:20", "20m"); step("0:25", "25m"); step("0:30", "30m"); step("0:35", "35m"); step("0:40", "40m"); step("0:45", "45m"); step("0:50", "50m"); step("0:55", "55m"); step("0-24", "0-24"); step("0-24s", "0-24s"); step("0-120s", "0-2m"); step("0s-120m", "0-2"); step("1-2", "1-2"); step("30m-1", "30m-1"); step("30m-90m", "30m-1h30m"); step("0/to/24/by/3", "0/3/6/9/12/15/18/21/24"); step("12/to/48/by/12", "12/24/36/48"); step("12/to/47/by/12", "12/24/36"); step("40m/to/2/by/10m", "40m/50m/1/1h10m/1h20m/1h30m/1h40m/1h50m/2"); step("0/to/6/by/30m", "0/30m/1/1h30m/2/2h30m/3/3h30m/4/4h30m/5/5h30m/6"); step("0-6/to/18-24/by/6", "0-6/6-12/12-18/18-24"); step("0-24/to/48-72/by/24", "0-24/24-48/48-72"); step("0/to/24/by/3/0-6/to/18-24/by/6", "0/3/6/9/12/15/18/21/24/0-6/6-12/12-18/18-24"); step("0-24/to/24-48/by/90m", "0-24/1h30m-25h30m/3-27/4h30m-28h30m/6-30/7h30m-31h30m/9-33/10h30m-34h30m/12-36/13h30m-37h30m/15-39/" "16h30m-40h30m/18-42/19h30m-43h30m/21-45/22h30m-46h30m/24-48"); } void activity(std::vector values, std::vector expected) { expandKey("activity", values, expected); } void experiment(std::vector values, std::vector expected) { expandKey("experiment", values, expected); } void model(std::vector values, std::vector expected) { expandKey("model", values, expected); } CASE("test_metkit_expand_lowercase") { activity({"ScenarioMIP"}, {"scenariomip"}); activity({"CMIP6"}, {"cmip6"}); activity({"ScenarioMIP"}, {"scenariomip"}); activity({"cmip6"}, {"cmip6"}); experiment({"SSP3-7.0"}, {"ssp3-7.0"}); experiment({"ssp3-7.0"}, {"ssp3-7.0"}); experiment({"hist"}, {"hist"}); model({"IFS-NEMO"}, {"ifs-nemo"}); model({"IFS"}, {"ifs"}); model({"ifs-nemo"}, {"ifs-nemo"}); model({"ifs"}, {"ifs"}); model({"ICON"}, {"icon"}); model({"icon"}, {"icon"}); } CASE("test_metkit_expand_param") { { const char* text = "retrieve,class=od,expver=0079,stream=enfo,date=-1,time=00/12,type=pf,levtype=sfc,step=24,number=1/to/" "2,param=mucin/mucape/tprate"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 3); EXPECT_EQUAL(params[0], "228236"); EXPECT_EQUAL(params[1], "228235"); EXPECT_EQUAL(params[2], "260048"); } { const char* text = "retrieve,class=od,expver=0079,stream=enfh,date=-1,time=00/12,type=fcmean,levtype=sfc,step=24,number=1/to/" "2,param=mucin/mucape/tprate"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 3); EXPECT_EQUAL(params[0], "228236"); EXPECT_EQUAL(params[1], "228235"); EXPECT_EQUAL(params[2], "172228"); } { const char* text = "retrieve,class=od,expver=1,stream=wave,date=-1,time=00/12,type=an,levtype=sfc,step=24,param=2dfd "; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "140251"); } { const char* text = "retrieve,class=od,expver=1,stream=enwh,date=-1,time=00/12,type=cf,levtype=sfc,step=24,param=tmax"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "140217"); } { const char* text = "retrieve,class=ai,expver=1,stream=oper,model=aifs-singlw,date=-1,time=00/" "12,type=pf,levtype=pl,step=24,param=t"; EXPECT_THROWS_AS(MarsRequest::parse(text, true), eckit::UserError); } { const char* text = "retrieve,class=ai,expver=1,stream=oper,model=aifs-single,date=-1,time=00/" "12,type=pf,levtype=pl,step=24,param=t"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "130"); } { const char* text = "retrieve,class=od,date=20240723,domain=g,expver=0079,levtype=sfc,param=asn/cp/lsp/sf/tcc/" "tp,step=0,stream=oper,time=0000,type=fc"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 6); EXPECT_EQUAL(params[0], "32"); EXPECT_EQUAL(params[1], "143"); EXPECT_EQUAL(params[2], "142"); EXPECT_EQUAL(params[3], "144"); EXPECT_EQUAL(params[4], "164"); EXPECT_EQUAL(params[5], "228"); } { const char* text = "retrieve,class=od,expver=1,stream=msmm,date=-1,time=0000,type=em,levtype=sfc,step=24,param=e"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "172182"); } { const char* text = "retrieve,class=od,expver=1,stream=msmm,date=-1,time=0000,type=em,levtype=sfc,step=24,param=e/erate"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 2); EXPECT_EQUAL(params[0], "172182"); EXPECT_EQUAL(params[1], "172182"); } { const char* text = "retrieve,class=od,expver=1,stream=enwh,date=-1,time=0000,type=pf,levtype=sfc,step=24,param=sh10"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "140120"); } { const char* text = "retrieve,class=od,expver=1,stream=enwh,date=-1,time=0000,type=pf,levtype=sfc,step=24,param=p1ww"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "140223"); } { const char* text = "retrieve,class=od,expver=1,stream=waef,date=-1,time=0000,type=cf,levtype=sfc,step=24,param=WSK/MWP"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 2); EXPECT_EQUAL(params[0], "140252"); EXPECT_EQUAL(params[1], "140232"); } { const char* text = "retrieve,class=od,expver=1,stream=eefo,date=-1,time=0000,type=fcmean,levtype=sfc,step=24,param=MSL"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "151"); } { const char* text = "retrieve,class=od,expver=1,stream=eefo,date=-1,time=0000,type=fcmean,levtype=sfc,step=24,param=strda"; MarsRequest r = MarsRequest::parse(text); auto params = r.values("param"); EXPECT_EQUAL(params.size(), 1); EXPECT_EQUAL(params[0], "171175"); } } CASE("test_metkit_expand_d1") { { const char* text = "retrieve,class=d1,dataset=extremes-dt,date=-1"; ExpectedVals expected{{"class", {"d1"}}, {"dataset", {"extremes-dt"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; expand(text, "retrieve", expected, {-1}); } { const char* text = "retrieve,class=d1,dataset=climate-dt,levtype=pl,date=20000101,activity=CMIP6,experiment=hist,model=IFS-" "NEMO,generation=1,realization=1,resolution=high,stream=clte,type=fc,param=134/137"; ExpectedVals expected{{"class", {"d1"}}, {"dataset", {"climate-dt"}}, {"activity", {"cmip6"}}, {"experiment", {"hist"}}, {"model", {"ifs-nemo"}}, {"generation", {"1"}}, {"realization", {"1"}}, {"resolution", {"high"}}, {"expver", {"0001"}}, {"time", {"1200"}}, {"stream", {"clte"}}, {"type", {"fc"}}, {"levtype", {"pl"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"param", {"134", "137"}}}; expand(text, "retrieve", expected, {20000101}); } { const char* text = "retrieve,date=20120515,time=0000,dataset=climate-dt,activity=cmip6,experiment=hist,generation=1,model=" "icon,realization=1,georef=acbdef,resolution=high,class=d1,expver=0001,type=fc,stream=clte,levelist=1," "levtype=o3d,param=263500"; const char* text2 = "retrieve,date=20120515,time=0000,dataset=climate-dt,activity=cmip6,experiment=hist,generation=1,model=" "icon,realization=1,resolution=high,class=d1,expver=0001,type=fc,stream=clte,levelist=1," "levtype=o3d,param=263500"; ExpectedVals expected{{"class", {"d1"}}, {"dataset", {"climate-dt"}}, {"activity", {"cmip6"}}, {"experiment", {"hist"}}, {"model", {"icon"}}, {"generation", {"1"}}, {"realization", {"1"}}, {"resolution", {"high"}}, {"expver", {"0001"}}, {"time", {"0000"}}, {"stream", {"clte"}}, {"type", {"fc"}}, {"levtype", {"o3d"}}, {"levelist", {"1"}}, {"param", {"263500"}}}; expand(text, "retrieve", expected, {20120515}); expand(text2, "retrieve", expected, {20120515}); } } CASE("test_metkit_expand_ng") { { const char* text = "retrieve,class=ng,date=20000101,activity=CMIP6,experiment=hist,model=IFS-NEMO,generation=1,realization=1," "resolution=high,stream=clte,type=fc,levtype=pl,param=134/137"; ExpectedVals expected{{"class", {"ng"}}, {"levtype", {"pl"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"activity", {"cmip6"}}, {"experiment", {"hist"}}, {"model", {"ifs-nemo"}}, {"generation", {"1"}}, {"realization", {"1"}}, {"resolution", {"high"}}, {"expver", {"0001"}}, {"date", {"20000101"}}, {"time", {"1200"}}, {"stream", {"clte"}}, {"type", {"fc"}}, {"param", {"134", "137"}}}; expand(text, "retrieve", expected, {20000101}); } } CASE("test_metkit_expand_ai") { { const char* text = "retrieve,class=ai,date=20250208,time=1800,expver=9999,model=aifs-single,type=fc,levtype=sfc,param=169"; ExpectedVals expected{{"class", {"ai"}}, {"domain", {"g"}}, {"expver", {"9999"}}, {"levtype", {"sfc"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1800"}}, {"type", {"fc"}}, {"model", {"aifs-single"}}, {"param", {"169"}}}; expand(text, "retrieve", expected, {20250208}); } } CASE("test_metkit_expand_list") { { const char* text = "list,date=20250105,domain=g,levtype=pl,expver=" "0001,step=0,stream=oper,levelist=1000/850/700/500/400/" "300,time=1200,type=an,param=129"; ExpectedVals expected{{"class", {"od"}}, {"date", {"20250105"}}, {"domain", {"g"}}, {"levtype", {"pl"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"expver", {"0001"}}, {"time", {"1200"}}, {"stream", {"oper"}}, {"type", {"an"}}, {"param", {"129"}}}; expand(text, "list", expected, {20250105}); } { const char* text = "list,class=tr,date=20250105"; ExpectedVals expected{{"class", {"tr"}}, {"date", {"20250105"}}}; expand(text, "list", expected, {20250105}); } } CASE("test_metkit_expand_read") { { const char* text = "read,class=tr,date=20250105,domain=g,levtype=pl,expver=0001,step=0,stream=oper," "levelist=1000/850/700/500/400/300,time=1200,type=an,param=129"; ExpectedVals expected{{"class", {"tr"}}, {"date", {"20250105"}}, {"domain", {"g"}}, {"levtype", {"pl"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"expver", {"0001"}}, {"time", {"1200"}}, {"stream", {"oper"}}, {"type", {"an"}}, {"param", {"129"}}}; expand(text, "read", expected, {20250105}); } { const char* text = "read,date=20250105,param=129"; ExpectedVals expected{{"date", {"20250105"}}, {"param", {"129"}}}; expand(text, "read", expected, {20250105}); } } CASE("test_metkit_expand_clmn") { { const char* text = "retrieve,class=d1,expver=1,dataset=climate-dt,activity=story-nudging,experiment=Tplus2.0K,generation=1," "model=IFS-FESOM,realization=1,stream=clmn,year=2024,month=october,resolution=standard,type=fc,levtype=sfc," "param=144"; ExpectedVals expected{{"class", {"d1"}}, {"dataset", {"climate-dt"}}, {"activity", {"story-nudging"}}, {"experiment", {"tplus2.0k"}}, {"generation", {"1"}}, {"model", {"ifs-fesom"}}, {"realization", {"1"}}, {"expver", {"0001"}}, {"stream", {"clmn"}}, {"year", {"2024"}}, {"month", {"10"}}, {"resolution", {"standard"}}, {"type", {"fc"}}, {"levtype", {"sfc"}}, {"param", {"144"}}}; expand(text, "retrieve", expected, {}); } } CASE("test_metkit_expand_chem") { const char* text = "retrieve,chem=co/no2/no/so2,class=mc,date=2006-12-18,expver=9001,levtype=sfc,param=402000,step=12,stream=oper," "time=00:00:00,type=fc,target=\"output\""; const char* expected = "RETRIEVE,CLASS=MC,TYPE=FC,STREAM=OPER,EXPVER=9001,LEVTYPE=SFC,PARAM=402000,CHEM=2/17/27/233,DATE=20061218," "TIME=0000,STEP=12,DOMAIN=G,TARGET=output"; expand(text, expected); } CASE("test_metkit_expand_frequency") { const char* text = "retrieve,class=od,date=20250401,direction=31,domain=g,expver=0001,frequency=7,levtype=sfc,param=140217,step=0," "stream=wave,time=1200,type=an"; const char* expected = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=WAVE,EXPVER=0001,REPRES=SH,LEVTYPE=SFC,PARAM=140217,DATE=20250401,TIME=1200," "STEP=0,DOMAIN=G,FREQUENCY=7,DIRECTION=31"; expand(text, expected); } CASE("test_metkit_expand_obscutoff1") { const char* text = "retrieve,class=rd,date=20201204,expver=hk3a,obsgroup=22,reportype=21001,obstype=1,stream=lwda,obscutoff=2," "time=1200,type=mfb," "target=\"reference.vYyJf6.data\""; /// @todo DUPLICATES const char* expected = "RETRIEVE,CLASS=RD,TYPE=MFB,STREAM=LWDA,EXPVER=hk3a,REPRES=BU,OBSGROUP=AMSUA_AS,REPORTYPE=21001,obstype=1," "DATE=20201204,TIME=1200,DOMAIN=G,TARGET=reference.vYyJf6.data,DUPLICATES=KEEP"; expand(text, expected); } CASE("test_metkit_expand_obscutoff2") { const char* text = "retrieve,class=od,date=20201204,expver=1,obsgroup=22,reportype=21001,obstype=1,stream=xwda,obscutoff=2,time=" "1200,type=mfb," "target=\"reference.vYyJf6.data\""; /// @todo DUPLICATES const char* expected = "RETRIEVE,CLASS=OD,TYPE=MFB,STREAM=XWDA,obscutoff=0200,EXPVER=0001,REPRES=BU,OBSGROUP=AMSUA_AS,REPORTYPE=21001," "obstype=1," "DATE=20201204,TIME=1200,DOMAIN=G,TARGET=reference.vYyJf6.data,DUPLICATES=KEEP"; expand(text, expected); } CASE("test_metkit_expand_obscutoff3") { const char* text = "retrieve,class=od,date=20201204,expver=1,obsgroup=22,reportype=21001,obstype=1,stream=xwda,obscutoff=2h,time=" "1200,type=mfb," "target=\"reference.vYyJf6.data\""; /// @todo DUPLICATES const char* expected = "RETRIEVE,CLASS=OD,TYPE=MFB,STREAM=XWDA,obscutoff=0200,EXPVER=0001,REPRES=BU,OBSGROUP=AMSUA_AS,REPORTYPE=21001," "obstype=1," "DATE=20201204,TIME=1200,DOMAIN=G,TARGET=reference.vYyJf6.data,DUPLICATES=KEEP"; expand(text, expected); } CASE("test_metkit_expand_obscutoff4") { const char* text = "retrieve,class=od,date=20201204,expver=1,obsgroup=22,reportype=21001,obstype=1,stream=xwda,obscutoff=120m," "time=1200,type=mfb," "target=\"reference.vYyJf6.data\""; /// @todo DUPLICATES const char* expected = "RETRIEVE,CLASS=OD,TYPE=MFB,STREAM=XWDA,obscutoff=0200,EXPVER=0001,REPRES=BU,OBSGROUP=AMSUA_AS,REPORTYPE=21001," "obstype=1," "DATE=20201204,TIME=1200,DOMAIN=G,TARGET=reference.vYyJf6.data,DUPLICATES=KEEP"; expand(text, expected); } // issues from https://confluence.ecmwf.int/pages/viewpage.action?pageId=496866851 CASE("test_metkit_expand_MARSC-218") { // https://jira.ecmwf.int/browse/MARSC-218 const char* text = "retrieve,accuracy=10,class=ea,date=1969-03-28,expver=11,grid=0.25/" "0.25,levtype=sfc,packing=si,param=142.128/143.128/151.128/165.128/166.128,step=0/6/12/18/24/30/36/42/48/" "54/60/66/72/78/84/90/96/102/108/114/120/132/144/156/168/180/192/204/216/228/" "240,stream=oper,time=00:00:00,type=fc,target=\"reference.rFP7XB.data\""; const char* expected = "RETRIEVE,CLASS=EA,TYPE=FC,STREAM=OPER,EXPVER=0011,LEVTYPE=SFC,PARAM=142/143/151/165/" "166,DATE=19690328,TIME=0000,STEP=0/6/12/18/24/30/36/42/48/54/60/66/72/78/84/90/96/102/108/114/120/132/144/" "156/168/180/192/204/216/228/240,DOMAIN=G,TARGET=reference.rFP7XB.data,RESOL=AUTO,ACCURACY=10,GRID=.25/" ".25,PACKING=SIMPLE"; expand(text, expected); } CASE("test_metkit_expand_MARSC-221") { // https://jira.ecmwf.int/browse/MARSC-221 const char* text = "retrieve,accuracy=12,area=90.0/0.0/-90.0/359.5,date=20240102,domain=g,grid=0.5/" "0.5,leve=off,levtype=sfc,padding=0,param=134/137/165/166/167/168/" "235,stream=da,style=dissemination,time=00,type=an,target=\"reference.tzpUX7.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=OPER,EXPVER=0001,REPRES=GG,LEVTYPE=SFC,PARAM=134/137/165/166/167/168/" "235,DATE=20240102,TIME=0000,STEP=0,DOMAIN=G,TARGET=reference.tzpUX7.data,RESOL=AV,ACCURACY=12,STYLE=" "DISSEMINATION,AREA=90/0/-90/359.5,GRID=.5/.5,PADDING=0"; expand(text, expected); } CASE("test_metkit_expand_MARSC-212") { // https://jira.ecmwf.int/browse/MARSC-212 const char* text = "retrieve,accuracy=16,area=14.8/-19.6/-14.5/19.8,class=od,date=20230810,expver=1,grid=0.09/0.09,levelist=1/" "to/137,levtype=ml,number=-1,param=z,process=local,rotation=-78.8/-61.0,step=000,stream=scda,time=18," "type=an,target=\"reference.ect1qF.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=SCDA,EXPVER=0001,REPRES=SH,LEVTYPE=ML,LEVELIST=1/2/3/4/5/6/7/8/9/10/11/" "12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/" "47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/" "82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100/101/102/103/104/105/106/107/108/109/110/111/112/" "113/114/115/116/117/118/119/120/121/122/123/124/125/126/127/128/129/130/131/132/133/134/135/136/137," "PARAM=129,DATE=20230810,TIME=1800,STEP=0,DOMAIN=G,TARGET=reference.ect1qF.data,RESOL=AUTO,ACCURACY=16," "AREA=14.8/-19.6/-14.5/19.8,ROTATION=-78.8/-61,GRID=.09/.09,PROCESS=LOCAL"; expand(text, expected); } CASE("test_metkit_expand_MARSC-210") { // https://jira.ecmwf.int/browse/MARSC-210 const char* text = "retrieve,accuracy=24,area=90.0/-179.0/-90.0/180.0,class=od,dataset=none,date=20231231/to/20231231,expver=1," "grid=off,levelist=1,levtype=ml,number=off,padding=0,param=152.128,resol=255,step=00,stream=oper,time=00/12," "type=an,target=reference.data"; const char* expected = "RETRIEVE,DATASET=none,CLASS=OD,TYPE=AN,STREAM=OPER,EXPVER=0001,REPRES=SH,LEVTYPE=ML,LEVELIST=1,PARAM=152," "DATE=20231231,TIME=0000/1200,STEP=0,DOMAIN=G,TARGET=reference.data,RESOL=255,ACCURACY=24," "AREA=90/-179/-90/180,PADDING=0"; expand(text, expected); } CASE("test_metkit_expand_MARSC-220") { // https://jira.ecmwf.int/browse/MARSC-220 // the original request contains an_offet which is not supported, and has been replaced by anoffset const char* text = "retrieve,anoffset=9,class=rd,date=20210828,expver=i8k5,gaussian=regular,grid=4000,levtype=sfc,param=151." "128/165.128/166.128,step=15,stream=da,time=00,type=fc,target=\"reference.6Zr8N7.data\""; const char* expected = "RETRIEVE,CLASS=RD,TYPE=FC,STREAM=OPER,EXPVER=i8k5,REPRES=SH,LEVTYPE=SFC,PARAM=151/165/" "166,DATE=20210828,TIME=0000,STEP=15,DOMAIN=G,TARGET=reference.6Zr8N7.data,RESOL=AUTO,GRID=F4000"; expand(text, expected); } CASE("test_metkit_expand_MARSC-214") { // https://jira.ecmwf.int/browse/MARSC-214 const char* text = "retrieve,anoffset=90,class=ce,database=marser,date=20240102,domain=g,expver=0001,level=3,levtype=sol," "model=lisflood,origin=ecmf,param=260199,step=6/to/72/by/" "6,stream=efas,time=0000,type=sfo,expect=any,target=\"reference.43PsBL.data\""; const char* expected = "RETRIEVE,CLASS=CE,TYPE=SFO,STREAM=EFAS,EXPVER=0001,MODEL=lisflood,REPRES=SH,LEVTYPE=SOL,LEVELIST=3,PARAM=" "260199,DATE=20240102,TIME=0000,STEP=6/12/18/24/30/36/42/48/54/60/66/" "72,ANOFFSET=90,DOMAIN=G,ORIGIN=ECMF,EXPECT=ANY,TARGET=reference.43PsBL.data,DATABASE=marser"; expand(text, expected); } CASE("test_metkit_expand_MARSC-222") { // https://jira.ecmwf.int/browse/MARSC-222 const char* text = "retrieve,class=rd,date=20201204,expver=hk3a,obsgroup=22,reportype=21001,stream=lwda,time=1200,type=mfb," "target=\"reference.vYyJf6.data\""; /// @todo DUPLICATES const char* expected = "RETRIEVE,CLASS=RD,TYPE=MFB,STREAM=LWDA,EXPVER=hk3a,REPRES=BU,OBSGROUP=AMSUA_AS,REPORTYPE=21001,obstype=1," "DATE=20201204,TIME=1200,DOMAIN=G,TARGET=reference.vYyJf6.data,DUPLICATES=KEEP"; // expand(text, expected); } CASE("test_metkit_expand_MARSC-219") { // https://jira.ecmwf.int/browse/MARSC-219 const char* text = "retrieve,class=od,date=20231205,expver=0001,obstype=gpsro,stream=lwda,time=18,type=ai,target=\"reference." "E2RRc8.data\""; const char* expected = // "RETRIEVE,CLASS=OD,TYPE=AI,STREAM=LWDA,EXPVER=0001,REPRES=BU,OBSTYPE=250,DATE=20231205,TIME=1800,DOMAIN=G," // "TARGET=reference.E2RRc8.data,DUPLICATES=KEEP"; "RETRIEVE,CLASS=OD,TYPE=AI,STREAM=LWDA,EXPVER=0001,OBSTYPE=250,DATE=20231205,TIME=1800,DOMAIN=G," "TARGET=reference.E2RRc8.data,DUPLICATES=KEEP"; expand(text, expected); } CASE("test_metkit_expand_MARSC-213") { // https://jira.ecmwf.int/browse/MARSC-213 const char* text = "retrieve,class=od,date=20230821,expect=any,expver=0001,levtype=sfc,param=70.228/71.228/72.228/73.228/" "74.228,stream=da,time=12,type=gai,target=\"reference.1e7AY1.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=GAI,STREAM=OPER,EXPVER=0001,REPRES=GG,LEVTYPE=SFC,PARAM=228070/228071/228072/" "228073/228074,DATE=20230821,TIME=1200,STEP=0,DOMAIN=G,TARGET=reference.1e7AY1.data,EXPECT=ANY"; expand(text, expected); } CASE("test_metkit_expand_MARSC-211") { // https://jira.ecmwf.int/browse/MARSC-211 const char* text = "retrieve,class=ce,database=marser,date=20240102,domain=g,expver=0001,level=2,levtype=sol,model=lisflood," "origin=ecmf,param=260199,step=6/to/240/by/" "6,stream=efas,time=0000,type=fc,target=\"reference.JkqWoW.data\""; const char* expected = "RETRIEVE,CLASS=CE,TYPE=FC,STREAM=EFAS,EXPVER=0001,MODEL=lisflood,REPRES=SH,LEVTYPE=SOL,LEVELIST=2,PARAM=" "260199,DATE=20240102,TIME=0000,STEP=6/12/18/24/30/36/42/48/54/60/66/72/78/84/90/96/102/108/114/120/126/" "132/138/144/150/156/162/168/174/180/186/192/198/204/210/216/222/228/234/" "240,DOMAIN=G,ORIGIN=ECMF,TARGET=reference.JkqWoW.data"; expand(text, expected); } CASE("test_metkit_expand_MARSC-243") { // https://jira.ecmwf.int/browse/MARSC-243 const char* text = "retrieve,DOMAIN=G,LEVTYPE=PL,LEVELIST=200,DATE=20040114,TIME=0000,PARAM=155.128,CLASS=OD,TYPE=FCMEAN,STREAM=" "MOFM,EXPVER=0001,NUMBER=0,SYSTEM=2,METHOD=1,FCPERIOD=5-11,target=\"reference.GP8yZ1.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=FCMEAN,STREAM=MOFM,EXPVER=0001,REPRES=SH,LEVTYPE=PL,LEVELIST=200,PARAM=155,DATE=" "20040114,FCPERIOD=5-11,TIME=0000,NUMBER=0,DOMAIN=G,SYSTEM=2,METHOD=1,TARGET=reference.GP8yZ1.data"; expand(text, expected); } CASE("test_metkit_expand_MARSC-246") { // https://jira.ecmwf.int/browse/MARSC-246 const char* text = "retrieve,DOMAIN=G,LEVTYPE=DP,DATE=20120201,TIME=0000,STEP=288,PARAM=175.151,CLASS=OD,TYPE=OF,STREAM=OCEA," "EXPVER=0001,NUMBER=0,SYSTEM=3,METHOD=1,PRODUCT=TIMS,SECTION=Z,LEVELIST=0.000,LATITUDE=-9.967,RANGE=264,target=" "\"reference.t2APXu.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=OF,STREAM=OCEA,EXPVER=0001,REPRES=SH,LEVTYPE=DP,LEVELIST=0,PARAM=151175,DATE=20120201," "TIME=0000,RANGE=264,STEP=288,NUMBER=0,DOMAIN=G,SYSTEM=3,METHOD=1,PRODUCT=TIMS,SECTION=Z,LATITUDE=-9.967," "TARGET=reference.t2APXu.data"; expand(text, expected); } CASE("test_metkit_expand_MARSC-253") { // https://jira.ecmwf.int/browse/MARSC-253 const char* text = "retrieve,class=od,type=tf,stream=enfo,expver=0001,repres=bu,obstype=32,date=20060112,time=0000/" "1200,domain=g,duplicates=keep,target=\"data.reference\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=TF,STREAM=ENFO,EXPVER=0001,REPRES=BU,OBSTYPE=32,DATE=20060112,TIME=0000/" "1200,STEP=0,DOMAIN=G,TARGET=data.reference,DUPLICATES=KEEP"; expand(text, expected); } CASE("test_metkit_expand_MARSC-254") { // https://jira.ecmwf.int/browse/MARSC-254 const char* text = "retrieve,type=ob,date=20250414,time=00,range=1439,target=\"data.reference\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=OB,STREAM=OPER,EXPVER=0001,REPRES=BU,OBSTYPE=1,DATE=20250414,TIME=0000,RANGE=1439," "DOMAIN=G,TARGET=data.reference,DUPLICATES=KEEP"; expand(text, expected); } // https://jira.ecmwf.int/browse/MARSC-306 CASE("test_metkit_expand_MARSC-306") { const std::string text = "retrieve,CLASS=OD,TYPE=PF,STREAM=EF,EXPVER=0001,REPRES=SH,LEVTYPE=SFC,PARAM=167/165/166/164/" "228,DATE=20250523,TIME=0000,STEP=6/12,NUMBER=1/2,DOMAIN=G,RESOL=N128,AREA=70.5/-21.0/30.0/40.5,GRID=1.5/" "1.5,target=\"target.vareps.test\",RESOL=N128"; // N128 needs to be forwarded and is therefore not manipulated const std::string expected = "retrieve,class=od,type=pf,stream=enfo,levtype=sfc,date=20250523,time=0000,step=6/" "12,expver=0001,domain=g,number=1/2,param=167/165/166/164/228,target=target.vareps.test,grid=1.5/1.5,area=70.5/" "-21/30/40.5,repres=sh,resol=N128"; expand(text, expected); } // https://jira.ecmwf.int/browse/MARSC-433 CASE("test_metkit_expand_MARSC-433") { const std::string text = "retrieve,CLASS=EA,DATE=20061212,DOMAIN=G,EXPVER=0001,LEVELIST=1/2/" "3,LEVTYPE=ML,PARAM=152,REPRES=SH,RESOL=199,STREAM=DA,TIME=00/06/12/18,TYPE=AN"; const std::string expected = "retrieve,CLASS=EA,DATE=20061212,DOMAIN=G,EXPVER=0001,LEVELIST=1/2/" "3,LEVTYPE=ML,PARAM=152,REPRES=SH,truncation=199,STREAM=oper,TIME=0000/0600/1200/1800,TYPE=AN"; expand(text, expected); } CASE("test_metkit_expand_resol") { { const std::string text = "retrieve,class=ea,date=20061212,domain=g,expver=1,levelist=1/2/" "3,levtype=ml,param=152,repres=sh,resol=av,stream=da,time=00/06/12/18,type=an"; const std::string expected = "retrieve,class=ea,date=20061212,domain=g,expver=0001,levelist=1/2/" "3,levtype=ml,param=152,repres=sh,intgrid=source,truncation=none,stream=oper,time=0000/0600/1200/" "1800,type=an"; expand(text, expected); } { const std::string text = "retrieve,class=ea,date=20061213,domain=g,expver=1,levelist=1/2/" "3,levtype=ml,param=152,repres=sh,resol=N128,stream=da,time=00/06/12/18,type=an"; const std::string expected = "retrieve,class=ea,date=20061213,domain=g,expver=0001,levelist=1/2/" "3,levtype=ml,param=152,repres=sh,intgrid=N128,stream=oper,time=0000/0600/1200/1800,type=an"; expand(text, expected); } } CASE("test_metkit_expand_coeffindex") { { const char* text = "retrieve,class=rd,coeffindex=1/to/3,date=2026-01-01,expver=1,levelist=1,levtype=ml,param=130/131/132/138/" "139/152/155,step=0,number=1,stream=oper,time=09:00:00,type=me,target=output"; const char* expected = "retrieve,class=rd,coeffindex=1/2/3,date=20260101,expver=0001,levelist=1,levtype=ml,param=130/131/132/138/" "139/152/155,step=0,number=1,stream=oper,time=0900,type=me,target=output"; expand(text, expected); } { const char* text = "retrieve,class=od,coeffindex=1/to/3,date=2026-01-01,expver=1,levelist=1,levtype=ml,param=130/131/132/138/" "139/152/155,step=0,number=1,stream=oper,time=09:00:00,type=pf,target=output"; const char* expected = "retrieve,class=od,date=20260101,expver=0001,levelist=1,levtype=ml,param=130/131/132/138/139/152/" "155,step=0,number=1,stream=oper,time=0900,type=pf,target=output"; expand(text, expected); } } CASE("PGEN-566 class=ai") { const char* text = "retrieve,date=20260506,class=ai,stream=oper,expver=0001,domain=g,type=fc,levtype=sfc,param=sd/swh,time=0000/" "0600/1200/1800,step=0"; const char* expected = "retrieve,date=20260506,class=ai,type=fc,stream=oper,levtype=sfc,time=0000/0600/1200/" "1800,step=0,expver=0001,domain=g,param=228141/140229"; expand(text, expected); } CASE("PGEN-566 od") { const char* text = "retrieve,date=20260506,class=od,stream=oper,expver=0001,domain=g,type=fc,levtype=sfc,param=sd/swh,time=0000/" "0600/1200/1800,step=0"; const char* expected = "retrieve,date=20260506,class=od,type=fc,stream=oper,levtype=sfc,time=0000/0600/1200/" "1800,step=0,expver=0001,domain=g,param=141/3100"; expand(text, expected); } CASE("test_metkit_expand_A") { const char* text = "retrieve,accuracy=16,area=14.8/-19.6/-14.5/19.8,class=od,date=20230810,expver=1,grid=0.09/0.09,levelist=1/" "to/137,levtype=ml,number=-1,param=z,process=local,rotation=-78.8/" "-61.0,step=000,stream=scda,time=18,type=an,target=\"reference.ect1qF.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=SCDA,EXPVER=0001,REPRES=SH,LEVTYPE=ML,LEVELIST=1/2/3/4/5/6/7/8/9/10/11/" "12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/" "47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/" "82/83/84/85/86/87/88/89/90/91/92/93/94/95/96/97/98/99/100/101/102/103/104/105/106/107/108/109/110/111/112/" "113/114/115/116/117/118/119/120/121/122/123/124/125/126/127/128/129/130/131/132/133/134/135/136/" "137,PARAM=129,DATE=20230810,TIME=1800,STEP=0,DOMAIN=G,TARGET=reference.ect1qF.data,RESOL=AUTO,ACCURACY=16," "AREA=14.8/-19.6/-14.5/19.8,ROTATION=-78.8/-61,GRID=.09/.09,PROCESS=LOCAL"; expand(text, expected); } CASE("test_metkit_expand_B") { const char* text = "retrieve,accuracy=16,area=60.0/-60.0/-60.0/60.0,class=ea,date=20101029,expver=1,grid=0.30/0.30,levelist=1/" "to/137,levtype=ml,number=-1,param=q/t/u/v/lnsp/z,rotation=0.0/" "0.0,step=000,stream=oper,time=15:00:00,type=an,target=\"reference.1OEDK0.data\""; const char* expected = "RETRIEVE,CLASS=EA,TYPE=AN,STREAM=OPER,EXPVER=0001,LEVTYPE=ML,LEVELIST=1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/" "16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/47/48/49/50/" "51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/82/83/84/85/" "86/87/88/89/90/91/92/93/94/95/96/97/98/99/100/101/102/103/104/105/106/107/108/109/110/111/112/113/114/115/" "116/117/118/119/120/121/122/123/124/125/126/127/128/129/130/131/132/133/134/135/136/137,PARAM=133/130/131/" "132/152/129,TIME=1500,STEP=0,DOMAIN=G,TARGET=reference.1OEDK0.data,RESOL=AUTO,ACCURACY=16,AREA=60/-60/-60/" "60,ROTATION=0/0,GRID=.3/.3,DATE=20101029"; expand(text, expected); } CASE("test_metkit_expand_C") { const char* text = "retrieve,accuracy=12,area=90.0/0.0/-90.0/359.5,date=20240102,domain=g,grid=0.5/" "0.5,leve=off,levtype=sfc,padding=0,param=134/137/165/166/167/168/" "235,stream=da,style=dissemination,time=00,type=an,target=\"reference.tzpUX7.data\""; const char* expected = "RETRIEVE,CLASS=OD,TYPE=AN,STREAM=OPER,EXPVER=0001,REPRES=GG,LEVTYPE=SFC,PARAM=134/137/165/166/167/168/" "235,DATE=20240102,TIME=0000,STEP=0,DOMAIN=G,TARGET=reference.tzpUX7.data,RESOL=AV,ACCURACY=12,STYLE=" "DISSEMINATION,AREA=90/0/-90/359.5,GRID=.5/.5,PADDING=0"; expand(text, expected); } CASE("test_metkit_disseminate") { const char* text = "disseminate,option=normal,target=NTI:HT,date=20250506,time=0/12,stream=oper,levtype=sfc,type=an,param=lsm," "step=0,direction=off,frequency=off,area=54.5/1.5/51/8,grid=.1/.1,packing=simple\n" "disseminate,stream=wave,param=2dfd,direction=1/to/36,frequency=1/to/29,packing=off\n" "disseminate,type=fc,step=3/to/144/by/3\n" "disseminate,stream=oper,type=an,param=lsm,step=0,direction=off,frequency=off,area=64/-15/43/13," "grid=.25/.25,packing=simple\n" "disseminate,stream=wave,param=tmax/hmax/swh/mwd/pp1d/mwp/shww/mdww/mpww/shts/mdts/mpts/sh10\n" "disseminate,type=fc,step=3/to/72/by/3\n" "disseminate,step=78/to/240/by/6"; std::vector expected = { "disseminate,stream=oper,type=an,param=172,step=0,area=54.5/1.5/51/8,grid=.1/.1,packing=simple,class=od," "date=20250506,domain=g,expver=0001,levtype=sfc,option=normal,target=NTI:HT,time=0000/1200", "disseminate,stream=wave,param=140251,direction=1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/" "25/26/27/28/29/30/31/32/33/34/35/36,frequency=1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/" "25/26/27/28/29,area=54.5/1.5/51/8,class=od,date=20250506,domain=g,expver=0001,grid=.1/.1,levtype=sfc," "option=normal,step=0,target=NTI:HT,time=0000/1200,type=an", "disseminate,type=fc,step=3/6/9/12/15/18/21/24/27/30/33/36/39/42/45/48/51/54/57/60/63/66/69/72/75/78/81/84/87/" "90/93/96/99/102/105/108/111/114/117/120/123/126/129/132/135/138/141/144,area=54.5/1.5/51/" "8,class=od,date=20250506,direction=1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/" "29/30/31/32/33/34/35/36,domain=g,expver=0001,frequency=1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/" "22/23/24/25/26/27/28/29,grid=.1/.1,levtype=sfc,option=normal,param=140251,stream=wave,target=NTI:HT,time=0000/" "1200", "disseminate,stream=oper,type=an,param=172,step=0,area=64/-15/43/13,grid=.25/" ".25,packing=simple,class=od,date=20250506,domain=g,expver=0001,levtype=sfc,option=normal,target=NTI:HT,time=" "0000/1200", "disseminate,stream=wave,param=140217/140218/140229/140230/140231/140232/140234/140235/140236/140237/140238/" "140239/140120,area=64/-15/43/13,class=od,date=20250506,domain=g,expver=0001,grid=.25/" ".25,levtype=sfc,option=normal,packing=simple,step=0,target=NTI:HT,time=0000/1200,type=an", "disseminate,type=fc,step=3/6/9/12/15/18/21/24/27/30/33/36/39/42/45/48/51/54/57/60/63/66/69/72,area=64/-15/43/" "13,class=od,date=20250506,domain=g,expver=0001,grid=.25/" ".25,levtype=sfc,option=normal,packing=simple,param=140217/140218/140229/140230/140231/140232/140234/140235/" "140236/140237/140238/140239/140120,stream=wave,target=NTI:HT,time=0000/1200", "disseminate,step=78/84/90/96/102/108/114/120/126/132/138/144/150/156/162/168/174/180/186/192/198/204/210/216/" "222/228/234/240,area=64/-15/43/13,class=od,date=20250506,domain=g,expver=0001,grid=.25/" ".25,levtype=sfc,option=normal,packing=simple,param=140217/140218/140229/140230/140231/140232/140234/140235/" "140236/140237/140238/140239/140120,stream=wave,target=NTI:HT,time=0000/1200,type=fc"}; expand(text, expected, false); } CASE("test_metkit_disseminate") { const char* text = "disseminate,stream=waef,levtype=sfc,param=131074,class=od,type=ep,date=20250918,time=1200,step=12,expver=0001," "domain=g,area=90/-180/-90/179.6,grid=.4/.4,target=OPN:DT"; std::vector expected = {text}; expand(text, expected, false); } CASE("test_metkit_disseminate_params-static") { const char* text = "disseminate,target=CAM:TT,option=normal,expver=0001,class=mc,stream=oper,date=20250807,time=0000,type=an,step=" "0,levtype=sfc,param=asn/hcc/mcc/sp/t/tcc"; std::vector expected = { "disseminate,class=mc,type=an,stream=oper,levtype=sfc,time=0000,step=0,expver=0001,param=32/188/187/134/130/" "164,option=normal,target=CAM:TT,date=20250807,domain=g"}; expand(text, expected, false); } CASE("test_metkit_files") { eckit::LocalPathName testFolder{"expand"}; ASSERT(testFolder.exists()); eckit::StdDir d(testFolder); for (;;) { struct dirent* e = d.dirent(); if (e == nullptr) { break; } if (::strstr(e->d_name, ".req")) { try { // look for the corresponding .expected file std::string reqFileName{testFolder / e->d_name}; eckit::PathName expFileName{reqFileName.substr(0, reqFileName.find_last_of('.')) + ".expected"}; ASSERT(expFileName.exists()); std::ifstream reqFile(reqFileName); std::stringstream req; req << reqFile.rdbuf(); std::ifstream expFile(expFileName); std::stringstream exp; exp << expFile.rdbuf(); expand(req.str(), exp.str()); } catch (...) { std::cerr << "ERROR processing file: " << e->d_name << std::endl; throw; } } } } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return run_tests(argc, argv); } metkit-1.20.2/tests/test_integer_range.cc0000664000175000017500000001224115246725757020622 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_integer_day.cc /// @author Metin Cakircali /// @date March 2025 #include #include #include "eckit/testing/Test.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypeInteger.h" namespace metkit::mars::test { using ::eckit::ValueList; using ::eckit::ValueMap; //---------------------------------------------------------------------------------------------------------------------- CASE("Test TypeInteger expansion range=[1,100]") { ValueMap settings; settings["range"] = ValueList{1, 100}; TypeInteger type("day", settings); Type& tday = type; // in range for (int i = 1; i < 101; ++i) { auto num = std::to_string(i); std::string value = num; EXPECT(tday.expand(value)); EXPECT_EQUAL(value, num); } // out of range { std::string value = "0"; EXPECT(!tday.expand(value)); } { std::string value = "101"; EXPECT(!tday.expand(value)); } } //---------------------------------------------------------------------------------------------------------------------- CASE("Test TypeInteger expansion range=[1,1]") { ValueMap settings; settings["range"] = ValueList{1, 1}; TypeInteger type("day", settings); Type& tday = type; { std::string value = "1"; EXPECT(tday.expand(value)); EXPECT_EQUAL("1", value); } { std::string value = "2"; EXPECT(!tday.expand(value)); } } //---------------------------------------------------------------------------------------------------------------------- CASE("Test TypeInteger day expansion range=[-1,1]") { ValueMap settings; settings["range"] = ValueList{-1, 1}; TypeInteger type("day", settings); Type& tday = type; { std::string value = "-2"; EXPECT(!tday.expand(value)); } { std::string value = "-1"; EXPECT(tday.expand(value)); EXPECT_EQUAL("-1", value); } { std::string value = "0"; EXPECT(tday.expand(value)); EXPECT_EQUAL("0", value); } { std::string value = "1"; EXPECT(tday.expand(value)); EXPECT_EQUAL("1", value); } { std::string value = "2"; EXPECT(!tday.expand(value)); } } //---------------------------------------------------------------------------------------------------------------------- CASE("Test disseminate day expansion default by/1") { std::vector expected; for (int i = 1; i < 32; ++i) { expected.push_back(std::to_string(i)); } const auto* text = R"(disseminate, class = od, expver = 1, levtype = sfc, time = 0, stream = eefo, type = fcmean, param = sd/mn2t6/mx2t6/mtsfr/tcc/stl1/msdr/tprate/msl/rsn/2d/2t/10u/10v, step = 0-168/168-336/336-504/504-672, use = monday, day = 1/to/31, number = 0/to/10, area = 90/-180/-90/179.5, grid = .5/.5, packing = simple )"; auto request = MarsRequest::parse(text); auto days = request.values("day"); EXPECT_EQUAL(days, expected); } CASE("Test disseminate day expansion") { const auto expected = std::vector{"1", "3", "5", "7", "9", "11", "13", "15", "17", "19", "21", "23", "25", "27", "29", "31"}; const auto* text = R"(disseminate, class = od, expver = 1, levtype = sfc, time = 0, stream = eefo, type = fcmean, param = sd/mn2t6/mx2t6/mtsfr/tcc/stl1/msdr/tprate/msl/rsn/2d/2t/10u/10v, step = 0-168/168-336/336-504/504-672, use = monday, day = 1/to/31/by/2, number = 0/to/10, area = 90/-180/-90/179.5, grid = .5/.5, packing = simple )"; auto request = MarsRequest::parse(text); auto days = request.values("day"); EXPECT_EQUAL(days, expected); } CASE("Test disseminate day expansion fails outside range") { const auto* text = R"(disseminate, class = od, expver = 1, levtype = sfc, time = 0, day = 1/to/48, packing = simple )"; EXPECT_THROWS_AS(MarsRequest::parse(text), eckit::UserError); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_mars_language_strict.cc0000664000175000017500000000142115246725757022204 0ustar alastairalastair#include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" namespace metkit::mars::test { CASE("retrieve_best_match_param_not_matching") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching EXPECT_THROWS(language.bestMatch("param", {"parameter"}, false, false, true, {})); }; CASE("retrieve_best_match_param_matching") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching auto match = language.bestMatch("param", {"parameter", "param"}, false, false, true, {}); EXPECT(match == "param"); }; } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/regressions/0000775000175000017500000000000015246725757017006 5ustar alastairalastairmetkit-1.20.2/tests/regressions/METK-89/0000775000175000017500000000000015246725757020004 5ustar alastairalastairmetkit-1.20.2/tests/regressions/METK-89/METK-89.sh.in0000775000175000017500000000375115246725757021754 0ustar alastairalastair#!/usr/bin/env bash set -eux parse="$" srcdir=@CMAKE_CURRENT_SOURCE_DIR@ bindir=@CMAKE_CURRENT_BINARY_DIR@ wdir=$bindir/METK-89 export ECCODES_DEFINITION_PATH="@ECCODES_DEFINITION_PATH@" ### cleanup and prepare test cat > req < req < req < req.porcelain < req.json < list < content #cmp list content metkit-1.20.2/tests/regressions/METK-89/CMakeLists.txt0000664000175000017500000000027215246725757022545 0ustar alastairalastair if ( HAVE_GRIB AND HAVE_BUILD_TOOLS ) ecbuild_configure_file( METK-89.sh.in METK-89.sh @ONLY ) ecbuild_add_test( TYPE SCRIPT COMMAND METK-89.sh ) endif() metkit-1.20.2/tests/regressions/METK-103/0000775000175000017500000000000015246725757020047 5ustar alastairalastairmetkit-1.20.2/tests/regressions/METK-103/ok0000664000175000017500000107734015246725757020417 0ustar alastairalastairBUFRb 4~hd!^ jL!bFV%R .dEF F (!I.@@ @@ @` @`!Aa@!@A@a@@@@D!Aa!a!Aa!Aa!AaĂBb"Bb"Bb@B@b@@@@AA"ABAbAAAABCbCCCCDD"DBDbDDb€"Bb"Bb‚"Bbƒ"Bbb"Bb"Bb‚¢"BbÂâ"BbĂģc#Cc#Cc#Cc#Cc@c@@@@AA#ACAcAAAABB#BCBcBBBBCC#CCCcCCCCDD#DCDcDDDÀcÀ#CcÁ#CcÂ#CcÃ#Ccc#Cc#Ccƒ£#CcÃã#Ccăģd$Dd$Dd$Dd$Dd@d@@@@AA$ADAdAAAABB$BDBdBBBBCC$CDCdCCCCDD$DDDdDDDDdĀ$Ddā$DdĂ$Ddă$DdĄDd$Dd$Dd„¤$DdÄä$DdĄĤEe%Ee%Ee%Ee%Ee%@e@@@@AA%AEAeAAAABB%BEBeBBBBCC%CECeCCCCDD%DEDeDDDDEE%EEeEeUŀ%EeŁ%Eeł%EeŃ%Eeń%EUe%Ee%Ee…¥%EeÅå%Eeąĥ%Eeuu&Ff&Ff&Ff&Ff&Ff%%@@AA&AFAfAAAABB&BFBfBBBBCC&CFCfCCCCDD&DFDfDDDDEE&EFEfEEe&FfƁ&FfƂ&Ffƃ&FfƄ&Ffƥ&Ff&Ff†¦&FfÆæ&FfĆĦ&FfņŦ'Gg'Gg'Gg'Gg'Gg%W%gA'AGAgAAAABB'BGBgBBBBCC'CGCgCCCCDD'DGDgDDDDDDEEE'E7EGEWEgEwEEEEEEFFF'dddddddeee'e7eGeWegeweeeeeeefff'f7fGGgǁ'Ggǂ'Ggǃ'GWgwDŽׄ'7GWgwDžׅ'7GW'7GWgwǤפ'7GWgwǥץ'7GWgg'Gg‡§'GgÇç'7GWgwćėħķ'7GWgwŇŗŧŷ'7GWgw'7GWgw'7GWgw'7GWgwh(Hh(Hh(8HXhx(8HXhx(8HXhx#######$$$($8$H$X$h$x$$$$$$$$%%%(%8%H%X%h%x%%%%%%%%&&&(&8&H&X&h&x&&&&AAAABB(BHBhBBBBCC(CHChCxCCCCCCCCDDD(D8DHDXDhDxDDDDDDDDEEE(E8EHEXEhExEEEEEEEEFFF(F8FHFXFhFxFFFFFchcxccccccccddd(d8dHdXdhdxddddddddeee(e8eHeXehexeeeeeeeefff(f8fHfXfhfxffffff؁ȁ(HhȂ(HXhxȃ؃(8HXhxȄ؄(8HXhxȅ؅(8HXhxȆ؆8HXhxȣأ(8HXhxȤؤ(8HXhxȥإ(8HXhxȦئ(Hhˆ¨(8HXhxÈØèø(8HXhxĈĘĨĸ(8HXhxňŘŨŸ(8HXhxƈƘƨƸ((8HXhx(8HXhx(8HXhx(8HXhx(9 )Ii )9IYiy )9IYiy )9IYiy )9"""""""# ##)#9#I#Y#i#y########$ $$)$9$I$Y$i$y$$$$$$$$% %%)%9%I%Y%i%y%%%AAB B)BIBiByBBBBBBBBC CC)C9CICYCiCyCCCCCCCCD DD)D9DIDYDiDyDDDDDDDDE EE)E9EIEYEibibybbbbbbbbc cc)c9cIcYcicyccccccccd dd)d9dIdYdidydddddddde ee)e9Ɂ )Iiyɂق )9IYiyɃك )9IYiyɄل iyɢ٢ )9IYiyɣ٣ )9IYiyɤ٤)IYiy‰™©¹ )9IYiyÉÙéù )9IYiyĉęĩĹYiy )9IYiy )9IYiyz *:JZjz *:JZjz"""""""# ##*#:#J#Z#j#z########$ $$*$:$J$Z$j$z$$$$$BBBBBC CC*C:CJCZCjCzCCCCCCCCD DD*D:DJDZDjDzDDDDbbbc cc*c:cJcZcjczccccccccd dd*d:dJdZdjdzddd *:JZjzʃڃ *:JZjz *:JZjzʣڣ *:JZjz*:JZjzÊÚêú *:JZjzĊĚĪ:JZjz *:JZjzK[k{ +;K[k{#[#k#{########$ $$+$;$K$[$k${$$CkC{CCCCCCCCD DD+D;DKD[DkD{DDc{ccccccccd dd+d;dKd[dkd{dd˃ۃ +;K[k{ˣۣ +;K[k{û +;K[k{ċě +;K[k{ ,<L\l|##$ $$,$<$L$\$l$|$$$CD DD,DmB* XhX "n`7J,8](AD+h ¡>A(XH0@z"8&gAiT u$1< \|_h\M70)Be: fm"zS Qq U pZ/>RA O &dž8~Y~5Auqƚ &/PC(2&Ԃ1ܴ`DB\~PE(3^{D")Q݉c N}QбQ/7 i#gkq=;^'?P,P'tV `cDP>4P5DB/WP}:)1[ I]ϰƳ7M莫xx)ĈdpRD]Kpl=i6[\Ch#͡, 4Qɂ@#021[f /l f>r3AH`*q= aiÅ,ؠXOXE߂J (6+fhʋ`";*8!T| CV b@ݮRqK8B2п)e6ϬVqB #+Qbx1LQ+l+ׇ>ׂB  |`,Tp| 'E!U6 bMPap L_ >e4͍lm#v;pK=:zkABLа7OR"HH^I Q*""o~r,m/d^C8vV3q= Oll-n}G/:QWycNExAdMH1"!GbFg sv*A }pWT&!K 5`u9{YAb#jD'IA >SbjŦJ.aw La$!e~N4 nC8rV;Q4O|./BY\#M I("P IK"m|װ+*6AX{YzBe60p fE#F@7ĊZ8t#80G>~q 43!^1bȎ/ Vdg#00Ft6kA#hqSNJ=0=8t$ 4 #dpC&kčvI$$b >6J]9mE 6ؔc&!< L5&"aÉ hJb^\@@ *)@P WP:/Ōle5A26rC}8N=qʹz 1 nHnDM)R@d{v$HI%HK _5 'd20&j u{$; 0\n\z K?;A~&"&I("V#8xD(M'$VB½rֈ/q Nd(.s6L)4qCh=A72' H_CR%ct $y#9J\3Hq8 nHnze؇?Ԣ 8ȉi$"'ىtPMQx,ؤE@*]$XZX"1 hO#P F8`Îhu҃'MGɎ?%58\$(t!E6h錐ĕ%9,>}LÒlD p^) @{Jp%Hk _2jp9ߡܐO} !'숵GYH`'E dTI Pn.{ bW 2n(Ƒ5c1֍ocfi<&qu}` 0BtB0(ed# "IT܄Ғ');tI,갮|. gYTb(lYJhA>`(*abL ~_P@̠8az= >{|EB9;01Ĵ&A>*SBx,T-t``C,t&4za n}Z-L\;<;8{#q@H%(AP@_$a2 SrV2"at _pn`CD0?B +Ged$1& lM"upB<*1\ Z"zqȽ|4 o;Qx)[8>̶';@ ;$Sb,E-v!PbP2eyL!ց M@M@`/G! hh`B qa ! IFC}D'C% OtT*⮸7`3O $(A. Re­H41A =mMC,pH? Ȉ.\6#$ZLbqE3*vPr)h xIi$@҅t-S!w ,hdC@ܐݮ": !qdlH7OO倚H(PQI?h a xKdu@|.~! ]fECL@;,;,$Z!wHbUvހc $3 S¸лg28A# en"O )? ؉gf^$ &nMv_GXPR,CAo b/TU9D}}!1 G$Fj{,'MM ZBD3PKw5k1tN %Bۢ$p%a3܄'!E hX;ܞĕZ4 sósPdABS(%.)!Y 8]e6 @wڸ(VDbB00nL-azk ?eWE0,T; P2!ahHa=b WVn1U803m pCx|@BS yP7y-xy? 5eCC(P+&:A`!*dY `_C\ĆӨ8Iazf YA zXE!] T]C *0\7_Y`xCĈcDϡb _{08΀zPF Caˈ{-$Av  dQC=LÐ:y2V!x1;!: &hl(>`B(`u 5U!#, tCݐ"lBz CewFz .H;AmpK  8oD5&AtaýĐ2 y8o"tn?A =m@Ä?b ln[T?Zhs^E:!x(s)C vpd=@*b/ xR(<8)q D`W(@@@8| k!FH@9D`  pti '/A5. K O3`\i@'gРx,\ TЂ@`!7 K gh3 cs`\x/Du [)P=E$րEo!P@Y˒:0p׃~$=H5 +h?9@=1WAn _7X:,_ ؐUà '8 KHk8 =GTw{U`l209A @nʃl"84 ew!Ġ /x [YఐD!Іl*!%(@0~d Ь^8*aS XS`(AC O{ݴ&!3: KY^$va!tG=5<O"4Ar [xH,c Wj_*S S0 (B OxlτH&gA1 wpK%U!0 P`=.u p\hBߌېⅦ,e PXZ¾Ӏd*T T*`(aC> hkC?rih3 Hd"&Ʈ-11 -`|$/4aw H\,-f "Xᐭg4*ậ mCj; 5 Tj3MQў$3ᝓ fq/n]Gl1 ]biW@/A| x^*BjG`Ap:@[ts5Ö*<8Koyܛl6᳥ k[P4! g;Ն]2R `c$ĈtqsÇp7a0 mh!P5w =iqGB`w5Ba8~0Eӄ>g]|{|ؒVL@h;ܩըv#íhJprC`9ߡ"a@v#î"QLDb#=!a fCB`"f /A?"3 DL k?a}Dp =i[<yoǔ0 GB>jЎ҄sB#'Fb1m}XX"wDb#0D[PJSĜ$%h$hO9Bx:P@D'8(MkVMhD̶&I1kK]İ%h*cKLJ""OiHӄ${"HMb@+)KWDR"У(E &pPbc&4'?|O^yMx@'$Q8AhMk$K&ja2ɉLz"b?%/,uKZDj%mA+LN"v_'DF'-8ÜNo pRؠ&5G Mh3dd&@11 hL/"`uPEW*1Tn T,O?\)N:kS*#%)G 7QŠ}F$(aBPn`4'? pO{A_<'x;U ׈N"t9D'q8 \Mmh 4)1L! ]R&\)$HB,QBo@;(QE%%DQ"4sE (=A| \P#nh'Q>60O[yh!DH,Ia X t+q[ V"\*Uh T/pIH*7QN TbPE:)MkSC#ޅ-)XaJ_ OR\b@ (F 2QxBQZv(}C(Pbg+-!XĤV uuZ*QUU1b?ةELr*UR/ XTSE>0)NqpSr//)rK3V|R#)H>DQFF-dQjg MZ?"ЂxXe,Adr XfQ+A_jWds+\% ݤV"f:+&XƊLUboJX\*UB,UB6x{J*A1Q pT7b% E=h)QNqSxšx[,qf4 .Y[Ixŏ ,mqbTXlw,1_ (W"tJ+\1VBf:+%QXU"oPEX*UtLU.B@M*[RǠ^Bm.tP\@4E-ܡnm[R"жů-l!k TdZBӉд0-g ;Ybxۅ,Qdr pXbZ,&n,.`PX RPjEy^+q]vW.¸Džm+ZqZ$VmPVa&.s P\!EX.p [M -mahP[#BkPE-Pj(MZNhH,f2hYtO?Ej,sc XB4E,a`? W?H2xd0 p`IZ/~_ _=Bs[/?Qy\^6^.v \]uPM.|so\\E,.p6~0[bE-l cȹ.[1rT\wfE.oҋ{t[`]9\1 j c>C*.1c SbNu"1 ;a Yd40 "`C3Ph P0? `1/~F_YbؾE$/kzۋӼ^~_z/w ]i״.!u> ]#6Fe3! e#,[hʩFS2L @dC&r.>H29ёw d2 F=1ݡ qcpCXW1(1|XbF$1Qb @Tam0(01) &aC8 0H `@=o3p; ָf4P̼d3A e.qHXFYd2e;(C0FMz2bq dy# jFA2yc#51| a$b=(^4ˡ )$icG2@fF4Cҍ thHAHF31g<ۈΪFt&3!N Pf6vp?hn3:` Ȩf)#0~\21r em*N?P2xQR d$e Ԁ5 !=tiMkH(FH4c 'i-H@PІ&4\hzCf4Ō gƒ=Lv3q g7px~Fj03EЌfB#d*nF5at$kc[ب֦59 _jVxNF5=Ij6QF4䡦 3iKV04q@hCE(4*4h ?LH6Ј6yQlCd".6$܍l c_דp5- nHk_Z4(/FN5udWjCTƤ5 @iNoTd7ha ncs0ܧB7amnqbل6QmCiF 6na ldHج6QFk^g<5qhp1P<7o{xި7!bnvV7? xnAqYޘ6A mkYښv61.lÌ\(ⵇ8Ċ!0p3v X8Ia pQ# X+7D oC|(7Lncwo7FZrb`G'9-!FLrN}G8ڱƗ1qz!TXpZ8qp#)`80a8p"CJLA9ϟ{sC}69"fTsP弇,9ZʋQ1={޹cG=āl {P*ǯ=va[XzChמ>Q}.@>g|>Y~ qG?Iʹ~^(XEG>}_ 9@"אcG$?Qst#$pw@2 6D m ]H$@"܁D 8q @Q d% AĈ,A^b R肁d H#AB~#y!QHCIK44$' TJZ;)@p;  @@d5 4lwf5  p^_2 ہQ@( Ԁp 9Q@) h p;`d΀`x< `X]1pH H|PaP28 Eks:fLT,R +L @Rp:`>NF4T`+   h5@X5@}D"h jd2 HA`pG% (Fj_0p| ~eEAs&@Q@*  Ԁn:@h~X8S+ i~ `e84F&q`> 0 `^@]1P HG%L 9^Z. x]A@ @ԀH%P |x|j@8@4J=+0P)0 xԁypi(6ZK_28&+Q@+ @R{Ax"` ]ӂuET`,$ z`  xʀp=`0 0^ ^2LւH% ,y`ʀj7}K'  @ }@D%\ (xwj70&M(f ?Ls;t,W+ Op( p p;`@L]RK:`0[H0 HA΀ @,W, (Q@(  Ԁp:4KylP^1x ~π @@,W2J&#Q@)8  Ԁp9Hтb`0 |A  hX.Y@2H&#@Q@) ' Ԁp:@ 'i ]/ 0`൐ P`+ ݀E%P (xg`5  FAQZ- 19 `NX}K& F.}A0"h t@d5  vpE@W-P T p[K2`g@d@<L@,W.l `Nh( qd(4,P Ut@ @M@+ I%P   }D8$0$ ^Z/ Z hA `L܀}B% @}D%hP Jx@}A" QZ@. |`P& (ZЀd>`@ , |A`$ P DxyA"` WrAEW.` E<s ,d2  `pB%L `tp=H H~X8Wh-P @8p8@ d:% `@$W2H`d5l K2< up=+لÀh`5B }pH85"ÁAHJ$ClWs`4\v-0K2  `x&ˀ@K( 8 p;0lR+ ǪcA\]3 BX`](\pQ#d~E D$aAP`_NA8jew)(d70VH뀺XH>~#]M}8f Zzb<&NG2 `@[q>"`D `^Pb0D^)sCp)!{9PYsx-R<nxQZ|iKdbe<C 0y հڰN/0CY 0F${>!Uk|)CM&QWz_0Y!LM(ROf:\l&R FhTq(ɀ71 L !.(J1̄JsbHy"yK1S1{GƣY2>iEDVA7՗D+*fHR}JRܒumF̔"oCVM=(Krd#۵4d;Q-<|Ld=TI%;u,M-!IPq6)@g؈ r_pC^/D`=$&fݡrL=6} ŎiόE ռ3"0:9/A? Wo#)w2-TMcAhT?8OsRK0a2% `^@W+``@l2/xqq7A5rxV *Lw9X/{)KǠnOH]k5ZzJ֥ r?k&. Lo\m$DZ|أw f\_.,ǟsyh I$'* - 2H֡p 𖛴0)N&P[)ovPβ)nF˹[15a2.sKn(A0,h<+iP0--b4rrnN(e(4P@+"x#x T@rp[*e6tZ vA2޹~|C_4s5mܔר!H2ˬ-oݨ2I}!gP׺97i=/b8b aDt`Z 9]~;]Cn?7Ѱ>eiA(-5'< 1 V r+Jk&Bᨬzp¯,ܸ"dK2@ ` &!sZC1m}Pq˞x:Z)&C*7RGJǕ@Arhq8+h75ںT Z xGva;(8r)AKw۽ATe^;3 ۹ .~~WZ;'[@V6u*11%1Gz$!1Ca1l" Q4"lj 3DgTtPf.KS<b э9F@m^{ ^5Φ]Y0Sw8I 8ސRd(/@mgA'gTR<ēW2dH@k3\".W cI}BNVѠG\t8]n ȁS;W w|wFuDo;oG=cQq&Xj@"Q̂gstu*  x6[j DX[v!`=j$-Uk<^>Hǽ ncP4 Z@&դki0<[7tV oRHڠS-V&i(Q:hqB %ђY8]PɐM֦0jb1*εf̸O>'he~Tc)=jZpZߞo##A]2*%~y!AL#sK̡#^I4@r-H)`@Od%1םFj*2FK*;+xM&dCĪՇ$X䊙ٵƌacA<]Ums4MHa?䙡V DbթHk-{ IjbyPdV "爅ؘB/$ 8 <(CO`zC,f4ɝ e+%-pVbe()VwObD%1/QLfV!*A8D'0, v exéP<1h'\"a$RcF2ѓ hCcpH EREi3I"otUF".qe W(F~7<)h:j NZP L{$Rs'1J _C;(fIbྦ|8qکiL%hk^r4`L A"&x~0Ql&U fyAM:ֆ&8 0ܚR% 8! jCQ 0Q8D_෦J7PXЩhl%h3J&y4pm`H,a px@&AXl#!)HZT('Fhⴶ4ď3l]e^!4+1B 7 4|- ͠wt5j~Zx'A4 \nr0KOH$H҈tƆ; I-͌M\s+!AA+`1JH Pb!7 $e i" ZpHBH2uZFC6/CF0h@ f'!y,!\Ď(j̎nΡe$ VdWc"ƒ`jh``@ 0_%p ȂD,!pĘ))n̶pC!(JWYׁT# V L B\%!k D,qd)tLr#u-EYc@AHA XF 60.`Z%m Ѓ-!܆*{,tt%3|[;wC" PaT P @1@T/ `]!= jc"7Ja3_hNRf;!Їj0W /@0`|MB4_#mQ YiI҅Uy80ML "<`D 6L@#<$ G-0 pUN !2 Q8D'JJYpơ7-~T)餚,܇A<PʌěBWF}6n~!!Ks4L 8=^& PH2L! h%d v=&aE[St֧;qPbt; JUdYMvܠ XGQ=m(F<5dd`%A<|X&9O$d3TV7ůNts(yFNG}itM@(@ `}x:^舸Rφ;(Yn`/n>j Q,'aI Y\6.{l!?J*fT+!u kHJ{ E1MtР Ih"E}-~ pg^\' Pe8Sֵ 9R) MH"a[ hl&N HacFDrFX%n-Lk7DAR'+ g a-p%u O(ϖ5PuÐQ PQˆ:( SK8^ \K=RލdCjX dž}6NyC H\3=af뙆&x @@@B"|1(Ć'QT|cRx EBM|.aLl(BE:D-9ha/ܧ*LQ)OW6`5 )af gztGcD/ Np(~G"SrMEqLd#AgR @Y  Nº҇0> hX-,pL@e1ssȟ HL:{`80x _0HB9*qm 4hc{^GY. lc &ap@pl;0$, Xt!% THφ:ħ'YL\C ֧ ^#A(?~"a% UBX?#hL-Q n#Ip0! 5a X¤ $aGBF<, oC 1 HOt6xDW$6 :V2՗DbqCX0,!T XQ,+g d\8FBZ0 t $M"hBxC φ80!v pZB0 PpL 4{"#ɜQh@pb@͆P2 fCH h>bp)`cGA)< *`\9A nvP@yB*)]a2z?pj"dBЈDX#%OE1sC IkL"Vh$&TL"pb,u4f#W@ '= O`EP+i a#(P,<h%;+qY Uб.1| 2 gN݇:8##IvNxkC[ن7ǎw#bEr?09ώvCA\D&I= D=l|#0B"&); *T l%!HF;@'qDtVB0m#!,JapE)XJrT&9 Q$e-EڣU`bR_L;7bDZQFIY/t䟔Zջo/rAHM"TZQGJ)y6' AfcjMU=CrCȝ#SToB[Sw/È$UNۃlpY<c"Fi.'U VjbLݮW\`,2:_NAT)2כrΑ< d0*`Oq^ڛ|tIع#%La̩5&ٛs@]أz_L7WB_*R.^NEUID)tc3f؜zGu@*EXDnu忨Ud4JL!V,2_LA5&$ҊuSk uVۜP.= TʩS*e`e YLA]v*z"I6EPK L, ctB2)ec{=LE_.4[ӫ{F"Ǚ^# }!=%(cea=i&(%|#{P>M*UaB\}r(HYvHb x0p0Y :'el#|CMqe/Tk~/I9-Z !(#p¸a aA@,@diNaC);eYgsO:Cx( d AB aX1%H茓a Y?*%bXCnΥ(ЗdP!`C(x˜` GJe1h^ gt$Z"q) H p: A> ;N1$(szCN*h0fٜ̀P7JY9$,81&+`!īLs'J Y.6(ћ˭|P^&Iw<@0  )'EDpsm GIKuZxFY74Xx%$E`D,BhaDIA?*պ ttrx-ge$} `@ÈoBQ Q7'#Υ2a졠];{oBdϝtF֚`lq\ӴyϦs#|3v|ZcSkP$"86 p9B/"䮘#Rt'GQ5%2B_ E46\{xOqY}2hNH\&ۜ}JF-0]vڜvAX?paEOv 0"!' #(Љ"^ Y6'jCK)Cj"b5c{ #JvКKPzÙXoΙ?E#ĤUm&UmлD#b9U1 4֨i( |nndWQ=0*3,j aQrPKǩTzJ(ʖmL` e&rWvp_o^s}$F)#v#4_E6pQ)rU5*ekpq@s1:k.{)Bdq刑dZ8M"[K&S*8e=9_7\1/$lMFF3Alԇl"/3#y$u:`Cd:buR*0";d0 EV;ous=:vOkxX<Ia "IZ [HL<@s0頖Cl25cMںyXN0'%{a?i (}Q2^QmՐU~8Òc EpYnsz]oW٫Zk"6*Ng:#APDT#bR)z傇MX[dWIk6nojB6CaՎ x*}?C&1rk7Ng PK&& bZj0_x0LFSepEN7uzx\.838qdt<Ph~Ua5ȠfR*UҊdff4GZmF+EɊ^u;m)Vܭ(Xt-Xvd ǀ}?A?کJzmKTX0-dWR!#iѫj6|ۼN+;S(oI€JZ /v 9&e 9G'%I.RYaz]0ؙvoIQڡoM8\NiNZf m^0L¦TQbpHGY9 _"u"@#C&Pf{AEfJQ⮵Z .hWڬ=E5Y2m6\M.cWNh7lvDa7;oWyt=QIxHHX-h_&)s'Jk69G:ëنvݢ@0?("?m*6x^"34&/U%nVO)}}kEz-`Ơe-V{ɠv;{.u@>x "CBA`zfE'L }Fߥ&g> fθ_YϖcWѴjOq$9:֗pp<b4Bi᪑ذj># (cQ ȸDri-_dX]eXuL۷o8wu|A@(H\Z7 i$p|BO—X [/2>o6]Ai"nSnds0JYxDO1' k26x?ݡQS1PdZgW܌?G[6h֛mj7\kSc۴n`B #!9BGH c]5rh1SE0pRVkhEkRȒe{S6Y5ڋnK)$[UwL\Ku ABdճ CTq~`m7`[wӕBv^;7 B2 (BH!Xe<"J=H`^C]vRɵu*[Y-#+Ɇd3,cO* )RUf l?]X!,90!͕)gkZzm`i_p+\b?ӧ52]w6Q/CGݵAU!9Ht2O.N(6}p%O_Z.WS Wd3̍ Sٮ:Im7ZkuZ;]q|r_ka[Pt*1 "A!?p_3C E#RXar?XR*/u*нm⹒^)<2"mMg̴wmbƒipp6b@ aBbw6QCGݵ~^%Gd}G$gɂyC&SȪKpڻ^0VlR<{'ŘΔhg4u˛np8ܩrGLxmlPSY~rhFXe6}by˴mC8w&Ogs2 y F$ zqT*ui@ڣ{S<*X ՏJҙn^Mʘ4Bv4#~͓g4-fjI2ޓpF8\dA;׀{w9R/DwK~jeAwo=AW$F0 K&MS)zP:W ,8skdq|Na1c,d;m0Kj5[4F~Dr$.7e2yJOwv]!Xw?"GJEs"єPl?7ǤB PE#= QTm%L{*&RTAW, ")~b,!l?E+k";CD"=!8$K\&y.b)_,]90 ¦baBu< )d.b#.2I %US t*HTث ezܙsb;_Z0Jq^A*)M!' %.BI\[/AK~mcN1ߘr:B2BGerқ |RvA0tS*ByJ%Z9lʸ]|X ,,)IWP' yN̪~W,|ƋC!$jj6NgcXB"Qo甋zQ.LN([ZC[Y-l u:TM*Rt@aS1qyv<!&+*!Y@HRɞtZ~aD¥"SUJyoF+ oҼ1aZ{-X ѕJj6FɎ9H3XAQ` (qzD='LЧj&T:W,Vp)iе[`.R e Y$g4ͻgӖALz> _h\Dr-tI%. †HTܫUkixܽF_B0KN=+aTj6ӎ1>|~@[ èt\9m">KKV }M|]UV"'S.fN3PM^v (u5;wόG  @EE(@:eM8P(j]jazf3: Ch$rz:=G31-xCrQDP-fN(J<3Vޭ5Ǜ*M&v< *E7=\I32)O\(QTydF:4ގpb8Ԝgch}?ꠀPhF,,I:%;t{BS(2Ufg@dy=W(tB"F #0_I4ObԀ*fEFfx=(Ϭ4 LD\O,iM]Dj!šMQܫI>?vOz HD#IDRI-M'f*"#ڛ9RB@ Њ`=%BG$OkIVg 72P1( rJ 3C"VQ`ttA$̔dK2&RIrF8VSlHL,iTPHstXe6{O(iwJ]@dFuQ dS-M<*MUO#iD*S,L&ӮU J^&T*@5zY/{M6jڐ}LT)g"e}5|O '04e(QVC&3 $2w>QVTj}%M2ZVNM' ҏK)TpyY?6Q)(TkBW4HW|,0(j(M4T*ՕRqHLiSY,ꬥbZE^YD* /*a-]sX,` Ål:Y``Y-$V*Ձbe7ZѭÖ1K,թmz#Z˔Etֻ]/82RB \y(E;}.BRʩ_-6%Ĩ>\; 1@)e_"*`Pi;&(ENt`-WɔCR(sfVʋ;s"/&%\יM!*Ch"[ߡt2Nsd3qPCvy5b2iP̊^-d­S8iM8J) ´vвcCh{"@LҊ)IKD+<Šغ.sf 2!7MGP?xF>4a 6jP|R/B(g-f!$D؉A;)E$TFCb\ɍ3D|su<{,6 :AL e!:̈j&E"WO>Jna^+-ŹJlPMS9ga8~#݌}J@*u!1D-!3De"Ĥ]n@љA!&h"b7̻63xXy*?53℻ CH "$VmQ2FhRT$OfJcr.<ܚFcoMa0; 9^PDZ BV(\ !EȉK,qE}(j$qQ1 #?n`LY6GDm3z}ϒ@$.P#D(=$g,IJ:G z!IT-Jtȩ,e@p=oپ<JD Ў3CHo"XR5Gh#ԂАՒ)F+D֓(b\XKax44F$TPzH@j*i!q9*]0 *ETztCQRAH~#$5pt$H!7hM1ċ:t'qtJ;RC"_ᑷ:G!$ITt3ULI?e(&6C1&;еg= l6a&<8jtN#]EQ#^s6BH$$ԓSJ)WH,g1p7>G(XSShraN?Hšcd; Sj/F?#Ԇ`EK&I)=(4$蕤ZY{KYIp8E<89.;*̺Sx/OylAiVE"]X쌌Q;Hi#uLRRSJ ],sԷT_L-2>O( ."% !TktM:ATAQ`"xt]VQ?H)%$pҰbXK?n.4za2cLɘ 3w4DHajCn8uO~jeBXH}Uӣ!rR%I,IB)%ij_0LIu1&Qt̪Di}MOIU6&tڨST NXb1raxq2?d@ ˄m$*JI)N+% cjLI4@SemMϩ98' DSjs%N ׼ aB1t7hǬ"4bH;12MEJb)_=-%tB.7hMT 6ߢ9rNmq:f'U SwOI5=fxaP8b^Ldi4&ܸs%Nխ;4'tD/yON='F"B}FO ?pl0 9褡%F%<Γ}ZsNIYꅡPw?S*waN9ಛSmlMq)e4ȦϊS/eL1NՔ{V W*ԵXUU!YS*oLl(T~ʍQw*'CaTIO)e=^TSJtNpE8ʛSkjlMvAX &_o'M11 f(W'R۪Z+6d%,;VّZ?epUۊVEWPriUUS*nLrU)ҤTjQ*-Dʨ ,T*P)? TZSJxiN):G*DX/_vZtVV Y.dDZ[UЊV*W2QU ʡSuM.`T R*9Fv2.T:*%P A:JS!*A Ugly4uZp *aZFig̵ !4ĴX`K-_+yҺ4WjZNgURZVj W~5[08aʫU_JT*Q=DTتaRXJR0U#z Tw*Q*,%Dਈ[:\!j7u*]Z}XKb&,+e7W\ޫeo\ZVj̵Y(eaU{2UVfWTŕTU/ETL*Oz7T*QR*QI!lmvjPBCBej0뎻AJ*\J|n-ε"Y«0qe,uZ•X& _SExή::W j݅[=+YiU}V8IX.^2o*KUUڪUNzU!yT#*}O6nT+-{\'E;:$\&kyMmҭ%(ЇYk3Ee%䱛(bçXLk_5{`*bW8jE[qlbUVjjaX+a>U{U*)V*ͅXʪ[«UTJT誗ER".UBQu0W9:1[ ;If fF z^ov .̸:ݩ[gckeKed7xbeYw(dd,u5+BX"_wyԮؒ&W*[+nlFZVkYa̬~ʯPUeVZ$UaBUkUKFsbVKC"Hcp U0V j$j/^av.'W|[ kl]-kVυY1ey5hV,mXh`,~*钼FWlQ\+%pd VJZ7EeʬUjDVW*]īUp UJV/`s_ؑvNd6RV0Z1=WflA1f3|v `~`ꆼxu]똍q.彤+ԒؓZ֫S]iгg:ˍYE #cҬgŊ X!+u_EzƯ"WD \P+nvtfk I VqOYSS#c `uf? _/ ykUʺ;\Tko$-ĕVRZkGh"쥚VbɓY c ,Pu V4A&!B`1K]}밼WE]esܮ[Uǂ{@|u/^9ͻwhB6Gq%a fX,ZcMh٭a3D]e$sck\( &k_+|/fȼ]y][kt78 t.rYV>ݮqek[崑0ٸC1e,s4Ōؓ;aike ^w=8N+t.o18FxgEkb4Y̳4>e }2:ZOثsal,Y0Mvؿ^396;W\(\ӑqM6ЮGSim٠3vmmhC(d̀ )fOo ` ]/3stch=ȋ iulOn4񶒊9fl2kElDzXkrbC0&z:ctBde8'.>jwlwMtMu6RgK;^fЌJr\[&bՌM&ec]Cr " d7f&^k-JA~4yc4Ce ݲ>F@\/S b]n 9 @Syn_ uIfh,u3fdRP&KdT|1NVۏq/7;vVٿ[ [KjA}c:Y4Tf MI2OvCljӖq!ۄ?bk\Ͷ:flɕ<2&NZkܲTq O; ]kMm648*[8]f,avW+C ouζ|׼Z+Ti-)d4'&|Ύk2sV n8PkpMmX@[ZfHdhE3g7HuۈqpJqQ7ْ k\2jKI"lq!BVLk[bg:lDuAVAj;CIrn9F,P盒;m m6fCgHr.9F/6[3mm͐u5F<ԗr+qN"u7vt[il- |Dqcpl-e7@ތڣ[/akck99,/k~wo/u6͌؜ZcZrm8-g8n$D"q*N<'o-IŒ8 R"|p,@:_;+-|nWJ;_z_'|Guᄑ׹z^kzXꂽ:WO^ky1垼ג ^6+ŽxR +_zk}澆k_"k|1f׸^MzݯV5*2W#^z%y/^W^2+=x W _v+e}{{̺?_=|}׷^ǫםzǯT'Wz^vͥy,W^-ĵxu W_p}jJsW˪_k| {ν׵i^kzR钽סz^ry}*¼׎^)5xeWa_+}RW_)k|M5ֽ׹^ҫzYꂽ=W:O^ky1嶼W ^6+ŽxRׂ:;_+}ȯW*_.}|c.׻:^+٥{\UڽHW{^υyʯ4uדj^;Exᖼ"W:%^km~6UzWj_K|֯U)Wj^{viU쒽|W*M^ҝz-@炼י:^Q-xKWZ^ xWJ_g+}D`WJ_+{w2W^+zM5Wj^jky\&VwWY^!-xDW_m]}_ kWʪ_5{yW I^֕zOfWj^nk̝yr)䖼W{^'+xZ5b*_sk }u5vxZ5_|}UWj^+EzR龽$W^sMy,U׏J^++]xj 5ࢼ~+_x}ξͺ__!+|,RW^kzׯUu/W*^yky.UFאJ^0x{ U W^%5}u;_ך1^`˕y\)בZ ^=+xͯⲼNW^xI5ࢼ Wz]+wˮ»_eU}>U]W_ ]{߯vu׳J^ezTB=WJ^k=z@5¼Wi^hmy},^W A^Cx"[׊z-^!íxe ׂ:%^wz5]zZUש ^zHFU芽Wj^sy3"W*^N]y Ҽq׍:^++xu᪼-Ws^ +x ߲W}]+-wbW _J+|Я&^k{viUҽW^ezTB=W^kmz@¼Wi^h̝y}-^WjA^Cx"[׊z-^!íxe ׂ:%^뿽wz?]{Ug쾽װ:^=zSu:W^+z?u疼כ]^f+Eyx,UFג5^Bxu YW#^ +Åx_ W]+w5zZ)]k}rW%_9|uWJq^+۵{`j꽒W^֕zTB?צ^mz@¼Ws^h̝y-rWjK^Ekx6^׊-^!íxe ׂ1^뿽w.{ ?]%ws׸^-{/c5y׭^kzN钽'WO^ky:W^^+=yQ(5¼W^:kx¯U↼H׈ ^}x>v W*]kwƮޖWx]۫}ޯUW_5U|~UWq^ܕ{|lBWJ^EzүWꮽJק^kz2C2W ^nkMy0uʼWw^Ikȭyzf׋Y^'+]x{ Uj$׃Q^+=wr| a]뼥w5ݢt_ k|}f׻:;^ۍ{[h쾽W^kezT*=WJ^k=z@5¼WJi^gmy},^W A^Cx"[׊z-^!íxe ׂ:%^wz5]wsubJ_g+}D`WJ_+|\&3Eȱm*\6!{ā)L #ju'0 ajb&OB&0((B tl8XIBVYgYt]!Y" QR<-ڀ@2Sbz@4Zv o?b*Д 3fn BX#Zo@{q 1 bp&U"@~f,waVڡA1K^0@݀;(AVN@/ \!8& C3@-p`H|cmr@0n>UXL$[vn wZx ZWU((EYQ@06< `ݐ+|> Kpk #R`xG#ZNȺ.g ֢[`p<"ᜈoe@۱eS]p `??I1t xl' ;-@ ;"c~j&! C~Z0#@BS#0㼈o;aȈRzgm!2=d_\s]/0 0 p$6 ~{.0`a q?#(p2~ "Ђ$3Ma}o!\P{bNc`i;X ȷ6], t؂r X"](#q G.yXFq#~4 cƕ4$jl:a? p جAVN!)@8n #} @* Gi g9DSLOSU\^W<6ԣ+)x#{FY&pSG]#: w- *B C-p:c`<dAT JY@7ti&D#b09 (npl5ÆցZpc> < *W8*i t<Ԡ[T _Z@ ^h,h@EUt@1H f>hJD%D\X$-@{8mX}،ݚ1/``N4J`3 @H#Ed1k}PI` Ko;f4o #EhWY$$&Ch#^T8 ) gXnF$Vڡ-zBڤޗQ0*` )|2\`q`S{EEL7  3`#0da]@qBYT&"PAnz'$Юl%>W^eoar6 w(!<w_h,6I K@< K0=$i8π]W@') p0Dx%W OS/ۈd-Pڢ? kAp8~x 5O`R +t:zV;d1p K}I j*6@ ;8(4I;Dyu6nW<cP'1[YJhXejH`P1xR p7@xXx+?L i:瀕!j2؀8 p}#hD&pH&zq?FXڱWCfTFad&OԐG#hcx X;!80>$ |J@x(J#`;F h^.u{ lI9-~#tF}vUq! DKD6 lB]!!U1@i%i>R?. } [$e Zzv3 ,C /UkXćAFJ\J1 dC[{%LAzF iQ@}0"? \$сO-`30M v&YnXЀS$JDdeC%D1,@luCp<>Cl=pMCTpit<<]Cj'tq̕iǕ02gp.8<s|F>`R|Cz;Sè]p: csSC"9fOUIC9%FCÑLP09?Ï&<8E6+Ì% uʠ0*GW #B܄p6/= `ʄ0U HC82 *p˃3 1C8P3 CD0 49 0cCO 0L\55m WCVp 5k lxC[.3E nIii%Rh28B/e_Cכ\L!BMP%! B~p') =¹\, aY/ H\0j %=C Pë,1k L1 pW;023 OC&G0=t2 ,˃- XQ_e. yBQ. B1\/7 Cup0L C30 CG)P|1Z_ ^C铊KCW10򟫸M2Z>m/hű;@P<E{. !9pP0 % zB~p) ^VB|0nj*m PBh+k jB, @B3е- xB᰹5<.x; /O {Bо+/7 p\0)B.,pD$/ )XRЖ\&#U p't' ‰簣<)R iŸ0 4* PB#p+? L, 4B-дt-aG d۶pD.; 簺<.o Hpi:Jϥ#V辕ܓ5p3L+hPcD-3<pyl;9B ;p,T!)1dpmD$ Y )XQOp4& m4'i B( QvB1d*)M }«*,+/ ¹갯Y, JBʐ^L, B{p(31 ѿ& {/G@1[kd@-w @A4:\PRz!s`9%d+0kӄA͝0uD;0~Ud #B!!IB+9jD#peBAnj$# D+BU\% BgM& Z+h0"Kt-lC}OJ啣:d~hؽeYER۹{r?HpUl@V;T2$ ŞApE"J)BTJ4x0amlA0ujLN}MB `T!U~B%RpD# g; P <$S & N\0%sa lB_>/t ?eoG|P@^|Q{@F2| AA:DT=S q_ItM0j HAPstQt{.C/p RBL"Ys0 Il#Q6CP $# @-mKÕ 軴Y-~:mn*[Y=s{͌F};/.26M]+ňCq6 p 4)4 3;7;2Kt1SpW\A>pc e#0lÅutAP|-B,! X"B0"]B/p#qAуP8`CoT됶?+o?o3vr0sV_p,A<@0,4 <N@bph ȀʱK?ogc JiѿK\,5) s7@09!P6W.Cx>gDnHR0w˳RM<;o T2@=PC=}ƒNzZSY"?)/x~@Oī@H0M @U0S-.D]۹c 3N-'rKT'|u6z%m{y-8|NG#<:gβDUj;pS"/ϖAc |>gﺙk6ܿHd ?oc32lX=j#5 Jg{bF7<-~~9b˞; %};# F<Hxk&=y$>bhϡsl6>OQ?c$`4SJz5 -xm.E 7Lv#u9y3q:;2MobF?==oNs@w6!};lUOJ>,[\kN *զ͛ nݗ7NC}|x!sA:z{BCOH{@=/vD8>Q>/KoϽjcH^D{"h17y9m3sc9 `BShR:.Hݻ!kr-hO5бbje(FzB}fS2.63gq2$I%L9mNuȣ]:$׃tݛ/X1f=4Z>P+/jc7(6>OkQv{b6Rs ׳s/4tR J]9(t{q RncD7;>f Im)gsgH#;x8yNJ!{H'!*UqF;@)ֶ1ɫ:2]<J{IBԽ/sbhA[s3 :NKӗ>A$۪߮˦4.ܶl率 cȜUE[D]E pݽ37΄ +Wo?/:`BSfaDΩ㮔쨪;gRG! >7 l:W ƈcL<3ȏ=Lnmd;8Dn;C=9hNU7:[zG1z?M;{Y.#8Y*]F\y.s++"DGγW+}X;w;@ <>mm|ZXd,[ a's; Ӱ ;nKnC~7ԯ+NQ9"ΓIèr :îZ#_aKM#撸nA;=9[NvT벫>vmXÃj8CcGm : μòH[8#(G9#nUN&I: .kS:ǨL.6ji9tSX:}n m|ec 9 W S: .zCn:n ønIS/x9"[QǺ. #;K"xNMfj}.ų{P9ki B#:D!ˌ? 8Y.\qs:nC9=n],X{:in4t9yBkuK0Hq_{gNW`dDBO湆fV3`Zk%&%F0]&f!&af浉ɒ։)F^%f5%f2&ZFf漧'ǥQ),IHYiɃI !*I 彆0fH'Ii&F'.B'ZȇRۈC{Hȹ Yi]e* AceX{fX&FCFqg` hbhH踨)eɄ) iJ 5ʆNeSfF_g ffeƤF g穧皧i ȢHeH)G))i &eeffJ E'o['!GCUG~蚈(i .bi śvEffD-e>'mbG@3bGǞw(hM(Ih'*iiCv)FOg[&2JftiK^GSGGGg (Yk{h)?(Щ钩Ffݦ6G%&fF&X?]GGh9(Rh+2HȾI\)절dFI9d62tFdƹfg^' FG!YtGggǡD(I .1i )@~DեyFgV&eܦӇƇf'+8kǠGH G7 :)I iFd^ HNE'{]fF֦g ;Gʇ( G"(ȹH+AF'DHDDŽ%1fkFg&FƲF&GJ%BgyGG' (Ha4HGȳh ԫPVeÆ2'JpfƯf ƩFgDžGvg%((BCIH_(pH+L6%EڅhEfƑ&k&g&2gX/zgH g$HnhZH) wm lYE%'r$2f&'g'CZ'gG(po(H j9fRƩfwWFƂƯFFgg4ǒzgHH (okȃHHi" ʺL^ ; ]hb%Ƥ&3E&NFfF|ffg/Y'(glHϨȮH)I: ,,u[ f.?Df'&F~Fg|ǩш '2Jy )Q` *:ˍ,挊!ƗFF(FyfƜf f'@.'4'dG]'G(8ehh 6o @grf*ofHf;J'1f&F&ކ'<n_mM-Mmmm M-N3.@?n2*N0CV]nUC,nn-- &nDaxNΒNΜNίλJjl{9?._bjnm--m.1./N.NN#Oέ.N.Vn̎NyI=eΡHJo-N6qNێn.nNo<߮Nt-M,, T PNNcN*EONΠ..^Pnt+M Y2kN.ξNŽinN M,l,,mx--8.M׎-.¯ONni{.΢. KNwN.-.yn .U ୸ % z ͍ H i8n΀.7Lͺ~.N&mN A<1 N>.-um?mkMν.nB.Ȏ-mnz-nΚm# -ݍ :M;΋/AHG5N-N4mxn/nHM=N.&p ҮA.Dm mx./NO(n ȭ-- Wxg;NE.9Un.n U, N®.jNc mMD)ͅM0NuNM k [ nn͑YMomzCMX JM_M˭rA 3 m> 5m\-!.Kek.nk1Vt=Z Z)Rȓ@ E"U{8 %z>b,I41e.l7GL5$; E,Axb2q@AFF% "`nG&R@? q|: ap:"!N)Wtd,ѰR$pX, l:a@LF24;D `* A L( !|B# u(e+CH ax8P* N_'IdA "0d(X,  X0BW\A^'0x0 @( Ba`l< nz>:$1L P P0  aPl `0`@( a`l"z<:D1@  P@  \4FL>$ؼF `AP.bQ WP#D5PX4  aL%j4qY%@L @@0( Bpt@# Kb)±\  @@0( a`l X^0L( !t>  PZ/:Dk-HAP4P X0! P* CAx>! qHX.FcA  `4BA0T, a|@"DHX. FcAnFLXh*¡`$p8  x@$ Bpd4CH%Bh`2FÁ A@`4a@X. ax@!Ģa8T,!n8K$ f+Q`: P( `h8a@X. a|@!Ăa8T+ j74 h4BA@X. ax> bA(P) f4 #~@c+(cJ`h, Bx8H& h6F$PZ.FC1p9 . A D B! P* Ap:"!L' EB`^0ƃQr;V%Ї3XA 0p4 PL& a@X. ax> DbA(N(bp`1 ƣat;G!p4 `T* h6F$HV- h6#~@!Ȅb9%TGXDł8F!áh2 pd4CB"a8T+ Ed3 Fv=$"H$Rr5 ł@J#l6 Ap:"!J&"XZ.C1l7GC~@ ȄR9 K& U&8 %R,CQZ+A@Ct:"!H% PX-#!j6#1W)I$Rt8hR' b!@F$HV,]h#h1%By,D cd0 B8J$B!H%HT+ EZv8\N%Ȅt7"hT) a0L&PV,NF瓙c/Ji(D!ǣh2XT)E"PV, E=>&1X*IIJAACd1 hZ-E{IzU(S)$Zy92lT(d1?#f3 F#`0֪q*D cb/RCp7XTI9>&I_.JB8J$2~>GbN#ye1 `U**"y0G"Ps7 &c!^.%BKȔ*z;Qd1KZ@K$Ȥ2z;&af2"}4I#Hdx:aҳViY$E!Gu9MuPO&j) @OglV)IU"E!Ј#Zjeu2Hh2\*:JR!\,ʕ*:K$(3xY+U*U u4JLVlVT$q4d륲ZS(-6k!]-JZd0˥`h5pk4Y#zjY3ig7mv@ @   @ A @ H  @@H$ @  C@$ @  !2 C!$ @!@21 F#B@ B D"`$ D F`0D"B!J, D !`1"B!DH$@!D  "@!D@ DA H, B@"@!"@ DA H%@" B@ B!" J%DQ(H$!D0@HB "J$ DA J$ D,BH"B! DA(H%DQB!D \( J% DA(J%B E@T"@ P A(H% DA@!D"@ E!@P @J% A H%@!D@! @T*!P0%DA(@ B @ D"H *CШT  D@!"@!D@ D"H R A T( B B @ "B)A(B!@ @!"@!DB  A (B!DP!DB!D"@ @ E@ @D0!D@!D"@!D@ D"@$@""@!D"@!"B!"@$B!@@GB!"@!"@!DB!D"@(D"@ D"B!D"@  @@D00D"@!D"B!DB B!B!H @!DB!B!B!R @@ #@!@ D"B!B!B!@ H!D"B!D"B B!"@ @ E H $b1 DB!D"B D"B!D"B!D"@!D@ "H!@!DB!"@ D"@ @   H0 "`0D@ B!@ D"@ D"@ "$D@ "B!@ D"@ "@   @ @b!"B!@!DB!B!@C#@ D"B D"B!B "B    !D0 E@ D"B!@ D"@!@ @ A H$D" E"B D"B!"@ D@ @ A @ A H$ @P( D BHP!D@ D"B!"@ H$ A@ E@ DB "BA H  @@@ @"@ P(@!DBH$@ 4 D"B!D  "B!@ H$ @B!D"@ @$R)"@!AH@P 0@ !D" H@!DH$ @ (@ B!AB AH @h4@B!D @ P( A@ @h@B!D @$( @@(@@ B!D@ @$ A @BD @ $ A @(@BD @ H$ P  @ "!@ A  @D"H P  `0 P  @d@@("H P@ D" @ B! @  @0@@ @ @P    @P    P  @ $P X P H,P @@(A    A  A`4 7777BUFRUb 4~hd!]}L!bV@.dEF FUL (!I@@@ @ @@ @@`a!!AAaa@!@!@A@A@a@a@@@@@@!!AAaa!!AAaa!!aaāĂBBbb""BBbb""BBbb@B@B@b@b@@@@@@@@AAA"A"ABABAbAbAAAACCDDD"D"DBDBDbDbDDDDbb€€""BBbbbbƒƒ""BBbbbb""BBbb""BBbb‚‚¢¢""BBbbÂÂââ""BBbbĂĂĢģcc##CCcc##CCcc##CCcc##CCcc@c@c@@@@@@@@AAA#A#ACACAcAcAAAAAAAABBB#B#BCBCBcBcBBBBBBBBCCC#C#CCCCCcCcCCCCCCCCDDD#D#DCDCDcDcDDDDDDÀÀÀ##CCccÁÁ##CCccÂÂ##CCccÃÃ##CCccÄ##CCcc##CCccƒƒ££##CCccÃÃãã##CCccăăģģ$$DDdd$$DDdd$$DDdd$$DDdd@d@d@@@@@@@@AAA$A$ADADAdAdAAAAAAAABBB$B$BDBDBdBdBBBBBBBBCCC$C$CDCDCdCdCCCCCCCCDDD$D$DDDDDdDdDDDDDDDDddĀĀ$$DDddāā$$DDddĂĂ$$DDddăă$$DDddĄĄdd$$DDdd$$DDdd„„¤¤$$DDddÄÄää$$DDddĄĄĤĤEEee%%EEee%%EEee%%EEee%%EEee%%@E@E@e@e@@@@@@@@AAA%A%AEAEAeAeAAAAAAAABBB%B%BEBEBeBeBBBBBBBBCCC%C%CECECeCeCCCCCCCCDDD%D%DEDEDeDeDDDDDDDDEEE%E%eEeEeeŀŀ%%EEeeŁŁ%%EEeełł%%EEeeŃŃ%%EEeeńń%%EEUUEEUU%%EEee%%EEee……¥¥%%EEeeÅÅåå%%EEeeąąĥĥ%%EEUUeeeeuv&&FFff&&FFff&&FFff&&FFff&&FFffvv%v%v%%@@@@AAA&A&AFAFAfAfAAAAAAAABBB&B&BFBFBfBfBBBBBBBBCCC&C&CFCFCfCfCCCCCCCCDDD&D&DFDFDfDfDDDDDDDDEEE&E&EFEFEfEfEEEEeeeeƀƀ&&FFffƁƁ&&FFffƂƂ&&FFffƃƃ&&FFffƄƄ&&FFffƥ&&FFff&&FFff††¦¦&&FFffÆÆææ&&FFffĆĆĦĦ&&FFffņņŦŦ''GGgg''GGgg''GGgg''GGgg''GGgg&&A'A'AGAGAgAgAAAAAAAABBB'B'BGBGBgBgBBBBBBBBCCC'C'CGCGCgCgCCCCCCCCDDD'D'DGDGDgDgDDDDDDDDEEE'E'EGEGEgEgEEEEEEEEFFeWeWegeg''GGggǁǁ''GGggǂǂ''GGggǃǃ''GGggDŽDŽׄׄ''77GGWWggwwDžDž''77ǤǤפפ''77GGWWggwwץץ''77GGGGgg''GGgg‡‡§§''GGggÇÇçç''GGWWggwwććėėħħķķ''77GGWWggwwŇŇŗŗŧŧŷŷ''77GGWW''77GGWWggww''77GGWWggww''77GGWWghhh((HHhh((HHhh((88HHXXhhxx((88HHXXhhxx((88HHXXhhxx########$$$$$($($8$8$H$H$X$X$h$h$x$x$$$$$$$$$$$$$$$$%%%%%(%(%8%8%H%H%X%X%h%h%x%x%%%%%%%%%%%%%%%%&&&&&(&(&8&8&H&H&X&X&h&h&x&x&&&&AhAhAAAAAAAABBB(B(BHBHBhBhBBBBBBBBCCC(C(CHCHChChCCCCCCCCCCCCCCDDDDD(D(D8D8DHDHDXDXDhDhDxDxDDDDDDDDDDDDDDDDEEEEE(E(E8E8EHEHEXEXEhEhExExEEEEEEEEEEEEEEEEFFFFF(F(F8F8FHFHFXFXFhFhFxFxFFFFFFccccccccccccccddddd(d(d8d8dHdHdXdXdhdhdxdxddddddddddddddddeeeee(e(e8e8eHeHeXeXehehexexeeeeeeeeeeeeeeeefffff(f(f8f8fHfHfXfXfhfhfxfxffffffffȁȁ((HHhhȂȂ((HHhhxxȃȃ؃؃((88HHXXhhxxȄȄ؄؄((88HHXXhhxxȅȅ؅؅((88HHXXhhxxȆȆ؆أhhxxȣȣأأ((88HHXXhhxxȤȤؤؤ((88HHXXhhxxȥȥإإ((88HHXXhhxxȦȦئئ((HHhhˆˆ¨¨((HHXXhhxxÈÈØØèèøø((88HHXXhhxxĈĈĘĘĨĨĸĸ((88HHXXhhxxňňŘŘŨŨŸŸ((88HHXXhhxxƈƈƘƘƨƨƸƸ88HHXXhhxx((88HHXXhhxx((88HHXXhhxx((88HHXXhhxx  ))IIii  ))99IIYYiiyy  ))99IIYYiiyy  ))99IIYYiiyy  ))99IIYYiiyy  ))""""""# # ###)#)#9#9#I#I#Y#Y#i#i#y#y################$ $ $$$)$)$9$9$I$I$Y$Y$i$i$y$y$$$$$$$$$$$$$$$$% % %%%)%)%9%9%I%I%Y%Y%i%i%y%y%%%%%%%%%%%%%%%%& & &&&)&)&9&9&I&I&Y&Y&i&i&y&y&&&&&&&&&&&&&&&&' ' ''')')'9'9'I'IAAAAB B B)B)BIBIBiBiBBBBBBBBBBBBBBC C CCC)C)C9C9CICICYCYCiCiCyCyCCCCCCCCCCCCCCCCD D DDD)D)D9D9DIDIDYDYDiDiDyDyDDDDDDDDDDDDDDDDE E EEE)E)E9E9EIEIEYEYEiEiEyEyEEEEEEEEEEEEEEEEF F FFF)F)bbbbbbbbbbbbbbc c ccc)c)c9c9cIcIcYcYcicicycyccccccccccccccccd d ddd)d)d9d9dIdIdYdYdididydydddddddddddddddde e eee)e)e9e9eIeIeYeYeieieyeyeeeeeeɁɁ ))IIiiyyɂɂقق ))99IIYYiiyyɃɃكك ))99IIYYiiyyɄɄلل ))99IIYYiiiiyyɢɢ٢٢ ))99IIYYiiyyɣɣ٣٣ ))99IIYYiiyyɤɤ٤٤ ))99 ))IIiiyy‰‰™™©©¹¹ ))99IIYYiiyyÉÉÙÙééùù ))99IIYYiiyyĉĉęęĩĩĹĹ iiyy ))99IIYYiiyy ))99IIYYiiyy JJZZjjzz  **::JJZZjjzz  **::JJZZjjzz"j"j"z"z""""""""""""""""# # ###*#*#:#:#J#J#Z#Z#j#j#z#z################$ $ $$$*$*$:$:$J$J$Z$Z$j$j$z$z$$$$$$$$$$$$BBBBBBBBBBBBBBBBC C CCC*C*C:C:CJCJCZCZCjCjCzCzCCCCCCCCCCCCCCCCD D DDD*D*D:D:DJDJDZDZDjDjDzDzDDDDDDDDDDDDbbbbbbbbbbbbc c ccc*c*c:c:cJcJcZcZcjcjczczccccccccccccccccd d ddd*d*d:d:dJdJdZdZdjdjdzdzddddddddddʂʂʂڂڂ **::JJZZjjzzʃʃڃڃ **::JJZZjjzz **::JJZZjjzzʣʣڣڣ **::JJZZjjzz **::JJZZjjzzÊÊÚÚêêúú **::JJZZjjzzĊĊĚĚĪĪĺĺ**::JJZZjjzz **::JJZZjjzz++;;KK[[kk{{  ++;;KK[[kk{{#;#;#K#K#[#[#k#k#{#{################$ $ $$$+$+$;$;$K$K$[$[$k$k${${$$$$$$C[C[CkCkC{C{CCCCCCCCCCCCCCCCD D DDD+D+D;D;DKDKD[D[DkDkD{D{DDDDDDckckc{c{ccccccccccccccccd d ddd+d+d;d;dKdKd[d[dkdkd{d{dddddd{{˃˃ۃۃ ++;;KK[[kk{{ˣˣۣۣ ++;;KK[[kk{{ÛÛëëûû ++;;KK[[kk{{ċċěěīī ++;;KK[[kk{{  ,,<<LL\\ll||########$ $ $$$,$,$<$<$L$L$\$\$l$l$|$|$$$$$$CCCCCCD D DDD,D,DmAjPЊB _A&7'`70|R"!/ ~xR TZA](A  'XV °)AN *aPw0E (X*D)R!e -`7CD =npപP4u ! hO,yf `.c> qj_CR?_hD2!9} oSDc>"A<%J PYNt1%Ie K(Y"k3^ tOxĐ$@"n{v%,d ϸN}xTt,Tqb\ zś,32i#g=pਇD9׎TwӰGz@2?!&"1 ;Y;XPAN v0'tV 0\{ Ɖ|5E(wüȈRB/~ &_q2tP#Cn-'ai; p]DFG:31ag@k:tG:oxG Aq" GU`)zK `p]D%8 ")c _6 ՜6[zC l`D6<Ж'0990Qɂ@J-1od_S#3a4! l@:;aLzfc'q9MhBD ,ff g,,$`G?dlX\*q= c Cbupӫ X8R݊P~,`!=a E߂J PHE,(@ m$Si"oxeE*.!vQ  `"p~{5wTn"yT^9Vq "QG"WD(ֱFUÞŽV/|Sb/XҾ6`Qp%cQׇn=(AB} D)GV>\倲 3jTpt[t {NBtRS`p. \iMPs;AxD%" $= ưN73A9hqCNJp#G=8Q 6a.)t NJp)H 0_كkd[ Dƌ4`fH9/~x#A9H @U *1T! ,p.ÁvT>ڢ؋HDZB$$ PLj~aUE2+GZ=> YUpn v2pgW܎t8ưu Óǽ?֐S/! n(#) D(AG _ug?D $x(ak X_$B"{pۆʬ6Uanz= >{C:Q'8 -xQk¦k3XdE"-nO H^CqF[4uDl#4;VڵB@z\ B7x)6I/<Q,0a e84GJ:Pp3Ml*kan tbiNIM7!8}}!t8EG8DĂ'Q>aSBh@E.t͌`ΰu5\n`GN<$'u{'Bbs h @l( =(E ݀^c$ CBbp+,(a`q ^.hE66!̥e(y -lwC8ǐ0&Q7(dQC"X*-m^`,Yd4za l\}?1;BQ=,ycTQl `B bkl` /ix @P7!z yp%5) W+]-1h@HBBp#ݴ&Ra [a=4t! pvhDLd>A*Wu&0~ OB/xdx,f ]cǢF=3a R(jCo~H:aQ x'caA* VLd;M!i MdCDR"iH5 5؀P/7y lDb!7qJJRVE(}+zk Xe^* O.H9rA5kX!!iFR`x(aD LTb (/RzAb+TZў6᰷ ocGe<}(k #YH?"BB܇V3!D w@ hS@]p-h =I Ph9@Apll#ca6A TMBjŰ-<0\ o`{`;p>stmSl$J"U ~Kz tE++ QXb/HYzB7F 1ұgS,`ۚ8įtXG?bE<),u!ctDx"#˜\@z" "i' T-(5A ][ CB (d^K X^O~D&5fS0yVʴ @mCn6h@c&!6A Pp{-al[ ^xcwH}S4Nu k{劇,T;ب2Ld", 0Hi^@Y)G1J9UtS`E0 qSncv6@mFj6ͱm q#p|h=~ ~ UDbB#N܊vL"dHR5:t ӠiL!- n OՂDZ (<:L$aA WA ~K2] prB??BаBj%( Md11IEBJ+!^ ]`Ze(\2a3 hnc^iL9IMuWGJ@Bqă$7!F 0Me )f#6-Π+#Y {PcڃU F01;pۅ:)ai Na7 ܆7Xպ|8 A DAp8'~;KR\bo?E.?q_$ LFjb5 mfC?1YGB;ȱE^z#^BR숏d_" SHښG:b95 heC(  O?.`ԙ;A@$@ V·h0AE2.ᬝ dqC"(PB?"0gX$'P M5$@?\+]]VZF2kѓ^(h9C\F90Ʌu$I Ȕ?!k Y6!9QFR6B=y~^P-*$ =*V epc+,)`?EB.%,54)hA B`-<7)C{n .DD"@E(Dr'e;/DR'BGb8.&q0T_bg4QJok~iLH8IaKHrE#SZ;d!py ս?Q ; a O&D-"!iA ]"G<cX3hCTؓĞ6! oX@ 1:aQ vn H8=a l};cnq@H A* VLd;f!1>HEk+_D$q#!H2F0L `g99 AZ NT;;^0\$? XV³RP@d2! XhqCPz?}"xd$q&{ME< +!\ OZ} &/~F(aD>I3(G h#Mlˆ\6`ncpK"^9!aux؇=OQz |Qk\@cP&!4 hXD ߑ=HD\"H"Gb<+$|$|(D[,Rb^"oc~+_30Y_Eϲ/Sz 4`YG8v;2ѕgFz3Ƨ6᰷ m|{}G9xltcp P<۱ݏm8{iEx@@rFh3D- i 6HYC6 ĉ$U"!ƮG8uXČ)$aI&{3p,{Ϭ}`v*AT Ob|EuB+ d.)wf 5`ṵe6ҳ{iN MjDR&p3 tNT)бNUPjŋV-2Qi\pȾ&0a Zb#$3!ec(3q2i#Z4Ѡώ7U PpZg8r;:AպTwh3@ǧ>Q𺏼 }A} |32!1HD',pi#MHNBwJPD%+v[%&)7K"m|k(qD+WDR"d =a+_8/HYzBe(;/G!z9 `={89219ߴfE,Ʀ&6ѰFm{E(9hGt#~<`iP{J?8XG@0B3 WCőxT"QHĺF7 dH$ZA&= 1JhrSCh@JUj@R @go x`4jVvOL a" 8O^z7.op sjYRE,}0;@M*h#GbKNZp+ı^&b3ΌNtb6 `)1N!U4,Eb-$Ai" ~[hE20 Vb#93a3a# /di{#Y:٭h7Dq#hp;Cm{h99:=lvcn+pG>=8} MhAo |028!D&kċ^$h#EHH?RAF 1$+8 YK2]9mCn)H8m4$?!F8J1^x '< atE0s! Rb"?_3 +|i[X@nt74ѹpssG7:ۄv#č$h{=P}qHBA[B l1%!)}pDIJ&%gx;g#HJjT,+a^@ZPֈ/q|k`QFB2_HgCIN`Ʈ6L` LnJchX91yu G=Azy{`e(@rrR肗D"M!i _CRґ,gX"!G@:<9{${'=I9JUPᘗ &04 Np$5!\ r/Cze(Ib]蠚'< lPb@*V WpbH .?qŬ^-b$F"1d4ОF5 v|kkX8`Nrp](;q&y7CMh1Ɏ?%- N jqH5BB:$C"aWȢE/@zc#!IZ"Jl%/I}LÒfg?'<=I H_*Ð^)M `r@D%8p!) ac_ _p̣4; pg8~r@;O} NQrIBOȋ[Z#f!I Uƨ'q8sO"^O2x*lSg$V" PH-qn[(]IB IF J1+\ dC-n(v~4!i XjcchP71i1qÙN;MAj xL]>q$ 0'NB4٤<`!HJE{+r# "HG'od%Lq-InBLNbbsJHRD%');tIۢOb}:)a\ `Zg>pL0a g?΀t7Oa>uOCz-h>! DJ! \dD"3`CD$'P pK"i{K؝Ä(HBD HlRCb=Y+]8 Ybd ż/x8_c ~[>12hEϰf}Af 0ӾF5F m@w> 9'A:t㭚lGvZnq@H A)B J*!P!IPJ-`k(M nPV¶VF|.Ⴠ dD"&@:5} qD0!X<:7y2 ^B%(ed$1 6I[8 D'_:8Pq‘@E<*1UW.7E.Ap x]š1| |d24a mko#hFݕ8>qEr/C h7(E'/A ml@h@˂\ `x#A+ \NEr,gp;P(*AaR [B`#'d1;!: i3CI"<7"ivõ` > P !|Ek8Č%;A) L(nvD(D \R⤐$dx+_΋4\YڪP H/XQzŒ ``cljFD!!iEFb70IĊN%+)] Lml(!DQ XpRÂ}=a+_E 0Ye(/G!z9 `@{1JV +i_|l>&@:WT"#h @L2Ba⠟.)wY YBb?001 whg 96A&tl䇴=AXt&Cp T4B䰯%-uy? a @@P42 wox](:A%X||ψ4ABԘO#XqÉ H`"Pb}D&5@OJ2PE'*qPU27$0u g38D' %a<6 Re“.o|,,aq `_BWi3H! =mMCjj)`K?9݊PzդP2@YȈ.At" GuIHxD&@12XN2E)LT?AZGg#=' <M8i$@">)]8 ZB240 h@ܐp7kȽEu<;؇H>"As !q D4[ؐox$'rKj*QPH(PQBIRO;)D%8@%: ׈R!\|,kp _BB@dT3"v 3mCh;,9a^zo~r@b-h?"wGPBG?]&+1_ N}XEc #a&V 2Lgn:)a\ `Z[Blg0! g>F`[7?ǑU:!!!1 Exb9$ čn%A* L5bnwIDBJ"(E'MM jhV ֈ.A5 d%!,p`5k XqÌf<`<$t@6 [tBۢ$p#tDc#A 3IZ&0Drd'!E /TBw<-{ HbJTk^x4 o}09gP;:59}2 ">(AB芙DT# dH"R`D`0)!Y YB*G`;\0:8 yg2946 )tp=![ ؈HDbB0(| $q%cKAD &pT¤^ -azk XaUʯ4XS om{hP劇a; 0H|ڃ e9xA0NDRr#iM HbQpRB`+,s `*Vm3m FpmCl:ԇD:'ޝz@BSmCh"qGJ PHT@¢@2-xy? a @@P42 woxpۇ](:A%X|`3AzЉO#S G@z  6a}s=T3e (|!d9 !պxCƚ PJ?hsׇ@="PA<4x(`<@wÿ|>c@/W <-h UP" P @)H%(-7`X U(@F:4@Bo}d$5$  _"rxCp -l?/um h'ˁ>\ ЀNjVqÈ%, ?0IF6Pb `\{ۤ P,e X/_*bS tS`nZr@`4 ׀б3 Pf2(D@1| 9a̓81Ox/=y ]h Ub"Aw0C F0x >Šp8~ @ovTۈ@6RA skSҾ4&0 g 0 N+́^d ʀVT@J>0)UAJ ,Qe &с6 LwỲ> |iH<ʁT`xgP:Vt6| 88rÐ ou(@$ 63 ek-P ` 3 fq)`K>p1] 0PaP P.Aw2 \@_,uAc @WT"KXDIB"J4,!a XB6n@r @B08=AX z̨e@ ;A݊@v*TB=9Sʘ-qm @6t lZg84 Pgƒ4ʼU2Ap%Wa( GIMtL`c$T `G=  Ii#M_E,@BH"@}pChBB $!$a7A)B J$Ё& >ׁ|ڸ=u Ox@:8' 6s MBh%,&a.& q0KX04$% .HBG |#F5<<X"@Dh#D3! hS@B] o!: ?Ƞ~EF@Vy W*T PTB 8T)¡L! aR((AD 0Px0'; NXBrsԸ&2 xL2Ba蠗E%A* P`IOhUt$c GB<p#g#=EO*~1C"!qV [L-rai MYV] ,xaa @W¿poh+{AY 0U\N4*qQ Sւ`,)dI HQIN (N@ Ow{',!7 HM8i(E0& .& q0JWDŽ$! Ub7nu0! `PC𿿅/A{ ܸ^bzׅֈ.As p\tзօ-!k \Zg>w,c_ XNBrs+Z 8V"d$P*aQ XS𦿅,)dI HQH0A (N@7 hCD"'z43ѡ f50q[2- hdC&rȯ=1Av kb60a -0`dP҅/!} X^.ݐ.u% (\0 y-al dhZԜ,Ac Xml௯t+[+ XV;moh73A 0mCk@6Hᰅ (kC]"`[5^ HiσN|TҥX4j{ g?n|3sᙫ Xe/nYȆN2v c8`ƻ-@1jC JaC:Qˆ 0W X_B `#/G!w ] 8a oACz ݕ7&t m`k866! } krC[0ц 59 =iqK:ІP42n pgXC:͝d3% xe2C)+^BX2 uc7!$1 !U 2z|C$<& x~\;ݼvat; ֛u6C.PrD:$aHz@sU &90>qsË< 8< oy{7< mrk8`63Y zkLZf՟d5#!% 9(i?I0х4 PjYR`?4~X>E(|`>$q {\=8yPCʂ4񦇆,<1a߰woxgP;:u<?Ht:Cs8`(9@A>qsË:ԇ86 @om{h76a hmlkdK\8@0M*h!8 ňGBA8|| =āIbHzx$<͡G"8xĦ`#yH;Avô> Xx:tl99TjsT_:*WTR"zԈD$>!DE*"v|CBh@7!a `Cbyȅ4)!/Q zDB&b|Pk^ ݈'A?"@( T F4 l@KbX?Wĸ}xI`Kʈ>TA|{CB@JH=ZAF(yP򇇐T<|@H?\z#qG@8\m#R1̈́Fl"1`"&,Eb*VHHR@"tQ Db#ЈyC"yCBnp5&! \[B؂HD%!<4A hDJ ,@b(x ?X~nw@*1Pį%eq++SJS7c$Q'8IǂLe`Ė$$ `HE/xĈ$)!NlHb?ȏ\z#qLG:b8*Pl#GA:,FYb0]"Ee)kKXN"VAE *(PQB Phb Р'?$ Oz Mh'u;\NspX'8 MleՀ&4 L"e(v&0 ~xK]4%-, c KW&6&5 VNzpo[|*V UV'I8O.*cST"ȨbEC)O y@S6`)qLÊ_R#)).1IqDR'B_zs(F(QFBpC (PQB DPb"O xE'?LOb}'>'= OXyP:D'~;8NvF0YD'HA:B lN[bqKX&!7 M"npLDb)H =QŽLr`a(ȡFE .@QrV@s(ADj 0Q7ȡ (SqBP*P(@ P" PD'> |O{߀'q=#O,xEJ,Rb Xv@2E+_ W"ȮuD+\lVb Xh+-!Yi V(B}\D*ǑV<@UZ'I8O.*]QRꊐLTbjP0EA)qO{S(I:H)N. mSlBnE3r)L__R襀,)PJQR".pE$)H B,Rbx{.(F3QŒ&a0E(aEK &Q6F EJ(tAC TPҢ-AE (BAB V>" `^*qWU͢l`7EY*1UlUcb:QЪPR*T< \T⦝4uEK*MRmTB2Dp*P TŸ|ৠ=)ۡN sS0E5)AM d(S!B[إޅ.)lKeWRB"()7IK(RYBHN"t)!H9 >DQ"܅(Å-l `["RŪ-=Ai ItZKдEz,f֋0YʿUKŒ^,ycϋXM"hEz,`tW󢾣v{+1^WBjPEu+\ Wnn+ea[+ pVhg+-!Yi (VAB `^*qWU͢sk*YV*U؊@UZOx=EQ*AT Tb91ȩOJ~*IRH TuD>*qPT8>0)qOv S7)M iy+^ DWj"HjsP+!\ W"EmB+_!Z ԨVBbgf+.YuVG[عyE.PAr T\tEŊ.Ap p\#Tž-Aob w([BŸ-mj[UBH:-l \Zwe,-IjN OZ}$-h A@Z o{xŝN,f֋30Y` …,!e! % Y)NBpᅏ ,kc_ tX.)p,;qaۋ XXZ@Wł, q`KX" E|+ڑ^ԊWC N0p `CD" F0a _B/} _F˅\/[qzۋ8^_/x ^ X.wW]h0خ.au ]XOAE .at+ \5hyE.PAr \[ظ.!p D\" HP-qo#u[܅(H-mn g[HJ@ ,}c_IHE/!~ (_B)Hޅ/}L_=B H/y!{ ^"HEB/G!z9 ^oE/qx`^ w{,.qv󋳴]>aEֺ.1u1]6EйEϲ.qs\\h+@!.8qƋ\X :.!p |[.pf0-qn[p["@T-ld[&bkXůF-oAkz XZłՖ-Ej-AjM\Zj8ǢF=1ұo4cyoxDF2"1y Xbǃ`F&j1qAb wHVF01a/a|C LZ`¡01ь#a,A`F0iN `%)(09Qʌ `V(IH0Q:_`/1~_`d/1}$_!"ZнE/i{L @^x/4ay ɔ^L/x.!w ]BpȻG>.qv+t]k|Smh.tv ]u;͌._r \%ǰ.4!q \L"X@.p6~0[Ƃ41 bPc(F.:1f7 V4b(,14ቧ J|bSJ"P1q#>`auÂ0 1|a ]³0 $a'wC^0lg |`%)(F0: `V(B01!_bt/~x_~H@/|_X/`!{ ^rE"//y}^Fb5E/x]uxE2.Ӂv ]B^кֈ.u> ]<7W~Fk3IMfg1 Fa21e,gc8FV2! lec&5wK2Ta d#xF2#@d2 @F@21zcCD:$1ġ% nlcsc֘|311ac CŴ-1` Ulbcz'1.u HbGcZF 0 ;a#!qP0/.av U0a !a}; 0Z! `cFb0%( X`*p/ _B/!~! _u /a| _(@$/l{e ^Ƃl`~Fk3V1lfc4(Ffz3'Q:|f;1`2 e#.)qHd[&2!Y pe+;YʣU2A e(MBhFO 2mak d%F*0H2<猌d`"&0]B2 aK c0Ǖ<1njrTc0F6 1a+ ec.ĠF/1nuXbǃ[(1;! JbWJ"P1 =atiH0n /a|C LZ`§F:0Q$a'wCF0q `c-IF J0Eт.`xF0Ќ,`!cuT4! *iUIOJxF4i1I8hCֆ4)!I h_l{`3΁t HgC;Pζu31 g/8ŨFo3sᛟ f58?i3E( ͼfm2~c3f#/}†^2⑗eC,dW2! eH#)L,FQb2! d#&r3jKT2KX d## ȏD~2 d8> 1Quc|7h11igc>C*PƠ4;i#Ncsfƛ64Q0iK]8ҷƕ4$&i0#HGXƐF4wA hՃF0S4IO\hzC(@И4 h#@S܆~3ꡟU Pgƒ=3'Fy:3 4ga:,`es,3QZߴf7>͗Fl3Z 4f4Ffz3'Q:f80( `l2 e΃-n(9Y2 lc{؍l6 l ~Lkc^5a 5x pk[ܐֲ5A ek-Xư5w ZjVT5K^ Oj~S^ԥ,5  D|j#PHF4򱧕9icMl;4A .8iqJUh҅Ɣ.4a "iG= 4d$ hD&45 hO#AP:F4 gC>x{3HgC;qۈ{F6qq xHG8ڱՎ3qcMbh8akuOjx.GYr:unV(T^:uC;AGOJ:oa{t,FJ6:E1)twpD:ht#C ?x9aϓyxsÝ@FG:29(nswcטG49Qʎcs7G/B;8lvc,`m^;^qըvC GhJ;5ٮvX1`c;vv}8]:!1huCh Xx:ߎu^aSfGS2:1ԁuc=齇M:f0`t(Ht"A|.G=|{Uv=rL{c Ӈ=Oh${A#Z1=]zGB=XŏS zcx@>Q}eG>AT}Z?Qd >}}=p>g8|cD* +\>?|m#H>||hL`?4߬~cUL?b~C Gj?;~^sq?`~Hަ>AR8}_j2Gْ@P䀇$ H@)H<$d !3@b$#G?`iH?ATRG?f@dt {@06ȁD 5 i! @-hkD VH*@$ DS BHz@pD$ /S @INmh AH42A cp Á/Ar Z `+AM2 iQX qH&"A' =G096 !!Bh@ d%m!+i 5IBE-ud#.!qDB'D !! @NA{dL as;Arr(D1! IHbJC `HD/!| ]B(Ld-$!i! )YNBod*!W) TlBܐą$(t!CCzC"I|d;f!1unC,1d9!qpC~"ݔ6! HlrCXІ4^!D|#xF"5 HDWR"DI""ID1! PB "QVȂD oS"ȥE()EGp;Q_",`E( >؉O"x9Dݲ&5xL"fAH2E/ly d]"iL`Eɢ.Mpt[n"qHEB-:g8YH"A&H2E,PBi#L:FB4Bg?#9ͶFb3e2#) F@b2HDtU#LG4"9LRdr#fH2G8u o#~HނH@@#HG?D$|#G=h$VY"HD$'d$E" PHzCՒĕ$%II*JIJJTO$$$%JP $'I>IOhyLd%^i*VnJU]D!% - mHK_RZ pK&3LɚfJ wp;L&6m6L&yzi^p/8, Y,j5x \~P?(! `SA)Nx(f K_`Z/  'ȐdH2n {T`Pm8H$|>q?0W+K%| >R``^p/8 K%`jP5( `@Ap"D Z-`Nx(f K_`Z.J A|Pa(1 ^Kڀm@8l6+@q 9$K%  ``Zp-8  `i 4 w`A"F Z-`Nx(D G]X-| _s_h18 cR@nh:(̀f@8xtI$ vP@ X0,D `j`50L&w`A"F}ZA- pM(d2 f3R0V+ Ovpa82\. lA s ce`5< @Ep" vrGX,d 2`j5xL&w`A"l6 Z-@M' 3I@pT+ of`5L &0 FcÀa2X<ހ~`E`"]GV+H N`j5x fA"F S)K'< F`U- M&>`l8H$D́Pc1L`,p8 D+@o9?K%| >xe^p/8, W`u@:/E$  ki4P*\. rϐg6B whl6@C &0 cX2X Ȁ~`Ep"]GX0, h4h( +~0? ~M9AJ@'  Z-ρg`a03d Smp:-`0`P (L`&0 ce`8xVI$ vWX0, ;j5xL&w@@@ !0j^/@Q,@  A gx5L `u7c@1 #L`'  xe`8x`K% ZX,p K%`j5x4 ?"d2 YEW.  `e4J σ`vhLtM& L`&0 ce`4`< @Ep" vgGW+ @Ǡd2X ^h{ =!x JADU- z@aH2< }frTl*6iM& l6@&0 cb2X<ǀ~`C@!\]9Q( ybP1(, Wwp; f -}> T+ _kp\0@ EAmD"T!AҡeL7f3`P (*L`,p8 +e`6 <@Ep" v]GX0, R@àd2X ^+@G@&` 3R௰W,x< }ׁk`H28 (oAЉD- {@ 0nxf3` @U@,P T+n`8xtG@# v^X0, K%q08IY,N(f K_`W+ k d5  ݅ԃH8js 4`p t:v; 0u:X,H |q9'K%| >p@`0x Z`B$ tt:`0Q*\. Y[`PY/  ,,@jx=`R )HX,qM& l6̀~?@! ca3  |q?0~a0.`Q( 󁠀@r9.,`I&x< 邍FpT*\. }ځm@@^1L \RA {}>q 0te2 l6ހ?(0 J+q3p `ǀ~`E@" vfGY, v~?!0sZ-`Nx( 3RWx-L փ`ƀc@3 fBA"HD0" 2@ @`HM& l6V@* e`8pz+.`L&H| >kp50E# ?\.Nx(f Mg\/ 1&A ix6b n@@x @ i6p nĀf3`\c1X`,p8 VG#\ 'a_0/8 w{= DM&(`pH8% RW-  ȐdH3 Empm0 2 xLi4`p r̀y@< cf8xX,1Rp)8 ip4 ^݀|>p zQA `a' AX _44 W+nA yV+FVV+A@5| ?,`0i8\.f@r; ߂ucj 5C! x-hH$ {}>dD2$Y!ĐL&PȠdP G£0p3h ȃŁE"A@Xo7@ L7N@'  c1@Y3@ K%| >C``^p/8, O'p8h 4AHW$<}c1[|.{=FiPܨh04F #KBH`U.1yQm,u*G}d@ P|(>Y BxJL%& B! _¯[TD*" or@`0l gPo74` 0n|p8@% 4q?0pW+.`M`&| >Ui`0f! F#Ca0sA քa=Ŏ=šb73?U*\k.|>SxL& ryCxÉaı8c _FAȡy@O~:o7_/Q(SvqS]DA }ƾqlS6NW! U&A@ # 5ad@7aNAlB!u:eײõaآ,Q I$SB)(K%zN ''N.PǦcI]g @ uR_)/Mͦc19!" vVe+-cL˦I$ǃ% W+?\q8q@8iЄh@P(,o7$@|F`*@ ve`8x\R ZiHC!ip8 Y,vj~Uh4J)КMD(]&TL&|Id b[u:НZE=b{tCveɞdDW{Q(g+iδ'|=phc4&J%܌E0P`jرlXLLO&&Fs#8 na0 YI$R%lb1O'ŠbC!P( AM!r !xpx@ J%@L,p8 c[2X<B"eg֋EUr*@ߠݐn>\rYlΆgC.ΥR˷y:ԝjPU*1z>N_'0Y8,Cz!B~͐f/Z] %9Nih:4 ;FCT Cϝ@v>@_ 3 FC#AF"W+L&j_L(Nsp6Pk 5 e2(B Qs_/8U\*rСPx}o7^ w>;f'3o7òY CԣjO7v|An Wp8i/4L f/ތi4D"8enT|Q(2M&Àa5 چu?U?DB! xV+nGI#I2$~aM0XzJI$ &L`&0 f3@L0]DTOܣ6Q#N äA G~QRh4h5bIN T*Rq^7npӈRhq4>^pxL.GA۩mJ#rPpX[+iѴs=OwV-OD &yI7T7*M/;i nj74z9myD=3 BX*v;LZB-"o̷ @g3Cv pr9@R)h,@O'ǜ}>H$bBsvZ)D Ԙ/L 1֘.[OE%"YtKوl¦!S'(bun#FZ%aH$Nf[pͻFݣق~vm5T|*LT&*I$ 8:upm!׻kݻ.˘e Rk@`Mr9co5֚bذvX1rIGTqi8|>x ̉$쒁@Mh"QvƓHt`M& )L`?`" >`v/$ u"m}>t^0R)LXei難ya`74KJNM'&T ~<{lLGr#H]0>h4B[!$ LJ)f\Pr6[Lٷl۸yP_(,_#` UC !t@FM &GbxrP9(].?kG5 ӄq8P΍F_mg_LJs^կjyšbаXh4S'CNa/6|^p77z r9 ҆N'9ʜc} G#ԏO!r.M5vxoy7RݹnC!Zu- Pǹf3` ( au:`Q ASQf4sZ5-PU*M&rYp 5@֠kP0H7TX# LF&4^SPw<2~`J%{Yky[-Sԁ>~ώ')y ֏DT;}|\>.Sw)\-tcM&vrж n'Mgg35A`Tv`30\&փkAd0^ǂfZuY^^RNgl+Cq!39wweDG#ӒIbD"{@ : iAl2mT&]}.ҞOC9ߵoں1`0{dݲn1i(8'A!ci* f\.[m7}qjPܨnOQv=A `/6ԊtйW+pj4's|K:~7a-Ėk7kdf3áWQVk5ɁzC`p4*n\".Hd`oV+i>qp c}GO'FǣcMэhƸ!wQs (>x{J!σgz)gU*WśEʢKq8Üg23* c\.|CoςʇeCaðk2Svzet !ri. ΉgLB,VT*eJyJ H$wXa06\Mn&h4LR&) @.NWy+~Ni1ɘVt+)Ք{뾇C7f].meԵjZzƽuƺOVPls|>C&Cӈdzm{Ap8E(}|>Mv;el2j5Mtr۴Vr9uQ>HMSrsQm6(s6c/|$>%`. e?bxm&6\.#0h[Zy]SΗgKHͤ"J|<^J W+e2 q:NBz=1ǘ㿀ldzMTJVLI/ta0se`0O[MΦ{p={[=G&xqI!7*ѕhVE' K%yFUTSVxDp (űt@6^?V*ɕd/nFW#O޵Z*``90>NPh4sN#C8ݍw9ōV!yb YkZ ?_/8Kٌt5˂U*PڤRN JT*z[d[,rT*EK I %wW1Td2)?A]QY}-X J e{u](w: h)4Cڢ`紳ZSA)'`NS p8H`y4XZ,+Kťe F!5PQZ?O#Y,\A j A r9dL)}: l6ZRnbKG .i(A|'R[i\D*L&NYL&p? ʼn>ԟiipn7\DYYjxQOZyΜ(F7gS:d&K2YLe-E.AmKjZזܶ.i[ T2%(G>R(D'2IJTXē$' 8KXP(ϝp4!6ALd#!F4p&Z[cG<厨uCG*9ώvD &8Ld]iOp #l`ц2 dC&0n5QoC(D'!Qʎ3nk݆wb&IF0֮w PƆ4.v 0YЭl,!a d["FZ6qucJU@,~ g=Ǜ<Pr$q&Y2TRǖ<5Qnt'CRʦUF5<'q;tKRD&4 P2a mþ.G<EU*t eS*܇{;P:e&0 BRu-mmۮpGCvfsG?dS"]DZ ?! HqV.wg#hD'e1)k^c c;\%.J7Q5)MmîrfINDqՆx3 mCjp@:ОD,` @Z.p$?Ў'^`ԇH:AX%.DR"ǖ'Y^ `Z:h&m3jrSV6!HrC޿hCF7"nCrP$0-n If0 PJJP`5ȓ*T 0axGF5ȞE, 8k_&A2:QқܪMRkz[9Ϗ|0*1QEYtX `3P RD@(GB:Ю4D*qS4a}GB:ő,^mckCBB*W]K'>VҺ~Bb,1aH\p(->.X7Ad+! |$%U@(j Pd$g=JГT)AJWrͶmw Ihs LAP`@8@*`/x `` |*P m\"Z iCJ LJbȆD9qːP$JT\B'8ODJ%,@RP4y-qkޗh;DF"0 L(F x3@  HF7B-n 4iAMlĮ&0|wC Hv_v esoxWjИMbkC2 (IN l\d @zD `@"Y  @Z0 ^戜DX6}Dr#@YȆ7OZ$9%( Q–p͞mX2 PNbr@*,Abp;( @(@$px@B `0~(! py$!! UG"A" t3)tg=?j.p)M^ZX&9o}5!HDB"a0L`@`h@@@@D &05d C5,f gHMh& 0itw# H"wUbw,erWrF1Nt|FAY @ `[@KX P ` ` Ox U' 8\`Db8E6}v#BJZb88OzDB%]*7QFn9qGG>9wezp ` 0@r.(@?A0H2AP+1Yd HKX$%/Y,tæ 9FmhL%q,dCe,X?iQd߰z GB: g@] n0(@  H@ PhAY܈PBxE5d{$b#= XǘƦ79D4 FzNRrtӨaC TZVž/~yc?YV¸A2yƊ4RHDe:Й|5Ď$v<1A&0-NړT`,g+] df[4n#g@6DRVR@g (!@!,D @r->A81 *V tc6JrSĞ%>)cK]F4Ast?ƌe$&M 5A_+s[83ў{kہ\ 7I}8yV2:Թ99x7w#HzHBB4(YJ[Rژ ]2Mjq>b ^DJ#hKBuӮ$*W4Y^25io|$e/n{t` (QE(1$3yϚ|ԜTB``@*KX 0QpZ݈XB{ث^.qsd]4=tc#*TO2y,qx cC(F7~yΟcC2D&4 Pz|-n+_sXx5ݮp7P;aFߖ'CΞux)L hM$ kKZvS%*W7YWb2Ŗ-5io|%;e/l{d O' %39ϒ|T*`4I H> ]]@" X `x10$)! |!@Jp.we#d F>1񐔄j#Q,dO~,ycHF7YΎzc iCJ4&4P~-n_Vznp+(WPS;FߖgCSʞU}l0ه|; hDz&7TbĆ$4Ѧ$q#AD'P&INWR0 lCbt{;϶i HR]4IJJV2~0 eOxVAv^BQCyc(FLf5&7U*X:жh@p#40 : `X X1 UB4C" pKE/~fr`?ȆDx#/~PT-!} d$|7Φ{# E(uC7d&6j"Q( pk_X6p{8ז p[=xGD')DZ"U,s{& 0R`3{! 0I~d.td#\G2=d$^") HNvܦ,)u bsφ6NZx(LBb,Qb 0iOĭek-9i^CXfc5-mn\w=+_G>2Ц?)R]l3mlފXmhhF `'A:%((aC W"xN6ts8E/D'QM hX<0ldG};}H2[ޝ-IjU̖d0e+IN8vަBo}&0"a(R Wb֮<58p3 e(@@`3v0!- hTPp3(|! ,Ib{ث\.!q dc#RG<(DK"Y#MRjth+AmKj`S`5auӮH@p%))MTBH`|[69ɞMwkD]}T nE/Re6ٶvҦadk#\˾^%-hGض! 7o xK\|4X$$D {@:`&0 X4XB4)q f0߇="Ć'!9 TF4QoúGC& B U2|/i|iH@:6i HEIRJ'?jhSBxCb0V)NSr ^ &6 @J8X"AFx4v,!a* PST2 yC!GkX8,g `c::}d*!QKX؞>)b]r똜y3ɳMrS>vO$ IiKJuS5!VbЌDbp01M hFB204 0a *AR PR` +\ _\CЖ8)p eC*݇DawC4A H\<)A MBj$! HBB@%8 RB. l`>Ȑ&4TR2A \jh?dy#.IrO~E,It! >oCz@<1| [BP*L `QŽ@(L `U0. XiH|;F$$N"Ŏ/QzdXGpl3a h`p.l `[. dC"0ц7A| F"U*AR {>!ȁ!1 D:Д&7 4Qp/| d#KX:h{D qȆG=ܖ'Ħ$1!F*PDB!lCbF"GG8'8 (QB0E.qs0a38qÎv10D&%>I&7 L"W$&4IPĮ&0 M~$*U Xb21 ip4Q}H AR $?!HGr;XĹ%7PbĦ%^* HB")!I dC,d]"HHE&8PbU*`KF4j#QG<Ĕ$)NK^X'D S"X}+iLJW%.tLBbD$'= P@*XJX".y>IO}(1DJ"QE*)VʶWbLe-uˮ_G*9S URp+`KYr˖.QzK`Ŕ,f 0YΖ-p]bf0)LBd E/Q|K_$&0LZdC"t˦z3Ѧ4k^G29f0F4)%(#AІ,!EZ0ц,[wn%ĸZ_k홳2^͙=gvØsby-5V\ sWʦTʱV+5fXK-eESʀP CԺSSbY3&%DcLi5DP=G25vKɅp)E(rR JIII)%mM<(U TzTڛ_kׂa,$2Aa "BR1F($$bXK 3'tTJS*CXk#]NF(HڻWwn;~O@H-$$"DHtBZKI7(ETW Yk/eػ:gMy8W ]kzBY"ĆZN9E(E޻ګTl7w<3~@h=8b?GU*ڢBT aE;+emI7yBXK2XNDD޻d3clp98Cw<P&DtRJYUWcxBAt~ҾWDD] 0&@8i15؛cloM8TS~OA$DPJR,sb6xA;"d}WN DGV g,ܻCc a1Hs.h 5fݜӚx?hY #4fҚuNeL|W+a6FwϱA9bl~XN9E#ZOIO)刱\ q.%̹`2&snsy=("TJ:jMJ=Gun a,њ6{w;|2ZNG(y@"DtSqT*DU ]+cLi4ޝ~OCH$ĢTJV+mmf ٛ`l=ǻ||!1N)i .+Eh_q4Fxb G9'%Xbf 7 њ]K%Del#b7V;{P:D(㼭>gPPc ax/кc a5` D\KB+Eh8g1>"ș"] 2ޞSʃPj7F1 ]uvoA5|]i)<Øap. XG"p`S at.p~,E4+`#dlAD0&DRf8'!D(kMjeL(e i8~%$.eˉq4FPj 1 pnGH)X{b8G 1F.x LIT*会@rA>(q$TQj0F͙hmmͼ $917iM0f ܛW̹ ) @"@\ Ca@(xO r5FdPrnVف0&ԝ#O(E U*E՚\lQ4f[ˬuh-"dR\I0ߛ DH  $@@ZI !l-!8^ >#RcLi6|ѺeLMI5&da8~F%ΙӦt͙=gT @H$pn@\ 9)8rR J AM)̹6q!=m ӺTYk0vYlm=G`L?:[9'%xP@  ( $@*@ |XK !d4F$LpWJ0֝s.Eq.(]lq4vϭA⼍bOF(%8pP `0fB(Z A8)E(cq2I1J)E0pN =h] DT+Eh&fli<917iM7mW@ 00P*A0!``xO r5Ƹ`vX 0/Eq.(U]L 16Mi8Ӵv9>E8"T3vnϙEh156u.zXyo/u{(e qӼ~BBW4a2bLK9g3|t:GO\T P, $@¸m A8-#wLIT*伙>rA>Hq#$dRJ_K;("WQj.#cٛ4Cp.mͻWj{}0A=aT7ȹ)4˜SvnϙEh2U:VfLɭ5SzQ?Q "TVJɱ6(5ڲVK)e.%ľbLFZvnA:T{A(% alH1&d̵FhB(E$Uʹ^k P  $VAK ax/0"DH\ !;Gi !d˜[y0&PsNn9Pj"DH$䜙"{OjII*RV [ x0Y :gMI6\+s='dІøwq26FʉQ1&$ߛ}z/LOXim)l- h6$|@m b:]K9U W =guοb4؛{onQ:\;|/X+ !Kq3H'$丗NiB]'_+嘳P|0F P5 !PCHTJV0xJIIH)d^ٍ1|ϛ#dqN);'+Ei %dbPJaL+`:[{0ə3?g]St޽g PuBHJY1'ƨ!d,^My<TjXnۋqu.2@\ A7 w$Ą0B(GH&$vQ1S*e_ 1fl͚Vo 9#"B~ҚhM >%$BWUjcLə4[ zoNI:V#{A! TDz/G$"dNI?tޛ:[uV;wg \kW P*@Ba !7%D8gA9"dd@I9/%䜔"SJiY+%"` 1tΚZoM9ǀ"Bȩ#zReL=("WKAh.uοb46snn1:GH]zϽd̃sBY#D|2FI9,%̙tPK)eTZYx/™#$hM 6ߝxO >H!d,QJgY6\+s<D0n Hitg@9-њ3vЊH?njd*^{πea5Cht@(,e;`HPL4&؛tOE(0BWh-̹bY3қChp:G3P*BxzbLI;(V%d arZhm90y/ -#Ԕ[14F#x2E)%$읔"Q !H)0V _+圳^ن0Dș0Rh !"Q 1F) !%4S*wN)E):UXel0Xel10Hi 8GCzCq#drnQ1T*^ф00C(g 4f՛ChoM8@øy9>h +Eh$kMi>U Vc`l Ѻ9 zBHI"^O J)E`r] 0,řC(gL4֛Sjo8@y!>P#Dhz_K7TTwq?!H8JII5&Wk-eS dL3fҚ\m7 vjY6 vQJ4FҺdL9(rS优Ccy3fӛ3fo9gXzY> Cȥ4fII)U*SjzOJ!D)U*cq3&`n8LzQ>Cȡ#$dQHI M)会RvN A 4frq!:ps|?!!4&P(E#x:RJIu.НP 1F)298g8yO)> tC$dGHE(ĸ:rNI?(TjXx= T Ch#bGHE(ĸ:qN)>T V+t{q?( &D#v2QJ)q.&d̜S|OC5&UJ[5"Ai "TJ;Gi#%fL8:RJyO*RUX+9g!4Q*0Fd[Ki3'PII):UJV`,Ř1F(dR\K3PII):UJV_ֺ\i#$RgL9'BRjyO*RW-ee]y/&ҜӚ~O%DE(UJZZZˉq.ھWuN@(TT UelV Y]k%Ļr_,ԺTJV+unYal.5ƻz_L""U\Ez[+r`I1::Z+qn.uμ_!16&*eL՚WJ^X d 3vnК`L50.e圴F&dŘvњ3RjMu~ZKUj}ݛclm7#}o!9'$圳t.ݐ>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>%>>>>>>>>>>>>>>>>>>>6Mŀ>>>>>>>>>>>>>>>>>>>f >>>>>>>>>>>>>>>>>>>=xF@S>{>>>>>>>>>>>>>>>>>{*ʀ>>>>>>>>>>>>>>>>>>>)JP$I>>>>>>>>>>>>>>>>>>>>sn{><>>>>>>>>>>>>>>>>>>f#>>>>>>>>>>>>>>>>>>>>.>>>>>>>>>>>>>><3LӬ(*D%ahZd<>=ORXDzip& BBc@P#){H%a< b[>>>>>>>>>>>>c !dA<hP (ZA< (  BX!(In1TU Ba@@P>>>>>>>>>>4'7 ( x@P(F@PP(2 (/hn@P (( @P  2 (>>>>>>>>>+hZ((@P@("2 x@Pda(02 @ xx @P >>>>>>>+*2 x@@<<xx( ("H@P8 (dx >>>>>>3&x  a( 2 @'iنax ( x@P>>>>>>0xA ( (@P @P(RԨ*N/( P(>>>3 #H<xF(P( 0"@Pİ7( >>>\% D @PZ((   @P(*@P @S>>N A@P( -I|_ bX   (((/c (( ( @P((   @PF( Zh@P ( #Hx(( F)Fx@P(  (( x2 <(((.ȯWիuдZ{-|־kr -aXl e\}Y>VUe2L^.S*ʟOگ)WV(.T*% EBR!)j@zaTMzryM<P (CF#MةTӿXPJX,KvIZo7̞9O19ʚiM4T=Bޡo~'dq~ dH $I%d }i4BN(T :ZaN0UU~xsa0 zk9EJ"t$h;@H$zikĵgy3DN(6 I$PԬVV+,V {za0q9YܬEïwI$hi5Z孭yռpYΌ7F0w#jA!}PdG0rG$tzԽjoa7P ԎFZXڭ‹z }a~1ʘLfZ3=3imKyͼq9^NF`dwԟjK%5o7xF#6I$^?4Ty*f d-rZKv%=Jo2ud 2YgQJ?!{$dK%֙1LjS%)UXD|V+cz b2u:s{KAk6g Ì].siNea&P(C!Co6s ÜzB,!H$bqSx[-󘿌_ƣQŀxq<8e245I&}>e2LLU,a0o%xnI`XŬW=i6 6~r 9\.[KR$u:}b 04v5 u:{ؼ8^0L Q(h}4E!"I9œlL&b1R F@߄oRu):M(Q(t:rZ@Y\. W+֥S ^c3Y- TGlX[.g VtnU7cpv}8C$HDY(sDya\/L=&"Tafy+ЕT^J%|sLa0Ұ]X.X-̥Scr2Yv-&SlM[W e2tj;].Wq].wl[-Q(`00;A!P(a0 Yi4,@x E'" …n ~g3G<CwIOnu7:P* U*pQgU\WӬ;V"F_fz=Me6jY,ްoX8Ugw3TvNs[L#U*R tC+ǍF!DP(rlK6&$qLr9gTH#D&I$HOܒIIHjU5,K^Bmv`Jģ&Sjdyi]_/L&{ 0~?@!oȔJr3=7Hrnd2h4Z`N("J9⚡MPT+'{dZ-˨bz@_0I Lv&;\ɐd2Y}qAҧiS6ModHAmSC~0q9Go7QѨuQݍnqcx<^j/OwE"/|p8 $ڇC"Qn(tw:I$JΦ7S|>xUQ*\T*CJVήMW&G#>e2ڡmP[\W+%e2Ýaα VC+Y,f3ޙMO=j m6qYXo,8 $W+^sM Qv Czq^u׺}Ak5fhTS*(@mE6ϑgǤc8 e228U*gV3+@ t:S IB:A!HQ6HHҋ{k5ȞO(K%Cu!Nd*U+Dբjre~Z. WU֪z_OO'iɺd3Yfs3B\iq5BZ-s&e5Tm7W۫Y¬Dq9Jܥnpg83uVNq{xV+-ȴdZ2b k>}>h5 caaom7[&󓁽^q/9c'1 tsji4xh_/g3U*~ F#-!xa0cQ`!u@ HHroTتl\.L̀[ql6r9d͇ix<@&H@J-GiEiWb1JM'S*bIާSYem2`c P (;REBxѼi$Q(̗K̦WitAƣQ)ڊMY@pW,FKNRio4B]!/4K]Jb%1֌H-ͭfֳ"V+QUuk:#[ rNޢoQ8&N(sLs&: P ׄ~v;]/KQ(Mz&y޼w5}LڗKGLXs8CI$`H}Cw!<dRxV@\s.9݌no#7 n7cN1QCL&|> h/dE D٣=ўT*M&KUt: D"?ʢQq)wʌ%FaW0lW6,MV&?UڑmHB\.{k}UaqX@)f`mF#MŦՖj5Z-o n }>zr9\ꮒI[v]]ίG#9zy=L^/l7U|۟m'X`,%02XRYe,,K&qf3sMIܓnIht:SOƂEA"gPGj0pmGUi\Ԯjc1ЛM E"IS*UEl" `Y~l 5šus^&f3Q5cc1̽^8F^h/4ZQK[Elq]{pnXJ%[ Zt-:]E._s^gwC!+Ǖѥ {=^GɣvA~ p߸o'q%`r$R)I @ U d Gr#I~$rt9D`T*r9tX`a00ufp8XԬm6՜B!gFC!n{=d ! GD#quG#ޕ J S)يxR+*G$R) Fd2].zM='\ӮjQF0S*A ^ʺa]0LX,iŴrf]/ k q8Ńb Qc1ΫgU9Z0֘kTZN*'bbH1 X,H`$0u :W1N 'I% B!Q(V+[R)e{,a0ڙ,̖f|#>Xլm6HN$'DC!z=SxϼhJcCQڔmJB!tI饵ډs-9OۨSW+ڡPUuUZfpZ K׊z޿_0JlK%-[dth 7؛O''#‘PHV"+ u_/VX,Akc5gc1w;ƞVg܃BȄB!Qj51MH&keTeM2:N'ɓ e2MIS*UM*Ui*E_yYh4PO('A B!M T*ZEri\m.6 af3?RM)&#`q85Κ}SH}> 8H? D@HK%J6it:zq=8uPp8zSV?Eѕhʶ[{.`0 |S)וPqXX,Wh+2Eq^J/% -#^g3Gcgdr9DUΪ烃J~? A ? ԉD#4њh~bJ%dJѦSD"xVIF%YlA6 N'6e2uL:TdnjpalDY-qָqk[]-˂eвYxD_j/7'\ȮfE3" FTadٲo-7k5BáaXyo< ^oB7hP3G$RP)TT*`Y0,MigӳҌ5FRު JXb[P8X,VY%B|ܾn`0;!ɍpƸd2pL&AɨZl16G #˔uE:I?PF(M&A LE&#C~JI%$EJ u:&P(ZJW+MP&UYS~adc 1 zM&gB3-MCR\.m6g!S~v;@4GQrG@#ZHV+Z 1EV#W$J%t_Jiue:P(TJTU**iOjU5+=՞|l6h4No7ƱsXn)7M'sΘLu:G|~?^ I$9u :DN"fmAe IzdIBk55^N(J(rJDS*Fjuepظm63ts9НWuc0{l=fGm6BB!J/EFG#$3R5$W+dL2&^i$5@QKDJyB(JF?Qܩ4O)'@K &/Sيv;l.P(UJR%)eO2UGNӧi|w;N'gF~q?8 &R CO"3Q$X5AG$~?iEZ-j(L&q⁕@ʢQm)OTz=b%TxG$+T*XK&iS4ya:QB"DH "vdSЊzbR,JE@]벛Sp I\-`h a3fzi2m&M8`< v$A(`vn6PbL@ d!N)ЦZChb4Ɣ?Ƈ#D蘝"$WsR.jFHÉ#3j_ma-'DXipm/E(.7fF6&ӄrN;sTPs@{(!?EGt tPW A(? .E1.%zDhע:MډQO)E΢dVѤ4F۽vh i5)e ,XZ 9{1pF.-Pdj 8GhëuB<\#}y@|H ОC aM!ɤA<'"z$ODx"\ц0FrA#aq !Q2:G(#V~J>*JEIHRj]LBшZ3 a\ӨujDmH 95xOW>cglW`P>A6 10&Dht"NQ#_+Eh##jBѽ7GGI4}RJ@H] bhMJL I+x<s a ̰4<ڟSpN{al;XkPsܺ{Oa?\` 8Bh^mRTBzOQ,%E/H"a"3FHx4>HiM!04CR7FI)#d}sr"ZDKI?''YֺZL2fLҐRjmW :.Eأz9?d' 1$ЂB\BTCc1&7ENH/cѪ5XGH#|(b@Ha) %"$TґRJI]Ii-1&$:YH#-de*%B|Rϋcr/NeGh⍫yo8=sx4a=>Gh$|"BhY-)4Cڈ{Q7&Ep٢4g6ѹ71GNh#ؤBHi#$|ēR_KI7'ۤtҔZRZS cUv(B y#O'\V\^!44憜ڷVFqεֲGi <ǐCthn8'QH)pb. Fb]ww8j?MHUi "$\$:'RYK;I6'ҟSJWU+%y$PJZKp.ĥؑ!J$)E/mw >i'R_L⡜T6宜Ӽwp?~Gߠ{$PעDWȊ"_5G@(#zR.EI" $A%v҉:Q'JoM*ƥXԯ*YeKbl]._%rRr_L i 1#$tęxVN_@ 7HQ%Hd ƕ,]9g6Fը#xϣyo@a 1 4Q*#E[Hi#$o2QZՂ:H"$Xd’xRnMJ)E9*=G~jYKp/4X zaLsim2P>ZgM!=5&$֞blRMQ6ttpN$Ę<.%格m#i "ULTي6&ӄfl|T0B(YEĢ8\7 GӨu"{OtwNJbLY+MisRj\K|0&+JeiL 4P8[k`M6ۦtݨ~oN98ԧ SsPNvi:@옝v !dFv.q'`٣hmNq?G) k-tGQ1Gn!=RBPJU,%ĺVJB`HLe 2&XtMriMmi6⛜S~oN9 t(sNq:'[$Sw8Nߵ,ŐrBY\U &B.e6jT\v!r$.Fԝ פĒؒRKicm.T~+eM )e54چPy:o'NI9B(T羜ӨuNi;'ttZy O;q=F'vR{O>Дa~O)%?~hPA8TP{pC7fqܔ?O a@3u? JiPGܨh}rQ>G(E%⤼TڒۣTj{8$#`tnM1{ظdJ̉W[4f`U`b USbH"UJIT O)<ΧzoS*reM᩼56HS7S {O\UGR*T[ aQ=*'DD2T3*ePB*EUTqP-@R TjSj~O)E>T !SJ{]+qخ;VjmZX5i"$UVU ʡX+a֬:Ձ*%UjWRU\ jڭ[UV uVUW"UL T誝RSUFҨU jmS*~O8 T*eS*cKs+pTjMR]KH! !T{JiQ*9FJ TXJ QB(UDU T: AP*EBWe zB$D{^Pqn9ͯWuΡCaf,,}ïu)2&[tk+euP*^XKb,@EW*E^uyD(^WEj\5prVJIZ+RhcVSuXb.EՃjmUW\諝nzUJVXX:U\ʫUd UKuTFMZU&JTdP?rTjmS*sM4ʦT*R*_EKDh*ҥZTjMRf*LI""BHTj-Q*p3LtɎsaC(y/^גB]̫uf$\95oL):[`=j^ʴVZ@gO,=[|Y@(dN,Ŏ/X=a,:ń |X _+|zWj]뫽uvUں;W.\`+p UPVjڭ[`jWV*ХY+9EfՔ:VA9X+b:GU"VjW*^xUҊQW[vlUVi*& 7_tK{/xEX]ktӮuX \*Qon-Ź,%[[ aYk+eu֕ҼZ!KD)gгzc~Ya,9d߬97X+ebuNX99`q,%ʯU"W^y#U W^]#+es aƲW*[+x%mĭrNVֽZ+Si8'#VtΕY+3%efՒ@V:JIX+Eb@UrV W*^xj Uj/EH~Р\~97=X]mm I5i&sY0:d*̅Yd1L2XO `\0 IJ^xƯܴZY]K)r.]ƚW[znEȵ춝ؘZ֫Zj~Oզ֍ѵZ@g_ʳYV`*YY+1d,9vîO; `,ŀVr^ؽyk/-eߠf]D˨s.y%LI\QoB-E !V:ڧ[Kcik-q]ZLIhu-q;Y+3e٬5K:gY!csh)j-Xa,5ܰ X_~V_W ^zUxtܪW\](szoUJ)W}\ +eo$䕸rVҪU[ aukHiZ+VZ%+Dgά՛ʳyVbYY\+aI)3#uվz=Kqo-7#vٮ\jCHqq4&;8gf$ę6Gc a13&ÛO; `0z^ܱz@2FWrU]tˮt.E͎$\Lˉp+uҷV܀[Xkk l-eԵږZkQMiw-.`ց:Yk;fެՙ#Z[YI)9dUP 97XbU5ڱVZËXWk `2FVSWjM_o+`+nnѴI6יڧTi0=vxk4eLi2&DȂ;b]Z1+F8ÇOk `0"&ڲV_kMzR䄼|]˴YuO.м0\5q. EL BH[kr-mo-zOV׷Z+XjhMδ֐Z+Ceg,䳜j"DY0e,E.H6b5.}ϟ{qȻc~o"[Krn]ŕ8(tmGMN ֮wNh apY1es a2^@{obZY!xOk `E(p*_!K){a:]Kiu.ӆp<\ !q.:blV[KxnK-e趽ˢtZ_kDhtNZTJh-zSum--屜Xpnͺڷ[V׬cbk8g5|okBgsLiTV^fK)}d 4ƗXb5F0Fy2]`] j-WR^k-z3Fu|{}] !uu=7\Uq.3%`[+xnMɵ *٥nV*C(_q !zrNY:뀽nڝv߃[j~OѦ64ƊT>g ]@3(Y4Y=S'dSLi2]cLb'Dư~4`k q0WR^߱zU5ׄ] v .ԌBjM\KrVJʸW C{q]ټ7uw97+\kXӋp&ջ7z@ۨ0[f kwycVӹ@cH h0k:gYK0 e] 2^BUSc b\1K!=k aa@l( ZkVRX_+}/d,5^}Ϲx/krn]m뭽t.r_~ Y#DwbSJxڧTTLꉜclrT}ō8Fh+xmQ6z&X«XUj$DDR Y>"g ᵙ1V[{I+)%d 7[ bLPZ VPP `,Ł0?fXKr_||u ^S}x=/~k zoR䯼|L[ku.K9g)x/YS*pB.E7f;Hil/-5ZfLhՍb VvX5f9 2fNߙ#9cL|)1&.Xb A0ƒ9C(`90E࿼"_B+E{}`x/cFhSGhPM vqF(١;4'e,ŕƲO#d,Cv2HIbZKUB]Qu!+:Eg7ܛsqj-Y˸9vۦstm-ųr6ՋZjSYvZ%kDg ᝋ3fl͟s2.e2LɑY"c |14&KbQ J!U;JX*]tsglɞ93#oݷ[ۺCh}lIM)$5Ć0զ{Qiy/5flփw=QgL)MI`d{,e !2SCrnX[kcfll͌wU]sY.k%}9/\"cLo @7hM3ils͎yݖh Rim6X4[?kgb Aele̳Yw.efՓ2yHV  u<4Kqq.5J8IFt[[w+nqb6FՇډSQ*i1|#?$gd,셛qf}[/eX: 3,eqw.™S6߳[xnS-el6͆Έakr5zԱq#N$i3&uTvFHS= g&LɚWvcuc,s'd5&,[o-7mmm q1VׂȃYjQ]V֚8Gh^ ٟ|3vX7{s~ap9$`pݿ7y+tEm-Ŵ6FpK_IkP j5]TZgLi-#E\4K5{=s.|eO9$`p.%ޞ[{tmM66X[`kzoQsvԷtcNiM):gVУ\s a9"X 2\p7jm[p~mk a76fjmZC]kbq D:Z_Ki ]'906ܑs.qn1MQj7B[{ṋٕؔ;g{lZmM&5ƳzڭUj/E4ا;}+rCHa78  roM𩻇p܁im0mm ?ޚS[*j \y5/';crBnHMGX{oѻ7zܳpcn m-vN$$\ckaΦ9/.ܐq2ï8udk}-o -e79۸S jalհڶVc+r]9 3=+pSN i7;[StJm 6&ټbsr˧t$gp|8/Rߊ[y|nӑ960?Kgr\ !7BAK)pj UKvނ۬{un%Ĺw6є2rCHyƗoYG7f݌ۏKqm9!VD눝pķF۶kvnTʑƻ8g7#bo7l㍜q"$UZKW|o-Į ?Coy8G V 0q8'иZ |uBhWj_.|BhWJI_95|RJW_5k|U?W*_E|믝u_J)_km|A5_5k|UAz_ ]}Cu^κ_1k-|56_k{}μׁ*%_L}nm__ |WƾZ ^k{u=W^x&;_>ךQ^b+Eya,5W5^>kxدƼX׉ !^kíxT μ _U+}(~W _)+%|x2&W A^+ߥ{ίyWj^ҫU{#du|W^]zOb,W*e^]ykxүUƼX׉ !^}xN ඼`M_~zo_b]}_ξz_6|Az_ ]{N׺zO^k {Uj즽װ:^ֽzVBW^ѽzB5疼כ}^gkyw.Fד a^BkMx aWC^ +x_ WP~*U_hk }u&;_μׂ*E^ +ExW_t}꾽W_H|*eW?_|Gn ׾^{r½WJ^kz^UcW A^+ӥzMI螽נ ^vy6u6זz^P+y#|׍^-Žxᾼ7ׅJ^ x"UJ WQ^~UWy_d+}d⾜W_8+|&D_ k|f׻*e^5{ZkU캽W^+zV*EW:^kzB5窼WJ^gky}/ZW a^C}xc׊jM^!k-xd !ׂ*E^x2W a_+%}دW _U+}(~W̪_'|su# A^+ߥ{ȯyrW ^+%{cʽyW^+zyO5N)עzO^+yگ;Uμט^Y=yF(~׏^6+xZKWZ^+¥x3uJ W^+ewj W_p}W}_D|毜]W_|6*W^ݕ{p킽WJi^+Ez\Uڽ[W^z=GZ W ^rkMy4uוj^Mkɭy!㺼wWz^)5xᒼ2W:^x޻]k~ 5vc_`+}TW_4+|kxblWz^-ŽxZKWZ ^kíxd R*W:g^xU]5w֮ߚ_skm}5־:_E|믝u_J)_+E|kxJiW*^,kōx5BHW ^}x_ :'W}^ kmxJ W]kw5BW|Z]kwfװ^+z\UFh׫w^k-zR?צ^+zBHU貽W9^5y>~ל^nky5JW*^[+ey\+.דZk^Ik-y"W^7kxͯ^WY^&xu=W^kxI 5WQ^xW~]wˮuWz_]wWy_d+}d⾜W_8+|&D_ k|f׺[^k {ZkUW]^k{`uʽyW^5zVꂽPW^+ӥzcLu6&ףzo^=zB5W^u+Υyį8μי;^ckmyw.垼וj^P+y0&~א^>kxblWz^,kōx5BHW ^}x_ :'W}^ x"UJ W]kw5ZW|Z]kwUfx]߫{el>׳s^{bu~׮z^k֍zWuꮽUשZ+^znMb,W*^ѕzC.ןZ^yk-yϯ9WQ^f+y0UʼW^R]y;'u䪼WJ)^A+%xu㎼q׌^/+xnM׈^xj Uf,ׄ^+x(v׀]+wn} ]+wޒWyJ)]wxJWjM_^}N􊾑Wj_2U|ξ9z_{}"W9^5{pnjWJ^˫u{c"W*^+z̯Yڽ[W A^UzyO5鎽1פ^kz'DZ W ^|+υyگ;UW:g^hy1זz^UʵyF(¼W?^C}x㢼tW^1=x⚼SWj-^!k-xuᒼ2WJ^x3uW*%^=wߚW}j]k-wUުWy?]㫼uw~λ׻{^={r5׶Z^}{?g즽ױ:'^kz\^k׬^]zRBW^+zHIʽWC^]y?u疼םJ^oy5bW^\˕ya,5Fדw^JUy#U&׏:^8xүU aWc^(k x@W^xN W[^kx W]k wЮW{Zk]+%w5&w]kmw]Wy_d+}d⾜W_8+|&D*_ | UzW^ݕ{sW ^+{JiUҽW=^+Ez^U늽qW^յzT>Gק^kMzSJuעY^еz@¼W^rkMy75掼י*%^_kyl-rWj^Mkɭy%$>׏^;uxݯ6f׋y^)5xBׇZ^+%xT !׃:g^ +Exz]5wۮu.{u]}w:Wx:]뻝wh^z_k |+uV ׿^kM{ӯzu׺zO^k܍{p5׵Z^{)e5Nװ:^kmzׯZ`׫ a^zPҽ:W^+Ez7F膽ס#^~y=URWJ^kuy3"Wz^Y=yQ*5W a^Fy !5|׎^4ƕx¯UƼX׊C^$+ąxᾼ7ׅ^ux>඼ׁ;^x5ƻ~]wŮֻzU]kw5Wv]ثwRU݊_k+e}RWҊQ_?+|ЯRW_+|bU2&Wjm_+| UҽW^+E{wW^ݫ۵{`l*WJi^{a|W*^k֍zWuꖽRר^ziM5N)פ*^+ezCWZ^wyϯ9ךQ^d̕y0U岼W^R]y5&䖼בJ)^A+%xuvn׌^/+xnM׈^xj Uf,Z_+}|5A_|A5Z+^{}5f׼:^km{r׶^kڭ{Dh캽W3^z]rnWj^+ՅzT*EWJ^zMI޽עzO^kЍz@窼W^q+%y6uvט^]˽yg,ZW ^L+Ʌy#>׏^:+E} Wj-_ZU}>JW_95|Я]WJI_#u|x1W_+|&BW^+%{ίyW*E^+e{vn햽״^k{#du:W^=zѯZ5]תW^kԭzO麽7W^z2FUrW^}kϭy<>כ}^j+Ey3 W*^Xk yK)u;_A=|5nm̪_-+|&D:_k|Gƾ7_k-{}f׼:^km{rW ^֫{Dh캽W=^+Ez^UrnWj^յzT*EW^zSJuעzO^еz@¼W^rkMy6u掼ט^_k}ݯƾZ _Vk}-^_:]|կ_ʪU_%+|~4W*_+|&BW^+%{ίy׺zO^k܍{{ou햽״^k{)e5Nׯ^=zׯZ`תW^zP麽7W^+Ez2FU膽נ^~y~ל_[}}Y5[_E}5uZ_/|URJW_+E|RU ºW_k{尿׽:^k{tF׷^]{UjײS^؝{`u붽v׭z^5zVjMWJ ^+ӥz^K6&ףzo^k zB5_N+}"U2Wj_8+|ʯUҾZWJI_#u|x1W_ | *WJ^{ȯyʽW*E^+e{vn킽W^+٥{#du:W^zѯZ5]תZK^kԭzO馽4ץ^z,ErW[_Gk}ƾxͺ_1k-|fL:_km|W ºW_k{尿׽:^{u^׸^]{UjW]^k{`uʽyW^5zVꂽPW^+ӥzcLu6&ף{^=}U~Wj_4+|URW_+|bU2&Wjm_+| UWJ^u{wW^ݫ۵{el>׳s^{a~׮z^k֍zX5ꮽUשZ+^+%znMb,W_׳^kM{bu~׮^ֽzX5½XW5^+%ztN*eWJi_'|5ξ9_=|15n Z ^}{ޯ{"W*e^+{p½W ^ҫU{4fzW^+z\U2fWw^k-zQ?ʪU_%+|~4W*_+|&V ׿^kM{ӯzu׺zO^k܍{p5׵Z^{)e5fװ:^kmzܯ[cW a^+|&D:_|LھW A_U{~zW^ݕ{tU2Wj^+{JiU潜ײ:G^ëuz_랽s׭^zUufL:_km|W ºW_k{尿׽^{u^׸^]{ZkUW]^k{aʽyW^+ezV׳s^{bu~_ ]|׾^k{ïxu׹z/^5{pnjWJ^+٥{c"Wjm_ 5|WJ^u{w׹#^{elV׳^kM{buƾ7_k-{}f׼^ݕ{sWj^+{JiUҽW:G_ 5|׾^ޝ{w׹z/^k {kmuV״J^˫u| *WJ^{ȯyW*E^+e{vn햽״^k{尿׽^{u^׸j ^+ۅ{ZkUWJi^{|:Wq^={r5׶Z^kڭ{?gf׼^ݕ{sWj^+{JiU׺zO^ܽ{p5׵Z^ݕ{tU2Wj^k-{Oi^׸j ^+ۅ{ZkUF׷^k-{pڽW4, *ُc+ۺPe, 25h- qBxx*""Hg,p8" @)0 DLs縜0 ʐ+O➞' b31CP*J,c ;­pH\(͘3f"mb<$Չ=c* ʟ0T~ް7+o0' Yp<#臀z o &1 m-, 'O WuU`#uE ( 4 ` tp37CGCh1>b Oox+d  p%v ]. n ۡh0y 4 ְi5"an[o[AAxPC=AG]@|c0RTԕnhZ b@tWƀ;CxX83 z^+؂ @*$Չ=bh臚:aqrz2!n`[0녌:GQ&6Ft5 z<;| 6 h`4 #Ġ@-P bp' E8QN@"% lz=x-فv`k(V3_7Mf@T8,<@JLB껐yr:JC^8צN1IeRb־){g ^]@`"t- a}'ihpDڅ](WJ=A&+I;>mj ^5W [v-ݤ 2'0 9kNZ"f<wD2h/bVU[>ZVO8i+`EH|jB+׳@8-c>ϼ/Mh⮔&D"1bX8ʆr}x^ g7Y7`MXQrTa` Ae@6( )&`.kD ps' +xJ !u@d8? w`W,DGKMfa*JAXGŘf 7 $>xyc1"} HB@*% b/ 8Ni"Aٰcl|5dMY$ H8 z@<ސ c0 @+' 3C~0L)cglٿvoT,6N2E3xL@ 8S-?O- f@P $F 5:b+@0L vG iZ4EarX\P`,M)hCl#c|24R ( &b@\4 !HaPD!$ԈI5"Ճu`7p SZր$x+ hp"?I@l((J=V( ͆3aDTPF5 zf@) ~M@ p\nƀPT#?7x*{ 6ͺine"YHZ( 7= 1E(* sG{`5M`Ys\1<`@$jfڙ-̀ s ^.Fc>r0܌櫀9 {&`#6hĦ1&_I9Ď DH~ nFGeę~"H1gD}aGGg'EPDTdDDCQ C{D5s BL(1LB/D*@fAYVfr܅t9{S7 >Ù2=b0r[,QGT| ==OO`h'ˁ]#HqBP5`@ Xs9x^E3 cHX"Uq&dIĆs$G6UDcQyE^w%0IL1lCn4 tB#1$AW2D̑|@_+5D Q9@Nm80?70?.,A>yT(pL==p0A8dSAT<|;9CP=l>[qfp=}qQiZp7t= 7C< UT45 h-Z p03L1 |C6_C:3ΐ*.iZ_Ŗy*l ]9WNbxOS!HR0 ALSv9ݎpIla@db"w('AzЅ,!`-0l$ [ TCBUЙ&c u0(33 (Š,p)1 Z – <)O ¡0 *rC «03+ րB+A ¾yT,0 OS$, 4/ύ x0-!uB#y"},B7K<$/ '3IS% gBYИH&+ h`& :Bv' ‚p$( :Ž<)A c˜𧒌) £b*# ¬pU\+W ZµXT+ B[T, *, .zB˞,i G'Bе/L-K ^װp- t7B з-{ &p.D 浰G.cq(J( JQ0.L ,u8 h.r qCR1Ԍp7Mdzq~}Arh, ;m@zPJ=ZV0_$H0oلa9k0vAP}<CAЂ S|!(06# 9ppl$ [ %BICB% b5XpЄ%! Bf;P*&ʵ Gr/L' ~pD(@ &B( N>“*) tB7q*o FB4* u®pw+ mµ[p6+ R¼T+ \,W5 !BfL,S 7Bд-- s L!0s-\L|0bgoAPjAqdTYIq0w}. }STAЂJ4 MhBZ!$$0W"Ԗ5%;D# BDPd$ KBR됖V% ~_}&_k bkN' ܄w!0' IRp(j .‹)1 RBP); qBi * Bd*z ªp*! hB4+M! (B ,+ ½,݄)wa "ȷWp,6 Nwkpk˹ZȻ+.hOiЌt#upp/T 3<0v3\AW{b؅00X\ 'B Є,!- nB|"7B+}Ќ#09_$ H0 $ S0T0%- e`p&h uBkPN' LvS0') BqСD(@  ‡pD( @LB<)A ^V—ռ)o {NBӐ* BC* «p( (@P5q \ss@\$\A9PJLAOiXdVAorܡ$7 t$<wAy~eK~ BЃ OBP !?B#"ǷB1Ў(#'IB?P`$X% -Khe %C ]BWzЗ6%́ aT&p bk ' Btr@|' B}РT(' a…Xp5D(Q 3lB)1 OQBP)pĭ|=[j/ S%Ӄ]4Mrw-n8$o/6ͱ2NӯC=P&ɬ΃`?4OC8@8 4 ';sNK! C0(< -9p4pL 6荰?*ʹAA@H$#ΟA3QEQoQAT`X:LJq_3AfeE0lKA.q$nCT0vtA%{?cBz aX"@|!$?"ő1dp#}GB=$>e &!BIP<$ OS0#\% vB]|,& fİ& goY$':I ݗwe' ~pt(* †&oKū?jo|Y?P:5erD"l. @%09UlU[p0B-p5A PK_QA=PS<Omh[Z0Z05زAv,`L5;fٰfAl:qAP<90v'1z^A~9/p[\ BBPT!~yeBYPCt"P +pY#G,B7K#; Bd$m /K0 d% UUUUpy4%M yB^t&+ Bf& /n|')X3+?ĬH+, 5r|-uin]WzuDSc#9qq+\J9+NJﰾ/ԁ> Oɍ{c^F?QK?3m?5%m'o00x@^&~i<blLS#j-xP^*"}Ȯ9#uo3:ȇ!<y/oH[/h6=ψq+Jh`Z/C]okihкC.ho*ʼ?tƿ챰H @$7PXk@V\,  @*&' @x/ G08*q3@1JAHZa/pO1e'IUdd`Ab[vݿAyd`=9APpf<0j4ћAfo-uEAőPsgLVթ0w\pz^A~3 <0H R-,% p*! Y5BMP!!v"]*p"=_B4WЎ9#qB=vN#8»ppBļ71/$1 rW0CPMoos~f?Y4(_[A??o @9Ɓ1p֔up\p @%Y V{H0-4 {R԰5Ǥ qhp=E [ACrZAJLtA5Cˈ::Np<5 B2DoL;Ԟ5'u]_06 ar@CÄaM8SNWĤW-9Nѫj[Vx|ӽ%t𐗼$%S0{o^S׻C=z\CޗmRTx"~'>OߓN?_WY?V}3+LI6M댳,#(~?_~'[lEQFHRއw"fm9@NP*ٌ c °2 g4-,kK/m5[k _fܶ-#s-37L9kNZeeUv]-UUn󣡼l:;E|;_휋g"k-kἚo7v`=VHՒ8B=qi*=vO~QؾTv/#^>W[{Ǝ+??4#7vS>]ϡKaR>:6 H ?4Ԍ#.o*K>?ϏNӀV?ՠ=e @k,~ @+O>S]mGՋQba5XMKdReu6]MVvߚ7樎鋆:bn-8ۋNSÔ09o[:N{o4[˸fO;OnF[ѮIRnaKp=yC}PR>>gw[>Vϵ*J޾wv[]?46 ly29?O]lA C/ljɲZln/[8P3 gBY*T Up5M/ fϬ-o3w[G7c{ 8NS &9vN|l@:tPӬh;!܃+ /ScT!nH["Q#ȔHeYo?$6E/Z֭vmcrSܭVq+J%۾IvjTio3e>ϾId^?,sKDQ /[?mD34CumJFӾ__pW6 +hJ0; xƸ;モ8RC溉9NC:iPί볫G.c#þb<2Y[V-֏.K⯾x:jrܦSw5mp@^wH^R֛'5͊8bڞm[r:kn/(8{9LˎS2*YJnjkZ: B(.ѓ4d8nNùhplĻ1/(6h>s%F>Q2Ll45M3s\*6JͰl:879kzjŸn {=^9.RF9w>YVE⻪xNS%N< SZVr<-ȋr"<G8=N$O_h=Ct![VS>Eq|`&G>Ϫ_;@wP-|s_+٧#iXm4?7O {xո%n"ӈG8QRC9?+7:Mxé0n̍#hJvc|Fѯ .h,:$P8hct܌6# جcv+/X7 Ӄ tM8d<%|59MEhÚ:0⦹s|Sn˭rH;%۶}c< PcŔ;o0&`ʶH++ <۽O6^׮or٫ܶj=}c65{Mj!:7ݑwd`lu#Ho8=j&8L9Nhכ5inxˤ25:AεSAe;D־㵯e黙zn{j=<~}&m<{O+c~<ߢC:oYo[8?/m|[_l=9ͯ^ kׂܐ7$2xߪ7ꮎ4<޸7>돿@9P8Nhk1ů9kΏCˣ׺5+ ;;;Ә4;#u<y j|ϯ'?Th;ryܭmf79yL7Å=0!8N@#ÐN9ShC-PFnˣu:N.l;ks:\;N Sqm[f# |żo:Asΐ\P=4OۻJ6ҍӂtޫa7Ni:y#^H,49NUekYZy^n{jڦ 5.Z+.;9Q+TJ;!DekoĴ<|O*ʨnaz9nahՊub`n7˳ (j.C)`=3=/FѬl<=1|4ѕtem[|!!n[7 S8TDL o[i B'9΋#Mpn廪n1nST탈`.sy0;,ȓ$߼0781<|xrmd Lӂ4`8|2ی6Y#9HWcz󞯼谥:,)NUl dn6.:T;U5FQ&yߧ7b{7 4+&manX48柶zsG*vκ3ˮ2e;D˓'I tD/@ޣ޷}0En$>f٪H@ے69f6jQCP稷9-Ί>雛:fΨiFnOۓĒ۱$G޹jsf˽ٲ>7dC~P߮&Kモ8In9j滤y3|LI`Ƅ޸7:+9,GN[n[ۖ+Jzsz'ޮsifnX*~.skm}~M SnȻr.F;ǥ91N\^ӹ{˞2B'c^:γhS =(ߖW ;PqT.-:Nܮ+MѣthulvqnlyF:^QBKP%ZV$k47QKRM|Sn/qcXnO˓9yBmDD綩nls:\N%cnMdIEn!+hJX.A%;IN9Aw_RԀ˹|F:&N ?:f@8,3ی, QCmPJnybĜ9'+q my:[^N#HmV[U&RˉYʸrD/Ka2Lf9َ|'U:'UN㥤87}STan.TRÕ#B9Ўo|9_惢y.G[X{VcWn}m[f8ISTl9[7d% |n}ԛu&䪭9*NX6c Rw9qy@m91NYI RBa|_.M˓xjn[3 $2 ,BF@*PW@H2BPz[PC2C=<> D1+M99sH|J*HR@b8*`-gI]y! HPLQ[*G-2Y]Aʍ`{kFir; H*U"^!m|}9jIF(3CQx\0X@^1zB!RUio_A z`Q RYj}9$ zCVPQܻ97PL_`ci(o2w BZe*qI1kx} zqA2JsBs9"6Dp;1i+%0 I gh[wi^1W"~i$B| li "sPm j9$XHzZ ުQ:8Hc"Y [!s(A=7 A9 S"yCFcbqY bp- EZnx1jrzX iq~xO7 xRx ZR) z٠+x1a8*;' 4IA9JV9Ga i5*j +ZQya.ezt9tbpn 6>*nji"dQ1ki'ҩ b ! "hCcI 35S43: 621C?[yD8x $JOI@: $oh:/"yA7ڬXnQ/ڔHr@군RJ:Xk)KpsS#YA[') 3,#+ 9#QjyL J%6N| RXYBu$`(ʿٺ"ʨA zA BxZ9jX@a kI [ 'Q6qCUF2\'zb y':0j:| QQZ) ʬqrq$i9 *z! [ Q A#*">AiZ/QFy!,JA%bjRJY߱I*piby-Q =ev+aX3 k'w!v&}&|"9bu w #2qڔRY qY"/ұ0'B1)4i)ב7 A"a0'y- 0 0k5c5=D3YKlISm;ZazYa.rqka!u90rx+ri;w962|>nDZ|1?AR *Y))r0BA#z$ƙʑ/Ba>ڰ8r(j-3%q-:>83: i=9 3.19FyM[);dDz)F1WZtqN\ 2C).$B%q!4m2J9,I0RA.ʍ'x#چ-ڑ4Bq-b"r'"01?r1;²*i79>3ґ6Cr!=8I>DC7%{]!!)1;;t!D)@"Fk))"k!l1P +yJb3"`9zZ4b <8D)BBʹ<PyNrDZqDFRIXQS K R^3RAa!^D)]L*_+el yhb9Jl-,Rfa g4~q0/B #R%@r.:u;zQA|.R~!9)F*}KrqG)@HrGA*ƹ=đHʾYC>JD)G)N1NsQO+QV+TqNNKyR2W`A\c kncqbb+uD[I@~*zl?:h7!4Jg"@y1ʣYva2‚77A6ڎQ>ʔy?RiKr :ʔQ?EB NVRQTUȩLqN)X9Z["^4!Y{BS;Wc4[#AX[Xuq`lSAs v]laRqr{Y1ʏ1K{AjkYAV7uaF"s @"k!Y\JjQ>2Y=1;!8"I7q;•iJz5ʤ 2ҧK\UQSsU-1^E^#ly_yIcSIf)ec_[VPKՑN)RqYy_˶ba_k ]SI_ʙikԡn3qIs+YrpӌIWˠQUUk)Xs]aC`KZSQ 9J9HKR$XtQ[9[+XkYUUy] d, j,nq4)q2p9o*}a$)g9 yBJc926҄L y>?B8zZ_!6"paCڊ?)FbA>QAʙ!>RMTQKZAIzܱ^K aY_KPmKKaM+QR#MH\ G4)Kt$IR'1Y(9Z<)V4.1O45qI ; F>H@YM@qR>TaIh4QlYYn]Qo|_in_n1RSQsr1O+YTka]Ә_aUHKYB|ELH,FtA@N=T@U)H$RPOaTLULaR,NYNOMONOQPT,S T$UPW!KVFTBRC4TGlZNddAV4o]yctYgijilT{mltni"zc J !BlCk/Z!:Ҏ9JʝK1SuQl:2=SJqFaJIGyIi=jQY 9QE UM3)H3qHHKQD W V4LB B$Ey>,HA?R@`?$i>j)@dF,[!L$QPKSJaTLVQaX TyZ,U[V[LWXZQT<\N<]H\]AC]yBL`ChGtuyL QܓV[A` dRQ=$WY<<]>^CXJNODIP;Pl7!QT8T@ZtJy_RbXaa[)]l\Wt\QD\KZH ZE^IEdgF,vGIKN\IR4YW$],c̙i9n}qJ@~8b[)*(QDy:l!BCa1P?ORiS^q]ҕ!HZ!NUC1\IG$&QQDBUOQRZQQLc Vb9X[YP4WIDX?YA TBKALBA<@8B7D>lAIt=Rt9!TD4!P.L,M,/RT9aYlEi^Q1_Yq\]V^Pt]AKZHXGdZFbiF$qEdDdCCùE̱I\QOtW,_LfYlopVA]܁S"YC4M@[JVaQU1SL`Vg\aYYtTAK,K=H8,EY:?y=8;464Q235t2)?1aL3T79T8O6Kl3QK\3OL9SBVDMaU V P[YL]QH\G [F[F`yEk D,{A?,=d=T?|ϡD\΁KT Sq\cl)hlYbw.jfq!Bd zsx)Rajb.x6kDI3"YJ5EWHkQ;#BkQM TQL;9P<9OLAALHQIOaFTTEIB1MHSLNXQ]dO`LIQ`dB]?1Vl?QNlBGC$ Y>2C^9C܀1=|@DrDLx?nQ?JG|/J5iDtP;b<`IVXlR]R[Q^TMyfMjVYdtbYXgR4cTl\Z\[dfUsMxIlqMtaWPbFifEbKYYQPLUKULUySlW[^b,i)d\taa|[TO4{YMlsOTkqTn9\k9iY;VA?CABQC,KFYFeEgKbUl[a\lX)_lWbYQe]cgY[TsRtyNuPDnASlSsN}G)DTxGTgOVXM[OZ WU4`QdiRdVd^gedoh{QgLaiY̑R9NNvSiY`Ya[hd\lcmdmikyfaT)\QY9YD[0I3T!U[>aRYQT9R=t:,%$)@,nG\MYTa!Pi NcTU\N`T _l^QZfYUmiSvR })P$})M$vJqAI|qAHuGxH sJhN]RX!TZTbTkIV|pZs9`tfyj,ieܖ^WIPܕMN{yStoqYg`cffimiyfya[̛V StSDbStR^\Y!Z,dU^YL`FYbSQ[dqR$k)OLkTtn Y|uQVtyM tDkCteHf1NKJa&[`iT;TH7aY+_AlUqVKQYt'!RC Q`YI,1YBENn9C<>wG qyF\~GQ9\ y ZM\aBF\hQQ$VX\[hi`hedyikf~ZLaMKsSlZ@79E79D$4G[Yh+р%9[ p7Yetain h3AMzLPf4OiF+0/ (4V._Jl_ZToQRsHlZa>DP5U9];yߩzT]$5W:]|DRdPR 2YpD0Qq|uN!BYo_pCc>_Mh(!;i>1?l4,36ÚA4z{Ti&Y3ᒼT9w}49uLlHr㚱))}D QɎ\ 0Di9{dm·&ԡ| |I+ѠJ9wItu]INÛqQ!|a<%9dP)4y7$yёê6z8s9tq,)Xq}vLh1<8VjDv"6\q{!F^p `uI1y0pBrd~`[1 L|AimA!hT Q頄PAK~JjM nDɰlD9Fy}$RQq>Y49ɕ\E)~<7!@Qh7 @lHj# ?|I"$bO AI$b(?H$]BvI j$^h\DI$ (?I$ < ?Ib# CI#Ԡ@H# ?&H#hR EI##BlI#ӈ @@H$ (Ơ>Ir$NH3 ;H$B( @~H$ AH$Hq?FH:# H#BIR$& =IZ$-<?:H$.?Iz$%(<€H#n=I$ 8>6H#h,@I$H ?XH$H5:ڀH#>I:$( >I $!( ANIJ#H;?2IJ#A2I#H?dH#Y BI$QEDI$ HG =@I#h? H#hʠ@jI$ 1 >΀H"#H ?΀H$HJ 9H$ @"H#a?fI$kH] ZI$ (?LH#HI 7&H$/(vBHI *$%y 6H# CH#h, @I#h`>ހH#h@I#H @tI$ ?nH:#ɨ. MhI)B$" H&H$6 >H2#h >H# ?hI"$)(C ?I"$:@ H$H >Hb$K B I*#I% (< IƀI#R#e BI $M >ڀH$8 ;H*$H FI "$&AԀI#h7 A0H#(bC4I $# <؀H%X CH"$87 AHb$O(>I:$"h' B^Hz# ?I2$Fh"?H$(۠>܀IZ$*>I$%k AHI$ /IQ#h>?*H$( BIr$ ? Hj#ޠ?0H#( ANI$  <~H#H~@I$%$AI#( AIj$H %8HB$Q>dH"$xhA؀H#h FI$ ?H2# BI *$ ?ƀI$"xBxI J$W BڀI J$H BI "$6DHJ$,B CFIJ#ȨȠ>hHr#@ CbI#h ?0I$ĠA I#h_?fI$& DI*$AI $qCfI $Q(kFIR$d7ĀI3$O4I #HTCH#H ?Hj#CHI#=ԀHZ# @fH $ B\I$ A*Ij$"h CրI$J/ DI $$H2 EI&:$[h^ P0I$~>>@Hr#ڨ >I$BH#H ?dH#(?XHj$ BVI b$&A&I2$G AI$?LH#H1:HB#TH# H$\ A$I $v"=ހH#B ?րI #HlB8H2#3CH$H0A(I $E8 EހI$JHT D6I$  CNI#H+=4H$ a9I #(BrI$H D^I$' Hj#HL >H2#H ?I"#ޠ?HZ#Hs=ƀHr# >HZ#>|H#HG=2H#>H#(N HPHr$?:`H$]A\I $B DIZ$  BjH$G;H${ ?I$0h BI$ >؀H#x > H#Ƞ?DH#( >TH#| >&H# >vH#(EtI*$ BRI:$  ?܀H#Ϡ?(H#?H#h >؀H#?"H#(?H*# =H #H4HIb$?(ˠ>8Ir$ahCI$:$EӠB2I*$R @I$h >BHb#h?H# @I$h5@I$/AI$a AIJ#à>ԀHj#hР?~H*$ @rI$@:Ir# ?xH#hmAI#( ?jIR$. @H$h6 @܀IJ$ @Iz$& D@I"$-hG?tHZ#>H$h2AI $ , @I$H^ AI Z$*( BH*#Ө EPI#HALH#q DIr$: @2I *$v AȀI$\ @H#(?IZ$(@I$$ AvI$]BI r$Mo E\I$H>DH# ?I$(G@IR$H@I:$#h̠DI$RhM?€I$'(Z @Ib$ ( H# @FI$?^H#H @IR$/ DI*$\ >\I#?H#(" @I$ >؀I $TDI$ A܀H#?rH#(<H#( >̀Hb$pBI $VIC~I $W BI z$!b @I$@*H#h >HR#HR =H# A~I *$7 DI$E HI$5AI2# @HJ#h ;tH# ?H:#h @΀I $OH DI#HU =H#! AI$hq AlI# (H$(JBI $2>*HZ#g;0H"$h A\H2$( FI!$GI$%5AƀIZ# @ȀI#(f =`H:$(UAЀI2$G@I$! DI# :܀HJ# ?TI$hv B>IJ$>8Hz# 9؀H#(;:HZ#?H$ hj9pI$Y G΀I$DAI$,e @pIz$+ AI$i AbI$*HCI $ @I$ =H*# =|H#hݠ@xIR$' BDI#xH"#? H#?FI:$ CI$-c?H# :̀H#o:H"#H =8HR#(% @LH*#h DI2$Hڠ9H$_hJI$"ؠ?Ir$3H AI $=^G$I$e(AI2$B2I$ H ?I*# ?I$Z@ЀH#h @܀I z$<) DI$/h@2H"#( ;bHj#:DH"#H =lH#FrH#A IIJ#M =I$(?I$\DPI#(* >Ir$ H.I*$4 AI$ AfI R$U FI"$!(3@ҀI$(k AIR$_@H# AI $(hh@H$(` CI"$OH97ހH ##?NI#H BI $>()?Hr#( EI $ E\I:$%`@H$QBĀI$IAI$(_B^I B$1ܠCI $")?@H:#GBDI b$T D؀Ir$L JI#h ;.HB#H?2H#H AʀIb$E$ =̀H$"gB,I*$HCI $  ?I*$ B I J$B@I$-H DI$>HΠB^I#H OrI7$ AHb#( ҀH*#H"A8I:$&q @H#AAI"$nBI 2$#CtI$K BRI$Qנ?IZ$hFI(I$e AI $ Ir$ FI%R$i( 8HJ#H6?ʀI:$3 CZI#Ƞ?HZ$7GFI$DN ?H*#H EI $ KXI*B$]h B6Ir#(S;HB#H ?I $rDHR#h B^I$DbIZ$BHN >H#BI2$rCRI$]>ހI"$ EI $7H AH#n?H$֠D I $)(s @\I$.hBI *$G(WD Ib# ?ZI$*H ?>H # JhI9$H D Iz$NH?8H2#" @(I$>I$ @RI $;( CI$Qh@ހH#API#C ?I "$( E4I $+(AH#( BbI$ k:ԀH2$ߠ=dH$ CI$D BHr#ߨ@H*$h IրI $Rhw @,IR# <I :$k?@0HJ#ܠ>H:#h A&I $* C܀I :$&H:$d @|I:$,@H# JI4$( EIj$ ;I$gBЀH#-@8I$4HyF2I B#H: CI $hwGRI*r$DI$H >I$hݠAH#^DI$H IրI$ E(IB$= HI*$=m~,KDj*h٠?Hb$Hj@0I *$TWDnI$H( IB$h @xIR$ H=CҀI$- D8I6$G܀I $ ?I *$8o @LI <\I$:K IZ$d( <H#h DHB#0 ;I"$(m 8H$aH9IB#y* @ 7i[nȳjh(-s0u)tfz}gBkBD]z},':jF*:[!eS2s^aE RH@.gs(ƚZ`Q$E <™.*"4,/GҼrV_rOC9CYd2Q%i"'b*)⪸r(FP_"%iNDqAc|6c)T.b8)i b ! "1$bQ&q"(+G"h$UDF9#IZ/b6(Y#aAAa b "1$ZrzJ^!QLQ>cj2<)Y"AAAaA ! #+R~hTD~7#H,"q#pA` A` ah pAa" "(J_!eIK9\E#l4.'BȤ ఠ ج   HDڜn%fSy=IH-i  !0 @Ц @  H b0h:s&J&WD?CaJ-a A  `hX``x`  A(Aha8)$犂iN)8B,'X P @~@~Hh  A0ah :JSB.n&Ri;#6(aP @Ф `p 8~(x(x0|Hp ز P!I<+"( Hఌ@v pnp t` z@pࠦ ضAX1}'&.^EcV1  @`z@ rnp t` z8h вH!!a)Vh!EMD!r7b%a  0t pr t` z8`  8xAЃG2>d aJ#b5b#A 8v r t`x(`P @x ض AP 3+glo" SDq<#)**b  Pฐ@`|(v` v z0P` @ ش! 0 AP pA д `  @ `aA B)ǪVk!ePDIt:"( H p8z0|8`Hh x ` ض  !(!Hhaa  !( H`PX@p ȴ ` A 0aHh!a " !) #HjvFzZٚCÙD0!A  p@P@P`PX h x ` ะ @и  !0 PpA" !)"#bI,%``h p า @и  ! 0H`!A !!#"A($Y2&)Jƒ':8eʼnMd1j9C "(B !` xx x` @ @ȶ  a A8 PAhAa !!#BA(%a2'x ` ` ؼ A !(@Xapa " !) #BA*%a4'<)0I*''22eM$1j:c&*0  @Т ਞ ࠞ @ ȶ a !(@Xapaa  "9&$Y0&By8(B*a ب `Ȧ @ ` Ȳ `غ A( a8PAh!!!!#"A&%Y0&By:(D*L,/22*eNAj;),+HA XA8A  ج Ь а @ظ ! A8 H`xaA B !) #BA(%a2'b:(D*N,"A ax P A0 @ `  !  ( a8 P!`xaaB !!#"A&$Y.&By8(‘@*bJ,T-\/4*Z*h*`s&YYER4B'b Aa X @ !(    ( A8 H`apA!  ")"#bI*%a2'by:(B*bL,T.^/# X5:."(ba#BaahH 0A  Aa ( !8 H `Ap!a  B !!""9&$Q,%i4'<)D+L,"V.^0Ch1+6i::fOiBl:aN24, 'bY#b A p!X H 88 !@P`ApaA"!"1$#bAP$G$1?#d7cAH12+ 'Y#! !h aXaXX hAxA!b):\u"& b%VMDz<\5#)F04+"'Y$! !aax!xaABo"fcXE1N$E=Cf7IP2>-b.)by &I" aA!ۀ2rZbvg.j!fB^TKqC|<f6AR1B-2)$&I#! !aaaA~gzLq#F e ZeYQ$ɲH$IAcz;f69V1F-6*('"Y$b1" a!!a.irď*hrNJTr*ff[qRٺIaB$=ér8a`3P0@,2)y&&"Q$1""!    ʐ*g^s4h"* ]SDKDE!?~:Cl6CAZ2CL.B>+2(y(&BY"$A#1#)"b*)~{&JRo#*d!eYQQJDqD?ɀ:n6cA^2 P/D,:)b2'q,&a(%bQ$%"J(rv%Hj&R` W1OIDiC?Ʉ:r6Id3V/"L-bB+<)6(B2'G|wgRl#j(b!EY%QQK$F9Acɐ;8#q~6Zx':fnFDfF*&_! XYRMDHaD!@#=;9)u'`m%Be#F&^!X %QRMdHDiEd1A?#ٞ<~s^kBdF&]W%IQMIyFACD@)Ǫv'*lnNg$&:4`"eZ rT!OdLHqE$B})u'jmƚNf$&*4_YrT)PMIdwBxp&\hR@b#E(\!ŚW RSOLh*~Gvg*vo&Zh$J@aE*\"X!b T %)Q-HR+'Ңy)'RqFfj%rLd$&6_"&ZW!%Z-:纞wBp'&fijNd$F:_%,[ebʂ+z)jrrl&fZf&JHb$& :^舭JȀʨy)Rr(tl&`g%fRPc0Hzԃg{gtnfnj&fH.(b҂,Gzzto0RЁ,'ڶz*rtg*p ;BҀGҺz炦u+:Ԁھ{1 H:ڀKhj2;1(3s.) L4 sRL @$@@ @ @@  @@ @@$@ A@ 0@  @@$@@0F0 Cd0 @$@@  Bdp9 `0 @@ @! D C`1 ` @!@!@ DQ X,A BD  G`0@!@!@ D H$ A X,A D BD0 @!D@!D@!H% DA H$ A` @ BD `` @ D@!@! DA H$ Q H$ `4:D0 BD P(@!D@ H$ Q H% DA H% A P)¡@@ B`   @!@  H$ DA H$ Q H$ D@! Ax(@ A@ @!Q H$ DA H$ DQ H%D@!P(B@P  @ D H% DA H$ DQ H%D@!@ @ PT(D@@ H% Q H$ DA H% @ @!@ P(РP(@ @ P) DA H$ DA H$ A@ @!@ @ @  @BP BP8 Q`H$ DQ H% D@!@!@ @ D@!D@P(E@ ( P 8PT H$@ D@!D@ @ @!@ @ A4 @( B@D@ @ @!@ @ @!@  A@G@P D @ @ @!@ @ @ @ @!P)E@  @PD E@ @!@!D"@ D@!@ @!D@ `0BD  D@ @!B!@ @ D@ @!E@  P  C@ D@!@!D@!@ @ @ DP(D@ @ @@ F@!D@!@ @ D@ @ @ D@!D@ @P(D@!D@ @!D@ D@!D@ @@ B`0D@ @!D@!@!D@!D@ @!D@ @!D@!P( A@ @ @!D@ @!@ D@!@ @ @  E@$@@ @ `!D@ D@ @ @ @!@ @ @ D@ @ @ P) E@ @ D@!D@!D@!D@!@ D@!@ @ @ @ @P(`0 p9 @ D@ @!@!@!D@ D@!@!D@!@ @ @ @ E@ D@ D@ @ @!D@ D@ @!D@ @ D@!@ A@ @P@  `0 @ @ @ @!D@ @ @!D@ @!D@ @!D@$ A@!@ @ D@ @ @!@ D@ @ @!@@ @@@`p9@ D@ @ @!@!D@!D@ @!D@ @!@@@@  Cp)D@!D@!D@ D@ @ @!D@ @!D@@  P  B`0E@ @ @ @ @!@ D@ @!@@@ A@$ A@  0 @D B@0E@@ @ @ @ @!@!D@ @@ @  A@$@ @$ A@$0 `0@@ @@$ B@ BD0 E@@!D@!D@ D@ @!@!@@ @ @  @P  D  @@!@!D@!D@ D@! A@$@ P @`0@ @ @@ @ @!@!D@ @$ @ @B@ B@ @@  @ @ @ @$@$@ @@`4 @ D BD@@   D@!D@ @@@@`0 @@ B@ @E@@ D@ @$ A@   P  @ @$  @!E@ A@ A`0@PBD B@ @ P( @ @  @@ @@ D B@ @A@ P(H$ @ @@ @@@B@ @ @@@$A H$ @ @ B@ D@ A  @  @ D D @$ A @@ @@B@ B@ @  @P D B@  A@ @@ @  B@ C  @ @@ @ B@ d P P  @0 A@@ BD  @ @ @@BD  C@ @ @@0 A@$ @@ d @P P  0A8@@ @ 0 8@ @$ @P @ A`x4@@ X$ @P @Px4 @@ @A`4 @ @  @@ @@$ @ A`0A@  `0 `4 7777BUFR4b4~hd!]}L!bV@ .dEF F (!I@ @ @`!Aa@!@A@a@@@!AaAa!aĂBb"Bb"Bb@B@b@@@@AA"ABAbAACDD"DBDbDDb€"Bbƒ"Bbb"Bb"Bb‚¢"BbÂâ"BbĂģc#Cc#Cc#Cc#Cc@@@@AA#ACAcAAAABB#BCBcBBBBCC#CCCcCCCCDD#DCDcDDDÀÀ#CcÁ#CcÂ#CcÃ#Cc#Cc#Ccƒ£#CcÃã#Ccăģ$Dd$Dd$Dd$Dd@@@@AA$ADAdAAAABB$BDBdBBBBCC$CDCdCCCCDD$DDDdDDDDdĀ$Ddā$DdĂ$Ddă$DdĄd$Dd$Dd„¤$DdÄä$DdĄĤEe%Ee%Ee%Ee%Ee%@E@e@@@@AA%AEAeAAAABB%BEBeBBBBCC%CECeCCCCDD%DEDeDDDDEE%EEeEeŀ%EeŁ%Eeł%EeŃ%Eeń%EUEUe%Ee%Ee…¥%EeÅå%Eeąĥ%EUeuev&Ff&Ff&Ff&Ff&Ffv%v%%@@AA&AFAfAAAABB&BFBfBBBBCC&CFCfCCCCDD&DFDfDDDDEE&EFEfEEEeee&FfƁ&FfƂ&Ffƃ&FfƄ&Ff&Ff&Ff†¦&FfÆæ&FfĆĦ&FfņŦ'Gg'Gg'Gg'Gg'Gg&A'AGAgAAAABB'BGBgBBBBCC'CGCgCCCCDD'DGDgDDDDEE'EGEgEEEEFeWegGgǁ'Ggǂ'Ggǃ'GgDŽׄ'7GWgwDž'7GǤפ'7GWgwץ'7GWGg'Gg‡§'GgÇç'GWgwćėħķ'7GWgwŇŗŧŷ'7GWg'7GWgw'7GWgw'7GWgxh(Hh(Hh(8HXhx(8HXhx(8HXhx####$$$($8$H$X$h$x$$$$$$$$%%%(%8%H%X%h%x%%%%%%%%&&&(&8&H&X&h&x&&AAAABB(BHBhBBBBCC(CHChCCCCCCCDDD(D8DHDXDhDxDDDDDDDDEEE(E8EHEXEhExEEEEEEEEFFF(F8FHFXFhFxFFFFcccccccddd(d8dHdXdhdxddddddddeee(e8eHeXehexeeeeeeeefff(f8fHfXfhfxfffffȁȁ(HhȂ(Hhxȃ؃(8HXhxȄ؄(8HXhxȅ؅(8HXhxȆأhxȣأ(8HXhxȤؤ(8HXhxȥإ(8HXhxȦئ(Hhˆ¨(HXhxÈØèø(8HXhxĈĘĨĸ(8HXhxňŘŨŸ(8HXhxƈƘƨƸ8HXhx(8HXhx(8HXhx(8HXhx )Ii )9IYiy )9IYiy )9IYiy )9IYiy )9"""# ##)#9#I#Y#i#y########$ $$)$9$I$Y$i$y$$$$$$$$% %%)%9%I%Y%i%y%%%%%%%%& &&)&9&I&Y&i&y&&&&&&&&' '')'9'IAAB B)BIBiBBBBBBBC CC)C9CICYCiCyCCCCCCCCD DD)D9DIDYDiDyDDDDDDDDE EE)E9EIEYEiEyEEEEEEEEF Fbbbbbbbc cc)c9cIcYcicyccccccccd dd)d9dIdYdidydddddddde ee)e9eIeYeieyeeeɁ )Iiyɂق )9IYiyɃك )9IYiyɄل )9IYiiyɢ٢ )9IYiyɣ٣ )9IYiyɤ٤ )9I )Iiy‰™©¹ )9IYiyÉÙéù )9IYiyĉęĩĹ )iy )9IYiy )9IYiy JZjz *:JZjz *:JZjz"z""""""""# ##*#:#J#Z#j#z########$ $$*$:$J$Z$j$z$$$$$$$BBBBBBBC CC*C:CJCZCjCzCCCCCCCCD DD*D:DJDZDjDzDDDDDDbbbbbc cc*c:cJcZcjczccccccccd dd*d:dJdZdjdzdddddʂڂ *:JZjzʃڃ *:JZjzʢ *:JZjzʣڣ *:JZjz *:JZjzÊÚêú *:JZjzĊĚĪĺ*:JZjz *:JZjz;K[k{ +;K[k{#K#[#k#{########$ $$+$;$K$[$k${$$$C[CkC{CCCCCCCCD DD+D;DKD[DkD{DDDckc{ccccccccd dd+d;dKd[dkd{ddd{˃ۃ +;K[k{ˣۣ +;K[k{ëû +;K[k{ċěī +;K[k{ ,<L\l|###$ $$,$<$L$\$l$|$$$CCD DD,D!Y1 )0j)c- fXi iD ;E*a z_:#XҾ 8(xzPx D)}pK@GtRS`..4}%"$8OB&P0l ks4rS=GVІ@n(Jp)f zC%AGX `_\10;e@ وDd(ŁS [" ˶6RAm\ṽъeEO 0%ϠfH9 Q-A ,p.÷mQIcTUEh,uW d#<܎:ɏz5! tאDO`DVpeవD2Ძnz=! UN^3XdEU-!1>H|FE8ܑd`AZ \ L # O³RP 48!Hd$q4 |Sh`&/х ^dc2fƶ\68r%asǎ=OQDl A ơ HhXD'sR$xV$/I$ B'D[,T±ȳ3.}z 4b[h9ƌ5= oc@Fe\<ݏm8}t 9f>ZB͒6 ĊcW#QRISc}`P(1>A$)wf g2o~+0ӈ"lDq>H„:(!qG t,UP-XE .}Ɍ$b#$3ƙB5AhpZg@qC3" b9`i#!;%JRV IK"m|װ+*6AX{YzBe60p fE#F@7ĊZ8t#80G>~q 43!^1bȎ/ Vdg#00Ft6kA#hqSNJ=0=8t$ 4 #dpC&kčvI$$b >6J]9 Cn)Аģ%6A PPEz,l5^o t(R3o1C Hdkcm& +(:2qPyxZ6@̇ E"RݺGȒE_&32(FpjDE ^@@3^z o! )E<Qؕʐ'-@ 6Si" Ţb-Av D`Cz4 nisGS;nFG{#qH+hB6dY#!'(?"f!J"cT8E)Sg$X5B$帺0RQ\ e;?FՌZ7i1s<sǟ=A„! HxDR+^䀊$ry&SKrbsJH'ܱBJBVυ0a 8k:t}ꞇ@>!Y(.L2#: :K"i{p"$6)QV{ YbdE0| ,f}Afw# 7y}It㭚h)Ǭt>9d!; =rE.,*$y(_Kעeb*( CD`LE͔ M xGY,pS,Io b0h\pٔ9Á}G}!oG:bGf8ߨ'QEWaTOz.q{L Hb48e΄ƏL5RYoSŠ鋇g<gq}>n (BB/T0k)A[+ D`]0:70!xun̡^D2i&:8R!i_]-Ep x_# k`We4 mmCvT8^>Dp7( :2`x%pa9 3U¤P#'d2A poÚD0 @aBkq$e1) M"|"LI +#_΋4\[UB$(!$1QόhcT  42~%"!$I OBԔ0P jp\ =āIHJ#a1KAgn)aUtX>0\0f8?Y8-^ f2gC:P*[`q n^Cbpl7Hy sHEFb7)8h&Q="RÂ}-Qs| `@1J|vL! @L2B|TЬ.! whg St jH;!*X*"1!9IlX2؜l(N!Vı >/Q¤@k90ZpT%56 $Tỳ02 o``ͨ@bE0NDm4$B( | MBz+j acC%p9|D a FbAzx,T'zABITA;H [ BW0,kp b2*g`9a^M} f!"GPBGp'ܱEc $3 S¸лg28A# en"O )? ؉gf^$ &nMv_GXPR,CAo b/TU9D}}!1 G$Fj'ЙE)Z B]hj ``7a30xI[tD1 1DL&{L*q` rx^CTk7sΠyC "ttEL7*Ȕf,tv c9Ƈ:Mj~D![”GKgQ3^ 0_ l{hP;$>mBrRr#i!F8)^XV -\VͶ86!éwCyQLD6/G@BP2! wontU0? Љjx$,,ǰ/題 xiCi$t=T3,D@"ޑ.!B h [.,W͝d2 d%D07!$0ʡB@yɈ0 ~\;vC6MrD9T\raC\7A nyCo~\`5!3 YjFCNJOх3A K @9>E|v0@1=*xCo;:t:/9@A.xpCj876a m.`TBqb Q @6؀&C>8||0@$= m"8x h|;AtRp1h9o!ب8R"zDR PZ7!a YB^  @Bh؀&C^\>ׁ|B@iX(%'C2IWH9\.$Q ",IZ"n(E)|)2Hۊ@QZmE|(iQB P3B @!'A;\N}r,h&4 Lc; p焻%-, c Jl]ЫӅ[|*U[T9%HEC)O( sSf()).1H =|Q Q (PQA

'=b (O"wp'HA9m,N Bovb&H =QɂdP(Dj 0P")ET('@ PB~a''=#Ow5(Kn,$`dW"Jo6+`Z1 HV(BqEU*qSˊTTbj=)QN qpSlBȦ//)oKT$R".h[!(GK7*qPT S7@07)M'-YWH E ,jQb XB:^+_w WKEsP+[ VǢ*HEd+XV ,UギTl`7EW*U6 U;EK/&Qx@]BZy.or`\L"зr-1laHZ"L-7iT GZ#7z(,f ,(YKbzD|,]blXgBr %l+^WQHjr$+[ ڄVB*e+#X\"Ŋ.Ap P[bP@-mU g$[ ū,-IiKZBBJŝN,ff 0YaBBJ@ ,kbX÷, q`W"Я 0q ` /С} _./'x ^ wux .qu ]?BEZ.^Qr \kbz 4E-o#u[ۤx-kYZԜ$-1hA ?Ybul?l,Qe_'Y)Ȁ>/~ (_"zȾz/{ ^B!`/ x`]o8;E|.u1]B8.HqqƋ\<\-فn[p[hڪHLŰ-z1kz XZԵťP1qDtcyoȈ,x1JG\b j00B &a,; 0RQ" T`V EZ/ _Ր/{`^pх/x^ ]Bhֺ.t ]u60.Jq \L"E-ao h,c+(F,1Xъ{PXbiJ 0[8 a _]³@0;`)(F\0+A,`b{t/q} 8_-X/Pz9 D^_b35E.qwWd]^к.afc3= F^F2Q3$e2C(5wJ2G猌dG!@F>1!tHcC֘|21!ٌ[HbC[%1# AaCqP0[ (a$E; D0O1 `JVp`/~ (_B /| H^B4`5k3V1Mfg2uE`2xec,`U2A~ 4d&/QH2<񑃌d. 8<1Joc`V˨%/1n UlbC|"P1ᇿ:xa _:0Q!`#4n J0Eс `M#XF4!V% 1Q qcvϐƟ416liL=]8ҷƔ4u hF0S>4=q h9C@܆}T3Atgw; `eqj3~ћ tf4Fd3are.tn(9X86Jql_a5 k(kFYlư5k UHjSԥ5D?icNcl;H4V(\i&H=4XhhkCByP:Ft3!Hg#FEr8gLpP8% oc~ GB:~sރ^@FG99͒is<.z9gQT\rC#9a@>q#AmX88k=czfG=L19}`ժ>}9>TAW|m# W?~cU0G?NA~^s K0>}_fI@Hr@; @<$d q?.i`N?0dt wHd@RC0$kD Pf@2ސ8$ *a'H5A c Y+AX" iQXq s"lAr$Z!YˈDBx ;ArEĔ$0!| \B2+{!W) SrB:H|d;f!չsC<߈6! biCE#x$F$"+>D H l_@^ 0( @H% Ea`Z.L IaQAP K`(  p;XtzY6@@S*H A@ h6aɀi`8ppF`Y@/ T`H%h AR@@[P0d N烚A % &}E# 4dxj8:O?) `NP'p\ ph:b#/X@,0 *@Q+D FU|AP!n h?` [h1(  `P^ad7`y[FX/, @D# *A @jp6,_4 xaɀq@:`pH 2V^ 1( TpF%j @mx8XXb2 xvɀq@=0pH lX ^ 20 Tހ@J( ~l8xqL2  xaɀh8pdFW/ eƀJ((l ?ۂAȠg6Lv 3% ^6a`2`P(/)-Q+$  # w Ap$ uցk@ `X2 heAڰ8d~D% L&}?" li@a20LqjE W,d Y"A`ۀ(3&@0 {R@Z2`p(j)Q+ FUÀK'0\ {^`a2\ Ov08d̀t?@$p jq>!H lo@@j8p sE Uh+X` +A@0hưRD7@&@` X /`2 Q+  A@L'8b Ilbp2lpP D~O20dSK`&8h ,?t܀`pG% ]@]@0x  p+L2` 6ɀq ;!0 @s> <Z9@T, %@pVg4&@ X)/X@2`hH_h7H,h$' @`Nx*< mΠj6d " ph~K, X(S`H^ 2H TAH0 ^A m F( phY3XO^ 20( Tr\h~ 1܋i8Q*PiyB0$7? UpO'czFt]-%X O$ Б54W"m =#xpM.Z m"*12,L7~zWT0-C BY!F0jg3&@p d/`E %D 4~SHsA#W aǐT3:VG䪺#g ۊJYZm(]EAހ(h 3~jsK<$c1;ڱHІ82 {jE(XypL4'1sTm9LYD  @O( w`8 h@A+@0  KpA >81 ,1q*䩮K7aMDeHtP 5x/=MНEH<_!lm.EY!/TP 0 uÐAZu?~)(F3i69ɘRQS0T'[%i!̜^̥˜I}e|8B`P4Z》S, 8 KB0vhtp-2,"oXG :[F%hRG >+Zi<%2Gf3̆"G+PU; /a btρ7C0̓HA+8 I1c!N@e3 hq,A:<3PhV,%)g @`p@L,  &oB$2,@2ER7b[ҲTv]iU; ilо&ǏZj엍:1ZJ0cy;N eZHJ3_C~2t4@3 4rx җ6rЍAH$yuK͆8xX\/xw*;9y)'Ѵ׃s~8G Gdiu~&uEEfIʆtª,ۑ6ܭI"G.) 0 !~D % ` '2FbpYBߨQT j'ɊnEIVpH6Զ86y'[}QQ'):_Ve^noQtz\go'hPioy7g@ZF!6O8!=N&E= ʘ*gU!uI|tAԅIě\e$Kz=PHGoiջh nxo77F\~ 3uѻhm{ge2 Hہ4AwlDubγrv5@E X$/Apg;h z ԓk_Nv +,QW L+jAE`,חfͶBBv@er@y\Km0;e-;[HO?\\~ Hu|4”@4 #˲o5{*tk5Y VJK^iƉ$0GTNcwBṳyt7x4 VvDθ+ w] ![H$&>`sm"{at-"JKHyL-P&5H7>E7e/3Ih=fo7^N<ߋuW0Dgcɮ w6Y³bbڂ4"cs0G[i5e*3╈og$Kt6@Nc3TغN[o6R=w5Bٺl0@`51v1"s|XXAXgRPhL" / o*oGd}.\.7HSb[p ]H[DyR7,#K^w+x%1Q]1g&\ߞ :{=P^O˺fw.B 57-;Zl::0ߧcMlz ݝfHnǃQ|n%vs<dnUK:ՎAgΉij>Ux&ε^/>ffme?K'_}Ytc5O\O+ ߰t_&M2_ؠ,KUqV=B=N0BG۩8x8ڂ Ký"v8ږ Í+Vlw76\")M4|ki #}ZPl/gvl)ٽ(tgT HlOeMʙg6\97Øѧx̳p+ *=Jޔ7#Ffs[8 L>$7w7ylrty6dm :ƕuC^"D0<-OB`OͰ٥n;\MZU"V58ިHT|f"(LBq,f4x MOtP>J LKpȥ7Ad`ħt4ayE&s8= F1BeW!SjKʪp+8G; ׋;eVJLRMVLNgє,[h--к&dzdM2() 9agARd]6 /D;[Ud4cfbrROn=ɋS$A]DvK$`.h< H[.*Igbs4Yڦ =QujqYE.ፍHrc!# ULۇo@R,ġqՒm|sݡ IiB0ĺ$' xO"F:Ad%2-QMrt"a)j Vg8= &a<>`%K`d#si 4PĘ<6>!Rxf. xs5|PFa`kC8Շ$>b' E4Ӑ4d+~ rS&VR7C>V1ޘȂ`T)AQ Pd Zc*PM¯>"jhɓ͖t! 94l G  ,aD+q {R&iTg&ģe0Mx b#@%&@5$@T.!4]YYɖUDE_ԮOJĆp@X/`R 0^]ɮVFi-u͏@!TpAR0Np؇vA?S2U~Aa&(1h' @O&`<<9?:F;79琼+{Lo`K F- U Cl(d7h `e$aU Xqd $HKxf#1 aQX>=Ea\p@Jp tL'?BXˇLBrYƏ:@-stK렍" :L" p`; (mD&l0yM&] ,l3 !iVZ|tRMT9 TxPlU,A༇0EGrɦ= R50Yn0z A خP@   2!8@94YѪo vl_D;at2`T @NP6p k&l 0yDQ' _ Bm!9#W*/R(T ;$ @L`L1 \` # B\4phKҗF2@q @A `e2 K|Lҳ(q> 2$'uIYn˖!|S)F7Ϗ:1)&PE1͞}pK_|FfG眵jX > {h:X戬R" L2٭v K:uŚ.̰ioǷ1B,s>N cY4ESħ.Mnl`F:LS<@n cc<GR|d$i-k3$0 @ @J4!U ()Ae gz~HRfdŶ0Ijq5GZ]u,ekLvgl=Px%39RVK21t{ #)+ T2ȗƦ7)Џ@2)&P% xp@x(C12yOՅ! ; l ;"E:-1 pc Mb31N0wS iKzw.͆,io@=fi>:JBv rC"A+ WxڇP>*ig dKg@RѪ(eQk[ϖ%&})˂q?ks0|" @P)𡅤20'aU c#S"ERM|.YLlBEBD-+5ha/Z܇*;2* c˼cBt?Qt$ƧMZ֨i̮|<oXvi]XCn;(@9B p2`D'VcCTEKxs.kÊ/D>Ĝ*d`&4݃D#_%Y~ζenrfE J L ] b ! ZC-ר%LJXNVt_5 h#L o#? bCbLb@l6Dm%>JXF9ԄW$U/R²ր2ŦrHGD9rmhRڒ0tQq`0@86Al H{@ROPT?,أ|/1s  >NR並3AN(v Jq`.-ha `\@#KFx:!8E+at|jiȖHih/ FohmFzMS>=I WP~@-^ S`h9🅀0~DH%? \Cpᇂ? *gcF'?zbP" 8 c@h7A, XtC"!' Uц;̎d'P]g";& 0j NN`8py #!2 p[DNت0ѡ v#!QIVN31-!R LD` !(CB4l. v"(UBX:$'!JJ[TGth+aL LV`%a5 U`ч < x*n 0gsPb`(DT9 qC?p(WX_)r>p(N!a>| !|FbI E@,1t (5ڣUcԢgN @((+TZQFI]/|  Bu"lBSL A)hQt*ֻ9s=g t(F )tUz]16^S.1ctV6c6&✳v?H-TBYK3Q8J@i,.hI6F|: Gie<)|S2k!>!|TT r0$ƙ(f18lTSZVki2Ʈ݃߁8J)B)#Zqo.%ؿ8l9?q֚e Q;" sf?a&E TJc.fS{Ђ3JS,ɚv/ X*@kA7&d2zRr1fTҒyTk9}2v].?K9,xBbA?q !D8BlR}3fN£Y5lr^ A6%tT q6G#HO s2fLʥZL )Jc1uA4)Řc4mEM-8sҊ}UKav^dOMI`c p.CbtTa7 %%$dvP+JX [{2 y[2$̴x)(I d1!cHvNR Y6y#4Ucm1'℆2lJ6 PvpY D f3GI%,Szy=MJacRs~Ю0yg3 @@$`#4uZTK8g-Ͻ8ӆZ  D > AD @ h4&lZYPsߝ惑JDK20(@%a + 'x nO"GLxy=5០\pd`c$t\TѢ9%hMԽ%c*( ^kx]GJ g1 p^׀a\aB<8`0< AQ K!;ȁ2*҂~Uu{0Nsq=!\EgκH[Qj]-%4i͝9hD*HK0fƦ֐.X @< ^hXWAAK.Xt"TMJL+e- G~FhdĴ:W+4f肐5Ii<(L`yzAucoO-Lukl 0&(-&Ő 2^ Y6'jCK)Cj"b5[{ #FvКKPjzŌs4k=dxBRaD !T*ؐ#tR$E%%ԟX az1\cuIAȁ$t*V n/&Pl;&1/,lt~jcUƼxc dX2NJɉ8' \3FzD#%ܠXKaw0V8SboNE<7`t'1n:S1&Ʀ֋ h-پGdϡChk ?>-(؄hM 9' "Y qz0HϚxr?9#4SbR_UX.hu8H^E8,C}@M D(,bYKiw0&0Sdpy="flP QT+դem 6D!>)ŀHpD 4(e4XAo.C@j: $$GK)@)5N_E3*EC4maA$&Z is/F3:i͙9dd 27I)m6&\釲Q>'ba 8Gh!$\U j.%Se 63{O CH U h.E/(xtRC,'e(b^ 1\s`nN;G $ r>I85,U#e 70$2`Oq\-E"fM 6&îxqAY#DuPaU8PDRM!R,%c"f6FӬxY@h=rhO*)M?!#ҔV )r00˚Vl9XZD $伛SS<"ZN !Q+ŬS$gMI7szAQ"rҪdNj IlPJiY,ȿ0i y88|BHu#DkO*!LU m/FəVm:3ЊDQ倵ccѦ6FCzO@E T`:RK=(,]L2t>G!4Qz:HA.&ꠔ2՛ss>4z9H=-&T䟔Z5Fîxq@= D\NK)8' 9GTrDh$4nOJHVuN>!tDQCIa1U Tв!E!$԰jxPj9M*edZD($mO G)JUD($mO G)HZh!$԰jxPj=LE`:[9,DܞT"SJX+L=(U 2Wi-ҽSR*qS%z]KՀ0(JW%j-ֽ c [, ^ 1Jr\k|F31\}f"C3g%1TZLj쉖ӚehE6vSupǹ<]+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>2%>>>>>>>>>M X>>>>>>>>>>a>>>>>>>>>>8>>>>>>>>> >>>>>>>>>$@>>>>>>>>>>>>>>>>>>#>>>>>>>>>>辏>>>>>><4(XZ<=IazFʢg-B9@P{Hx `X>>>>>>2d@Z hd x +%IsB>>>>>#p2 ( @ P / @ >>>>(P@( @ xP)6b (Px>>>>2@ <(" (8M>>>32(F P8Iن(@x>>>s(P P J @ >3p  #K ( >5 Z9 ( PP>蓃 @P ԗ% (( ( (P( PF hZ@(H( F ((  <( yoYmUګ RV)+d*@*"P|N' zMЩ^L`&pSb)FԦ}?,^QPhgBE+J3OmܕJŊbutaYkv\ދq 9c·zn7˜Wt'hE u)҈N.XQS<vi5ԛcqxwٌFNavr :|˨0?:3#N(OɊ5jsfNafYn֝+^i#qa94ϗ`1L&3oApv{@WwQfitblA Uo+V ˸gͽVÌt)v 3RypɁtGCT*+z_\详g{`A}n.8yuV[R)! erYu jf5\NGb#5dM"ԭꬅԩtPaزmdir59l!v1RJ%w IrxRi{7fOy Ar9(:OhUc:KdpU4 \Λv(tZI$4QTC"x%Bl†g6!bH?8(ؚ|P/)u5홀d3Miz%ĠtIUDX2 HhxR;XHTsrdIܱ_y3F} ]M(?ڪܭx8bYM5+n=:eʸ6^0X଺Q$ܘp<4J@ Α_`4x Hwl:.PTȘDZ:T|+փC nh5d6' ):uV9.C}M$K Y 8NO+ FciH&JG%R!k_12Y1ᖔ#[q\9]G83U0//õa蘃M.|HR|no;_ƄBf@ U,gF }i[.W1t":yyXc)" GѭxbO,82}QJ;~n͉qa&f$~r9UΘZ.Hl$ m@${T1#Z!fD$$ ە_~N]QXqJUo8Ӯ`L" @4NL$9J&`ZXľ1u˼n:hf@*>V,v+& aj! "-0Wwz@ {FtCт2=`4Q# T?<%+XQR*+UkB݇xȐf4lӈuvX%  F|S.+1MGNNL\'juwa1vLfg3<Ӓjܵ<{ǂxt.uoi^/BlHf7%INh4Z jkQpc%ͮP{E@xx|!k섑zV1O(ԩ YB%a[.@s  ы@e-)CUٟm7i[wAJu^;.骉F$. u:vASlseYorZ^{BlH67K$іh4MhjHަpI܋cg@CQ*Rw<-K?׬C.*QdJ)eL^ƭsY5D:*zd8rQBbʶqg\.WBtf]$m4ƫ^aN۩n7\16*Kuv:;W#>4 PtqT7"K&j"ZITzWJ|8bQ1Y,o@Y}kL,VFkm]s:j{ \Vy=b(P&c~n#@*QT,9M3A|x~A0!y*TM-\N(baUH"]xZ-0e } d&򆊣N D׭l޶𛷭 ۍʰ(tx]'%J{,.FCaDd}@/$x I!+M'=N>[YYq +%3-cg|,&J%Zlf6ۚۊLt :xs՗xsz=ȟXb/6Wraq ēP,ᦫs98dyF>c wsTT7"rtKʦ eJSD5K#͍g4zahNp{\yZg;w-85{}%xHX- VaLs;Ot% Hӥ1IGT++R=s*_I0AmL\@)Nh4ڧtʫm58p8\jwCw?^TOCwii}XP"}> n;HW)5ԠzS+ K Sl8':i8dEJri@Ըhy:0 Q{uʶqÑg \ZWLkzɋe3ZM$3Yձڂn?7 "S2pQ*#>σi6tNljQЃClԚY2ƜfO(T~U"q`&Z-L ͥs&ldX٦,3(G>#~㤁Ka+hN(fJ%w`c3l}M3Y@|@ Ψ4e*?%LaS*"jSȬW,|kqeY} agXdbY04O(p sq a2GMNà Ty>kbaI0LJ dŽL7U+&K<Ųiv`0JdRp5N_' 54B[[W.ؘ XfXAHp9;#r ER=)MJj:RQvof8\RIT|*ԋ }<2d3EMM|3Htz#>^^F/EnJBSG}J U[+W (E:T*fEʲeb [Q.L"2+XԪl7{3!xB6wH餋Q=.@NYFNU<YmX- e˙i8\v.Ռ#F/3'LҠk7cZ8A!tEq(\LܧS*$ZSaWz^˕բa|a1?LƌWy@ަq: %GB-zuD"Q։tJ[y3$HOzejmK]a&1L&ZS9ytZl7T1_SӽQ<*@աha;#5Kr&`ӆI M4(UٙtLFKBnm8&ΌiCƉA59(d<#[Kcӆi LШUbS潳jr:zN%҃?BӢR(D|H)F0Lާ bIQn[7ǜ=N'AC)`{>͟?HCD4R#y|H Ҡwq=֡cQ #`4w<ϡG4r D#Qi tbU/JNXT0?e2x/<՞ ҆|DHBN,VMX'BSe {>P (!21vEy#TZ12N'BjF5|APt$H, H$Ivo!;:Q;,T( ] "&Dh"DHQ(6'L&I"J.RB]tS0mH r4l:nBP(Գ {TJ*bG$@RVGb`44OBjA1:>GG*жC vcLi5͈5"UP!N$+teǘcff@i6Z";MH]Z.F =_Z 44T͜Ӕjtz$ChoSw1aFuHr^C;4,՜sua =fǠ$PjBgq"tHʉS-'`"Eh%ͨcTQ524G '%VڗBeM)6tԜuPNЉY',byzOWm>z'`TP. iAW(3%l %P :DLD](TNturN =>'TPU*BO (5%P D,(2\TNQLJ)F(QXv-w%'&MTj"PJmC (vܢYQQmJ/F9(Nan Q9GN(5ޣyhQ>G( jkJ_O5_g@jXšS9JeLt0h2Rj\Km-r:[RȪXK_+pbR VJ)WU*]TB sĂSt əWY+ZHbUTIQ-CSUS{OAU;DZʝSrNG58n߂SvjnaM)6HTšSZJjMS*'DRu ҡMT ʃ=PF@>uS|Os)=ZSw9Nө:֧UT̙UzƴrS[ Hye,D~"Vi WUXɕPJU=S*rMU/ҥTRP*DG T[ʊQ*!CΨl "MT$Pd @zS Y&+UaTUyUjEVd*ÕWJUj/U8*9Ts*P4;*TJuS[5JةI&TQ*;GUZTYQ8*$s#[{uj-28zݓZ+?ecfŅlҺmW'[+djܭ$ VHqXv+_fUsUJV@*%WUUjKU= TPU>TJS]hEL.JT*R|*LƯڽ2WI]+5pdմVYʫ.dkjU꽙Wv[bhRUʯ-U*UZPU/TxPΪ ?T EEC87pi;ًa^*N[ MgmV0ĤXj`a+}aWT\vunBVJY+,dvlVW*\zl6U -V*WWҪUMT*Rҵz[Y 3e,5* X[)`P}PmzWdjI\ëup.նV*өZ(+,>XebD=52 rX JA_+|ݫۘoӭ5fZ{;!f|̵U7˜a; -h*B^ku.eήWW"[xnF- 2qZ Kh- 5,l2̊Ys*d⬏E&3ŷr-7DfӐNEhl536V`^KJbH W_7Kz1/ULX\딅q귙h[? byk-aƴ֔фZ+=,HПN3̾wmw2+klEMeƄ /e,lF,<Ăq f` L/uW$^ vA. 'S\4moMz%)'E_3 n/f)>g y2|vEǔ+b;8W0vVPj_%kڥz1/!z_] rl45X FхѐSO>]ͳt=[8|2^!jˍ<-4vw@q+6dL)1*sr ` a׽^gw5j?O}o}̼8Go6 oq Y, śQi]IS3fg>˞N%3dst1Sv#zÖXY+` 1.]^JkwC^ !(y:w@\d+/oIǑ۶"kGAh, JVY [ c bz&ۘDs`d MH׫S|Sӻghn\˕qi2ּV6i'=fr"~+rdL}10:3،aul#0;VdWܺz9/c)QNܮیp #60׵Ri-E3–nt̮q*dL!N/'Xa{$M0C7441tz-ɸG :۪n(l捂MBf>Z >g=q2vQGY  cV ^!;˹2uNWSvvm덢Ƭ4$ZYKEh!ٛ3:F_ >$Bdv)1uv(gu֎M9V'twsnQܖԤjH3hn+3avd<ˉS&dolh]*7r.6&7vn[#eAkgE96hW*s_Ck |5v^e{`gU:ג ^7xujW_H|˯&^k{viU쒽|W*W^kz2A疼י^UɅy"S׈^kE}e56sW˪_| {W u^ëEzR龽$W*^ukuy,W^-ĵxu U WJQ_?+|56׿^۵{PdiW ^kѕz U]W_ {w2W)^+ zM"W^kEyg(5䂼|׍o^%íxTuWҊ_4+-|~UWE^+څ{)_^UרZ^ey7zWa^BMxȯ2ׄJG_g+}D`WJ_+{wJW)^+ zM"W^kEyg(5䂼|׍o^%íxTuʾWժ]_@|JW^+{Pe5kת ^z <W^L+Ux➼CWZ_~kE}W_%+E|B諾W*^إzX5n:WE^~έy1垼ג ^6+ŽxR_m]}_um _+e|z׵ZS^ֽzPzW^q+yx*5¼W^)5xeFߚ_hk}Iƾbɪ_{xu^׳3^k=zN:W^kmyg(5䂼|׍o^%íxT`WZ_ok}_um _+e|{5׵Z_^zQU钽W^q+yx*¼׎^)5xejW_}u>z_'|Guᄑ׹z^kzXꂽ:WO^ky1垼ג ^6+ŽxRׂ:s^ +µ_Nkm|ᯖ&. ^km{ku쾽׭m^%z=B5箼W:^UɅy5"S׈j^+x#Uຼ _v+e}{̺?_=|~&׷^ǫםz̯T)עz^vͥy-W^-ĵxu μׁ*]^km~ W֪}_D5|^={`f&qWj!^+z>*ח^M}x➼CWZ^x6WJ_k+}ThWZ_k {y5W=^+ezOUNנj^nk̝ym(䖼׍{^%íxTub׀_}+E}Wju_#|7W^ΫuzWB4פ:9^|+Uy0urWZ^3kex &Wz^~ ʾWժ]_@|JW^{PdiW^kѕz<ח*^Ik%xޯr=ׅs^+w5ҾW_Q+|쯘R4W!^+{l꽊W^k}zHCڼך^Xy6VW^kEx ➼KW:o_b}3nWJ_ +{ԯuײ^]zKʽWZ^f+˕yQ'u䮼WJ^8Ɲx¯UrEׇ^xW}]+̝y}-^WjA^Cx"[׊z-^!íxe μׁ*]=w֮uC_]kM}5MW_U{rױJ^kztI芼מZ^ouy1⼱W^Jy 5㎼i׌*e^(kčx~'׃]^kmwU߆| k^MzSHu趽Wj^x+Uy4uNז^Qɵy%"wW^/kexּ2ׅJ^kEx޻}]U}㯶.:_6}|׽[^kڭ{/`uvXש^+zNG芽ן^uk%y3"Wz^PkɅy!Ut׍^, x¼/ׄ}^ xUʻ}]k-zTV?צ^mz@ڼWs^jk̝y.UrWjK^Ek%x6^׊9^"xj U&ׂ1^뿽w.{ ?_t=}{{̺?_=|}׷^kםzݯXڽPW^+uz=DFמ^q+ͥy1⼳וz^L+y 5㦼lWo^(kčx~'׃g^xU߆W|jk]꫼wbtW*^ԭzMf$W*9^+ϵy9י^\yL'u䮼אJ^8ƝxrEׇ^Mx9v]+wޖWxJ_ok}e56pZ_k|{ν׵^+{ ^uvfWjA^zdIסz^|+yį6掼ח^U5y0$>׎^2+Žx:׆Z^+x#U ~z]wU>Wj^M{_붽k׬W^-zoK"W*^~-yկ7溼W^Xʍy;%ujWJ^4x5F=׆^kx.6W~]kwRw_v+e}~W K_km|!~&׸z^+U{4c&|W*^5zOU馽)ף[^5y<*ך:'^_kmy\(ڼא^;xͯ➼KWj^k¥xDu Wz]뾵wˮu»x ]E{u^׶^M{`u붽k׬c^-ztK"W*#^~]yկ8溼W^XʍyA&jW^4x5F@W ^x.6W*]k5wjw]u}¯W*_.}|c.׼q^e{vl*WJ^+zүWꚽHW^kz-CW ^nkMy0u嶼Wm^Ikȭyzf׋Y^'+]x{ Uj$׃Q^+=wr| a]뼥w5ݢt_e|ᄑ׼:[^ {kkuױ^kzǯV5ꂽEWJ^z'B5ם^m+y/垼הa^H+}x5bdWzO^%5xp R"W:G^wZW{ZU]櫼}w~ݎWtz_k+}ThWZ_E|RW^{qW u^+uz]J^W+^}zYHuʽW^yk}y55bח*^S+y+"zW:^0exu5W̺k_'M|s ^{ïuv׷^k}{a5νn׬m^+UzzLNעz/^kυyگ9UҼט^[kʽyF&䂼׏_}+E}W*_5|4W*y_ {|:W%^+5{Pg즽W^ zSu7ץ^z ?u疼כ]^f+Eyx,UF:_= |˯~D:_|uֽ׼g^k={ql*W^+z̯VꚽHW^z-BW ^nky0u嶾W* _Vu}UkW U_%+|h Wj^ޥ{uJ׶^M{`u붽k׬zc^-ztK:W*#^~]yկ8溼WӚG_Ck|ᯚR_m|75.׽^{n킽WJI^ëzZUSWZ^kzCErWj^s}_5ξz_6-|U7WŊ_ +|}Uf׺/^k]{Uh쾽װ:^k=zT*:WJ^k=z@5箾WҊ%_?+5|ЯUGǚ_k|!Wq^e{vlBWJ^EzүX5ꮽJרZ^kEz2C2z_6-|U7WŊ_ k|}f׺/^ۍ{[h쾽W^kezTB=WJ^km|B]Wʪ)_E|RW^{rUW ^kإz]^`ת7^kӥz^IeZ?_"k|]ƾ Z^M{s׶ ^+{_늽fWM^+ziJuZ`Wʪ)_E|R W ^+{rU׵Z^k{]^c׫7^ӥ|&9_ | ~~׻:E^+۵{`iU꽒W^+֕zUuV?ʪ5_!+u|XU W ^k{r׵^{ ^uvc׫jA^}|yu6^%{ίwW^+{)btWz^%|n  ^{ïuv׷^k٥{a5νqWm_kE|RW^{rUWZ^kإ{]^c:_|ֽ׼g^k={vl*WJ^+EzүJW-_+U{ԯw׸^k-{/c5y׭_ | ~~׻E^+۵{fj꽒WJ^+|,׽^kܽ{n5n׳J=^kם|75BWJ^+E{o횽WS^+|1u׽^{n5킽WJ=^ë5|U諾WQ^ {kjױ7_߭{߯y5νW^+څ{:d: W ^+{r׵^| ~WE^{fjZ^k{s׶ ^+%{ԯwW^k{ɯvuWj^+{ïu^׷^k{o횽W^k{muVW 4-M b .C 1W/] XK¶`!'H #N*h5iZ/e^2b9^'S Z9]ó`. Bz& 5$0(n / #BP'+9 ?3bmwȓ"r<* 9s($$3'S"쉘KF)F W4o!__7pbt ;T.Z8'X< kAzfe00)cb#&0BtPdC M-\ 倝% '1Ԡ71fdb\'y'rB%0<daVPd.6M8N,a ">'Ũ2 PzP)l [:8"H0.Egs ͟0e \"x%T-y hzD]d(/62H+ #@j9 <:@û.^d ?#@%ӂ!`ZTB. BdX{#Dxr;!t}W\\gCNC ߏ4 o?I * Z!| ݁n@tpJō,?Y$D-:"pY萸!$WkoXxP!]a=8Z^ʒ/ 5/`h%` EXNԁ`f1809㍉%c&P&+P@N9g=abz̅L :I$o78R @s* Y@!OHك$`oxQ7v XPB<؎'#H8Sx,ưIB\sFahb35S3hG)w\x` 4 Lި@( a21xNA)@fd -SO˸?ߎ@pʹ.tꊜBktȌ: Oa]<?!+(Dnا2, @@%]ayMT[\x  .IFzG#c&5)⿭x4#WQj7* 5vš> H/:`36 M@@%.lvkhyb{XP|B A8^FBiY+UHKp-SDLLWbi!zUG AZ݈9B `%(. 63W|0f(Z0xɂa=A-8qƀ.f U<ě9rAC\*lbJPu2IJI@f@.(0 !9Ke `F4F vO0 ~^ /(~Z(%1oFqÕa\,󉳹u̞=0F=wF`%,W =tf[|Uma*x88 ۜ% $b@Ry&eA^RXDVh6B Ep\_׆L[pK^j| h,R !.| `#Ɓ] N hUtU0X Tx?Ar@ ${&q*JTDpdTS7-Ѥ=p2& }B/tq'A( ;> 1e ~!Sg YsB @3L j]P8 S;8( Iy8DQ!XDHgeDjFlOT@.炄Uڢ2,iYca]Jd`(0,R ]kݨޱd7 g~6x(; hę:1#H3ODoլEa^ S1"C{D6A9IvUc^j<$cAZypD E|r/2> gvz `vI)}q?Fd?o8DR1D)yS4 jBszD1 L@p~9DWv)i Hߡ)"U=Il Zo e#hDL!zG%_DT_=Tp< #Gސ<oü6s,;kà 癛۰qk$@~y@ :F =}!"Ch죬:C,09s/OÒPl86,C8,Cp8{)LC>8"CD6T]Wz<}B(L*C Ϥ.x; |0\0a 9CPƍT1) OC(lp2 7Pλ3 yCCP4 0cNǐ5 M[CTՁ\5q C2Xnd: -@PFT3A硰" -cp ( dBߐ#\+ &Ӫp!.@q & 0M/ pR\0 )C ެ1K Q:/P1 oEC 0$29 C&2 GBsP, IL֝ж-ݻ tB^Pl. +;&“PF'iXKC_V.T󸫜M=fHVRxҏ#CEa@7܌9<0T z}N0D,[S޶GU4aK/clOAorHApy<eۮA Y5B!$"ǷB8$>e %BN吔4%# tsBa𰙎& UBr֐L') B o[Tp4?)^kp "@Ȱ5`A_pHAOW_!l0dby.Ao-nBBAjpxDO˖A604 mEB-"]B1l#ʅ BFِd% UUY&#W Bj' 2[ (,pӖ6"{hFZ:O<>}di_WoKژEWP~+[epsL V{@P5sg F,)^IUCA*|0b+?%zgljA90v;AP}WB sp!%0t# g;Q$O %BNd%j jB^o{L>>}K ?V8X{"?a5@T:0T2e00 KI0B4 D6VpQCx#Aj]$0A*Ph4ҭ@q\QyecHЀЌ =PB$"oB+90D#R[B>$zy .,:k** c+Zyq 9 ζ9S2=,X]{۴쓾5ϛZzC>{*3|\ @, JtPsWP"Ut ۅ P5qsg&A!pEEA?S&dp^$[@AhdҍAqI\ރypD˖A04 Z-8B4|!& "_B8Lk;nŶnȼRB4 /( ~>aO<(mO֛X4-?PF N0H :@P-!4 _?\z@翰=d`QA"8K$sT`X ןb?%uVAk4yǶs$c'tA㛰zڛ }5T!W&e0*rV1̩.sDC2E=$!mF)5zC u; T#ȇh^Z=OX[!u|ϒ*C痞^>҆hq?hvK|.?[<= BP2d '@) #@C:Dt;AxH8T]LAETJ<Aq^|S:TA/'\!9HfCs,r:!%㻚Kc/,Nϖ$z!Om|[ʐQǏ걎S>O$2?H!;_?{AM@zS"@* w/j@i0:yD;AGRdPL`O*f4*-^bM&(h>LC9S.0cgV끟A;瓛,i5\S{Yӽr ߺ[o[D^Oǽ~/h6EWp@C܌'< @P֞_6J"mJw C;TNeJβ;֡ _SOM*KܪS|%S/03?<?!Z^?0\aMP5\,.9(,%^#SPOel2CxKE.C;t?:WXNOSMc>D[ɼdE[$o)oz9t lUov>/p}wl"#/ٔMx/2)uSC3eMCh(Z78}HyΣ:ꤛլ;eì<9ì0/Y[٠*bvO dϾ&ȿ دt8؏(oF J_2 3W4nO@uCY/؆m ˙zzव8C»~ߺ>.C ;nnK/ϕH־ BX?>mcH~43PJC̽5l`w7: #<C9vNu+X9R:H|{ ȋL(lJ'=zkJ\>aV/t#X%@o 8tO2m6f[UZ׋zcX7uz68>.*k9ӦN(tU"/X!<5P+ʹ=oRBׄ y=O~JY>jCX:>FmB"kX61Mvxy_I],(;Oϓ9΍nnS@#5.7 z忌V6H-Ór@I=MXjMrnr]3V0j$k & ;?naO5K#d"l`bK4,h-ƓmL  չn] [hWb.pӔtn߻0w\/$1*ɤ=4RY w6(8nG.C ,9!n]ORH:[玨k(c=/`30ǥ9i.h+m+=.1z,oUni 0NO\s5柶(C9c5ߪjNRCT滛NW`1+7}dP @@ظPp`@أPΠHдXˈРP8 8p0p @ `x X8|`d` 0$$(8XȷЦ@PHtOY0`!x#x&'h&`X(h(8X@@|rgi`{[OrO84*@ABH1@$@%02*("pp &@XHyh0XO@ai gXbypsoO@F?98;4/000*h%x""%'$H8x0DMNXk`N(_x`\X``UHpG?pG;0+p*p--,*),/ ДЮx@pQy|p`\_M`QU U0D NX7/(&0:51.+*.8(>@PPYirW`eUHPNU7@;3`02:E3$!+8<`1 _ P|p0n8cxn8^NxTPG`GxMB7*#(h$/@` "P((@v0X c00q^sm`u`b@[XIXhH8EJB720 8 ;70!'p&8h(f `thphqpq`bN`N`THKHIx21h(.'7Ep*%h(HH%xuPj w(\XXMhS0FO8?=6>.1 +(=1`/.`p.qrp xfzp`СؕtjjxeZZF ?D8>F5 =?<(8@5@,$1%"8#pxF`Q5h ap]v0z0afnRPW^(QLDJIA@89(:6* %`80 .}[jQW}\0elOud`hw8|PnHw@je0p`epWQ(QW_RF;BA@,258+'%0h2 %I|Q5 ؂8xvz}y \lЁxzm8jhhPV(b^VP9:P73GA<;`6p93#(8$2`9=)>Ʉ}tg`z@mh|@THZfnx`c]xK[ bT H`7h:pB=@<(<8&5D`-X`"8, %Cy;Aq'1K]@[bX]jj0t@|xHfu(yHhd\P]dQOJ>PLF@@`?HxCCMF2*/1: gAPǐLxnodmwyxixrvmXfpa8aY`T`UHV(RX@UPE`KMIUZ@S^njH\PQVjh~øg@get8v(p o`jxU\hb aXpbeNNXNPJMxLRt@.)AHC3y X@)a%AU_]VUq]p馡IueZ!KIHKNQT]l}ltUa(yHxPyxf<~k\(f LxFxY8ZXHRu0A ? Zvi ʩy I1ڡY1y!ϙ1i)ɪ1a!qA!3D ISanQyԑy RIq9Qi1q" R y)a(rzqk(vxe8xГXhvpiXNPR``WhIX1Qqɑq۱yb'b/."(%%B! "Jq)"*!j'(2#*   : r s"B!j(j0435rj/*.0--.R)  1! IA!QiQQq z:z 1F2NZdXCb:B4)#R( .z/B+% *RB: Z YAYAQi)aAaQR J*r2 arh@i`ht}rБqJN2s:fJ`"j^ 9J#r*0r%RJ"(,z%Bb),%B"! RRaQQI2z J j8 /hnR:PBM0 )"#z%z& 7*>9b0.381!2: "  9q nGr@fXv8w@`|iRz5J[yBz2aO>:# j+64+J#"$#&z02?2KNJEA ;J/z" "*j y*zb!q bzzY6QDAsἡj;n2sjRduz|haS0 :.*'L:aJ\A/B5EI>15 FZUjR?*&J!R+r/:, %RJJaTiIR!rOZW:@B`_@5R="GJWSWnbfJG:ZG"TWre…oX:PS2QrIE:IPRNB5J127<7b'r r  *:6y|н 1!Yb0HXb*2m`n*Wr."B&#+z57*O"`P<8J_Rm}Z ⃒i"QBI"Qj^cb`jZVP:D6+#Rq%y*< lZjwyzeD8r84r-$ 1Y WjEBZFRdZt‚ oXQr_syodaR_Ur@)>zȀ Ej*"~*Rʎʍjz`^2T="50BjKjT:IjPjSZjB|BxZsJtzna]ji x:zodbBbrZIb=gju…҆{z"Z*Jo*djL03)2 *"`ri*TV\Z[cRwR~rib2imf_Rbbii`2W9B)ZRWd"du{t z҄}:|ګBba_B246":2[RYRU2YY]mڃjx2_rZgph[ TVV:W ^[:i{vxR*yljx2*_r^X2AZ/Z&jJ J?ROPBNH K_w~fRJUrhbskjXL]cz{}Jr*]bY*gڄ{cJ^jQ- * :7GM PDb9LZl~yzc2KbBNebrJjzZy~xkOCFUtxh"^Bj A ;BLjH KLBKljjM<>M`]JeiZS:;"B2TbnxzriY3 BhJ\DR<> PRx"2ZfjQjKQ"ZZ)Fz[2IabBj^j.8mD"*?{j5(R‡>Rk^a8tK!tVjRJVʔIZS9pT'Se"U)Ăp4KeQB"*!e.$0n9=x6eBi=f5C=w6%Q |9XT* BXX- #!pGƳ `G hP&DQ(L&X\0 $`+X(BB!B1J' .;bix4 t8 x>DBA0T,GaL!b(: p`0 ax@" "j~7IJb*!pP& P\0 ÁL()hL p. x<  P\2bQC^CLŒip/AP< h8 `l: E ‰{5 1L P$A `4 x@#[%RUo/Hx> @8 P0xB#C]uv2I> A `(`8$ B`pdR ( `0   \2DQJfӈcD Da8 @P0 !F$č "|5ICѐ>@@@0 A \4bAJZCL8Pp$ @X4  AH%UbUn.#QP2 @@0  !0d6A2H= $N A``@  T.BQ0N['S!T;C`,  `8" CaL()!R"E@T ` @P0 CF&CLUjYo/Hcq`4P `@(! T, D% "` H& !t< a@T, :Hm.CaX2A@p( p@$ AD$ l:DBA(P) x @$ x@" Bph6"! L(pb\dzYpAAH p8 P( H& l:DBA(P)d A H$ `h8Ba@X. B" DPX-#15jFeyn,CP@ `P, h8BA0T, a|@!q@V, !j6 Ap4 h8! L( p:B1 N) Ţxd3 35lFMr/CX* Bx<@" PX. a|@!Ģa@T,j6Ah0 Ba D H$ ``2 B"q@V,j7GC㩠R$PP; Ģ1|: CpP( A L& Bp`2 B"a8T+j6#|- Ģ1|< !X* PX, !p:H% XZ/ FCAp9ǃjTDV$1xT'"t8 Cp\. h6CD# Da8T+ Ef4 z>$"@E QX* !|<Ad2 at:"!H% PX- j6GC~@ dHͰUSiX(!p4ł@J#p8 t<"!H% HV,cg0JdI= ƃxT)D1>|> DB!J&"T(n1DE;TK#h1Eb@J$"B!B1 J&+6D IW)Dbx9 "pT) Q H$ Q(L' fчہX$Jq5 Œ@I"h2 PP'q8N( E-FBdNHf0 2y,D f1 XT* BXqY+咡:EgCq^-DQACd1 hZ-\mfRfO%QCj2KDJ#Cr7c!`0 RK#Ph2er@J#HDt8Qh_J==N1Z+QC r80e"B3qa.J24H"$z<0V"m(COgcd0 %b@K$2W)t)~< 9\,eq0H#HBJ#Pn5edT(ɤQ%fR'Zz:M1]-ʥ"=lT(SiDr!~<I`.bdR $b|<Qa/ +ղPMQ*w9 9_`.Vj*}2Fgq7e`Qb~=#yYTQC ^ u0G"3`:6I#Ht_-*"}4I#h{!Z+ 4i,GL*pV)IY&gXKTQ'M1\ʥ2d׫uTf+\i2جݬd9i3[w @ @@ @  P @ @ @ @ X @@ @ @$ (@ @  A @ @  A H$ @ @ A H$ A @ H$ A H$ A@P @  A H$ @ @@ A H$ A @  @  A H$ @ H$ A H$@ A H$DA @ @ @( ``X$ Q(@ @ @ @ P@`0A @!@ @ P( `0@P @ @ @ @P@ @ @ P(@@ @ @ @ `P @ @ @ @ @ @ @ @ @@ @ @ @ @( @ @ @ @@ @ @ @ @ @ @(@ @ @ @ @  @ @ @ @ @ @ @ @  @ @ @ @ @ @( 0 @ @ @ @ @ @ @  @@ @ @ @ @ @ @@ @ @ @ @ @  @ @ @ @ @  (@ @ @ @ @@ (@ @ @ @ @@P @ @ @ @ @ @ P(@ @ @ @ @ @ @@ @ @  @ @` @ P @ @ @ @   @ @ @  @@ @ @ ` @ @ @ @ @   (@  @ @ 0 @$@ @$@ @$ @ H @  @  @ @ @ @  @@ @   @ P8@ ``0`0``0 7777metkit-1.20.2/tests/regressions/METK-103/METK-103.sh.in0000775000175000017500000000070515246725757022056 0ustar alastairalastair#!/usr/bin/env bash set -eux check="$" srcdir=@CMAKE_CURRENT_SOURCE_DIR@ bindir=@CMAKE_CURRENT_BINARY_DIR@ wdir=$bindir/METK-103 export ECCODES_DEFINITION_PATH="@ECCODES_DEFINITION_PATH@" ### prepare test for f in corrupted ok do cp $srcdir/$f $bindir done rm -f out aux $check ok out diff ok out touch empty $check corrupted out diff empty out cat corrupted ok corrupted > aux $check aux out diff ok out metkit-1.20.2/tests/regressions/METK-103/corrupted0000664000175000017500000010402215246725757022000 0ustar alastairalastairBUFRb!4~hbDif7anA.d jF Fr (!E,p @ ` 0(bP @(`` &(P0@@ (Q0D@ 0`@ 0(`  $J0p `8 pH`:8xp $J8 ` @ H` @@ `  .@`` >@ ` &$H @@ $H(X A $"HH!@B $2Hh!C  $BH"@D (P @P  (P,`@ ($PLAP (4PlA !(DP BP ,X(``  ,X0h` ,&XPa` ,6Xpa ",FX(b` 0 `8   0`8x  0*`X  0:`x  $0J`8@  4 h8A   4h8xB  4*hXиC  4:hxD  $4Jh8 8 p8  8p8x  8*pX  8:px $8Jp8„t)!ѤCHJ":D:u)!դCHZ"zD;v)!٤CHj"D;w)!ݤCHz"D<x)!CH":D"D|T#$GH&}"MD4Ih$$IHL&"MBD5Ij$$IH\&"MD6Il$$IHl&"MD7In$$IH|&"ND8Ip$$IH'"NBD9Ir%ϤKHB."]DDt%ӤKHR."]ZDĉu%פKHb."]DDv%ۤKHr."]Dĉw%ߤKH/ "^DDx%KH/)"^ZDĉ hR&$MHH6"m2Dt jR&$MHX6"mrD lR&$MHh6"mDt nR&$MHx6"mD pR&$MH7"n2Dt rR&$MH>"}D4R'$OHP>"}RDR'$OH`>"}D4R'$OHp>"}DR'$OH?"~D4R'$OH?%"~RDR($QHHF"2Et5 jR($QHXF"rE6 lR($QHhF"Et7 nR($QHxF"E8 pR($QHG"2Et9 rR($QHG5"rE:4tR)$SHPN"RE:uR)$SH`N"E;4vR)$SHpN"E;wR)$SHO"E<4xR)$SHO%"REF|D3gHT#MbFԍ5k4$iHd#MFT6m4$iHt#MFԍ7o4$iHӄ #N"FT85$kH\#]FvI5$kHl#]FwI5$kH|#^FxI5$kH^#mF$im6ڤmHn#mFۤio6ޤmH~#n F$iq7$oHh#}Ft R7$oHx#}F R7$oHh#Gt7 nR8$qHx#G8 pR8$qHl#G;wI9$sH|#G<xIm:$uHt#G[Ԏo:$uH넑#G{i;ޤwH~# G|$7In<$yH|#G8I=${H|#GxIo>ޤ}H~# G$?ߤH $ H8) pAःI -!݇BD[~X%`{(BBD@x-?-}܅:DB"q y )<$W!;# hZ>[vtDO9#?p-PT^ &Z%G ^!!$\{ A HE`g|I! NB~Űbt6!5F8 zve )zmD@ZŃ7Š@KԂxTDL55ը|v"Cm UMBE. ifgK$pP,":"!TS  mP!_#h ސULQX9A5a$10 HRe«#F<0 k #q@NGv=AAq\̤7"@'gk sH"/ AX(~ X;fAa5z%_17GS¸J0fn1 1lc}pǏ> aI$\C*Az py-$| {`30)aR dU2ׇ,A%Qh(zP.Z20hn41 XrCBJA*5T0z:p'UO na=Lt  @BgD'J- X"نPh4 ȄqZãCT@iP .-A{bD&aIe x^C%qx> d0>&D+hW.V873KA oc <? | >`z]p* z28$=b `[ P߇Al=a 8&;>S\Ub(  *2hnSËS!5>z Usn!< ng RYBÞj8{a^@YL#+Q ˰Վ061iCf`HJlDD0`0|3`6"<*!m# -gCm&̈8C1TO):V#[4wHFt5ގ)dtaûW (A"A}@$;1S@N׼ EB_Q25!,ṽ8 Áj\Jbn9Xp-at͌c]3cxB9hס||b I Hf`! x*b yJ+$m; [)  :<،5Ē&a=I LUyBؽg2pލadn(Ǩ(>"ːyDO@ɀBtuL`1h?- /! MxpCj1݈GBUvpE>b+|hA x`7Co71x0ȹlU4D4.0 ``cŠ$;R l$!Va# ڨTضLPΗd9Á`$0TRV"4 'B0R jpKu=;2̃6"|!xF6?@@mP9xk`(+x^ @jL8Q-\Dx"<]ț_ 0)A[@]#Vϐ̩Ɩ6AcVPu#( H"B!n䎐nAX6 T5RxHBvF8е3a5ny!t !LyتE -zk 5d>:2M0}} rF~@^f l$'5j ׀rh"Ё. 90W@g;:>VЈ݄w%IQ6lS|@F1q +lu|{yG4=C:DB"qIRf$%_.V>b v0&ABwLV$>#2"3J o㔳k@Ǿ?}"CTG[ #sdN#9 IRldʩ&8IO64ɤ$*pLFhtu@67(*ٔNNw@EB\m1QvU cJ|lP'`blz,sc1{PعF49D֛H{#n HȃE94d%*,jI| Lk{@E(FEKEF<lSd%[I-InM nx(waFH.٭(BD` 0vA 0s<ʴa 0(H_(HESz,$m Ψa$Փ48|Bz@ 9B8 m)'Tj ڠrh"!. 6WBp(;$OȈDu%96S©E"/1 i8" DnF:35jyo!fe /a IpZD1.TG_B mfC` ZHE߁ Opyôp'T3!Hrpy!pKj>] %94 gU 2}` h&/44 N t @TP2ԁ ڠg9>x $ 9F& @$BQx"E{_ &gA44 Mdmu@(D (QY,\,e: .Y`F-Cj Z@Z: .u> ]pfgZ2 fK3$xn3 gk<υ~4 heC%`X4wA "i ITҾH4 8`iN|)9_bs/D`:9Ϭ~tH:MtTH`T:Ո`uhn`]:v0?8 YX@/Bx 3@)X _p@9 t ~!@ABE_#_ĈF*1Pe#1axF4B6j#SF6`m#sF8\p0q#!XG19Ȅp&>2' LBd(R&Z3 Lf8P&z3 HMh6C@&4&4 M,BiN0~&5 pMK॓-)k!Kr ] Rz`0 )!L! aS062)L eS-™4l)aM4 iSSB`5)M lSfHJ@^,d 'xY;P,!e: )YNV,e ,Yg<[pۅ,e .Ytˠ^􅗤,!f 0Y6/́~l `_6/́~l `_6/́~l `_6/́~l `_6/́~l `_ `l3A HeC/}^2ᗞ eC/ xP˩]L2a: e΃.Brː\ 2 @e-nw[X2 e-m`nj6Ĺ%q.& pKv[ڐ%1-8hKDYؖjD%Q@P O`2'>hO~{sװ'Q TfBਚDp*!Q T= ثE\*WU"n,f .YWBL?E,}cXŒ(ň,9a ,XD h8+_ΊWڢq0vz+1^pXWBݨڅvp+A]]WQ"pҸtJ+\hX5(E,g1c- X.qN.os h\"#,.phP[xź-n)oX[hrE-lB `Z?ص&-\jTZjED-3i KhZX"Ҩx-!!hGZ8ѮxŽ-oUzH["GpE-1n `$ul/Q~__l"N|/a{ی^Ƃ@"/5y}ɔ^@"p(L.w]̂npZ .!v8 ]x"vZ .uc]OJQ.at 4_ By˅*/o{X`^fh6/`!z@cscDF0`1xP W`b#aXĺ$1{ Aa#s`oFN0ʡ .\a`# kP8\F0 `c2 v0Syh`v" k3OfK1^2!Ɍec+;T`qR\2a dc%*0%Hf2>ΌdQC! >@2 |f Osmt4c 'i H<;ў4N hhXA !Fz3< gc<Fu3QmCi ?ٿ<6RA\lIaUd5ޡ qkrCZHF5|1YjU PƦ89Dp cPW77 4ocw|P7Bṳ n:q'6! 4s8㘴@G+(9HɷIr2c|8-LqQcG8mQiu҃cpGT:qOtC'HF:%пT!yunEb-lfLV"Y:G:b9$èn`$di"pHQd@YB"^p  @H=2*p ` @ K2v=p@4& @X.i@>%:H8` @pX,d2 `xq0;`rnEA(0@Xd9@H `xt>! 0Xc \`/ }@` D$ % ` }I% @z@K`*h   h5 2l M(( F[2 h,R`+8 W. Rp9x|#+A% PO( d2@XXWW1pKI>@`2   z>hlO3Qh*0t}@@% xp=!XH 3Ӏk8@@`D0" `EW+X}J`' "!@FP&Pd @Ԁo8@@Fx# IT+~ K@+8  Lp)h X, p8!>+A J% QT`* S+ x|** V,p Dqfq;` H<'M' 0D\W*0K+ &,V-h 6@Q@t;`$`N (DP gFb@@Y`,< d@>$0 8NM@*` @@s=HfpG% (B] PZ-P& T^ XJd8@p `wg7 @A!t$X8T`+ OAwZ- i>   K(x @Q@x>@h]J*A! @I%h 8g@@[@.p ja2H"W0@IXE]2LND#L `n8RH* gw ]-X  $ (x |DP%L `v>ph~W?%`N '\P mRj`x0p8 uc3  KY2p.9]1( h AG$ P@0W-P* `.p H9j@ $@/{`?0"h .x`q> 0~]; Q@) w`2 !we3 H~Pd`8@ 6&,W. H K% (\`0[H. 2@%Ad0t ?nKA j@5 }@"` `o@j@8@~[A8ࢀT`*@ gÂAŐd3X r9c3 փk@|C%&K( @)Q}D$ )Am^00( d<Ԁj@5  <j6 +B;A㋘:{}]@@D" `^_P3H*5+`S+ ."Ұk62 ̓X@d2 kG&F>lcR_=  D"<;%s W,hp <OTÀ }A$n ^`3, *py;` '@g 4* щ0A[ HQ x{j 7 j @;&S*0 TAp;. p5 RF`Z0. A E# ']@]0t  }=<YRa2 9Ƃ  K& |sl``2 EփPx;`@Ev>h 0qE@W-PP  Ԁl7{]}A"` |] ]8. |hMo8@tbX;@R+L pj7XX ;ࢀT`+| (Ґk8<46T`Z. rR0p9L >`@h64z,Aເ`2f H΀v;`j @v;r `w<L% {>@|> Q梼=Ύ*owHFM1{ عe,cY]?y+9M*Ow )4N;\%"E+p%95 ʎvtίHj@Ty C4lThz]HI6Rs=|9a`icIG:q,$&K^*BR6ea-݆n{'82tF .qj8Z7􈤖'iRbaIFM-o.A;X9 T Y4q UHRI<Ƥ9Dp,Yiۊ^E3Y: C:B.@O;X0٫N~K%%A ]凪H RB `U \ k'!s ~)awB@;1NiAJ&`9'øZ[d„aL O @f*JB SBHzKF<RE-y(pǠSA;4AL1A$$!@ I!) @kC$S m"4^s?|xI~UBr(v<k8 @:P @L`$P668a̞~0ΞY%GK _[5wC;} H8AJ0&@X:&N Z#7)0Qՙ'AaC D:2i$O "ޗ5Қ=ְPz5h4 15$eU/9L}$CB*^*>C< Z.\*X{ɶ~-PnhntfM F B < @p38 ^!9Lgc! ( L[S%hEHq((OA\A3Qͳ˯ʼnhs\x|: !S9=pj[k- [BR_` b1 H[Cn q/ d#  X F`&3`w _80!?@6h `? `0!Ah5F7D+A~M s5)J Џ𯄤)A*CH XZ`&`lT᧴`P%P5A c|!!= n%1Tm")/~\s,Q<n"A PFjʇHBcÆ=b"}Ì+X ]( OD`B!xoCfPh?.04!͏D 8!qIbrF6x$)!S ^$DV `F7$'(G 8{"9"R xR#+S̘'I Yp-YvL$eC fj4 pG[7g,0V˜scO?-E\˲JIC*eBX1l.szur E 4(\ i ͼgdy\w?HA$B|O D)l&j!?u&Vc%$Ro3F؄DF#"Z6Tdp Yݐ{QfDKb*@b\Q8p{FqC+e{Њ5KJT-Z|u/^8$ :+ kRvݝ3Հ4I? `!PĸrZy;h Df yƻbtST$(&AP: a|:H/#䭗FrOH)JƚCwtVCh&dU`@ plpx\ 1CqO.&TʃUiF^1JBKiG*}ivrW#k `*!(.!'$rby?h%E<#j =UJa ¤:e˼X 0xPwLQ !50~] Q:_->`JIZv6}%FX;=m2& '"x{RQ!4 !pB[!6' tX:+R+ {;1M;i ۾?dL X)@e A2)czr3KJ U-EڻvOD(J[?nnl1U{cr-8'f<# DtF YA+ƚ{r2K S,{{u/^Xd+0BeBK ;i$ȡB_ɴ:G "4jSYՏ0l a9%PT k/D#~:KS,D@0J X/!D]4"dPh AJ*pTt2MJj/4xs)B(& ƘW4&3~)Hi}>0JV-3vOQB(dJk0Fdc{ E)8hD0UMJMc|P2E,&UAH}$:Vk)ƝbV5r&BZkb`qhU#:cl5ݠ+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> *>>>>>>>>>>>n >>>>>>>>>>>އ X2f@>>>>>>>>>>軲ϭ>>>>>>>>>>>觀 >>>>>>>>>p>>>>>>>f>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> %]jo5u}}>MBHPZeo"+6C7,$ 5"U2g?|G7/̌$,lԄ'#:/PWe{gOT<| 5 # ;F~ o!8OWe{ ,K>h6' &>Vm>s873:{B;Nc^crӣ߃",DȟıլUBEe}5F V-&Kjn>VێG/)B7Zc"JB 1+I;d[{c,3YTE> buUm.N>lv'_-FКһbɲ;"#Ad sC;D%NK^xJ;2S^ ۷DL?dkL,m=d͊me;[Fz>ӆO $>`J>! y.Mq kPïߔ=mȜMHMp]E-~(IifŖ^/G3gM&iukn9|Iޢ %gŐ-$GhF.fV/4Ny]a"x`hY5Aqɯ-lB;![Ô\ 4g\<ǜ=!Lu=v1StnN $?X!PhmMR{4IvٹA<{Z3mݤPW LҲUJ K+#([čllD=n~+Mo&N>NW ;_UOn?zןhGtρ'7_i0XPЦ 1Eل!âAڀb5sn+Fx׽1[}Ŭ-ҵ>_^^W/IUbo{7ߟP\iWu/OǰƯљ`aPq8t*$]4tf<$Jt%~ /Qr>.#'=Wp|Oww̿W^_jwW/צ7ҿݏ1lYF*Y )Ek1iʪ27o#R%W};eE="DNevw2wL_erG~ߕϡ7'7o`myogǜw7ɇ_'ygI`ewɶaJAtZ KD\K{T.X}͕F86Yvy>nn ?(BAɧѱQ J(TZjk;K~ [,|C|rĠʹ$%M=uu=& -No z9Qjb)J:o˜: P 4>m$TDeDel]^ `J:2R1R7DXrۣC/c[ÈCl>Djܖ%m=e=:x2suZ}ZjCl+[4\Al\9`M2ҹ2+:]C#$Hql 4M7]k*+A\{S ,S,y|$==8X k3C=taL,δM;kk1Qrt|L  T40Khܦ\4uE(D$d8Oh<|ܺDm54,q$ܞlє%=BL]7]R35Kde!]5JaxQdx͎}XLz\z f-D+rP]_EȪx\K 1(= Tf:Tef-E.$h H+EbpU ild.F_S?:j9͎&HE,iQQf+кc.e)96D$>3)E(%O#U$oK+ (Ws&dь6M3wa&Y>H )=tЕ"Ck}e"kT2WB3jFƈ#Ԁ\Qkʗ9_-D:jM8={@8H\-#1D#hϪ9+E#v\z2BH$mU6rV0f9ʹSgoNN9Ӷ;'Y#nMj~Bhuv"6ONQ{22FH5G#4")R8IhIs6Ⓖ4[yKA3HLӗuA>8m{|RDs蠁-#h(Qb>H 9#;Te}QJwDDneB`Z6̉3"枤گ#ZvQO>1Ϡ4&&Dz#E#V4r("ZBHI!%ğRV J_ !B"X 12&(Asw-tT ‚P^B=` J&vEP%a|vckP._Ga %&%N4"Zh(Msa8:4릝R{FO?( 5ZPjeC|HNQ PC \RLAמ@@}06R:eSMI 9'SDﺞ"~BO*AoA ;GQ# 'ET%iQ*:G5 " sd4/ȬCڏҔ_M Y:&tN PD.~TeQ*<G)!UTLRWJLI)=(`"'R*VJCg>Ei|}95 TQ٪A!H&&ȥ T^RYAK^re/_ScL)3l*SFJiaM?5T#ShQPv4ַkVj V窲RƪU6RTiR:FUoTE PEBX 6T$ PwEA2TqPKA Uڠ{Tj}Q*0FB' troɶTp5Y7_+sBUVCíW*U[EbrUcJURn5CTYSpuM3j6TJyR*[KZg,2vU T3*PjU@2^9BGWߟFc$ras+vj..4V}UXKa`zzV؉Z+A5fXV*qW\,fiRU*IU*VĪUXҪUZjUHTqUV*eZT@fꬱUR`6C~g2\gl 0/^6:}[aKZ9i:eN6ĹXWK!_+y:U 3WJU[B[j՝VQ Xa2ՁUYWE^uVm ̙Yu+*d⬎V2 yXy΢|RΡ~hai{(.&VZPqh5xArUX `,U~2/Whj\+pLU8V ֥Z+Ri'V|*ΕY+3f UȒW [xntյoVŪ׭Z׫W%jmXbB#M}O8ߨ̚K1dbl[E;,^|x\ o-ƵJVbkY+1eR%{XJ)`}UﺽWq &7ۨPKqϭ4y"˱^a3/5rWZl\wk o~%p֮ӹZ7K?]gȕ%y5(O=-Pdfj+2VDYX )`yK|qBUD"L\H ~ny%IԬ"f(YL:&k!kim4Y6RV2ȋЫa,ဈ/%]*;–&_++w&.țFފ#ZSG;gʱ5<p`s}hmC vΌ86)Z9^fṞ ϥ,7 fkSm\PV{Ϝp }:ZKHh@K5[/js1}8Vݞnek-Z5w7 }gl .K8&^QN upHM]e8/&` 0+$V`6D +$V@}د 1$lP@AE`T_k~u(`6D(l]~;Ujߚ_vmJ !Pz@4Dn ~_w@pxL@ 0"{6@pQ p!n `j'xy H!6~%•02hWH s@ 1rHhD] (!p]_+~ 5*@FFhp `0 @\FX R Zk_k~uW)@;D<0m .@FC@ R @G WU_M} G'@ Xe k*;hD0@&W_u}X L(0@%Anh@ `v,(=G1`.)_+}Z R(00C@k -`T&?@`M@@h7@W ?_}[` W);D<0m .@<B`OBlZK_+%}` h+H\H @v2P~J@X` 4!p]_+~ 5/@xP` (#J9ЀX P4# y -@ ) X-~5ĵRW:@\ @]*@FFhp `0 RDU @G U_M}`2 U dA&(? pBc `)@7X<<0`ֿJ_{}e \. O &#4p8@U@ )"`s `* t#pB~*U&ފȀ`\ W)@AD<0m .@<B`O<lZK_+}40@Q 1$l ;`Z ?% L~p0P.pn@Ujߚ_v+8 ^ e+@2hWH  s@ 1hG@ ,Z @@R@ Zk_k}vU 8<&  =`,`@ XU'5@7  W_z+}ǯ{.M \ "*H6`R !p j)@xy H!6k~%•W_p}]` T_*&LF@r `0 RFX M a_M}bzM_hk}nWذPS 9%p= Lހp"] O' p24 Ƶ WJ_x}¯5ٺ!_b}j_akZ T)0`6D(l .h0@`M@@h7XW ?_}读Wzc_k+ }.ת_o+}nW _`+}o\ (Z)AE`To@ 8/`FC@ RB lK_+%}uJWY_hk}uWZ_Z}j!_b}z.HO (#@9`V .#v ,`&L-~0>W_sk}j_ak}u՚_p} _`+}uUՐQ .$``:`ӀY@ x9$`4~p.P,pb@]~;UjW_{}^ l_*&@8QG`@r 0@\G@Dc P>"@c @`S 9%=`p"^ (U'4@7(R` 3%v@x<`4ۀ] J&x 2 h+H\H @v2P~J@X` 2hQG`s@ 1hGh ,1$` ;`Z D%`= 4ۀp"^ (O' p@9`V .#To@ 8/`R9`V P4= Lހp,`@ XU'pBc `)@`,`@ XU9ЀY@ `0Ȁ ,╋j'*.~ 5(²޸6/gc i1ȨV(ۉ8'$0bq G0&)$Y[*G@ &o (/# d(/z'L"Gr92 & L:#*)H>. B5PJX& 7Eb1$Z*)6 [i- #RDtnBX+K Os縕6#mhPy&˔h0  O(&: IB @!Wz X ,* ~;}\(P#"wyI@paoaIq@M8{kan ?DD3L 1o}H) `S@"wf5ǁ w9h1 )bꨫ (x։bBHȈyb /ahkRqzj;xUq)a DηX6 ,R u{j`x#"~zBMc`FX͆60 ºaX& "Y%X.Z!>Axd@!NΈN΂]u>tX1Q …'m S*4 {ŏkIe O`ȡ/\'l$ @yV cA],ZIa2G[i`Px8 +`,H ^Pp`R"z/W tyy@[@6_ OC Pض*)1"VH <ïi-&!T(O ̈́Uo<\4SU@}@. {$Fx)6(ŀ]WH"H3HJ8ʍ_c#t+2Sb^a` a gzmaKt8L(@9v o!&+ 3fae[!Bh3`l(abb @R>z =&;#v WC?| ,hbg Y!Xf$RPl!@(J p~6LH (L [,jl4`la4/(3@G%HvJ߲@3]In=x|c`iL.B} Bp@@ &{]8d <>a3hF8 ۛ2 A@@$t sU"j Sj-g;$ (L E(F-54.yl[T$&2Jێ@kFr!lBxIWvq`ް2: fHH#.Eߔ@m !OLo23 h 5\/Ii$cAО@b吣kg-&- ,Id`KLoT{CUB1΄Ri@8i &Ű& ~qH8OQ'`L&>z@1 6dנ+JJsCR F{&Uoydް%DԌÝ)8. qQB݆ll-PP#`36 t1$ / d^VƁ j;$ <j!1}MIِ{b*JZ.4EsQM; NIir$>\ X7[DB< }V?2!SX3) C|8`6hxҧT}X ߲@0 5%Qq0̀qB6## @a~Ē!GethpYbv@eq&"BKc>H&"+ϡ7]߄Ix2 _]@PhO`GH0"1"T= F( D#G}De1Qj,DD;Q B`%rD>IkD#RH&ʜAӰVfIl)׬DZb8s;ܿ,dI _DF!G2=zUq0CY'LAT53+pj4?j{Ah8~RP;3'raK@\x,.85< !] hu_ DctF;>Q lB3][| QI\?wPl=ICNp$<÷FVAr^[Hdh;([:aK&–M*)M ¬pw+b ½[T,R !B0$- s NpD- pel."e BP$, ( B0"A< k'X;J p;"Ox6Xƽ5$z ZE[T Pv;B"s",BBJP%Y xeM$'B  С(; C•)Y .Bpc*a BU\+/ "µ䰤k)=E V—PLd)# sžDt2~K{<+/Y[{qOsW@6OAJYsbAo36h W 9#<D*d%3 gB`}& Bs' }Lpt(q …%w h]ap&' g,&i BBq:ʰK. qS M*lǨ>D=P ?e1E@s1T.)vpQ#dɬfSHARu>h0&\ _B 9t",B= $v/ )XMI %. W{BXHЖ% -%R""qB2|#q{?Pd$O BJV*ě*?0LUXF݁9.6S=1sSXx?Oٔ+"cP »8WezA1Q<Ametkit-1.20.2/tests/regressions/METK-103/CMakeLists.txt0000664000175000017500000000027515246725757022613 0ustar alastairalastair if (HAVE_BUFR AND HAVE_BUILD_TOOLS) ecbuild_configure_file( METK-103.sh.in METK-103.sh @ONLY ) ecbuild_add_test( TYPE SCRIPT COMMAND METK-103.sh ) endif() metkit-1.20.2/tests/regressions/CMakeLists.txt0000664000175000017500000000017715246725757021553 0ustar alastairalastairif (HAVE_BUILD_TOOLS) # test scripts use the metkit tools add_subdirectory(METK-89) add_subdirectory(METK-103) endif() metkit-1.20.2/tests/mars2grib/0000775000175000017500000000000015246725757016333 5ustar alastairalastairmetkit-1.20.2/tests/mars2grib/backend/0000775000175000017500000000000015246725757017722 5ustar alastairalastairmetkit-1.20.2/tests/mars2grib/backend/mars2grib-test-search.cc0000664000175000017500000001767315246725757024357 0ustar alastairalastair/* * (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. */ #include #include #include #include // dictionary access traits #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_codes_handle.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "eckit/log/Log.h" #include "eckit/testing/Test.h" #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h" #include "metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h" #include "metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h" #include "metkit/mars2grib/backend/sections/resolver/SectionTemplateSelector.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/SectionTemplateSelectors.h" using ActiveConceptsData = metkit::mars2grib::backend::sections::resolver::ActiveConceptsData; using ResolvedTemplateData = metkit::mars2grib::backend::sections::resolver::dsl::ResolvedTemplateData; std::tuple make_ActiveConceptData_from_PayloadEntry(const ResolvedTemplateData& pe) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; ActiveConceptsData res{}; res.count = pe.count; for (std::size_t i = 0; i < res.activeConceptsIndices.size(); ++i) { res.activeConceptsIndices[i] = GeneralRegistry::missing; } for (std::size_t i = 0; i < res.activeVariantIndices.size(); ++i) { res.activeVariantIndices[i] = GeneralRegistry::missing; } for (std::size_t i = 0; i < res.count; ++i) { std::size_t vid = pe.variantIndices[i]; std::size_t cid = GeneralRegistry::conceptIdArr[vid]; res.activeConceptsIndices[i] = cid; res.activeVariantIndices[cid] = vid; } return std::tuple{res, pe.templateNumber}; } CASE("Section 0") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section0Recipes; const auto payload = Section0Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section0Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 0 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } CASE("Section 1") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section1Recipes; const auto payload = Section1Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section1Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 1 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } CASE("Section 2") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section2Recipes; const auto payload = Section2Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section2Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 2 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } CASE("Section 3") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section3Recipes; const auto payload = Section3Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section3Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 3 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } CASE("Section 4") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section4Recipes; const auto payload = Section4Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section4Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 4 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } CASE("Section 5") { using metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; using metkit::mars2grib::frontend::resolution::recipes::impl::Section5Recipes; const auto payload = Section5Recipes.getPayload(); const auto selector = SectionTemplateSelector::make(Section5Recipes); for (std::size_t i = 0; i < payload.size(); ++i) { auto [activeConcepts, expectedTemplateNumber] = make_ActiveConceptData_from_PayloadEntry(payload[i]); const auto& sectionLayoutData = selector.select_or_throw(activeConcepts); eckit::Log::error() << "Section 5 :: " << i << " expected template number " << expectedTemplateNumber << " but found " << sectionLayoutData.templateNumber << std::endl; ASSERT(expectedTemplateNumber == sectionLayoutData.templateNumber); } } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/mars2grib/backend/CMakeLists.txt0000664000175000017500000000024515246725757022463 0ustar alastairalastair ecbuild_add_test( TARGET mars2grib-test-search SOURCES mars2grib-test-search.cc NO_AS_NEEDED LIBS eckit metkit ) metkit-1.20.2/tests/mars2grib/utils/0000775000175000017500000000000015246725757017473 5ustar alastairalastairmetkit-1.20.2/tests/mars2grib/utils/CMakeLists.txt0000664000175000017500000000004615246725757022233 0ustar alastairalastairadd_subdirectory( dictionary_traits ) metkit-1.20.2/tests/mars2grib/utils/dictionary_traits/0000775000175000017500000000000015246725757023226 5ustar alastairalastairmetkit-1.20.2/tests/mars2grib/utils/dictionary_traits/mars2grib-localConfigurationTraits-tests.cc0000664000175000017500000006005115246725757033576 0ustar alastairalastair#include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/testing/Test.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" CASE("Test generic get_or_throw valid key") { #define TEST_GET_OR_THROW_VALID(TYPE, TYPENAME_STRING, KEY, EXPECTED) \ SECTION("get_or_throw<" TYPENAME_STRING "> valid key") { \ using metkit::mars2grib::utils::dict_traits::get_or_throw; \ EXPECT_NO_THROW({ \ TYPE expected_result = (EXPECTED); \ TYPE actual_result = get_or_throw(cfg, (KEY)); \ EXPECT_EQUAL(actual_result, expected_result); \ }); \ } // Used symbols using metkit::mars2grib::utils::dict_traits::get_or_throw; // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_GET_OR_THROW_VALID(bool, "bool", "bool_scalar_var", true); TEST_GET_OR_THROW_VALID(int, "int", "int_scalar_var", 7); TEST_GET_OR_THROW_VALID(long, "long", "long_scalar_var", 12); TEST_GET_OR_THROW_VALID(float, "float", "float_scalar_var", 3.14f); TEST_GET_OR_THROW_VALID(double, "double", "double_scalar_var", 3.14) TEST_GET_OR_THROW_VALID(std::string, "std::string", "string_scalar_var", std::string("abc")) TEST_GET_OR_THROW_VALID(std::vector, "std::vector", "int_vec_var", (std::vector{7, 6})) TEST_GET_OR_THROW_VALID(std::vector, "std::vector", "long_vec_var", (std::vector{12, 13})) TEST_GET_OR_THROW_VALID(std::vector, "std::vector", "float_vec_var", (std::vector{3.14f, 2.71f})) TEST_GET_OR_THROW_VALID(std::vector, "std::vector", "double_vec_var", (std::vector{3.14, 2.71})) TEST_GET_OR_THROW_VALID(std::vector, "std::vector", "string_vec_var", (std::vector{"abc", "def"})) #undef TEST_GET_OR_THROW_VALID } CASE("Test generic get_or_throw missing key") { #define TEST_GET_OR_THROW_MISSING(TYPE, TYPENAME_STRING, KEY) \ SECTION("get_or_throw<" TYPENAME_STRING "> missing key") { \ using metkit::mars2grib::utils::dict_traits::get_or_throw; \ EXPECT_THROWS({ TYPE actual_result = get_or_throw(cfg, (KEY)); }); \ } // Used symbols using metkit::mars2grib::utils::dict_traits::get_or_throw; // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_GET_OR_THROW_MISSING(bool, "bool", "missing_key"); TEST_GET_OR_THROW_MISSING(int, "int", "missing_key"); TEST_GET_OR_THROW_MISSING(long, "long", "missing_key"); TEST_GET_OR_THROW_MISSING(float, "float", "missing_key"); TEST_GET_OR_THROW_MISSING(double, "double", "missing_key") TEST_GET_OR_THROW_MISSING(std::string, "std::string", "missing_key") TEST_GET_OR_THROW_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OR_THROW_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OR_THROW_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OR_THROW_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OR_THROW_MISSING(std::vector, "std::vector", "missing_key") #undef TEST_GET_OR_THROW_MISSING } CASE("Test generic get_opt valid key") { #define TEST_GET_OPT_VALID(TYPE, TYPENAME_STRING, KEY, EXPECTED) \ SECTION("get_opt<" TYPENAME_STRING "> valid key") { \ using metkit::mars2grib::utils::dict_traits::get_opt; \ EXPECT_NO_THROW({ \ TYPE expected_result = (EXPECTED); \ std::optional actual_result = get_opt(cfg, (KEY)); \ EXPECT(actual_result.has_value()); \ EXPECT_EQUAL(actual_result.value(), expected_result); \ }); \ } // Used symbols using metkit::mars2grib::utils::dict_traits::get_opt; // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); // Try to get the value of "step" using the generic get function // ---- bool ---- TEST_GET_OPT_VALID(bool, "bool", "bool_scalar_var", true) TEST_GET_OPT_VALID(int, "int", "int_scalar_var", 7) TEST_GET_OPT_VALID(long, "long", "long_scalar_var", 12) TEST_GET_OPT_VALID(float, "float", "float_scalar_var", 3.14f) TEST_GET_OPT_VALID(double, "double", "double_scalar_var", 3.14) TEST_GET_OPT_VALID(std::string, "std::string", "string_scalar_var", std::string("abc")) TEST_GET_OPT_VALID(std::vector, "std::vector", "int_vec_var", (std::vector{7, 6})) TEST_GET_OPT_VALID(std::vector, "std::vector", "long_vec_var", (std::vector{12, 13})) TEST_GET_OPT_VALID(std::vector, "std::vector", "float_vec_var", (std::vector{3.14f, 2.71f})) TEST_GET_OPT_VALID(std::vector, "std::vector", "double_vec_var", (std::vector{3.14, 2.71})) TEST_GET_OPT_VALID(std::vector, "std::vector", "string_vec_var", (std::vector{"abc", "def"})) #undef TEST_GET_OPT_VALID } CASE("Test generic get_opt missing key") { #define TEST_GET_OPT_MISSING(TYPE, TYPENAME_STRING, KEY) \ SECTION("get_opt<" TYPENAME_STRING "> missing key") { \ using metkit::mars2grib::utils::dict_traits::get_opt; \ EXPECT_NO_THROW({ \ std::optional actual_result = get_opt(cfg, (KEY)); \ EXPECT(!actual_result.has_value()); \ }); \ } // Used symbols using metkit::mars2grib::utils::dict_traits::get_opt; // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_GET_OPT_MISSING(bool, "bool", "missing_key") TEST_GET_OPT_MISSING(int, "int", "missing_key") TEST_GET_OPT_MISSING(long, "long", "missing_key") TEST_GET_OPT_MISSING(float, "float", "missing_key") TEST_GET_OPT_MISSING(double, "double", "missing_key") TEST_GET_OPT_MISSING(std::string, "std::string", "missing_key") TEST_GET_OPT_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OPT_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OPT_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OPT_MISSING(std::vector, "std::vector", "missing_key") TEST_GET_OPT_MISSING(std::vector, "std::vector", "missing_key") #undef TEST_GET_OPT_MISSING } CASE("Test generic set_or_throw") { #define TEST_SET_OR_THROW(TYPE, TYPENAME_STRING, KEY, EXPECTED) \ SECTION("set_or_throw<" TYPENAME_STRING ">") { \ using metkit::mars2grib::utils::dict_traits::set_or_throw; \ using metkit::mars2grib::utils::dict_traits::get_or_throw; \ TYPE expected_result = (EXPECTED); \ EXPECT_NO_THROW({ set_or_throw(cfg, (KEY), expected_result); }); \ EXPECT_NO_THROW({ \ TYPE actual_result = get_or_throw(cfg, (KEY)); \ EXPECT_EQUAL(actual_result, expected_result); \ }); \ } // Prepare a custom dictionary to test long const std::string yaml(R"json({})json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_SET_OR_THROW(bool, "bool", "bool_scalar_var", true); TEST_SET_OR_THROW(int, "int", "int_scalar_var", 7); TEST_SET_OR_THROW(long, "long", "long_scalar_var", 12); TEST_SET_OR_THROW(float, "float", "float_scalar_var", 3.14f); TEST_SET_OR_THROW(double, "double", "double_scalar_var", 3.14); TEST_SET_OR_THROW(std::string, "std::string", "string_scalar_var", std::string("abc")); TEST_SET_OR_THROW(std::vector, "std::vector", "int_vec_var", (std::vector{7, 6})); TEST_SET_OR_THROW(std::vector, "std::vector", "long_vec_var", (std::vector{12, 13})); TEST_SET_OR_THROW(std::vector, "std::vector", "float_vec_var", (std::vector{3.14f, 2.71f})); TEST_SET_OR_THROW(std::vector, "std::vector", "double_vec_var", (std::vector{3.14, 2.71})); TEST_SET_OR_THROW(std::vector, "std::vector", "string_vec_var", (std::vector{"abc", "def"})); #undef TEST_SET_OR_THROW } CASE("Test generic set_or_ignore") { #define TEST_SET_OR_IGNORE(TYPE, TYPENAME_STRING, KEY, EXPECTED) \ SECTION("set_or_ignore<" TYPENAME_STRING ">") { \ using metkit::mars2grib::utils::dict_traits::set_or_ignore; \ using metkit::mars2grib::utils::dict_traits::get_or_throw; \ TYPE expected_result = (EXPECTED); \ EXPECT_NO_THROW({ set_or_ignore(cfg, (KEY), expected_result); }); \ EXPECT_NO_THROW({ \ TYPE actual_result = get_or_throw(cfg, (KEY)); \ EXPECT_EQUAL(actual_result, expected_result); \ }); \ } // Used symbols // Prepare a custom dictionary to test long const std::string yaml(R"json({})json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_SET_OR_IGNORE(bool, "bool", "bool_scalar_var", true); TEST_SET_OR_IGNORE(int, "int", "int_scalar_var", 7); TEST_SET_OR_IGNORE(long, "long", "long_scalar_var", 12); TEST_SET_OR_IGNORE(float, "float", "float_scalar_var", 3.14f); TEST_SET_OR_IGNORE(double, "double", "double_scalar_var", 3.14); TEST_SET_OR_IGNORE(std::string, "std::string", "string_scalar_var", std::string("abc")); TEST_SET_OR_IGNORE(std::vector, "std::vector", "int_vec_var", (std::vector{7, 6})); TEST_SET_OR_IGNORE(std::vector, "std::vector", "long_vec_var", (std::vector{12, 13})); TEST_SET_OR_IGNORE(std::vector, "std::vector", "float_vec_var", (std::vector{3.14f, 2.71f})); TEST_SET_OR_IGNORE(std::vector, "std::vector", "double_vec_var", (std::vector{3.14, 2.71})); TEST_SET_OR_IGNORE(std::vector, "std::vector", "string_vec_var", (std::vector{"abc", "def"})); #undef TEST_SET_OR_IGNORE } CASE("Test generic has valid") { // Used symbols using metkit::mars2grib::utils::dict_traits::has; // Prepare a custom dictionary to test long const std::string yaml(R"json({step: 12})json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); SECTION("has existing key") { EXPECT_NO_THROW({ bool actual_has = has(cfg, "step"); EXPECT(actual_has); }); } } CASE("Test generic has missing") { // Used symbols using metkit::mars2grib::utils::dict_traits::has; // Prepare a custom dictionary to test long const std::string yaml(R"json({step: 12})json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); SECTION("has missing key") { EXPECT_NO_THROW({ bool actual_has = has(cfg, "missing_key"); EXPECT(!actual_has); }); } } CASE("Test generic typed has valid") { #define TEST_TYPED_HAS_VALID(TYPE, TYPENAME_STRING, KEY) \ {SECTION("has<" TYPENAME_STRING "> valid"){using metkit::mars2grib::utils::dict_traits::has; \ EXPECT_NO_THROW({ \ bool actual_has = has(cfg, (KEY)); \ EXPECT(actual_has); \ }); \ } \ } // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_TYPED_HAS_VALID(bool, "bool", "bool_scalar_var"); TEST_TYPED_HAS_VALID(int, "int", "int_scalar_var"); TEST_TYPED_HAS_VALID(long, "long", "long_scalar_var"); TEST_TYPED_HAS_VALID(float, "float", "float_scalar_var"); TEST_TYPED_HAS_VALID(double, "double", "double_scalar_var"); TEST_TYPED_HAS_VALID(std::string, "std::string", "string_scalar_var"); TEST_TYPED_HAS_VALID(std::vector, "std::vector", "int_vec_var"); TEST_TYPED_HAS_VALID(std::vector, "std::vector", "long_vec_var"); TEST_TYPED_HAS_VALID(std::vector, "std::vector", "float_vec_var"); TEST_TYPED_HAS_VALID(std::vector, "std::vector", "double_vec_var"); TEST_TYPED_HAS_VALID(std::vector, "std::vector", "string_vec_var"); #undef TEST_TYPED_HAS_VALID } CASE("Test generic typed has missing") { #define TEST_TYPED_HAS_MISSING(TYPE, TYPENAME_STRING, KEY) \ { \ using metkit::mars2grib::utils::dict_traits::has; \ SECTION("has<" TYPENAME_STRING ">") { \ bool actual_has; \ EXPECT_NO_THROW({ actual_has = has(cfg, "missing_key"); }); \ EXPECT(!actual_has); \ } \ } // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_TYPED_HAS_MISSING(bool, "bool", "bool_scalar_var"); TEST_TYPED_HAS_MISSING(int, "int", "int_scalar_var"); TEST_TYPED_HAS_MISSING(long, "long", "long_scalar_var"); TEST_TYPED_HAS_MISSING(float, "float", "float_scalar_var"); TEST_TYPED_HAS_MISSING(double, "double", "double_scalar_var"); TEST_TYPED_HAS_MISSING(std::string, "std::string", "string_scalar_var"); TEST_TYPED_HAS_MISSING(std::vector, "std::vector", "int_vec_var"); TEST_TYPED_HAS_MISSING(std::vector, "std::vector", "long_vec_var"); TEST_TYPED_HAS_MISSING(std::vector, "std::vector", "float_vec_var"); TEST_TYPED_HAS_MISSING(std::vector, "std::vector", "double_vec_var"); TEST_TYPED_HAS_MISSING(std::vector, "std::vector", "string_vec_var"); #undef TEST_TYPED_HAS_MISSING } CASE("Test generic check valid") { #define TEST_CHECK_VALID(TYPE, TYPENAME_STRING, KEY) \ SECTION("check<" TYPENAME_STRING "> valid") { \ using metkit::mars2grib::utils::dict_traits::check; \ SECTION("check<" TYPENAME_STRING ">") { \ EXPECT_NO_THROW({ \ bool actual_has = check(cfg, (KEY), [](const auto&) { return true; }); \ EXPECT(actual_has); \ }); \ } \ } // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_CHECK_VALID(bool, "bool", "bool_scalar_var"); TEST_CHECK_VALID(int, "int", "int_scalar_var"); TEST_CHECK_VALID(long, "long", "long_scalar_var"); TEST_CHECK_VALID(float, "float", "float_scalar_var"); TEST_CHECK_VALID(double, "double", "double_scalar_var"); TEST_CHECK_VALID(std::string, "std::string", "string_scalar_var"); TEST_CHECK_VALID(std::vector, "std::vector", "int_vec_var"); TEST_CHECK_VALID(std::vector, "std::vector", "long_vec_var"); TEST_CHECK_VALID(std::vector, "std::vector", "float_vec_var"); TEST_CHECK_VALID(std::vector, "std::vector", "double_vec_var"); TEST_CHECK_VALID(std::vector, "std::vector", "string_vec_var"); #undef TEST_CHECK_VALID } CASE("Test generic check missing") { #define TEST_CHECK_MISSING(TYPE, TYPENAME_STRING, KEY) \ SECTION("check<" TYPENAME_STRING "> missing") { \ using metkit::mars2grib::utils::dict_traits::check; \ SECTION("check<" TYPENAME_STRING ">") { \ EXPECT_NO_THROW({ \ bool actual_has = check(cfg, (KEY), [](const auto&) { return false; }); \ EXPECT(actual_has); \ }); \ } \ } // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); TEST_CHECK_MISSING(bool, "bool", "bool_scalar_var"); TEST_CHECK_MISSING(int, "int", "int_scalar_var"); TEST_CHECK_MISSING(long, "long", "long_scalar_var"); TEST_CHECK_MISSING(float, "float", "float_scalar_var"); TEST_CHECK_MISSING(double, "double", "double_scalar_var"); TEST_CHECK_MISSING(std::string, "std::string", "string_scalar_var"); TEST_CHECK_MISSING(std::vector, "std::vector", "int_vec_var"); TEST_CHECK_MISSING(std::vector, "std::vector", "long_vec_var"); TEST_CHECK_MISSING(std::vector, "std::vector", "float_vec_var"); TEST_CHECK_MISSING(std::vector, "std::vector", "double_vec_var"); TEST_CHECK_MISSING(std::vector, "std::vector", "string_vec_var"); #undef TEST_CHECK_MISSING } CASE("Test sub-configuration") { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::set_or_throw; // Prepare a custom dictionary to test long const std::string yaml(R"json({ bool_scalar_var: true, int_scalar_var: 7, long_scalar_var: 12, float_scalar_var: 3.14, double_scalar_var: 3.14, string_scalar_var: "abc", int_vec_var: [7,6], long_vec_var: [12,13], float_vec_var: [3.14, 2.71], double_vec_var: [3.14, 2.71], string_vec_var: ["abc", "def"] })json"); // Initialize the configuration const eckit::YAMLConfiguration root(yaml); eckit::LocalConfiguration cfg(root); eckit::LocalConfiguration n1(root); eckit::LocalConfiguration n2(root); std::vector n3{n1, n2}; SECTION("set sub-configuration n1") { EXPECT_NO_THROW({ set_or_throw(cfg, "n1", n1); }); EXPECT_NO_THROW({ eckit::LocalConfiguration actual_n1 = get_or_throw(cfg, "n1"); }); } SECTION("set sub-configuration n2") { EXPECT_NO_THROW({ set_or_throw(cfg, "n2", n2); }); EXPECT_NO_THROW({ eckit::LocalConfiguration actual_n2 = get_or_throw(cfg, "n2"); }); } SECTION("set sub-configurations") { EXPECT_NO_THROW({ set_or_throw>(cfg, "n3", n3); }); EXPECT_NO_THROW({ std::vector actual_n3 = get_or_throw>(cfg, "n3"); }); } #undef TEST_CHECK_MISSING } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/mars2grib/utils/dictionary_traits/mars2grib-codesHandleTraits-tests.cc0000664000175000017500000000564615246725757032176 0ustar alastairalastair#include #include #include #include #include "eckit/testing/Test.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_codes_handle.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" CASE("CodesHandle: has vs typed has") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::has; // tablesVersionLatest è long scalar EXPECT(has(*h, "tablesVersionLatest")); EXPECT(has(*h, "tablesVersionLatest")); EXPECT(!has(*h, "tablesVersionLatest")); } CASE("CodesHandle: get_or_throw type mismatch throws") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::get_or_throw; EXPECT_THROWS(get_or_throw(*h, "tablesVersionLatest")); EXPECT_THROWS(get_or_throw(*h, "tablesVersionLatest")); } CASE("CodesHandle: get_opt type mismatch returns nullopt") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::get_opt; auto v = get_opt(*h, "tablesVersionLatest"); EXPECT(!v.has_value()); } CASE("CodesHandle: bool semantics") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::set_or_throw; // set_or_throw(*h, "myBool", 2L); bool xxx = get_or_throw(*h, "localUsePresent"); EXPECT(!xxx); set_or_throw(*h, "subCentre", 1L); // (*h).set( "subCentre", 0L ); std::cout << "After set subCentre" << get_or_throw(*h, "subCentre") << std::endl; // set_or_throw(*h, "setLocalDefinition", true ); // EXPECT(!get_or_throw(*h, "myBool")); } CASE("CodesHandle: scalar vs vector distinction") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::has; EXPECT(has(*h, "tablesVersionLatest")); // EXPECT(!has>(*h, "tablesVersionLatest")); } CASE("CodesHandle: missing") { auto h = metkit::codes::codesHandleFromSample("GRIB2"); using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::isMissing; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::dict_traits::setMissing_or_throw; set_or_throw(*h, "productDefinitionTemplateNumber", 0L); setMissing_or_throw(*h, "scaleFactorOfFirstFixedSurface"); EXPECT(isMissing(*h, "scaleFactorOfFirstFixedSurface")); // EXPECT(!get_opt(*h, "myMissing").has_value()); // EXPECT_THROWS(get_or_throw(*h, "myMissing")); } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/mars2grib/utils/dictionary_traits/CMakeLists.txt0000664000175000017500000000061115246725757025764 0ustar alastairalastair ecbuild_add_test( TARGET mars2grib-localConfigurationTraits-tests SOURCES mars2grib-localConfigurationTraits-tests.cc NO_AS_NEEDED LIBS eckit metkit ) ecbuild_add_test( TARGET mars2grib-codesHandleTraits-tests SOURCES mars2grib-codesHandleTraits-tests.cc NO_AS_NEEDED LIBS eckit metkit )metkit-1.20.2/tests/mars2grib/api/0000775000175000017500000000000015246725757017104 5ustar alastairalastairmetkit-1.20.2/tests/mars2grib/api/test_python_api.py0000664000175000017500000000065415246725757022674 0ustar alastairalastair#!/bin/env python3 import sys print(sys.path) import pymars2grib encoder = pymars2grib.Mars2Grib() mars = { "origin": "ecmf", "class": "od", "stream": "oper", "type": "fc", "expver": "0001", "grid": "N200", "packing": "ccsds", "param": 130, "levtype": "hl", "levelist": 2, "date": 20260205, "time": 000000, "step": 0, } vals = [237.15] * 200 encoder.encode(vals, mars) metkit-1.20.2/tests/mars2grib/api/test_cpp_api.cc0000664000175000017500000000323315246725757022066 0ustar alastairalastair/* * (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. */ #include #include "eckit/config/LocalConfiguration.h" #include "eckit/log/CodeLocation.h" #include "eckit/testing/Test.h" #include "metkit/mars2grib/api/Mars2Grib.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" CASE("mars2grib_api") { try { auto encoder = metkit::mars2grib::Mars2Grib(); eckit::LocalConfiguration mars; mars.set("origin", "ecmf"); mars.set("class", "od"); mars.set("stream", "oper"); mars.set("type", "fc"); mars.set("expver", "0001"); mars.set("grid", "N200"); mars.set("packing", "ccsds"); mars.set("param", 130); mars.set("levtype", "hl"); mars.set("levelist", 2); mars.set("date", 2026'02'05); mars.set("time", 00'00'00); mars.set("step", 0); std::vector vals(200, 237.15); encoder.encode(vals, mars); } catch (const std::exception& e) { metkit::mars2grib::utils::exceptions::printExceptionStack(e, eckit::Log::error()); std::throw_with_nested( metkit::mars2grib::utils::exceptions::Mars2GribGenericException("CPP API test failed", Here())); } } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/mars2grib/api/CMakeLists.txt0000664000175000017500000000110615246725757021642 0ustar alastairalastair ecbuild_add_test( TARGET mars2grib_test_cpp_api CONDITION HAVE_MARS2GRIB INCLUDES "${ECCODES_INCLUDE_DIRS}" SOURCES test_cpp_api.cc LIBS metkit NO_AS_NEEDED ) ecbuild_add_test( TARGET mars2grib_test_python_api CONDITION HAVE_MARS2GRIB_PYTHON COMMAND python3 ARGS ${CMAKE_CURRENT_SOURCE_DIR}/test_python_api.py ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}/lib ) metkit-1.20.2/tests/mars2grib/CMakeLists.txt0000664000175000017500000000015515246725757021074 0ustar alastairalastairif( HAVE_MARS2GRIB ) add_subdirectory( api ) add_subdirectory( utils ) add_subdirectory( backend ) endif() metkit-1.20.2/tests/test_param_axis.cc0000664000175000017500000006556415246725757020155 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_param_axis.cc /// @author Baudouin Raoult /// @date Mai 2019 #include #include #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Param.h" #include "metkit/mars/ParamID.h" #include "metkit/mars/Type.h" namespace metkit::mars::test { //----------------------------------------------------------------------------- static std::ostream& operator<<(std::ostream& out, const std::vector& params) { out << '['; const char* sep = ""; for (auto p : params) { out << sep << p; sep = ", "; } out << ']'; return out; } static void test_param_axis(const std::vector& user, const std::vector& axis, const std::vector& expect, bool expectWind, NormalisationMode mode = NormalisationMode::FullTableDropping) { bool windRequested = false; MarsRequest ignore; std::vector params(user.begin(), user.end()); std::vector expected(expect.begin(), expect.end()); std::vector index(axis.begin(), axis.end()); std::sort(index.begin(), index.end()); std::cout << "Axis:" << index << std::endl; std::cout << "User:" << params << std::endl; std::cout << "NormalisationMode:" << static_cast(mode) << std::endl; ParamID::normalise(ignore, params, index, windRequested, mode); std::cout << "Params expected: " << expected << std::endl; std::cout << " returned: " << params << std::endl; std::cout << "Wind expected: " << expectWind << std::endl; std::cout << " returned: " << windRequested << std::endl; EXPECT_EQUAL(params, expected); EXPECT_EQUAL(expectWind, windRequested); } void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); MarsRequest req; req.setValuesTyped(language.type(name), values); req = language.expand(req, false, true); EXPECT_EQUAL(expected, req.values(name)); } CASE("canonical param") { assertTypeExpansion("param", {"129.228"}, {"228129"}); assertTypeExpansion("param", {"228129"}, {"228129"}); EXPECT_THROWS_AS(assertTypeExpansion("param", {".228"}, {""}), eckit::UserError); EXPECT_THROWS_AS(assertTypeExpansion("param", {"9999"}, {""}), eckit::UserError); assertTypeExpansion("param", {"254"}, {"254"}); assertTypeExpansion("param", {"254.128"}, {"254"}); assertTypeExpansion("param", {"254.129"}, {"129254"}); assertTypeExpansion("param", {"3003"}, {"3003"}); assertTypeExpansion("param", {"999.128"}, {"999"}); EXPECT_THROWS_AS(assertTypeExpansion("param", {"999.129"}, {"129999"}), eckit::UserError); EXPECT_THROWS_AS(assertTypeExpansion("param", {"1000.128"}, {""}), eckit::UserError); EXPECT_THROWS_AS(assertTypeExpansion("param", {"228003.228"}, {""}), eckit::UserError); } CASE("trivial") { std::vector user = {"1", "2", "3"}; std::vector axis = {"1", "2", "3"}; std::vector expect = {"1", "2", "3"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind1") { std::vector user = {"131", "132"}; std::vector axis = {"138", "155"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind2") { std::vector user = {"131", "132"}; std::vector axis = {"131", "132", "138", "155"}; std::vector expect = {"131", "132"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind3") { std::vector user = {"131", "132", "138", "155"}; std::vector axis = {"138", "155"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind4") { std::vector user = {"131.128", "132.128", "138.128", "155.128"}; std::vector axis = {"138.128", "155.128"}; std::vector expect = {"131.128", "132.128", "138.128", "155.128"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind5") { std::vector user = { "131.128", "132.128", }; std::vector axis = {"138", "155"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind6") { std::vector user = {"131.128", "132.128", "138.128", "155.128"}; std::vector axis = {"138", "155"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind7") { std::vector user = {"131", "132", "138", "155"}; std::vector axis = {"138.128", "155.128"}; std::vector expect = {"131.128", "132.128", "138.128", "155.128"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind8") { std::vector user = { "131", "132", }; std::vector axis = {"138.128", "155.128"}; std::vector expect = {"131.128", "132.128", "138.128", "155.128"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind9") { std::vector user = { "131", "132", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind10") { std::vector user = { "131", "132", }; std::vector axis = {"129138", "129155"}; std::vector expect = {"129131", "129132", "129138", "129155"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind11") { std::vector user = { "131", "132", }; std::vector axis = {"138", "155", "129138", "129155"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind12") { std::vector user = { "131", "132.129", }; std::vector axis = {"138", "155", "129138", "129155"}; std::vector expect = {"131", "129132", "138", "155", "129138", "129155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind13") { std::vector user = { "131.128", "132.128", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind14") { std::vector user = { "131", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"131", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind15") { std::vector user = { "210131", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"210131"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind16") { std::vector user = { "210131", "131", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"210131", "131", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind17") { std::vector user = { "210131", "132", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"210131", "132", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind18") { std::vector user = { "210131", "131.128", }; std::vector axis = {"138", "155", "210131"}; std::vector expect = {"210131", "131", "138", "155"}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, expect, /*expectWind*/ true, metkit::NormalisationMode::Strict); } CASE("wind19") { std::vector user = { "132", }; std::vector axis = {"160138", "160155", "160131"}; std::vector expect = {"160132", "160138", "160155"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind20") { std::vector user = { "131", }; std::vector axis = {"160138", "160155", "160131"}; std::vector expect = {"160131"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("wind21") { std::vector user = { "132", }; std::vector axis = { "120138", "120155", "170138", "170155", }; std::vector expect = {"170132", "170138", "170155"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ true); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("mixed") { std::vector user = { "129", }; std::vector axis = {"129.128", "129"}; std::vector expect = {"129"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("ocean1") { std::vector user = {"145"}; std::vector axis = {"145.128", "164.128", "175.128", "148.128", "145.151", "164.151", "175.151", "148.151"}; std::vector expect = {"145.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("ocean2") { std::vector user = {"145", "151145"}; std::vector axis = {"145.128", "164.128", "175.128", "148.128", "145.151", "164.151", "175.151", "148.151"}; std::vector expect = {"145.128", "145.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("ocean3") { std::vector user = {"145", "164", "175", "148", "151145"}; std::vector axis = {"145.128", "164.128", "175.128", "148.128", "145.151", "164.151", "175.151", "148.151"}; std::vector expect = {"145.128", "164.128", "175.128", "148.128", "145.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("ocean4") { std::vector user = {"145", "164", "175", "148"}; std::vector axis = {"145.151", "164.151", "175.151", "148.151"}; std::vector expect = {"145.151", "164.151", "175.151", "148.151"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("MARS-794 - GRIB1 and GRIB2 in axis") { // Note the paramId's at the end std::vector axis = { "1.228", "3.228", "7.228", "8.128", "8.228", "9.128", "9.228", "10.228", "11.228", "12.228", "13.228", "14.228", "20.3", "20.128", "21.228", "22.228", "23.228", "24.228", "26.128", "29.228", "31.128", "32.128", "33.128", "34.128", "35.128", "36.128", "37.128", "38.128", "39.128", "40.128", "41.128", "42.128", "44.128", "44.228", "45.128", "46.228", "47.128", "47.228", "48.228", "49.128", "50.128", "57.128", "58.128", "59.128", "66.128", "67.128", "78.128", "79.128", "80.228", "81.228", "82.228", "83.228", "84.228", "85.228", "88.228", "89.228", "90.228", "98.174", "129.128", "130.151", "131.151", "131.228", "132.151", "132.228", "134.128", "136.128", "137.128", "139.128", "141.128", "142.128", "143.128", "144.128", "145.128", "145.151", "146.128", "147.128", "148.128", "148.151", "151.128", "159.128", "163.151", "164.128", "164.151", "165.128", "166.128", "167.128", "168.128", "169.128", "170.128", "172.128", "175.128", "175.151", "176.128", "177.128", "178.128", "179.128", "180.128", "181.128", "182.128", "183.128", "186.128", "187.128", "188.128", "189.128", "195.128", "196.128", "197.128", "198.128", "201.128", "202.128", "205.128", "206.128", "208.128", "209.128", "210.128", "211.128", "212.128", "213.128", "216.228", "217.228", "218.228", "219.228", "220.228", "221.228", "226.228", "227.228", "228.128", "229.128", "230.128", "231.128", "232.128", "235.128", "236.128", "238.128", "239.228", "240.228", "241.228", "243.128", "244.128", "245.128", "246.228", "247.228", "251.228", "162071", "162072", "228050", "228051", "260015", "260048", "260109", "260121", "260123", "26.228", "27.228", "28.228", "121.128", "122.128", "123.128", "222.228", "223.228", "224.228", "225.228", "228035", "228036", "228057", "228058"}; // by parameter xxx SECTION("by xxx only - priority to table 128") { std::vector user = {"148", "145", "164", "175"}; std::vector expect = {"148.128", "145.128", "164.128", "175.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by xxx and param.table") { std::vector user = {"148", "145.128", "164", "175.151"}; std::vector expect = {"148.128", "145.128", "164.128", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by xxx and paramId") { std::vector user = {"148", "128145", "164", "151175"}; std::vector expect = {"148.128", "145.128", "164.128", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by xxx and paramId and param.table") { std::vector user = {"148", "128145", "164", "175.151"}; std::vector expect = {"148.128", "145.128", "164.128", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by xxx and paramId all ") { std::vector user = {"148", "145", "164", "175", "151148", "151145", "151164", "151175"}; std::vector expect = {"148.128", "145.128", "164.128", "175.128", "148.151", "145.151", "164.151", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } // by paramId SECTION("by paramId only 128") { std::vector user = {"128148", "128145", "128164", "128175"}; std::vector expect = {"148.128", "145.128", "164.128", "175.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by paramId only 151") { std::vector user = {"151148", "151145", "151164", "151175"}; std::vector expect = {"148.151", "145.151", "164.151", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by paramId mixed 128 and 151") { std::vector user = {"151148", "128145", "128164", "151175"}; std::vector expect = {"148.151", "145.128", "164.128", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by paramId all") { std::vector user = {"151148", "151145", "151164", "151175", "128148", "128145", "128164", "128175"}; std::vector expect = {"148.151", "145.151", "164.151", "175.151", "148.128", "145.128", "164.128", "175.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } // by param.table SECTION("by param.table only 128") { std::vector user = {"148.128", "145.128", "164.128", "175.128"}; std::vector expect = {"148.128", "145.128", "164.128", "175.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by param.table only 151") { std::vector user = {"148.151", "145.151", "164.151", "175.151"}; std::vector expect = {"148.151", "145.151", "164.151", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by param.table mixed 128 and 151") { std::vector user = {"148.128", "145.151", "164.128", "175.151"}; std::vector expect = {"148.128", "145.151", "164.128", "175.151"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } SECTION("by param.table all") { std::vector user = {"148.151", "145.151", "164.151", "175.151", "148.128", "145.128", "164.128", "175.128"}; std::vector expect = {"148.151", "145.151", "164.151", "175.151", "148.128", "145.128", "164.128", "175.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } } CASE("table1") { std::vector user = {"129", "130.128"}; std::vector axis = {"129.128", "130"}; std::vector expect = {"129.128", "130"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table2") { std::vector user = {"129", "130.128"}; std::vector axis = {"129.128", "130"}; std::vector expect = {"129.128", "130"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table3") { std::vector user = { "129", }; std::vector axis = { "140129", }; std::vector expect = {"140129"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table4") { std::vector user = { "129", }; std::vector axis = { "129.140", }; std::vector expect = {"129.140"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table5") { std::vector user = { "129", }; std::vector axis = {"129.128", "129.140"}; std::vector expect = {"129.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table6") { std::vector user = { "129.128", }; std::vector axis = {"129.128", "129.140"}; std::vector expect = {"129.128"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table7") { std::vector user = { "129", }; std::vector axis = {"129", "140129"}; std::vector expect = {"129"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table8") { std::vector user = { "129.128", }; std::vector axis = {"129", "140129"}; std::vector expect = {"129"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table9") { std::vector user = { "129.128", }; std::vector axis = {"129", "140129"}; std::vector expect = {"129"}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table10") { std::vector user = {"131"}; std::vector axis = {"210131"}; std::vector expect = {}; test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Loose); test_param_axis(user, axis, axis, /*expectWind*/ false, metkit::NormalisationMode::FullTableDropping); } CASE("table11") { std::vector user = {"131"}; std::vector axis = {"131.210"}; std::vector expect = {}; test_param_axis(user, axis, expect, /*expectWind*/ false, metkit::NormalisationMode::Loose); test_param_axis(user, axis, axis, /*expectWind*/ false, metkit::NormalisationMode::FullTableDropping); } CASE("table12") { std::vector user = {"131", "132"}; std::vector axis = {"210131", "170131", "180131", "160132"}; std::vector expect = {"170131", "160132"}; test_param_axis(user, axis, expect, /*expectWind*/ false); } CASE("table13") { std::vector user = {"131", "132"}; std::vector axis = {"210131", "131.170", "180131", "160132"}; std::vector expect = {"131.170", "160132"}; std::vector empty = {}; test_param_axis(user, axis, expect, /*expectWind*/ false); test_param_axis(user, axis, empty, /*expectWind*/ false, metkit::NormalisationMode::Strict); } CASE("table14") { std::vector user = {"134", "133"}; std::vector axis = {"210131", "133.170", "180134"}; std::vector expect = {"180134", "133.170"}; test_param_axis(user, axis, expect, /*expectWind*/ false); } CASE("table15") { std::vector user = {"134", "133.128"}; std::vector axis = {"210131", "133.170", "180134"}; std::vector expect = {"180134"}; test_param_axis(user, axis, expect, /*expectWind*/ false); } } // namespace metkit::mars::test //----------------------------------------------------------------------------- int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_codes_api.cc0000664000175000017500000002541115246725757017742 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ // Include operator<< via LocalConfiguration #include #include "eccodes.h" #include "eckit/config/LocalConfiguration.h" #include "eckit/io/Buffer.h" #include "eckit/testing/Test.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/CodesTypes.h" #include "metkit/codes/api/KeyIterator.h" namespace metkit::grib::test { //----------------------------------------------------------------------------- // No explicit expectations here // However, we iterate the whole sample and print all keys // Keys are fetched by inspecting the native type and calling a specialized getXXX // A non-throwing behaviour is at least CASE("Test iterate sample, getting all keys by native type on iterator") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); for (auto& k : handle->keys()) { // std::cout << "\t" << k.name() << ": "; auto valFromIt = k.get(); auto valFromHandle = handle->get(k.name()); auto type = k.type(); // std::visit([](const auto& v) { std::cout << v << std::endl; }, valFromIt); if (k.name() != "sectionNumber" && k.name() != "numberOfSection") { EXPECT_EQUAL(valFromIt.index(), valFromHandle.index()); std::visit( [&](const auto& v) { // std::cout << "\t(from handle): " << v << std::endl; EXPECT_EQUAL(v, std::get>(valFromHandle)); }, valFromIt); } } } CASE("Test iterate and rewrite keys") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); using IF = KeyIteratorFlags; std::vector> values; for (auto& k : handle->keys(IF::SkipReadOnly)) { values.emplace_back(k.name(), k.get()); } EXPECT(values.size() > 0); for (const auto& p : std::move(values)) { // TODO(pgeier) Raise eccodes bug about not readonly keys if (p.first == "validityDateTime") continue; if (p.first == "productType") continue; if (p.first == "isTemplateDeprecated") continue; if (p.first == "isTemplateExperimental") continue; if (p.first == "datasetForLocal") continue; if (p.first == "isMessageValid") continue; try { std::visit([&](const auto& val) { handle->set(p.first, val); }, p.second); } catch (...) { std::cerr << "Error setting " << p.first << ": "; std::visit([&](const auto& val) { std::cerr << val << std::endl; }, p.second); throw; } }; } CASE("Test geo iterator") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); long numberValues = handle->getLong("numberOfValues"); auto lons = handle->getDoubleArray("longitudes"); auto lats = handle->getDoubleArray("latitudes"); auto values = handle->getDoubleArray("values"); EXPECT_EQUAL(lons.size(), numberValues); EXPECT_EQUAL(lats.size(), numberValues); EXPECT_EQUAL(values.size(), numberValues); long count = 0; // TODO(pgeier) Use structured binding with C++20; for (const auto& {longitude, latitude, value}: handle->values()) // {} for (const auto& data : handle->values()) { // std::cout << "\t" << count << ": " << data.longitude << "/" << data.latitude << ": " << data.value << // std::endl; EXPECT_EQUAL(lons[count], data.longitude); EXPECT_EQUAL(lats[count], data.latitude); EXPECT_EQUAL(values[count], data.value); ++count; } EXPECT_EQUAL(count, numberValues); } CASE("Test setting values") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); long numberValues = handle->getLong("numberOfValues"); std::vector newVals; for (int i = 0; i < numberValues; ++i) { newVals.push_back((double)i); } EXPECT_NO_THROW(handle->set("values", newVals)); auto values = handle->getDoubleArray("values"); EXPECT_EQUAL(newVals, values); long count = 0; for (const auto& data : handle->values()) { EXPECT_EQUAL(newVals[count], data.value); ++count; } EXPECT_EQUAL(count, numberValues); } CASE("Test load and iterate mars keys") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); handle->set("date", 20250101); handle->set("time", 1400); handle->set("step", (long)18); handle->set("param", (long)132); for (auto& k : handle->keys(namespaces::mars)) { if (k.name() == "date") { EXPECT_EQUAL(std::get(k.get()), 20250101); } if (k.name() == "time") { EXPECT_EQUAL(std::get(k.get()), 1400); } if (k.name() == "step") { EXPECT_EQUAL(std::get(k.get()), 18); } if (k.name() == "levtype") { EXPECT_EQUAL(std::get(k.get()), "sfc"); } if (k.name() == "param") { EXPECT_EQUAL(std::get(k.get()), 132); } } } CASE("Test isDefined, has, isMissing, set MARS key \"class\"") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); EXPECT(!handle->isDefined("class")); EXPECT(!handle->has("class")); EXPECT(!handle->isDefined("anoffset")); EXPECT(!handle->has("anoffset")); // Set a local definition template with mars keys EXPECT_NO_THROW(handle->set("setLocalDefinition", 1)); EXPECT_NO_THROW(handle->set("localDefinitionNumber", 15)); // Mars directly get a "default" value instead of being set to missing EXPECT(handle->isDefined("class")); EXPECT(!handle->isMissing("class")); EXPECT(handle->has("class")); EXPECT_NO_THROW(handle->set("class", "od")); EXPECT_EQUAL(handle->getString("class"), std::string("od")); EXPECT(!handle->isMissing("class")); EXPECT(handle->has("class")); // Mars key can not set to be missing EXPECT_THROWS(handle->setMissing("class")); } CASE("Test set missing") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); EXPECT_NO_THROW(handle->set("productDefinitionTemplateNumber", 0)); EXPECT(handle->isDefined("scaledValueOfFirstFixedSurface")); EXPECT(handle->isMissing("scaledValueOfFirstFixedSurface")); EXPECT(!handle->has("scaledValueOfFirstFixedSurface")); EXPECT_NO_THROW(handle->set("scaledValueOfFirstFixedSurface", 123)); EXPECT_EQUAL(handle->getLong("scaledValueOfFirstFixedSurface"), 123); EXPECT(!handle->isMissing("scaledValueOfFirstFixedSurface")); EXPECT(handle->has("scaledValueOfFirstFixedSurface")); EXPECT_NO_THROW(handle->setMissing("scaledValueOfFirstFixedSurface")); EXPECT(handle->isMissing("scaledValueOfFirstFixedSurface")); EXPECT(!handle->has("scaledValueOfFirstFixedSurface")); } CASE("Test copyInto and clone") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); size_t size = handle->messageSize(); std::vector bytes(size); EXPECT_NO_THROW(handle->copyInto(bytes.data(), size)); auto handle2 = handle->clone(); EXPECT_EQUAL(handle2->messageSize(), size); size_t size2 = handle2->messageSize(); std::vector bytes2(size2); EXPECT_NO_THROW(handle2->copyInto(bytes2.data(), size2)); EXPECT_EQUAL(size, size2); for (int i = 0; i < size; ++i) { EXPECT_EQUAL(bytes[i], bytes2[i]); } } CASE("Test copyInto and codesHandleFromMessage") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); size_t size = handle->messageSize(); std::vector bytes(size); EXPECT_NO_THROW(handle->copyInto(bytes.data(), size)); auto handle2 = codesHandleFromMessage({bytes.data(), size}); EXPECT_EQUAL(handle2->messageSize(), size); size_t size2 = handle2->messageSize(); std::vector bytes2(size2); EXPECT_NO_THROW(handle2->copyInto(bytes2.data(), size2)); EXPECT_EQUAL(size, size2); for (int i = 0; i < size; ++i) { EXPECT_EQUAL(bytes[i], bytes2[i]); } } CASE("Test copyInto and codesHandleFromMessageCopy") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); size_t size = handle->messageSize(); std::vector bytes(size); EXPECT_NO_THROW(handle->copyInto(bytes.data(), size)); auto handle2 = codesHandleFromMessageCopy({bytes.data(), size}); EXPECT_EQUAL(handle2->messageSize(), size); size_t size2 = handle2->messageSize(); std::vector bytes2(size2); EXPECT_NO_THROW(handle2->copyInto(bytes2.data(), size2)); EXPECT_EQUAL(size, size2); for (int i = 0; i < size; ++i) { EXPECT_EQUAL(bytes[i], bytes2[i]); } } CASE("Test copyInto and codesHandleFromFile") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); size_t size = handle->messageSize(); std::vector bytes(size); EXPECT_NO_THROW(handle->copyInto(bytes.data(), size)); std::string ofname{"GRIB2.tmpl"}; { std::ofstream of(ofname, std::ios::binary); if (!of) { throw std::runtime_error("Failed to open file: " + ofname); } of.write(reinterpret_cast(bytes.data()), size); of.close(); } auto handle2 = codesHandleFromFile(ofname, Product::GRIB); EXPECT_EQUAL(handle2->messageSize(), size); size_t size2 = handle2->messageSize(); std::vector bytes2(size2); EXPECT_NO_THROW(handle2->copyInto(bytes2.data(), size2)); EXPECT_EQUAL(size, size2); for (int i = 0; i < size; ++i) { EXPECT_EQUAL(bytes[i], bytes2[i]); } } } // namespace metkit::grib::test namespace std { template <> struct default_delete { void operator()(codes_handle* h) { ::codes_handle_delete(h); } }; } // namespace std namespace metkit::grib::test { CASE("Test release handle") { using namespace codes; auto handle = codesHandleFromSample("GRIB2"); std::unique_ptr raw = std::unique_ptr(reinterpret_cast(handle->release())); EXPECT(raw); EXPECT_THROWS_AS(handle->getLong("discipline"), CodesException); } //----------------------------------------------------------------------------- } // namespace metkit::grib::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/sol.grib0000664000175000017500000001604015246725757016106 0ustar alastairalastairGRIBb$ ( 5659 @( Z#Z#q $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($ " @* 7777GRIBb$ ( 5659 @( Z#Z#q $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($ " @* 7777GRIBb$ ( 5659 @( Z#Z#q $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($ " @* 7777GRIBb$ ( 5659 @( Z#Z#q $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPTX\`dhlptx|  $(,048<@DHLPPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($  |xtplhd`\XTPLHD@<840,($ " @* 7777metkit-1.20.2/tests/test_step.cc0000664000175000017500000000705315246725757016771 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_step.cc /// @author Emanuele Danovaro /// @date March 2025 #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Type.h" namespace metkit::mars::test { using ::eckit::BadValue; //----------------------------------------------------------------------------- void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); language.type(name)->expand(values); EXPECT_EQUAL(expected, values); } CASE("Test Step expansions") { // times with units assertTypeExpansion("step", {"0"}, {"0"}); assertTypeExpansion("step", {"12"}, {"12"}); assertTypeExpansion("step", {"260m"}, {"4h20m"}); assertTypeExpansion("step", {"30m", "1h", "1h30m", "120m"}, {"30m", "1", "1h30m", "2"}); assertTypeExpansion("step", {"0-1"}, {"0-1"}); assertTypeExpansion("step", {"30m-60m"}, {"30m-1"}); EXPECT_THROWS_AS(assertTypeExpansion("step", {"-1"}, {""}), BadValue); EXPECT_THROWS_AS(assertTypeExpansion("step", {"2-1"}, {""}), BadValue); assertTypeExpansion("step", {"0-3", "to", "9-12", "by", "3h"}, {"0-3", "3-6", "6-9", "9-12"}); assertTypeExpansion("step", {"0-3", "to", "0-12", "by", "3"}, {"0-3"}); assertTypeExpansion("step", {"0-30m", "to", "1h30m-2h", "by", "30m"}, {"0-30m", "30m-1", "1-1h30m", "1h30m-2"}); assertTypeExpansion( "step", {"0m", "to", "1440m", "by", "10m"}, {"0", "10m", "20m", "30m", "40m", "50m", "1", "1h10m", "1h20m", "1h30m", "1h40m", "1h50m", "2", "2h10m", "2h20m", "2h30m", "2h40m", "2h50m", "3", "3h10m", "3h20m", "3h30m", "3h40m", "3h50m", "4", "4h10m", "4h20m", "4h30m", "4h40m", "4h50m", "5", "5h10m", "5h20m", "5h30m", "5h40m", "5h50m", "6", "6h10m", "6h20m", "6h30m", "6h40m", "6h50m", "7", "7h10m", "7h20m", "7h30m", "7h40m", "7h50m", "8", "8h10m", "8h20m", "8h30m", "8h40m", "8h50m", "9", "9h10m", "9h20m", "9h30m", "9h40m", "9h50m", "10", "10h10m", "10h20m", "10h30m", "10h40m", "10h50m", "11", "11h10m", "11h20m", "11h30m", "11h40m", "11h50m", "12", "12h10m", "12h20m", "12h30m", "12h40m", "12h50m", "13", "13h10m", "13h20m", "13h30m", "13h40m", "13h50m", "14", "14h10m", "14h20m", "14h30m", "14h40m", "14h50m", "15", "15h10m", "15h20m", "15h30m", "15h40m", "15h50m", "16", "16h10m", "16h20m", "16h30m", "16h40m", "16h50m", "17", "17h10m", "17h20m", "17h30m", "17h40m", "17h50m", "18", "18h10m", "18h20m", "18h30m", "18h40m", "18h50m", "19", "19h10m", "19h20m", "19h30m", "19h40m", "19h50m", "20", "20h10m", "20h20m", "20h30m", "20h40m", "20h50m", "21", "21h10m", "21h20m", "21h30m", "21h40m", "21h50m", "22", "22h10m", "22h20m", "22h30m", "22h40m", "22h50m", "23", "23h10m", "23h20m", "23h30m", "23h40m", "23h50m", "24"}); } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_matcher.cc0000664000175000017500000001474615246725757017450 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #include "eckit/testing/Test.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Matcher.h" #include using namespace eckit::testing; using namespace metkit::mars; namespace metkit::test { // ---------------------------------------------------------------------------------------------------------------------- CASE("parse string with spaces") { std::map map = parseMatchString("expver=(x[0-9a-z]{3}), number = (1|2) , stream= ^enfo$ "); EXPECT(map.size() == 3); EXPECT(map.find("expver") != map.end()); EXPECT(map.find("number") != map.end()); EXPECT(map.find("stream") != map.end()); } CASE("parse string errors") { // Check basic parsing EXPECT_THROWS_AS(parseMatchString(""), eckit::BadValue); EXPECT_THROWS_AS(parseMatchString("expver"), eckit::BadValue); EXPECT_THROWS_AS(parseMatchString("=expver"), eckit::BadValue); EXPECT_THROWS_AS(parseMatchString("expver="), eckit::BadValue); EXPECT_THROWS_AS(parseMatchString("expver,number=(1|2)"), eckit::BadValue); EXPECT_THROWS_AS(parseMatchString("number=(1|2),number=(3|4)"), eckit::BadValue); } CASE("match basic") { Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "xxxx"); req.values("number", {"1", "2"}); req.setValue("stream", "enfo"); req.setValue("step ", "0"); // step is not in the matcher. This should have no effect on matching EXPECT_EQUAL(req.count(), 2); EXPECT_EQUAL(match_any.match(req), true); EXPECT_EQUAL(match_all.match(req), true); } CASE("partially matching request") { Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "xxxx"); req.values("number", {"1", "2", "3"}); // number=3 does not match, the others do req.setValue("stream", "enfo"); EXPECT_EQUAL(req.count(), 3); EXPECT_EQUAL(match_any.match(req), true); EXPECT_EQUAL(match_all.match(req), false); } CASE("request entirely not matching") { Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "yyyy"); // expver=yyyy does not match req.values("number", {"1", "2"}); req.setValue("stream", "enfo"); EXPECT_EQUAL(req.count(), 2); EXPECT_EQUAL(match_any.match(req), false); EXPECT_EQUAL(match_all.match(req), false); } CASE("match with missing keys") { Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "xxxx"); req.values("number", {"1", "2"}); // stream is not set: matching depends on MatchMissingPolicy EXPECT_EQUAL(req.count(), 2); EXPECT_EQUAL(match_any.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_all.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_any.match(req, Matcher::MatchOnMissing), true); EXPECT_EQUAL(match_all.match(req, Matcher::MatchOnMissing), true); } CASE("match missing key and wrong values") { // -- Combinations of the above some of the above Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "yyyy"); // yyyy does not match req.values("number", {"1", "2", "3"}); // number=3 does not match EXPECT_EQUAL(req.count(), 3); EXPECT_EQUAL(match_any.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_all.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_any.match(req, Matcher::MatchOnMissing), false); EXPECT_EQUAL(match_all.match(req, Matcher::MatchOnMissing), false); } CASE("match missing key but some values match") { // Missing key, but expver matches, number partially matches Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); req.setValue("expver", "xxxx"); req.values("number", {"1", "2", "3"}); EXPECT_EQUAL(match_any.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_all.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_any.match(req, Matcher::MatchOnMissing), true); EXPECT_EQUAL(match_all.match(req, Matcher::MatchOnMissing), false); } CASE("match empty request") { // Testing that this does not raise an exception Matcher match_any("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::Any); Matcher match_all("expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$", Matcher::Policy::All); MarsRequest req("retrieve"); EXPECT_EQUAL(match_any.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_all.match(req, Matcher::DontMatchOnMissing), false); EXPECT_EQUAL(match_any.match(req, Matcher::MatchOnMissing), true); EXPECT_EQUAL(match_all.match(req, Matcher::MatchOnMissing), true); } CASE("streaming matcher is deterministic") { Matcher matcher("stream=^enfo$, expver=(x[0-9a-z]{3}), number=(1|2)", Matcher::Policy::Any); std::ostringstream oss; oss << matcher; EXPECT_EQUAL(oss.str(), "{expver=(x[0-9a-z]{3}),number=(1|2),stream=^enfo$}"); } // ---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/expand/0000775000175000017500000000000015246725757015722 5ustar alastairalastairmetkit-1.20.2/tests/expand/MARSC-263.req0000664000175000017500000000050315246725757017606 0ustar alastairalastairretrieve, levelist=0/5, section=h, stream=ocea, system=3, method=1, number=0/to/4, time=00, levtype=dp, product=inst, expver=1, param=129/130/145, class=od, date=20120101, type=of, step=24/to/96/by/24, expect=60, database=marsod, target=data.referencemetkit-1.20.2/tests/expand/MARSC-231.req0000664000175000017500000000036015246725757017602 0ustar alastairalastairretrieve, area=58./-10./35./80,class=od,date=20231231,expver=1,grid=0.1/0.1,levtype=surf,param=2t,step=0,stream=oper,time=00:00:00/12:00:00,type=fc,target=target.bbafa703-e7da-4383-8c34-d36a7cbee4f5,repres=gg, target="reference.vEDx0X.data"metkit-1.20.2/tests/expand/MARSC-227.expected0000664000175000017500000000100715246725757020620 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 33, DATE = 20231221, TIME = 0000, STEP = 0, DOMAIN = G, TARGET = reference.zcbyW7.data, RESOL = AUTO, GRID = .125/.125, PADDING = 0, USE = INFREQUENT metkit-1.20.2/tests/expand/PGEN-569.expected0000664000175000017500000000055315246725757020522 0ustar alastairalastairdisseminate, expver = 0080, class = mc, stream = oper, time = 0000/1200, type = an, step = 0, levtype = ml, levelist = 1, param = 131/132, date = 20260420, domain = gmetkit-1.20.2/tests/expand/MARSC-254.re0000664000175000017500000000013315246725757017424 0ustar alastairalastairretrieve, type=ob, date=-3, time=00, range=1439, target=data.reference metkit-1.20.2/tests/expand/MARSC-267.expected0000664000175000017500000000114715246725757020631 0ustar alastairalastairRETRIEVE, CLASS = S2, TYPE = PF, STREAM = ENFH, EXPVER = prod, REPRES = SH, LEVTYPE = PL, LEVELIST = 200/300/500/700/850/925/1000, PARAM = 130, DATE = 20240111, HDATE = 20040111, TIME = 0000, STEP = 0/24/48/72/96/120/144/168/192/216/240/264/288/312/336/360/384/408/432/456/480/504/528/552/576/600/624/648/672/696/720/744/768/792/816/840/864/888/912/936/960/984/1008/1032/1056/1080/1104, NUMBER = 1, ORIGIN = ECMF, TARGET = target.out, AREA = 40/-180/-40/180, PADDING = 0metkit-1.20.2/tests/expand/MARSC-252.req0000664000175000017500000000041015246725757017601 0ustar alastairalastairretrieve, class=od, stream=da, type=an, levtype=sfc, levelist=off, resol=off, date=20250416, time=00, step=00, param=tcc/10u/10v/2t/msl, param=tcc, repres=gg, format=p, expver=0001, target=data.reference metkit-1.20.2/tests/expand/MARSC-248.req0000664000175000017500000000017215246725757017613 0ustar alastairalastairretrieve, levtype=pl, level=1000/500, date=20250416, param=t, accuracy=LOW, target=data.reference metkit-1.20.2/tests/expand/MARSC-253.expected0000664000175000017500000000044215246725757020621 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = TF, STREAM = ENFO, EXPVER = 0001, REPRES = BU, OBSTYPE = 32, DATE = 20060112, TIME = 0000/1200, STEP = 0, DOMAIN = G, TARGET = data.reference, DUPLICATES = KEEP metkit-1.20.2/tests/expand/MARSC-255.req0000664000175000017500000000025415246725757017612 0ustar alastairalastairretrieve, date=20250416, area=75/60/10/-20, grid=2.5/2.5, levtype=ml, levelist=50, param=248, interpolation=bilinear, target=data.reference metkit-1.20.2/tests/expand/MARSC-228.re0000664000175000017500000000022015246725757017422 0ustar alastairalastairretrieve, date=20210501,time=2101/to/2400,type=ob,target="/ec/fws2/sb/work/rd/erds/i7ug/LWDA/2021050200/an/scat", target="reference.xoB8Cx.data"metkit-1.20.2/tests/expand/MARSC-241.req0000664000175000017500000000024315246725757017603 0ustar alastairalastairretrieve, DOMAIN=G,LEVTYPE=ML,LEVELIST=1,DATE=20041015,TIME=1200,STEP=0,PARAM=73.210/74.210,CLASS=RD,TYPE=FC,STREAM=OPER,EXPVER=f36f, target=reference.datametkit-1.20.2/tests/expand/MARSC-245.expected0000664000175000017500000000062415246725757020624 0ustar alastairalastairRETRIEVE, CLASS = MS, COUNTRY = IT, MODEL = HRM, BCMODEL = IFS, ICMODEL = MVOI, DOMAIN = EUROATL, STREAM = OPER, TYPE = FG, EXPVER = 0001, LEVTYPE = 110, PARAM = 201033, TIME = 1800, DATE = 20040102, STEP = 6, LEVELIST = 1, TARGET = reference.data, GRIB = ECMWFmetkit-1.20.2/tests/expand/MARSC-228.expected0000664000175000017500000000052715246725757020627 0ustar alastairalastairRETRIEVE,     CLASS      = OD,     TYPE       = OB,     STREAM     = OPER,     EXPVER     = 0001,     REPRES     = BU,     OBSTYPE    = 1,     DATE       = 20210501,     TIME       = 2101,     DOMAIN     = G,     TARGET     = reference.xoB8Cx.data,     DUPLICATES = KEEP,     RANGE      = 179metkit-1.20.2/tests/expand/MARSC-244.expected0000664000175000017500000000060215246725757020617 0ustar alastairalastairRETRIEVE, CLASS = TI, TYPE = PF, STREAM = ENFO, EXPVER = test, MODEL = LAM, REPRES = SH, LEVTYPE = SFC, PARAM = 166, DATE = 20130225, TIME = 0000, STEP = 3, NUMBER = 1, ORIGIN = SRNWPPEPS-DWD-EUA, TARGET = reference.data, PADDING = 0, PROCESS = LOCALmetkit-1.20.2/tests/expand/MARSC-236.req0000664000175000017500000000033315246725757017607 0ustar alastairalastairretrieve, accuracy=12,area=90.0/0.0/-90.0/359.5,date=20240102,domain=g,grid=0.5/0.5,leve=off,levtype=sfc,padding=0,param=134/137/165/166/167/168/235,stream=da,style=dissemination,time=00,type=an, target="data.reference"metkit-1.20.2/tests/expand/MARSC-258.expected0000664000175000017500000000053515246725757020631 0ustar alastairalastairRETRIEVE, CLASS = RD, TYPE = FC, STREAM = LWDA, EXPVER = h3f7, REPRES = SH, LEVTYPE = PL, LEVELIST = 1000/850/700/500/400/300, PARAM = 129, DATE = 20181101, TIME = 0000, STEP = ALL, ANOFFSET = 9, DOMAIN = G, TARGET = data.reference metkit-1.20.2/tests/expand/MARSC-241.expected0000664000175000017500000000047115246725757020620 0ustar alastairalastairRETRIEVE, CLASS = RD, TYPE = FC, STREAM = OPER, EXPVER = f36f, REPRES = SH, LEVTYPE = ML, LEVELIST = 1, PARAM = 210073/210074, DATE = 20041015, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = reference.datametkit-1.20.2/tests/expand/MARSC-385.expected0000664000175000017500000000067715246725757020641 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = PF, STREAM = WAEF, EXPVER = 0001, REPRES = SH, LEVTYPE = SFC, PARAM = 140229/140232/140245, DATE = 20201101, TIME = 0000/0600/1200/1800, STEP = 9, NUMBER = 1, DOMAIN = G, TARGET = reference.0GhkzD.data, RESOL = AUTO, AREA = 39.5/140/39.5/140, GRID = .1/.1, PADDING = 0 metkit-1.20.2/tests/expand/MARSC-227.req0000664000175000017500000000025615246725757017613 0ustar alastairalastairretrieve, date=2023-12-21,expver=1,grid=0.125/0.125,levtype=sfc,padding=0,param=33.128,step=0,stream=oper,time=00:00:00,type=fc,use=infrequent, target="reference.zcbyW7.data"metkit-1.20.2/tests/expand/MARSC-248.expected0000664000175000017500000000051315246725757020624 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = AN, STREAM = OPER, EXPVER = 0001, REPRES = SH, LEVTYPE = PL, LEVELIST = 1000/500, PARAM = 130, DATE = 20250416, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = data.reference, ACCURACY = 8 metkit-1.20.2/tests/expand/MARSC-264.expected0000664000175000017500000000064015246725757020623 0ustar alastairalastair RETRIEVE, CLASS = OD, TYPE = FCMAX, STREAM = SFMM, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 167, DATE = 19911001, FCMONTH = 2/3/4, TIME = 0000, NUMBER = 0/1/2/3/4, DOMAIN = G, SYSTEM = 2, METHOD = 1, SOURCE = mod.cl.surface, TARGET = data.reference, DATABASE = filemetkit-1.20.2/tests/expand/MARSC-234.req0000664000175000017500000000033515246725757017607 0ustar alastairalastairretrieve, area=Europe,class=od,date=2023-12-20,expver=1,grid=0.125/0.125,levtype=sfc,param=22.228,step=0,stream=oper,time=12:00:00,type=fc,target=target.27f72f51-1e60-437f-b1ce-e57129e2dff6, target="reference.A0FgVE.data"metkit-1.20.2/tests/expand/MARSC-249.req0000664000175000017500000000021415246725757017611 0ustar alastairalastairretrieve, levtype=pl, level=1000/500, date=20250416, param=t, accuracy=REDUCED, grid=5/5, target=data.reference metkit-1.20.2/tests/expand/MARSC-233.expected0000664000175000017500000000073115246725757020620 0ustar alastairalastairRETRIEVE, DATASET = tigge, CLASS = TI, TYPE = FC, STREAM = OPER, EXPVER = prod, REPRES = SH, LEVTYPE = PL, LEVELIST = 250/500/850, PARAM = 156, DATE = 20231224, TIME = 0000/1200, STEP = 0, ORIGIN = RKSL, TARGET = reference.data, RESOL = AUTO, AREA = 90/0/-90/359.75, GRID = .25/.25, PADDING = 0, PROCESS = LOCAL metkit-1.20.2/tests/expand/MARSC-263.expected0000664000175000017500000000074015246725757020623 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = OF, STREAM = OCEA, EXPVER = 0001, REPRES = SH, LEVTYPE = DP, LEVELIST = 0/5, PARAM = 129/130/145, DATE = 20120101, TIME = 0000, STEP = 24/48/72/96, NUMBER = 0/1/2/3/4, DOMAIN = G, SYSTEM = 3, METHOD = 1, PRODUCT = INST, SECTION = H, TARGET = data.reference, DATABASE = marsod, EXPECT = 60metkit-1.20.2/tests/expand/MARSC-264.req0000664000175000017500000000045315246725757017613 0ustar alastairalastairretrieve, class=od, type=fcmax, stream=sfmm, expver=0001, repres=gg, levtype=sfc, param=167, date=19911001, fcmonth=02/03/04, time=0000, number=0/1/2/3/4, domain=g, system=02, method=01, source="mod.cl.surface", target="data.reference"metkit-1.20.2/tests/expand/MARSC-255.expected0000664000175000017500000000063715246725757020631 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = AN, STREAM = OPER, EXPVER = 0001, REPRES = SH, LEVTYPE = ML, LEVELIST = 50, PARAM = 248, DATE = 20250416, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = data.reference, RESOL = AUTO, INTERPOLATION = BILINEAR, AREA = 75/60/10/-20, GRID = 2.5/2.5 metkit-1.20.2/tests/expand/PGEN-569.req0000664000175000017500000000050615246725757017506 0ustar alastairalastairdisseminate, expver = 80, class = mc, stream = oper, time = 0/12, type = an, step = 0, levtype = ml, levelist = 1, param = u/v, date = 20260420metkit-1.20.2/tests/expand/MARSC-240.req0000664000175000017500000000036015246725757017602 0ustar alastairalastairretrieve, DOMAIN=G,LEVTYPE=DP,DATE=20070601,TIME=0000,STEP=288,PARAM=145.151,CLASS=OD,TYPE=OF,STREAM=OCEA,EXPVER=0001,NUMBER=0,SYSTEM=2,METHOD=1,PRODUCT=TIMS,SECTION=Z,LEVELIST=0.000,LATITUDE=-9.967,RANGE=264, target="reference.0DY4f2.data"metkit-1.20.2/tests/expand/MARSC-233 copy.expected0000664000175000017500000000070015246725757021547 0ustar alastairalastairRETRIEVE, DATASET = tigge, CLASS = TI, TYPE = FC, STREAM = OPER, EXPVER = prod, REPRES = SH, LEVTYPE = PL, LEVELIST = 250/500/850, PARAM = 156, DATE = 20231224, TIME = 0000/1200, STEP = 0, ORIGIN = RKSL, TARGET = reference.data, RESOL = AUTO, AREA = 90/0/-90/359.75, GRID = .25/.25, PADDING = 0metkit-1.20.2/tests/expand/MARSC-258.req0000664000175000017500000000025715246725757017620 0ustar alastairalastairretrieve, class=rd, stream=lwda, levtype=pl, type=fc, expver=h3f7, step=all, anoffset=9, time=00, date=20181101, target=data.reference metkit-1.20.2/tests/expand/MARSC-233.req0000664000175000017500000000033515246725757017606 0ustar alastairalastairretrieve,area=90.0/0.0/-90.0/359.75,class=ti,date=20231224,expver=prod,grid=0.25/0.25,levtype=pl,padding=0,param=156,step=0,time=0000/1200,type=fc,levelist=250/500/850,origin=rksl,dataset=tigge, target=reference.data metkit-1.20.2/tests/expand/MARSC-225.req0000664000175000017500000000027115246725757017606 0ustar alastairalastairretrieve, class=ti,date=2023-12-30,expver=prod,grid=0.2/0.2,levtype=sfc,padding=0,param=167,step=0/to/360/by/6,time=00:00:00,type=pf,number=1,origin=ecmf, target="reference.8nWO8K.data"metkit-1.20.2/tests/expand/MARSC-251.expected0000664000175000017500000000056415246725757020624 0ustar alastairalastairRETRIEVE, CLASS = TI, TYPE = CF, STREAM = ENFO, EXPVER = ALL, REPRES = SH, LEVTYPE = SFC, PARAM = 167, DATE = 20250414, TIME = 1200, STEP = 24, ORIGIN = ALL, TARGET = data.reference, PADDING = 0, PROCESS = LOCAL, EXPECT = ANY, NUMBER = 0metkit-1.20.2/tests/expand/MARSC-242.expected0000664000175000017500000000037715246725757020626 0ustar alastairalastairRETRIEVE, CLASS = E4, TYPE = CL, STREAM = MODA, EXPVER = 0001, REPRES = SH, LEVTYPE = ML, LEVELIST = 1, PARAM = 155, DATE = JAN, DOMAIN = G, TARGET = reference.datametkit-1.20.2/tests/expand/MARSC-249.expected0000664000175000017500000000057015246725757020630 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = AN, STREAM = OPER, EXPVER = 0001, REPRES = SH, LEVTYPE = PL, LEVELIST = 1000/500, PARAM = 130, DATE = 20250416, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = data.reference, RESOL = AUTO, ACCURACY = 8, GRID = 5/5 metkit-1.20.2/tests/expand/MARSC-251.req0000664000175000017500000000031415246725757017603 0ustar alastairalastairretrieve, class=ti, date=20250414, time=12, origin=all, expver=all, type=cf, stream=enfo, levtype=sfc, param=2t, step=24, expect=any, target=data.reference metkit-1.20.2/tests/expand/MARSC-385.req0000664000175000017500000000041115246725757017611 0ustar alastairalastairretrieve, area=39.5/140/39.5/140, class=od, date=20201101, expver=1, grid=0.1/0.1, number=1, padding=0, param=229.140/232.140/245.140, step=9, stream=waef, time=00/06/12/18, type=pf, target=reference.0GhkzD.data metkit-1.20.2/tests/expand/MARSC-236.expected0000664000175000017500000000071415246725757020624 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = AN, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 134/137/165/166/167/168/235, DATE = 20240102, TIME = 0000, STEP = 0, DOMAIN = G, TARGET = data.reference, RESOL = AV, ACCURACY = 12, STYLE = DISSEMINATION, AREA = 90/0/-90/359.5, GRID = .5/.5, PADDING = 0 metkit-1.20.2/tests/expand/MARSC-231.expected0000664000175000017500000000056715246725757020625 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 167, DATE = 20231231, TIME = 0000/1200, STEP = 0, DOMAIN = G, TARGET = reference.vEDx0X.data, RESOL = AUTO, AREA = 58/-10/35/80, GRID = .1/.1 metkit-1.20.2/tests/expand/MARSC-242.req0000664000175000017500000000021015246725757017576 0ustar alastairalastairretrieve, DOMAIN=G,LEVTYPE=ML,LEVELIST=1,DATE=JAN,PARAM=155.128,CLASS=E4,TYPE=CL,STREAM=MODA,EXPVER=0001, target=reference.data metkit-1.20.2/tests/expand/MARSC-261.expected0000664000175000017500000000063215246725757020621 0ustar alastairalastair RETRIEVE, CLASS = OD, TYPE = PF, STREAM = ENFO, EXPVER = 0001, REPRES = SH, LEVTYPE = SFC, PARAM = 167/165/166/164/228, DATE = 20250416, TIME = 0000, STEP = 6/12, NUMBER = 1/2, DOMAIN = G, TARGET = data.reference, RESOL = N128, AREA = 70.5/-21/30/40.5, GRID = 1.5/1.5metkit-1.20.2/tests/expand/MARSC-247.expected0000664000175000017500000000050715246725757020626 0ustar alastairalastairRETRIEVE, CLASS = RD, TYPE = AN, STREAM = OPER, EXPVER = aaaa, REPRES = SH, LEVTYPE = PL, LEVELIST = 1000/850/700/500/400/300, PARAM = 129, DATE = 20250413, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = data.reference metkit-1.20.2/tests/expand/MARSC-232.req0000664000175000017500000000035515246725757017607 0ustar alastairalastairretrieve, area=85.000/-180.000/-60.000/180.000,class=od,date=20231003/to/20231003,expver=1,grid=0.100/0.100,levtype=sfc,padding=0,param=144.128,step=0/to/12/by/1,stream=oper,time=00/12,type=fc,dataset=mars, target=reference.data metkit-1.20.2/tests/expand/MARSC-237.expected0000664000175000017500000000100715246725757020621 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 151, DATE = 20240101, TIME = 0000, STEP = 0/3/6/9/12/15/18/21/24/27/30/33/36/39/42/45/48/51/54/57/60/63/66/69/72/75/78/81/84/87/90/93/96/99/102/105/108/111/114/117/120, DOMAIN = G, TARGET = reference.Hx2aUM.data, RESOL = AUTO, AREA = 90/-180/-90/180, GRID = .25/.25, PADDING = 0 metkit-1.20.2/tests/expand/MARSC-252.expected0000664000175000017500000000043515246725757020622 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = AN, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 164, DATE = 20250416, TIME = 0000, STEP = 0, DOMAIN = G, TARGET = data.reference metkit-1.20.2/tests/expand/MARSC-225.expected0000664000175000017500000000137415246725757020625 0ustar alastairalastairRETRIEVE, CLASS = TI, TYPE = PF, STREAM = ENFO, EXPVER = prod, REPRES = SH, LEVTYPE = SFC, PARAM = 167, DATE = 20231230, TIME = 0000, STEP = 0/6/12/18/24/30/36/42/48/54/60/66/72/78/84/90/96/102/108/114/120/126/132/138/144/150/156/162/168/174/180/186/192/198/204/210/216/222/228/234/240/246/252/258/264/270/276/282/288/294/300/306/312/318/324/330/336/342/348/354/360, NUMBER = 1, ORIGIN = ECMF, TARGET = reference.8nWO8K.data, RESOL = AUTO, GRID = .2/.2, PADDING = 0, PROCESS = LOCALmetkit-1.20.2/tests/expand/MARSC-237.re0000664000175000017500000000030715246725757017430 0ustar alastairalastairretrieve, area=90/-180/-90/180,class=od,date=2024-01-01,expver=1,grid=0.25/0.25,levtype=sfc,padding=0,param=mslp,step=0/to/120/by/3,stream=oper,time=00:00,type=fc, target="reference.Hx2aUM.data" metkit-1.20.2/tests/expand/MARSC-261.re0000664000175000017500000000045615246725757017432 0ustar alastairalastair retrieve, class=od, type=pf, stream=ef, expver=0001, repres=sh, levtype=sfc, param=167/165/166/164/228, date=20250416, time=0000, step=6/12, number=1/2, domain=g, resol=n128, area=70.5/-21.0/30.0/40.5, grid=1.5/1.5, target=data.referencemetkit-1.20.2/tests/expand/MARSC-247.req0000664000175000017500000000012615246725757017611 0ustar alastairalastair retrieve, class=rd, date=20250413, expver=aaaa, target=data.referencemetkit-1.20.2/tests/expand/MARSC-245.req0000664000175000017500000000032215246725757017605 0ustar alastairalastairretrieve, DOMAIN=EUROATL,LEVTYPE=110,LEVELIST=1,DATE=20040102,TIME=1800,STEP=6,PARAM=33.201,CLASS=MS,TYPE=FG,STREAM=OPER,EXPVER=0001,COUNTRY=IT,MODEL=HRM,BCMODEL=IFS,ICMODEL=MVOI, target=reference.data metkit-1.20.2/tests/expand/MARSC-254.expected0000664000175000017500000000043715246725757020626 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = OB, STREAM = OPER, EXPVER = 0001, REPRES = BU, OBSTYPE = 1, DATE = 20250414, TIME = 0000, RANGE = 1439, DOMAIN = G, TARGET = data.reference, DUPLICATES = KEEP metkit-1.20.2/tests/expand/MARSC-267.req0000664000175000017500000000036615246725757017621 0ustar alastairalastairretrieve,area=40/-180/-40/180,class=s2,date=2024-01-11,expver=prod,levtype=pl,padding=0,param=130,step=0/to/1104/by/24,stream=enfh,time=00:00:00,type=pf,levelist=200/300/500/700/850/925/1000,number=1,origin=ecmf,hdate=2004-01-11,target=target.outmetkit-1.20.2/tests/expand/MARSC-224.req0000664000175000017500000000027715246725757017613 0ustar alastairalastairretrieve, class=ti,date=20231226/to/20231226,expver=prod,grid=0.5/0.5,levtype=sfc,padding=0,param=151,step=0/to/240/by/6,time=12,type=fc,number=all,origin=ecmf, target="reference.OkV8i4.data"metkit-1.20.2/tests/expand/MARSC-232.expected0000664000175000017500000000067115246725757020622 0ustar alastairalastairRETRIEVE, DATASET = mars, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 144, DATE = 20231003, TIME = 0000/1200, STEP = 0/1/2/3/4/5/6/7/8/9/10/11/12, DOMAIN = G, TARGET = reference.data, RESOL = AUTO, AREA = 85/-180/-60/180, GRID = .1/.1, PADDING = 0 metkit-1.20.2/tests/expand/MARSC-224.expected0000664000175000017500000000126715246725757020625 0ustar alastairalastair RETRIEVE, CLASS = TI, TYPE = FC, STREAM = OPER, EXPVER = prod, REPRES = SH, LEVTYPE = SFC, PARAM = 151, DATE = 20231226, TIME = 1200, STEP = 0/6/12/18/24/30/36/42/48/54/60/66/72/78/84/90/96/102/108/114/120/126/132/138/144/150/156/162/168/174/180/186/192/198/204/210/216/222/228/234/240, NUMBER = ALL, ORIGIN = ECMF, TARGET = reference.OkV8i4.data, RESOL = AUTO, GRID = .5/.5, PADDING = 0, PROCESS = LOCAL metkit-1.20.2/tests/expand/MARSC-223.expected0000664000175000017500000000101015246725757020606 0ustar alastairalastair RETRIEVE, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 33, DATE = 20231221, TIME = 0000, STEP = 0, DOMAIN = G, TARGET = reference.zcbyW7.data, RESOL = AUTO, GRID = .125/.125, PADDING = 0, USE = INFREQUENT metkit-1.20.2/tests/expand/MARSC-223.req0000664000175000017500000000026615246725757017610 0ustar alastairalastairretrieve, date=2023-12-21,expver=1,grid=0.125/0.125,levtype=sfc,padding=0,param=33.128,step=0,stream=oper,time=00:00:00,type=fc,use=infrequent, target="reference.zcbyW7.data"metkit-1.20.2/tests/expand/MARSC-244.req0000664000175000017500000000026215246725757017607 0ustar alastairalastairretrieve, DATE=20130225,TIME=0000,ORIGIN=SRNWPPEPS-DWD-EUA,STEP=3,LEVTYPE=SFC,NUMBER=1,PARAM=166,EXPVER=test,CLASS=TI,MODEL=LAM,TYPE=PF,STREAM=ENFO, target=reference.datametkit-1.20.2/tests/expand/MARSC-234.expected0000664000175000017500000000057315246725757020625 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = FC, STREAM = OPER, EXPVER = 0001, REPRES = GG, LEVTYPE = SFC, PARAM = 228022, DATE = 20231220, TIME = 1200, STEP = 0, DOMAIN = G, TARGET = reference.A0FgVE.data, RESOL = AUTO, AREA = 73.5/-27/33/45, GRID = .125/.125 metkit-1.20.2/tests/expand/MARSC-240.expected0000664000175000017500000000072215246725757020616 0ustar alastairalastairRETRIEVE, CLASS = OD, TYPE = OF, STREAM = OCEA, EXPVER = 0001, REPRES = SH, LEVTYPE = DP, LEVELIST = 0, PARAM = 151145, DATE = 20070601, TIME = 0000, RANGE = 264, STEP = 288, NUMBER = 0, DOMAIN = G, SYSTEM = 2, METHOD = 1, PRODUCT = TIMS, SECTION = Z, LATITUDE = -9.967, TARGET = reference.0DY4f2.data metkit-1.20.2/tests/expand/MARSC-253.req0000664000175000017500000000031215246725757017603 0ustar alastairalastairretrieve, class=od, type=tf, stream=enfo, expver=0001, repres=bu, obstype=32, date=20060112, time=0000/1200, domain=g, duplicates=keep, target=data.reference metkit-1.20.2/tests/test_mars_language.cc0000664000175000017500000000357215246725757020625 0ustar alastairalastair #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" namespace metkit::mars::test { CASE("retrieve_best_match_param_matching") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching auto match = language.bestMatch("parameter", {"parameter"}, false, false, false, {}); EXPECT(match == "parameter"); }; CASE("retrieve_best_match_param_not_matching") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching auto match = language.bestMatch("param", {"parameter"}, false, false, false, {}); EXPECT(match == "parameter"); auto empty = language.bestMatch("param", {"car"}, false, false, false, {}); EXPECT(empty == ""); }; CASE("retrieve_best_match_param_not_matching_throw") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching auto match = language.bestMatch("param", {"parameter"}, true, false, false, {}); EXPECT(match == "parameter"); EXPECT_THROWS(language.bestMatch("param", {"car"}, true, false, false, {})); }; CASE("retrieve_best_match_param_not_matching") { const auto language = MarsLanguage("retrieve"); // Strict is defaulted to true and this is not matching auto match = language.bestMatch("param", {"parameter"}, false, false, true, {}); EXPECT(match == "parameter"); match = language.bestMatch("par", {"parameter"}, false, false, true, {}); EXPECT(match == "parameter"); match = language.bestMatch("par", {"car"}, false, false, true, {}); EXPECT(match == ""); }; } // namespace metkit::mars::test int main(int argc, char** argv) { putenv("METKIT_LANGUAGE_STRICT_MODE=0"); auto res = eckit::testing::run_tests(argc, argv); unsetenv("METKIT_LANGUAGE_STRICT_MODE"); return res; } metkit-1.20.2/tests/test_hypercube.cc0000664000175000017500000001611415246725757020002 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_MetFile.cc /// @date Jan 2016 /// @author Florian Rathgeber #include #include "eckit/testing/Test.h" #include "metkit/hypercube/HyperCube.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars::test { //----------------------------------------------------------------------------- CASE("test_metkit_hypercube") { const char* text = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500,param=138"; MarsRequest r = MarsRequest::parse(text); metkit::hypercube::HyperCube cube(r); EXPECT(cube.contains(r)); EXPECT(cube.size() == 1); EXPECT(cube.vacantRequests().size() == 1); EXPECT(!(r < *cube.vacantRequests().begin())); EXPECT(!(*cube.vacantRequests().begin() < r)); } CASE("test_metkit_hypercube_subset") { const char* text = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500/600,param=138"; MarsRequest r = MarsRequest::parse(text); metkit::hypercube::HyperCube cube(r); EXPECT(cube.size() == 2); EXPECT(cube.countVacant() == 2); EXPECT(cube.vacantRequests().size() == 1); EXPECT(!(r < *cube.vacantRequests().begin())); EXPECT(!(*cube.vacantRequests().begin() < r)); const char* text500 = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500,param=138"; MarsRequest r500 = MarsRequest::parse(text500); const char* text600 = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "600,param=138"; MarsRequest r600 = MarsRequest::parse(text600); EXPECT_THROWS(cube.contains(r)); EXPECT(cube.contains(r500)); EXPECT(cube.contains(r600)); cube.clear(r500); EXPECT(!cube.contains(r500)); EXPECT(cube.size() == 2); EXPECT(cube.countVacant() == 1); EXPECT(cube.vacantRequests().size() == 1); EXPECT(!(r600 < *cube.vacantRequests().begin())); EXPECT(!(*cube.vacantRequests().begin() < r600)); cube.clear(r600); EXPECT(cube.countVacant() == 0); } CASE("test_metkit_hypercube_request") { const char* text = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500/600,param=138/155"; MarsRequest r = MarsRequest::parse(text); metkit::hypercube::HyperCube cube(r); EXPECT(cube.size() == 4); EXPECT(cube.countVacant() == 4); EXPECT(cube.vacantRequests().size() == 1); EXPECT(!(r < *cube.vacantRequests().begin())); EXPECT(!(*cube.vacantRequests().begin() < r)); const char* text500 = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500,param=138"; MarsRequest r500 = MarsRequest::parse(text500); const char* text600 = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "600,param=138"; MarsRequest r600 = MarsRequest::parse(text600); EXPECT_THROWS(cube.contains(r)); EXPECT(cube.contains(r500)); EXPECT(cube.contains(r600)); cube.clear(r500); EXPECT(!cube.contains(r500)); EXPECT(cube.size() == 4); EXPECT(cube.countVacant() == 3); EXPECT(cube.vacantRequests().size() == 2); cube.clear(r600); EXPECT(cube.countVacant() == 2); EXPECT(cube.vacantRequests().size() == 1); const char* text155 = "retrieve,class=rd,type=an,stream=oper,levtype=pl,date=20191110,time=0000,step=0,expver=xxxy,domain=g,levelist=" "500/600,param=155"; MarsRequest r155 = MarsRequest::parse(text155); EXPECT(!(r155 < *cube.vacantRequests().begin())); EXPECT(!(*cube.vacantRequests().begin() < r155)); } CASE("test_metkit_hypercube_request METK-132") { std::vector keys = {"levelist", "param"}; MarsRequest r = MarsRequest::parse("retrieve,levelist=2/4/1/3/5,param=228038/235094/235077/235078"); metkit::hypercube::HyperCube cube{r}; // unset levelist 5 for params 235077/235094 cube.clear(MarsRequest::parse("retrieve,levelist=5,param=235077")); cube.clear(MarsRequest::parse("retrieve,levelist=5,param=235094")); std::vector expected_flat_requests = { MarsRequest::parse("retrieve,levelist=1,param=228038"), MarsRequest::parse("retrieve,levelist=2,param=228038"), MarsRequest::parse("retrieve,levelist=3,param=228038"), MarsRequest::parse("retrieve,levelist=4,param=228038"), MarsRequest::parse("retrieve,levelist=5,param=228038"), MarsRequest::parse("retrieve,levelist=1,param=235077"), MarsRequest::parse("retrieve,levelist=2,param=235077"), MarsRequest::parse("retrieve,levelist=3,param=235077"), MarsRequest::parse("retrieve,levelist=4,param=235077"), MarsRequest::parse("retrieve,levelist=1,param=235094"), MarsRequest::parse("retrieve,levelist=2,param=235094"), MarsRequest::parse("retrieve,levelist=3,param=235094"), MarsRequest::parse("retrieve,levelist=4,param=235094"), MarsRequest::parse("retrieve,levelist=1,param=235078"), MarsRequest::parse("retrieve,levelist=2,param=235078"), MarsRequest::parse("retrieve,levelist=3,param=235078"), MarsRequest::parse("retrieve,levelist=4,param=235078"), MarsRequest::parse("retrieve,levelist=5,param=235078"), // These two requests we have removed from the cube: // MarsRequest::parse("retrieve,levelist=5,param=235077"), // MarsRequest::parse("retrieve,levelist=5,param=235094"), }; auto dense_requests = cube.vacantRequests(); EXPECT_EQUAL(dense_requests.size(), 2); std::vector flattened_requests; for (const auto& req : cube.vacantRequests()) { // surely this should be requests, not vacantRequests... anyway... auto flattened = req.split(keys); for (const auto& f : flattened) { flattened_requests.push_back(f); } } EXPECT_EQUAL(flattened_requests.size(), 18); EXPECT_EQUAL(flattened_requests.size(), expected_flat_requests.size()); // compare both vectors std::sort(flattened_requests.begin(), flattened_requests.end()); std::sort(expected_flat_requests.begin(), expected_flat_requests.end()); for (size_t i = 0; i < expected_flat_requests.size(); ++i) { EXPECT_EQUAL(flattened_requests[i].values("levelist"), expected_flat_requests[i].values("levelist")); EXPECT_EQUAL(flattened_requests[i].values("param"), expected_flat_requests[i].values("param")); } } } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_steprange_axis.cc0000664000175000017500000001160115246725757021024 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_param_axis.cc /// @author Baudouin Raoult /// @date Mai 2019 #include #include #include #include #include "eckit/testing/Test.h" #include "eckit/types/FloatCompare.h" #include "metkit/mars/StepRange.h" #include "metkit/mars/StepRangeNormalise.h" namespace eckit { template <> struct VectorPrintSelector { using selector = VectorPrintSimple; }; } // namespace eckit namespace metkit::mars::test { //----------------------------------------------------------------------------- CASE("steprange") { { StepRange sr{0, 24}; EXPECT_EQUAL(sr.from(), 0); EXPECT_EQUAL(sr.to(), 24); } { StepRange sr{"0-24"}; EXPECT_EQUAL(sr.from(), 0); EXPECT_EQUAL(sr.to(), 24); } { StepRange sr{0, .5}; EXPECT_EQUAL(sr.from(), 0); EXPECT(eckit::types::is_approximately_equal(sr.to(), 0.5)); } { StepRange sr{"0-30m"}; EXPECT_EQUAL(sr.from(), 0); EXPECT(eckit::types::is_approximately_equal(sr.to(), 0.5)); } { StepRange sr{"0-24s"}; EXPECT_EQUAL(sr.from(), 0); EXPECT(eckit::types::is_approximately_equal(sr.to(), 24. / 3600.)); } { StepRange sr{"40m-260m"}; EXPECT(eckit::types::is_approximately_equal(sr.from(), 2. / 3.)); EXPECT(eckit::types::is_approximately_equal(sr.to(), 4 + 1. / 3.)); } { StepRange sr{"20m"}; EXPECT_EQUAL(sr.toString(), "20m"); EXPECT_EQUAL(sr.toString(true), "20m"); } { StepRange sr{"60m"}; EXPECT_EQUAL(sr.toString(), "1"); EXPECT_EQUAL(sr.toString(true, false), "1"); EXPECT_EQUAL(sr.toString(true, true), "1h"); } { StepRange sr{"1d"}; EXPECT_EQUAL(sr.toString(), "24"); EXPECT_EQUAL(sr.toString(true, false), "1d"); EXPECT_EQUAL(sr.toString(true, true), "1d"); } { StepRange sr{"25"}; EXPECT_EQUAL(sr.toString(), "25"); EXPECT_EQUAL(sr.toString(true, false), "1d1h"); EXPECT_EQUAL(sr.toString(true, true), "1d1h"); } } static void test_steprange_axis(const std::vector& user, const std::vector& axis, const std::vector& expect) { std::vector values(user.begin(), user.end()); std::vector result(expect.begin(), expect.end()); std::vector index(axis.begin(), axis.end()); std::sort(index.begin(), index.end()); std::cout << "User:" << values << std::endl; std::cout << "Axis:" << index << std::endl; StepRangeNormalise::normalise(values, index); std::cout << "Result:" << values << std::endl; EXPECT(values == result); } CASE("trivial") { std::vector user = {"1", "2", "3"}; std::vector axis = {"1", "2", "3"}; std::vector expect = {"1", "2", "3"}; test_steprange_axis(user, axis, expect); } CASE("subselection") { std::vector user = {"2", "3"}; std::vector axis = {"1", "2", "3"}; std::vector expect = {"2", "3"}; test_steprange_axis(user, axis, expect); } CASE("missing values") { std::vector user = {"1", "2", "3"}; std::vector axis = {"1", "3"}; std::vector expect = {"1", "3"}; test_steprange_axis(user, axis, expect); } CASE("ranges") { std::vector user = {"0-24", "24-48", "3-9"}; std::vector axis = {"0-24", "6-30", "12-36", "18-42", "24-48"}; std::vector expect = {"0-24", "24-48"}; test_steprange_axis(user, axis, expect); } CASE("default start-point") { std::vector user = {"1", "2", "24", "25"}; std::vector axis = {"1", "0-1", "3", "0-3", "0-24", "25"}; std::vector expect = {"1", "0-1", "0-24", "25"}; test_steprange_axis(user, axis, expect); } CASE("match range start") { // SDS: I'm not really sure why this is supported, but the original // MARS code did it... std::vector user = {"2-24"}; std::vector axis = {"1", "2", "3"}; std::vector expect = {"2"}; test_steprange_axis(user, axis, expect); } } // namespace metkit::mars::test //----------------------------------------------------------------------------- int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_obstype.cc0000664000175000017500000000533315246725757017502 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_obstype.cc /// @author Emanuele Danovaro /// @date July 2025 #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Type.h" namespace metkit::mars::test { using ::eckit::UserError; //----------------------------------------------------------------------------- void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); language.type(name)->expand(values); EXPECT_EQUAL(expected, values); } CASE("Test Obstype expansions") { // times with units assertTypeExpansion("obstype", {"1"}, {"1"}); assertTypeExpansion("obstype", {"ssmi"}, {"126"}); assertTypeExpansion("obstype", {"trmm"}, {"129", "130"}); assertTypeExpansion("obstype", {"ti3r", "trmm"}, {"130", "129"}); assertTypeExpansion("obstype", {"130", "trmm"}, {"130", "129"}); assertTypeExpansion("obstype", {"trmm", "qscat"}, {"129", "130", "137", "138"}); { std::vector values{"sd"}; static MarsLanguage language("retrieve"); language.type("obstype")->expand(values); std::vector expected{"121", "122", "123", "124", "210", "212", "213", "214", "216", "217", "218", "51", "53", "54", "55", "56", "57", "59", "60", "61", "62", "63", "65", "71", "72", "73", "75", "138", "139", "153", "155", "211", "240", "250", "126", "49", "127", "129", "130", "137", "206", "207", "208", "209", "156", "154", "201", "202", "252", "245", "246"}; EXPECT(values.size() >= expected.size()); std::set valuesSet(values.begin(), values.end()); EXPECT_EQUAL(values.size(), valuesSet.size()); for (const auto& v : expected) { EXPECT(valuesSet.find(v) != valuesSet.end()); } } EXPECT_THROWS_AS(assertTypeExpansion("obstype", {"foo"}, {""}), UserError); } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_request.cc0000664000175000017500000002171015246725757017502 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_request.cc /// @date Jul 2024 /// @author Emanuele Danovaro #include "eckit/log/JSON.h" #include "eckit/types/Date.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsParser.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" #include "eckit/testing/Test.h" #include "eckit/utils/Tokenizer.h" using namespace eckit::testing; namespace metkit { namespace mars { namespace test { //----------------------------------------------------------------------------- CASE("test_request_json") { { const char* text = "retrieve,class=od,expver=0079,stream=enfh,date=20240729,time=00/" "12,type=fcmean,levtype=sfc,step=24,number=1/to/2,param=mucin/mucape/tprate"; MarsRequest r = MarsRequest::parse(text); { std::stringstream ss; eckit::JSON plain(ss); r.json(plain); EXPECT_EQUAL( ss.str(), "{\"class\":\"od\",\"type\":\"fcmean\",\"stream\":\"enfh\",\"levtype\":\"sfc\",\"date\":\"20240729\"," "\"time\":[\"0000\",\"1200\"],\"step\":\"24\",\"expver\":\"0079\",\"number\":[\"1\",\"2\"],\"param\":[" "\"228236\",\"228235\",\"172228\"],\"domain\":\"g\"}"); } { std::stringstream ss; eckit::JSON array(ss); r.json(array, true); EXPECT_EQUAL( ss.str(), "{\"class\":\"od\",\"type\":\"fcmean\",\"stream\":\"enfh\",\"levtype\":\"sfc\",\"date\":[\"20240729\"]," "\"time\":[\"0000\",\"1200\"],\"step\":[\"24\"],\"expver\":\"0079\",\"number\":[\"1\",\"2\"],\"param\":" "[\"228236\",\"228235\",\"172228\"],\"domain\":\"g\"}"); } } { const char* text = "retrieve,class=od,expver=1,stream=wave,date=20240729,time=00,type=an,levtype=sfc,step=24,param=2dfd "; MarsRequest r = MarsRequest::parse(text); { std::stringstream ss; eckit::JSON plain(ss); r.json(plain); EXPECT_EQUAL(ss.str(), "{\"class\":\"od\",\"type\":\"an\",\"stream\":\"wave\",\"levtype\":\"sfc\",\"date\":" "\"20240729\",\"time\":\"0000\",\"step\":\"24\",\"expver\":\"0001\",\"param\":\"140251\"," "\"domain\":\"g\"}"); } { std::stringstream ss; eckit::JSON array(ss); r.json(array, true); EXPECT_EQUAL(ss.str(), "{\"class\":\"od\",\"type\":\"an\",\"stream\":\"wave\",\"levtype\":\"sfc\",\"date\":[" "\"20240729\"],\"time\":[\"0000\"],\"step\":[\"24\"],\"expver\":\"0001\",\"param\":[" "\"140251\"],\"domain\":\"g\"}"); } } } CASE("test_request_count") { { const char* text = "retrieve,class=od,expver=0079,stream=enfh,date=20240729,time=00/" "12,type=fcmean,levtype=sfc,step=24,number=1/to/2,param=mucin/mucape/tprate"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(12, r.count()); } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=1/to/" "137,levtype=ml,number=-1,param=z,process=local,step=000,stream=scda,time=18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(1, r.count()); } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=1/to/137,levtype=ml,number=-1,param=z/" "t,process=local,step=000,stream=scda,time=18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(138, r.count()); } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=1/to/137,levtype=ml,number=-1,param=22/127/" "128/129/152/u/v,process=local,step=000,stream=scda,time=18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(279, r.count()); } } CASE("test_request_count_single_level_params") { // Test that single-level parameters (like 'z') are only counted when level "1" is in levelist { // levelist includes "1" - single-level param 'z' should be counted const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=1/to/" "137,levtype=ml,param=z,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(1, r.count()); // Only level 1 for param z } { // levelist does NOT include "1" - single-level param 'z' should NOT be counted const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=2/to/" "137,levtype=ml,param=z,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(0, r.count()); // No levels counted for param z when level 1 is absent } { // levelist includes "1" - mix of single-level (z) and multi-level (t) params const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=1/to/137,levtype=ml,param=z/" "t,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(138, r.count()); // 137 levels for t + 1 for z } { // levelist does NOT include "1" - only multi-level param 't' should be counted const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=2/to/137,levtype=ml,param=z/" "t,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(136, r.count()); // 136 levels (2-137) for t, 0 for z } { // levelist includes "1" in the middle of range - single-level params should be counted const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=1/50/100,levtype=ml,param=z/" "t,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(4, r.count()); // 3 levels for t + 1 for z } { // levelist with only level "1" - both param types should be counted const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=1,levtype=ml,param=z/" "t,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(2, r.count()); // 1 level for t + 1 for z } { // Multiple single-level params with level "1" present const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=1/to/10,levtype=ml,param=152/" "z,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(2, r.count()); // 1 for z + 1 for 152 (both single-level params) } { // Multiple single-level params without level "1" const char* text = "retrieve,class=od,date=20230810,expver=1,levelist=2/to/10,levtype=ml,param=152/" "z,step=000,stream=scda,time=18,type=an"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(0, r.count()); // 0 for both single-level params when level 1 is absent } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=1/to/137,levtype=ml,number=-1,param=22/127/" "128/129/152/u/v,process=local,step=000,stream=scda,time=6/18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(558, r.count()); } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=3/to/137,levtype=ml,number=-1,param=22/127/" "128/129/152/u/v,process=local,step=000,stream=scda,time=6/18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(540, r.count()); } { const char* text = "retrieve,accuracy=16,class=od,date=20230810,expver=1,levelist=1/2,levtype=ml,number=-1,param=22/127/" "128/129/152/u/v,process=local,step=0/1/2,stream=scda,time=6/18,type=an,target=reference.data"; MarsRequest r = MarsRequest::parse(text); EXPECT_EQUAL(54, r.count()); } } //----------------------------------------------------------------------------- } // namespace test } // namespace mars } // namespace metkit int main(int argc, char** argv) { return run_tests(argc, argv); } metkit-1.20.2/tests/test_context.cc0000664000175000017500000000272315246725757017501 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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 test_step.cc /// @author Emanuele Danovaro /// @date March 2025 #include #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Type.h" namespace metkit::mars::test { using ::eckit::BadValue; //----------------------------------------------------------------------------- CASE("Context match") { Context c; std::set cc{"s2", "ti"}; c.add(std::make_unique("class", cc)); std::set tt{"cf"}; c.add(std::make_unique("type", tt)); std::string text = "retrieve, " "class=ti,date=20250414,time=12,origin=all,expver=all,type=cf,stream=enfo,levtype=sfc,param=2t,step=24,expect=" "any,target=data.reference"; metkit::mars::MarsRequest r = MarsRequest::parse(text, true); EXPECT(c.matches(r)); } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_codes_decoder.cc0000664000175000017500000006571415246725757020610 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @date Nov 2022 /// @author Philipp Geier #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/io/FileHandle.h" #include "eckit/io/MemoryHandle.h" #include "eckit/message/Message.h" #include "eckit/message/Reader.h" #include "eckit/testing/Test.h" namespace metkit::codes::test { //---------------------------------------------------------------------------------------------------------------------- class MetadataSetter : public eckit::LocalConfiguration { public: using eckit::LocalConfiguration::getDouble; using eckit::LocalConfiguration::getLong; using eckit::LocalConfiguration::getString; using eckit::LocalConfiguration::has; template void setValue(const std::string& key, const T& value) { set(key, value); } template T get(const std::string& key) { T value; eckit::LocalConfiguration::get(key, value); return value; } std::vector keys() { return eckit::LocalConfiguration::keys(); } }; static unsigned char unstr_latlon[] = { 0x47, 0x52, 0x49, 0x42, 0xff, 0xff, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00, 0x00, 0x15, 0x01, 0x00, 0x62, 0x00, 0xff, 0x19, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x04, 0x01, 0x30, 0x30, 0x30, 0x31, 0x00, 0x00, 0x00, 0x23, 0x03, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x65, 0x06, 0x00, 0x00, 0x0a, 0x01, 0x66, 0xa3, 0x41, 0xd2, 0x1d, 0xcf, 0x11, 0xb2, 0x88, 0x0c, 0x0f, 0x16, 0x45, 0xf3, 0xd1, 0xdc, 0x00, 0x00, 0x00, 0x22, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x15, 0x05, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0xff, 0x00, 0x00, 0x00, 0x05, 0x07, 0x37, 0x37, 0x37, 0x37}; #define MD_EXPECT_STRING(md, name, eq) \ EXPECT(md.has(name)); \ std::cout << "expect string for " << name << " to equal " << eq << " (got " << md.getString(name) << ")" \ << std::endl; \ EXPECT_EQUAL(eq, md.getString(name)); // we accept two possible encodings, to enable testing with different versions of ecCodes (ECC-1704) #define MD_EXPECT_STRINGS(md, name, eq1, eq2) \ EXPECT(md.has(name)); \ std::cout << "expect string for " << name << " to equal " << eq1 << " or " << eq2 << " (got " \ << md.getString(name) << ")" << std::endl; \ EXPECT(md.getString(name) == eq1 || md.getString(name) == eq2); #define MD_EXPECT_LONG(md, name, eq) \ EXPECT(md.has(name)); \ std::cout << "expect long for " << name << " to equal " << eq << " (got " << md.getLong(name) << ")" << std::endl; \ EXPECT_EQUAL(eq, md.getLong(name)); #define MD_EXPECT_LONGS(md, name, eq1, eq2) \ EXPECT(md.has(name)); \ std::cout << "expect long for " << name << " to equal " << eq1 << " or " << eq2 << " (got " << md.getLong(name) \ << ")" << std::endl; \ EXPECT(md.getLong(name) == eq1 || md.getLong(name) == eq2); #define MD_EXPECT_GE_LONG(md, name, eq) \ EXPECT(md.has(name)); \ std::cout << "expect long for " << name << " to be greater than or equal to " << eq << " (got " \ << md.getLong(name) << ")" << std::endl; \ EXPECT(md.getLong(name) >= eq); #define MD_EXPECT_DOUBLE(md, name, eq) \ EXPECT(md.has(name)); \ std::cout << "expect double for " << name << " to equal " << std::to_string(eq) << " (got " \ << std::to_string(md.getDouble(name)) << ")" << std::endl; \ EXPECT_EQUAL(std::to_string(eq), std::to_string(md.getDouble(name))); //---------------------------------------------------------------------------------------------------------------------- CASE("test codessplitter unstr_latlot.tmpl Native") { eckit::MemoryHandle data(static_cast(unstr_latlon), sizeof(unstr_latlon)); std::cout << "Data location " << ((size_t)&unstr_latlon) << std::endl; eckit::message::Reader reader(data); eckit::message::Message msg; msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::Native; mdOpts.nameSpace = ""; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_STRING(md, "globalDomain", "g"); MD_EXPECT_LONG(md, "GRIBEditionNumber", 2); MD_EXPECT_GE_LONG(md, "tablesVersionLatestOfficial", 30); MD_EXPECT_GE_LONG(md, "tablesVersionLatest", 30); MD_EXPECT_LONG(md, "grib2divider", 1000000); MD_EXPECT_LONG(md, "angleSubdivisions", 1000000); MD_EXPECT_LONG(md, "missingValue", 9999); MD_EXPECT_LONG(md, "ieeeFloats", 1); MD_EXPECT_LONG(md, "isHindcast", 0); MD_EXPECT_LONG(md, "section0Length", 16); MD_EXPECT_STRING(md, "identifier", "GRIB"); MD_EXPECT_LONG(md, "discipline", 2); MD_EXPECT_LONG(md, "editionNumber", 2); MD_EXPECT_LONG(md, "totalLength", 159); MD_EXPECT_LONG(md, "section1Length", 21); MD_EXPECT_STRING(md, "centre", "ecmf"); MD_EXPECT_STRING(md, "centreDescription", "European Centre for Medium-Range Weather Forecasts"); MD_EXPECT_LONG(md, "subCentre", 255); MD_EXPECT_LONG(md, "tablesVersion", 25); MD_EXPECT_STRING(md, "masterDir", "grib2/tables/[tablesVersion]"); MD_EXPECT_LONG(md, "localTablesVersion", 0); MD_EXPECT_LONG(md, "significanceOfReferenceTime", 0); MD_EXPECT_LONG(md, "year", 1); MD_EXPECT_LONG(md, "month", 1); MD_EXPECT_LONG(md, "day", 1); MD_EXPECT_LONG(md, "hour", 0); MD_EXPECT_LONG(md, "minute", 0); MD_EXPECT_LONG(md, "second", 0); MD_EXPECT_LONG(md, "dataDate", 10101); MD_EXPECT_DOUBLE(md, "julianDay", 1.7214235e+06); MD_EXPECT_LONG(md, "dataTime", 0); MD_EXPECT_LONG(md, "productionStatusOfProcessedData", 0); MD_EXPECT_STRING(md, "typeOfProcessedData", "an"); MD_EXPECT_LONG(md, "selectStepTemplateInterval", 1); MD_EXPECT_LONG(md, "selectStepTemplateInstant", 1); MD_EXPECT_STRING(md, "stepType", "instant"); MD_EXPECT_LONG(md, "is_chemical", 0); MD_EXPECT_LONG(md, "is_chemical_distfn", 0); MD_EXPECT_LONG(md, "is_chemical_srcsink", 0); MD_EXPECT_LONG(md, "is_aerosol", 0); MD_EXPECT_LONG(md, "is_aerosol_optical", 0); MD_EXPECT_LONG(md, "setCalendarId", 0); MD_EXPECT_LONG(md, "deleteCalendarId", 0); MD_EXPECT_LONG(md, "grib2LocalSectionPresent", 0); MD_EXPECT_LONG(md, "deleteLocalDefinition", 0); MD_EXPECT_LONG(md, "section2Length", 17); MD_EXPECT_LONG(md, "addEmptySection2", 0); MD_EXPECT_LONG(md, "grib2LocalSectionNumber", 1); MD_EXPECT_STRING(md, "marsClass", "od"); MD_EXPECT_STRING(md, "marsType", "an"); MD_EXPECT_STRING(md, "marsStream", "oper"); MD_EXPECT_STRING(md, "experimentVersionNumber", "0001"); MD_EXPECT_STRING(md, "class", "od"); MD_EXPECT_STRING(md, "type", "an"); MD_EXPECT_STRING(md, "stream", "oper"); MD_EXPECT_LONG(md, "productDefinitionTemplateNumberInternal", -1); MD_EXPECT_LONG(md, "localDefinitionNumber", 1); MD_EXPECT_LONG(md, "eps", 0); MD_EXPECT_LONG(md, "addExtraLocalSection", 0); MD_EXPECT_LONG(md, "deleteExtraLocalSection", 0); MD_EXPECT_LONG(md, "extraLocalSectionPresent", 0); MD_EXPECT_LONG(md, "gridDescriptionSectionPresent", 1); MD_EXPECT_LONG(md, "section3Length", 35); MD_EXPECT_LONG(md, "sourceOfGridDefinition", 0); MD_EXPECT_LONG(md, "numberOfDataPoints", 496); MD_EXPECT_LONG(md, "numberOfOctectsForNumberOfPoints", 0); MD_EXPECT_LONG(md, "interpretationOfNumberOfPoints", 0); MD_EXPECT_LONG(md, "PLPresent", 0); MD_EXPECT_LONG(md, "gridDefinitionTemplateNumber", 101); MD_EXPECT_STRING(md, "gridDefinitionDescription", "General unstructured grid"); MD_EXPECT_LONG(md, "shapeOfTheEarth", 6); MD_EXPECT_LONG(md, "numberOfGridUsed", 10); MD_EXPECT_LONG(md, "numberOfGridInReference", 1); MD_EXPECT_STRINGS(md, "unstructuredGridType", "unknown", "DART"); MD_EXPECT_STRING(md, "unstructuredGridSubtype", "T"); MD_EXPECT_STRING(md, "unstructuredGridUUID", "unknown"); MD_EXPECT_STRINGS(md, "gridName", "unknown", "DART_T"); MD_EXPECT_STRING(md, "gridType", "unstructured_grid"); MD_EXPECT_LONG(md, "section4Length", 34); MD_EXPECT_LONG(md, "NV", 0); MD_EXPECT_LONG(md, "neitherPresent", 0); MD_EXPECT_STRING(md, "datasetForLocal", "unknown"); MD_EXPECT_LONG(md, "productDefinitionTemplateNumber", 0); MD_EXPECT_LONG(md, "genVertHeightCoords", 0); MD_EXPECT_LONG(md, "parameterCategory", 0); MD_EXPECT_LONG(md, "parameterNumber", 0); MD_EXPECT_STRING(md, "parameterUnits", "Proportion"); MD_EXPECT_STRING(md, "parameterName", "Land cover (0 = sea, 1 = land)"); MD_EXPECT_LONG(md, "typeOfGeneratingProcess", 2); MD_EXPECT_LONG(md, "backgroundProcess", 0); MD_EXPECT_LONG(md, "generatingProcessIdentifier", 1); MD_EXPECT_LONG(md, "hoursAfterDataCutoff", 0); MD_EXPECT_LONG(md, "minutesAfterDataCutoff", 0); // MD_EXPECT_LONG(md, "indicatorOfUnitOfTimeRange", 1); MD_EXPECT_LONG(md, "stepUnits", 1); MD_EXPECT_LONG(md, "forecastTime", 0); MD_EXPECT_LONG(md, "startStep", 0); MD_EXPECT_LONG(md, "endStep", 0); MD_EXPECT_STRING(md, "stepRange", "0"); MD_EXPECT_LONG(md, "validityDate", 10101); MD_EXPECT_STRINGS(md, "validityTime", "0", "0000"); MD_EXPECT_STRING(md, "typeOfFirstFixedSurface", "168"); MD_EXPECT_STRING(md, "unitsOfFirstFixedSurface", "Numeric"); MD_EXPECT_STRING(md, "nameOfFirstFixedSurface", "Ocean model level"); MD_EXPECT_LONG(md, "scaleFactorOfFirstFixedSurface", 0); MD_EXPECT_LONG(md, "scaledValueOfFirstFixedSurface", 2147483647); MD_EXPECT_LONG(md, "typeOfSecondFixedSurface", 255); MD_EXPECT_STRING(md, "unitsOfSecondFixedSurface", "unknown"); MD_EXPECT_STRING(md, "nameOfSecondFixedSurface", "Missing"); MD_EXPECT_LONG(md, "scaleFactorOfSecondFixedSurface", 0); MD_EXPECT_LONG(md, "scaledValueOfSecondFixedSurface", 2147483647); MD_EXPECT_STRING(md, "pressureUnits", "hPa"); MD_EXPECT_STRING(md, "typeOfLevel", "oceanModel"); MD_EXPECT_LONG(md, "level", 0); MD_EXPECT_LONG(md, "bottomLevel", 0); MD_EXPECT_LONG(md, "topLevel", 0); MD_EXPECT_STRING(md, "tempPressureUnits", "hPa"); MD_EXPECT_STRING(md, "levtype", "o3d"); MD_EXPECT_LONG(md, "PVPresent", 0); MD_EXPECT_STRING(md, "deletePV", "1"); MD_EXPECT_LONGS(md, "lengthOfHeaders", 107, 117); // changed, as a consequence of fixes for ECC-2268 MD_EXPECT_LONG(md, "section5Length", 21); MD_EXPECT_LONG(md, "numberOfValues", 496); MD_EXPECT_LONG(md, "dataRepresentationTemplateNumber", 0); MD_EXPECT_STRING(md, "packingType", "grid_simple"); MD_EXPECT_LONG(md, "referenceValue", 0); MD_EXPECT_DOUBLE(md, "referenceValueError", 1.17549e-38); MD_EXPECT_LONG(md, "binaryScaleFactor", -15); MD_EXPECT_LONG(md, "decimalScaleFactor", 0); MD_EXPECT_LONG(md, "optimizeScaleFactor", 0); MD_EXPECT_LONG(md, "bitsPerValue", 0); MD_EXPECT_LONG(md, "typeOfOriginalFieldValues", 0); MD_EXPECT_LONG(md, "section6Length", 6); MD_EXPECT_LONG(md, "bitMapIndicator", 255); MD_EXPECT_LONG(md, "bitmapPresent", 0); MD_EXPECT_LONG(md, "section7Length", 5); MD_EXPECT_DOUBLE(md, "packingError", 1.17549e-38); MD_EXPECT_DOUBLE(md, "unpackedError", 1.17549e-38); MD_EXPECT_LONG(md, "maximum", 0); MD_EXPECT_LONG(md, "minimum", 0); MD_EXPECT_LONG(md, "average", 0); MD_EXPECT_LONG(md, "numberOfMissing", 0); MD_EXPECT_LONG(md, "standardDeviation", 0); MD_EXPECT_LONG(md, "skewness", 0); MD_EXPECT_LONG(md, "kurtosis", 0); MD_EXPECT_LONG(md, "isConstant", 1); MD_EXPECT_LONG(md, "changeDecimalPrecision", 0); MD_EXPECT_LONG(md, "decimalPrecision", 0); MD_EXPECT_LONG(md, "setBitsPerValue", 0); MD_EXPECT_LONG(md, "getNumberOfValues", 496); MD_EXPECT_LONG(md, "scaleValuesBy", 1); MD_EXPECT_LONG(md, "offsetValuesBy", 0); MD_EXPECT_STRING(md, "productType", "unknown"); MD_EXPECT_LONG(md, "section8Length", 4); MD_EXPECT_STRING(md, "7777", "7777"); MD_EXPECT_STRING(md, "uuidOfHGrid", "66a341d21dcf11b2880c0f1645f3d1dc"); } } //---------------------------------------------------------------------------------------------------------------------- CASE("test codessplitter unstr_latlot.tmpl String") { eckit::MemoryHandle data(static_cast(unstr_latlon), sizeof(unstr_latlon)); std::cout << "Data location " << ((size_t)&unstr_latlon) << std::endl; eckit::message::Reader reader(data); eckit::message::Message msg; msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::String; mdOpts.nameSpace = ""; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_STRING(md, "globalDomain", "g"); MD_EXPECT_STRING(md, "GRIBEditionNumber", "2"); // This is not easy to test, as the latest official version can increment... // MD_EXPECT_STRING(md, "tablesVersionLatestOfficial", "30"); // MD_EXPECT_STRING(md, "tablesVersionLatest", "30"); MD_EXPECT_STRING(md, "grib2divider", "1e+06"); MD_EXPECT_STRING(md, "angleSubdivisions", "1e+06"); MD_EXPECT_STRING(md, "missingValue", "9999"); MD_EXPECT_STRING(md, "ieeeFloats", "1"); MD_EXPECT_STRING(md, "isHindcast", "0"); MD_EXPECT_STRING(md, "section0Length", "16"); MD_EXPECT_STRING(md, "identifier", "GRIB"); MD_EXPECT_STRING(md, "discipline", "2"); MD_EXPECT_STRING(md, "editionNumber", "2"); MD_EXPECT_STRING(md, "totalLength", "159"); MD_EXPECT_STRING(md, "section1Length", "21"); MD_EXPECT_STRING(md, "centre", "ecmf"); MD_EXPECT_STRING(md, "centreDescription", "European Centre for Medium-Range Weather Forecasts"); MD_EXPECT_STRING(md, "subCentre", "255"); MD_EXPECT_STRING(md, "tablesVersion", "25"); MD_EXPECT_STRING(md, "masterDir", "grib2/tables/[tablesVersion]"); MD_EXPECT_STRING(md, "localTablesVersion", "0"); MD_EXPECT_STRING(md, "significanceOfReferenceTime", "0"); MD_EXPECT_STRING(md, "year", "1"); MD_EXPECT_STRING(md, "month", "1"); MD_EXPECT_STRING(md, "day", "1"); MD_EXPECT_STRING(md, "hour", "0"); MD_EXPECT_STRING(md, "minute", "0"); MD_EXPECT_STRING(md, "second", "0"); MD_EXPECT_STRING(md, "dataDate", "10101"); MD_EXPECT_STRING(md, "julianDay", "1.72142e+06"); MD_EXPECT_STRING(md, "dataTime", "0000"); MD_EXPECT_STRING(md, "productionStatusOfProcessedData", "0"); MD_EXPECT_STRING(md, "typeOfProcessedData", "an"); MD_EXPECT_STRING(md, "selectStepTemplateInterval", "1"); MD_EXPECT_STRING(md, "selectStepTemplateInstant", "1"); MD_EXPECT_STRING(md, "stepType", "instant"); MD_EXPECT_STRING(md, "is_chemical", "0"); MD_EXPECT_STRING(md, "is_chemical_distfn", "0"); MD_EXPECT_STRING(md, "is_chemical_srcsink", "0"); MD_EXPECT_STRING(md, "is_aerosol", "0"); MD_EXPECT_STRING(md, "is_aerosol_optical", "0"); MD_EXPECT_STRING(md, "setCalendarId", "0"); MD_EXPECT_STRING(md, "deleteCalendarId", "0"); MD_EXPECT_STRING(md, "grib2LocalSectionPresent", "0"); MD_EXPECT_STRING(md, "deleteLocalDefinition", "0"); MD_EXPECT_STRING(md, "section2Length", "17"); MD_EXPECT_STRING(md, "addEmptySection2", "0"); MD_EXPECT_STRING(md, "grib2LocalSectionNumber", "1"); MD_EXPECT_STRING(md, "marsClass", "od"); MD_EXPECT_STRING(md, "marsType", "an"); MD_EXPECT_STRING(md, "marsStream", "oper"); MD_EXPECT_STRING(md, "experimentVersionNumber", "0001"); MD_EXPECT_STRING(md, "class", "od"); MD_EXPECT_STRING(md, "type", "an"); MD_EXPECT_STRING(md, "stream", "oper"); MD_EXPECT_STRING(md, "productDefinitionTemplateNumberInternal", "-1"); MD_EXPECT_STRING(md, "localDefinitionNumber", "1"); MD_EXPECT_STRING(md, "eps", "0"); MD_EXPECT_STRING(md, "addExtraLocalSection", "0"); MD_EXPECT_STRING(md, "deleteExtraLocalSection", "0"); MD_EXPECT_STRING(md, "extraLocalSectionPresent", "0"); MD_EXPECT_STRING(md, "gridDescriptionSectionPresent", "1"); MD_EXPECT_STRING(md, "section3Length", "35"); MD_EXPECT_STRING(md, "sourceOfGridDefinition", "0"); MD_EXPECT_STRING(md, "numberOfDataPoints", "496"); MD_EXPECT_STRING(md, "numberOfOctectsForNumberOfPoints", "0"); MD_EXPECT_STRING(md, "interpretationOfNumberOfPoints", "0"); MD_EXPECT_STRING(md, "PLPresent", "0"); MD_EXPECT_STRING(md, "gridDefinitionTemplateNumber", "101"); MD_EXPECT_STRING(md, "gridDefinitionDescription", "General unstructured grid"); MD_EXPECT_STRING(md, "shapeOfTheEarth", "6"); MD_EXPECT_STRING(md, "numberOfGridUsed", "10"); MD_EXPECT_STRING(md, "numberOfGridInReference", "1"); MD_EXPECT_STRING(md, "uuidOfHGrid", "66a341d21dcf11b2880c0f1645f3d1dc"); MD_EXPECT_STRINGS(md, "unstructuredGridType", "unknown", "DART"); MD_EXPECT_STRING(md, "unstructuredGridSubtype", "T"); MD_EXPECT_STRING(md, "unstructuredGridUUID", "unknown"); MD_EXPECT_STRINGS(md, "gridName", "unknown", "DART_T"); MD_EXPECT_STRING(md, "gridType", "unstructured_grid"); MD_EXPECT_STRING(md, "section4Length", "34"); MD_EXPECT_STRING(md, "NV", "0"); MD_EXPECT_STRING(md, "neitherPresent", "0"); MD_EXPECT_STRING(md, "datasetForLocal", "unknown"); MD_EXPECT_STRING(md, "productDefinitionTemplateNumber", "0"); MD_EXPECT_STRING(md, "genVertHeightCoords", "0"); MD_EXPECT_STRING(md, "parameterCategory", "0"); MD_EXPECT_STRING(md, "parameterNumber", "0"); MD_EXPECT_STRING(md, "parameterUnits", "Proportion"); MD_EXPECT_STRING(md, "parameterName", "Land cover (0 = sea, 1 = land)"); MD_EXPECT_STRING(md, "typeOfGeneratingProcess", "2"); MD_EXPECT_STRING(md, "backgroundProcess", "0"); MD_EXPECT_STRING(md, "generatingProcessIdentifier", "1"); MD_EXPECT_STRING(md, "hoursAfterDataCutoff", "0"); MD_EXPECT_STRING(md, "minutesAfterDataCutoff", "0"); // MD_EXPECT_STRING(md, "indicatorOfUnitOfTimeRange", "h"); MD_EXPECT_STRING(md, "stepUnits", "h"); MD_EXPECT_STRING(md, "forecastTime", "0"); MD_EXPECT_STRING(md, "startStep", "0"); MD_EXPECT_STRING(md, "endStep", "0"); MD_EXPECT_STRING(md, "stepRange", "0"); MD_EXPECT_STRING(md, "validityDate", "10101"); MD_EXPECT_STRINGS(md, "validityTime", "0", "0000"); MD_EXPECT_STRING(md, "typeOfFirstFixedSurface", "168"); MD_EXPECT_STRING(md, "unitsOfFirstFixedSurface", "Numeric"); MD_EXPECT_STRING(md, "nameOfFirstFixedSurface", "Ocean model level"); MD_EXPECT_STRING(md, "scaleFactorOfFirstFixedSurface", "0"); MD_EXPECT_STRING(md, "scaledValueOfFirstFixedSurface", "MISSING"); MD_EXPECT_STRING(md, "typeOfSecondFixedSurface", "255"); MD_EXPECT_STRING(md, "unitsOfSecondFixedSurface", "unknown"); MD_EXPECT_STRING(md, "nameOfSecondFixedSurface", "Missing"); MD_EXPECT_STRING(md, "scaleFactorOfSecondFixedSurface", "0"); MD_EXPECT_STRING(md, "scaledValueOfSecondFixedSurface", "MISSING"); MD_EXPECT_STRING(md, "pressureUnits", "hPa"); MD_EXPECT_STRING(md, "typeOfLevel", "oceanModel"); MD_EXPECT_STRING(md, "level", "0"); MD_EXPECT_STRING(md, "bottomLevel", "0"); MD_EXPECT_STRING(md, "topLevel", "0"); MD_EXPECT_STRING(md, "tempPressureUnits", "hPa"); MD_EXPECT_STRING(md, "levtype", "o3d"); MD_EXPECT_STRING(md, "PVPresent", "0"); MD_EXPECT_STRING(md, "deletePV", "1"); MD_EXPECT_STRINGS(md, "lengthOfHeaders", "107", "117"); // changed, as a consequence of fixes for ECC-2268 MD_EXPECT_STRING(md, "section5Length", "21"); MD_EXPECT_STRING(md, "numberOfValues", "496"); MD_EXPECT_STRING(md, "dataRepresentationTemplateNumber", "0"); MD_EXPECT_STRING(md, "packingType", "grid_simple"); MD_EXPECT_STRING(md, "referenceValue", "0"); MD_EXPECT_STRING(md, "referenceValueError", "1.17549e-38"); MD_EXPECT_STRING(md, "binaryScaleFactor", "-15"); MD_EXPECT_STRING(md, "decimalScaleFactor", "0"); MD_EXPECT_STRING(md, "optimizeScaleFactor", "0"); MD_EXPECT_STRING(md, "bitsPerValue", "0"); MD_EXPECT_STRING(md, "typeOfOriginalFieldValues", "0"); MD_EXPECT_STRING(md, "section6Length", "6"); MD_EXPECT_STRING(md, "bitMapIndicator", "255"); MD_EXPECT_STRING(md, "bitmapPresent", "0"); MD_EXPECT_STRING(md, "section7Length", "5"); MD_EXPECT_STRING(md, "packingError", "1.17549e-38"); MD_EXPECT_STRING(md, "unpackedError", "1.17549e-38"); MD_EXPECT_STRING(md, "maximum", "0"); MD_EXPECT_STRING(md, "minimum", "0"); MD_EXPECT_STRING(md, "average", "0"); MD_EXPECT_STRING(md, "numberOfMissing", "0"); MD_EXPECT_STRING(md, "standardDeviation", "0"); MD_EXPECT_STRING(md, "skewness", "0"); MD_EXPECT_STRING(md, "kurtosis", "0"); MD_EXPECT_STRING(md, "isConstant", "1"); MD_EXPECT_STRING(md, "changeDecimalPrecision", "0"); MD_EXPECT_STRING(md, "decimalPrecision", "0"); MD_EXPECT_STRING(md, "setBitsPerValue", "0"); MD_EXPECT_STRING(md, "getNumberOfValues", "496"); MD_EXPECT_STRING(md, "scaleValuesBy", "1"); MD_EXPECT_STRING(md, "offsetValuesBy", "0"); MD_EXPECT_STRING(md, "productType", "unknown"); MD_EXPECT_STRING(md, "section8Length", "4"); MD_EXPECT_STRING(md, "7777", "7777"); } } CASE("test levelist String") { eckit::FileHandle f("pl.grib"); eckit::message::Reader reader(f); eckit::message::Message msg; std::vector expect{"0", "0.02", "0.2", "2", "20", "200"}; for (size_t i = 0; i < expect.size(); ++i) { msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::String; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_STRING(md, "levelist", expect[i]); } } msg = reader.next(); EXPECT(!msg); } CASE("test levelist Native") { eckit::FileHandle f("pl.grib"); eckit::message::Reader reader(f); eckit::message::Message msg; std::vector expect{0, 0.02, 0.2, 2, 20, 200}; for (size_t i = 0; i < expect.size(); ++i) { msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::Native; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_DOUBLE(md, "levelist", expect[i]); } } msg = reader.next(); EXPECT(!msg); } CASE("test levelist String") { eckit::FileHandle f("sol.grib"); eckit::message::Reader reader(f); eckit::message::Message msg; std::vector expect{"0.02", "0.2", "2", "20"}; for (size_t i = 0; i < expect.size(); ++i) { msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::String; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_STRING(md, "levelist", expect[i]); } } msg = reader.next(); EXPECT(!msg); } CASE("test levelist Native") { eckit::FileHandle f("sol.grib"); eckit::message::Reader reader(f); eckit::message::Message msg; std::vector expect{0.02, 0.2, 2, 20}; for (size_t i = 0; i < expect.size(); ++i) { msg = reader.next(); EXPECT(msg); MetadataSetter md; eckit::message::TypedSetter gatherer{md}; eckit::message::GetMetadataOptions mdOpts{}; mdOpts.valueRepresentation = eckit::message::ValueRepresentation::Native; msg.getMetadata(gatherer, mdOpts); { MD_EXPECT_DOUBLE(md, "levelist", expect[i]); } } msg = reader.next(); EXPECT(!msg); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/tools/0000775000175000017500000000000015246725757015603 5ustar alastairalastairmetkit-1.20.2/tests/tools/d1.grib0000664000175000017500000005661015246725757016764 0ustar alastairalastairGRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"eR* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"g R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"g R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"gR* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"gR* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($"R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($: R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($: R* 7777GRIBbT" s0000000. aad3HR(Z-Z-p^@$(-2<@HHKQZ`dlxx}   ,,@@@Dhhhhhhww@@@@@@XXXX      **```````````88888888888888eeeeeeeeeeeeeeeeeeeeeeeeeeee88888888888888```````````**      XXXX@@@@@@wwhhhhhhD@@@,,   }xxld`ZQKHH@<2-($: R* 7777metkit-1.20.2/tests/tools/grib-to-request.sh.in0000775000175000017500000000167215246725757021606 0ustar alastairalastair#!/usr/bin/env bash set -eux yell() { printf "$(basename "$0"): \033[0;31m!!! %s !!!\033[0m\\n" "$*" >&2; } die() { yell "$*"; exit 1; } line_count() { [[ $# -eq 1 ]] || die "line_count requires 1 argument; expected line count" val=$(wc -l < out) && val=$((val + 0)) [[ $val -eq $1 ]] || die "Incorrect count => [$val != $1]" } grep_count() { [[ $# -eq 2 ]] || die "grep_count requires 2; regex and expected count" val=$(grep -cE "$1" out) [[ $val -eq $2 ]] || die "Incorrect count [$val != $2] for regex [$1]" } g2r="$" srcdir=@CMAKE_CURRENT_SOURCE_DIR@ bindir=@CMAKE_CURRENT_BINARY_DIR@ export ECCODES_DEFINITION_PATH="@ECCODES_DEFINITION_PATH@" $g2r $srcdir/d1.grib | tee out line_count 16 grep_count "step=0" 13 grep_count "step=12" 3 grep_count "timespan=fs" 3 $g2r --one $srcdir/d1.grib | tee out line_count 1 $g2r --compact $srcdir/d1.grib | tee out line_count 2 grep_count "timespan=fs" 1 metkit-1.20.2/tests/tools/CMakeLists.txt0000664000175000017500000000050315246725757020341 0ustar alastairalastair if ( HAVE_GRIB AND HAVE_BUILD_TOOLS ) list( APPEND metkit_tools grib-to-request ) foreach( _test ${metkit_tools} ) ecbuild_configure_file( ${_test}.sh.in ${_test}.sh @ONLY ) ecbuild_add_test( TYPE SCRIPT COMMAND ${_test}.sh ) endforeach( ) endif() metkit-1.20.2/tests/test_multihandle.cc0000664000175000017500000000356415246725757020327 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_MetFile.cc /// @date Jan 2016 /// @author Florian Rathgeber #include "eckit/io/FileHandle.h" #include "eckit/io/MultiHandle.h" #include "eckit/testing/Test.h" #include "eccodes.h" namespace metkit::grib::test { //---------------------------------------------------------------------------------------------------------------------- CASE("openf filehandle") { eckit::FileHandle dh("latlon.grib"); FILE* f = dh.openf("r"); codes_handle* h; int err = 0; size_t count = 0; while ((h = codes_handle_new_from_file(nullptr, f, PRODUCT_ANY, &err))) { count++; codes_handle_delete(h); } fclose(f); EXPECT(count == 1); EXPECT(err == GRIB_SUCCESS); } //---------------------------------------------------------------------------------------------------------------------- CASE("openf multihandle") { eckit::MultiHandle dh; dh += new eckit::FileHandle("latlon.grib"); dh += new eckit::FileHandle("latlon.grib"); FILE* f = dh.openf("r"); codes_handle* h; int err = 0; size_t count = 0; while ((h = codes_handle_new_from_file(nullptr, f, PRODUCT_ANY, &err))) { count++; codes_handle_delete(h); } fclose(f); EXPECT(count == 2); EXPECT(err == GRIB_SUCCESS); } //----------------------------------------------------------------------------- } // namespace metkit::grib::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/marsgen/0000775000175000017500000000000015246725757016077 5ustar alastairalastairmetkit-1.20.2/tests/marsgen/metkit_marsgen_legacy.sh.in0000775000175000017500000000166215246725757023405 0ustar alastairalastair#!/bin/bash set -uex [ -e outfile.legacy ] && rm outfile.legacy MARSGEN="@CMAKE_BINARY_DIR@/bin/mars-archive-script" GENFILE="@CMAKE_CURRENT_SOURCE_DIR@/genfile.legacy" OUTFILE="outfile.legacy" CHECKFILE="checkfile.legacy" $MARSGEN --in="$GENFILE" --out="$OUTFILE" cat << EOF > $CHECKFILE \$RETRIEVE_MARS << @ retrieve, class=EA, stream=LWDA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, target=mars.source.0 @ \$RETRIEVE_COMPLETE \$UNBLOCK mars.source.0 mars.archive.0 \$ARCHIVE_MARS << @ archive, class=EA, stream=DA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, source=mars.archive.0 @ \$ARCHIVE_COMPLETE EOF cmp $CHECKFILE $OUTFILE metkit-1.20.2/tests/marsgen/metkit_marsgen_simple.sh.in0000775000175000017500000000165315246725757023432 0ustar alastairalastair#!/bin/bash set -uex [ -e outfile.simple ] && rm outfile.simple MARSGEN="@CMAKE_BINARY_DIR@/bin/mars-archive-script" GENFILE="@CMAKE_CURRENT_SOURCE_DIR@/genfile" OUTFILE="outfile.simple" CHECKFILE="checkfile.simple" $MARSGEN --in="$GENFILE" --out="$OUTFILE" cat << EOF > $CHECKFILE \$RETRIEVE_MARS << @ retrieve, class=EA, stream=LWDA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, target=mars.source.0 @ \$RETRIEVE_COMPLETE \$UNBLOCK mars.source.0 mars.archive.0 \$ARCHIVE_MARS << @ archive, class=EA, stream=DA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, source=mars.archive.0 @ \$ARCHIVE_COMPLETE EOF cmp $CHECKFILE $OUTFILE metkit-1.20.2/tests/marsgen/genfile.default0000664000175000017500000000055015246725757021056 0ustar alastairalastairverb, default@expver = 9999, default@class = EA, default@stream = LWDA, class=EA, stream=LWDA, expver=1001, type=analysis,anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass="runda", date=20200601, time=06, step=00 metkit-1.20.2/tests/marsgen/metkit_marsgen_compare.sh.in0000775000175000017500000000164315246725757023566 0ustar alastairalastair#!/bin/bash set -uex [ -e outfile.compare ] && rm outfile.compare MARSGEN="@CMAKE_BINARY_DIR@/bin/mars-archive-script" GENFILE="@CMAKE_CURRENT_SOURCE_DIR@/genfile" OUTFILE="outfile.compare" CHECKFILE="checkfile.compare" $MARSGEN < $GENFILE > $OUTFILE cat << EOF > $CHECKFILE \$RETRIEVE_MARS << @ retrieve, class=EA, stream=LWDA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, target=mars.source.0 @ \$RETRIEVE_COMPLETE \$UNBLOCK mars.source.0 mars.archive.0 \$ARCHIVE_MARS << @ archive, class=EA, stream=DA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, source=mars.archive.0 @ \$ARCHIVE_COMPLETE EOF cmp $CHECKFILE $OUTFILEmetkit-1.20.2/tests/marsgen/genfile.legacy0000664000175000017500000000071115246725757020675 0ustar alastairalastairverb, fdb_retrieve@expver = 9999, archive@expver = 9999, fdb_retrieve@class = EA, archive@class = EA, fdb_retrieve@stream = LWDA, archive@stream = DA, class=EA, stream=LWDA, expver=1001, type=analysis,anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass="runda", date=20200601, time=06, step=00 metkit-1.20.2/tests/marsgen/genfile0000664000175000017500000000067515246725757017443 0ustar alastairalastairverb, retrieve@expver = 9999, archive@expver = 9999, retrieve@class = EA, archive@class = EA, retrieve@stream = LWDA, archive@stream = DA, class=EA, stream=LWDA, expver=1001, type=analysis,anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass="runda", date=20200601, time=06, step=00 metkit-1.20.2/tests/marsgen/metkit_marsgen_obs.sh.in0000775000175000017500000000117115246725757022717 0ustar alastairalastair#!/bin/bash set -uex [ -e outfile.obs ] && rm outfile.obs MARSGEN="@CMAKE_BINARY_DIR@/bin/mars-archive-script" GENFILE="@CMAKE_CURRENT_SOURCE_DIR@/genfile" OUTFILE="outfile.obs" CHECKFILE="checkfile.obs" $MARSGEN --obs < $GENFILE > $OUTFILE cat << EOF > $CHECKFILE \$PREOBS \$header.1 \$data.1 mars.archive.0 \$ARCHIVE_MARS << @ archive, class=EA, stream=DA, expver=9999, type=analysis, anoffset=3, param=129/152/130/138/155/75/76/133/203.128/246/247/248, levtype=modellevels, levels=1/to/137, pass=runda, date=20200601, time=06, step=00, source=mars.archive.0 @ \$ARCHIVE_COMPLETE EOF cmp $CHECKFILE $OUTFILE metkit-1.20.2/tests/marsgen/CMakeLists.txt0000664000175000017500000000052315246725757020637 0ustar alastairalastair list( APPEND marsgen_tests simple compare obs legacy) foreach( _test ${marsgen_tests} ) configure_file( metkit_marsgen_${_test}.sh.in metkit_marsgen_${_test}.sh @ONLY ) ecbuild_add_test( TYPE SCRIPT CONDITION HAVE_BUILD_TOOLS COMMAND metkit_marsgen_${_test}.sh ) endforeach()metkit-1.20.2/tests/test_type_levelist.cc0000664000175000017500000000616015246725757020704 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_levelist.cc /// @author Metin Cakircali /// @author Emanuele Danovaro /// @date November 2023 #include #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Type.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars::test { //----------------------------------------------------------------------------- CASE("test_metkit_exists_to-by-list-float") { std::stringstream ss; TypesFactory::list(ss); const std::string str(ss.str()); EXPECT(str.find_first_of("to-by-list-float")); } void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); language.type(name)->expand(values); EXPECT_EQUAL(values, expected); } CASE("test_metkit_expand_levelist") { // by > 0 assertTypeExpansion("levelist", {"-1", "to", "2", "by", "0.5"}, {"-1", "-0.5", "0", ".5", "1", "1.5", "2"}); // by > 0 assertTypeExpansion("levelist", {"-10.0", "to", "2.0", "by", "1"}, {"-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", "1", "2"}); // by > 0 && from < to assertTypeExpansion("levelist", {"4", "to", "20", "by", "4"}, {"4", "8", "12", "16", "20"}); assertTypeExpansion("levelist", {"4", "to", "18", "by", "4"}, {"4", "8", "12", "16"}); // by > 0 && from > to assertTypeExpansion("levelist", {"20", "to", "4", "by", "4"}, {"20", "16", "12", "8", "4"}); // by = 0 EXPECT_THROWS_AS(assertTypeExpansion("levelist", {"4", "to", "20", "by", "0"}, {"4"}), eckit::BadValue); EXPECT_THROWS_AS(assertTypeExpansion("levelist", {"-1", "to", "2", "by", "0"}, {"-1"}), eckit::BadValue); // by < 0 && from > to assertTypeExpansion("levelist", {"20", "to", "4", "by", "-4"}, {"20", "16", "12", "8", "4"}); assertTypeExpansion("levelist", {"10", "to", "4", "by", "-2"}, {"10", "8", "6", "4"}); assertTypeExpansion("levelist", {"-2", "to", "-4", "by", "-0.5"}, {"-2", "-2.5", "-3", "-3.5", "-4"}); assertTypeExpansion("levelist", {"0", "to", "-2", "by", "-0.5"}, {"0", "-0.5", "-1", "-1.5", "-2"}); // by < 0 && from < to EXPECT_THROWS_AS(assertTypeExpansion("levelist", {"4", "to", "10", "by", "-4"}, {"4", "8", "12", "16", "20"}), eckit::BadValue); EXPECT_THROWS_AS( assertTypeExpansion("levelist", {"-4", "to", "2", "by", "-0.5"}, {"0", "-0.5", "-1", "-1.5", "-2"}), eckit::BadValue); } } // namespace metkit::mars::test //----------------------------------------------------------------------------- int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_filter.cc0000664000175000017500000001213715246725757017302 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_filter.cc /// @date Apr 2025 /// @author Emanuele Danovaro #include #include #include #include #include #include #include "eckit/testing/Test.h" #include "eckit/types/Date.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Tokenizer.h" #include "metkit/mars/MarsParser.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" namespace metkit::mars::test { namespace { using ExpectedVals = std::map>; } //----------------------------------------------------------------------------- void filter(MarsRequest& r, const MarsRequest& f, const ExpectedVals& expected, const std::vector dates) { r.filter(f); for (const auto& e : expected) { if (!r.has(e.first)) { std::cerr << eckit::Colour::red << "Missing keyword: " << e.first << eckit::Colour::reset << std::endl; } EXPECT(r.has(e.first)); auto vv = r.values(e.first); if (e.first != "date") { // dates are verified at a later stage EXPECT_EQUAL(e.second.size(), vv.size()); } for (int i = 0; i < vv.size(); i++) { if (e.first == "grid") { EXPECT_EQUAL(eckit::StringTools::upper(e.second.at(i)), vv.at(i)); } else { EXPECT_EQUAL(e.second.at(i), vv.at(i)); } } } if (dates.size() > 0) { EXPECT(r.has("date")); auto dd = r.values("date"); EXPECT_EQUAL(dates.size(), dd.size()); for (int i = 0; i < dates.size(); i++) { long d = dates.at(i); if (d < 0) { eckit::Date day(d); d = day.yyyymmdd(); } EXPECT_EQUAL(std::to_string(d), dd.at(i)); } } } void filter(const std::string& text, const std::string& filter_text, const ExpectedVals& expected, std::vector dates, bool strict = true) { MarsRequest r = MarsRequest::parse(text, strict); std::string f_text = "filter," + filter_text; std::istringstream in(f_text); metkit::mars::MarsParser parser(in); std::vector f = parser.parse(); ASSERT(f.size() == 1); filter(r, f[0], expected, std::move(dates)); } void expand(const std::string& text, const std::string& filter_text, const std::string& expected, bool strict = true, std::vector dates = {}) { ExpectedVals out; eckit::Tokenizer c(","); eckit::Tokenizer e("="); eckit::Tokenizer s("/"); eckit::StringList tokens; c(expected, tokens); std::string verb; for (const auto& t : tokens) { if (verb.empty()) { verb = eckit::StringTools::lower(t); continue; } auto tt = eckit::StringTools::trim(t); eckit::StringList kv; e(tt, kv); EXPECT_EQUAL(2, kv.size()); auto key = eckit::StringTools::lower(eckit::StringTools::trim(kv[0])); if (key == "date") { EXPECT_EQUAL(0, dates.size()); } eckit::StringList vals; s(kv[1], vals); std::vector vv; for (auto v : vals) { auto val = eckit::StringTools::trim(v); if (key != "source" && key != "target") { val = eckit::StringTools::lower(val); } if (key == "date") { dates.push_back(std::stol(val)); } else { vv.push_back(val); } } if (key != "date") { out.emplace(key, vv); } } MarsRequest r = MarsRequest::parse(text, strict); std::string f_text = "filter," + filter_text; std::istringstream in(f_text); metkit::mars::MarsParser parser(in); std::vector f = parser.parse(); ASSERT(f.size() == 1); filter(r, f[0], out, std::move(dates)); } CASE("day") { const char* text = "ret,date=20250301/to/20250306"; const char* filter_text = "day=1/3/5/7/9/11/13/15/17/19/21/23/25/27/29/31"; ExpectedVals expected{{"class", {"od"}}, {"domain", {"g"}}, {"expver", {"0001"}}, {"levelist", {"1000", "850", "700", "500", "400", "300"}}, {"levtype", {"pl"}}, {"param", {"129"}}, {"step", {"0"}}, {"stream", {"oper"}}, {"time", {"1200"}}, {"type", {"an"}}}; filter(text, filter_text, expected, {20250301, 20250303, 20250305}); } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_time.cc0000664000175000017500000001051215246725757016746 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_time.cc /// @author Simon Smart /// @author Emanuele Danovaro /// @date April 2020 #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Type.h" #include "metkit/mars/TypeTime.h" namespace metkit::mars::test { using ::eckit::BadTime; using ::eckit::BadValue; using ::eckit::Value; //----------------------------------------------------------------------------- void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); language.type(name)->expand(values); EXPECT_EQUAL(values.size(), expected.size()); EXPECT_EQUAL(expected, values); } void checkExpansion(const Type& tt, const std::string& value, const std::string& expected) { auto expanded = tt.tidy(value); EXPECT_EQUAL(expected, expanded); } CASE("Test TypeTime expansions") { TypeTime ttime("time", Value()); Type& tt(ttime); // 1 and 2-digit times checkExpansion(tt, "0", "0000"); checkExpansion(tt, "00", "0000"); checkExpansion(tt, "12", "1200"); checkExpansion(tt, "6", "0600"); checkExpansion(tt, "06", "0600"); // 3 and 4-digit times checkExpansion(tt, "000", "0000"); checkExpansion(tt, "0000", "0000"); checkExpansion(tt, "012", "0012"); checkExpansion(tt, "0012", "0012"); checkExpansion(tt, "1234", "1234"); checkExpansion(tt, "623", "0623"); checkExpansion(tt, "0623", "0623"); { EXPECT_THROWS_AS(auto vv = tt.tidy("675"), BadTime); } // 5 and 6-digit times checkExpansion(tt, "00000", "0000"); checkExpansion(tt, "001200", "0012"); checkExpansion(tt, "123400", "1234"); checkExpansion(tt, "062300", "0623"); checkExpansion(tt, "62300", "0623"); { // We don't support seconds yet. EXPECT_THROWS_AS(auto vv = tt.tidy("000012"), BadValue); } { // We don't support seconds yet. EXPECT_THROWS_AS(auto vv = tt.tidy("123456"), BadValue); } { // We don't support time > 24h EXPECT_THROWS_AS(auto vv = tt.tidy("283456"), BadValue); } // times with colons checkExpansion(tt, "0:0", "0000"); checkExpansion(tt, "00:00", "0000"); checkExpansion(tt, "00:00:00", "0000"); checkExpansion(tt, "0:12", "0012"); checkExpansion(tt, "00:12", "0012"); checkExpansion(tt, "12:34", "1234"); checkExpansion(tt, "6:23", "0623"); checkExpansion(tt, "06:23", "0623"); checkExpansion(tt, "00:12:00", "0012"); checkExpansion(tt, "12:34:00", "1234"); checkExpansion(tt, "06:23:00", "0623"); checkExpansion(tt, "6:23:00", "0623"); { EXPECT_THROWS_AS(auto vv = tt.tidy("00:62"), BadTime); } { // We don't support seconds yet. EXPECT_THROWS_AS(auto vv = tt.tidy("00:00:12"), BadValue); } { // We don't support seconds yet. EXPECT_THROWS_AS(auto vv = tt.tidy("12:34:56"), BadValue); } // times with units assertTypeExpansion("time", {"0h"}, {"0000"}); assertTypeExpansion("time", {"00H"}, {"0000"}); assertTypeExpansion("time", {"60m"}, {"0100"}); assertTypeExpansion("time", {"2h30m"}, {"0230"}); assertTypeExpansion("time", {"60s"}, {"0001"}); EXPECT_THROWS_AS(assertTypeExpansion("time", {"6s"}, {"0000"}), BadValue); EXPECT_THROWS_AS(assertTypeExpansion("time", {"25"}, {"0000"}), BadValue); // from to by assertTypeExpansion("time", {"0", "to", "18"}, {"0000", "0600", "1200", "1800"}); assertTypeExpansion("time", {"0", "to", "6", "by", "1"}, {"0000", "0100", "0200", "0300", "0400", "0500", "0600"}); assertTypeExpansion("time", {"0", "to", "1h30m", "by", "30m"}, {"0000", "0030", "0100", "0130"}); } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/pymetkit/0000775000175000017500000000000015246725757016311 5ustar alastairalastairmetkit-1.20.2/tests/pymetkit/integration/0000775000175000017500000000000015246725757020634 5ustar alastairalastairmetkit-1.20.2/tests/pymetkit/integration/mars_request/0000775000175000017500000000000015246725757023346 5ustar alastairalastairmetkit-1.20.2/tests/pymetkit/integration/mars_request/test_parse.py0000664000175000017500000000521215246725757026071 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Parsing MARS requests from strings and files.""" from datetime import datetime, timedelta from pymetkit import MarsRequest, parse_mars_request yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y%m%d") MULTIPLE_REQUESTS = """ retrieve, class=od, domain=g, expver=0001, levtype=sfc, stream=enfo, date=-1, time=12, param=151.128, grid=O640, step=0/to/24/by/6, target=test.grib, type=em retrieve, class=od, domain=g, expver=0001, levtype=pl, stream=enfo, date=-1, time=12, param=129, levelist=500, grid=O640, step=0/to/24/by/6, target=test.grib, type=em """ SINGLE_REQUEST = "retrieve,class=od,date=-1,time=12,param=129,step=12,target=test.grib" # --------------------------------------------------------------------------- # Parsing requests # --------------------------------------------------------------------------- def test_parse_single_request_from_string(): requests = parse_mars_request(SINGLE_REQUEST) assert len(requests) == 1 assert requests[0].verb() == "retrieve" assert requests[0].num_values("step") == 1 def test_parse_multiple_requests_from_string(): requests = parse_mars_request(MULTIPLE_REQUESTS) assert len(requests) == 2 for req in requests: assert req.verb() == "retrieve" assert req.num_values("step") == 5 def test_parse_from_file_object(tmpdir): request_file = f"{tmpdir}/requests" with open(request_file, "w") as handle: handle.write(MULTIPLE_REQUESTS) with open(request_file, "r") as handle: requests = parse_mars_request(handle) assert len(requests) == 2 assert "class" in requests[0] assert requests[1]["levelist"] == "500" def test_parse_returns_marsrequest_objects(): requests = parse_mars_request(MULTIPLE_REQUESTS) assert all(isinstance(req, MarsRequest) for req in requests) def test_parse_expands_relative_date(): requests = parse_mars_request(MULTIPLE_REQUESTS) for req in requests: assert req["date"] == yesterday # --------------------------------------------------------------------------- # Empty input # --------------------------------------------------------------------------- def test_parse_empty_string_returns_no_requests(): assert parse_mars_request("") == [] def test_parse_empty_file_returns_no_requests(tmpdir): request_file = f"{tmpdir}/requests" with open(request_file, "w") as handle: handle.write("") assert parse_mars_request(open(request_file, "r")) == [] metkit-1.20.2/tests/pymetkit/integration/mars_request/test_construction.py0000664000175000017500000000413615246725757027515 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Construction of MarsRequest objects: verbs and selections.""" import pytest from pymetkit import MarsRequest # --------------------------------------------------------------------------- # Verb and selection sources # --------------------------------------------------------------------------- def test_verb_only(): req = MarsRequest("retrieve") assert req.verb() == "retrieve" assert list(req.keys()) == [] def test_selection_mapping_argument(): req = MarsRequest("retrieve", {"class": "od", "param": [151, 129]}) assert req["class"] == "od" assert req["param"] == ["151", "129"] # --------------------------------------------------------------------------- # Value normalization # --------------------------------------------------------------------------- def test_numeric_values_are_stringified(): req = MarsRequest("retrieve", {"step": [0, 6, 12], "number": 1, "threshold": 1.5}) assert req["step"] == ["0", "6", "12"] assert req["number"] == "1" assert req["threshold"] == "1.5" def test_range_value_is_expanded_to_strings(): req = MarsRequest("retrieve", {"step": range(0, 13, 6)}) assert req["step"] == ["0", "6", "12"] def test_slash_separated_string_is_split(): req = MarsRequest("retrieve", {"date": "20200101/20200102", "step": "0/to/24/by/6"}) assert req["date"] == ["20200101", "20200102"] assert req["step"] == ["0", "to", "24", "by", "6"] # --------------------------------------------------------------------------- # Rejected input # --------------------------------------------------------------------------- def test_mapping_value_is_rejected(): with pytest.raises(ValueError): MarsRequest("retrieve", {"bad": {"nested": 1}}) def test_unsupported_value_object_is_rejected(): with pytest.raises(ValueError): MarsRequest("retrieve", {"bad": object()}) def test_non_mapping_selection_is_rejected(): with pytest.raises(ValueError): MarsRequest("retrieve", [("class", "od")]) metkit-1.20.2/tests/pymetkit/integration/mars_request/test_expand.py0000664000175000017500000000467715246725757026254 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Expansion and validation against the MARS language definition.""" from datetime import datetime, timedelta import pytest from pymetkit import MarsRequest, MetKitException yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y%m%d") def valid_request(): return MarsRequest( "retrieve", { "class": "od", "domain": "g", "date": "-1", "expver": "0001", "step": range(0, 13, 6), }, ) # --------------------------------------------------------------------------- # Expansion # --------------------------------------------------------------------------- def test_expand_preserves_verb(): assert valid_request().expand().verb() == "retrieve" def test_expand_normalizes_relative_date(): assert valid_request().expand()["date"] == yesterday def expansion_of_short_names(): request = valid_request() request_different = valid_request() request_different["class"] = "operational" request_different["domain"] = "global" assert request == request_different def test_expand_inherits_default_values(): expanded = valid_request().expand() assert "param" in expanded def test_expand_without_inherit_adds_no_defaults(): request = valid_request() inherited = request.expand(inherit=True) minimal = request.expand(inherit=False) assert minimal.verb() == "retrieve" assert set(minimal.keys()) <= set(inherited.keys()) def test_expand_returns_a_new_object_and_leaves_original_untouched(): request = valid_request() keys_before = set(request.keys()) expanded = request.expand() assert expanded is not request assert set(request.keys()) == keys_before # --------------------------------------------------------------------------- # Validation # --------------------------------------------------------------------------- def test_validate_accepts_a_valid_request(): valid_request().validate() # must not raise def test_validate_rejects_an_invalid_value(): request = MarsRequest( "retrieve", { "class": "invalid", "domain": "g", "date": "-1", "expver": "0001", "levtype": "sfc", "step": range(0, 13, 6), }, ) with pytest.raises(MetKitException): request.validate() metkit-1.20.2/tests/pymetkit/integration/mars_request/test_merge.py0000664000175000017500000000537215246725757026065 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Merging two MarsRequest objects. metkit's merge takes the union of the values of matching parameters, keeping the values of ``self`` first and appending only those values of ``other`` that are not already present (order-preserving, deduplicated). The two requests must carry the same parameters and the merged result must validate against the MARS language definition. """ import pytest from pymetkit import MarsRequest, MetKitException # Shared parameter set so both operands carry identical keys unless a test # deliberately diverges. BASE = {"class": "od", "domain": "g", "expver": "0001", "step": range(0, 13, 6)} def _left(): return MarsRequest("retrieve", {**BASE, "date": "-1", "levtype": "sfc"}) def _right(): return MarsRequest("retrieve", {**BASE, "date": "20230101", "levtype": "sfc"}) # --------------------------------------------------------------------------- # Successful merge # --------------------------------------------------------------------------- def test_merge_unions_values_keeping_self_first(): merged = _left().merge(_right()) # date differs between the two -> union, self's value first assert merged["date"] == ["-1", "20230101"] def test_merge_deduplicates_identical_values(): # levtype is "sfc" on both sides -> a single value, not duplicated assert _left().merge(_right())["levtype"] == "sfc" def test_merge_leaves_self_only_values_unchanged(): merged = _left().merge(_right()) assert merged["class"] == "od" assert merged["step"] == ["0", "6", "12"] def test_merge_returns_a_distinct_new_request(): left, right = _left(), _right() merged = left.merge(right) assert isinstance(merged, MarsRequest) assert merged is not left assert merged is not right def test_merge_does_not_mutate_the_operands(): left, right = _left(), _right() left.merge(right) # values are untouched on both inputs assert left["date"] == "-1" assert right["date"] == "20230101" assert set(left.keys()) == set(right.keys()) # --------------------------------------------------------------------------- # Rejected merges # --------------------------------------------------------------------------- def test_merge_with_different_parameters_raises_value_error(): left = _left() right = MarsRequest("retrieve", {**BASE, "date": "-1", "levtype": "sfc", "type": "em"}) with pytest.raises(ValueError): left.merge(right) def test_merge_producing_invalid_request_raises_metkit_exception(): left = _left() right = MarsRequest("retrieve", {**BASE, "date": "-1", "levtype": "pl"}) with pytest.raises(MetKitException): left.merge(right) metkit-1.20.2/tests/pymetkit/integration/mars_request/test_equality.py0000664000175000017500000000521315246725757026615 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Equality, hashing and string representation of MarsRequest.""" from pymetkit import MarsRequest def base_request(verb="retrieve", **overrides): selection = { "class": "od", "domain": "g", "date": "20230101", "param": "130", "expver": "0001", "step": range(0, 13, 6), } selection.update(overrides) return MarsRequest(verb, selection) # --------------------------------------------------------------------------- # Equal requests # --------------------------------------------------------------------------- def test_identical_requests_are_equal(): assert base_request() == base_request() def test_equality_ignores_value_representation(): # 20230101 vs "20230101" and 130 vs "130" expand to the same request assert base_request() == base_request(date=20230101, param=130) # --------------------------------------------------------------------------- # Unequal requests # --------------------------------------------------------------------------- def test_different_verb_is_not_equal(): assert base_request(verb="retrieve") != base_request(verb="compute") def test_different_values_are_not_equal(): assert base_request(param="130") != base_request(param="131") def test_comparison_with_non_request_is_not_equal(): assert (base_request() == "retrieve") is False assert base_request() != "retrieve" # --------------------------------------------------------------------------- # Representation # --------------------------------------------------------------------------- def test_repr_contains_the_verb(): assert "retrieve" in repr(base_request()) # --------------------------------------------------------------------------- # Hashing # --------------------------------------------------------------------------- def test_equal_requests_have_same_hash(): assert hash(base_request()) == hash(base_request()) def test_hash_consistent_with_equality_across_representations(): # date as int vs string, param as int vs string: different pre-expansion # forms that expand to the same request and must therefore hash identically. assert hash(base_request()) == hash(base_request(date=20230101, param=130)) def test_unequal_requests_have_different_hash(): assert hash(base_request(param="130")) != hash(base_request(param="131")) def test_request_usable_as_dict_key(): req = base_request() d = {req: "value"} assert d[req] == "value" def test_request_usable_in_set(): req = base_request() assert req in {req} metkit-1.20.2/tests/pymetkit/integration/mars_request/test_selection.py0000664000175000017500000001333615246725757026752 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """Tests for internal value normalisation (pymetkit.pymetkit_type.UserInputMapper).""" import pytest from pymetkit.pymetkit_type import UserInputMapper # --------------------------------------------------------------------------- # map_selection_to_internal: scalar values # --------------------------------------------------------------------------- def test_single_value_internal(): result = UserInputMapper.map_selection_to_internal({"key-1": "value-1"}) assert len(result) == 1 assert "key-1" in result assert result["key-1"] == ["value-1"] def test_int_and_float_become_string_lists(): result = UserInputMapper.map_selection_to_internal({"number": 1, "threshold": 1.5}) assert result["number"] == ["1"] assert result["threshold"] == ["1.5"] def test_slash_separated_string_is_split(): result = UserInputMapper.map_selection_to_internal({"date": "20200101/20200102"}) assert result["date"] == ["20200101", "20200102"] def test_string_without_slash_is_kept_whole(): result = UserInputMapper.map_selection_to_internal({"grid": "O640"}) assert result["grid"] == ["O640"] # --------------------------------------------------------------------------- # map_selection_to_internal: collection values # --------------------------------------------------------------------------- def test_collection_values_are_stringified(): result = UserInputMapper.map_selection_to_internal( { "key-1": ["value-1", "value-2", "value-3"], "key-2": ["value-2"], "key-3": ["value-3", 214, 213.54], "key-4": [120, 123, 124, 125], } ) assert result["key-1"] == ["value-1", "value-2", "value-3"] assert result["key-2"] == ["value-2"] assert result["key-3"] == ["value-3", "214", "213.54"] assert result["key-4"] == ["120", "123", "124", "125"] def test_range_is_expanded(): result = UserInputMapper.map_selection_to_internal({"step": range(0, 13, 6)}) assert result["step"] == ["0", "6", "12"] # --------------------------------------------------------------------------- # map_selection_to_internal: mixed numeric types # --------------------------------------------------------------------------- def test_to_internal_mixed_numeric_types(): result = UserInputMapper.map_selection_to_internal( { "key-1": ["value-2", "value-4"], "key-2": ["0.1", 0.2], "key-3": [0.1, 0.2], "key-4": [1, 2], } ) assert result["key-1"] == ["value-2", "value-4"] assert result["key-2"] == ["0.1", "0.2"] assert result["key-3"] == ["0.1", "0.2"] assert result["key-4"] == ["1", "2"] # --------------------------------------------------------------------------- # map_selection_to_external # --------------------------------------------------------------------------- def test_single_value_external(): result = UserInputMapper.map_selection_to_external({"key-1": ["value-1"]}) assert result["key-1"] == "value-1" def test_multi_value_external(): result = UserInputMapper.map_selection_to_external( { "key-1": ["value-1", "value-2", "value-3"], "key-2": ["value-2"], } ) assert result["key-1"] == ["value-1", "value-2", "value-3"] assert result["key-2"] == "value-2" # --------------------------------------------------------------------------- # map_values_to_external # --------------------------------------------------------------------------- def test_map_values_to_external_collapses_single_value(): assert UserInputMapper.map_values_to_external(["od"]) == "od" assert UserInputMapper.map_values_to_external(["151", "129"]) == ["151", "129"] # --------------------------------------------------------------------------- # Key overwrite behavior # --------------------------------------------------------------------------- def test_overwrite_key_internal(): result = UserInputMapper.map_selection_to_internal( { "key-1": "value-1", "key-1": ["value-3", "214", "213.54"], } ) assert len(result) == 1 assert result["key-1"] == ["value-3", "214", "213.54"] def test_overwrite_key_external(): result = UserInputMapper.map_selection_to_external( { "key-1": ["value-1"], "key-1": ["value-3", "214", "213.54"], } ) assert len(result) == 1 assert result["key-1"] == ["value-3", "214", "213.54"] # --------------------------------------------------------------------------- # Rejected values # --------------------------------------------------------------------------- def test_mapping_value_is_rejected(): with pytest.raises(ValueError): UserInputMapper.map_selection_to_internal({"bad": {"nested": 1}}) def test_unsupported_value_object_is_rejected(): with pytest.raises(ValueError): UserInputMapper.map_selection_to_internal({"bad": object()}) # --------------------------------------------------------------------------- # Pythonic interface # --------------------------------------------------------------------------- def test_pythonic_interface(): result = UserInputMapper.map_selection_to_internal( { "key-1": ["value-1", "value-2", "value-3"], "key-2": 0.1, "key-3": list(range(1, 5)), "key-4": [0.1, "0.2"], "key-5": [1 + 0.5 * x for x in range(2)], } ) assert result["key-1"] == ["value-1", "value-2", "value-3"] assert result["key-2"] == ["0.1"] assert result["key-3"] == ["1", "2", "3", "4"] assert result["key-4"] == ["0.1", "0.2"] assert result["key-5"] == ["1.0", "1.5"] metkit-1.20.2/tests/pymetkit/integration/mars_request/test_accessors.py0000664000175000017500000000554715246725757026757 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 """The Mapping-like interface of MarsRequest: reading, mutating and iterating.""" import pytest from pymetkit import MarsRequest def make_request(): return MarsRequest("retrieve", {"class": "od", "param": [151, 129], "step": [0]}) # --------------------------------------------------------------------------- # Reading values # --------------------------------------------------------------------------- def test_single_value_returns_scalar(): req = make_request() assert req["class"] == "od" assert req["step"] == "0" def test_multiple_values_return_list(): assert make_request()["param"] == ["151", "129"] def test_num_values(): req = make_request() assert req.num_values("param") == 2 assert req.num_values("class") == 1 def test_dict_view(): assert dict(make_request()) == { "class": "od", "param": ["151", "129"], "step": "0", } # --------------------------------------------------------------------------- # Membership and iteration # --------------------------------------------------------------------------- def test_contains(): req = make_request() assert "class" in req assert "missing" not in req def test_keys_yields_parameter_names(): req = make_request() assert set(req.keys()) == {"class", "param", "step"} def test_iter_yields_key_value_pairs(): req = make_request() pairs = list(req) assert {k for k, _ in pairs} == {"class", "param", "step"} assert dict(pairs) == {"class": "od", "param": ["151", "129"], "step": "0"} # --------------------------------------------------------------------------- # Mutating values # --------------------------------------------------------------------------- def test_set_scalar_value(): req = make_request() req["expver"] = "0001" assert req["expver"] == "0001" assert "expver" in req def test_set_overwrites_existing_value(): req = make_request() req["class"] = "ea" assert req["class"] == "ea" def test_set_list_and_range_values(): req = make_request() req["date"] = ["20200101", "20200102"] req["step"] = range(0, 13, 6) assert req["date"] == ["20200101", "20200102"] assert req["step"] == ["0", "6", "12"] # --------------------------------------------------------------------------- # Missing keys # --------------------------------------------------------------------------- def test_getitem_missing_key_raises_keyerror(): with pytest.raises(KeyError): make_request()["missing"] def test_num_values_missing_key_raises_keyerror(): with pytest.raises(KeyError): make_request().num_values("missing") def test_set_invalid_value_is_rejected(): with pytest.raises(ValueError): make_request()["bad"] = {"nested": 1} metkit-1.20.2/tests/pymetkit/integration/test_marsrequest.py0000664000175000017500000001055115246725757024622 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 from contextlib import nullcontext as does_not_raise from datetime import datetime, timedelta import pytest from pymetkit import MarsRequest, MetKitException, parse_mars_request request = """ retrieve, class=od, domain=g, expver=0001, levtype=sfc, stream=enfo, date=-1, time=12, param=151.128, grid=O640, step=0/to/24/by/6, target=test.grib, type=em retrieve, class=od, domain=g, expver=0001, levtype=pl, stream=enfo, date=-1, time=12, param=129, levelist=500, grid=O640, step=0/to/24/by/6, target=test.grib, type=em """ yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y%m%d") def test_parse_file(tmpdir): request_file = f"{tmpdir}/requests" with open(request_file, "w") as f: f.write(request) requests = parse_mars_request(open(request_file, "r")) assert len(requests) == 2 for req in requests: assert req.verb() == "retrieve" assert len(req["step"]) == 5 assert req["date"] == yesterday assert "class" in requests[0] assert requests[1]["levelist"] == "500" def test_parse_string(): requests = parse_mars_request(request) assert len(requests) == 2 for req in requests: assert req.num_values("step") == 5 requests = parse_mars_request( "retrieve,class=od,date=-1,time=12,param=129,step=12,target=test.grib" ) assert len(requests) == 1 assert requests[0].num_values("step") == 1 def test_empty_request(tmpdir): request_file = f"{tmpdir}/requests" with open(request_file, "w") as f: f.write("") requests = parse_mars_request(open(request_file, "r")) assert len(requests) == 0 def test_new_request(): req = MarsRequest("retrieve") assert req.verb() == "retrieve" req = MarsRequest("request", {"class": "od", "type": "pf", "date": ["20200101", "20200102"]}) assert req["class"] == "od" assert req["type"] == "pf" assert req["date"] == ["20200101", "20200102"] def test_request_from_selection(): req = MarsRequest("retrieve", {"class": "od", "param": [151, 129]}) assert req["class"] == "od" assert req["param"] == ["151", "129"] def test_request_from_expand(): req = MarsRequest( "retrieve", { "class": "od", "domain": "g", "date": "-1", "expver": "0001", "step": range(0, 13, 6), }, ) expanded = req.expand() assert expanded.verb() == req.verb() assert expanded["date"] == yesterday assert "param" in expanded expanded.validate() assert req == expanded @pytest.mark.parametrize( "extra_kv", [ {"class": "invalid"}, ], ) def test_request_validate(extra_kv): request = { "class": "od", "domain": "g", "date": "-1", "expver": "0001", "step": range(0, 13, 6), "levtype": "sfc", } request.update(extra_kv) req = MarsRequest("retrieve", request) with pytest.raises(MetKitException): req.validate() @pytest.mark.parametrize( "extra_kv, expectation", [ [{"levtype": "pl", "date": "-1"}, pytest.raises(MetKitException)], [{"levtype": "sfc", "date": "-1", "type": "em"}, pytest.raises(ValueError)], [{"levtype": "sfc", "date": "20230101"}, does_not_raise()], ], ) def test_request_merge(extra_kv, expectation): request = { "class": "od", "domain": "g", "expver": "0001", "step": range(0, 13, 6), } req = MarsRequest("retrieve", {**request, "date": "-1", "levtype": "sfc"}) other_req = MarsRequest("retrieve", {**request, **extra_kv}) with expectation: req.merge(other_req) @pytest.mark.parametrize( "verb, updates, expected", [["retrieve", {"date": 20230101, "param": 130}, True], ["compute", {}, False]], ) def test_request_equality(verb, updates, expected): init_request = { "class": "od", "domain": "g", "date": "20230101", "param": "130", "expver": "0001", "step": range(0, 13, 6), } req = MarsRequest("retrieve", init_request) second_request = {**init_request, **updates} req2 = MarsRequest(verb, second_request) assert (req == req2) == expected metkit-1.20.2/tests/pymetkit/cli/0000775000175000017500000000000015246725757017060 5ustar alastairalastairmetkit-1.20.2/tests/pymetkit/cli/test_cli.py0000664000175000017500000002036715246725757021250 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 import logging import sys from pathlib import Path from unittest.mock import MagicMock import findlibs import pymetkit._internal as _internal import pytest from pymetkit.__main__ import DEPENDENCY_ORDER, OPTIONAL_DEPENDENCIES, main ALL_LIBS = { "eckit": "/fake/eckit/lib/libeckit.so", "eccodes": "/fake/eccodes/lib/libeccodes.so", "metkit": "/fake/metkit/lib/libmetkit.so", } FAKE_VERSION_INFO = [ ("eckit", "1.32.5", "def5678", "/fake/eckit/lib/libeckit.so"), ("eccodes", "2.46.0", "jkl3456", "/fake/eccodes/lib/libeccodes.so"), ("metkit", "1.19.0", "ghi9012", "/fake/metkit/lib/libmetkit.so"), ] def _run_cli(args, monkeypatch): monkeypatch.setattr(sys, "argv", ["pymetkit"] + args) try: main() return 0 except SystemExit as exc: return exc.code def _expected_entry(name, path): """Build the expected log fragment for a findlibs lookup line.""" label = f"{name} [Optional]" if name in OPTIONAL_DEPENDENCIES else name home = Path(path).parent.parent return f"{label}: {home}" @pytest.fixture(autouse=True) def capture_info_logs(caplog): caplog.set_level(logging.INFO) @pytest.fixture(autouse=True) def version_info_mock(monkeypatch): mock = MagicMock(return_value=FAKE_VERSION_INFO) monkeypatch.setattr(_internal, "version_info", mock) return mock @pytest.fixture def find_mock(monkeypatch): mock = MagicMock(side_effect=lambda name: ALL_LIBS.get(name)) monkeypatch.setattr(findlibs, "find", mock) return mock # --------------------------------------------------------------------------- # --print-home # --------------------------------------------------------------------------- def test_print_home_success(find_mock, monkeypatch, caplog): exit_code = _run_cli(["--print-home"], monkeypatch) assert exit_code == 0 def test_print_home_logs_version_info(find_mock, monkeypatch, caplog): _run_cli(["--print-home"], monkeypatch) name, version, git_sha, path = FAKE_VERSION_INFO[-1] # metkit entry assert name in caplog.text assert version in caplog.text assert git_sha in caplog.text def test_print_home_not_found(monkeypatch, caplog): monkeypatch.setattr(findlibs, "find", MagicMock(return_value=None)) exit_code = _run_cli(["--print-home"], monkeypatch) assert exit_code == 1 assert "not found by findlibs" in caplog.text def test_print_home_calls_find_with_metkit(find_mock, monkeypatch): _run_cli(["--print-home"], monkeypatch) find_mock.assert_called_once_with("metkit") # --------------------------------------------------------------------------- # --print-home-deps # --------------------------------------------------------------------------- def test_print_home_deps_all_found(find_mock, monkeypatch, caplog): exit_code = _run_cli(["--print-home-deps"], monkeypatch) assert exit_code == 0 for name, path in ALL_LIBS.items(): assert _expected_entry(name, path) in caplog.text def test_print_home_deps_logs_dependency_versions(find_mock, monkeypatch, caplog): _run_cli(["--print-home-deps"], monkeypatch) assert "Dependency Versions:" in caplog.text for name, version, git_sha, _ in FAKE_VERSION_INFO: assert name in caplog.text assert version in caplog.text assert git_sha in caplog.text def test_print_home_deps_dependency_versions_logged_before_exit(monkeypatch, caplog): libs_without_eckit = {k: v for k, v in ALL_LIBS.items() if k != "eckit"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eckit.get(n)) ) _run_cli(["--print-home-deps"], monkeypatch) assert "Dependency Versions:" in caplog.text for name, version, git_sha, _ in FAKE_VERSION_INFO: assert name in caplog.text assert version in caplog.text assert git_sha in caplog.text def test_print_home_deps_missing_required_exits_nonzero(monkeypatch, caplog): libs_without_eckit = {k: v for k, v in ALL_LIBS.items() if k != "eckit"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eckit.get(n)) ) exit_code = _run_cli(["--print-home-deps"], monkeypatch) assert exit_code == 1 assert "eckit" in caplog.text def test_print_home_deps_missing_required_logs_error(monkeypatch, caplog): libs_without_eckit = {k: v for k, v in ALL_LIBS.items() if k != "eckit"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eckit.get(n)) ) _run_cli(["--print-home-deps"], monkeypatch) errors = [ r for r in caplog.records if r.levelno == logging.ERROR and "eckit" in r.message ] assert errors def test_print_home_deps_missing_optional_exits_zero(monkeypatch): libs_without_eccodes = {k: v for k, v in ALL_LIBS.items() if k != "eccodes"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eccodes.get(n)) ) exit_code = _run_cli(["--print-home-deps"], monkeypatch) assert exit_code == 0 def test_print_home_deps_missing_optional_logs_info_not_error(monkeypatch, caplog): libs_without_eccodes = {k: v for k, v in ALL_LIBS.items() if k != "eccodes"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eccodes.get(n)) ) _run_cli(["--print-home-deps"], monkeypatch) eccodes_records = [r for r in caplog.records if "eccodes" in r.message] assert eccodes_records assert all(r.levelno == logging.INFO for r in eccodes_records) def test_print_home_deps_optional_marker_in_message(monkeypatch, caplog): libs_without_eccodes = {k: v for k, v in ALL_LIBS.items() if k != "eccodes"} monkeypatch.setattr( findlibs, "find", MagicMock(side_effect=lambda n: libs_without_eccodes.get(n)) ) _run_cli(["--print-home-deps"], monkeypatch) assert "[Optional]" in caplog.text def test_print_home_deps_queries_all_deps(find_mock, monkeypatch): _run_cli(["--print-home-deps"], monkeypatch) queried = {call.args[0] for call in find_mock.call_args_list} assert queried == set(DEPENDENCY_ORDER) def test_print_home_deps_disable_vars_appear_before_homes( find_mock, monkeypatch, caplog ): monkeypatch.setenv("FINDLIBS_DISABLE_METKIT", "1") _run_cli(["--print-home-deps"], monkeypatch) lines = caplog.text.splitlines() disable_idx = next( i for i, line in enumerate(lines) if "FINDLIBS_DISABLE_METKIT" in line ) first_dep_idx = next(i for i, line in enumerate(lines) if "metkit:" in line) assert disable_idx < first_dep_idx # --------------------------------------------------------------------------- # Output format # --------------------------------------------------------------------------- def test_logging_format(monkeypatch): calls = [] monkeypatch.setattr(logging, "basicConfig", lambda **kwargs: calls.append(kwargs)) monkeypatch.setattr( findlibs, "find", MagicMock(return_value="/fake/metkit/lib/libmetkit.so") ) _run_cli(["--print-home"], monkeypatch) assert calls, "basicConfig must be called" fmt = calls[0]["format"] assert "%(asctime)s" in fmt assert "%(levelname)" in fmt assert "%(message)s" in fmt def test_verbose_sets_debug_level(monkeypatch): calls = [] monkeypatch.setattr(logging, "basicConfig", lambda **kwargs: calls.append(kwargs)) monkeypatch.setattr( findlibs, "find", MagicMock(return_value="/fake/metkit/lib/libmetkit.so") ) _run_cli(["--print-home", "--verbose"], monkeypatch) assert calls[0]["level"] == logging.DEBUG def test_default_level_is_info(monkeypatch): calls = [] monkeypatch.setattr(logging, "basicConfig", lambda **kwargs: calls.append(kwargs)) monkeypatch.setattr( findlibs, "find", MagicMock(return_value="/fake/metkit/lib/libmetkit.so") ) _run_cli(["--print-home"], monkeypatch) assert calls[0]["level"] == logging.INFO # --------------------------------------------------------------------------- # No arguments # --------------------------------------------------------------------------- def test_no_args_exits_with_code_2(monkeypatch): exit_code = _run_cli([], monkeypatch) assert exit_code == 2 metkit-1.20.2/tests/pymetkit/CMakeLists.txt0000664000175000017500000000374015246725757021055 0ustar alastairalastair# (C) Copyright 2025- ECMWF. # # 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. # Create the pytest-tmp folder for encompassing the different scenarios file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pytest-tmp") function(add_pymetkit_test scenario test) get_filename_component(stem ${test} NAME_WLE) add_test( NAME ${scenario}_${stem} COMMAND ${Python_EXECUTABLE} -m pytest --basetemp ${CMAKE_CURRENT_BINARY_DIR}/pytest-tmp/${scenario}/ -vv -s ${CMAKE_CURRENT_SOURCE_DIR}/${test} ) set(_env "PYTHONPATH=${CMAKE_BINARY_DIR}/pymetkit-python-package-staging:$ENV{PYTHONPATH}" # prevent findlibs in pymetkit from picking up any globally installed metkit "METKIT_DIR=${CMAKE_BINARY_DIR}" "FINDLIBS_DISABLE_PACKAGE=yes" # metkit_env provides METKIT_HOME and ECCODES_DEFINITION_PATH for expansion ${metkit_env} ) set_tests_properties(${scenario}_${stem} PROPERTIES RESOURCE_LOCK ${scenario} ENVIRONMENT "${_env}" LABELS ${scenario} ) endfunction() set(test_files integration/mars_request/test_construction.py integration/mars_request/test_accessors.py integration/mars_request/test_selection.py integration/mars_request/test_expand.py integration/mars_request/test_merge.py integration/mars_request/test_equality.py integration/mars_request/test_parse.py cli/test_cli.py ) foreach(test_file ${test_files}) add_pymetkit_test("pymetkit" ${test_file}) endforeach() set(doc_test_files ../../docs/pymetkit/examples.rst ) foreach(test_file ${doc_test_files}) add_pymetkit_test("pymetkit_doc" ${test_file}) endforeach() metkit-1.20.2/tests/test_language.cc0000664000175000017500000003134315246725757017600 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/testing/Test.h" #include "eckit/types/Date.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" #include "metkit/mars/TypeAny.h" #include "metkit/mars/TypeDate.h" #include "metkit/mars/TypeEnum.h" #include "metkit/mars/TypeExpver.h" #include "metkit/mars/TypeMixed.h" #include "metkit/mars/TypeParam.h" #include "metkit/mars/TypesFactory.h" #define EXPECT_EMPTY(request, key) EXPECT_EQUAL(request.values(key, true).size(), 0); namespace metkit::mars::test { struct Expected { const std::string verb; std::map> keyvalue; }; using Sequence = std::vector; Sequence make_random_sequence(Sequence::value_type min, Sequence::value_type max, Sequence::size_type N) { static std::mt19937 gen{std::random_device{}()}; static std::uniform_int_distribution dist(min, max); Sequence s(N); std::generate(s.begin(), s.end(), [&] { return dist(gen); }); return s; } Sequence make_power_sequence(Sequence::value_type min, Sequence::value_type max) { Sequence s; for (auto i = min; i <= max; i *= 2) { s.push_back(i); } return s; } void expect_mars(const std::string& text, const Expected& expected, bool strict = false) { auto r = MarsRequest::parse(text, strict); EXPECT_EQUAL(expected.verb, r.verb()); for (const auto& kv : expected.keyvalue) { auto v = r.values(kv.first); if (kv.first == "grid") { EXPECT(kv.second.size() == 1 && v.size() == 1 && v[0] == kv.second[0]); continue; } EXPECT_EQUAL(kv.second, kv.second); } } // ----------------------------------------------------------------------------- CASE("grid: regular Gaussian grids") { const Sequence F{16, 24, 32, 48, 64, 80, 96, 128, 160, 192, 200, 256, 320, 400, 512, 576, 640, 800, 912, 1024, 1280, 1600, 2000, 2560, 4000, 8000}; const auto Fn = [](const auto& other) { auto seq = make_random_sequence(2, 8000, 20); seq.insert(seq.end(), other.begin(), other.end()); return seq; }(F); for (const auto& n : Fn) { // known grids are expanded correctly (pattern matching doesn't work reliably here) Expected expected{"retrieve", {{"grid", {"F" + std::to_string(n)}}}}; if (std::find(F.begin(), F.end(), n) == F.end()) { expected.keyvalue.clear(); } expect_mars("ret, date=-1, grid=F" + std::to_string(n), expected); expect_mars("ret, date=-1, grid=f" + std::to_string(n), expected); expect_mars("ret, date=-1, grid=" + std::to_string(n), expected); } } // ----------------------------------------------------------------------------- CASE("grid: octahedral Gaussian grids") { for (const auto& n : make_random_sequence(2, 8000, 20 /*reduced number of tests*/)) { const Expected expected{"retrieve", {{"grid", {"O" + std::to_string(n)}}}}; expect_mars("ret, date=-1, grid=O" + std::to_string(n), expected); expect_mars("ret, date=-1, grid=o" + std::to_string(n), expected); } } // ----------------------------------------------------------------------------- CASE("grid: reduced classical Gaussian grids") { for (const auto& n : Sequence{32, 48, 64, 80, 96, 128, 160, 200, 256, 320, 400, 512, 640, 800, 1024, 1280, 8000}) { const Expected expected{"retrieve", {{"grid", {"N" + std::to_string(n)}}}}; expect_mars("ret, date=-1, grid=N" + std::to_string(n), expected); expect_mars("ret, date=-1, grid=n" + std::to_string(n), expected); } } // ----------------------------------------------------------------------------- CASE("grid: HEALPix grids") { for (const auto& n : make_power_sequence(1, 8192)) { const Expected expected{"retrieve", {{"grid", {"H" + std::to_string(n)}}}}; expect_mars("ret, date=-1, grid=H" + std::to_string(n), expected); expect_mars("ret, date=-1, grid=h" + std::to_string(n), expected); const Expected expected_ring{"retrieve", {{"grid", {"HR" + std::to_string(n)}}}}; expect_mars("ret, date=-1, grid=Hr" + std::to_string(n), expected_ring); expect_mars("ret, date=-1, grid=hR" + std::to_string(n), expected_ring); const Expected expected_nested{"retrieve", {{"grid", {"HN" + std::to_string(n)}}}}; expect_mars("ret, date=-1, grid=Hn" + std::to_string(n), expected_nested); expect_mars("ret, date=-1, grid=hN" + std::to_string(n), expected_nested); } } // ----------------------------------------------------------------------------- CASE("check language file") { EXPECT_THROWS(MarsLanguage::jsonFile("langauge.yaml")); eckit::Value file; EXPECT_NO_THROW(file = MarsLanguage::jsonFile("language.yaml")); EXPECT(file.contains("disseminate")); EXPECT(file.contains("archive")); EXPECT(file.contains("retrieve")); EXPECT(file.contains("read")); EXPECT(file.contains("get")); EXPECT(file.contains("list")); EXPECT(file.contains("compute")); EXPECT(file.contains("write")); EXPECT(file.contains("pointdb")); } CASE("check defaults and _clear_defaults") { { // check "retrieve" that _clear_defaults is not set auto request = MarsRequest::parse("retrieve,param=2t,step=10/to/12/by/1", true); EXPECT_EQUAL(request.values("class").size(), 1); EXPECT_EQUAL(request.values("class")[0], "od"); // check date is yesterday auto values = request.values("date"); EXPECT_EQUAL(values.size(), 1); std::ostringstream yesterday; yesterday << eckit::Date(-1).yyyymmdd(); EXPECT_EQUAL(values[0], yesterday.str()); } { // check "read" that _clear_defaults is set for class auto request = MarsRequest::parse("read,param=2t", true); EXPECT_EMPTY(request, "class"); EXPECT_EMPTY(request, "date"); EXPECT_EMPTY(request, "domain"); EXPECT_EMPTY(request, "expver"); EXPECT_EMPTY(request, "levelist"); EXPECT_EMPTY(request, "levtype"); EXPECT_EMPTY(request, "step"); EXPECT_EMPTY(request, "stream"); EXPECT_EMPTY(request, "time"); EXPECT_EMPTY(request, "type"); } { // check "list" that _clear_defaults is set for class auto request = MarsRequest::parse("list,param=2t", true); EXPECT_EQUAL(request.values("class").size(), 1); EXPECT_EQUAL(request.values("class")[0], "od"); EXPECT_EMPTY(request, "date"); EXPECT_EMPTY(request, "domain"); EXPECT_EMPTY(request, "expver"); EXPECT_EMPTY(request, "levelist"); EXPECT_EMPTY(request, "levtype"); EXPECT_EMPTY(request, "step"); EXPECT_EMPTY(request, "stream"); EXPECT_EMPTY(request, "time"); EXPECT_EMPTY(request, "type"); } { // check "pointdb" that _clear_defaults is set for class auto request = MarsRequest::parse("pointdb,param=2t", true); EXPECT_EMPTY(request, "class"); EXPECT_EMPTY(request, "date"); EXPECT_EMPTY(request, "domain"); EXPECT_EMPTY(request, "expver"); EXPECT_EMPTY(request, "levelist"); EXPECT_EMPTY(request, "levtype"); EXPECT_EMPTY(request, "step"); EXPECT_EMPTY(request, "stream"); EXPECT_EMPTY(request, "time"); EXPECT_EMPTY(request, "type"); } { // check "number" is not removed from the request for type=pf auto request = MarsRequest::parse("disseminate,class=od,stream=enfo,type=pf,number=2", true); EXPECT_EQUAL(request.values("number").size(), 1); EXPECT_EQUAL(request.values("number")[0], "2"); } { // check "number" is not removed from the request for type=wp auto request = MarsRequest::parse("disseminate,class=od,stream=enfo,type=wp,number=2", true); EXPECT_EQUAL(request.values("number").size(), 1); EXPECT_EQUAL(request.values("number")[0], "2"); } } CASE("check method: isData()") { EXPECT_EQUAL(MarsLanguage("retrieve").isData("class"), true); EXPECT_EQUAL(MarsLanguage("retrieve").isData("date"), true); EXPECT_EQUAL(MarsLanguage("retrieve").isData("time"), true); EXPECT_EQUAL(MarsLanguage("retrieve").isData("step"), true); EXPECT_EQUAL(MarsLanguage("retrieve").isData("number"), true); EXPECT_EQUAL(MarsLanguage("disseminate").isData("accuracy"), false); EXPECT_EQUAL(MarsLanguage("disseminate").isData("grid"), false); } CASE("check method: flatten()") { struct Output : public FlattenCallback { std::ostringstream oss; void operator()(const MarsRequest& request) override { oss << request << '\n'; } }; Output output; auto request = MarsRequest::parse( "retrieve,class=od,type=an,stream=oper,levtype=pl,time=1200,param=2t,step=10/to/14/by/2,levelist=300/400/" "500,date=20250717", true); MarsLanguage("retrieve").flatten(request, output); EXPECT_EQUAL(output.oss.str(), "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=10,levelist=300,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=10,levelist=400,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=10,levelist=500,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=12,levelist=300,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=12,levelist=400,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=12,levelist=500,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=14,levelist=300,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=14,levelist=400,param=" "167,expver=0001,domain=g\n" "retrieve,class=od,type=an,stream=oper,levtype=pl,date=20250717,time=1200,step=14,levelist=500,param=" "167,expver=0001,domain=g\n"); } CASE("check some types") { { EXPECT_THROWS(MarsLanguage("read").type("unknown")); EXPECT_THROWS(MarsLanguage("retrieve").type("unknown")); EXPECT_NO_THROW(MarsLanguage("retrieve").type("_hidden")); } { auto language = MarsLanguage("retrieve"); auto* type = language.type("class"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("param"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("expver"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("domain"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("date"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("grid"); EXPECT(dynamic_cast(type) != nullptr); EXPECT_EQUAL(type->multiple(), true); type = language.type("area"); EXPECT(dynamic_cast(type) != nullptr); EXPECT_EQUAL(type->multiple(), true); type = language.type("accuracy"); EXPECT(dynamic_cast(type) != nullptr); type = language.type("resol"); EXPECT(dynamic_cast(type) != nullptr); } { auto language = MarsLanguage("archive"); auto* type = language.type("resol"); EXPECT(dynamic_cast(type) != nullptr); EXPECT_THROWS_AS(language.type("grid"), eckit::SeriousBug); EXPECT_THROWS_AS(language.type("area"), eckit::SeriousBug); } } // ----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/mars2mars/0000775000175000017500000000000015246725757016352 5ustar alastairalastairmetkit-1.20.2/tests/mars2mars/mars2mars-test-02.cc0000664000175000017500000000675415246725757022000 0ustar alastairalastair/* * (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. */ #include #include #include #include #include #include #include #include "eckit/testing/Test.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars2mars/api/Mars2Mars.h" #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_mars_request.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #ifndef MARS2MARS_TEST_REQUEST_FILE #define MARS2MARS_TEST_REQUEST_FILE "mars2mars-test-requests.mars" #endif namespace { const std::vector& mars2marsSplitKeys() { static const std::vector keys = { "class", "stream", "type", "expver", "levtype", "param", "levelist", "step", "date", "time", "number", "chem", "wavelength", "timespan", "hdate", "htime", }; return keys; } std::vector readRequestsFromFile(const std::string& path) { std::ifstream in(path); if (!in) { throw std::runtime_error("Cannot open MARS request file: " + path); } return metkit::mars::MarsRequest::parse(in); } std::vector flattenRequest(const metkit::mars::MarsRequest& request) { return request.split(mars2marsSplitKeys()); } std::string jsonArrayFromRequests(const std::vector& requests) { using metkit::mars2mars::utils::dict_traits::dict_to_json; std::ostringstream out; out << "[\n"; for (std::size_t i = 0; i < requests.size(); ++i) { out << " " << dict_to_json(requests[i]); if (i + 1 != requests.size()) { out << ","; } out << "\n"; } out << "]"; return out.str(); } } // namespace CASE("mars2mars_flatten_convert_append_to_json") { // try { using metkit::mars2mars::Mars2Mars; const std::string reqFileName = MARS2MARS_TEST_REQUEST_FILE; const std::vector requests = readRequestsFromFile(reqFileName); EXPECT(!requests.empty()); metkit::mars::MarsExpansion expand(true, true); Mars2Mars mars2mars; std::vector convertedPoints; for (const auto& request : requests) { const metkit::mars::MarsRequest expanded = expand.expand(request); const std::vector points = flattenRequest(expanded); EXPECT(!points.empty()); for (const auto& point : points) { const auto [converted, misc] = mars2mars.convert(point); convertedPoints.push_back(converted); } } EXPECT(!convertedPoints.empty()); const std::string outputJson = jsonArrayFromRequests(convertedPoints); std::cout << outputJson << std::endl; EXPECT(!outputJson.empty()); EXPECT(outputJson.front() == '['); EXPECT(outputJson.back() == ']'); // } // catch (const eckit::Exception& e) { // std::cerr << "Test failed with exception: " << e.what() << std::endl; // } } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); }metkit-1.20.2/tests/mars2mars/mars2mars-test-01.cc0000664000175000017500000000426515246725757021772 0ustar alastairalastair/* * (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. */ #include #include #include #include "eckit/log/CodeLocation.h" #include "eckit/testing/Test.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars2mars/api/Mars2Mars.h" #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_mars_request.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace { metkit::mars::MarsRequest generateMarsRequest() { metkit::mars::MarsRequest mars; mars.verb("retrieve"); // String-only keys mars.setValue("class", "od"); mars.setValue("stream", "oper"); mars.setValue("type", "fc"); mars.setValue("expver", "1"); mars.setValue("date", "2026-02-09"); mars.setValue("time", "00:00:00"); mars.setValue("levtype", "sfc"); // Explicit scalar conversions supported by the MarsRequest trait mars.setValue("param", "261018"); mars.setValue("step", "0"); metkit::mars::MarsExpansion expand(true, true); mars = expand.expand(mars); return mars; } } // namespace CASE("mars2mars_marsrequest_convert_to_json") { using metkit::mars2mars::Mars2Mars; using metkit::mars2mars::utils::dict_traits::dict_to_json; try { metkit::mars::MarsRequest marsRequest = generateMarsRequest(); const auto [converted, misc] = Mars2Mars{}.convert(marsRequest); const std::string json = dict_to_json(converted); std::cout << json << std::endl; EXPECT(!json.empty()); } catch (const std::exception& e) { throw std::runtime_error(std::string("Test failed with exception: ") + e.what()); } } int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); }metkit-1.20.2/tests/mars2mars/mars2mars-test-requests.mars0000664000175000017500000000421115246725757023771 0ustar alastairalastairretrieve, class=od, date=2026-02-09, expver=1, levtype=sfc, param=3.228/7.228/8.128/8.228/9.128/9.228/10.228/11.228/12.228/13.228/14.228/20.3/20.128/20.228/21.228/22.228/23.228/24.228/26.128/26.228/27.128/27.228/28.128/28.228/29.128/29.228/30.128/31.128/32.128/33.128/34.128/35.128/36.128/37.128/38.128/39.128/40.128/41.128/42.128/43.128/44.128/44.228/45.128/46.228/47.128/47.228/48.228/49.128/50.128/57.128/58.128/66.128/67.128/74.128/78.128/79.128/80.228/81.228/82.228/83.228/84.228/85.228/88.228/89.228/90.228/96.174/98.174/121.128/122.128/123.128/129.128/129.228/130.151/130.228/131.151/131.228/132.151/132.228/134.128/136.128/137.128/139.128/141.128/142.128/143.128/144.128/145.128/145.151/146.128/147.128/148.128/148.151/151.128/159.128/160.128/161.128/162.128/163.128/163.151/164.128/164.151/165.128/166.128/167.128/168.128/169.128/170.128/172.128/173.128/174.128/175.128/175.151/176.128/177.128/178.128/179.128/180.128/181.128/182.128/183.128/186.128/186.210/187.128/187.210/188.128/188.210/189.128/189.210/190.210/191.210/195.128/196.128/197.128/198.128/201.128/202.128/205.128/206.128/207.128/208.128/209.128/210.128/211.128/212.128/213.128/216.228/217.228/218.228/219.228/220.228/221.228/222.228/223.228/224.228/225.228/226.228/227.228/228.128/229.128/230.128/231.128/232.128/234.128/235.128/236.128/238.128/239.228/240.228/241.228/243.128/244.128/245.128/246.228/247.228/249.228/251.228/162071/162072/228035/228036/228045/228050/228051/228057/228058/228231/228232/228233/228234/228235/228236/228237/260004/260005/260015/260048/260109/260121/260123/260242/260255/260318/260319/260320/260321/260338/260339/261001/261002/261014/261015/261016/261018, step=0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41/42/43/44/45/46/47/48/49/50/51/52/53/54/55/56/57/58/59/60/61/62/63/64/65/66/67/68/69/70/71/72/73/74/75/76/77/78/79/80/81/82/83/84/85/86/87/88/89/90/93/96/99/102/105/108/111/114/117/120/123/126/129/132/135/138/141/144/150/156/162/168/174/180/186/192/198/204/210/216/222/228/234/240/246/252/258/264/270/276/282/288/294/300/306/312/318/324/330/336/342/348/354/360, stream=oper, time=00:00:00/12:00:00, type=fcmetkit-1.20.2/tests/mars2mars/CMakeLists.txt0000664000175000017500000000076215246725757021117 0ustar alastairalastairif( HAVE_MARS2MARS ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/mars2mars-test-requests.mars ${CMAKE_CURRENT_BINARY_DIR}/mars2mars-test-requests.mars COPYONLY ) ecbuild_add_test( TARGET mars2mars-test-01 SOURCES mars2mars-test-01.cc NO_AS_NEEDED LIBS eckit metkit ) ecbuild_add_test( TARGET mars2mars-test-02 SOURCES mars2mars-test-02.cc NO_AS_NEEDED LIBS eckit metkit ) endif() metkit-1.20.2/tests/test_date.cc0000664000175000017500000000670515246725757016736 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_date.cc /// @author Emanuele Danovaro /// @date March 2025 #include #include #include "eckit/testing/Test.h" #include "eckit/types/Date.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/TypeDate.h" namespace metkit::mars::test { using ::eckit::BadValue; using ::eckit::Value; //----------------------------------------------------------------------------- void assertTypeExpansion(const std::string& name, std::vector values, const std::vector& expected) { static MarsLanguage language("retrieve"); language.type(name)->expand(values); EXPECT_EQUAL(expected, values); } std::string date(long d) { if (d <= 0) { eckit::Date day(d); d = day.yyyymmdd(); } return std::to_string(d); } CASE("Test TypeDate expansions") { TypeDate tdate("date", Value()); Type& td(tdate); assertTypeExpansion("date", {"20140506"}, {"20140506"}); assertTypeExpansion("date", {"2014-05-06"}, {"20140506"}); assertTypeExpansion("date", {"20140506", "20140507"}, {"20140506", "20140507"}); assertTypeExpansion("date", {"20140506", "to", "20140506"}, {"20140506"}); assertTypeExpansion("date", {"20140506", "to", "20140507"}, {"20140506", "20140507"}); assertTypeExpansion("date", {"20140506", "to", "20140508"}, {"20140506", "20140507", "20140508"}); assertTypeExpansion("date", {"20140504", "20140506", "to", "20140508"}, {"20140504", "20140506", "20140507", "20140508"}); assertTypeExpansion("date", {"-1", "0"}, {date(-1), date(0)}); assertTypeExpansion("date", {"-1", "to", "-3"}, {date(-1), date(-2), date(-3)}); assertTypeExpansion("date", {"-3", "to", "-1"}, {date(-3), date(-2), date(-1)}); assertTypeExpansion("date", {"-5", "to", "-1", "by", "2"}, {date(-5), date(-3), date(-1)}); assertTypeExpansion("date", {"2"}, {"feb"}); assertTypeExpansion("date", {"jan"}, {"jan"}); assertTypeExpansion("date", {"september"}, {"sep"}); assertTypeExpansion("date", {"9"}, {"sep"}); assertTypeExpansion("date", {"1-01"}, {"jan-1"}); assertTypeExpansion("date", {"jan-01"}, {"jan-1"}); assertTypeExpansion("date", {"january-01"}, {"jan-1"}); assertTypeExpansion("date", {"feb-23"}, {"feb-23"}); assertTypeExpansion("date", {"2018-23"}, {"20180123"}); assertTypeExpansion("date", {"2018-41"}, {"20180210"}); { EXPECT_THROWS(auto vv = td.tidy("20141506")); // throws BadDate that is not exported } { EXPECT_THROWS(auto vv = td.tidy("20180132")); // throws BadDate that is not exported } { EXPECT_THROWS(auto vv = td.tidy("202401366")); // throws BadDate that is not exported } { EXPECT_THROWS_AS(auto vv = td.tidy("abc"), BadValue); } { EXPECT_THROWS_AS(auto vv = td.tidy("abc-01"), BadValue); } } //----------------------------------------------------------------------------- } // namespace metkit::mars::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/tests/test_c_api.c0000664000175000017500000000132315246725757016720 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_c_api.c /// @date Dec 2024 /// @author Christopher Bradley #include #include "metkit/api/metkit_c.h" int main(int argc, char** argv) { const char* version = metkit_version(); metkit_error_t err = metkit_initialise(); fprintf(stdout, "MetKit version: %s\n", version); return 0; } metkit-1.20.2/tests/CMakeLists.txt0000664000175000017500000000770415246725757017213 0ustar alastairalastair# (C) Copyright 1996- ECMWF. # # 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. list(APPEND metkit_env "METKIT_HOME=${CMAKE_BINARY_DIR}") if( DEFINED ECCODES_DEFINITION_PATH ) list(APPEND metkit_env "ECCODES_DEFINITION_PATH=${ECCODES_DEFINITION_PATH}") elseif( IS_DIRECTORY "${CMAKE_BINARY_DIR}/share/eccodes/definitions" ) list(APPEND metkit_env "ECCODES_DEFINITION_PATH=${CMAKE_BINARY_DIR}/share/eccodes/definitions") endif() ecbuild_get_test_multidata( TARGET grib_get_data DIRNAME grib-api/test-data/data NAMES latlon.grib synthetic_2msgs.grib ) ecbuild_get_test_multidata( TARGET metkit_get_odb_data NAMES multiodb.odb multiodb2.odb NOCHECK ) configure_file(pl.grib pl.grib COPYONLY) configure_file(sol.grib sol.grib COPYONLY) add_custom_target(soft_link_expand_test_data ALL COMMAND ${CMAKE_COMMAND} -E create_symlink "${CMAKE_CURRENT_SOURCE_DIR}/expand" "${CMAKE_CURRENT_BINARY_DIR}/expand") ecbuild_add_test( TARGET metkit_test_multihandle CONDITION HAVE_GRIB INCLUDES "${ECCODES_INCLUDE_DIRS}" SOURCES test_multihandle.cc LIBS metkit NO_AS_NEEDED TEST_DEPENDS grib_get_data ENVIRONMENT "${metkit_env}" ) ecbuild_add_test( TARGET "metkit_test_codes_decoder" CONDITION HAVE_GRIB OR HAVE_BUFR SOURCES "test_codes_decoder.cc" INCLUDES "${ECKIT_INCLUDE_DIRS}" "${ECCODES_INCLUDE_DIR}" LIBS metkit NO_AS_NEEDED ENVIRONMENT "${metkit_env}" ) ecbuild_add_test( TARGET metkit_test_odbsplitter CONDITION HAVE_ODB SOURCES test_odbsplitter.cc INCLUDES "${ECKIT_INCLUDE_DIRS}" TEST_DEPENDS metkit_get_odb_data NO_AS_NEEDED LIBS metkit ) ecbuild_add_test( TARGET metkit_test_codes_api CONDITION HAVE_GRIB OR HAVE_BUFR SOURCES test_codes_api.cc INCLUDES "${ECKIT_INCLUDE_DIRS}" ENVIRONMENT "${metkit_env}" NO_AS_NEEDED LIBS metkit) foreach( test c_api context date expand filter hypercube integer_range matcher language mars_language mars_language_strict obstype param_axis request step steprange_axis time type_levelist typesfactory ) ecbuild_add_test( TARGET "metkit_test_${test}" SOURCES "test_${test}.cc" INCLUDES "${ECKIT_INCLUDE_DIRS}" ENVIRONMENT "${metkit_env}" NO_AS_NEEDED LIBS metkit) endforeach() # Compile C test ecbuild_add_test( TARGET metkit_test_c_compiled SOURCES test_c_api.c INCLUDES "${ECKIT_INCLUDE_DIRS}" LIBS metkit NO_AS_NEEDED ENVIRONMENT "${metkit_env}") # if ( HAVE_NETCDF ) # add_subdirectory(netcdf) # endif() add_subdirectory(regressions) add_subdirectory(marsgen) add_subdirectory(mars2grib) add_subdirectory(mars2mars) add_subdirectory(tools) if( HAVE_PYTHON_METKIT_INTERFACE ) add_subdirectory(pymetkit) endif() metkit-1.20.2/tests/test_c_api.cc0000664000175000017500000002014615246725757017067 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 test_c_api.cc /// @date Dec 2024 /// @author Christopher Bradley #include #include #include #include #include "eckit/testing/Test.h" #include "eckit/types/Date.h" #include "metkit/api/metkit_c.h" #include "metkit/mars/MarsRequest.h" namespace metkit::test { using ::eckit::testing::TestException; // Wrapper around the C function calls that will throw an exception if the function fails // i.e. if the return value is not METKIT_SUCCESS void test_success(int e) { metkit_error_values_t err = static_cast(e); if (err != METKIT_SUCCESS) throw TestException("C-API error: " + std::string(metkit_get_error_string(err)), Here()); } void EXPECT_STR_EQUAL(const char* a, const char* b) { if (std::strcmp(a, b) != 0) { throw TestException("Expected: " + std::string(a) + " == " + std::string(b), Here()); } } // Fairly minimal test coverage CASE("metkit_marsrequest") { // ----------------------------------------------------------------- // Basics // ----------------------------------------------------------------- metkit_marsrequest_t* request{}; test_success(metkit_marsrequest_new(&request)); EXPECT(request); // set/get verb test_success(metkit_marsrequest_set_verb(request, "retrieve")); const char* verb{}; test_success(metkit_marsrequest_verb(request, &verb)); EXPECT_STR_EQUAL(verb, "retrieve"); // set array of values const char* dates[] = {"20200101", "20200102", "-1"}; test_success(metkit_marsrequest_set(request, "date", dates, 3)); // set single value const char* expver = "xxxx"; test_success(metkit_marsrequest_set_one(request, "expver", expver)); test_success(metkit_marsrequest_set_one(request, "param", "2t")); // check values bool has = false; test_success(metkit_marsrequest_has_param(request, "date", &has)); EXPECT(has); test_success(metkit_marsrequest_has_param(request, "random", &has)); EXPECT(!has); size_t count = 0; test_success(metkit_marsrequest_count_values(request, "date", &count)); EXPECT_EQUAL(count, 3); for (size_t i = 0; i < count; i++) { const char* value{}; test_success(metkit_marsrequest_value(request, "date", i, &value)); EXPECT_STR_EQUAL(value, dates[i]); } // ----------------------------------------------------------------- // Expand // ----------------------------------------------------------------- metkit_marsrequest_t* expandedRequest{}; test_success(metkit_marsrequest_new(&expandedRequest)); test_success(metkit_marsrequest_expand(request, false, true, expandedRequest)); // Check date expanded -1 -> yesterday test_success(metkit_marsrequest_count_values(expandedRequest, "date", &count)); EXPECT_EQUAL(count, 3); std::array dates_expanded; for (size_t i = 0; i < count; i++) { test_success(metkit_marsrequest_value(expandedRequest, "date", i, &dates_expanded[i])); } EXPECT_STR_EQUAL(dates_expanded[2], std::to_string(eckit::Date(-1).yyyymmdd()).c_str()); // check param expanded 2t -> 167 const char* param{}; test_success(metkit_marsrequest_value(expandedRequest, "param", 0, ¶m)); EXPECT_STR_EQUAL(param, "167"); // ----------------------------------------------------------------- // Merge // ----------------------------------------------------------------- metkit_marsrequest_t* req_manydates{}; test_success(metkit_marsrequest_new(&req_manydates)); const char* dates_many[] = {"19000101", "19000102", "19000103"}; test_success(metkit_marsrequest_set(req_manydates, "date", dates_many, 3)); test_success(metkit_marsrequest_merge(request, req_manydates)); test_success(metkit_marsrequest_count_values(request, "date", &count)); EXPECT_EQUAL(count, 6); // ----------------------------------------------------------------- // done metkit_marsrequest_delete(request); metkit_marsrequest_delete(expandedRequest); metkit_marsrequest_delete(req_manydates); } //----------------------------------------------------------------------------- CASE("metkit_requestiterator_t parsing") { metkit_requestiterator_t* it{}; test_success( metkit_parse_marsrequests("retrieve,date=-1,param=2t \n retrieve,date=20200102,param=2t,step=10/to/20/by/2", &it, true)); // two separate requests std::vector requests; metkit_iterator_status_t status; while ((status = metkit_requestiterator_next(it)) == METKIT_ITERATOR_SUCCESS) { metkit_marsrequest_t* req{}; test_success(metkit_marsrequest_new(&req)); EXPECT_EQUAL(metkit_requestiterator_current(it, req), METKIT_ITERATOR_SUCCESS); requests.push_back(req); } EXPECT_EQUAL(status, METKIT_ITERATOR_COMPLETE); EXPECT_EQUAL(requests.size(), 2); // check the date const char* date{}; test_success(metkit_marsrequest_value(requests[0], "date", 0, &date)); EXPECT_STR_EQUAL(date, std::to_string(eckit::Date(-1).yyyymmdd()).c_str()); // parser also calls expand test_success(metkit_marsrequest_value(requests[1], "date", 0, &date)); EXPECT_STR_EQUAL(date, "20200102"); // Check steps have been parsed size_t count = 0; test_success(metkit_marsrequest_count_values(requests[1], "step", &count)); EXPECT_EQUAL(count, 6); for (size_t i = 0; i < count; i++) { const char* step{}; test_success(metkit_marsrequest_value(requests[1], "step", i, &step)); EXPECT_STR_EQUAL(step, std::to_string(10 + i * 2).c_str()); } // cleanup metkit_requestiterator_delete(it); // NB: requests have been moved out of the iterator for (auto& req : requests) { metkit_marsrequest_delete(req); } } // Ensure that the param iterator works as expected CASE("metkit_paramiterator_t ") { std::string str = "retrieve,date=20200102,param=2t,step=10/to/20/by/2"; metkit_marsrequest_t* request{}; test_success(metkit_marsrequest_new(&request)); test_success(metkit_parse_marsrequest(str.c_str(), request, true)); metkit_paramiterator_t* it{}; test_success(metkit_marsrequest_params(request, &it)); metkit_iterator_status_t status; std::set keys; while ((status = metkit_paramiterator_next(it)) == METKIT_ITERATOR_SUCCESS) { const char* key{}; EXPECT_EQUAL(metkit_paramiterator_current(it, &key), METKIT_ITERATOR_SUCCESS); keys.insert(key); } EXPECT_EQUAL(status, METKIT_ITERATOR_COMPLETE); // Compare with C++ impl for consistency mars::MarsRequest req = mars::MarsRequest::parse(str, true); std::set keys_cpp; for (const auto& k : req.params()) { keys_cpp.insert(k); } EXPECT_EQUAL(keys, keys_cpp); } CASE("metkit_requestiterator_t 1 item") { // Edge case: verify iterator with one item works the same way. metkit_requestiterator_t* it{}; test_success(metkit_parse_marsrequests("retrieve,date=-1,param=2t", &it, true)); std::vector requests; metkit_iterator_status_t status; while ((status = metkit_requestiterator_next(it)) == METKIT_ITERATOR_SUCCESS) { metkit_marsrequest_t* req{}; test_success(metkit_marsrequest_new(&req)); EXPECT_EQUAL(metkit_requestiterator_current(it, req), METKIT_ITERATOR_SUCCESS); requests.push_back(req); } EXPECT_EQUAL(status, METKIT_ITERATOR_COMPLETE); EXPECT_EQUAL(requests.size(), 1); // cleanup metkit_requestiterator_delete(it); for (auto& req : requests) { metkit_marsrequest_delete(req); } } } // namespace metkit::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.20.2/rust/0000775000175000017500000000000015246725757014276 5ustar alastairalastairmetkit-1.20.2/rust/.cargo/0000775000175000017500000000000015246725757015447 5ustar alastairalastairmetkit-1.20.2/rust/.cargo/config.toml0000664000175000017500000000041415246725757017610 0ustar alastairalastair[build] jobs = -1 [target.'cfg(all())'] rustflags = [ "-Wclippy::all", "-Wclippy::pedantic", "-Wclippy::nursery", "-Wclippy::unwrap_used", "-Aclippy::module_name_repetitions", "-Aclippy::missing_errors_doc", ] [net] git-fetch-with-cli = true metkit-1.20.2/rust/README.md0000664000175000017500000000465015246725757015562 0ustar alastairalastair[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ecmwf/metkit/blob/develop/LICENSE) # metkit Rust bindings Rust bindings for [metkit](https://github.com/ecmwf/metkit), ECMWF's toolkit for processing meteorological requests. - **`metkit`** — safe Rust wrapper: `MarsRequest` building/expansion, `CodesHandle` GRIB access, `HyperCube`, protocol environment metadata. - **`metkit-sys`** — raw C++ bindings via [cxx](https://cxx.rs), including an auto-generated typed `Error` enum derived from metkit's exception headers. ## Requirements Build dependencies: - CMake --- For use and installation see http://www.cmake.org/ - C++17 compiler Runtime dependencies (built from source or found on the system, depending on the build mode): - eckit -- http://github.com/ecmwf/eckit - eccodes -- http://github.com/ecmwf/eccodes (for the `grib` / `bufr` features) ## Build modes Exactly one build strategy feature must be enabled: - `vendored` (default) --- clone and build the pinned metkit version from source with [ecbuild](https://github.com/ecmwf/ecbuild) (fetched automatically). - `system` --- link against an installed metkit found via CMake `find_package`; set `METKIT_DIR` or `CMAKE_PREFIX_PATH` to locate it. ## Features | Feature | Default | Description | |---|---|---| | `grib` | yes | GRIB format support (via eccodes) | | `bufr` | yes | BUFR format support (via eccodes) | | `mars2grib` | yes | MARS2GRIB encoder | | `metkit-config` | yes | Install metkit configuration files | | `netcdf` | no | NetCDF data support | | `odb` | no | ODB data support | | `experimental` | no | Experimental features | ## License Copyright 2021 European Centre for Medium-Range Weather Forecasts (ECMWF) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at > http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 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. metkit-1.20.2/rust/crates/0000775000175000017500000000000015246725757015557 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/0000775000175000017500000000000015246725757017670 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/tests/0000775000175000017500000000000015246725757021032 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/tests/version.rs0000664000175000017500000000107415246725757023067 0ustar alastairalastair//! Guard against drift between the crate version and the repo's `VERSION` //! file (cargo cannot read it dynamically). #[test] fn crate_version_matches_repo_version_file() { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../VERSION"); let Ok(version) = std::fs::read_to_string(path) else { // Not building from the repo checkout (e.g. a packaged crate). return; }; assert_eq!( version.trim(), env!("CARGO_PKG_VERSION"), "rust/crates/metkit-sys/Cargo.toml version and VERSION file are out of sync" ); } metkit-1.20.2/rust/crates/metkit-sys/src/0000775000175000017500000000000015246725757020457 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/src/lib.rs0000664000175000017500000001743615246725757021606 0ustar alastairalastair//! FFI bindings to ECMWF metkit C++ library. //! //! Provides cxx bridge to: //! - `MarsRequest` — MARS request parsing, access, modification //! - `RequestEnvironment` — environment metadata //! - `MarsLanguage` — keyword categorization use bindman::track_cpp_api; // Auto-generated metkit Error enum + From impl include!(concat!(env!("OUT_DIR"), "/metkit_exceptions.rs")); #[track_cpp_api( ("metkit/mars/MarsRequest.h", class = "MarsRequest"), ("metkit/mars/MarsLanguage.h", class = "MarsLanguage"), )] #[cxx::bridge(namespace = "metkit_bridge")] pub mod ffi { unsafe extern "C++" { include!("MetkitBridge.h"); // ==================== MarsRequest ==================== type MarsRequestWrapper; fn verb(self: &MarsRequestWrapper) -> String; fn has(self: &MarsRequestWrapper, key: &str) -> bool; fn values(self: &MarsRequestWrapper, key: &str) -> Result>; fn get_first(self: &MarsRequestWrapper, key: &str) -> Result; fn empty(self: &MarsRequestWrapper) -> bool; fn count(self: &MarsRequestWrapper) -> usize; fn matches(self: &MarsRequestWrapper, filter: &MarsRequestWrapper) -> bool; fn params(self: &MarsRequestWrapper) -> Vec; // Mutation fn set_verb(self: Pin<&mut MarsRequestWrapper>, verb: &str); fn set_value_string(self: Pin<&mut MarsRequestWrapper>, key: &str, value: &str); fn set_values(self: Pin<&mut MarsRequestWrapper>, key: &str, values: Vec); fn set_value_long(self: Pin<&mut MarsRequestWrapper>, key: &str, value: i64); fn unset_values(self: Pin<&mut MarsRequestWrapper>, key: &str); // Extract parameters by category (e.g. "postproc") fn extract( self: &MarsRequestWrapper, category: &str, ) -> Result>; // Expansion fn expand( self: &MarsRequestWrapper, inherit: bool, strict: bool, ) -> Result>; // Cross-crate ExternTypes from eckit-sys #[namespace = "eckit_bridge"] type StreamWrapper = eckit_sys::StreamWrapper; #[namespace = "eckit_bridge"] type DataHandleWrapper = eckit_sys::DataHandleWrapper; #[namespace = "eckit_bridge"] type ConfigWrapper = eckit_sys::ConfigWrapper; #[namespace = "eckit_bridge"] type MessageWrapper = eckit_sys::MessageWrapper; fn encode(self: &MarsRequestWrapper, stream: Pin<&mut StreamWrapper>) -> Result<()>; /// Create a `MarsRequestHandle` — DataHandle for Hermes protocol. fn make_handle( self: &MarsRequestWrapper, config: &ConfigWrapper, ) -> Result>; // Output fn to_json(self: &MarsRequestWrapper) -> Result; fn dump(self: &MarsRequestWrapper) -> String; // Factories #[Self = "MarsRequestWrapper"] #[must_use] fn create(verb: &str) -> UniquePtr; /// Create a `MarsRequest` from a GRIB message (reads metadata keys). #[Self = "MarsRequestWrapper"] fn from_message(msg: &MessageWrapper) -> Result>; /// Decode a `MarsRequest` from a stream. #[Self = "MarsRequestWrapper"] fn decode(stream: Pin<&mut StreamWrapper>) -> Result>; // ==================== CodesHandle ==================== type CodesHandleWrapper; // Query fn is_defined(self: &CodesHandleWrapper, key: &str) -> Result; fn is_missing(self: &CodesHandleWrapper, key: &str) -> Result; fn has(self: &CodesHandleWrapper, key: &str) -> Result; // Scalar get fn get_string(self: &CodesHandleWrapper, key: &str) -> Result; fn get_long(self: &CodesHandleWrapper, key: &str) -> Result; fn get_double(self: &CodesHandleWrapper, key: &str) -> Result; // Array get fn get_double_array(self: &CodesHandleWrapper, key: &str) -> Result>; fn get_long_array(self: &CodesHandleWrapper, key: &str) -> Result>; // Scalar set fn set_string(self: Pin<&mut CodesHandleWrapper>, key: &str, value: &str) -> Result<()>; fn set_long(self: Pin<&mut CodesHandleWrapper>, key: &str, value: i64) -> Result<()>; fn set_double(self: Pin<&mut CodesHandleWrapper>, key: &str, value: f64) -> Result<()>; // Array set fn set_double_array( self: Pin<&mut CodesHandleWrapper>, key: &str, values: &[f64], ) -> Result<()>; // Missing fn set_missing(self: Pin<&mut CodesHandleWrapper>, key: &str) -> Result<()>; // Size and data fn value_count(self: &CodesHandleWrapper, key: &str) -> Result; fn message_size(self: &CodesHandleWrapper) -> Result; fn message_data(self: &CodesHandleWrapper) -> Result<&[u8]>; // Clone #[rust_name = "clone_handle"] fn clone(self: &CodesHandleWrapper) -> Result>; // Factories — static methods on CodesHandleWrapper, called from Rust as // `ffi::CodesHandleWrapper::from_message(...)` etc. #[Self = "CodesHandleWrapper"] fn from_message(data: &[u8]) -> Result>; #[Self = "CodesHandleWrapper"] fn from_file(path: &str) -> Result>; #[Self = "CodesHandleWrapper"] fn from_file_at_offset(path: &str, offset: i64) -> Result>; #[Self = "CodesHandleWrapper"] fn from_sample(sample: &str) -> Result>; // ==================== HyperCube ==================== type HyperCubeWrapper; fn size(self: &HyperCubeWrapper) -> usize; fn count(self: &HyperCubeWrapper) -> usize; fn count_vacant(self: &HyperCubeWrapper) -> usize; fn contains(self: &HyperCubeWrapper, request: &MarsRequestWrapper) -> Result; fn clear(self: Pin<&mut HyperCubeWrapper>, request: &MarsRequestWrapper) -> Result; fn field_ordinal(self: &HyperCubeWrapper, request: &MarsRequestWrapper) -> Result; #[Self = "HyperCubeWrapper"] fn create(request: &MarsRequestWrapper) -> Result>; // ==================== ParsedRequests ==================== type ParsedRequestsWrapper; fn count(self: &ParsedRequestsWrapper) -> usize; fn at(self: &ParsedRequestsWrapper, index: usize) -> Result>; #[Self = "ParsedRequestsWrapper"] fn parse(input: &str, strict: bool) -> Result>; /// Raw parse without verb validation — uses `MarsParser` directly. #[Self = "ParsedRequestsWrapper"] fn parse_raw(input: &str) -> Result>; // ==================== RequestEnvironment ==================== type RequestEnvironmentWrapper; #[Self = "RequestEnvironmentWrapper"] fn initialise(keys: Vec, values: Vec); #[Self = "RequestEnvironmentWrapper"] fn request() -> Result>; // ==================== MarsLanguage ==================== type MarsLanguageWrapper; fn sink_keywords(self: &MarsLanguageWrapper) -> Vec; fn is_data(self: &MarsLanguageWrapper, keyword: &str) -> bool; #[Self = "MarsLanguageWrapper"] fn create(verb: &str) -> Result>; } } // Public re-exports pub use cxx::{Exception, UniquePtr}; pub use ffi::*; metkit-1.20.2/rust/crates/metkit-sys/cpp/0000775000175000017500000000000015246725757020452 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/cpp/MetkitBridge.h0000664000175000017500000000062315246725757023176 0ustar alastairalastair// metkit C++ bridge for Rust FFI — umbrella header pulled in by the // cxx-generated bridge (`include!("MetkitBridge.h")` in lib.rs) and by // downstream `-sys` crates. Real declarations live in the per-topic headers // below. #pragma once #include "CodesHandle.h" #include "HyperCube.h" #include "MarsLanguage.h" #include "MarsRequest.h" #include "ParsedRequests.h" #include "RequestEnvironment.h" metkit-1.20.2/rust/crates/metkit-sys/cpp/CodesHandle.cc0000664000175000017500000000753715246725757023146 0ustar alastairalastair// metkit CodesHandle bridge — implementation. #include "metkit_exceptions.h" #include "CodesHandle.h" namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- bool CodesHandleWrapper::is_defined(rust::Str key) const { return handle_->isDefined(std::string(key)); } bool CodesHandleWrapper::is_missing(rust::Str key) const { return handle_->isMissing(std::string(key)); } bool CodesHandleWrapper::has(rust::Str key) const { return handle_->has(std::string(key)); } rust::String CodesHandleWrapper::get_string(rust::Str key) const { return rust::String(handle_->getString(std::string(key))); } int64_t CodesHandleWrapper::get_long(rust::Str key) const { return static_cast(handle_->getLong(std::string(key))); } double CodesHandleWrapper::get_double(rust::Str key) const { return handle_->getDouble(std::string(key)); } rust::Vec CodesHandleWrapper::get_double_array(rust::Str key) const { auto vec = handle_->getDoubleArray(std::string(key)); rust::Vec result; result.reserve(vec.size()); for (double v : vec) { result.push_back(v); } return result; } rust::Vec CodesHandleWrapper::get_long_array(rust::Str key) const { auto vec = handle_->getLongArray(std::string(key)); rust::Vec result; result.reserve(vec.size()); for (long v : vec) { result.push_back(static_cast(v)); } return result; } void CodesHandleWrapper::set_string(rust::Str key, rust::Str value) { handle_->set(std::string(key), std::string(value)); } void CodesHandleWrapper::set_long(rust::Str key, int64_t value) { handle_->set(std::string(key), static_cast(value)); } void CodesHandleWrapper::set_double(rust::Str key, double value) { handle_->set(std::string(key), value); } void CodesHandleWrapper::set_double_array(rust::Str key, rust::Slice values) { handle_->set(std::string(key), metkit::codes::Span(values.data(), values.size())); } void CodesHandleWrapper::set_missing(rust::Str key) { handle_->setMissing(std::string(key)); } size_t CodesHandleWrapper::value_count(rust::Str key) const { return handle_->size(std::string(key)); } size_t CodesHandleWrapper::message_size() const { return handle_->messageSize(); } rust::Slice CodesHandleWrapper::message_data() const { auto span = handle_->messageData(); return {span.data(), span.size()}; } std::unique_ptr CodesHandleWrapper::clone() const { return std::make_unique(handle_->clone()); } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr CodesHandleWrapper::from_message(rust::Slice data) { return std::make_unique( metkit::codes::codesHandleFromMessageCopy(metkit::codes::Span(data.data(), data.size()))); } std::unique_ptr CodesHandleWrapper::from_file(rust::Str path) { return std::make_unique( metkit::codes::codesHandleFromFile(std::string(path), metkit::codes::Product::GRIB)); } std::unique_ptr CodesHandleWrapper::from_file_at_offset(rust::Str path, int64_t offset) { return std::make_unique( metkit::codes::codesHandleFromFile(std::string(path), metkit::codes::Product::GRIB, offset)); } std::unique_ptr CodesHandleWrapper::from_sample(rust::Str sample) { return std::make_unique(metkit::codes::codesHandleFromSample(std::string(sample))); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/HyperCube.cc0000664000175000017500000000262115246725757022650 0ustar alastairalastair// metkit HyperCube bridge — implementation. #include "metkit_exceptions.h" #include "HyperCube.h" namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- HyperCubeWrapper::HyperCubeWrapper(const MarsRequestWrapper& request) : cube_(std::make_unique(request.inner())) {} size_t HyperCubeWrapper::size() const { return cube_->size(); } size_t HyperCubeWrapper::count() const { return cube_->count(); } size_t HyperCubeWrapper::count_vacant() const { return cube_->countVacant(); } bool HyperCubeWrapper::contains(const MarsRequestWrapper& request) const { return cube_->contains(request.inner()); } bool HyperCubeWrapper::clear(const MarsRequestWrapper& request) { return cube_->clear(request.inner()); } size_t HyperCubeWrapper::field_ordinal(const MarsRequestWrapper& request) const { return cube_->fieldOrdinal(request.inner()); } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr HyperCubeWrapper::create(const MarsRequestWrapper& request) { return std::make_unique(request); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/CodesHandle.h0000664000175000017500000000445415246725757023003 0ustar alastairalastair// metkit CodesHandle bridge — wraps `metkit::codes::CodesHandle`. #pragma once #include "metkit/codes/api/CodesAPI.h" #include "rust/cxx.h" #include #include #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Wraps `metkit::codes::CodesHandle` for Rust FFI. class CodesHandleWrapper { std::unique_ptr handle_; public: explicit CodesHandleWrapper(std::unique_ptr h) : handle_(std::move(h)) {} // Query bool is_defined(rust::Str key) const; bool is_missing(rust::Str key) const; bool has(rust::Str key) const; // Scalar get rust::String get_string(rust::Str key) const; int64_t get_long(rust::Str key) const; double get_double(rust::Str key) const; // Array get rust::Vec get_double_array(rust::Str key) const; rust::Vec get_long_array(rust::Str key) const; // Scalar set void set_string(rust::Str key, rust::Str value); void set_long(rust::Str key, int64_t value); void set_double(rust::Str key, double value); // Array set void set_double_array(rust::Str key, rust::Slice values); // Missing void set_missing(rust::Str key); // Size and data size_t value_count(rust::Str key) const; size_t message_size() const; rust::Slice message_data() const; /// Deep-copy this handle (wraps `metkit::codes::CodesHandle::clone()`). std::unique_ptr clone() const; // Access underlying const metkit::codes::CodesHandle& inner() const { return *handle_; } metkit::codes::CodesHandle& inner() { return *handle_; } // ============== Factories ============== static std::unique_ptr from_message(rust::Slice data); static std::unique_ptr from_file(rust::Str path); static std::unique_ptr from_file_at_offset(rust::Str path, int64_t offset); static std::unique_ptr from_sample(rust::Str sample); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/ParsedRequests.cc0000664000175000017500000000304515246725757023735 0ustar alastairalastair// metkit ParsedRequests bridge — implementation. #include "metkit_exceptions.h" #include "ParsedRequests.h" #include "metkit/mars/MarsParser.h" #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- ParsedRequestsWrapper::ParsedRequestsWrapper(rust::Str input, bool strict) { auto str = std::string(input); std::istringstream iss(str); requests_ = metkit::mars::MarsRequest::parse(iss, strict); } size_t ParsedRequestsWrapper::count() const { return requests_.size(); } std::unique_ptr ParsedRequestsWrapper::at(size_t index) const { return std::make_unique(requests_.at(index)); } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr ParsedRequestsWrapper::parse(rust::Str input, bool strict) { return std::make_unique(input, strict); } std::unique_ptr ParsedRequestsWrapper::parse_raw(rust::Str input) { auto str = std::string(input); std::istringstream iss(str); auto parsed = metkit::mars::MarsParser(iss).parse(); auto wrapper = std::make_unique(); for (auto& r : parsed) { wrapper->push(r); } return wrapper; } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/RequestEnvironment.cc0000664000175000017500000000203715246725757024640 0ustar alastairalastair// metkit RequestEnvironment bridge — implementation. #include "metkit_exceptions.h" #include "RequestEnvironment.h" #include "metkit/mars/RequestEnvironment.h" #include #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- void RequestEnvironmentWrapper::initialise(rust::Vec keys, rust::Vec values) { std::map env; for (size_t i = 0; i < keys.size() && i < values.size(); ++i) { env[std::string(keys[i])] = std::string(values[i]); } metkit::mars::RequestEnvironment::initialize(env); } std::unique_ptr RequestEnvironmentWrapper::request() { const auto& env = metkit::mars::RequestEnvironment::instance(); return std::make_unique(env.request()); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/MarsRequest.cc0000664000175000017500000001033515246725757023236 0ustar alastairalastair// metkit MarsRequest bridge — implementation. #include "metkit_exceptions.h" #include "MarsRequest.h" #include "eckit/log/JSON.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsRequestHandle.h" #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- rust::String MarsRequestWrapper::verb() const { return rust::String(request_.verb()); } bool MarsRequestWrapper::has(rust::Str key) const { return request_.has(std::string(key)); } rust::Vec MarsRequestWrapper::values(rust::Str key) const { const auto& vals = request_.values(std::string(key)); rust::Vec result; result.reserve(vals.size()); for (const auto& v : vals) { result.push_back(rust::String(v)); } return result; } rust::String MarsRequestWrapper::get_first(rust::Str key) const { return rust::String(request_[std::string(key)]); } bool MarsRequestWrapper::empty() const { return request_.empty(); } size_t MarsRequestWrapper::count() const { return request_.count(); } bool MarsRequestWrapper::matches(const MarsRequestWrapper& filter) const { return request_.matches(filter.request_); } rust::Vec MarsRequestWrapper::params() const { auto p = request_.params(); rust::Vec result; result.reserve(p.size()); for (const auto& name : p) { result.push_back(rust::String(name)); } return result; } void MarsRequestWrapper::set_verb(rust::Str verb) { request_.verb(std::string(verb)); } void MarsRequestWrapper::set_value_string(rust::Str key, rust::Str value) { request_.setValue(std::string(key), std::string(value)); } void MarsRequestWrapper::set_values(rust::Str key, rust::Vec values) { std::vector vec; vec.reserve(values.size()); for (const auto& v : values) { vec.emplace_back(std::string(v)); } request_.values(std::string(key), vec); } void MarsRequestWrapper::set_value_long(rust::Str key, int64_t value) { request_.setValue(std::string(key), static_cast(value)); } void MarsRequestWrapper::unset_values(rust::Str key) { request_.unsetValues(std::string(key)); } std::unique_ptr MarsRequestWrapper::extract(rust::Str category) const { return std::make_unique(request_.extract(std::string(category))); } std::unique_ptr MarsRequestWrapper::expand(bool inherit, bool strict) const { metkit::mars::MarsLanguage lang(request_.verb()); auto expanded = lang.expand(request_, inherit, strict); return std::make_unique(std::move(expanded)); } rust::String MarsRequestWrapper::to_json() const { std::ostringstream oss; eckit::JSON json(oss); json << request_; return rust::String(oss.str()); } rust::String MarsRequestWrapper::dump() const { std::ostringstream oss; request_.dump(oss); return rust::String(oss.str()); } void MarsRequestWrapper::encode(eckit_bridge::StreamWrapper& stream) const { stream.inner() << request_; } std::unique_ptr MarsRequestWrapper::make_handle( const eckit_bridge::ConfigWrapper& config) const { return std::make_unique( new metkit::mars::MarsRequestHandle(request_, config.inner())); } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr MarsRequestWrapper::create(rust::Str verb) { return std::make_unique(metkit::mars::MarsRequest(std::string(verb))); } std::unique_ptr MarsRequestWrapper::from_message(const eckit_bridge::MessageWrapper& msg) { return std::make_unique(metkit::mars::MarsRequest(msg.inner())); } std::unique_ptr MarsRequestWrapper::decode(eckit_bridge::StreamWrapper& stream) { return std::make_unique(metkit::mars::MarsRequest(stream.inner())); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/MarsLanguage.h0000664000175000017500000000164715246725757023201 0ustar alastairalastair// metkit MarsLanguage bridge — wraps `metkit::mars::MarsLanguage`. #pragma once #include "metkit/mars/MarsLanguage.h" #include "rust/cxx.h" #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Wraps `metkit::mars::MarsLanguage` for Rust FFI. class MarsLanguageWrapper { std::unique_ptr lang_; public: explicit MarsLanguageWrapper(rust::Str verb); rust::Vec sink_keywords() const; bool is_data(rust::Str keyword) const; // ============== Factories ============== /// Construct a MarsLanguage for the given verb. static std::unique_ptr create(rust::Str verb); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/ParsedRequests.h0000664000175000017500000000240715246725757023600 0ustar alastairalastair// metkit MarsRequest batch-parse bridge — holds parsed requests. #pragma once #include "MarsRequest.h" #include "metkit/mars/MarsRequest.h" #include "rust/cxx.h" #include #include #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Holds parsed requests — parse once, iterate by index. class ParsedRequestsWrapper { std::vector requests_; public: ParsedRequestsWrapper() = default; ParsedRequestsWrapper(rust::Str input, bool strict); void push(const metkit::mars::MarsRequest& r) { requests_.push_back(r); } size_t count() const; std::unique_ptr at(size_t index) const; // ============== Factories ============== /// Parse MARS requests with verb validation. static std::unique_ptr parse(rust::Str input, bool strict); /// Raw parse without verb validation — uses `MarsParser` directly. static std::unique_ptr parse_raw(rust::Str input); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/RequestEnvironment.h0000664000175000017500000000170615246725757024504 0ustar alastairalastair// metkit RequestEnvironment bridge — wraps the singleton // `metkit::mars::RequestEnvironment`. #pragma once #include "MarsRequest.h" #include "rust/cxx.h" #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Wraps the singleton `metkit::mars::RequestEnvironment`. All members are /// static — the underlying C++ type is itself a process-wide singleton. class RequestEnvironmentWrapper { public: /// Initialise the global request environment from a list of key/value pairs. static void initialise(rust::Vec keys, rust::Vec values); /// Return the current environment's MARS request. static std::unique_ptr request(); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/HyperCube.h0000664000175000017500000000237115246725757022514 0ustar alastairalastair// metkit HyperCube bridge — wraps `metkit::hypercube::HyperCube`. #pragma once #include "MarsRequest.h" #include "metkit/hypercube/HyperCube.h" #include "rust/cxx.h" #include #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Wraps `metkit::hypercube::HyperCube` for Rust FFI. class HyperCubeWrapper { std::unique_ptr cube_; public: explicit HyperCubeWrapper(const MarsRequestWrapper& request); size_t size() const; size_t count() const; size_t count_vacant() const; bool contains(const MarsRequestWrapper& request) const; bool clear(const MarsRequestWrapper& request); size_t field_ordinal(const MarsRequestWrapper& request) const; // Access underlying const metkit::hypercube::HyperCube& inner() const { return *cube_; } // ============== Factories ============== /// Build a HyperCube from a MarsRequest. static std::unique_ptr create(const MarsRequestWrapper& request); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/MarsRequest.h0000664000175000017500000000501615246725757023100 0ustar alastairalastair// metkit MarsRequest bridge — wraps `metkit::mars::MarsRequest`. #pragma once #include "EckitBridge.h" #include "metkit/mars/MarsRequest.h" #include "rust/cxx.h" #include #include namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- /// Wraps `metkit::mars::MarsRequest` for Rust FFI. class MarsRequestWrapper { metkit::mars::MarsRequest request_; public: MarsRequestWrapper() = default; explicit MarsRequestWrapper(metkit::mars::MarsRequest r) : request_(std::move(r)) {} // Query rust::String verb() const; bool has(rust::Str key) const; rust::Vec values(rust::Str key) const; rust::String get_first(rust::Str key) const; bool empty() const; size_t count() const; bool matches(const MarsRequestWrapper& filter) const; rust::Vec params() const; // Mutation void set_verb(rust::Str verb); void set_value_string(rust::Str key, rust::Str value); void set_values(rust::Str key, rust::Vec values); void set_value_long(rust::Str key, int64_t value); void unset_values(rust::Str key); // Extract parameters by category std::unique_ptr extract(rust::Str category) const; // Expansion std::unique_ptr expand(bool inherit, bool strict) const; // Stream serialization void encode(eckit_bridge::StreamWrapper& stream) const; // Output rust::String to_json() const; rust::String dump() const; /// Create a `MarsRequestHandle` — DataHandle for Hermes retrieve/list/get. std::unique_ptr make_handle(const eckit_bridge::ConfigWrapper& config) const; // Access underlying const metkit::mars::MarsRequest& inner() const { return request_; } metkit::mars::MarsRequest& inner() { return request_; } // ============== Factories ============== /// Create a MarsRequest with the given verb. static std::unique_ptr create(rust::Str verb); /// Build a MarsRequest from an eckit message. static std::unique_ptr from_message(const eckit_bridge::MessageWrapper& msg); /// Decode a MarsRequest from a stream. static std::unique_ptr decode(eckit_bridge::StreamWrapper& stream); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/cpp/MarsLanguage.cc0000664000175000017500000000226715246725757023336 0ustar alastairalastair// metkit MarsLanguage bridge — implementation. #include "metkit_exceptions.h" #include "MarsLanguage.h" namespace metkit_bridge { //---------------------------------------------------------------------------------------------------------------------- MarsLanguageWrapper::MarsLanguageWrapper(rust::Str verb) : lang_(std::make_unique(std::string(verb))) {} rust::Vec MarsLanguageWrapper::sink_keywords() const { const auto& kw = lang_->sinkKeywords(); rust::Vec result; result.reserve(kw.size()); for (const auto& k : kw) { result.push_back(rust::String(k)); } return result; } bool MarsLanguageWrapper::is_data(rust::Str keyword) const { return lang_->isData(std::string(keyword)); } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr MarsLanguageWrapper::create(rust::Str verb) { return std::make_unique(verb); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit_bridge metkit-1.20.2/rust/crates/metkit-sys/build.rs0000664000175000017500000003054315246725757021342 0ustar alastairalastair//! Build script for metkit-sys //! //! Supports two build modes: //! - `vendored` (default): Clone and build metkit from source using ecbuild //! - `system`: Use `CMake` `find_package` to find system-installed metkit fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=METKIT_DIR"); println!("cargo:rerun-if-env-changed=CMAKE_PREFIX_PATH"); println!("cargo:rerun-if-env-changed=DOCS_RS"); if bindman_utils::is_docs_rs() { generate_exceptions(&docs_source_include()); return; } bindman_utils::validate_build_mode(cfg!(feature = "system"), cfg!(feature = "vendored")); let include = if cfg!(feature = "system") { build_system() } else { build_vendored() }; generate_exceptions(&include); build_cxx_bridge(&include); let crate_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); bindman_build::check_cpp_api(&include, &crate_dir.join("src/lib.rs")); // Export cpp directory for downstream crates (MetkitBridge.h) println!("cargo:cpp_dir={}", crate_dir.join("cpp").display()); } /// Generate `metkit_exceptions.{h,rs}`. The bridge exposes `CodesHandleWrapper` /// methods that can throw `metkit::codes::CodesException` / `CodesWrongLength`; /// nothing in the bridge currently calls `mars2grib`, so its exception headers /// aren't a source here. eckit's exceptions are inherited from eckit-sys. fn generate_exceptions(include: &std::path::Path) { let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set")); let own = vec![bindman_build::ExceptionSource { header: include.join("metkit/codes/api/CodesTypes.h"), include_path: "metkit/codes/api/CodesTypes.h".to_string(), cpp_namespace: "metkit::codes".to_string(), message_prefix: "metkit".to_string(), base_class: "eckit::Exception".to_string(), recursive: true, }]; let inherited = bindman_build::collect_dep_exception_sources(); bindman_build::generate_exception_bridge(&bindman_build::ExceptionBridgeConfig { primary_namespace: "metkit", out_dir: &out_dir, own: &own, inherited: &inherited, }); bindman_build::publish_exception_sources(&own, &out_dir); } /// Header root for docs builds (`DOCS_RS=1`), where the native metkit build /// is skipped. `docs-headers/` mirrors the include-tree layout with a symlink /// into this repo's `src/`; `cargo package` embeds the linked file's content. fn docs_source_include() -> std::path::PathBuf { std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")) .join("docs-headers") } /// Compile the CXX bridge. fn build_cxx_bridge(include: &std::path::Path) { let crate_dir = std::path::PathBuf::from( std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"), ); let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set")); let eckit_include = std::env::var("DEP_ECKIT_SYS_INCLUDE") .expect("DEP_ECKIT_SYS_INCLUDE not set — eckit-sys must be a dependency"); // metkit >= 1.19 public headers (CodesAPI.h) include eccodes.h directly. let eccodes_include = std::env::var("DEP_ECCODES_SYS_INCLUDE") .expect("DEP_ECCODES_SYS_INCLUDE not set — eccodes-sys must be a dependency"); println!("cargo:rerun-if-changed=cpp/MetkitBridge.h"); println!("cargo:rerun-if-changed=cpp/MarsRequest.h"); println!("cargo:rerun-if-changed=cpp/MarsRequest.cc"); println!("cargo:rerun-if-changed=cpp/CodesHandle.h"); println!("cargo:rerun-if-changed=cpp/CodesHandle.cc"); println!("cargo:rerun-if-changed=cpp/HyperCube.h"); println!("cargo:rerun-if-changed=cpp/HyperCube.cc"); println!("cargo:rerun-if-changed=cpp/ParsedRequests.h"); println!("cargo:rerun-if-changed=cpp/ParsedRequests.cc"); println!("cargo:rerun-if-changed=cpp/MarsLanguage.h"); println!("cargo:rerun-if-changed=cpp/MarsLanguage.cc"); println!("cargo:rerun-if-changed=cpp/RequestEnvironment.h"); println!("cargo:rerun-if-changed=cpp/RequestEnvironment.cc"); let mut build = cxx_build::bridge("src/lib.rs"); build .file(crate_dir.join("cpp/MarsRequest.cc")) .file(crate_dir.join("cpp/CodesHandle.cc")) .file(crate_dir.join("cpp/HyperCube.cc")) .file(crate_dir.join("cpp/ParsedRequests.cc")) .file(crate_dir.join("cpp/MarsLanguage.cc")) .file(crate_dir.join("cpp/RequestEnvironment.cc")) .include(include) .include(crate_dir.join("cpp")) .include(&eckit_include) .include(&eccodes_include) .include(&out_dir); // for metkit_exceptions.h (generated) // Include eckit's cpp dir for EckitBridge.h (needed for StreamWrapper) if let Ok(eckit_cpp_dir) = std::env::var("DEP_ECKIT_SYS_CPP_DIR") { build.include(&eckit_cpp_dir); } build .flag_if_supported("-std=c++17") .compile("metkit_sys_bridge"); bindman_utils::link_cpp_stdlib(); } #[cfg(feature = "system")] fn build_system() -> std::path::PathBuf { // Minimum supported system version; the crate version tracks the vendored release. let (root, include, lib_dir) = bindman_utils::cmake_find_package("metkit", "1.18.1"); println!("cargo:rustc-link-search=native={}", lib_dir.display()); println!("cargo:rustc-link-lib=dylib=metkit"); bindman_utils::link_cpp_stdlib(); // Export for downstream crates println!("cargo:root={}", root.display()); println!("cargo:include={}", include.display()); include } #[cfg(not(feature = "system"))] fn build_system() -> std::path::PathBuf { unreachable!("build_system called without system feature"); } /// Locate the metkit C++ sources: prefer the in-tree checkout when the crate /// lives inside the metkit repository (path or git dependency), falling back /// to cloning the release tag (packaged crates.io case). #[cfg(feature = "vendored")] fn resolve_metkit_src(src_dir: &std::path::Path) -> std::path::PathBuf { const METKIT_REPO: &str = "https://github.com/ecmwf/metkit.git"; const METKIT_TAG: &str = env!("CARGO_PKG_VERSION"); let manifest_dir = std::path::PathBuf::from( std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"), ); if let Some(root) = manifest_dir.ancestors().nth(3) && root.join("CMakeLists.txt").exists() && root.join("VERSION").exists() && root.join("src/metkit").is_dir() { eprintln!("metkit-sys: building in-tree sources at {}", root.display()); // Retrigger on C++ source edits. println!("cargo:rerun-if-changed={}", root.join("src").display()); println!( "cargo:rerun-if-changed={}", root.join("CMakeLists.txt").display() ); println!("cargo:rerun-if-changed={}", root.join("VERSION").display()); // Diverging is fine mid-development, but should never go unnoticed. let tree_version = std::fs::read_to_string(root.join("VERSION")) .map(|s| s.trim().to_string()) .unwrap_or_default(); if tree_version != METKIT_TAG { println!( "cargo:warning=metkit-sys {METKIT_TAG} is building in-tree metkit {tree_version} (versions differ)" ); } return root.to_path_buf(); } bindman_utils::git_clone(METKIT_REPO, METKIT_TAG, &src_dir.join("metkit")) } /// Build metkit from source using ecbuild #[cfg(feature = "vendored")] #[allow(clippy::too_many_lines)] fn build_vendored() -> std::path::PathBuf { use std::env; use std::fs; use std::path::PathBuf; use std::process::Command; const ECBUILD_REPO: &str = "https://github.com/ecmwf/ecbuild.git"; const ECBUILD_TAG: &str = "3.13.1"; let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); let src_dir = out_dir.join("src"); let build_dir = out_dir.join("build"); let install_dir = out_dir.join("install"); fs::create_dir_all(&src_dir).expect("Failed to create src directory"); fs::create_dir_all(&build_dir).expect("Failed to create build directory"); // Get dependency paths let eckit_root = env::var("DEP_ECKIT_SYS_ROOT") .expect("DEP_ECKIT_SYS_ROOT not set - eckit-sys must be a dependency"); let eccodes_root = env::var("DEP_ECCODES_SYS_ROOT") .expect("DEP_ECCODES_SYS_ROOT not set - eccodes-sys must be a dependency"); let ecbuild_src = bindman_utils::git_clone(ECBUILD_REPO, ECBUILD_TAG, &src_dir.join("ecbuild")); let metkit_src = resolve_metkit_src(&src_dir); // cmake hard-errors if the source path recorded in CMakeCache.txt changes // (e.g. cloned <-> in-tree); wipe the build dir when it is stale. if let Ok(cache) = fs::read_to_string(build_dir.join("CMakeCache.txt")) { let cached_src = cache .lines() .find_map(|l| l.strip_prefix("CMAKE_HOME_DIRECTORY:INTERNAL=")); if cached_src != metkit_src.to_str() { fs::remove_dir_all(&build_dir).expect("Failed to remove stale metkit build directory"); fs::create_dir_all(&build_dir).expect("Failed to create build directory"); } } let ecbuild_bin = ecbuild_src.join("bin/ecbuild"); let num_jobs = bindman_utils::build_parallelism(); let cmake_prefix_path = format!("{eckit_root};{eccodes_root}"); // Build metkit let mut cmd = Command::new(&ecbuild_bin); cmd.current_dir(&build_dir) .arg(format!("--prefix={}", install_dir.display())) .arg("--") .arg(&metkit_src) .arg(format!("-DCMAKE_PREFIX_PATH={cmake_prefix_path}")) .arg(format!( "-DCMAKE_BUILD_TYPE={}", bindman_utils::cmake_build_type() )) // Always disabled (no features) .arg("-DENABLE_TESTS=OFF") .arg("-DENABLE_DOCS=OFF") .arg("-DENABLE_BUILD_TOOLS=OFF") .arg("-DENABLE_MARS2GRIB_PYTHON=OFF"); // Feature-gated options cmd.arg(format!( "-DENABLE_GRIB={}", bindman_utils::on_off(cfg!(feature = "grib")) )); cmd.arg(format!( "-DENABLE_BUFR={}", bindman_utils::on_off(cfg!(feature = "bufr")) )); cmd.arg(format!( "-DENABLE_NETCDF={}", bindman_utils::on_off(cfg!(feature = "netcdf")) )); cmd.arg(format!( "-DENABLE_ODB={}", bindman_utils::on_off(cfg!(feature = "odb")) )); cmd.arg(format!( "-DENABLE_MARS2GRIB={}", bindman_utils::on_off(cfg!(feature = "mars2grib")) )); cmd.arg(format!( "-DENABLE_METKIT_CONFIG={}", bindman_utils::on_off(cfg!(feature = "metkit-config")) )); cmd.arg(format!( "-DENABLE_EXPERIMENTAL={}", bindman_utils::on_off(cfg!(feature = "experimental")) )); cmd.arg(format!( "-DENABLE_FAIL_ON_CCSDS={}", bindman_utils::on_off(cfg!(feature = "fail-on-ccsds")) )); // Use @rpath install names — the leaf binary sets rpaths via bindman_utils::emit_rpaths() #[cfg(target_os = "macos")] cmd.arg("-DCMAKE_INSTALL_NAME_DIR=@rpath"); bindman_utils::run_command(&mut cmd, "ecbuild configure metkit"); bindman_utils::run_command( Command::new("cmake") .args(["--build", ".", "--parallel", &num_jobs]) .current_dir(&build_dir), "cmake build metkit", ); bindman_utils::run_command( Command::new("cmake") .args(["--install", "."]) .current_dir(&build_dir), "cmake install metkit", ); // Copy share directory (contains language.yaml needed at runtime) let share_src = build_dir.join("share"); let share_dst = install_dir.join("share"); if share_src.exists() { bindman_utils::copy_dir_all(&share_src, &share_dst) .expect("Failed to copy share directory"); } // Link directives let lib_dir = bindman_utils::resolve_lib_dir(&install_dir); println!("cargo:rustc-link-search=native={}", lib_dir.display()); println!("cargo:rustc-link-lib=dylib=metkit"); bindman_utils::link_cpp_stdlib(); // Export for downstream crates let include = install_dir.join("include"); println!("cargo:root={}", install_dir.display()); println!("cargo:include={}", include.display()); include } #[cfg(not(feature = "vendored"))] fn build_vendored() -> std::path::PathBuf { unreachable!("build_vendored called without vendored feature"); } metkit-1.20.2/rust/crates/metkit-sys/docs-headers/0000775000175000017500000000000015246725757022231 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/docs-headers/metkit/0000775000175000017500000000000015246725757023526 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/docs-headers/metkit/codes/0000775000175000017500000000000015246725757024623 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/docs-headers/metkit/codes/api/0000775000175000017500000000000015246725757025374 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/docs-headers/metkit/codes/api/CodesTypes.h0000777000175000017500000000000015246725757037724 2../../../../../../../src/metkit/codes/api/CodesTypes.hustar alastairalastairmetkit-1.20.2/rust/crates/metkit-sys/README.md0000664000175000017500000000336315246725757021154 0ustar alastairalastair# metkit-sys Low-level Rust bindings to ECMWF's [metkit](https://github.com/ecmwf/metkit) C++ library. This crate provides raw FFI bindings using [cxx](https://cxx.rs/). For a safe, ergonomic API, use the higher-level `metkit` crate (forthcoming). ## Cargo build features These flags control what the underlying C++ metkit library is compiled with. Each maps to a `-DENABLE_=ON/OFF` flag in upstream [`metkit/CMakeLists.txt`](https://github.com/ecmwf/metkit/blob/develop/CMakeLists.txt). ### Build strategy (mutually exclusive) - `vendored` (default) - Clone and build metkit (and its eckit and ecCodes dependencies) from source. - `system` - Link against a system-installed metkit, located via CMake `find_package(metkit)`. Honours `METKIT_DIR` and `CMAKE_PREFIX_PATH`. ### Format support (enabled by default) - `grib` - GRIB format support. Pulls in `eccodes-sys/product-grib`. - `bufr` - BUFR format support. Pulls in `eccodes-sys/product-bufr`. ### Format support (off by default; require external libraries) - `netcdf` - NetCDF data support. - `odb` - ODB data support (requires `odc`). ### Encoding (enabled by default) - `mars2grib` - MARS2GRIB encoder. Pulls in `eckit-sys/geo-codec-grids` for ORCA / FESOM / ICON grid support. ### Configuration (enabled by default) - `metkit-config` - Install metkit configuration files (e.g. `language.yaml`). ### Other (off by default) - `experimental` - Experimental upstream features. - `fail-on-ccsds` - Fail when encountering CCSDS-encoded messages. ## Environment variables - `METKIT_DIR` - Install prefix of a metkit build, used by `system` mode. - `CMAKE_PREFIX_PATH` - Additional CMake search paths. - `DOCS_RS` - When set, the build script becomes a no-op (for docs.rs). ## License Apache-2.0 metkit-1.20.2/rust/crates/metkit-sys/Cargo.toml0000664000175000017500000000310015246725757021612 0ustar alastairalastair[package] name = "metkit-sys" version = "1.20.0" edition.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true readme.workspace = true keywords.workspace = true categories.workspace = true description = "C++ bindings to ECMWF metkit library using cxx" links = "metkit_sys" build = "build.rs" [features] # Defaults: core features default = ["vendored", "grib", "bufr", "mars2grib", "metkit-config"] # Build strategy (mutually exclusive) vendored = ["eckit-sys/vendored", "eccodes-sys/vendored", "eccodes-sys/eccodes-threads"] system = ["eckit-sys/system", "eccodes-sys/system"] # Format support (CMake default: ON) grib = ["eccodes-sys/product-grib"] # GRIB format support (requires eccodes) bufr = ["eccodes-sys/product-bufr"] # BUFR format support (requires eccodes) netcdf = [] # NetCDF data support (requires NetCDF library) odb = [] # ODB data support (requires odc) # Encoding (CMake default: ON) mars2grib = ["eckit-sys/geo-codec-grids"] # MARS2GRIB encoder (requires eckit geo codec grids for ORCA/FESOM/ICON) # Configuration (CMake default: ON) metkit-config = [] # Install metkit configuration files # Other (CMake default: OFF) experimental = [] # Experimental features fail-on-ccsds = [] # Fail on CCSDS [dependencies] cxx.workspace = true eckit-sys = { workspace = true, default-features = false } eccodes-sys = { workspace = true, default-features = false } bindman.workspace = true [build-dependencies] cxx-build.workspace = true bindman-utils.workspace = true bindman-build.workspace = true [package.metadata.docs.rs] metkit-1.20.2/rust/crates/metkit/0000775000175000017500000000000015246725757017054 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit/src/0000775000175000017500000000000015246725757017643 5ustar alastairalastairmetkit-1.20.2/rust/crates/metkit/src/hypercube.rs0000664000175000017500000000513215246725757022200 0ustar alastairalastair//! `HyperCube` — N-dimensional field indexing and counting. //! //! Wraps `metkit::hypercube::HyperCube`. Used for: //! - Counting expected fields in a request (`size()`) //! - Tracking which fields have been retrieved (`clear()`, `count()`) //! - Ordering fields in a fieldset (`field_ordinal()`) use crate::request::{Expanded, MarsRequest, RequestState}; /// N-dimensional field grid derived from a `MarsRequest`. pub struct HyperCube { inner: metkit_sys::UniquePtr, } // SAFETY: HyperCube is accessed through &self / &mut self only. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for HyperCube {} unsafe impl Sync for HyperCube {} impl HyperCube { /// Create from an expanded `MarsRequest` — builds the N-dimensional grid /// from the request's parameter values. /// /// The C++ `HyperCube` assumes canonical, enumerated values (no date /// ranges, no aliases), so only [`MarsRequest`] is accepted. pub fn new(request: &MarsRequest) -> crate::Result { let inner = metkit_sys::HyperCubeWrapper::create(request.as_sys()).map_err(crate::Error::from)?; Ok(Self { inner }) } /// Total number of cells in the hypercube. #[must_use] pub fn size(&self) -> usize { self.inner.size() } /// Number of remaining (uncleared) cells. #[must_use] pub fn count(&self) -> usize { self.inner.count() } /// Number of vacant (uncleared) cells. #[must_use] pub fn count_vacant(&self) -> usize { self.inner.count_vacant() } /// Check if a request matches a cell in the hypercube. /// /// Field requests often come from GRIB messages /// (`TryFrom<&eckit::Message>`, which yields `MarsRequest`), /// so any state is accepted. pub fn contains(&self, request: &MarsRequest) -> crate::Result { self.inner .contains(request.as_sys()) .map_err(crate::Error::from) } /// Mark a cell as found. Returns true if the cell existed and was cleared. pub fn clear(&mut self, request: &MarsRequest) -> crate::Result { self.inner .pin_mut() .clear(request.as_sys()) .map_err(crate::Error::from) } /// Get the ordinal position of a request in the hypercube (for field ordering). pub fn field_ordinal(&self, request: &MarsRequest) -> crate::Result { self.inner .field_ordinal(request.as_sys()) .map_err(crate::Error::from) } } metkit-1.20.2/rust/crates/metkit/src/handle.rs0000664000175000017500000000345315246725757021451 0ustar alastairalastair//! `MarsRequestHandle` — `DataHandle` for Hermes protocol operations. //! //! Mirrors C++ `metkit::mars::MarsRequestHandle`. //! Wraps the Hermes TCP protocol: connects on `open_for_read()`, //! reads GRIB data via `Read` trait. use crate::request::{Expanded, MarsRequest}; /// A `DataHandle` that retrieves data via the Hermes protocol. /// /// Created from a `MarsRequest` and a database configuration. /// The connection and protocol handshake happen on `open_for_read()`. /// /// # Example /// /// ```ignore /// let handle = MarsRequestHandle::new(&request, &db_config)?; /// let (handle, len) = handle.open_for_read()?; /// for msg in eckit::MessageReader::new(&mut handle)? { /// // ... /// } /// ``` pub struct MarsRequestHandle { inner: eckit::DataHandle, } impl MarsRequestHandle { /// Create a new handle from an expanded request and database configuration. /// /// Mirrors C++ `MarsRequestHandle(request, config)`. The request is sent /// to the server as-is, so it must already be language-expanded. pub fn new(request: &MarsRequest, config: &eckit::Config) -> crate::Result { let inner = request .as_sys() .make_handle(config.as_sys()) .map_err(crate::Error::from)?; Ok(Self { inner: eckit::DataHandle::from_raw(inner), }) } /// Open for reading — connects to the server and performs the Hermes handshake. /// /// Returns the opened handle and the estimated data length. pub fn open_for_read(self) -> crate::Result<(eckit::DataHandle, i64)> { self.inner.open_for_read().map_err(crate::Error::from) } } impl From for eckit::DataHandle { fn from(handle: MarsRequestHandle) -> Self { handle.inner } } metkit-1.20.2/rust/crates/metkit/src/lib.rs0000664000175000017500000000115315246725757020757 0ustar alastairalastair//! Safe Rust wrapper for ECMWF's metkit C++ library. //! //! Provides: //! - [`MarsRequest`] — build and validate MARS requests (typestate: [`Raw`] / [`Expanded`]) //! - [`initialize`] — protocol environment metadata pub mod codes; pub mod environment; pub mod error; pub mod handle; pub mod hypercube; pub mod request; pub use codes::{CodesGet, CodesHandle, CodesSet}; pub use environment::initialize; pub use error::{Error, Result}; pub use handle::MarsRequestHandle; pub use hypercube::HyperCube; pub use request::{ Expanded, MarsRequest, MarsValue, ParsedRequests, Raw, RequestState, parse, tokenize, }; metkit-1.20.2/rust/crates/metkit/src/request.rs0000664000175000017500000004467515246725757021721 0ustar alastairalastair//! MARS request — typestate-guarded: raw vs expanded, tracked by the type system. //! //! A [`MarsRequest`] is either: //! //! - [`MarsRequest`] (the default) — mutable, values stored as-is, not yet //! validated by the MARS language, or //! - [`MarsRequest`] — produced by metkit's own expansion //! ([`MarsRequest::expand`], [`parse`], [`str::parse`]) or by the explicit //! trust assertion [`MarsRequest::from_trusted`]. //! //! APIs that require expanded values (e.g. `HyperCube::new`) take //! `&MarsRequest`, so passing an unexpanded request is a compile //! error. Mirrors the typestate style of `eckit::DataHandle`. use std::marker::PhantomData; use crate::error::Result; mod sealed { pub trait Sealed {} } /// State of a MARS request. Sealed — only [`Raw`] and [`Expanded`]. pub trait RequestState: sealed::Sealed {} /// Values stored as-is; not yet validated/expanded by the MARS language. pub struct Raw; impl sealed::Sealed for Raw {} impl RequestState for Raw {} /// Expanded and validated by the MARS language (dates resolved, ranges /// enumerated, aliases canonicalized, defaults inherited). pub struct Expanded; impl sealed::Sealed for Expanded {} impl RequestState for Expanded {} /// A MARS request, parameterized by its expansion state. /// /// Mirrors the mutable C++ `metkit::mars::MarsRequest`. `MarsRequest` with no /// type argument means `MarsRequest`. /// /// # Example /// /// ```ignore /// // Build a raw request, then expand it — the only way to get /// let mut request = MarsRequest::new("retrieve"); /// request.set("class", "od"); /// request.set("date", 20260713); // i64 /// request.set("step", ["0", "6", "12"]); // multiple values /// let expanded = request.expand(true, false)?; /// /// // Parsing a single request expands it (C++ MarsRequest::parse runs MarsExpansion) /// let request: MarsRequest = "retrieve, class=od, date=-1".parse()?; /// /// // Multiple requests: parse the text, then collect into a Vec /// let requests: Vec> = /// metkit::parse("retrieve, class=od\nlist, class=od", false)?.to_vec()?; /// /// // Trusted input — syntax-checked only, caller vouches for validity /// let mut trusted: MarsRequest = /// MarsRequest::from_trusted("retrieve, class=od, date=20260713")?; /// /// // Escape hatch: edit in place, keeping . Same trust contract — /// // only when you're sure the new value stays valid and canonical. /// trusted.set_trusted("expver", "0002"); /// /// // The safe alternative: editing demotes back to Raw, then re-expand /// let mut edited = expanded.into_raw(); /// edited.set("expver", "0002"); /// let expanded = edited.expand(true, false)?; /// ``` pub struct MarsRequest { inner: metkit_sys::UniquePtr, _state: PhantomData, } // SAFETY: `UniquePtr` gives this struct exclusive ownership of the C++ // `MarsRequest`, which holds its parameters by value (no shared state, no // thread-locals). All mutation goes through `&mut self` // (`Pin<&mut MarsRequestWrapper>`), so Rust's aliasing rules guarantee // exclusive access during mutation — `Send` is sound. All `&self` methods // map to `const` C++ methods with no interior mutability, so concurrent // shared reads are safe — `Sync` follows. Same rationale as `eckit::Config` // / `eckit::DataHandle`. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for MarsRequest {} unsafe impl Sync for MarsRequest {} /// A value assignable to a [`MarsRequest`] key — a single string, an integer, /// or a collection of strings. /// /// Sealed: implemented for `&str`, `String`, `&String`, `i64`, `Vec`, /// `Vec<&str>`, `&[&str]`, `&[String]`, and `[&str; N]` / `&[&str; N]`. /// /// For iterators, collect first: `.collect::>()`. For a /// `&Vec`, pass `v.as_slice()`. pub trait MarsValue: sealed::Sealed { #[doc(hidden)] fn set_on(self, request: &mut MarsRequest, key: &str); } impl sealed::Sealed for &str {} impl MarsValue for &str { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_value_string(key, self); } } impl sealed::Sealed for String {} impl MarsValue for String { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_value_string(key, &self); } } impl sealed::Sealed for &String {} impl MarsValue for &String { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_value_string(key, self); } } impl sealed::Sealed for i64 {} impl MarsValue for i64 { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_value_long(key, self); } } impl sealed::Sealed for Vec {} impl MarsValue for Vec { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_values(key, self); } } impl sealed::Sealed for Vec<&str> {} impl MarsValue for Vec<&str> { fn set_on(self, request: &mut MarsRequest, key: &str) { let values: Vec = self.into_iter().map(str::to_string).collect(); request.inner.pin_mut().set_values(key, values); } } impl sealed::Sealed for &[&str] {} impl MarsValue for &[&str] { fn set_on(self, request: &mut MarsRequest, key: &str) { let values: Vec = self.iter().map(|v| (*v).to_string()).collect(); request.inner.pin_mut().set_values(key, values); } } impl sealed::Sealed for &[String] {} impl MarsValue for &[String] { fn set_on(self, request: &mut MarsRequest, key: &str) { request.inner.pin_mut().set_values(key, self.to_vec()); } } impl sealed::Sealed for [&str; N] {} impl MarsValue for [&str; N] { fn set_on(self, request: &mut MarsRequest, key: &str) { self.as_slice().set_on(request, key); } } impl sealed::Sealed for &[&str; N] {} impl MarsValue for &[&str; N] { fn set_on(self, request: &mut MarsRequest, key: &str) { self.as_slice().set_on(request, key); } } impl MarsRequest { /// Create a new raw request with a verb (e.g. `"retrieve"`). #[must_use] pub fn new(verb: &str) -> Self { Self { inner: metkit_sys::MarsRequestWrapper::create(verb), _state: PhantomData, } } /// Set the value(s) for a key. Last write wins. /// /// Accepts single strings, `i64`, and collections of strings — see /// [`MarsValue`]. pub fn set(&mut self, key: &str, value: impl MarsValue) { value.set_on(self, key); } /// Remove a key. pub fn unset(&mut self, key: &str) { self.inner.pin_mut().unset_values(key); } /// Change the verb. pub fn set_verb(&mut self, verb: &str) { self.inner.pin_mut().set_verb(verb); } /// Decode a request from a stream (C++ wire format). /// /// Calls C++ `MarsRequest(Stream&)` constructor. The wire carries no /// expansion provenance, so the result is conservatively `Raw`. pub fn decode(stream: &mut dyn eckit::Stream) -> crate::Result { let inner = metkit_sys::MarsRequestWrapper::decode(stream.as_sys_mut()) .map_err(crate::Error::from)?; Ok(Self { inner, _state: PhantomData, }) } } impl MarsRequest { /// Parse trusted input as an already-expanded request. /// /// Runs only the MARS parser (syntax check, no verb validation) — **no /// language expansion or validation**. The caller asserts the values are /// already valid and canonical; an invalid request surfaces as a runtime /// error in downstream C++ instead of at this boundary. /// /// Prefer [`str::parse()`] unless you fully control the input. /// /// Errors if the input is not exactly one syntactically valid request. pub fn from_trusted(input: &str) -> Result { let parsed = tokenize(input)?; match parsed.count() { 1 => { let MarsRequest { inner, .. } = parsed.at(0)?; Ok(Self { inner, _state: PhantomData, }) } n => Err(crate::Error::Other(format!( "expected exactly one MARS request, found {n}" ))), } } /// Set a value in place, **keeping** the [`Expanded`] tag — an escape hatch /// for callers who know exactly what they are doing. /// /// Editing normally invalidates expansion, so the safe path is /// [`into_raw`](MarsRequest::into_raw) → [`set`](MarsRequest::set) → /// [`expand`](MarsRequest::expand). `set_trusted` skips that round-trip and /// asserts, on the caller's authority, that the new value is already /// expanded and canonical and leaves the request valid — the same trust /// contract as [`from_trusted`](MarsRequest::from_trusted). A value that /// breaks that contract surfaces as a runtime error in downstream C++ /// instead of at this boundary. /// /// Prefer the safe path unless you fully control the value. pub fn set_trusted(&mut self, key: &str, value: impl MarsValue) { value.set_on(self, key); } } impl MarsRequest { /// Create from a raw metkit-sys wrapper (for internal use). /// The caller chooses the state and is responsible for its truth. pub(crate) const fn from_raw( inner: metkit_sys::UniquePtr, ) -> Self { Self { inner, _state: PhantomData, } } /// The request verb (e.g. `"retrieve"`, `"archive"`). #[must_use] pub fn verb(&self) -> String { self.inner.verb() } /// Check if a key exists. #[must_use] pub fn has(&self, key: &str) -> bool { self.inner.has(key) } /// Get all values for a key. pub fn values(&self, key: &str) -> Result> { self.inner.values(key).map_err(crate::Error::from) } /// Get the first value for a key. pub fn get(&self, key: &str) -> Result { self.inner.get_first(key).map_err(crate::Error::from) } /// Whether the request has no parameters. #[must_use] pub fn is_empty(&self) -> bool { self.inner.empty() } /// Number of parameters. #[must_use] pub fn count(&self) -> usize { self.inner.count() } /// Check if this request matches a filter (any state). #[must_use] pub fn matches(&self, filter: &MarsRequest) -> bool { self.inner.matches(&filter.inner) } /// Get all parameter names. #[must_use] pub fn params(&self) -> Vec { self.inner.params() } /// Expand the request via `MarsLanguage::expand()`. /// /// Resolves date ranges, parameter aliases, etc. This is the guarded /// [`Raw`] → [`Expanded`] transition; re-expanding an already expanded /// request is legal and idempotent. pub fn expand(&self, inherit: bool, strict: bool) -> Result> { let inner = self .inner .expand(inherit, strict) .map_err(crate::Error::from)?; Ok(MarsRequest { inner, _state: PhantomData, }) } /// Re-tag as [`Raw`] to edit the request. Free — no FFI call. /// /// Any edit invalidates expansion, so mutation lives on /// [`MarsRequest`]; re-expand with [`MarsRequest::expand`] after /// editing. #[must_use] pub fn into_raw(self) -> MarsRequest { let Self { inner, .. } = self; MarsRequest { inner, _state: PhantomData, } } /// Extract parameters by category (e.g. `"postproc"`). /// /// Returns a new request containing only the extracted parameters, /// keeping the expansion state. Mirrors C++ `MarsRequest::extract()`. pub fn extract(&self, category: &str) -> Result { let inner = self.inner.extract(category).map_err(crate::Error::from)?; Ok(Self { inner, _state: PhantomData, }) } /// Serialize the parameters to JSON, in metkit's own format. /// /// Mirrors eckit's `JSON << MarsRequest`: the object holds only the /// parameters (the verb is **not** included), with a lone value rendered as /// a scalar and multiple values as an array. This is a display/interchange /// format — it is lossy and not intended to round-trip. pub fn to_json(&self) -> Result { self.inner.to_json().map_err(crate::Error::from) } /// Dump as formatted text. #[must_use] pub fn dump(&self) -> String { self.inner.dump() } /// Encode this request to a stream (C++ wire format). /// /// Calls C++ `MarsRequest::encode(Stream&)` via `operator<<`. pub fn encode(&self, stream: &mut dyn eckit::Stream) -> crate::Result<()> { self.inner .encode(stream.as_sys_mut()) .map_err(crate::Error::from) } /// Access the underlying metkit-sys wrapper (for FFI interop with other -sys crates). #[must_use] pub fn as_sys(&self) -> &metkit_sys::MarsRequestWrapper { &self.inner } } impl std::str::FromStr for MarsRequest { type Err = crate::Error; /// Parse a single MARS request, expanding and validating it (the C++ /// `MarsRequest::parse` runs `MarsExpansion` internally). /// /// Errors if the input contains zero or more than one request — use /// [`parse`]/[`tokenize`] for multi-request text. fn from_str(s: &str) -> Result { let parsed = parse(s, false)?; match parsed.count() { 1 => parsed.at(0), n => Err(crate::Error::Other(format!( "expected exactly one MARS request, found {n}" ))), } } } impl std::fmt::Display for MarsRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.inner.dump()) } } impl eckit::StreamWrite for MarsRequest { // Trait fixes the error type to eckit::Error; bypass crate::Error wrapping. fn write_to(&self, stream: &mut dyn eckit::Stream) -> eckit::Result<()> { self.inner .encode(stream.as_sys_mut()) .map_err(eckit::Error::from) } } impl TryFrom<&eckit::Message> for MarsRequest { type Error = crate::Error; /// Build a request from a GRIB message's metadata keys. /// /// GRIB-derived requests are not language-expanded, hence `Raw`. fn try_from(msg: &eckit::Message) -> crate::Result { let inner = metkit_sys::MarsRequestWrapper::from_message(msg.as_sys()) .map_err(crate::Error::from)?; Ok(Self { inner, _state: PhantomData, }) } } /// Parsed requests — parse once, iterate by index. /// /// The state parameter records how the requests were produced: /// [`parse`] expands, so it yields `ParsedRequests`; /// [`tokenize`] does not, so it yields `ParsedRequests`. /// /// # Example /// /// ```ignore /// let parsed = metkit::parse("retrieve, class=od\nlist, class=od", false)?; /// for request in parsed.iter() { /// let request = request?; // MarsRequest /// println!("{}", request.verb()); /// } /// ``` pub struct ParsedRequests { inner: metkit_sys::UniquePtr, _state: PhantomData, } impl ParsedRequests { /// Number of parsed requests. #[must_use] pub fn count(&self) -> usize { self.inner.count() } /// Get request at index. pub fn at(&self, index: usize) -> Result> { let inner = self.inner.at(index).map_err(crate::Error::from)?; Ok(MarsRequest::from_raw(inner)) } /// Collect all parsed requests into a `Vec`. /// /// Short-circuits on the first request that fails to materialize. Use this /// when you want every request up front; [`iter`](Self::iter) when you want /// to process them lazily. pub fn to_vec(&self) -> Result>> { self.iter().collect() } /// Iterate over all parsed requests. #[must_use] pub const fn iter(&self) -> ParsedRequestsIter<'_, S> { ParsedRequestsIter { parsed: self, index: 0, } } } /// Iterator over parsed requests. pub struct ParsedRequestsIter<'a, S: RequestState> { parsed: &'a ParsedRequests, index: usize, } impl Iterator for ParsedRequestsIter<'_, S> { type Item = Result>; fn next(&mut self) -> Option { if self.index >= self.parsed.count() { return None; } let result = self.parsed.at(self.index); self.index += 1; Some(result) } fn size_hint(&self) -> (usize, Option) { let remaining = self.parsed.count() - self.index; (remaining, Some(remaining)) } } impl ExactSizeIterator for ParsedRequestsIter<'_, S> {} impl<'a, S: RequestState> IntoIterator for &'a ParsedRequests { type Item = Result>; type IntoIter = ParsedRequestsIter<'a, S>; fn into_iter(self) -> Self::IntoIter { self.iter() } } /// Parse MARS requests from input text, expanding and validating them. /// /// Mirrors C++ `MarsRequest::parse(istream, strict)`, which runs /// `MarsExpansion` internally — the results are [`Expanded`]. pub fn parse(input: &str, strict: bool) -> Result> { let inner = metkit_sys::ParsedRequestsWrapper::parse(input, strict).map_err(crate::Error::from)?; Ok(ParsedRequests { inner, _state: PhantomData, }) } /// Tokenize MARS requests from input text (no expansion, no verb validation). /// /// Mirrors C++ `MarsParser(istream).parse()` — the results are [`Raw`]. /// Accepts any verb including `"environ"`. pub fn tokenize(input: &str) -> Result> { let inner = metkit_sys::ParsedRequestsWrapper::parse_raw(input).map_err(crate::Error::from)?; Ok(ParsedRequests { inner, _state: PhantomData, }) } metkit-1.20.2/rust/crates/metkit/src/error.rs0000664000175000017500000000525715246725757021353 0ustar alastairalastair//! Error types for metkit operations. //! //! Wraps both the auto-generated [`metkit_sys::Error`] (metkit's own //! `metkit::codes::CodesException` family) and the [`eckit::Error`] //! exceptions that surface through the same bridge. A bare `cxx::Exception` //! whose message matches neither namespace is preserved as `Other`. //! //! # Example //! //! Pattern-match on the typed variants to recover from specific failures //! (and route everything else to a fallback): //! //! ```no_run //! use metkit::{CodesHandle, Error, Result}; //! use metkit_sys::Error as MetkitError; //! use eckit::Error as EckitError; //! //! fn process_grib(path: &str, key: &str) -> Result { //! let h = CodesHandle::from_file(path)?; // may throw eckit::FileError //! let value: String = h.get(key)?; // may throw metkit::CodesException //! Ok(value) //! } //! //! match process_grib("/tmp/foo.grib", "shortName") { //! Ok(v) => println!("shortName = {v}"), //! //! // Typed metkit error — recover or report with metkit-specific context //! Err(Error::Metkit(MetkitError::CodesException(msg))) => { //! eprintln!("ecCodes rejected the key: {msg}"); //! } //! Err(Error::Metkit(MetkitError::CodesWrongLength(msg))) => { //! eprintln!("buffer length mismatch: {msg}"); //! } //! //! // Typed eckit error — fall back, retry, or wrap //! Err(Error::Eckit(EckitError::FileError(msg))) => { //! eprintln!("file error: {msg} (will retry with default sample)"); //! } //! Err(Error::Eckit(EckitError::UserError(msg))) => { //! eprintln!("bad input: {msg}"); //! } //! //! // Untyped fallback //! Err(other) => eprintln!("unhandled: {other}"), //! } //! ``` use eckit::Error as EckitError; // re-export of eckit_sys::Error use metkit_sys::Error as MetkitError; /// Combined error type for metkit operations. #[derive(Debug, thiserror::Error)] pub enum Error { /// Typed metkit exception (e.g. `CodesException`, `CodesWrongLength`). #[error(transparent)] Metkit(#[from] MetkitError), /// Typed eckit exception (e.g. `UserError`, `FileError`, `BadParameter`). #[error(transparent)] Eckit(#[from] EckitError), /// Exception from neither namespace; raw message preserved. #[error("{0}")] Other(String), } impl From for Error { fn from(e: cxx::Exception) -> Self { if let Some(metkit) = MetkitError::try_from_cxx(&e) { return Self::Metkit(metkit); } if let Some(eckit) = EckitError::try_from_cxx(&e) { return Self::Eckit(eckit); } Self::Other(e.what().to_string()) } } pub type Result = std::result::Result; metkit-1.20.2/rust/crates/metkit/src/codes.rs0000664000175000017500000001645515246725757021321 0ustar alastairalastair//! `CodesHandle` — GRIB/BUFR field access via eccodes. //! //! Wraps `metkit::codes::CodesHandle` for reading and modifying //! individual GRIB/BUFR messages. use std::path::Path; /// Handle to a GRIB/BUFR message via eccodes. pub struct CodesHandle { inner: metkit_sys::UniquePtr, } // SAFETY: CodesHandle owns a UniquePtr to a C++ object that can be moved between threads. // NOT Sync: eccodes handles have internal mutable state (accessor caches, key iterators) // even on const methods — concurrent reads from multiple threads would be a data race. // Use Arc> if shared access is needed. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for CodesHandle {} impl CodesHandle { /// Create from raw message bytes (copies the data). pub fn from_message(data: &[u8]) -> crate::Result { let inner = metkit_sys::CodesHandleWrapper::from_message(data).map_err(crate::Error::from)?; Ok(Self { inner }) } /// Create from an eccodes sample (e.g. `"GRIB1"`, `"GRIB2"`). /// /// Mirrors C++ `metkit::codes::codesHandleFromSample()`. pub fn from_sample(sample: impl AsRef) -> crate::Result { let inner = metkit_sys::CodesHandleWrapper::from_sample(sample.as_ref()) .map_err(crate::Error::from)?; Ok(Self { inner }) } /// Create from a GRIB file. pub fn from_file(path: impl AsRef) -> crate::Result { let path_str = path.as_ref().to_str().ok_or_else(|| { crate::Error::Other(format!( "path is not valid UTF-8: {}", path.as_ref().display() )) })?; let inner = metkit_sys::CodesHandleWrapper::from_file(path_str).map_err(crate::Error::from)?; Ok(Self { inner }) } /// Create from a GRIB file at a specific byte offset. pub fn from_file_at_offset(path: impl AsRef, offset: i64) -> crate::Result { let path_str = path.as_ref().to_str().ok_or_else(|| { crate::Error::Other(format!( "path is not valid UTF-8: {}", path.as_ref().display() )) })?; let inner = metkit_sys::CodesHandleWrapper::from_file_at_offset(path_str, offset) .map_err(crate::Error::from)?; Ok(Self { inner }) } /// Check if a key is defined. pub fn is_defined(&self, key: &str) -> crate::Result { self.inner.is_defined(key).map_err(crate::Error::from) } /// Check if a key is missing. pub fn is_missing(&self, key: &str) -> crate::Result { self.inner.is_missing(key).map_err(crate::Error::from) } /// Check if a key exists and has a value. pub fn has(&self, key: &str) -> crate::Result { self.inner.has(key).map_err(crate::Error::from) } /// Get a typed value. pub fn get(&self, key: &str) -> crate::Result { T::get_from(&self.inner, key) } /// Set a typed value. pub fn set(&mut self, key: &str, value: T) -> crate::Result<()> { value.set_on(self.inner.pin_mut(), key) } /// Set a key to its missing value. pub fn set_missing(&mut self, key: &str) -> crate::Result<()> { self.inner .pin_mut() .set_missing(key) .map_err(crate::Error::from) } /// Number of elements for a key (1 for scalars, N for arrays). pub fn value_count(&self, key: &str) -> crate::Result { self.inner.value_count(key).map_err(crate::Error::from) } /// Size of the raw message in bytes. pub fn message_size(&self) -> crate::Result { self.inner.message_size().map_err(crate::Error::from) } /// Raw message data (no copy — lifetime bound to this handle). pub fn message_data(&self) -> crate::Result<&[u8]> { self.inner.message_data().map_err(crate::Error::from) } /// Access the underlying metkit-sys wrapper. #[must_use] pub fn as_sys(&self) -> &metkit_sys::CodesHandleWrapper { &self.inner } } impl CodesHandle { /// Clone the underlying eccodes handle. pub fn try_clone(&self) -> crate::Result { let inner = self.inner.clone_handle().map_err(crate::Error::from)?; Ok(Self { inner }) } } // ==================== Generic get/set traits ==================== /// Trait for types that can be read from a `CodesHandle`. pub trait CodesGet: Sized { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result; } /// Trait for types that can be written to a `CodesHandle`. pub trait CodesSet { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()>; } // ==================== CodesGet impls ==================== impl CodesGet for String { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result { handle.get_string(key).map_err(crate::Error::from) } } impl CodesGet for i64 { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result { handle.get_long(key).map_err(crate::Error::from) } } impl CodesGet for f64 { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result { handle.get_double(key).map_err(crate::Error::from) } } impl CodesGet for Vec { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result { handle.get_double_array(key).map_err(crate::Error::from) } } impl CodesGet for Vec { fn get_from( handle: &metkit_sys::UniquePtr, key: &str, ) -> crate::Result { handle.get_long_array(key).map_err(crate::Error::from) } } // ==================== CodesSet impls ==================== impl CodesSet for &str { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()> { handle.set_string(key, self).map_err(crate::Error::from) } } impl CodesSet for String { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()> { handle.set_string(key, &self).map_err(crate::Error::from) } } impl CodesSet for i64 { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()> { handle.set_long(key, self).map_err(crate::Error::from) } } impl CodesSet for f64 { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()> { handle.set_double(key, self).map_err(crate::Error::from) } } impl CodesSet for &[f64] { fn set_on( self, handle: std::pin::Pin<&mut metkit_sys::CodesHandleWrapper>, key: &str, ) -> crate::Result<()> { handle .set_double_array(key, self) .map_err(crate::Error::from) } } metkit-1.20.2/rust/crates/metkit/src/environment.rs0000664000175000017500000000170215246725757022555 0ustar alastairalastair//! Request environment — metadata sent with protocol requests. use crate::error::Result; use crate::request::MarsRequest; /// Initialize the global request environment. /// /// Must be called before using the Hermes protocol. /// Typically called once at startup with client name and version. /// /// # Example /// /// ```ignore /// metkit::initialize(&[ /// ("client", "mars-client-rs"), /// ("version", "0.1.0"), /// ]); /// ``` pub fn initialize(env: &[(&str, &str)]) { let keys: Vec = env.iter().map(|(k, _)| (*k).to_string()).collect(); let values: Vec = env.iter().map(|(_, v)| (*v).to_string()).collect(); metkit_sys::RequestEnvironmentWrapper::initialise(keys, values); } /// Get the current environment as a `MarsRequest`. pub fn environment_request() -> Result { let inner = metkit_sys::RequestEnvironmentWrapper::request().map_err(crate::Error::from)?; Ok(MarsRequest::from_raw(inner)) } metkit-1.20.2/rust/crates/metkit/build.rs0000664000175000017500000000010215246725757020512 0ustar alastairalastairfn main() { bindman_utils::reexport_dep_root("METKIT_SYS"); } metkit-1.20.2/rust/crates/metkit/Cargo.toml0000664000175000017500000000112315246725757021001 0ustar alastairalastair[package] name = "metkit" version = "0.1.0" edition.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true description = "Safe Rust wrapper for ECMWF's metkit C++ library" links = "metkit_rs" build = "build.rs" [features] default = ["vendored", "grib", "bufr"] vendored = ["metkit-sys/vendored"] system = ["metkit-sys/system"] grib = ["metkit-sys/grib"] bufr = ["metkit-sys/bufr"] [dependencies] metkit-sys.workspace = true eckit.workspace = true thiserror.workspace = true cxx.workspace = true [build-dependencies] bindman-utils.workspace = true metkit-1.20.2/rust/Cargo.toml0000664000175000017500000000211015246725757016220 0ustar alastairalastair[workspace] resolver = "2" members = ["crates/metkit-sys", "crates/metkit"] [workspace.package] edition = "2024" license = "Apache-2.0" repository = "https://github.com/ecmwf/metkit" rust-version = "1.90" readme = "README.md" keywords = ["ecmwf", "weather", "meteorology", "grib"] categories = ["science"] [workspace.dependencies] # Internal metkit-sys = { path = "crates/metkit-sys" } metkit = { path = "crates/metkit" } # Foundation crates eckit-sys = { git = "ssh://git@github.com/ecmwf/eckit.git", branch = "develop", default-features = false } eccodes-sys = { git = "ssh://git@github.com/ecmwf/rust-wrappers-playground.git", rev = "d6b582fd", default-features = false } # High-level wrappers eckit = { git = "ssh://git@github.com/ecmwf/eckit.git", branch = "develop" } # Build tools bindman = { git = "ssh://git@github.com/ecmwf/bindman.git", rev = "47edf68" } bindman-build = { git = "ssh://git@github.com/ecmwf/bindman.git", rev = "47edf68" } bindman-utils = { git = "ssh://git@github.com/ecmwf/bindman.git", rev = "47edf68" } # External cxx = "1.0" cxx-build = "1.0" thiserror = "2" metkit-1.20.2/metkit.code-workspace0000664000175000017500000000005315246725757017424 0ustar alastairalastair{ "folders": [ { "path": "." } ] }metkit-1.20.2/src/0000775000175000017500000000000015246725757014070 5ustar alastairalastairmetkit-1.20.2/src/experimental/0000775000175000017500000000000015246725757016565 5ustar alastairalastairmetkit-1.20.2/src/experimental/netcdf4-example.cc0000664000175000017500000001112015246725757022047 0ustar alastairalastair#include #include #include "netcdf.h" /* This is the name of the data file we will create. */ #define FILE_NAME "sfc_pres_temp.nc" /* We are writing 2D data, a 6 x 12 lat-lon grid. We will need two * netCDF dimensions. */ #define NDIMS 2 #define NLAT 6 #define NLON 12 #define LAT_NAME "latitude" #define LON_NAME "longitude" /* Names of things. */ #define PRES_NAME "pressure" #define TEMP_NAME "temperature" #define UNITS "units" #define DEGREES_EAST "degrees_east" #define DEGREES_NORTH "degrees_north" /* These are used to construct some example data. */ #define SAMPLE_PRESSURE 900 #define SAMPLE_TEMP 9.0 #define START_LAT 25.0 #define START_LON -125.0 /* Handle errors by printing an error message and exiting with a * non-zero status. */ #define ERR(e) \ { \ printf("Error: %s\n", nc_strerror(e)); \ return 2; \ } int main() { int ncid, lon_dimid, lat_dimid, pres_varid, temp_varid; /* In addition to the latitude and longitude dimensions, we will also create latitude and longitude netCDF variables which will hold the actual latitudes and longitudes. Since they hold data about the coordinate system, the netCDF term for these is: "coordinate variables." */ int lat_varid, lon_varid; int dimids[NDIMS]; /* We will write surface temperature and pressure fields. */ float pres_out[NLAT][NLON]; float temp_out[NLAT][NLON]; float lats[NLAT], lons[NLON]; /* It's good practice for each netCDF variable to carry a "units" attribute. */ char pres_units[] = "hPa"; char temp_units[] = "celsius"; /* Error handling. */ int retval; for (int lat = 0; lat < NLAT; lat++) lats[lat] = START_LAT + 5. * lat; for (int lon = 0; lon < NLON; lon++) lons[lon] = START_LON + 5. * lon; for (int lat = 0; lat < NLAT; lat++) { for (int lon = 0; lon < NLON; lon++) { pres_out[lat][lon] = SAMPLE_PRESSURE + (lon * NLAT + lat); temp_out[lat][lon] = SAMPLE_TEMP + .25 * (lon * NLAT + lat); } } /* Create the file. */ if ((retval = nc_create(FILE_NAME, NC_CLOBBER, &ncid))) ERR(retval); /* Define the dimensions. */ if ((retval = nc_def_dim(ncid, LAT_NAME, NLAT, &lat_dimid))) ERR(retval); if ((retval = nc_def_dim(ncid, LON_NAME, NLON, &lon_dimid))) ERR(retval); // define latitudes if ((retval = nc_def_var(ncid, "lat", NC_FLOAT, 1, &lat_dimid, &lat_varid))) ERR(retval); if ((retval = nc_put_att_text(ncid, lat_varid, "standard_name", strlen(LAT_NAME), LAT_NAME))) ERR(retval); if ((retval = nc_put_att_text(ncid, lat_varid, UNITS, strlen(DEGREES_NORTH), DEGREES_NORTH))) ERR(retval); // define longitudes if ((retval = nc_def_var(ncid, "lon", NC_FLOAT, 1, &lon_dimid, &lon_varid))) ERR(retval); if ((retval = nc_put_att_text(ncid, lon_varid, "standard_name", strlen(LON_NAME), LON_NAME))) ERR(retval); if ((retval = nc_put_att_text(ncid, lon_varid, UNITS, strlen(DEGREES_EAST), DEGREES_EAST))) ERR(retval); /* Define the netCDF variables. The dimids array is used to pass the dimids of the dimensions of the variables.*/ dimids[0] = lat_dimid; dimids[1] = lon_dimid; /// pressure if ((retval = nc_def_var(ncid, "press", NC_FLOAT, NDIMS, dimids, &pres_varid))) ERR(retval); if ((retval = nc_put_att_text(ncid, pres_varid, "standard_name", strlen(PRES_NAME), PRES_NAME))) ERR(retval); if ((retval = nc_put_att_text(ncid, pres_varid, UNITS, strlen(pres_units), pres_units))) ERR(retval); /// temperature if ((retval = nc_def_var(ncid, "temp", NC_FLOAT, NDIMS, dimids, &temp_varid))) ERR(retval); if ((retval = nc_put_att_text(ncid, temp_varid, "standard_name", strlen(TEMP_NAME), TEMP_NAME))) ERR(retval); if ((retval = nc_put_att_text(ncid, temp_varid, UNITS, strlen(temp_units), temp_units))) ERR(retval); /* End define mode. */ if ((retval = nc_enddef(ncid))) ERR(retval); // write data if ((retval = nc_put_var_float(ncid, lat_varid, &lats[0]))) ERR(retval); if ((retval = nc_put_var_float(ncid, lon_varid, &lons[0]))) ERR(retval); if ((retval = nc_put_var_float(ncid, pres_varid, &pres_out[0][0]))) ERR(retval); if ((retval = nc_put_var_float(ncid, temp_varid, &temp_out[0][0]))) ERR(retval); // Close the file if ((retval = nc_close(ncid))) ERR(retval); return 0; } metkit-1.20.2/src/experimental/CMakeLists.txt0000664000175000017500000000026215246725757021325 0ustar alastairalastairecbuild_add_executable( TARGET netcdf4-example CONDITION HAVE_NETCDF SOURCES netcdf4-example.cc INCLUDES ${ECKIT_INCLUDE_DIRS} LIBS eckit NetCDF::NetCDF_C ) metkit-1.20.2/src/tools/0000775000175000017500000000000015246725757015230 5ustar alastairalastairmetkit-1.20.2/src/tools/grib-to-mtg2.cc0000664000175000017500000011302015246725757017746 0ustar alastairalastair/* * (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 grib-to-mtg2.cc /// @brief CLI tool for converting GRIB1 to MTG2 compliant GRIB2 files. /// #include #include #include #include #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/io/DataHandle.h" #include "eckit/io/FileHandle.h" #include "eckit/io/MemoryHandle.h" #include "eckit/log/Log.h" #include "eckit/message/Message.h" #include "eckit/message/Reader.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/EckitTool.h" #include "eckit/option/SimpleOption.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/api/Grib2Mars.h" #include "metkit/mars2grib/api/Mars2Grib.h" #include "metkit/mars2mars/api/Mars2Mars.h" using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- class Grib1ToGrib2Tool final : public eckit::EckitTool { public: Grib1ToGrib2Tool(int argc, char** argv); ~Grib1ToGrib2Tool() override = default; private: int minimumPositionalArguments() const override { return 2; } void init(const CmdArgs& args) override; void execute(const CmdArgs& args) override; void usage(const std::string& tool) const override; bool skipDiscipline192_ = false; std::optional expver_ = std::nullopt; std::optional generatingProcessIdentifier_ = std::nullopt; }; //---------------------------------------------------------------------------------------------------------------------- Grib1ToGrib2Tool::Grib1ToGrib2Tool(int argc, char** argv) : eckit::EckitTool(argc, argv) { options_.push_back(new eckit::option::SimpleOption("help", "Print this help message")); // Input handling options_.push_back( new eckit::option::SimpleOption("skip-discipline-192", "Skip discipline 192 input messages")); // Override values options_.push_back(new eckit::option::SimpleOption("expver", "Override expver")); options_.push_back( new eckit::option::SimpleOption("generatingProcessIdentifier", "Override generatingProcessIdentifier")); } void Grib1ToGrib2Tool::init(const CmdArgs& args) { skipDiscipline192_ = args.has("skip-discipline-192"); if (args.has("expver")) { std::string expver; args.get("expver", expver); expver_ = expver; } if (args.has("generatingProcessIdentifier")) { long generatingProcessIdentifier; args.get("generatingProcessIdentifier", generatingProcessIdentifier); generatingProcessIdentifier_ = generatingProcessIdentifier; } } void Grib1ToGrib2Tool::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] input output" << std::endl << std::endl << "Convert (pre-MTG2) GRIB1 to (post-MTG2) GRIB2" << std::endl << std::endl; } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr readCodesHandle(eckit::message::Message& msg) { std::unique_ptr dataHandle{msg.readHandle()}; eckit::MemoryHandle* memoryHandle = reinterpret_cast(dataHandle.get()); if (memoryHandle == nullptr) { throw eckit::UserError("Could not read eckit::Message", Here()); } const auto* data = reinterpret_cast(memoryHandle->data()); const auto size = static_cast(memoryHandle->size()); return metkit::codes::codesHandleFromMessageCopy(metkit::codes::Span(data, size)); } eckit::LocalConfiguration mergeLocalConfigs(const eckit::LocalConfiguration& base, const eckit::LocalConfiguration& overwrite) { eckit::LocalConfiguration result{base}; for (const auto& key : overwrite.keys()) { if (overwrite.isString(key)) { result.set(key, overwrite.getString(key)); } else if (overwrite.isIntegral(key)) { result.set(key, overwrite.getLong(key)); } else if (overwrite.isFloatingPoint(key)) { result.set(key, overwrite.getDouble(key)); } else if (overwrite.isBoolean(key)) { result.set(key, overwrite.getBool(key)); } else if (overwrite.isFloatingPointList(key)) { result.set(key, overwrite.getDoubleVector(key)); } else { throw eckit::NotImplemented("Unexpected type for '" + key + "'", Here()); } } return result; } //---------------------------------------------------------------------------------------------------------------------- bool isDiscipline192(const long param, const bool ignoreIfMappable = true) { static const std::unordered_set discipline192Params{ 4, 5, 6, 7, 11, 12, 13, 14, 19, 24, 25, 35, 36, 37, 38, 39, 40, 41, 42, 46, 62, 63, 64, 65, 70, 71, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 123, 125, 126, 127, 128, 139, 140, 153, 154, 158, 170, 171, 183, 184, 185, 190, 191, 192, 193, 199, 200, 204, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 233, 236, 237, 241, 242, 249, 250, 251, 252, 253, 254, 129001, 129002, 129003, 129004, 129005, 129011, 129012, 129013, 129014, 129021, 129022, 129023, 129024, 129025, 129026, 129027, 129028, 129029, 129030, 129031, 129032, 129033, 129034, 129035, 129036, 129037, 129038, 129039, 129040, 129041, 129042, 129043, 129044, 129045, 129046, 129047, 129048, 129049, 129050, 129051, 129052, 129053, 129054, 129057, 129058, 129059, 129060, 129061, 129062, 129063, 129064, 129065, 129066, 129067, 129070, 129071, 129078, 129079, 129080, 129081, 129082, 129083, 129084, 129085, 129086, 129087, 129088, 129089, 129090, 129091, 129092, 129093, 129094, 129095, 129096, 129097, 129098, 129099, 129100, 129101, 129102, 129103, 129104, 129105, 129106, 129107, 129108, 129109, 129110, 129111, 129112, 129113, 129114, 129115, 129116, 129117, 129118, 129119, 129120, 129121, 129122, 129123, 129125, 129126, 129127, 129128, 129129, 129130, 129131, 129132, 129133, 129134, 129135, 129136, 129137, 129138, 129139, 129140, 129141, 129142, 129143, 129144, 129145, 129146, 129147, 129148, 129149, 129150, 129151, 129152, 129153, 129154, 129155, 129156, 129157, 129158, 129159, 129160, 129161, 129162, 129163, 129164, 129165, 129166, 129167, 129168, 129169, 129170, 129171, 129172, 129173, 129174, 129175, 129176, 129177, 129178, 129179, 129180, 129181, 129182, 129183, 129184, 129185, 129186, 129187, 129188, 129189, 129190, 129191, 129192, 129193, 129194, 129195, 129196, 129197, 129198, 129199, 129200, 129201, 129202, 129203, 129204, 129205, 129206, 129207, 129208, 129209, 129210, 129211, 129212, 129214, 129215, 129216, 129217, 129218, 129219, 129220, 129221, 129222, 129223, 129224, 129225, 129226, 129227, 129228, 129229, 129230, 129231, 129232, 129233, 129234, 129235, 129236, 129237, 129238, 129239, 129240, 129241, 129242, 129243, 129244, 129245, 129246, 129247, 129248, 129249, 129250, 129251, 129252, 129253, 129254, 129255, 130208, 130209, 130210, 130211, 130212, 130213, 130214, 130215, 130216, 130217, 130218, 130219, 130220, 130221, 130224, 130225, 130226, 130228, 130229, 130230, 130231, 131001, 131002, 131003, 131004, 131005, 131006, 131007, 131008, 131009, 131010, 131015, 131016, 131017, 131018, 131020, 131021, 131022, 131023, 131024, 131025, 131049, 131059, 131064, 131065, 131066, 131067, 131068, 131069, 131073, 131078, 131079, 131080, 131081, 131129, 131130, 131139, 131144, 131151, 131164, 131165, 131167, 131201, 131202, 131228, 131229, 131232, 131255, 133001, 133002, 133003, 133004, 133005, 133006, 133007, 133008, 133009, 133010, 133011, 133012, 133013, 133014, 133015, 133016, 133017, 133018, 133019, 133020, 133021, 133022, 133023, 133024, 133025, 133026, 133027, 133028, 133029, 133030, 133031, 133032, 133033, 133034, 133035, 133036, 133037, 133038, 133039, 133040, 133041, 133042, 133043, 133044, 133045, 133046, 133047, 133048, 133049, 133050, 133051, 133052, 133053, 133054, 133055, 133056, 133057, 133058, 133059, 133060, 133061, 133062, 133063, 133064, 133065, 133066, 133067, 133068, 133069, 133070, 133071, 133072, 133073, 133074, 133075, 133076, 133077, 133078, 133079, 133080, 133081, 133082, 133083, 133084, 133085, 133086, 133087, 133088, 133089, 133090, 133091, 133092, 140200, 140250, 140255, 150129, 150130, 150131, 150133, 150134, 150135, 150137, 150139, 150140, 150141, 150142, 150143, 150144, 150145, 150146, 150147, 150148, 150152, 150153, 150154, 150155, 150168, 150169, 150170, 150171, 150172, 150173, 150180, 150181, 150182, 150183, 150255, 151128, 151129, 151130, 151133, 151134, 151135, 151136, 151137, 151138, 151139, 151140, 151141, 151142, 151143, 151144, 151146, 151147, 151148, 151150, 151151, 151152, 151153, 151154, 151155, 151156, 151157, 151158, 151159, 151160, 151161, 151162, 151164, 151165, 151166, 151167, 151168, 151169, 151170, 151176, 151177, 151178, 151179, 151180, 151181, 151182, 151183, 151184, 151185, 151186, 151187, 151188, 151190, 151191, 151192, 151193, 151194, 151199, 151200, 151201, 151202, 151203, 151204, 151205, 151206, 151207, 151208, 151209, 151210, 151211, 151212, 151255, 160049, 160135, 160137, 160140, 160142, 160143, 160144, 160156, 160157, 160171, 160180, 160181, 160182, 160184, 160199, 160205, 160207, 160209, 160210, 160212, 160213, 160214, 160216, 160217, 160218, 160219, 160221, 160222, 160223, 160224, 160225, 160231, 160239, 160240, 160241, 160242, 160243, 160246, 160247, 160249, 160254, 162051, 162054, 162056, 162057, 162058, 162064, 162065, 162066, 162067, 162068, 162073, 162074, 162075, 162076, 162077, 162078, 162081, 162082, 162083, 162085, 162086, 162087, 162114, 162115, 162116, 162117, 162118, 162119, 162120, 162121, 162122, 162123, 162124, 162125, 162126, 162127, 162128, 162129, 162130, 162131, 162132, 162133, 162134, 162135, 162136, 162137, 162138, 162139, 162140, 162141, 162206, 162207, 162208, 162209, 162210, 162211, 162212, 162213, 162214, 162215, 162216, 162217, 162218, 162219, 162220, 162221, 162222, 162223, 162224, 162225, 162226, 162227, 162229, 162230, 162231, 162232, 162233, 162255, 170149, 170171, 170179, 171001, 171002, 171003, 171004, 171005, 171006, 171007, 171011, 171012, 171013, 171014, 171021, 171022, 171023, 171026, 171027, 171028, 171029, 171030, 171031, 171032, 171033, 171034, 171035, 171036, 171037, 171038, 171039, 171040, 171041, 171042, 171043, 171044, 171045, 171046, 171047, 171048, 171049, 171050, 171051, 171052, 171053, 171054, 171055, 171056, 171057, 171058, 171059, 171060, 171061, 171062, 171063, 171064, 171065, 171078, 171079, 171121, 171122, 171125, 171126, 171127, 171128, 171129, 171130, 171131, 171132, 171133, 171134, 171135, 171136, 171137, 171138, 171139, 171140, 171141, 171142, 171143, 171144, 171145, 171146, 171147, 171148, 171149, 171150, 171151, 171152, 171153, 171154, 171155, 171156, 171157, 171158, 171159, 171160, 171161, 171162, 171163, 171164, 171165, 171166, 171167, 171168, 171169, 171170, 171171, 171173, 171174, 171175, 171176, 171177, 171178, 171179, 171180, 171181, 171182, 171183, 171184, 171185, 171186, 171187, 171188, 171189, 171190, 171191, 171192, 171193, 171194, 171195, 171196, 171197, 171198, 171199, 171200, 171201, 171202, 171203, 171204, 171205, 171206, 171207, 171208, 171209, 171210, 171211, 171212, 171214, 171215, 171216, 171217, 171218, 171219, 171220, 171221, 171222, 171223, 171224, 171225, 171226, 171227, 171228, 171229, 171230, 171231, 171232, 171233, 171234, 171235, 171236, 171237, 171238, 171239, 171240, 171241, 171242, 171243, 171244, 171245, 171246, 171247, 171248, 171249, 171250, 171251, 171252, 171253, 171254, 171255, 172044, 172045, 172153, 172154, 172239, 172240, 172255, 173044, 173045, 173048, 173050, 173142, 173143, 173144, 173145, 173146, 173147, 173149, 173153, 173154, 173169, 173175, 173176, 173177, 173178, 173179, 173180, 173181, 173182, 173189, 173195, 173196, 173197, 173205, 173208, 173209, 173210, 173211, 173212, 173228, 173239, 173240, 173255, 174006, 174031, 174034, 174039, 174040, 174041, 174042, 174049, 174055, 174083, 174085, 174086, 174087, 174088, 174089, 174090, 174094, 174095, 174099, 174110, 174111, 174139, 174164, 174167, 174168, 174170, 174175, 174183, 174236, 174255, 175006, 175031, 175034, 175039, 175040, 175041, 175042, 175049, 175055, 175083, 175085, 175086, 175087, 175088, 175089, 175090, 175110, 175111, 175139, 175164, 175167, 175168, 175170, 175175, 175183, 175236, 175255, 180149, 180176, 180177, 180178, 180179, 190170, 190171, 190173, 190229, 200001, 200002, 200003, 200004, 200005, 200011, 200012, 200013, 200014, 200021, 200022, 200023, 200024, 200025, 200026, 200027, 200028, 200029, 200030, 200031, 200032, 200033, 200034, 200035, 200036, 200037, 200038, 200039, 200040, 200041, 200042, 200043, 200044, 200045, 200046, 200047, 200048, 200049, 200050, 200051, 200052, 200053, 200054, 200055, 200056, 200057, 200058, 200059, 200060, 200061, 200062, 200063, 200064, 200065, 200066, 200067, 200070, 200071, 200078, 200079, 200080, 200081, 200082, 200083, 200084, 200085, 200086, 200087, 200088, 200089, 200090, 200091, 200092, 200093, 200094, 200095, 200096, 200097, 200098, 200099, 200100, 200101, 200102, 200103, 200104, 200105, 200106, 200107, 200108, 200109, 200110, 200111, 200112, 200113, 200114, 200115, 200116, 200117, 200118, 200119, 200120, 200121, 200122, 200123, 200125, 200126, 200127, 200128, 200129, 200130, 200131, 200132, 200133, 200134, 200135, 200136, 200137, 200138, 200139, 200140, 200141, 200142, 200143, 200144, 200145, 200146, 200147, 200148, 200149, 200150, 200151, 200152, 200153, 200154, 200155, 200156, 200157, 200158, 200159, 200160, 200161, 200162, 200163, 200164, 200165, 200166, 200167, 200168, 200169, 200170, 200171, 200172, 200173, 200174, 200175, 200176, 200177, 200178, 200179, 200180, 200181, 200182, 200183, 200184, 200185, 200186, 200187, 200188, 200189, 200190, 200191, 200192, 200193, 200194, 200195, 200196, 200197, 200198, 200199, 200200, 200201, 200202, 200203, 200204, 200205, 200206, 200207, 200208, 200209, 200210, 200211, 200212, 200214, 200215, 200216, 200217, 200218, 200219, 200220, 200221, 200222, 200223, 200224, 200225, 200226, 200227, 200228, 200229, 200230, 200231, 200232, 200233, 200234, 200235, 200236, 200237, 200238, 200239, 200240, 200241, 200242, 200243, 200244, 200245, 200246, 200247, 200248, 200249, 200250, 200251, 200252, 200253, 200254, 200255, 201001, 201002, 201003, 201004, 201005, 201006, 201007, 201008, 201009, 201010, 201011, 201012, 201013, 201014, 201015, 201016, 201017, 201029, 201030, 201031, 201032, 201033, 201034, 201035, 201036, 201037, 201038, 201041, 201042, 201050, 201051, 201052, 201053, 201054, 201055, 201056, 201060, 201061, 201062, 201063, 201064, 201065, 201066, 201067, 201068, 201069, 201070, 201071, 201072, 201073, 201074, 201075, 201076, 201077, 201078, 201079, 201080, 201081, 201082, 201083, 201084, 201085, 201099, 201100, 201101, 201102, 201111, 201112, 201113, 201150, 201200, 201203, 201215, 201241, 201255, 210001, 210002, 210003, 210004, 210005, 210006, 210007, 210008, 210009, 210010, 210011, 210012, 210013, 210014, 210015, 210016, 210017, 210018, 210019, 210020, 210021, 210022, 210023, 210024, 210025, 210026, 210027, 210028, 210029, 210030, 210031, 210032, 210033, 210034, 210035, 210036, 210037, 210038, 210039, 210040, 210041, 210042, 210043, 210044, 210045, 210046, 210047, 210048, 210049, 210050, 210051, 210052, 210053, 210054, 210055, 210056, 210057, 210058, 210059, 210060, 210061, 210062, 210063, 210064, 210065, 210066, 210067, 210068, 210069, 210070, 210071, 210079, 210080, 210081, 210082, 210083, 210084, 210085, 210086, 210087, 210088, 210089, 210090, 210091, 210092, 210093, 210094, 210095, 210096, 210097, 210098, 210099, 210100, 210101, 210102, 210103, 210104, 210105, 210106, 210107, 210108, 210109, 210110, 210111, 210112, 210113, 210114, 210115, 210116, 210117, 210118, 210119, 210120, 210124, 210125, 210126, 210127, 210128, 210129, 210130, 210131, 210132, 210133, 210134, 210135, 210136, 210137, 210138, 210139, 210140, 210141, 210142, 210143, 210144, 210145, 210146, 210147, 210148, 210149, 210150, 210151, 210152, 210153, 210154, 210155, 210156, 210157, 210158, 210159, 210160, 210161, 210162, 210163, 210164, 210165, 210166, 210167, 210169, 210177, 210179, 210181, 210182, 210183, 210184, 210185, 210206, 210207, 210208, 210209, 210210, 210211, 210212, 210213, 210214, 210215, 210216, 210217, 210218, 210219, 210220, 210221, 210222, 210223, 210224, 210225, 210226, 210227, 210228, 210229, 210230, 210231, 210232, 210233, 210234, 210235, 210236, 210237, 210238, 210239, 210240, 210241, 210242, 210243, 210244, 210245, 210246, 211001, 211002, 211003, 211004, 211005, 211006, 211007, 211008, 211009, 211010, 211011, 211012, 211013, 211014, 211015, 211016, 211017, 211018, 211019, 211020, 211021, 211022, 211023, 211024, 211025, 211026, 211027, 211028, 211029, 211030, 211031, 211032, 211033, 211034, 211035, 211036, 211037, 211038, 211039, 211040, 211041, 211042, 211043, 211044, 211045, 211046, 211047, 211048, 211049, 211050, 211051, 211052, 211053, 211054, 211055, 211056, 211061, 211062, 211063, 211064, 211065, 211066, 211067, 211068, 211069, 211070, 211071, 211092, 211093, 211094, 211095, 211096, 211097, 211098, 211099, 211100, 211101, 211119, 211120, 211124, 211125, 211126, 211127, 211128, 211129, 211130, 211131, 211132, 211133, 211134, 211135, 211136, 211137, 211138, 211139, 211140, 211141, 211142, 211143, 211144, 211145, 211146, 211147, 211148, 211149, 211150, 211151, 211152, 211153, 211154, 211155, 211156, 211157, 211158, 211159, 211160, 211161, 211162, 211163, 211164, 211165, 211166, 211181, 211182, 211183, 211184, 211185, 211206, 211207, 211208, 211209, 211210, 211211, 211212, 211213, 211214, 211215, 211216, 212001, 212002, 212003, 212004, 212005, 212006, 212007, 212008, 212009, 212010, 212011, 212012, 212013, 212014, 212015, 212016, 212017, 212018, 212019, 212020, 212021, 212022, 212023, 212024, 212025, 212026, 212027, 212028, 212029, 212030, 212031, 212032, 212033, 212034, 212035, 212036, 212037, 212038, 212039, 212040, 212041, 212042, 212043, 212044, 212045, 212046, 212047, 212048, 212049, 212050, 212051, 212052, 212053, 212054, 212055, 212056, 212057, 212058, 212059, 212060, 212061, 212062, 212063, 212064, 212065, 212066, 212067, 212068, 212069, 212070, 212071, 212072, 212073, 212074, 212075, 212076, 212077, 212078, 212079, 212080, 212081, 212082, 212083, 212084, 212085, 212086, 212087, 212088, 212089, 212090, 212091, 212092, 212093, 212094, 212095, 212096, 212097, 212098, 212099, 212100, 212101, 212102, 212103, 212104, 212105, 212106, 212107, 212108, 212109, 212110, 212111, 212112, 212113, 212114, 212115, 212116, 212117, 212118, 212119, 212120, 212121, 212122, 212123, 212124, 212125, 212126, 212127, 212128, 212129, 212130, 212131, 212132, 212133, 212134, 212135, 212136, 212137, 212138, 212139, 212140, 212141, 212142, 212143, 212144, 212145, 212146, 212147, 212148, 212149, 212150, 212151, 212152, 212153, 212154, 212155, 212156, 212157, 212158, 212159, 212160, 212161, 212162, 212163, 212164, 212165, 212166, 212167, 212168, 212169, 212170, 212171, 212172, 212173, 212174, 212175, 212176, 212177, 212178, 212179, 212180, 212181, 212182, 212183, 212184, 212185, 212186, 212187, 212188, 212189, 212190, 212191, 212192, 212193, 212194, 212195, 212196, 212197, 212198, 212199, 212200, 212201, 212202, 212203, 212204, 212205, 212206, 212207, 212208, 212209, 212210, 212211, 212212, 212213, 212214, 212215, 212216, 212217, 212218, 212219, 212220, 212221, 212222, 212223, 212224, 212225, 212226, 212227, 212228, 212229, 212230, 212231, 212232, 212233, 212234, 212235, 212236, 212237, 212238, 212239, 212240, 212241, 212242, 212243, 212244, 212245, 212246, 212247, 212248, 212249, 212250, 212251, 212252, 212253, 212254, 212255, 213001, 213002, 213003, 213004, 213005, 213101, 213102, 213103, 213104, 213105, 213106, 213107, 213108, 213109, 213110, 213111, 213112, 213113, 213114, 213115, 213116, 213117, 213118, 213119, 213120, 213121, 213122, 213123, 213124, 213125, 213126, 213127, 213128, 213129, 213130, 213131, 213132, 213133, 213134, 213135, 213136, 213137, 213138, 213139, 213140, 213141, 213142, 213143, 213144, 213145, 213146, 213147, 213148, 213149, 213150, 213151, 213152, 213153, 213154, 213155, 213156, 213157, 213158, 213159, 213160, 213161, 213162, 213163, 213164, 213165, 213166, 213167, 213168, 213169, 213170, 213171, 213172, 213173, 213174, 213175, 213176, 213177, 213178, 213179, 213180, 213181, 213182, 213183, 213184, 213185, 213186, 213187, 213188, 213189, 213190, 213191, 213192, 213193, 213194, 213195, 213196, 213197, 213198, 213199, 213200, 213201, 213202, 213203, 213204, 213205, 213206, 213207, 213208, 213209, 213210, 213211, 213212, 213213, 213214, 213215, 213216, 213217, 213218, 213219, 213220, 213221, 214002, 214003, 214004, 214005, 214006, 214007, 214008, 214009, 214010, 214011, 214012, 214013, 214014, 214015, 214016, 214017, 214018, 214019, 214020, 214021, 214022, 214023, 214024, 214025, 214026, 214027, 214028, 214029, 214030, 214031, 214032, 214033, 214034, 214035, 214036, 214037, 214038, 214039, 214040, 214041, 214042, 214043, 214044, 214045, 214046, 214047, 214048, 214049, 214050, 214051, 214052, 215001, 215002, 215003, 215004, 215005, 215006, 215007, 215008, 215009, 215010, 215011, 215012, 215013, 215014, 215015, 215016, 215017, 215018, 215019, 215020, 215021, 215022, 215023, 215024, 215025, 215026, 215027, 215028, 215029, 215030, 215031, 215032, 215033, 215034, 215035, 215036, 215037, 215038, 215039, 215040, 215041, 215042, 215043, 215044, 215045, 215046, 215047, 215048, 215049, 215050, 215051, 215052, 215053, 215054, 215055, 215056, 215057, 215058, 215059, 215060, 215061, 215062, 215063, 215064, 215065, 215066, 215067, 215068, 215069, 215070, 215071, 215072, 215073, 215074, 215075, 215076, 215077, 215078, 215079, 215080, 215081, 215082, 215083, 215084, 215085, 215086, 215087, 215088, 215089, 215090, 215091, 215092, 215093, 215094, 215095, 215096, 215097, 215098, 215099, 215100, 215101, 215102, 215103, 215104, 215105, 215106, 215107, 215108, 215109, 215110, 215111, 215112, 215113, 215114, 215115, 215116, 215117, 215118, 215119, 215120, 215121, 215122, 215123, 215124, 215125, 215126, 215127, 215128, 215129, 215130, 215131, 215132, 215133, 215134, 215135, 215136, 215137, 215138, 215139, 215140, 215141, 215142, 215143, 215144, 215145, 215146, 215147, 215148, 215149, 215150, 215151, 215152, 215153, 215154, 215155, 215156, 215157, 215158, 215159, 215160, 215161, 215162, 215163, 215164, 215165, 215166, 215167, 215168, 215169, 215170, 215171, 215172, 215173, 215174, 215175, 215176, 215177, 215178, 215179, 215180, 215181, 215182, 215183, 215184, 215185, 215186, 215187, 215188, 216001, 216002, 216003, 216004, 216005, 216006, 216007, 216008, 216009, 216010, 216011, 216012, 216013, 216014, 216015, 216016, 216017, 216018, 216019, 216020, 216021, 216022, 216023, 216024, 216025, 216026, 216027, 216028, 216029, 216030, 216031, 216032, 216033, 216034, 216035, 216036, 216037, 216038, 216039, 216040, 216041, 216042, 216043, 216044, 216045, 216046, 216047, 216048, 216049, 216050, 216051, 216052, 216053, 216054, 216055, 216056, 216057, 216058, 216059, 216060, 216061, 216062, 216063, 216064, 216065, 216066, 216067, 216068, 216069, 216070, 216071, 216072, 216073, 216074, 216075, 216076, 216077, 216078, 216079, 216080, 216081, 216082, 216083, 216084, 216085, 216086, 216087, 216088, 216089, 216090, 216091, 216092, 216093, 216094, 216095, 216096, 216097, 216098, 216099, 216100, 216101, 216102, 216103, 216104, 216105, 216106, 216107, 216108, 216109, 216110, 216111, 216112, 216113, 216114, 216115, 216116, 216117, 216118, 216119, 216120, 216121, 216122, 216123, 216124, 216125, 216126, 216127, 216128, 216129, 216130, 216131, 216132, 216133, 216134, 216135, 216136, 216137, 216138, 216139, 216140, 216141, 216142, 216143, 216144, 216145, 216146, 216147, 216148, 216149, 216150, 216151, 216152, 216153, 216154, 216155, 216156, 216157, 216158, 216159, 216160, 216161, 216162, 216163, 216164, 216165, 216166, 216167, 216168, 216169, 216170, 216171, 216172, 216173, 216174, 216175, 216176, 216177, 216178, 216179, 216180, 216181, 216182, 216183, 216184, 216185, 216186, 216187, 216188, 216189, 216190, 216191, 216192, 216193, 216194, 216195, 216196, 216197, 216198, 216199, 216200, 216201, 216202, 216203, 216204, 216205, 216206, 216207, 216208, 216209, 216210, 216211, 216212, 216213, 216214, 216215, 216216, 216217, 216218, 216219, 216220, 216221, 216222, 216223, 216224, 216225, 216226, 216227, 216228, 216229, 216230, 216231, 216232, 216233, 216234, 216235, 216236, 216237, 216238, 216239, 216240, 216241, 216242, 216243, 216244, 216245, 216246, 216247, 216248, 216249, 216250, 216251, 216252, 216253, 216254, 216255, 217003, 217004, 217006, 217007, 217009, 217010, 217011, 217012, 217013, 217014, 217015, 217016, 217018, 217020, 217021, 217022, 217023, 217024, 217026, 217028, 217029, 217030, 217032, 217033, 217034, 217035, 217036, 217037, 217038, 217039, 217040, 217041, 217042, 217043, 217044, 217045, 217046, 217047, 217048, 217049, 217050, 217051, 217052, 217053, 217054, 217055, 217056, 217057, 217058, 217059, 217060, 217061, 217062, 217063, 217064, 217065, 217066, 217067, 217068, 217069, 217070, 217071, 217072, 217073, 217074, 217075, 217076, 217077, 217078, 217079, 217080, 217081, 217082, 217083, 217084, 217085, 217086, 217087, 217088, 217089, 217090, 217091, 217092, 217093, 217094, 217095, 217096, 217097, 217098, 217099, 217100, 217101, 217102, 217103, 217104, 217105, 217106, 217107, 217108, 217109, 217110, 217111, 217112, 217113, 217114, 217115, 217116, 217117, 217118, 217119, 217120, 217121, 217122, 217123, 217124, 217125, 217126, 217127, 217128, 217129, 217130, 217131, 217132, 217133, 217134, 217135, 217136, 217137, 217138, 217139, 217140, 217141, 217142, 217143, 217144, 217145, 217146, 217147, 217148, 217149, 217150, 217151, 217152, 217153, 217154, 217155, 217156, 217157, 217158, 217159, 217160, 217161, 217162, 217163, 217164, 217165, 217166, 217167, 217168, 217169, 217170, 217171, 217172, 217173, 217174, 217175, 217176, 217177, 217178, 217179, 217180, 217181, 217182, 217183, 217184, 217185, 217186, 217187, 217188, 217189, 217190, 217191, 217192, 217193, 217194, 217195, 217196, 217197, 217198, 217199, 217200, 217201, 217202, 217203, 217204, 217205, 217206, 218003, 218004, 218006, 218007, 218009, 218010, 218011, 218012, 218013, 218014, 218015, 218016, 218018, 218019, 218020, 218021, 218022, 218023, 218024, 218026, 218027, 218028, 218029, 218030, 218032, 218033, 218034, 218035, 218036, 218037, 218038, 218039, 218040, 218041, 218042, 218043, 218044, 218045, 218046, 218047, 218048, 218049, 218050, 218051, 218052, 218053, 218054, 218055, 218056, 218057, 218058, 218059, 218060, 218061, 218062, 218063, 218064, 218065, 218066, 218067, 218068, 218069, 218070, 218071, 218072, 218073, 218074, 218075, 218076, 218077, 218078, 218079, 218080, 218081, 218082, 218083, 218084, 218085, 218086, 218087, 218088, 218089, 218090, 218091, 218092, 218093, 218094, 218095, 218096, 218097, 218098, 218099, 218100, 218101, 218102, 218103, 218104, 218105, 218106, 218107, 218108, 218109, 218110, 218111, 218112, 218113, 218114, 218115, 218116, 218117, 218118, 218119, 218120, 218121, 218122, 218123, 218124, 218125, 218126, 218127, 218128, 218129, 218130, 218131, 218132, 218133, 218134, 218135, 218136, 218137, 218138, 218139, 218140, 218141, 218142, 218143, 218144, 218145, 218146, 218147, 218148, 218149, 218150, 218151, 218152, 218153, 218154, 218155, 218156, 218157, 218158, 218159, 218160, 218161, 218162, 218163, 218164, 218165, 218166, 218167, 218168, 218169, 218170, 218171, 218172, 218173, 218174, 218175, 218176, 218177, 218178, 218179, 218180, 218181, 218182, 218183, 218184, 218185, 218186, 218187, 218188, 218189, 218190, 218191, 218192, 218193, 218194, 218195, 218196, 218197, 218198, 218199, 218200, 218201, 218202, 218203, 218204, 218205, 218206, 219001, 219002, 219003, 219004, 219005, 219006, 219007, 219008, 219009, 219010, 219011, 219012, 219013, 219014, 219015, 219016, 219017, 219018, 219019, 219020, 219021, 219022, 219023, 219024, 219025, 219026, 219027, 219028, 219029, 219030, 219031, 219032, 219033, 219034, 219035, 219036, 219037, 219038, 219039, 219040, 219041, 219042, 219043, 219044, 219045, 219046, 219047, 219048, 219049, 219050, 219051, 219052, 219053, 219054, 219055, 219056, 219057, 219058, 219059, 219060, 219061, 219062, 219063, 219064, 219065, 219066, 219067, 219068, 219069, 219070, 219071, 219072, 219073, 219074, 219075, 219076, 219077, 219078, 219079, 219080, 219081, 219082, 219083, 219084, 219085, 219086, 219087, 219088, 219089, 219090, 219091, 219092, 219093, 219094, 219095, 219096, 219097, 219098, 219099, 219100, 219101, 219102, 219103, 219104, 219105, 219106, 219107, 219108, 219109, 219110, 219111, 219112, 219113, 219114, 219115, 219116, 219117, 219118, 219119, 219120, 219121, 219122, 219123, 219124, 219125, 219126, 219127, 219128, 219129, 219130, 219131, 219132, 219133, 219134, 219135, 219136, 219137, 219138, 219139, 219140, 219141, 219142, 219143, 219144, 219145, 219146, 219147, 219148, 219149, 219150, 219151, 219152, 219153, 219154, 219155, 219156, 219157, 219158, 219159, 219160, 219161, 219162, 219163, 219164, 219165, 219166, 219167, 219168, 219169, 219170, 219171, 219172, 219173, 219174, 219175, 219176, 219177, 219178, 219179, 219180, 219181, 219182, 219183, 219184, 219185, 219186, 219187, 219188, 219189, 219190, 219191, 219192, 219193, 219194, 219195, 219196, 219197, 219198, 219199, 219200, 219201, 219202, 219203, 219204, 219205, 219206, 219207, 219208, 219209, 219210, 219211, 219212, 219213, 219214, 219215, 219216, 219217, 219218, 219219, 219220, 220228, 221001, 221002, 221003, 221004, 221005, 221006, 221007, 221008, 221009, 221010, 221011, 221012, 221013, 221014, 221015, 221016, 221017, 221018, 221019, 221020, 221021, 221022, 221023, 221024, 221025, 221026, 221027, 221028, 221029, 221030, 221031, 221032, 221033, 221034, 221035, 221036, 221037, 221038, 221039, 221040, 221041, 221042, 221043, 221044, 221045, 221046, 221047, 221048, 221049, 221050, 221051, 221052, 221053, 221054, 221055, 221056, 221057, 221058, 221059, 221060, 221061, 221062, 221063, 221064, 221065, 221066, 221067, 221068, 221069, 221070, 221071, 221072, 221073, 221074, 221075, 221076, 221077, 221078, 221079, 221080, 221081, 221082, 221083, 221084, 221085, 221086, 221087, 221088, 221089, 221090, 221091, 221092, 221093, 221094, 221095, 221096, 221097, 221098, 221099, 221100, 221101, 221102, 221103, 221104, 221105, 221106, 221107, 221108, 221109, 221110, 221111, 221112, 221113, 221114, 221115, 221116, 221117, 221118, 221119, 221120, 221121, 221122, 221123, 221124, 221125, 221126, 221127, 221128, 221129, 221130, 221131, 221132, 221133, 221134, 221135, 221136, 221137, 221138, 221139, 221140, 221141, 221142, 221143, 221144, 221145, 221146, 221147, 221148, 221149, 221150, 221151, 221152, 221153, 221154, 221155, 221156, 221157, 221158, 221159, 221160, 221161, 221162, 221163, 221164, 221165, 221166, 221167, 221168, 221169, 221170, 221171, 221172, 221173, 221174, 221175, 221176, 221177, 221178, 221179, 221180, 221181, 221182, 221183, 221184, 221185, 221186, 221187, 221188, 221189, 221190, 221191, 221192, 221193, 221194, 221195, 221196, 221197, 221198, 221199, 221200, 221201, 221202, 221203, 221204, 221205, 221206, 228025, 228040, 228041, 228042, 228043, 228091, 228092, 228093, 228100, 228101, 228102, 228103, 228248, 228250, 228252, 228253, 228254, 228255, 234139, 234151, 234167, 234228, }; static const std::unordered_set discipline192MappableParams{35, 36, 37, 38, 39, 40, 41, 42, 123, 139, 170, 183, 236, 151130, 151148, 151164}; return (discipline192Params.find(param) != discipline192MappableParams.end()) && (!ignoreIfMappable || (discipline192MappableParams.find(param) == discipline192MappableParams.end())); } bool skipStepZero(const long param) { static const std::unordered_set skipStepZeroParams{49, 121, 122, 123, 201, 202, 228026, 228027, 228028, 228057, 228058, 228222, 228223, 228224, 228225, 228226, 228227, 228251}; return skipStepZeroParams.find(param) != skipStepZeroParams.end(); } //---------------------------------------------------------------------------------------------------------------------- void Grib1ToGrib2Tool::execute(const CmdArgs& args) { // Handles to conversion libraries metkit::grib2mars::Grib2Mars grib2mars; metkit::mars2mars::Mars2Mars mars2mars; metkit::mars2grib::Mars2Grib mars2grib; auto inputPath = eckit::PathName(args(0)); auto outputPath = eckit::PathName(args(1)); if (!inputPath.exists()) { throw eckit::UserError("Input file path '" + inputPath.path() + "' does not exist!", Here()); } // Remove the output file if it already exists, otherwise we are not able to write to it. if (outputPath.exists()) { remove(outputPath.asString().c_str()); } auto outputFile = eckit::FileHandle(outputPath, true); outputFile.openForWrite(0); eckit::message::Reader msgReader{args(0)}; eckit::message::Message msg; while ((msg = msgReader.next())) { auto codesHandle = readCodesHandle(msg); if (skipDiscipline192_ && ((codesHandle->getLong("edition") == 1 && isDiscipline192(codesHandle->getLong("paramId"))) || codesHandle->getLong("discipline") == 192)) { continue; } if (codesHandle->getLong("endStep") == 0 && skipStepZero(codesHandle->getLong("paramId"))) { continue; } // Read the MARS/Misc dictionary from the input GRIB sample const auto originalMarsMisc = grib2mars.convert(*codesHandle); // Read the values from the input GRIB sample const std::vector values = codesHandle->getDoubleArray("values"); // Apply mappings to convert pre-MTG2 MARS/Misc to post-MTG2 MARS/Misc const auto mappedMarsMisc = mars2mars.convert(originalMarsMisc.mars); auto mars = mappedMarsMisc.mars; auto misc = mergeLocalConfigs(mappedMarsMisc.misc, originalMarsMisc.misc); // Override values if specified by the user in the arguments if (expver_) { mars.set("expver", *expver_); } if (generatingProcessIdentifier_) { misc.set("generatingProcessIdentifier", *generatingProcessIdentifier_); } // Encode into GRIB2 using mars2grib encoder const auto newSample = mars2grib.encode(values, mars, misc); // Write newly encoded GRIB2 sample to the output file outputFile.write(newSample->messageData().data(), newSample->messageData().size()); } outputFile.close(); } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { Grib1ToGrib2Tool tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/mars-archive-script.cc0000664000175000017500000002163515246725757021431 0ustar alastairalastair #include "eckit/io/DataHandle.h" #include "eckit/io/FileHandle.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "metkit/mars/MarsParser.h" #include "metkit/tool/MetkitTool.h" #include using namespace metkit; using namespace metkit::mars; using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- class MarsArchiveScript : public MetkitTool { using OverridesDict = std::map>>; public: // methods MarsArchiveScript(int argc, char** argv); ~MarsArchiveScript() override = default; private: // methods void init(const CmdArgs& args) override; void execute(const CmdArgs& args) override; void usage(const std::string& tool) const override; OverridesDict extractOverrides(MarsRequest& request); template void setOverrides(MarsRequest& rq, const OverridesDict& overrides, const char* overrideName, Args... more); void setOverrides(MarsRequest& rq, const OverridesDict& overrides); std::string srcfile(int cnt) const; std::string arcfile(int cnt) const; std::string cmpfile(int cnt) const; private: // members std::string infile_; std::string outfile_; std::string tempPrefix_; bool obs_; bool compare_; // Output environment variables std::string preObs_; std::string retrieveMars_; std::string archiveMars_; std::string compareMars_; std::string unblockCommand_; std::string retrieveComplete_; std::string archiveComplete_; std::string compareComplete_; std::string compareCommand_; }; //---------------------------------------------------------------------------------------------------------------------- MarsArchiveScript::MarsArchiveScript(int argc, char** argv) : MetkitTool(argc, argv), tempPrefix_("mars"), obs_(false), compare_(false), preObs_("PREOBS"), retrieveMars_("RETRIEVE_MARS"), archiveMars_("ARCHIVE_MARS"), compareMars_("COMPARE_MARS"), unblockCommand_("UNBLOCK"), retrieveComplete_("RETRIEVE_COMPLETE"), archiveComplete_("ARCHIVE_COMPLETE"), compareComplete_("COMPARE_COMPLETE"), compareCommand_("COMPARE") { options_.push_back(new SimpleOption("out", "Output filename (defaults output to stdout)")); options_.push_back(new SimpleOption("in", "Input filename (defaults input to stdin)")); options_.push_back(new SimpleOption("obs", "Handle observations rather than output GRIBs")); options_.push_back(new SimpleOption("compare", "Re-retrieve and compare results")); options_.push_back( new SimpleOption("legacy", "Use legacy environment variable names for old suite compatability")); options_.push_back(new SimpleOption("prefix", "Prefix for the temporary files. Typically \"mars\"")); } void MarsArchiveScript::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options]" << eckit::newl << eckit::newl << "Note: The output of this tool assumes that we are running in an ecflow suite with" << eckit::newl << " failure trapping enabled, as well as the following environment variables:" << eckit::newl << " RETRIEVE_MARS, ARCHIVE_MARS, COMPARE_MARS, UNBLOCK, RETRIEVE_COMPLETE" << eckit::newl << " ARCHIVE_COMPLETE, COMPARE_COMPLETE" << eckit::newl << eckit::newl << "Examples:" << eckit::newl << "=========" << eckit::newl << std::endl; } void MarsArchiveScript::init(const CmdArgs& args) { infile_ = args.getString("in", infile_); outfile_ = args.getString("out", outfile_); compare_ = args.getBool("compare", compare_); tempPrefix_ = args.getString("prefix", tempPrefix_); obs_ = args.getBool("obs", obs_); if (args.getBool("legacy", false)) { retrieveMars_ = "MARS_FROM_FDB"; archiveMars_ = "MARS_TO_IBM"; compareMars_ = "MARS_FROM_IBM"; unblockCommand_ = "UNBLOCK"; retrieveComplete_ = "FDB_COMPLETE"; archiveComplete_ = "ARC_COMPLETE"; compareComplete_ = "CMP_COMPLETE"; } if (obs_) compareCommand_ = "COMPOBS"; } std::string MarsArchiveScript::srcfile(int cnt) const { return tempPrefix_ + ".source." + translate(cnt); } std::string MarsArchiveScript::arcfile(int cnt) const { return tempPrefix_ + ".archive." + translate(cnt); } std::string MarsArchiveScript::cmpfile(int cnt) const { return tempPrefix_ + ".compare." + translate(cnt); } MarsArchiveScript::OverridesDict MarsArchiveScript::extractOverrides(MarsRequest& request) { OverridesDict ret; std::vector keys; request.getParams(keys); for (const auto& k : keys) { int pos = k.find('@'); if (pos != std::string::npos) { std::vector vals; request.getValues(k, vals); ret[k.substr(0, pos)][k.substr(pos + 1)] = vals; request.erase(k); } } return ret; } template void MarsArchiveScript::setOverrides(MarsRequest& rq, const OverridesDict& overrides, const char* overrideName, Args... more) { auto namedset = overrides.find(overrideName); if (namedset != overrides.end()) { for (const auto& kv : namedset->second) { rq.setValue(kv.first, kv.second); } } setOverrides(rq, overrides, more...); } void MarsArchiveScript::setOverrides(MarsRequest& rq, const OverridesDict& overrides) {} void MarsArchiveScript::execute(const CmdArgs& args) { // Input from file, or from stdin? std::unique_ptr in_file; if (!infile_.empty()) { in_file = std::make_unique(infile_.c_str(), std::ios::in | std::ios::binary); in_file->exceptions(std::ios::badbit); } std::istream& in(in_file ? *in_file : std::cin); // Output to file, or to stdout? std::unique_ptr out_file; if (!outfile_.empty()) { out_file = std::make_unique(outfile_.c_str(), std::ios::out | std::ios::binary); out_file->exceptions(std::ios::badbit); } std::ostream& out(out_file ? *out_file : std::cout); // Parse the input request MarsParser parser(in); auto requests = parser.parse(); std::vector overrides; for (auto& rq : requests) { overrides.emplace_back(extractOverrides(rq)); } // 1. Retrieve the source data (typically from the FDB) // n.b. loop over non-const, non-reference requests --> mutable copy in the loops int cnt = 0; if (obs_) { for (cnt = 0; cnt < requests.size(); ++cnt) { out << "$" << preObs_ << " $header." << translate(cnt + 1) << " $data." << translate(cnt + 1) << " " << arcfile(cnt) << "\n\n"; } } else { out << "$" << retrieveMars_ << " << @\n\n"; cnt = 0; for (auto rq : requests) { rq.verb("retrieve"); rq.setValue("target", srcfile(cnt)); setOverrides(rq, overrides[cnt++], "default", "retrieve", "fdb_retrieve"); rq.dump(out); } out << "@\n\n$" << retrieveComplete_ << "\n\n"; // 1.a) Any intermediate step required. Typically ln -s (historically handle "blocked" data output by older // Fortran // based systems) for (cnt = 0; cnt < requests.size(); ++cnt) { out << "$" << unblockCommand_ << " " << srcfile(cnt) << " " << arcfile(cnt) << "\n\n"; } } // 2. Archive the data out << "$" << archiveMars_ << " << @\n\n"; cnt = 0; for (auto rq : requests) { rq.verb("archive"); rq.setValue("source", arcfile(cnt)); setOverrides(rq, overrides[cnt++], "default", "archive"); rq.dump(out); } out << "@\n\n$" << archiveComplete_; // 3. Comparison of re-retrieved data if (compare_) { out << "\n\n$" << compareMars_ << " << @\n\n"; cnt = 0; for (MarsParsedRequest rq : requests) { rq.verb("retrieve"); rq.setValue("target", cmpfile(cnt)); setOverrides(rq, overrides[cnt++], "default", "compare", "ibm_retrieve"); rq.dump(out); } out << "@\n\n"; for (cnt = 0; cnt < requests.size(); ++cnt) { out << "$" << compareCommand_ << " " << arcfile(cnt) << " " << cmpfile(cnt) << "\n\n"; } out << "$" << compareComplete_; } out << std::endl; } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { MarsArchiveScript tool(argc, argv); return tool.start(); }metkit-1.20.2/src/tools/message-to-mars.cc0000664000175000017500000000356315246725757020552 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/config/Resource.h" #include "eckit/io/Buffer.h" #include "eckit/io/Offset.h" #include "eckit/runtime/Tool.h" #include "metkit/codes/GribToRequest.h" #include "metkit/mars/MarsRequest.h" using namespace metkit; using metkit::grib::MetFile; //---------------------------------------------------------------------------------------------------------------------- class Grib2Request : public eckit::Tool { public: Grib2Request(int argc, char** argv) : Tool(argc, argv) { path_ = eckit::Resource("-in", "input.grib"); ///< @todo Move to use Option } virtual ~Grib2Request() {} virtual void run(); private: // members eckit::PathName path_; }; void Grib2Request::run() { eckit::Log::debug() << "Opening GRIB file : " << path_ << std::endl; eckit::Buffer buffer(MetFile::gribBufferSize()); long len = 0; grib::MetFile file(path_); metkit::mars::MarsRequest onereq("GRIB"); size_t nMsg = 0; while ((len = file.readSome(buffer)) != 0) { metkit::mars::MarsRequest req("GRIB"); grib::GribToRequest::gribToRequest(buffer, len, req); // eckit::Log::info() << req << std::endl; ++nMsg; onereq.merge(req); } eckit::Log::info() << onereq << std::endl; } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { Grib2Request tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/check-mars2conf.cc0000664000175000017500000001421715246725757020511 0ustar alastairalastair #include #include "eckit/config/LocalConfiguration.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/log/JSON.h" #include "eckit/log/Log.h" #include "eckit/option/CmdArgs.h" #include "eckit/runtime/Tool.h" // This include is required to enable dictionary access traits from eckit::LocalConfiguration #include "metkit/mars2grib/frontend/normalization/normalizeMarsDict.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/CoreOperations.h" eckit::LocalConfiguration mars2conf(const eckit::LocalConfiguration& mars) { eckit::LocalConfiguration opts; const std::string confJson = metkit::mars2grib::CoreOperations::dumpHeaderTest(mars, opts); return eckit::LocalConfiguration{eckit::YAMLConfiguration{confJson}}; } class CheckMars2conf : public eckit::Tool { public: CheckMars2conf(int argc, char** argv) : Tool{argc, argv} {} static void usage(const std::string& tool) { eckit::Log::info() << "\nUsage: " << tool << " inputFile" << std::endl; } void run() override { eckit::JSON json{eckit::Log::warning(), eckit::JSON::Formatting::indent(2)}; eckit::option::CmdArgs args{usage, 1, -1}; eckit::Log::info() << "Running " << args(0) << std::endl; const eckit::LocalConfiguration testCases{eckit::YAMLConfiguration{eckit::PathName(args(0))}}; eckit::Log::info() << "Loaded " << testCases.getSubConfigurations().size() << " test cases!" << std::endl; size_t count = 0; size_t failed = 0; for (const auto& testCase : testCases.getSubConfigurations()) { count++; if (!testCase.has("encoderConfiguration")) { continue; } auto mars = testCase.getSubConfiguration("mars"); const auto& expectedEncoder = testCase.getSubConfiguration("encoderConfiguration"); if (metkit::mars2grib::frontend::normalization::hack::fixMarsGrid(mars)) { eckit::Log::info() << "Fixed MARS grid" << std::endl; } eckit::LocalConfiguration actualEncoder; try { actualEncoder = mars2conf(mars).getSubConfiguration("GribHeaderLayoutData"); } catch (const eckit::Exception& e) { eckit::Log::warning() << "Encountered an exception!" << std::endl; { eckit::JSON json{eckit::Log::warning()}; json << testCase; eckit::Log::warning() << std::endl; } failed++; continue; } bool currentFailed = false; for (size_t si = 0; si <= 5; ++si) { const auto expectedSection = expectedEncoder.getSubConfigurations("sections")[si]; const auto actualSection = actualEncoder.getSubConfigurations("sections")[si].getSubConfiguration("SectionLayoutData"); const auto& expectedTemplate = expectedSection.getLong("templateNumber"); const auto& actualTemplate = actualSection.getLong("templateNumber"); if (expectedTemplate != actualTemplate) { eckit::Log::warning() << "Template number for section " << si << " does not match! : " << actualTemplate << " != " << expectedTemplate << std::endl; if (actualTemplate == 192'001'024'036) { eckit::Log::warning() << "Skipping..." << std::endl; continue; } currentFailed = true; } const auto& expectedConcepts = expectedSection.getSubConfigurations("concepts"); const auto& actualConcepts = actualSection.getStringVector("variantNames"); if (expectedConcepts.size() != actualConcepts.size()) { eckit::Log::warning() << "Number of concepts for section " << si << " does not match! : " << actualConcepts.size() << " != " << expectedConcepts.size() << std::endl; currentFailed = true; continue; } for (size_t ci = 0; ci < actualConcepts.size(); ++ci) { const auto expectedConcept = expectedConcepts[ci].getString("name"); const auto expectedVariant = expectedConcepts[ci].getString("type"); size_t pos = actualConcepts[ci].find("::"); const auto actualConcept = actualConcepts[ci].substr(0, pos); const auto actualVariant = actualConcepts[ci].substr(pos + 2); if (expectedConcept != actualConcept) { eckit::Log::warning() << "A concept for section " << si << " does not match! : " << actualConcept << " != " << expectedConcept << std::endl; currentFailed = true; } else if (expectedVariant != actualVariant) { eckit::Log::warning() << "A variant for section " << si << " does not match! : " << actualConcept << "::" << actualVariant << " != " << expectedConcept << "::" << expectedVariant << std::endl; currentFailed = true; } } } if (currentFailed) { failed++; } } std::ostringstream oss; oss << "Failed " << failed << " cases failed out of " << count << std::endl; eckit::Log::error() << oss.str(); if (failed != 0) { throw eckit::Exception(oss.str(), Here()); } } }; //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { CheckMars2conf tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/ncmerge.cc0000664000175000017500000000203115246725757017153 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ // Baudouin Raoult - ECMWF Jan 2015 #include "metkit/netcdf/InputDataset.h" #include "metkit/netcdf/NCFileCache.h" #include "metkit/netcdf/OutputDataset.h" #include using namespace metkit::netcdf; int main(int argc, char** argv) { try { NCFileCache cache; OutputDataset out("out.nc", cache); for (int i = 1; i < argc; i++) { InputDataset f(argv[i], cache); std::cout << "@@@@@@@@@@@ " << argv[i] << std::endl; out.merge(f); } out.save(); } catch (std::exception& e) { std::cerr << e.what() << std::endl; return 1; } return 0; } metkit-1.20.2/src/tools/grib-to-request.cc0000664000175000017500000001756715246725757020610 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include #include #include #include #include "eckit/filesystem/PathName.h" #include "eckit/io/FileHandle.h" #include "eckit/log/JSON.h" #include "eckit/log/Log.h" #include "eckit/message/Message.h" #include "eckit/message/Reader.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "eckit/utils/StringTools.h" #include "metkit/hypercube/HyperCube.h" #include "metkit/mars/MarsRequest.h" #include "metkit/tool/MetkitTool.h" using namespace metkit; using namespace metkit::mars; using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- class MarsRequestSetter : public eckit::message::MetadataGatherer { public: // methods MarsRequestSetter(MarsRequest& request) : request_(request) {} void setValue(const std::string& key, const std::string& value) override { request_.setValue(key, value); } void setValue(const std::string& key, long value) override { request_.setValue(key, value); } void setValue(const std::string& key, double value) override { request_.setValue(key, value); } private: // members MarsRequest& request_; }; //---------------------------------------------------------------------------------------------------------------------- class GribToRequestTool : public MetkitTool { public: GribToRequestTool(int argc, char** argv) : MetkitTool(argc, argv) { options_.push_back(new SimpleOption("verb", "Verb in the request, default = retrieve")); options_.push_back( new SimpleOption("database", "add database keyword to requests, default = none")); options_.push_back(new SimpleOption("source", "add source keyword to requests, default = none")); options_.push_back(new SimpleOption("target", "add target keyword to requests, default = none")); options_.push_back(new SimpleOption("one", "Merge into one request, potentially describing more data " "fields than the ones in the input file, default = false")); options_.push_back(new SimpleOption("compact", "Merge into a small number of requests, default = false")); options_.push_back(new SimpleOption("json", "Format request in json, default = false")); } private: // methods int minimumPositionalArguments() const override { return 1; } void execute(const eckit::option::CmdArgs& args) override; void init(const CmdArgs& args) override; void usage(const std::string& tool) const override; private: // members std::vector paths_; std::string verb_ = "retrieve"; std::string database_ = ""; std::string source_ = ""; std::string target_ = ""; bool one_ = false; bool compact_ = false; bool json_ = false; }; //---------------------------------------------------------------------------------------------------------------------- void GribToRequestTool::init(const CmdArgs& args) { args.get("one", one_); args.get("compact", compact_); args.get("verb", verb_); args.get("database", database_); args.get("source", source_); args.get("target", target_); args.get("json", json_); if (json_) { porcelain_ = true; } if (one_ and compact_) { Log::error() << "Options --one and --compact are mutually exclusive" << std::endl; std::exit(1); } } void GribToRequestTool::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] [request1] [request2] ..." << std::endl << std::endl; Log::info() << "Examples:" << std::endl << "=========" << std::endl << std::endl << tool << " --one --verb=retrieve data.grib" << std::endl << tool << " --compact --verb=retrieve data.grib" << std::endl << std::endl; } namespace { static void toJSON(const std::vector& requests) { JSON j(Log::info()); for (const auto& r : requests) { r.json(j); } Log::info() << std::endl; } static void toStdOut(const std::vector& requests) { for (const auto& r : requests) { eckit::Log::info() << r << std::endl; } } static void addKeyValue(std::vector& requests, const std::string& key, const std::string& value) { std::transform(requests.begin(), requests.end(), requests.begin(), [key, value](MarsRequest& r) -> MarsRequest { r.setValue(key, value); return r; }); } } // namespace void GribToRequestTool::execute(const eckit::option::CmdArgs& args) { PathName inFile(args(0)); FileHandle dh(inFile); dh.openForRead(); eckit::message::Reader reader(dh, false); eckit::message::Message msg; std::vector requests; while ((msg = reader.next())) { MarsRequest r(verb_); MarsRequestSetter setter(r); msg.getMetadata(setter); if (one_ and requests.size()) { requests.back().merge(r); } else { requests.push_back(r); } } if (compact_ && requests.size() > 1) { std::map, std::vector> coherentRequests; // split the requests into groups of requests with the same set of metadata (but potentially different values) for (const auto& r : requests) { std::set keys; for (const auto& p : r.parameters()) { keys.insert(p.name()); } coherentRequests[keys].push_back(r); } requests.clear(); // compact each group of requests into a single request (if possible) for (const auto& [keys, reqs] : coherentRequests) { if (reqs.size() == 1) { // it is a single field - return its request as is requests.push_back(reqs.front()); continue; } MarsRequest merged{reqs.front()}; for (size_t i = 1; i < reqs.size(); ++i) { merged.merge(reqs[i]); } if (merged.count() == reqs.size()) { // the set of fields forms a full hypercube - return corresponding merged request requests.push_back(std::move(merged)); continue; } // sparse hypercube - we have to compute a set of compact requests describing the input fields metkit::hypercube::HyperCube h{merged}; for (const auto& r : reqs) { h.clear(r); } for (const auto& r : h.requests()) { requests.push_back(r); } } } if (not database_.empty()) { addKeyValue(requests, "database", database_); } if (StringTools::lower(verb_) == "archive") { addKeyValue(requests, "source", inFile); } if (not source_.empty()) { addKeyValue(requests, "source", source_); } if (not target_.empty()) { addKeyValue(requests, "target", target_); } if (json_) { toJSON(requests); } else { toStdOut(requests); } } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { GribToRequestTool tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/parse-mars-request.cc0000664000175000017500000000100415246725757021272 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "ParseRequest.h" int main(int argc, char** argv) { ParseRequest tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/grib-blob.cc0000664000175000017500000000442715246725757017405 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/config/Resource.h" #include "eckit/io/Buffer.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/Option.h" #include "eckit/runtime/Tool.h" #include "metkit/codes/GribMetaData.h" using namespace metkit; //---------------------------------------------------------------------------------------------------------------------- class GribBlob; static GribBlob* instance_ = 0; class GribBlob : public eckit::Tool { public: virtual void usage(const std::string& tool) const { eckit::Log::info() << std::endl << "Usage: " << tool << " [path2] ..." << std::endl; } virtual int numberOfPositionalArguments() const { return -1; } virtual int minimumPositionalArguments() const { return 1; } virtual void run(); GribBlob(int argc, char** argv) : eckit::Tool(argc, argv) { ASSERT(instance_ == 0); instance_ = this; } protected: // members std::vector options_; }; static void usage(const std::string& tool) { ASSERT(instance_); instance_->usage(tool); } void GribBlob::run() { eckit::option::CmdArgs args(&::usage, options_, numberOfPositionalArguments(), minimumPositionalArguments()); eckit::Buffer buffer(grib::MetFile::gribBufferSize()); long len = 0; for (size_t i = 0; i < args.count(); i++) { eckit::PathName path(args(i)); std::cout << "Processing " << path << std::endl; grib::MetFile file(path); size_t nMsg = 0; while ((len = file.readSome(buffer)) != 0) { metkit::grib::GribMetaData grib(buffer, len); ++nMsg; eckit::Log::info() << nMsg << " " << grib << std::endl; } } } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { GribBlob tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/ParseRequest.cc0000664000175000017500000001533315246725757020167 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "ParseRequest.h" #include #include #include #include #include #include #include #include "eckit/log/JSON.h" #include "metkit/metkit_config.h" #include "metkit/hypercube/HyperCube.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsParser.h" #include "metkit/mars/MarsRequest.h" using namespace eckit; using namespace metkit::mars; #ifdef metkit_HAVE_MARS2MARS #include "metkit/mars2mars/api/Mars2Mars.h" #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_mars_request.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" using namespace metkit::mars2mars; namespace { class ConvertedRequests : public FlattenCallback { public: ConvertedRequests(std::vector& flattenedRequests) : flattenedRequests_(flattenedRequests) {} void operator()(const MarsRequest& req) override { flattenedRequests_.push_back(converter_.convert(req).mars); } std::vector& flattenedRequests_; Mars2Mars converter_; }; } // namespace #endif //---------------------------------------------------------------------------------------------------------------------- void ParseRequest::execute(const eckit::option::CmdArgs& args) { for (size_t i = 0; i < args.count(); i++) { process(args(i)); } } void ParseRequest::init(const CmdArgs& args) { args.get("json", json_); args.get("compact", compact_); args.get("porcelain", porcelain_); if (porcelain_) { compact_ = true; } } void ParseRequest::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] [request1] [request2] ..." << std::endl; if (grib2_) { Log::info() << " Converts requests to full GRIB2 metadata." << std::endl; } Log::info() << std::endl << "Examples:" << std::endl << "=========" << std::endl << std::endl << tool << " --json mars1.req mars2.req" << std::endl << tool << " --porcelain folderOfRequests" << std::endl << std::endl; } void ParseRequest::process(const eckit::PathName& path) { if (path.isDir()) { std::vector files; std::vector directories; path.children(files, directories); std::sort(files.begin(), files.end()); std::sort(directories.begin(), directories.end()); for (std::vector::const_iterator j = files.begin(); j != files.end(); ++j) { process(*j); } for (std::vector::const_iterator j = directories.begin(); j != directories.end(); ++j) { process(*j); } return; } if (!porcelain_) { std::cout << "==========> Parsing : " << path << std::endl; } std::ifstream in(path.asString().c_str()); MarsParser parser(in); bool inherit = true; MarsExpansion expand(inherit); auto p = parser.parse(); if (!porcelain_) { for (auto j = p.begin(); j != p.end(); ++j) { if (compact_) { j->dump(std::cout, "", ""); std::cout << std::endl; } else { j->dump(std::cout); } } std::cout << "----------> Expanding ... " << std::endl; } std::vector v = expand.expand(p); #ifdef metkit_HAVE_MARS2MARS if (grib2_) { std::vector out; for (const auto& r : v) { std::vector converted; ConvertedRequests cb(converted); expand.flatten(r, cb); if (converted.size() > 1) { std::map, std::vector> coherentRequests; // split the requests into groups of requests with the same set of metadata (but potentially different // values) for (const auto& r : converted) { std::set keys; for (const auto& p : r.parameters()) { keys.insert(p.name()); } coherentRequests[keys].push_back(r); } converted.clear(); // compact each group of requests into a single request (if possible) for (const auto& [keys, reqs] : coherentRequests) { if (reqs.size() == 1) { // it is a single field - return its request as is converted.push_back(reqs.front()); continue; } MarsRequest merged{reqs.front()}; for (size_t i = 1; i < reqs.size(); ++i) { merged.merge(reqs[i]); } if (merged.count() == reqs.size()) { // the set of fields forms a full hypercube - return // corresponding merged request converted.push_back(std::move(merged)); continue; } // sparse hypercube - we have to compute a set of compact requests describing the input fields metkit::hypercube::HyperCube h{merged}; for (const auto& r : reqs) { h.clear(r); } for (const auto& r : h.requests()) { converted.push_back(r); } } } out.insert(out.end(), converted.begin(), converted.end()); } std::swap(v, out); } #endif // HAVE_MARS2MARS for (std::vector::const_iterator j = v.begin(); j != v.end(); ++j) { if (json_) { if (compact_) { eckit::JSON jsonOut(std::cout); j->json(jsonOut); } else { eckit::JSON jsonOut(std::cout, eckit::JSON::Formatting(eckit::JSON::Formatting::BitFlags::INDENT_DICT)); j->json(jsonOut); } std::cout << std::endl; } else { if (compact_) { j->dump(std::cout, "", ""); std::cout << std::endl; } else { j->dump(std::cout); } } } } metkit-1.20.2/src/tools/odb-to-request.cc0000664000175000017500000001150015246725757020406 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include #include "eckit/filesystem/PathName.h" #include "eckit/io/FileHandle.h" #include "eckit/log/JSON.h" #include "eckit/log/Log.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/Option.h" #include "eckit/option/SimpleOption.h" #include "eckit/runtime/Tool.h" #include "eckit/utils/StringTools.h" #include "metkit/odb/OdbToRequest.h" #include "metkit/tool/MetkitTool.h" using namespace metkit; using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- class OdbToRequestTool : public MetkitTool { public: OdbToRequestTool(int argc, char** argv) : MetkitTool(argc, argv) { options_.push_back(new SimpleOption("verb", "Verb in the request, default = retrieve")); options_.push_back( new SimpleOption("database", "add database keyword to requests, default = none")); options_.push_back(new SimpleOption("source", "add source keyword to requests, default = none")); options_.push_back(new SimpleOption("target", "add target keyword to requests, default = none")); options_.push_back(new SimpleOption("one", "Merge into only one request, default = false")); options_.push_back(new SimpleOption("constant", "Only constant columns, default = true")); options_.push_back(new SimpleOption("json", "Format request in json, default = false")); } virtual ~OdbToRequestTool() {} private: // methods int minimumPositionalArguments() const { return 1; } virtual void execute(const eckit::option::CmdArgs& args); virtual void init(const CmdArgs& args); virtual void usage(const std::string& tool) const; private: // members std::vector paths_; std::string verb_ = "retrieve"; std::string database_ = ""; std::string source_ = ""; std::string target_ = ""; bool one_ = false; bool constant_ = true; bool json_ = false; }; //---------------------------------------------------------------------------------------------------------------------- void OdbToRequestTool::init(const CmdArgs& args) { args.get("one", one_); args.get("constant", constant_); args.get("verb", verb_); args.get("database", database_); args.get("source", source_); args.get("target", target_); args.get("json", json_); if (json_) { porcelain_ = true; } } void OdbToRequestTool::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] [request1] [request2] ..." << std::endl << std::endl; Log::info() << "Examples:" << std::endl << "=========" << std::endl << std::endl << tool << " --one --verb=retrieve data.odb" << std::endl << std::endl; } static void toJSON(const std::vector& requests) { JSON j(Log::info()); for (auto& r : requests) { r.json(j); } Log::info() << std::endl; } static void toStdOut(const std::vector& requests) { for (auto& r : requests) { eckit::Log::info() << r << std::endl; } } static void addKeyValue(std::vector& requests, const std::string& key, const std::string& value) { std::transform(requests.begin(), requests.end(), requests.begin(), [key, value](MarsRequest& r) -> MarsRequest { r.setValue(key, value); return r; }); } void OdbToRequestTool::execute(const eckit::option::CmdArgs& args) { PathName inFile(args(0)); FileHandle dh(inFile); dh.openForRead(); std::vector requests = odb::OdbToRequest(verb_, one_, constant_).odbToRequest(dh); if (not database_.empty()) addKeyValue(requests, "database", database_); if (StringTools::lower(verb_) == "archive") { addKeyValue(requests, "source", inFile); } if (not source_.empty()) addKeyValue(requests, "source", source_); if (not target_.empty()) addKeyValue(requests, "target", target_); if (json_) { toJSON(requests); } else { toStdOut(requests); } } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { OdbToRequestTool tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/ParseRequest.h0000664000175000017500000000312515246725757020025 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #pragma once #include #include "eckit/filesystem/PathName.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "metkit/tool/MetkitTool.h" using eckit::PathName; using eckit::option::CmdArgs; using eckit::option::SimpleOption; using metkit::MetkitTool; //---------------------------------------------------------------------------------------------------------------------- class ParseRequest : public MetkitTool { public: ParseRequest(int argc, char** argv, bool convertToGrib2 = false) : MetkitTool(argc, argv), grib2_(convertToGrib2) { options_.push_back(new SimpleOption("json", "Format request in json, default = false")); options_.push_back(new SimpleOption("compact", "Compact output, default = false")); } virtual ~ParseRequest() {} private: // methods int minimumPositionalArguments() const { return 1; } void process(const eckit::PathName& path); virtual void execute(const eckit::option::CmdArgs& args); virtual void init(const CmdArgs& args); virtual void usage(const std::string& tool) const; private: // members bool json_ = false; bool compact_ = false; bool grib2_ = false; }; metkit-1.20.2/src/tools/convert-mars-request.cc0000664000175000017500000000101215246725757021637 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "ParseRequest.h" int main(int argc, char** argv) { ParseRequest tool(argc, argv, true); return tool.start(); } metkit-1.20.2/src/tools/compare-mars-requests.py0000775000175000017500000002176115246725757022053 0ustar alastairalastair#!/usr/bin/env python3 # (C) Copyright 1996- ECMWF. # 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. # Emanuele Danovaro - ECMWF May 2021 import sys import os.path import json import datetime from dateutil.parser import parse def check_one(name, old, new): assert len(old) == 1 and len(new) == 1, f"{name.upper()}: too many values {len(old)} and {len(new)}" return old[0], new[0] def check_atMostOne(name, old, new): assert len(old) < 2 and len(new) < 2, f"{name.upper()}: too many values {len(old)} and {len(new)}" return old[0] if 0 < len(old) else None, new[0] if 0 < len(new) else None def compare_int(name, old, new): for o, n in zip(old, new): assert str(o).isnumeric() and str(n).isnumeric() and int(o) == int(n), f"{name.upper()}: value mismatch '{old}' and '{new}'" def compare_float(name, old, new): for o, n in zip(old, new): try: o = float(o) except ValueError: o = o.lower() try: n = float(n) except ValueError: n = n.lower() assert o == n, f"{name.upper()}: value mismatch '{old}' and '{new}'" def compare_intAndKeywords(name, old, new, keywords): o, n = check_atMostOne(name, old, new) if o in keywords or str(o).lower() in keywords: o = str(o).lower() if isinstance(o, str) else None if n in keywords or str(n).lower() in keywords: n = str(n).lower() if isinstance(n, str) else None assert o == n or (str(o).isnumeric() and str(n).isnumeric() and int(o) == int(n)), f"{name.upper()}: value mismatch '{old}' and '{new}'" def compare_grid(name, old, new): for o, n in zip(old, new): try: o = float(o) except ValueError: o = o.lower() try: n = float(n) except ValueError: n = n.lower() assert o == n, f"{name.upper()}: value mismatch '{old}' and '{new}'" def compare_expect(name, old, new): # 'any' or a positive integer value compare_intAndKeywords(name, old, new, ["any"]) def compare_accuracy(name, old, new): # 'off', 'av' or a positive integer value compare_intAndKeywords(name, old, new, [None, "off", "av", "n"]) def compare_frame(name, old, new): # 'off' or a positive integer value compare_intAndKeywords(name, old, new, ["off"]) def compare_truncation(name, old, new): # 'none', 'auto', 'off' or a positive integer value compare_intAndKeywords(name, old, new, ["off", "none", "auto"]) def compare_step(name, old, new): for o, n in zip(old, new): oParts = str(o).split('-') nParts = str(n).split('-') assert len(oParts) == len(nParts) and all(float(oi) == float(ni) for oi, ni in zip(oParts, nParts)), f"{name.upper()}: value mismatch '{old}' and '{new}'" def compare_str(name, old, new): o, n = check_one(name, old, new) assert o.replace('"', '').lower() == n.replace('"', '').lower(), f"{name.upper()}: string mismatch '{old[0]}' and '{new[0]}'" def compare_strList(name, old, new): for o, n in zip(old, new): compare_str(name, [o], [n]) def compare_strCaseSensitive(name, old, new): for o, n in zip(old, new): assert o.replace('"', '') == n.replace('"', ''), f"{name.upper()}: string mismatch '{old}' and '{new}'" def compare_time(name, old, new): o, n = check_one(name, old, new) if isinstance(o, str): o = int(o.replace(':', '')) if o>9999: o = o/100 if isinstance(n, str): n = int(n.replace(':', '')) if n > 9999: n = n / 100 assert o == n, f"{name.upper()}: time mismatch '{old[0]}' and '{new[0]}'" def compare_date(name, old, new): o, n = check_one(name, old, new) oStart = parse(str(o), default=datetime.datetime(1901, 1, 1)) oEnd = parse(str(o), default=datetime.datetime(2001, 12, 31)) nStart = parse(str(n), default=datetime.datetime(1901, 1, 1)) nEnd = parse(str(n), default=datetime.datetime(2001, 12, 31)) assert oStart.date() == nStart.date() and oEnd.date() == nEnd.date(), f"{name.upper()}: date mismatch '{old[0]}' and '{new[0]}'" def compare_dates(name, old, new): for o, n in zip(old, new): oStart = parse(str(o), default=datetime.datetime(1901, 1, 1)) oEnd = parse(str(o), default=datetime.datetime(2001, 12, 31)) nStart = parse(str(n), default=datetime.datetime(1901, 1, 1)) nEnd = parse(str(n), default=datetime.datetime(2001, 12, 31)) assert oStart.date() == nStart.date() and oEnd.date() == nEnd.date(), f"{name.upper()}: date mismatch '{o}' and '{n}'" def compare_expver(name, old, new): o, n = check_one(name, old, new) if isinstance(o, str): try: o = int(o) except ValueError: o = o.lower() if isinstance(n, str): try: n = int(n) except ValueError: n = n.lower() assert o == n, f"{name.upper()}: mismatch '{old[0]}' and '{new[0]}'" def ignore(name, old, new): pass def ignore_fixYaml(name, old, new): pass comparators = { "accuracy": ignore_fixYaml, # ????? # "accuracy": compare_accuracy, "anoffset": compare_int, # ????? "area": ignore_fixYaml, "bitmap": compare_strCaseSensitive, # block # channel "class": compare_str, "database": compare_str, # ????? "date": compare_date, "diagnostic": compare_int, # ?????? "direction": compare_int, # should we handle case "all" "domain": compare_str, "duplicates": compare_str, # can we ignore ? "expect": compare_expect, "expver": compare_expver, "fcmonth": compare_int, "fcperiod": compare_step, # ?????? "fieldset": compare_strCaseSensitive, "filter": compare_str, "format": compare_str, # ????? "frame": compare_frame, "frequency": compare_int, # ?????? "grid": compare_grid, "hdate": compare_dates, "ident": compare_int, "interpolation": compare_str, # "intgrid": "iteration": compare_int, # ?????? "latitude": compare_float, "levelist": compare_float, "levtype": compare_str, "longitude": compare_float, "lsm": compare_str, # ????? "method": compare_int, "number": compare_int, "obsgroup": compare_str, # ????? "obstype": compare_int, "origin": compare_expver, # ????? "packing": compare_str, "padding": compare_int, # ?????? "param": compare_float, # <-- to check that short names have been replaced, otherwise we can use compare_strList "priority": compare_int, "product": compare_str, # ????? "range": compare_int, "refdate": compare_date, "reference": compare_int, "reportype": compare_int, "repres": ignore, # resol "rotation": compare_grid, "section": compare_str, "source": compare_strCaseSensitive, "step": compare_step, "stream": compare_str, "system": compare_int, "target": compare_strCaseSensitive, "time": compare_time, "truncation": compare_truncation, "type": compare_str, "use": compare_str } def compare(name, old, new): if not isinstance(old, list): old = [old] if not isinstance(new, list): new = [new] assert len(old) == len(new), f"{name}: list mismatch {len(old)} and {len(new)}" assert name in comparators.keys(), f"{name}: not supported" comparators[name](name, old, new) # compare_date('date', '20210501', '2021-05-01') # compare_date('date', '20210501', '2021-May-01') # compare_date('date', '20210501', '2021-may-01') # compare_date('date', 'May-1', 'may-01') # compare_date('date', 'May', 'may') # compare_date('date', 'May', '20210521') # check command line parameters if len(sys.argv) != 3 or not os.path.isfile(sys.argv[1]) or not os.path.isfile(sys.argv[2]): print('Please specify the json files to be compared.') print('Usage: ',sys.argv[0], ' ') if len(sys.argv)>1 and not os.path.isfile(sys.argv[1]): print(sys.argv[1], 'is not a file') if len(sys.argv)>2 and not os.path.isfile(sys.argv[2]): print(sys.argv[2], 'is not a file') exit(1) #open json files and build case insensitive dictionaries json_lines_1 = [] with open(sys.argv[1]) as json_file: for line in json_file: j = {} for key, value in json.loads(line).items(): j[key.lower()] = value json_lines_1.append(j) json_lines_2 = [] with open(sys.argv[2]) as json_file: for line in json_file: j = {} for key, value in json.loads(line).items(): j[key.lower()] = value json_lines_2.append(j) assert len(json_lines_1) == len(json_lines_2), f"Json mismatch: {sys.argv[1]} contains {len(json_lines_1)} while {sys.argv[2]} contains {len(json_lines_2)}" for j1, j2 in zip(json_lines_1, json_lines_2): for key in set(j1.keys()) | set(j2.keys()): compare(key, j1.get(key), j2.get(key)) metkit-1.20.2/src/tools/grib-to-product-time.cc0000664000175000017500000003116515246725757021522 0ustar alastairalastair/* * (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 grib1-to-grib2.cc /// @brief CLI tool for converting grib1 to grib2 files. /// #include #include #include #include #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/io/DataHandle.h" #include "eckit/io/FileHandle.h" #include "eckit/io/MemoryHandle.h" #include "eckit/log/Log.h" #include "eckit/message/Message.h" #include "eckit/message/Reader.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/EckitTool.h" #include "eckit/option/SimpleOption.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/api/Grib2Mars.h" #include "metkit/mars2grib/api/Mars2Grib.h" #include "metkit/mars2mars/api/Mars2Mars.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsEnum.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsMatcher.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- class GribToProductTime final : public eckit::EckitTool { public: GribToProductTime(int argc, char** argv); ~GribToProductTime() override = default; private: int minimumPositionalArguments() const override { return 2; } void init(const CmdArgs& args) override; void execute(const CmdArgs& args) override; void usage(const std::string& tool) const override; bool skipDiscipline192_ = false; std::optional expver_ = std::nullopt; std::optional generatingProcessIdentifier_ = std::nullopt; }; //---------------------------------------------------------------------------------------------------------------------- GribToProductTime::GribToProductTime(int argc, char** argv) : eckit::EckitTool(argc, argv) { options_.push_back(new eckit::option::SimpleOption("help", "Print this help message")); // Input handling options_.push_back( new eckit::option::SimpleOption("skip-discipline-192", "Skip discipline 192 input messages")); // Override values options_.push_back(new eckit::option::SimpleOption("expver", "Override expver")); options_.push_back( new eckit::option::SimpleOption("generatingProcessIdentifier", "Override generatingProcessIdentifier")); } void GribToProductTime::init(const CmdArgs& args) { skipDiscipline192_ = args.has("skip-discipline-192"); if (args.has("expver")) { std::string expver; args.get("expver", expver); expver_ = expver; } if (args.has("generatingProcessIdentifier")) { long generatingProcessIdentifier; args.get("generatingProcessIdentifier", generatingProcessIdentifier); generatingProcessIdentifier_ = generatingProcessIdentifier; } } void GribToProductTime::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] input output" << std::endl << std::endl << "Convert (pre-MTG2) GRIB1 to (post-MTG2) GRIB2" << std::endl << std::endl; } //---------------------------------------------------------------------------------------------------------------------- std::unique_ptr readCodesHandle(eckit::message::Message& msg) { std::unique_ptr dataHandle{msg.readHandle()}; eckit::MemoryHandle* memoryHandle = reinterpret_cast(dataHandle.get()); if (memoryHandle == nullptr) { throw eckit::UserError("Could not read eckit::Message", Here()); } const auto* data = reinterpret_cast(memoryHandle->data()); const auto size = static_cast(memoryHandle->size()); return metkit::codes::codesHandleFromMessageCopy(metkit::codes::Span(data, size)); } eckit::LocalConfiguration mergeLocalConfigs(const eckit::LocalConfiguration& base, const eckit::LocalConfiguration& overwrite) { eckit::LocalConfiguration result{base}; for (const auto& key : overwrite.keys()) { if (overwrite.isString(key)) { result.set(key, overwrite.getString(key)); } else if (overwrite.isIntegral(key)) { result.set(key, overwrite.getLong(key)); } else if (overwrite.isFloatingPoint(key)) { result.set(key, overwrite.getDouble(key)); } else if (overwrite.isBoolean(key)) { result.set(key, overwrite.getBool(key)); } else if (overwrite.isFloatingPointList(key)) { result.set(key, overwrite.getDoubleVector(key)); } else { throw eckit::NotImplemented("Unexpected type for '" + key + "'", Here()); } } return result; } metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing mars2TypeOfStatisticalProcessing( const eckit::LocalConfiguration& mars, const metkit::mars2grib::Options& opt) { using metkit::mars2grib::backend::concepts_::statisticsMatcher; using metkit::mars2grib::backend::concepts_::StatisticsType; const std::size_t typeOfStatisticalProcessingId = statisticsMatcher(mars, opt); if (typeOfStatisticalProcessingId == metkit::mars2grib::backend::compile_time_registry_engine::MISSING) { // Hack for instant fields return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Missing; } else { const StatisticsType sel = static_cast(typeOfStatisticalProcessingId); switch (sel) { case StatisticsType::Average: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Average; case StatisticsType::Accumulation: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Accumulation; case StatisticsType::Maximum: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Maximum; case StatisticsType::Minimum: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Minimum; case StatisticsType::DifferenceFromStart: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::DifferenceEndMinusStart; case StatisticsType::RootMeanSquare: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::RootMeanSquare; case StatisticsType::StandardDeviation: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::StandardDeviation; case StatisticsType::Covariance: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Covariance; case StatisticsType::DifferenceFromEnd: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::DifferenceStartMinusEnd; case StatisticsType::Ratio: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Ratio; case StatisticsType::StandardizedAnomaly: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::StandardizedAnomaly; case StatisticsType::Summation: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Summation; case StatisticsType::ReturnPeriod: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::ReturnPeriod; case StatisticsType::Median: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Median; case StatisticsType::Severity: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Severity; case StatisticsType::Mode: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Mode; case StatisticsType::IndexProcessing: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::IndexProcessing; case StatisticsType::Default: return metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Missing; } throw eckit::UserError("Unexpected StatisticsType value: " + std::to_string(static_cast(sel)), Here()); } } //---------------------------------------------------------------------------------------------------------------------- void GribToProductTime::execute(const CmdArgs& args) { // Handles to conversion libraries metkit::grib2mars::Grib2Mars grib2mars; metkit::mars2mars::Mars2Mars mars2mars; metkit::mars2grib::Mars2Grib mars2grib; auto inputPath = eckit::PathName(args(0)); auto outputPath = eckit::PathName(args(1)); if (!inputPath.exists()) { throw eckit::UserError("Input file path '" + inputPath.path() + "' does not exist!", Here()); } // Remove the output file if it already exists, otherwise we are not able to write to it. if (outputPath.exists()) { remove(outputPath.asString().c_str()); } metkit::mars2grib::Options opts; eckit::message::Reader msgReader{args(0)}; eckit::message::Message msg; std::ofstream outFile(outputPath.asString()); if (!outFile.is_open()) { throw eckit::UserError("Failed to open output file for writing", Here()); } std::string separator = "["; while ((msg = msgReader.next())) { auto codesHandle = readCodesHandle(msg); if (skipDiscipline192_ && codesHandle->getLong("discipline") == 192) { continue; } // Read the MARS/Misc dictionary from the input GRIB sample const auto originalMarsMisc = grib2mars.convert(*codesHandle); // Read the values from the input GRIB sample const std::vector values = codesHandle->getDoubleArray("values"); // Apply mappings to convert pre-MTG2 MARS/Misc to post-MTG2 MARS/Misc const auto mappedMarsMisc = mars2mars.convert(originalMarsMisc.mars); auto mars = mappedMarsMisc.mars; auto misc = mergeLocalConfigs(mappedMarsMisc.misc, originalMarsMisc.misc); // Override values if specified by the user in the arguments if (expver_) { mars.set("expver", *expver_); } if (generatingProcessIdentifier_) { misc.set("generatingProcessIdentifier", *generatingProcessIdentifier_); } // Get type of statistical processing from the MARS dictionary const auto innerTypeOfStatisticalProcessing = mars2TypeOfStatisticalProcessing(mars, opts); // Generate productTimeSpec auto timeSpec = metkit::mars2grib::backend::models::ProductTimeSpec(innerTypeOfStatisticalProcessing, mars, misc, opts); // Generate a json out dictionary { std::string jsonMars = metkit::mars2grib::utils::dict_traits::dict_to_json(mars); std::string jsonMisc = metkit::mars2grib::utils::dict_traits::dict_to_json(misc); std::string jsonTimeSpec = timeSpec.to_json(); std::string jsonOut = "{ \"mars\": " + jsonMars + ", \"misc\": " + jsonMisc + ", \"productTimeSpec\": " + jsonTimeSpec + " }"; outFile << separator << jsonOut; separator = ",\n"; } } outFile << "]" << std::endl; outFile.close(); } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { GribToProductTime tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/bufr-sanity-check.cc0000664000175000017500000003672215246725757021067 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eccodes.h" #include "eckit/config/Resource.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/FileHandle.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "eckit/types/Date.h" #include "eckit/message/Message.h" #include "eckit/message/Reader.h" #include "metkit/codes/BUFRDecoder.h" #include "metkit/codes/BufrContent.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/tool/MetkitTool.h" #define WRONG_KEY_LENGTH 65535 using namespace metkit; using namespace eckit; using namespace eckit::option; //---------------------------------------------------------------------------------------------------------------------- enum Status { OK, FIXED, CORRUPTED }; class BufrCheck : public MetkitTool { public: BufrCheck(int argc, char** argv) : MetkitTool(argc, argv) { options_.push_back( new SimpleOption("abort-on-error", "Abort in case of corrupted message, default = true")); options_.push_back( new SimpleOption("patch-on-error", "Try to patch corrupted messages, default = false")); options_.push_back(new SimpleOption("skip-on-error", "Skip corrupted messages, default = false")); options_.push_back(new SimpleOption( "dont-patch-length", "Disable patching of message length in corrupted messages, default = false")); options_.push_back(new SimpleOption( "dont-patch-date", "Disable patching of date/time in corrupted messages, default = false")); options_.push_back(new SimpleOption( "ignore-century", "Disable patching of century in corrupted messages, default = false")); options_.push_back(new SimpleOption("ignore-type", "Ignore inconsistent type/subtype, default = false")); options_.push_back(new SimpleOption("acceptable-time-discrepancy", "Acceptable time discrepancy in seconds, default = 300")); options_.push_back( new SimpleOption("verbose", "Print details of all corrupted messages, default = false")); } virtual ~BufrCheck() {} private: // methods int minimumPositionalArguments() const { return 2; } Status checkMessageLength(const message::Message& msg, int numMessage, eckit::OrderedStringDict& transformation); Status checkSubType(const message::Message& msg, int numMessage); Status checkDate(const message::Message& msg, int numMessage, eckit::OrderedStringDict& transformation); void process(const PathName& input, const PathName& output); virtual void execute(const CmdArgs& args); virtual void init(const CmdArgs& args); virtual void usage(const std::string& tool) const; private: // members bool verbose_; bool abort_; bool patch_; bool skip_; bool ignoreLength_; bool ignoreDate_; bool ignoreCentury_; bool ignoreType_; long timeThreshold_; }; //---------------------------------------------------------------------------------------------------------------------- void BufrCheck::execute(const CmdArgs& args) { process(args(0), args(1)); } void BufrCheck::init(const CmdArgs& args) { patch_ = args.getBool("patch-on-error", false); skip_ = args.getBool("skip-on-error", false); abort_ = args.getBool("abort-on-error", !(patch_ || skip_)); if (abort_ + patch_ + skip_ > 1) { throw UserError( "Inconsistent configuration. You can only specify one of [--abort-on-error, --patch-on-error, " "--skip-on-error]"); } verbose_ = args.getBool("verbose", false); ignoreLength_ = args.getBool("dont-patch-length", false); ignoreDate_ = args.getBool("dont-patch-date", false); ignoreCentury_ = args.getBool("ignore-century", false); ignoreType_ = args.getBool("ignore-type", false); timeThreshold_ = args.getLong("acceptable-time-discrepancy", 300); } void BufrCheck::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << " [options] [input] [output]" << std::endl << std::endl; Log::info() << "Examples:" << std::endl << "=========" << std::endl << std::endl << tool << " input.bufr output.bufr" << std::endl << std::endl << tool << " --skip-on-error --verbose input.bufr output.bufr" << std::endl << std::endl << tool << " --patch-on-error --ignore-century --acceptable-time-discrepancy=600 input.bufr output.bufr" << std::endl << std::endl << tool << " --patch-on-error --dont-patch-date input.bufr output.bufr" << std::endl << std::endl; } Status BufrCheck::checkMessageLength(const message::Message& msg, int numMessage, eckit::OrderedStringDict& transformation) { long totalLength = msg.getLong("totalLength"); long messageLength = msg.getLong("messageLength"); if (totalLength != messageLength && totalLength < WRONG_KEY_LENGTH) { if (verbose_) { Log::error() << "message " << numMessage << ", wrong key length in bufr message " << messageLength << " instead of " << totalLength << std::endl; } if (!patch_ || ignoreLength_) { return Status::CORRUPTED; } else { transformation.emplace_back("messageLength", std::to_string(totalLength)); return Status::FIXED; } } return Status::OK; } Status BufrCheck::checkSubType(const message::Message& msg, int numMessage) { long type = msg.getLong("rdbType"); long subtype = msg.getLong("oldSubtype"); long expectedType; if (codes::BUFRDecoder::typeBySubtype(subtype, expectedType)) { if (type == expectedType) { return Status::OK; } else { if (verbose_ || !ignoreType_) { Log::error() << "message " << numMessage << ", type " << type << " and expected type " << expectedType << " don't match for subtype " << subtype << std::endl; } return Status::CORRUPTED; } } else { if (verbose_ || !ignoreType_) { Log::error() << "message " << numMessage << ", unknown subtype " << subtype << std::endl; } return Status::CORRUPTED; } } Status BufrCheck::checkDate(const message::Message& msg, int numMessage, eckit::OrderedStringDict& transformation) { bool toFix = false; long localYear = msg.getLong("localYear"); long typicalYear = msg.getLong("typicalYear"); if (ignoreCentury_) { typicalYear = (localYear / 100) * 100 + typicalYear % 100; } long typicalMonth = msg.getLong("typicalMonth"); long typicalDay = msg.getLong("typicalDay"); long typicalJulian = 0; try { Date date(typicalYear, typicalMonth, typicalDay); typicalJulian = date.julian(); } catch (...) { if (verbose_) { Log::error() << "message " << numMessage << ", date is weird " << typicalYear << "/" << typicalMonth << "/" << typicalDay << std::endl; } if (!patch_) { return Status::CORRUPTED; } toFix = !ignoreDate_; } long typicalHour = msg.getLong("typicalHour"); long typicalMinute = msg.getLong("typicalMinute"); long typicalSecond = msg.getLong("typicalSecond"); if (typicalHour > 23 || typicalHour < 0 || typicalMinute > 59 || typicalMinute < 0 || typicalSecond > 59 || typicalSecond < 0) { if (verbose_) { Log::error() << "message " << numMessage << ", typical time is weird " << typicalHour << ":" << typicalMinute << ":" << typicalSecond << std::endl; } if (!patch_) { return Status::CORRUPTED; } toFix = !ignoreDate_; } long typicalTime = typicalHour * 3600 + typicalMinute * 60 + typicalSecond; long localMonth = msg.getLong("localMonth"); long localDay = msg.getLong("localDay"); long localJulian = 0; try { Date date(localYear, localMonth, localDay); localJulian = date.julian(); } catch (...) { if (verbose_) { Log::error() << "message " << numMessage << ", date is weird " << localYear << "/" << localMonth << "/" << localDay << std::endl; } return Status::CORRUPTED; } long localHour = msg.getLong("localHour"); long localMinute = msg.getLong("localMinute"); long localSecond = msg.getLong("localSecond"); // we accept localSecond==60 for backward compatibility (filterbufr behaviour) if (localHour > 23 || localHour < 0 || localMinute > 59 || localMinute < 0 || localSecond > 60 || localSecond < 0) { if (verbose_) { Log::error() << "message " << numMessage << ", local time is weird " << localHour << ":" << localMinute << ":" << localSecond << std::endl; } return Status::CORRUPTED; } long localTime = localHour * 3600 + localMinute * 60 + localSecond; if (abs((typicalJulian - localJulian) * 86400 + typicalTime - localTime) > timeThreshold_) { if (verbose_) { Log::error() << "message " << numMessage << ", date-time (" << typicalYear << "/" << typicalMonth << "/" << typicalDay << " " << typicalHour << ":" << typicalMinute << ":" << typicalSecond << ") and local date-time (" << localYear << "/" << localMonth << "/" << localDay << " " << localHour << ":" << localMinute << ":" << localSecond << ") differs" << std::endl; } toFix = !ignoreDate_; } if (toFix) { if (msg.getLong("edition") == 3) { transformation.emplace_back("typicalYearOfCentury", std::to_string(localYear - 2000)); } else { transformation.emplace_back("typicalYear", std::to_string(localYear)); transformation.emplace_back("typicalSecond", std::to_string(localSecond)); } transformation.emplace_back("typicalMonth", std::to_string(localMonth)); transformation.emplace_back("typicalDay", std::to_string(localDay)); transformation.emplace_back("typicalHour", std::to_string(localHour)); transformation.emplace_back("typicalMinute", std::to_string(localMinute)); return Status::FIXED; } return Status::OK; } void BufrCheck::process(const PathName& input, const PathName& output) { message::Reader reader(input); FileHandle out(output.path()); Offset pos; out.openForWrite(0); AutoClose closer(out); int err = 0; void* buffer = NULL; size_t size = 0; bool ok; unsigned int numMessage = 0; unsigned int missingKey = 0; unsigned int messageLength = 0; unsigned int inconsistentSubType = 0; unsigned int inconsistentDate = 0; message::Message rawMsg; do { try { pos = reader.position(); if ((rawMsg = reader.next())) { auto h = codes::codesHandleFromMessage({reinterpret_cast(rawMsg.data()), rawMsg.length()}); if (!h) { throw FailedLibraryCall("eccodes", "codes_handle_new_from_message", "failed to create handle", Here()); } codes::BufrContent* c = new codes::BufrContent(std::move(h)); message::Message msg(c); // verify the presence of section 2 (to store the MARS key) long localSectionPresent = msg.getLong("localSectionPresent"); ok = localSectionPresent != 0; if (!ok) { missingKey++; } else { eckit::OrderedStringDict transformation; switch (checkMessageLength(msg, numMessage, transformation)) { case Status::CORRUPTED: ok = false; messageLength++; break; case Status::FIXED: messageLength++; break; case Status::OK: break; }; switch (checkSubType(msg, numMessage)) { case Status::CORRUPTED: if (!ignoreType_) { ok = false; } inconsistentSubType++; break; case Status::FIXED: inconsistentSubType++; break; case Status::OK: break; }; switch (checkDate(msg, numMessage, transformation)) { case Status::CORRUPTED: ok = false; inconsistentDate++; break; case Status::FIXED: inconsistentDate++; break; case Status::OK: break; }; if (ok) { if (transformation.size() != 0) { msg.transform(transformation); } msg.write(out); } } if (!ok && abort_) { Log::error() << "message " << numMessage << " not compliant" << std::endl; exit(1); } numMessage++; } } catch (eckit::Exception& e) { Log::warning() << " Error parsing message " << numMessage << " - offset " << pos << std::endl; if (verbose_) { e.dumpStackTrace(Log::warning()); } } } while (pos != reader.position()); if (missingKey) Log::warning() << missingKey << " message" << (missingKey > 1 ? "s miss " : " misses ") << " the MARS key" << std::endl; if (messageLength) Log::warning() << messageLength << " message" << (messageLength > 1 ? "s " : " ") << " with incoherent message length in the MARS key" << std::endl; if (inconsistentSubType) Log::warning() << inconsistentSubType << " message" << (inconsistentSubType > 1 ? "s " : " ") << " with unknown or inconsistent subtype" << std::endl; if (inconsistentDate) Log::warning() << inconsistentDate << " message" << (inconsistentDate > 1 ? "s " : " ") << " with inconsistent date" << std::endl; } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { BufrCheck tool(argc, argv); return tool.start(); } metkit-1.20.2/src/tools/nccompare.py0000775000175000017500000000747315246725757017567 0ustar alastairalastair#!/usr/bin/env python # (C) Copyright 1996- ECMWF. # 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. # Baudouin Raoult - ECMWF Jan 2015 # See http://unidata.github.io/netcdf4-python/netCDF4.Dataset-class.html from netCDF4 import Dataset import sys import numpy ERRORS = 0 def error(text): global ERRORS ERRORS += 1 print "+ ERROR:", text def mismatch(title, v1, v2): global ERRORS ERRORS += 1 print "+ MISMATCH:", title print " FILE:", sys.argv[1] print " VALUE:", v1 print " FILE:", sys.argv[2] print " VALUE:", v2 def compare_lists(what, l1, l2): s1 = set(l1) s2 = set(l2) if len(s1 - s2): error("%s [%s] only in %s" % (what, ",".join([str(x) for x in list(s1-s2)]), sys.argv[1])) return False if len(s2 - s1): error("%s [%s] only in %s" % (what, ",".join([str(x) for x in list(s2-s1)]), sys.argv[2])) return False return True def compare_attributes(ns, name, a1, a2): if type(a1) != type(a2): mismatch("Type of attributes '%s:%s'" % (ns, name,), type(a1), type(a2)) if a1 != a2: a1 = str(a1) a2 = str(a2) if len(a1) > 80: a1 = a1[:79] + '...' if len(a2) > 80: a2 = a2[:79] + '...' mismatch("Value of attributes '%s:%s'" % (ns, name,), a1, a2) def compare_variables(name, v1, v2): compare_lists("Dimensions for '%s'" % (name,), v1.dimensions, v2.dimensions) if v1.datatype != v2.datatype: mismatch("Type of variable '%s'" % (name,), v1.datatype, v2.datatype) if compare_lists("Variable '%s' attributes" % (name,), v1.ncattrs(), v2.ncattrs()): for a in v1.ncattrs(): compare_attributes(name, a, v1.getncattr(a), v2.getncattr(a)) vals1 = v1[:] vals2 = v2[:] if vals1.size != vals2.size: mismatch("Size of variable '%s'" % (name,), vals1.size, vals2.size) if not (vals1 == vals2).all(): vals1 = vals1.flatten() vals2 = vals2.flatten() idx = numpy.nonzero(vals1 - vals2)[0][0] if idx < len(vals1) and idx < len(vals2): mismatch("Value for variable '%s', first difference at index %s" % (name, idx), vals1[idx], vals2[idx]) def compare_dimensions(name, d1, d2): if len(d1) != len(d2): mismatch("Length of dimension '%s'" % (name,), len(d1), len(d2)) def check_file(nc, path): if len(nc.groups): error("Groups not supported (file %s)" % (path,)) if len(nc.cmptypes): error("Compound types not supported (file %s)" % (path,)) if len(nc.vltypes): error("Variable-length types not supported (file %s)" % (path,)) def compare_files(nc1, nc2): check_file(nc1, sys.argv[1]) check_file(nc2, sys.argv[2]) dims1 = nc1.dimensions dims2 = nc2.dimensions if compare_lists("Dimensions", dims1.keys(), dims2.keys()): for k in dims2.keys(): compare_dimensions(k, dims1[k], dims2[k]) vars1 = nc1.variables vars2 = nc2.variables if compare_lists("Variables", vars1.keys(), vars2.keys()): for k in vars1.keys(): compare_variables(k, vars1[k], vars2[k]) if compare_lists("Global attributes", nc1.ncattrs(), nc2.ncattrs()): for a in nc1.ncattrs(): compare_attributes("", a, nc1.getncattr(a), nc2.getncattr(a)) # Other things to checks # print nc1.data_model, nc1.disk_format # print nc1.parent, nc1.vltypes nc1 = Dataset(sys.argv[1], 'r') nc2 = Dataset(sys.argv[2], 'r') compare_files(nc1, nc2) if ERRORS: sys.exit(1) metkit-1.20.2/src/tools/CMakeLists.txt0000664000175000017500000000634215246725757017775 0ustar alastairalastair### provides MARS requests from GRIB files # ecbuild_add_executable( TARGET grib-blob # CONDITION HAVE_GRIB # SOURCES grib-blob.cc # INCLUDES ${ECKIT_INCLUDE_DIRS} # LIBS metkit eckit_option eckit # ) ecbuild_add_executable( TARGET grib-to-mtg2 SOURCES grib-to-mtg2.cc CONDITION HAVE_MARS2GRIB AND HAVE_MARS2MARS AND HAVE_BUILD_GRIB2_TOOLS INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option eckit ) ecbuild_add_executable( TARGET convert-mars-request CONDITION HAVE_MARS2MARS AND HAVE_BUILD_GRIB2_TOOLS SOURCES convert-mars-request.cc ParseRequest.cc ParseRequest.h INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option eckit ) # ecbuild_add_executable( # TARGET grib-to-productTime # SOURCES grib-to-product-time.cc # CONDITION HAVE_MARS2GRIB AND HAVE_MARS2MARS AND HAVE_BUILD_TOOLS # INCLUDES ${ECKIT_INCLUDE_DIRS} # NO_AS_NEEDED # LIBS metkit eckit_option eckit # ) ecbuild_add_executable( TARGET parse-mars-request CONDITION HAVE_BUILD_TOOLS SOURCES parse-mars-request.cc ParseRequest.cc ParseRequest.h INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option eckit ) ecbuild_add_executable( TARGET odb-to-request SOURCES odb-to-request.cc CONDITION HAVE_ODB AND HAVE_BUILD_TOOLS INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option ) ecbuild_add_executable( TARGET grib-to-request SOURCES grib-to-request.cc CONDITION HAVE_GRIB AND HAVE_BUILD_TOOLS INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option ) ecbuild_add_executable( TARGET bufr-sanity-check SOURCES bufr-sanity-check.cc CONDITION HAVE_BUFR AND HAVE_BUILD_TOOLS INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit eckit_option ) ecbuild_add_executable( TARGET mars-archive-script SOURCES mars-archive-script.cc CONDITION HAVE_BUILD_TOOLS INCLUDES ${ECKIT_INCLUDE_DIRS} NO_AS_NEEDED LIBS metkit ) # ecbuild_add_executable( # TARGET check-mars2conf # CONDITION HAVE_MARS2GRIB # SOURCES check-mars2conf.cc # INCLUDES ${ECKIT_INCLUDE_DIRS} # NO_AS_NEEDED # LIBS metkit # ) # ecbuild_add_executable( # TARGET ncmerge # SOURCES ncmerge.cc # CONDITION HAVE_NETCDF # LIBS metkit NetCDF::NetCDF_C ) if(HAVE_BUILD_TOOLS) list( APPEND tools nccompare.py compare-mars-requests.py ) foreach( _tool ${tools} ) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/${_tool} DESTINATION ${CMAKE_BINARY_DIR}/bin FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) endforeach() install( FILES ${CMAKE_BINARY_DIR}/bin/compare-mars-requests.py DESTINATION "bin" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ ) endif() metkit-1.20.2/src/metkit/0000775000175000017500000000000015246725757015365 5ustar alastairalastairmetkit-1.20.2/src/metkit/fields/0000775000175000017500000000000015246725757016633 5ustar alastairalastairmetkit-1.20.2/src/metkit/fields/FieldIndexList.cc0000664000175000017500000000315615246725757022016 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/exception/Exceptions.h" #include "metkit/fields/FieldIndexList.h" #include "metkit/fields/SimpleFieldIndex.h" namespace metkit { namespace fields { FieldIndexList::~FieldIndexList() { for (size_t i = 0; i < fields_.size(); ++i) { FieldIndex* h = fields_[i]; delete h; } } void FieldIndexList::readFrom(eckit::Stream& s) { ASSERT(length_.size() == 0); ASSERT(offset_.size() == 0); ASSERT(fields_.size() == 0); unsigned long count; s >> count; length_.resize(count); offset_.resize(count); fields_.resize(count); for (size_t i = 0; i < count; ++i) { unsigned long long x; s >> x; offset_[i] = x; s >> x; length_[i] = x; fields_[i] = new SimpleFieldIndex(s); } } void FieldIndexList::sendTo(eckit::Stream& s) const { ASSERT(length_.size() == offset_.size()); ASSERT(offset_.size() == fields_.size()); unsigned long count = length_.size(); s << count; for (size_t i = 0; i < count; ++i) { unsigned long long o = offset_[i]; unsigned long long l = length_[i]; s << o; s << l; fields_[i]->encode(s); } } } // namespace fields } // namespace metkit metkit-1.20.2/src/metkit/fields/SimpleFieldIndex.cc0000664000175000017500000000117715246725757022335 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/fields/SimpleFieldIndex.h" namespace metkit { namespace fields { SimpleFieldIndex::SimpleFieldIndex(eckit::Stream& s) : FieldIndex(s) {} SimpleFieldIndex::~SimpleFieldIndex() {} } // namespace fields } // namespace metkit metkit-1.20.2/src/metkit/fields/FieldIndex.h0000664000175000017500000000277215246725757021027 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #ifndef metkit_FieldIndex_H #define metkit_FieldIndex_H #include "eckit/memory/NonCopyable.h" #include "eckit/serialisation/Stream.h" namespace eckit { namespace message { class Message; } } // namespace eckit namespace metkit { namespace fields { class FieldIndex : private eckit::NonCopyable { public: FieldIndex(); FieldIndex(const eckit::message::Message&); FieldIndex(eckit::Stream&); virtual ~FieldIndex(); void getValue(const std::string& name, double& value); void getValue(const std::string& name, long& value); void getValue(const std::string& name, std::string& value); std::string substitute(const std::string& pattern) const; void encode(eckit::Stream&) const; void setValue(const std::string& name, double value); void setValue(const std::string& name, long value); void setValue(const std::string& name, const std::string& value); protected: // members std::map stringValues_; std::map longValues_; std::map doubleValues_; }; } // namespace fields } // namespace metkit #endif metkit-1.20.2/src/metkit/fields/FieldIndex.cc0000664000175000017500000000741215246725757021161 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "eckit/exception/Exceptions.h" #include "eckit/message/Message.h" #include "eckit/utils/StringTools.h" #include "metkit/fields/FieldIndex.h" namespace metkit { namespace fields { FieldIndex::FieldIndex() {} FieldIndex::FieldIndex(eckit::Stream& s) { bool b, more; std::string key; std::string str; long ival; double dval; s >> more; while (more) { s >> key; s >> b; if (b) { s >> str; stringValues_[key] = str; } s >> b; if (b) { s >> ival; longValues_[key] = ival; } s >> b; if (b) { s >> dval; doubleValues_[key] = dval; } s >> more; } } FieldIndex::FieldIndex(const eckit::message::Message& message) { eckit::message::TypedSetter gather(*this); message.getMetadata(gather); } void FieldIndex::encode(eckit::Stream& s) const { std::set keys; for (auto j = stringValues_.begin(); j != stringValues_.end(); ++j) { keys.insert((*j).first); } for (auto j = longValues_.begin(); j != longValues_.end(); ++j) { keys.insert((*j).first); } for (auto j = doubleValues_.begin(); j != doubleValues_.end(); ++j) { keys.insert((*j).first); } for (auto j = keys.begin(); j != keys.end(); ++j) { const std::string& key = (*j); s << bool(true); s << key; auto js = stringValues_.find(key); s << bool(js != stringValues_.end()); if (js != stringValues_.end()) { s << (*js).second; } auto ls = longValues_.find(key); s << bool(ls != longValues_.end()); if (ls != longValues_.end()) { s << (*ls).second; } auto ds = doubleValues_.find(key); s << bool(ds != doubleValues_.end()); if (ds != doubleValues_.end()) { s << (*ds).second; } } s << bool(false); } FieldIndex::~FieldIndex() {} std::string FieldIndex::substitute(const std::string& pattern) const { return eckit::StringTools::substitute(pattern, stringValues_); } void FieldIndex::getValue(const std::string& key, double& value) { std::map::iterator j = doubleValues_.find(key); if (j == doubleValues_.end()) throw eckit::UserError(std::string("FieldIndex::getDouble failed for [") + key + "]"); value = (*j).second; } void FieldIndex::getValue(const std::string& key, long& value) { std::map::iterator j = longValues_.find(key); if (j == longValues_.end()) throw eckit::UserError(std::string("FieldIndex::getLong failed for [") + key + "]"); value = (*j).second; } void FieldIndex::getValue(const std::string& key, std::string& value) { std::map::iterator j = stringValues_.find(key); if (j == stringValues_.end()) throw eckit::UserError(std::string("FieldIndex::getString failed for [") + key + "]"); value = (*j).second; } void FieldIndex::setValue(const std::string& name, double value) { doubleValues_[name] = value; } void FieldIndex::setValue(const std::string& name, long value) { longValues_[name] = value; } void FieldIndex::setValue(const std::string& name, const std::string& value) { stringValues_[name] = value; } } // namespace fields } // namespace metkit metkit-1.20.2/src/metkit/fields/SimpleFieldIndex.h0000664000175000017500000000167515246725757022202 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #ifndef metkit_SimpleFieldIndex_H #define metkit_SimpleFieldIndex_H #include "metkit/fields/FieldIndex.h" namespace metkit { namespace fields { // struct grib_handle; class SimpleFieldIndex : public FieldIndex { public: // -- Contructors SimpleFieldIndex(eckit::Stream&); // -- Destructor virtual ~SimpleFieldIndex() override; // -- Methods private: // members // friend std::ostream& operator<<(std::ostream& s,const SimpleFieldIndex& p) // { p.print(s); return s; } }; } // namespace fields } // namespace metkit #endif metkit-1.20.2/src/metkit/fields/FieldIndexList.h0000664000175000017500000000161715246725757021660 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #ifndef metkit_FieldIndexList_H #define metkit_FieldIndexList_H #include "eckit/io/Offset.h" #include "eckit/serialisation/Stream.h" #include "eckit/types/Types.h" namespace metkit { namespace fields { class FieldIndex; class FieldIndexList { public: ~FieldIndexList(); void readFrom(eckit::Stream& s); void sendTo(eckit::Stream& s) const; eckit::OffsetList offset_; eckit::LengthList length_; std::vector fields_; }; } // namespace fields } // namespace metkit #endif metkit-1.20.2/src/metkit/mars2grib/0000775000175000017500000000000015246725757017255 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/0000775000175000017500000000000015246725757021074 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/make_HeaderLayout.h0000664000175000017500000001347415246725757024641 0ustar alastairalastair/* * (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 make_HeaderLayout.h /// @brief Top-level factory for GRIB header structural resolution. /// /// This header defines the entry point for the resolution subsystem. It /// coordinates the semantic inference of concepts and the subsequent /// mapping of those concepts to a physical GRIB section layout. /// /// @ingroup mars2grib_frontend_resolution /// #pragma once // System includes #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h" #include "metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h" #include "metkit/mars2grib/frontend/GribHeaderLayoutData.h" #include "metkit/mars2grib/frontend/resolution/resolveActiveConcepts.h" #include "metkit/mars2grib/frontend/resolution/resolveSectionsLayout.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::frontend { /// /// @brief Orchestrates the complete resolution of a GRIB message blueprint. /// /// This factory function executes the two-stage resolution pipeline: /// * 1. **Semantic Resolution**: Infers which concepts and variants are /// active based on the input MARS and Options dictionaries. /// 2. **Structural Resolution**: Maps those active concepts to specific /// GRIB sections and selects the appropriate GRIB templates. /// /// The resulting @ref GribHeaderLayoutData is a POD-like structure suitable /// for move-construction into the @ref SpecializedEncoder. /// /// ------------------------------------------------------------------------ /// * @section resolution_pipeline Data Flow /// /// 1. `MarsDict_t` / `OptDict_t` -> @ref resolve_ActiveConcepts_or_throw /// 2. `ActiveConceptsData` -> @ref resolve_SectionsLayout_or_throw /// 3. `SectionLayoutData[]` -> @ref GribHeaderLayoutData /// /// ------------------------------------------------------------------------ /// /// @tparam MarsDict_t Type of the MARS dictionary. /// @tparam OptDict_t Type of the encoding options dictionary. /// * @param[in] marsDict Input MARS request. /// @param[in] optDict Encoder configuration and options. /// * @return A fully resolved @ref GribHeaderLayoutData. /// @throws Mars2GribHeaderLayoutException if any phase of the resolution fails. /// template GribHeaderLayoutData make_HeaderLayout_or_throw(const MarsDict_t& marsDict, const OptDict_t& optDict) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; using metkit::mars2grib::backend::sections::resolver::ActiveConceptsData; using metkit::mars2grib::frontend::GribHeaderLayoutData; using metkit::mars2grib::frontend::resolution::resolve_ActiveConcepts_or_throw; using metkit::mars2grib::frontend::resolution::resolve_SectionsLayout_or_throw; using metkit::mars2grib::frontend::resolution::SectionLayoutData; using metkit::mars2grib::utils::dict_traits::dict_to_json; using metkit::mars2grib::utils::exceptions::Mars2GribHeaderLayoutException; try { // Step 1: Semantic Analysis (What concepts are we encoding?) ActiveConceptsData activeConcepts = resolve_ActiveConcepts_or_throw(marsDict, optDict); // Step 2: Structural Mapping (Where do these concepts live in the GRIB sections?) std::array sectionsLayout = resolve_SectionsLayout_or_throw(activeConcepts); // Step 3: Blueprint Aggregation // We move the resolved array into the layout carrier to ensure zero-copy transfer. return GribHeaderLayoutData{{std::move(sectionsLayout)}}; } catch (...) { std::throw_with_nested(Mars2GribHeaderLayoutException("Critical failure: Unable to resolve GRIB HeaderLayout", dict_to_json(marsDict), Here())); } return {}; } namespace tests { /// /// @brief Generates a JSON diagnostic capture of the resolution pipeline. /// * This creates an object containing: /// 1. The original MARS request. /// 2. The resolved ActiveConcepts (Semantic layer). /// 3. The resolved GribHeaderLayout (Structural layer). /// * @return std::string A single JSON object string. /// template std::string capture_resolution_state_json(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::backend::sections::resolver::debug::debug_convert_ActiveConceptsData_to_json; using metkit::mars2grib::frontend::debug::debug_convert_GribHeaderLayoutData_to_json; using metkit::mars2grib::utils::dict_traits::dict_to_json; // 1. Resolve states // Note: In a real test, you'd likely catch exceptions here to log failures auto activeConcepts = resolution::resolve_ActiveConcepts_or_throw(mars, opt); auto headerLayout = make_HeaderLayout_or_throw(mars, opt); // 2. Build the aggregate JSON string // We leverage the debug helpers we built in previous steps std::ostringstream oss; oss << "{ " << "\"mars\": " << dict_to_json(mars) << ", " << "\"activeConcepts\": " << debug_convert_ActiveConceptsData_to_json(activeConcepts) << ", " << "\"headerLayout\": " << debug_convert_GribHeaderLayoutData_to_json(headerLayout) << " }"; return oss.str(); } } // namespace tests } // namespace metkit::mars2grib::frontendmetkit-1.20.2/src/metkit/mars2grib/frontend/GribHeaderLayoutData.h0000664000175000017500000001117515246725757025236 0ustar alastairalastair/* * (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 GribHeaderLayoutData.h /// @brief Structural blueprint of a resolved GRIB message. /// /// This header defines the data structures used to represent the resolved /// structural state of a GRIB message. It acts as a bridge between the /// frontend (Resolution) and the backend (Encoding). /// #pragma once // System includes #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend { /// /// @brief Aggregated layout metadata for a complete GRIB message. /// /// `GribHeaderLayoutData` serves as a **blueprint** or **manifest**. It contains /// the resolved templates and specific variants for every GRIB section (0-7). /// /// This structure is strictly "data-only" and is intended to be passed to /// specialized encoders which use these indices to perform lookups in the /// `GeneralRegistry`. /// struct GribHeaderLayoutData { using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; using SectionLayoutData = metkit::mars2grib::backend::sections::resolver::SectionLayoutData; /// @brief Number of sections defined by the GRIB registry (typically 8) /// we use 5 because section from 6 to 8 are pure data /// static constexpr std::size_t nSections = GeneralRegistry::NSections; /// @brief Array of layout definitions, indexed by GRIB section number. std::array sectionLayouts{}; }; /// /// @namespace metkit::mars2grib::frontend::debug /// @brief Diagnostic and serialization utilities for header layout data. /// namespace debug { /// /// @brief Serialize the header layout to a JSON-like diagnostic string. /// /// Produces a machine-readable representation of the resolved layout. This is /// primarily used for regression testing (dumping "GRIB blueprints") to /// ensure that changes in metadata resolution do not unexpectedly alter /// the resulting GRIB structure. /// /// @param data The layout data to serialize. /// @return A JSON formatted string containing the section-variant map. /// inline std::string debug_convert_GribHeaderLayoutData_to_json(const GribHeaderLayoutData& data) { using metkit::mars2grib::backend::sections::resolver::debug::debug_convert_SectionLayoutData_to_json; std::ostringstream oss; oss << "{ \"GribHeaderLayoutData\": { \"sections\": [ "; for (std::size_t sid = 0; sid < GribHeaderLayoutData::nSections; ++sid) { // Delegate to the resolution::debug helper to get the "Concept::Variant" names oss << debug_convert_SectionLayoutData_to_json(data.sectionLayouts[sid]); if (sid + 1 < GribHeaderLayoutData::nSections) { oss << ", "; } } oss << " ] } }"; return oss.str(); } /// /// @brief Detailed print to ostream for human-readable logging. /// /// Formats the layout into a hierarchical tree view, showing which GRIB /// Template is used for each section and listing the specific Concept/Variant /// pairs that will be encoded. /// /// @param data The layout data to print. /// @param prefix Leading string for each line (used for indentation/log headers). /// @param os The output stream. /// inline void debug_print_GribHeaderLayoutData(const GribHeaderLayoutData& data, std::string_view prefix, std::ostream& os) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: GribHeaderLayoutData Summary\n"; for (std::size_t sid = 0; sid < GribHeaderLayoutData::nSections; ++sid) { const auto& section = data.sectionLayouts[sid]; os << prefix << " :: Section[" << sid << "] Template: " << section.templateNumber << "\n"; for (std::size_t i = 0; i < section.count; ++i) { std::size_t id = section.variantIndices[i]; os << prefix << " :: - " << GeneralRegistry::conceptNameArr[id] << "::" << GeneralRegistry::variantNameArr[id] << "\n"; } } } } // namespace debug } // namespace metkit::mars2grib::frontend metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/0000775000175000017500000000000015246725757023762 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/normalization/normalization.h0000664000175000017500000000024115246725757027016 0ustar alastairalastair// Project includes #include "metkit/mars2grib/frontend/normalization/normalizeMarsDict.h" #include "metkit/mars2grib/frontend/normalization/normalizeMiscDict.h"metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/normalizeMiscDict.h0000664000175000017500000000476315246725757027565 0ustar alastairalastair/* * (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 normalization.h /// @brief Utilities for input dictionary sanitization and normalization. /// /// This header provides "just-in-time" sanitization routines used to ensure /// input dictionaries conform to backend expectations before the resolution /// phase begins. /// /// @ingroup mars2grib_frontend_normalization /// #pragma once // Project includes #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization { /// /// @brief Conditionally sanitizes a dictionary based on runtime options. /// /// This function implements a **pass-through or transform** pattern designed /// to minimize unnecessary copies. If sanitization is required by the provided /// options, the transformed data is stored in a caller-provided scratch /// buffer, and a reference to that buffer is returned. Otherwise, the /// original dictionary is returned as-is. /// /// @tparam MiscDict_t Type of the dictionary to be sanitized /// @tparam OptDict_t Type of the options dictionary driving the logic /// /// @param[in] miscDict Original dictionary to evaluate /// @param[in] optDict Configuration/Options dict used to determine policy /// @param[out] scratch Buffer used to store the sanitized result if needed /// /// @return A const reference to either the original or the sanitized dictionary /// template const MiscDict_t& normalize_MiscDict_if_enabled(const MiscDict_t& miscDict, const OptDict_t& optDict, const eckit::Value& language, MiscDict_t& scratch) { using metkit::mars2grib::utils::dict_traits::get_or_throw; // TODO: Implement sanitization trigger logic based on optDict settings if (get_or_throw(optDict, "normalizeMisc")) { // [Development Stub] // Example: logic to prune illegal keys or normalize units // scratch = perform_transform(miscDict); return scratch; } // Default path: zero-copy pass-through return miscDict; } } // namespace metkit::mars2grib::frontend::normalization metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/0000775000175000017500000000000015246725757025420 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/0000775000175000017500000000000015246725757026362 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/model.h0000664000175000017500000000106115246725757027631 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: model. /// template void sanitise_model_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for model } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/hdate.h0000664000175000017500000000106115246725757027616 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: hdate. /// template void sanitise_hdate_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for hdate } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/All.h0000664000175000017500000000533515246725757027251 0ustar alastairalastair#include "metkit/mars2grib/frontend/normalization/per_key/mars/activity.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/anoffset.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/channel.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/chem.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/class.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/dataset.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/date.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/direction.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/domain.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/experiment.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/expver.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/frequency.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/generation.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/grid.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/hdate.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/htime.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/ident.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/instrument.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/levelist.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/levtype.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/method.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/model.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/number.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/origin.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/packing.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/param.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/realization.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/resolution.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/stattype.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/step.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/stream.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/system.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/time.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/timespan.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/truncation.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/type.h" #include "metkit/mars2grib/frontend/normalization/per_key/mars/wavelength.h" #include "metkit/mars2grib/utils/generalUtils.h" metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/stattype.h0000664000175000017500000000107215246725757030410 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: stattype. /// template void sanitise_stattype_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for stattype } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/expver.h0000664000175000017500000000106415246725757030045 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: expver. /// template void sanitise_expver_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for expver } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/origin.h0000664000175000017500000000106415246725757030023 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: origin. /// template void sanitise_origin_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for origin } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/grid.h0000664000175000017500000000105615246725757027462 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: grid. /// template void sanitise_grid_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for grid } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/system.h0000664000175000017500000000106415246725757030060 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: system. /// template void sanitise_system_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for system } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/anoffset.h0000664000175000017500000000107215246725757030340 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: anoffset. /// template void sanitise_anoffset_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for anoffset } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/activity.h0000664000175000017500000000107215246725757030367 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: activity. /// template void sanitise_activity_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for activity } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/htime.h0000664000175000017500000000106115246725757027637 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: htime. /// template void sanitise_htime_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for htime } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/channel.h0000664000175000017500000000106715246725757030147 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: channel. /// template void sanitise_channel_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for channel } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/ident.h0000664000175000017500000000106115246725757027634 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: ident. /// template void sanitise_ident_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for ident } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/timespan.h0000664000175000017500000000107215246725757030353 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: timespan. /// template void sanitise_timespan_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for timespan } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/level.h0000664000175000017500000000101315246725757027635 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: level. /// template void sanitise_level_or_throw(T& value, const eckit::Value& language) { // TODO: Implement specific validation logic for level } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/wavelength.h0000664000175000017500000000110015246725757030667 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: wavelength. /// template void sanitise_wavelength_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for wavelength } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/chem.h0000664000175000017500000000105615246725757027451 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: chem. /// template void sanitise_chem_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for chem } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/domain.h0000664000175000017500000000106415246725757030003 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: domain. /// template void sanitise_domain_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for domain } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/generation.h0000664000175000017500000000110015246725757030656 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: generation. /// template void sanitise_generation_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for generation } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/realization.h0000664000175000017500000000110315246725757031047 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: realization. /// template void sanitise_realization_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for realization } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/packing.h0000664000175000017500000000106715246725757030153 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: packing. /// template void sanitise_packing_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for packing } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/experiment.h0000664000175000017500000000110015246725757030703 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: experiment. /// template void sanitise_experiment_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for experiment } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/instrument.h0000664000175000017500000000110015246725757030733 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: instrument. /// template void sanitise_instrument_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for instrument } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/levtype.h0000664000175000017500000000106715246725757030227 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: levtype. /// template void sanitise_levtype_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for levtype } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/method.h0000664000175000017500000000106415246725757030014 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: method. /// template void sanitise_method_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for method } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/date.h0000664000175000017500000000105615246725757027452 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: date. /// template void sanitise_date_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for date } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/stream.h0000664000175000017500000000106415246725757030027 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: stream. /// template void sanitise_stream_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for stream } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/truncation.h0000664000175000017500000000110015246725757030711 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: truncation. /// template void sanitise_truncation_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for truncation } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/dataset.h0000664000175000017500000000106715246725757030164 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: dataset. /// template void sanitise_dataset_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for dataset } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/param.h0000664000175000017500000000106115246725757027631 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: param. /// template void sanitise_param_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for param } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/number.h0000664000175000017500000000106415246725757030024 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: number. /// template void sanitise_number_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for number } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/frequency.h0000664000175000017500000000107515246725757030537 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: frequency. /// template void sanitise_frequency_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for frequency } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/levelist.h0000664000175000017500000000107215246725757030362 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: levelist. /// template void sanitise_levelist_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for levelist } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/time.h0000664000175000017500000000105615246725757027473 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: time. /// template void sanitise_time_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for time } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/resolution.h0000664000175000017500000000110015246725757030726 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: resolution. /// template void sanitise_resolution_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for resolution } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/type.h0000664000175000017500000000105615246725757027516 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: type. /// template void sanitise_type_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for type } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/class.h0000664000175000017500000000106115246725757027636 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: class. /// template void sanitise_class_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for class } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/direction.h0000664000175000017500000000107515246725757030516 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: direction. /// template void sanitise_direction_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for direction } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/step.h0000664000175000017500000000105615246725757027510 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. */ #pragma once #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Individual sanitization check for the GRIB key: step. /// template void sanitise_step_or_throw(const MarsDict_t& in, MarsDict_t& out, const eckit::Value& language) { // TODO: Implement specific validation logic for step } } // namespace metkit::mars2grib::frontend::normalization::per_key metkit-1.20.2/src/metkit/mars2grib/frontend/normalization/per_key/SanitizationRegistry.h0000664000175000017500000000227615246725757032005 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF */ #pragma once #include #include #include #include "metkit/mars2grib/frontend/normalization/per_key/mars/All.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization::per_key { /// /// @brief Registry to provide atomic access to sanitizers for testing. /// template struct MarsSanitizerRegistry { using SanitizerFn = std::function; struct Entry { std::string key; SanitizerFn func; }; static std::vector get_all_tests() { return { {"origin", per_key::sanitise_origin_or_throw}, {"class", per_key::sanitise_class_or_throw}, {"stream", per_key::sanitise_stream_or_throw}, {"type", per_key::sanitise_type_or_throw}, {"expver", per_key::sanitise_expver_or_throw}, {"date", per_key::sanitise_date_or_throw}, {"truncation", per_key::sanitise_truncation_or_throw} // ... the script can be extended to populate this list }; } }; } // namespace metkit::mars2grib::frontend::normalization::per_keymetkit-1.20.2/src/metkit/mars2grib/frontend/normalization/normalizeMarsDict.h0000664000175000017500000000746115246725757027572 0ustar alastairalastair/* * (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 normalization.h /// @brief Orchestration of input dictionary sanitization. /// #pragma once // System includes #include #include // Project includes #include "eckit/value/Value.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::normalization { namespace hack { /// /// @brief Normalizes legacy MARS grid strings in-place. /// @return true if the dictionary was actually modified. /// template bool needFixMarsGrid(const MarsDict_t& mars) { using metkit::mars2grib::utils::dict_traits::get_opt; auto marsGrid = get_opt(mars, "grid"); if (!marsGrid) return false; static const std::regex pattern{R"(L(\d+)x(\d+))"}; return std::regex_match(*marsGrid, pattern); } template bool fixMarsGrid(MarsDict_t& mars) { using metkit::mars2grib::utils::dict_traits::get_opt; auto marsGrid = get_opt(mars, "grid"); if (!marsGrid) return false; // Matches legacy LxN format (e.g., L640x320) static const std::regex pattern{R"(L(\d+)x(\d+))"}; std::smatch match; if (std::regex_match(*marsGrid, match, pattern)) { const long ni = std::stol(match[1].str()); const long nj = std::stol(match[2].str()); const double deltaLon = 360.0 / static_cast(ni); const double deltaLat = 180.0 / static_cast(nj - 1); mars.set("grid", std::to_string(deltaLon) + "/" + std::to_string(deltaLat)); return true; } return false; } } // namespace hack /// /// @brief Conditionally sanitizes a dictionary by running a suite of atomic key checks. /// /// This function iterates through specific keys (truncation, grid, etc.) and /// applies the normalization rules defined in the @ref per_key directory. /// /// @tparam MiscDict_t Type of the dictionary to be sanitized /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars Original dictionary /// @param[in] opt Options driving the sanitization policy /// @param[out] scratch Buffer to store results if a transformation occurs /// /// @todo build structure around tests /// /// @return A const reference to the sanitized or original dictionary /// template const MarsDict_t& normalize_MarsDict_if_enabled(const MarsDict_t& mars, const OptDict_t& opt, const eckit::Value& language, MarsDict_t& scratch) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::get_or_throw; // Track if we have moved data into scratch yet bool modified = false; bool needsFix = get_or_throw(opt, "fixMarsGrid") && hack::needFixMarsGrid(mars); bool needsSanitize = get_or_throw(opt, "normalizeMars"); if (needsFix || needsSanitize) { // We pay the performance debt here for the user's convenience scratch = mars; if (needsSanitize) { // Future placeholder for language-based logic } if (needsFix) { hack::fixMarsGrid(scratch); } return scratch; } // No flags enabled: zero overhead, return original reference return mars; } } // namespace metkit::mars2grib::frontend::normalization metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/0000775000175000017500000000000015246725757023277 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/0000775000175000017500000000000015246725757026373 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/SectionTemplateSelectors.h0000664000175000017500000000310415246725757033526 0ustar alastairalastair#pragma once #include #include "metkit/mars2grib/backend/sections/resolver/SectionTemplateSelector.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section0Recipes.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section1Recipes.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section2Recipes.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section3Recipes.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section4Recipes.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/impl/section5Recipes.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes { struct SectionTemplateSelectors { using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; using SectionTemplateSelector = metkit::mars2grib::backend::sections::resolver::SectionTemplateSelector; static inline const std::array value = [] { return std::array{ SectionTemplateSelector::make(impl::Section0Recipes), SectionTemplateSelector::make(impl::Section1Recipes), SectionTemplateSelector::make(impl::Section2Recipes), SectionTemplateSelector::make(impl::Section3Recipes), SectionTemplateSelector::make(impl::Section4Recipes), SectionTemplateSelector::make(impl::Section5Recipes)}; }(); }; } // namespace metkit::mars2grib::frontend::resolution::recipesmetkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/0000775000175000017500000000000015246725757027334 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section0Recipes.h0000664000175000017500000000245215246725757032547 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 0 Recipes (runtime, static inline) //------------------------------------------------------------------------------ #pragma once // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 0 – Individual Recipes //------------------------------------------------------------------------------ // Indicator Section (sentinel-only) inline const Recipe S0_R0 = make_recipe<0, Select >(); //------------------------------------------------------------------------------ // Section 0 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section0Recipes{ 0, std::vector{ &S0_R0 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section4Recipes.h0000664000175000017500000002730515246725757032557 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 4 Recipes (runtime, static inline) //------------------------------------------------------------------------------ // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 4 – Individual Recipes //------------------------------------------------------------------------------ inline const Recipe S4_R0 = make_recipe<0, Select, Select, Select, Select >(); inline const Recipe S4_R1 = make_recipe<1, Select, Select, Select, Select, Select >(); inline const Recipe S4_R2 = make_recipe<2, Select, Select, Select, Select, Select >(); inline const Recipe S4_R8 = make_recipe<8, Select, Select, Select, Select >(); // inline const Recipe S4_R9 = // make_recipe<9, // Select, // Select, // Select, // Select, // Select // >(); inline const Recipe S4_R11 = make_recipe<11, Select, Select, Select, Select, Select >(); inline const Recipe S4_R12 = make_recipe<12, Select, Select, Select, Select, Select >(); inline const Recipe S4_R32 = make_recipe<32, Select, Select, Select, Select >(); inline const Recipe S4_R33 = make_recipe<33, Select, Select, Select, Select, Select >(); inline const Recipe S4_R40 = make_recipe<40, Select, Select, Select, Select, Select >(); inline const Recipe S4_R41 = make_recipe<41, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R42 = make_recipe<42, Select, Select, Select, Select, Select >(); inline const Recipe S4_R43 = make_recipe<43, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R50 = make_recipe<50, Select, Select, Select, Select, Select >(); inline const Recipe S4_R45 = make_recipe<45, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R46 = make_recipe<46, Select, Select, Select, Select, Select >(); inline const Recipe S4_R85 = make_recipe<85, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R48 = make_recipe<48, Select, Select, Select, Select, Select >(); inline const Recipe S4_R49 = make_recipe<49, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R60 = make_recipe<60, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R61 = make_recipe<61, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R76 = make_recipe<76, Select, Select, Select, Select, Select >(); inline const Recipe S4_R77 = make_recipe<77, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R78 = make_recipe<78, Select, Select, Select, Select, Select >(); inline const Recipe S4_R80 = make_recipe<79, Select, Select, Select, Select, Select >(); inline const Recipe S4_R79 = make_recipe<79, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R99 = make_recipe<99, Select, Select, Select, Select >(); inline const Recipe S4_R100 = make_recipe<100, Select, Select, Select, Select, Select >(); inline const Recipe S4_R103 = make_recipe<103, Select, Select, Select, Select, Select >(); inline const Recipe S4_R104 = make_recipe<104, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R144 = make_recipe<144, Select, Select, Select, Select, Select >(); inline const Recipe S4_R145 = make_recipe<145, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R107 = make_recipe<107, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R121 = make_recipe<121, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R122 = make_recipe<122, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R131 = make_recipe<131, Select, Select, Select, Select, Select, Select >(); inline const Recipe S4_R142 = make_recipe<142, Select, Select, Select, Select >(); inline const Recipe S4_R143 = make_recipe<143, Select, Select, Select, Select >(); //------------------------------------------------------------------------------ // Section 4 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section4Recipes{ 4, std::vector{ &S4_R0, &S4_R1, &S4_R2, &S4_R8, &S4_R11, &S4_R12, &S4_R32, &S4_R33, &S4_R40, &S4_R41, &S4_R42, &S4_R43, &S4_R50, &S4_R45, &S4_R46, &S4_R85, &S4_R48, &S4_R49, &S4_R60, &S4_R61, &S4_R76, &S4_R77, &S4_R78, &S4_R79, &S4_R80, &S4_R99, &S4_R100, &S4_R103, &S4_R104, &S4_R107, &S4_R121, &S4_R122, &S4_R131, &S4_R142, &S4_R143, &S4_R144, &S4_R145 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section3Recipes.h0000664000175000017500000000550515246725757032554 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 3 Recipes (runtime, static inline) //------------------------------------------------------------------------------ #pragma once // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 3 – Individual Recipes //------------------------------------------------------------------------------ // Latitude / Longitude grid inline const Recipe S3_R0 = make_recipe<0, Select, Select >(); // Reduced or Gaussian grid inline const Recipe S3_R40 = make_recipe<40, Select, Select >(); // Spectral representation inline const Recipe S3_R50 = make_recipe<50, Select >(); // General unstructured grid inline const Recipe S3_R101 = make_recipe<101, Select, Select >(); inline const Recipe S3_R101_ORCA = make_recipe<101, Select, Select >(); // HEALPix grid inline const Recipe S3_R150 = make_recipe<150, Select, Select >(); // This is just meant to skip the section3 resolution in order to allow gridspec do be used after the encoder inline const Recipe S3_R1000 = make_recipe<1000, Select, Select >(); //------------------------------------------------------------------------------ // Section 3 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section3Recipes{ 3, std::vector{ &S3_R0, &S3_R40, &S3_R50, &S3_R101, &S3_R101_ORCA, &S3_R150, &S3_R1000 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section2Recipes.h0000664000175000017500000001062415246725757032551 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 2 Recipes (runtime, static inline) //------------------------------------------------------------------------------ #pragma once // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 2 – Individual Recipes //------------------------------------------------------------------------------ // Standard local definition inline const Recipe S2_R1 = make_recipe<1, Select >(); // Long-range products inline const Recipe S2_R15 = make_recipe<15, Select, Select >(); // Long-range products inline const Recipe S2_R16 = make_recipe<16, Select, Select >(); // 4i related products inline const Recipe S2_R20 = make_recipe<20, Select, Select >(); // Satellite-related products inline const Recipe S2_R24 = make_recipe<24, Select, Select >(); // 4DVar model errors inline const Recipe S2_R25 = make_recipe<25, Select, Select >(); // Analysis-related products inline const Recipe S2_R36 = make_recipe<36, Select, Select >(); // Brightness temperature satellite products inline const Recipe S2_R37A = make_recipe<37, Select, Select, Select >(); inline const Recipe S2_R37B = make_recipe<37, Select, Select, Select, Select >(); // 4i Analysis-related products inline const Recipe S2_R38 = make_recipe<38, Select, Select, Select >(); // 4DVar model errors for long window 4Dvar system inline const Recipe S2_R39 = make_recipe<39, Select, Select, Select >(); // Model error fourier coefficients // Note: Template 45 has the analysis concept, but it's unused (and unmatched) for stream oper inline const Recipe S2_R45 = make_recipe<45, Select, Select >(); //------------------------------------------------------------------------------ // Virtual (encoder-specific) templates //------------------------------------------------------------------------------ // DestinE Climate DT products inline const Recipe S2_R1001 = make_recipe<1001, Select, Select >(); // DestinE Extremes DT products inline const Recipe S2_R1002 = make_recipe<1002, Select, Select >(); // DestinE On-demand Extremes DT products //inline const Recipe S2_R1004 = // make_recipe<1004, // Select, // Select // >(); //------------------------------------------------------------------------------ // Section 2 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section2Recipes{ 2, std::vector{ &S2_R1, &S2_R15, &S2_R16, &S2_R20, &S2_R24, &S2_R25, &S2_R36, &S2_R37A, &S2_R37B, &S2_R38, &S2_R39, &S2_R45, &S2_R1001, &S2_R1002 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section5Recipes.h0000664000175000017500000000315115246725757032551 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 5 Recipes (runtime, static inline) //------------------------------------------------------------------------------ #pragma once // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 5 – Individual Recipes //------------------------------------------------------------------------------ // Simple packing inline const Recipe S5_R0 = make_recipe<0, Select >(); // CCSDS compression inline const Recipe S5_R42 = make_recipe<42, Select >(); // Spectral complex packing inline const Recipe S5_R51 = make_recipe<51, Select >(); //------------------------------------------------------------------------------ // Section 5 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section5Recipes{ 5, std::vector{ &S5_R0, &S5_R42, &S5_R51 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section1Recipes.h0000664000175000017500000000261015246725757032544 0ustar alastairalastair//------------------------------------------------------------------------------ // Section 1 Recipes (runtime, static inline) //------------------------------------------------------------------------------ #pragma once // All elements needed to used the "dsl" objects to define recipes #include "metkit/mars2grib/backend/sections/resolver/dsl.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::frontend::resolution::recipes::impl { using namespace metkit::mars2grib::backend::concepts_; using namespace metkit::mars2grib::backend::sections::resolver::dsl; // clang-format off //------------------------------------------------------------------------------ // Section 1 – Individual Recipes //------------------------------------------------------------------------------ // Identification Section inline const Recipe S1_R0 = make_recipe<0, Select, Select, Select, Select >(); //------------------------------------------------------------------------------ // Section 1 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section1Recipes{ 1, std::vector{ &S1_R0 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.20.2/src/metkit/mars2grib/frontend/resolution/resolveSectionsLayout.h0000664000175000017500000001313315246725757030036 0ustar alastairalastair/* * (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 GribHeaderLayoutResolver.h /// @brief Logic for mapping active semantic concepts to structural GRIB sections. /// /// This header defines the structural resolution logic that transforms /// normalized MARS request data (ActiveConceptsData) into a concrete /// GRIB message blueprint (GribHeaderLayoutData). /// /// @ingroup mars2grib_frontend_resolution /// #pragma once // System includes #include #include #include // Project includes #include "metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h" #include "metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h" #include "metkit/mars2grib/frontend/GribHeaderLayoutData.h" #include "metkit/mars2grib/frontend/resolution/section-recipes/SectionTemplateSelectors.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::frontend::resolution { using SectionLayoutData = metkit::mars2grib::backend::sections::resolver::SectionLayoutData; using ActiveConceptsData = metkit::mars2grib::backend::sections::resolver::ActiveConceptsData; /// /// @brief Resolves the structural layout of all GRIB sections. /// /// This function orchestrates the "Recipe Selection" phase of the frontend. /// It iterates through every canonical GRIB section and utilizes static /// selectors to determine: /// 1. Which GRIB template should represent the section. /// 2. Which concept variants should be mapped to that section's fields. /// /// ------------------------------------------------------------------------ /// /// @section resolution_logic Resolution Logic /// /// The resolution is deterministic and based on the @ref ActiveConceptsData. /// For each section $S \in [0, N_{sections})$, the function: /// - Invokes the corresponding @ref SectionTemplateSelector. /// - Validates that the selector returned a valid payload for the correct section. /// /// ------------------------------------------------------------------------ /// /// @param[in] activeConcepts The semantic interpretation of the MARS request. /// @return A dense array of resolved @ref SectionLayoutData. /// @throws Mars2GribGenericException if a section returns invalid data. /// @throws Mars2GribSectionResolutionException if any section fails to resolve. /// inline std::array resolve_SectionsLayout_or_throw( const ActiveConceptsData& activeConcepts) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; using metkit::mars2grib::backend::sections::resolver::debug::debug_convert_ActiveConceptsData_to_json; using metkit::mars2grib::frontend::resolution::recipes::SectionTemplateSelectors; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; using metkit::mars2grib::utils::exceptions::Mars2GribSectionResolutionException; std::size_t section = 0; try { // Recover the static structural recipes (Stage 1 resolution) const auto& selectors = SectionTemplateSelectors::value; // Pre-allocate the layout container on the stack std::array sectionsLayout; // Iterate through all canonical GRIB sections (0 through 8) for (section = 0; section < GeneralRegistry::NSections; ++section) { // Apply the recipe for this specific section index SectionLayoutData sectionData = selectors[section].select_or_throw(activeConcepts); // Validation: Ensure the recipe actually targetted the expected section index if (sectionData.sectionNumber != section) { throw Mars2GribGenericException("SectionTemplateSelector mismatch: expected section " + std::to_string(section) + " but recipe returned " + std::to_string(sectionData.sectionNumber), Here()); } sectionsLayout[section] = std::move(sectionData); } return sectionsLayout; } catch (...) { std::throw_with_nested( Mars2GribSectionResolutionException("Critical failure: Unable to resolve GRIB HeaderLayout", section, debug_convert_ActiveConceptsData_to_json(activeConcepts), Here())); } mars2gribUnreachable(); } /// /// @namespace metkit::mars2grib::frontend::resolution::debug /// @brief Diagnostic tools for the resolution process. /// namespace debug { /// /// @brief Logs the resolution results for debugging purposes. /// * @param[in] layout Resolved section layouts /// @param[out] os Target output stream /// inline void debug_print_resolved_layout( const std::array& layout, std::ostream& os) { os << "--- GRIB Layout Resolution Debug ---" << std::endl; for (const auto& s : layout) { os << "Section " << s.sectionNumber << " -> Template " << s.templateNumber << " (Concepts: " << s.count << ")" << std::endl; } } } // namespace debug } // namespace metkit::mars2grib::frontend::resolutionmetkit-1.20.2/src/metkit/mars2grib/frontend/resolution/resolveActiveConcepts.h0000664000175000017500000001061515246725757027765 0ustar alastairalastair/* * (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 resolve_ActiveConcepts.h /// @brief Logic for mapping MARS metadata to active GRIB concept variants. /// /// This header defines the core resolution engine that identifies which /// GRIB entities (Concepts and Variants) are triggered by a specific /// combination of MARS and auxiliary metadata. /// #pragma once // project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/concepts/MatchingCallbacksRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::frontend::resolution { using ActiveConceptsData = metkit::mars2grib::backend::sections::resolver::ActiveConceptsData; /// /// @brief Orchestrates the resolution of MARS metadata into GRIB concepts. /// /// This function is the primary entry point for the **Resolution Phase**. It iterates /// through the global registry of GRIB concepts and executes specialized "matching /// callbacks" against the input dictionaries. /// /// Each callback determines which specific variant of a GRIB concept (e.g., which /// type of Level or Step) is triggered by the current MARS request. /// /// ### Error Handling /// - Throws `Mars2GribGenericException` if the registry is inconsistent. /// - Uses `std::throw_with_nested` to preserve the trace of matching failures /// within specific callbacks. /// /// @tparam MarsDict_t Type of the MARS metadata dictionary. /// @tparam OptDict_t Type of the options/configuration dictionary. /// /// @param[in] marsDict The sanitized MARS request. /// @param[in] optDict The auxiliary metadata and encoding options. /// /// @return An `ActiveConceptsData` object containing the indices of all triggered variants. /// template ActiveConceptsData resolve_ActiveConcepts_or_throw(const MarsDict_t& marsDict, const OptDict_t& optDict) { // bring in GeneralRegistry and MatchersRegistry using metkit::mars2grib::backend::concepts_::GeneralRegistry; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; using Registry = metkit::mars2grib::backend::concepts_::MatchingCallbacksRegistry; using metkit::mars2grib::backend::sections::resolver::debug::debug_convert_ActiveConceptsData_to_json; try { // Lookup of the static registry const auto& callbacks = Registry::matchingCallbacks; // Error handling if (callbacks.size() != GeneralRegistry::NConcepts) { throw Mars2GribGenericException("Wrong size of Matchers", Here()); } /// /// @note The Matchers are typically ordered by Section (0 to 5) to ensure /// dependency resolution flows correctly. /// ActiveConceptsData activeConceptsData{}; activeConceptsData.count = 0; for (std::size_t i = 0; i < GeneralRegistry::NConcepts; ++i) { std::size_t localVariantId = callbacks[i](marsDict, optDict); std::size_t globalVariantId = GeneralRegistry::conceptOffsets[i] + localVariantId; if (localVariantId != GeneralRegistry::missing) { activeConceptsData.activeVariantIndices[i] = globalVariantId; activeConceptsData.activeConceptsIndices[activeConceptsData.count++] = i; } else { activeConceptsData.activeVariantIndices[i] = GeneralRegistry::missing; } } eckit::Log::debug() << "Resolved ActiveConceptsData: " << debug_convert_ActiveConceptsData_to_json(activeConceptsData) << std::endl; // Return the active concepts return activeConceptsData; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Unable to match ActiveConcepts", Here())); } mars2gribUnreachable(); }; } // namespace metkit::mars2grib::frontend::resolutionmetkit-1.20.2/src/metkit/mars2grib/frontend/header/0000775000175000017500000000000015246725757022324 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/frontend/header/SpecializedEncoder.h0000664000175000017500000004545115246725757026242 0ustar alastairalastair/* * (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 SpecializedEncoder.h /// @brief Fully specialized, hot-path GRIB header encoder. /// /// This file defines the `SpecializedEncoder` class template, which represents /// the **final, performance-critical encoding stage** of the mars2grib frontend. /// /// The encoder is responsible for: /// /// - owning a fully resolved GRIB header layout /// - generating an optimized, immutable execution plan at construction time /// - executing the plan in a dense, allocation-minimized hot path /// /// Unlike generic or incremental encoders, this class **internalizes all /// resolution work** and is designed to be invoked repeatedly with minimal /// overhead once constructed. /// /// ----------------------------------------------------------------------------- /// Architectural position /// ----------------------------------------------------------------------------- /// /// The `SpecializedEncoder` sits at the boundary between: /// /// - **frontend resolution** (concept selection, section layout, planning) /// - **encoding execution** (dictionary mutation via callbacks) /// /// All expensive or branching logic is assumed to have already happened before /// this class is instantiated. /// /// In particular: /// /// - Section templates are already resolved /// - Concept variants are already fixed /// - The header layout is complete and immutable /// /// As a result, `encode()` executes a **pre-compiled sequence of callbacks** /// without any conditional logic beyond null-checks. /// /// ----------------------------------------------------------------------------- /// Design goals /// ----------------------------------------------------------------------------- /// /// This class is explicitly designed with the following goals: /// /// 1. **Hot-path execution** /// - No dynamic resolution /// - No registry lookups /// - No allocation except controlled cloning /// /// 2. **Immutability** /// - The layout and plan are `const` /// - The encoder is thread-safe after construction /// /// 3. **Move-only ownership** /// - Layout data is moved into the encoder /// - Copies are explicitly disabled /// /// 4. **Failure transparency** /// - Any failure is wrapped in a domain-specific exception /// - Full diagnostic context is preserved via nested exceptions /// /// ----------------------------------------------------------------------------- /// Typical lifecycle /// ----------------------------------------------------------------------------- /// /// The intended usage pattern is: /// /// @code /// // One-time resolution phase /// GribHeaderLayoutData layout = resolve_layout_or_throw(...); /// /// // One-time specialization /// SpecializedEncoder encoder( /// std::move(layout) /// ); /// /// // Hot-path execution (possibly millions of times) /// auto grib = encoder.encode(mars, par, opt); /// @endcode /// /// ----------------------------------------------------------------------------- /// Transitional staged-encoding support /// ----------------------------------------------------------------------------- /// /// This class also exposes a temporary staged interface via `prepare()` and /// `finaliseEncoding()`. /// /// These functions are introduced in preparation for a cache mechanism that /// will soon be implemented in the higher-level encoding pipeline. /// /// Their purpose is to split the hot-path header construction into: /// /// - an immutable reusable preparation phase /// - a lightweight finalisation phase operating from a prepared sample /// /// This staged interface is intended to support incremental integration and /// performance comparison while the future cache design is being introduced. /// /// ----------------------------------------------------------------------------- /// Template parameters /// ----------------------------------------------------------------------------- /// /// @tparam MarsDict_t Dictionary type containing MARS metadata /// @tparam ParDict_t Dictionary type containing parameter metadata /// @tparam OptDict_t Dictionary type containing encoding options /// @tparam OutDict_t Dictionary type representing the GRIB output /// /// All dictionary types are expected to satisfy the `dict_traits` interface /// used throughout mars2grib. /// #pragma once // System includes #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/frontend/GribHeaderLayoutData.h" #include "metkit/mars2grib/frontend/header/EncodingPlan.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::frontend::header { /// /// @brief Fully specialized GRIB encoder. /// /// This class represents a **fully materialized encoder instance**, where: /// /// - the GRIB header layout is already resolved /// - the execution plan is generated eagerly at construction /// /// Once constructed, the encoder performs **no further planning or resolution**. /// The `encode()` method executes a dense, pre-computed plan consisting of /// concept setter callbacks organized by: /// /// - encoding stage /// - GRIB section /// - concept callback /// /// The encoder is **logically immutable** and safe to reuse across multiple /// encoding calls with different input dictionaries. /// template class SpecializedEncoder { public: /// /// @brief Alias for the execution plan. /// /// The execution plan is a statically typed, nested container describing /// the exact sequence of operations required to populate the GRIB header. /// /// Its structure reflects the conceptual hierarchy: /// /// - stages /// - sections /// - concept callbacks /// /// The plan is generated once at construction time and never modified. /// using Plan_t = metkit::mars2grib::frontend::header::detail::EncodingPlan; /// /// @brief Alias for resolved header layout data. /// /// This object describes the finalized layout of the GRIB header, /// including: /// /// - which sections are present /// - which templates are used /// - which concept variants are active /// /// It is assumed to be complete and internally consistent. /// using HeaderLayout_t = metkit::mars2grib::frontend::GribHeaderLayoutData; /// /// @brief Construct the encoder by internalizing the header layout. /// /// This constructor performs two tightly coupled operations: /// /// 1. Moves the resolved header layout into the encoder's internal state /// 2. Builds the optimized execution plan from the internalized layout /// /// The order of operations is intentional: /// /// - The layout is stored first /// - The plan factory reads directly from the stored layout /// /// @param[in] headerLayout /// Fully resolved header layout to be moved into the encoder. /// /// @throws Mars2GribException /// If plan construction fails due to inconsistent layout data. /// /// @note /// After construction, both the layout and the plan are immutable. /// explicit SpecializedEncoder(HeaderLayout_t&& headerLayout) : layout_{std::move(headerLayout)}, plan_{detail::make_EncodingPlan_or_throw(layout_)} {} /// /// @name Special member functions /// @{ /// /// /// @brief Copy construction is disabled. /// /// Copying the encoder would imply copying the layout and plan, /// which is both expensive and semantically undesirable. /// SpecializedEncoder(const SpecializedEncoder&) = delete; /// /// @brief Copy assignment is disabled. /// SpecializedEncoder& operator=(const SpecializedEncoder&) = delete; /// /// @brief Move construction is allowed. /// /// Enables efficient transfer of ownership when caching or /// storing encoders in containers. /// SpecializedEncoder(SpecializedEncoder&&) = default; /// /// @brief Move assignment is allowed. /// SpecializedEncoder& operator=(SpecializedEncoder&&) = default; /// /// @brief Destructor. /// /// Trivial destructor; all owned state is RAII-managed. /// ~SpecializedEncoder() = default; /// @} /// /// @brief Execute the encoding plan (hot path). /// /// This method performs the actual GRIB header encoding. /// /// Characteristics: /// /// - No layout resolution /// - No plan modification /// - No dynamic dispatch /// /// The algorithm is: /// /// 1. Create an initial GRIB sample dictionary /// 2. Iterate through the execution plan: /// - stages /// - sections /// - concept callbacks /// 3. Apply each non-null callback to the current dictionary /// 4. Clone the dictionary between stages as required /// /// @param[in] mars /// MARS metadata dictionary /// /// @param[in] par /// Parameter metadata dictionary /// /// @param[in] opt /// Encoding options dictionary /// /// @return /// A newly allocated dictionary containing the encoded GRIB header /// /// @throws Mars2GribEncoderException /// On any failure during encoding. The exception includes: /// - serialized input dictionaries /// - serialized header layout /// - full nested exception chain /// std::unique_ptr encode(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) const { using metkit::mars2grib::frontend::debug::debug_convert_GribHeaderLayoutData_to_json; using metkit::mars2grib::utils::dict_traits::clone_or_throw; using metkit::mars2grib::utils::dict_traits::dict_to_json; using metkit::mars2grib::utils::dict_traits::make_from_sample_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribEncoderException; try { auto samplePtr = make_from_sample_or_throw("GRIB2"); // Encoding loop as a dense set of optimized operations for (const auto& stage : plan_) { for (const auto& section : stage) { for (const auto& conceptCallback : section) { if (conceptCallback) { conceptCallback(mars, par, opt, *samplePtr); } } } samplePtr = clone_or_throw(*samplePtr); } return samplePtr; } catch (...) { std::throw_with_nested(Mars2GribEncoderException( "Critical failure in SpecializedEncoder execution", dict_to_json(mars), dict_to_json(par), dict_to_json(opt), debug_convert_GribHeaderLayoutData_to_json(layout_), Here())); } } /// /// @brief Execute the preparation phase of the staged encoder. /// /// This method evaluates only the prefix of the execution plan required /// to build an immutable reusable sample for later completion. /// /// In particular, it executes all stages up to and including the /// override stage, then returns the resulting output object as a /// read-only prepared sample. /// /// This method exists to support a temporary staged-encoding workflow /// introduced in preparation for a cache mechanism that will soon be /// implemented in the surrounding encoding pipeline. /// /// Characteristics: /// /// - No layout resolution /// - No plan modification /// - No dynamic dispatch /// - Execution limited to the preparation prefix of the plan /// /// The algorithm is: /// /// 1. Create an initial GRIB sample dictionary /// 2. Execute all sections for stages from the beginning of the plan /// through `StageOverride` /// 3. Clone between stages as required /// 4. Return the resulting object as an immutable prepared sample /// /// @param[in] mars /// MARS metadata dictionary /// /// @param[in] par /// Parameter metadata dictionary /// /// @param[in] opt /// Encoding options dictionary /// /// @return /// A newly allocated immutable dictionary containing the prepared /// reusable header state. /// /// @throws Mars2GribEncoderException /// On any failure during preparation. The exception includes: /// - serialized input dictionaries /// - serialized header layout /// - full nested exception chain /// /// @note /// This function is part of a temporary staged interface added in /// preparation for a cache that will soon be implemented. /// std::unique_ptr prepare(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) const { using metkit::mars2grib::backend::compile_time_registry_engine::StageOverride; using metkit::mars2grib::frontend::debug::debug_convert_GribHeaderLayoutData_to_json; using metkit::mars2grib::utils::dict_traits::clone_or_throw; using metkit::mars2grib::utils::dict_traits::dict_to_json; using metkit::mars2grib::utils::dict_traits::make_from_sample_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribEncoderException; try { auto samplePtr = make_from_sample_or_throw("GRIB2"); // Initialization of the sample for (const auto& section : plan_[0]) { for (const auto& conceptCallback : section) { if (conceptCallback) { conceptCallback(mars, par, opt, *samplePtr); } } } samplePtr = clone_or_throw(*samplePtr); // Encoding loop as a dense set of optimized operations for (std::size_t s = 0; s <= StageOverride; ++s) { for (const auto& section : plan_[s + 1]) { for (const auto& conceptCallback : section) { if (conceptCallback) { conceptCallback(mars, par, opt, *samplePtr); } } } samplePtr = clone_or_throw(*samplePtr); } return samplePtr; } catch (...) { std::throw_with_nested(Mars2GribEncoderException( "Critical failure in SpecializedEncoder execution", dict_to_json(mars), dict_to_json(par), dict_to_json(opt), debug_convert_GribHeaderLayoutData_to_json(layout_), Here())); } } /// /// @brief Execute the finalisation phase of the staged encoder. /// /// This method completes the encoding from a previously prepared sample. /// /// It clones the provided prepared sample, then executes only the runtime /// stage of the execution plan in order to produce a fresh finalized /// output object. /// /// This method exists to support a temporary staged-encoding workflow /// introduced in preparation for a cache mechanism that will soon be /// implemented in the surrounding encoding pipeline. /// /// Characteristics: /// /// - No layout resolution /// - No plan modification /// - No dynamic dispatch /// - Execution limited to the runtime stage of the plan /// /// The algorithm is: /// /// 1. Clone the provided prepared sample /// 2. Execute all sections in `StageRuntime` /// 3. Return the finalized output object /// /// @param[in] sample /// Immutable prepared sample previously produced by `prepare()` /// /// @param[in] mars /// MARS metadata dictionary /// /// @param[in] par /// Parameter metadata dictionary /// /// @param[in] opt /// Encoding options dictionary /// /// @return /// A newly allocated dictionary containing the finalized GRIB header /// /// @throws Mars2GribEncoderException /// On any failure during finalisation. The exception includes: /// - serialized input dictionaries /// - serialized header layout /// - full nested exception chain /// /// @note /// This function is part of a temporary staged interface added in /// preparation for a cache that will soon be implemented. /// std::unique_ptr finaliseEncoding(const OutDict_t& sample, const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) const { using metkit::mars2grib::backend::compile_time_registry_engine::StageRuntime; using metkit::mars2grib::frontend::debug::debug_convert_GribHeaderLayoutData_to_json; using metkit::mars2grib::utils::dict_traits::clone_or_throw; using metkit::mars2grib::utils::dict_traits::dict_to_json; using metkit::mars2grib::utils::exceptions::Mars2GribEncoderException; try { auto samplePtr = clone_or_throw(sample); // Encoding loop as a dense set of optimized operations for (const auto& section : plan_[StageRuntime + 1]) { for (const auto& conceptCallback : section) { if (conceptCallback) { conceptCallback(mars, par, opt, *samplePtr); } } } // @todo eventually need to return another clone to commit modifications return samplePtr; } catch (...) { std::throw_with_nested(Mars2GribEncoderException( "Critical failure in SpecializedEncoder execution", dict_to_json(mars), dict_to_json(par), dict_to_json(opt), debug_convert_GribHeaderLayoutData_to_json(layout_), Here())); } } private: /// /// @brief Internalized header layout. /// /// Stored as `const` to enforce immutability after construction. /// /// @note /// This member must be declared before `plan_` so that it is /// fully initialized before being used by the plan factory. /// const HeaderLayout_t layout_; /// /// @brief Optimized execution plan. /// /// Generated eagerly from `layout_` during construction. /// Never modified thereafter. /// const Plan_t plan_; }; } // namespace metkit::mars2grib::frontend::headermetkit-1.20.2/src/metkit/mars2grib/frontend/header/EncodingPlan.h0000664000175000017500000002170115246725757025037 0ustar alastairalastair/* * (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 EncodingPlan.h /// @brief Construction of the header encoding execution plan. /// /// This header defines the data structures and factory function used to /// build an **encoding plan** for GRIB header generation. /// /// An encoding plan is a fully resolved, runtime-ready representation of /// *what encoding callbacks must be executed*, organized by: /// /// - Encoding stage /// - GRIB section /// /// The plan is derived from a resolved header layout and from the /// compile-time encoding callback registry. /// /// @ingroup mars2grib_frontend_header /// #pragma once // System includes #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/EncodingCallbacksRegistry.h" #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/initializers/sectionRegistry.h" #include "metkit/mars2grib/frontend/GribHeaderLayoutData.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::frontend::header::detail { /// /// @brief Fixed-capacity vector used to strictly avoid dynamic allocation. /// /// `FixedVector` provides a minimal vector-like interface backed by a /// statically allocated `std::array`. /// /// The primary motivation for this type is to **completely avoid dynamic /// allocation** in hot-path code that may be executed billions of times. /// In this context: /// /// - The maximum number of elements is small and known at compile time /// (typically <= 22 elements) /// - Allocating tiny dynamic vectors would result in unacceptable /// allocation overhead and memory fragmentation /// /// This abstraction also allows future replacement with a small-buffer /// optimized container (e.g. `boost::container::small_vector`) without /// changing the surrounding code. /// /// @tparam T Element type /// @tparam Capacity Maximum number of elements /// template struct FixedVector { /// Underlying fixed storage std::array data; /// Number of valid elements in @ref data std::size_t current_size = 0; /// /// @brief Append an element to the vector. /// /// @throws Mars2GribGenericException /// If the fixed capacity is exceeded /// void push_back(const T& value) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (current_size >= Capacity) { throw Mars2GribGenericException("FixedVector capacity exceeded", Here()); } data[current_size++] = value; } /// Remove all elements void clear() { current_size = 0; } /// Return the number of stored elements std::size_t size() const { return current_size; } /// Begin iterator (non-const) T* begin() { return data.data(); } /// End iterator (non-const) T* end() { return data.data() + current_size; } /// Begin iterator (const) const T* begin() const { return data.data(); } /// End iterator (const) const T* end() const { return data.data() + current_size; } /// Element access (non-const) T& operator[](std::size_t i) { return data[i]; } /// Element access (const) const T& operator[](std::size_t i) const { return data[i]; } }; /// /// @brief Global compile-time registry of indexing and layout metadata. /// /// `GeneralRegistry` provides **compile-time constants and indexing /// infrastructure** shared across the entire mars2grib codebase. /// /// It defines: /// - The total number of concepts, variants, sections, and stages /// - Stable indices used to access registry-backed tables /// /// Callback registries are intentionally separated from `GeneralRegistry`: /// - They are heavily templated on dictionary types /// - In many contexts only index metadata is required /// - Templating on unused dictionary parameters would be unnecessary /// and harmful to compile-time and readability /// /// This separation keeps indexing lightweight while allowing callback /// registries to remain fully type-safe where required. /// using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Encoding callbacks registry alias. /// /// This alias refers to the compile-time registry that maps /// `(variant, section, stage)` to concrete encoding callbacks. /// /// The underscore namespace is used to avoid clashes with the C++20 /// `concepts` keyword. /// template using EncodingRegistry = metkit::mars2grib::backend::concepts_::EncodingCallbacksRegistry; /// /// @brief Alias for the encoding callback function type. /// /// Extracted from the encoding callbacks registry. /// template using Fn_t = typename EncodingRegistry::Fn_t; /// /// @brief Encoding execution plan. /// /// An `EncodingPlan` is a two-dimensional grid indexed by: /// /// - Encoding stage /// - GRIB section /// /// Each cell contains a fixed-capacity list of encoding callbacks /// that must be executed for that `(stage, section)` pair. /// /// Layout: /// /// @code /// EncodingPlan[Stage][Section] -> list of callbacks /// @endcode /// /// Stage `0` is reserved for **section initializers**. /// Stages `1..N` contain concept encoding callbacks. /// template using EncodingPlan = std::array, GeneralRegistry::NConcepts>, GeneralRegistry::NSections>, GeneralRegistry::NStages + 1>; /// /// @brief Build an encoding plan from resolved header layout data. /// /// This factory function constructs a complete `EncodingPlan` by: /// /// 1. Selecting section initializer callbacks (stage 0) /// 2. Selecting concept encoding callbacks for each section and stage /// /// The plan is fully determined by: /// - The resolved header layout (template numbers and variants) /// - The compile-time encoding callbacks registry /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @param[in] headerLayout Resolved header layout data /// /// @return Fully populated encoding plan /// /// @throws Mars2GribFrontendException /// If plan construction fails /// template EncodingPlan make_EncodingPlan_or_throw( const GribHeaderLayoutData& headerLayout) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { // Access the static callback registry const auto& callbacks = EncodingRegistry::encodingCallbacks; EncodingPlan table; // Stage 0: Populate section initializers (always one initializer per section) for (std::size_t sid = 0; sid < GeneralRegistry::NSections; ++sid) { std::size_t templ = headerLayout.sectionLayouts[sid].templateNumber; table[0][sid].push_back( metkit::mars2grib::backend::sections::initializers::sectionRegistry(sid, templ)); } // Stages 1 to N: Populate encoding callbacks for (std::size_t pid = 0; pid < GeneralRegistry::NStages; ++pid) { for (std::size_t sid = 0; sid < GeneralRegistry::NSections; ++sid) { const auto& section = headerLayout.sectionLayouts[sid]; // Index pid+1 because stage 0 is reserved for initializers auto& cell = table[pid + 1][sid]; cell.clear(); for (std::size_t cid = 0; cid < section.count; ++cid) { std::size_t vid = section.variantIndices[cid]; const auto& f = callbacks[vid][pid][sid]; if (f) { cell.push_back(f); } } } } return table; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Unable to create encoding plan", Here())); } } } // namespace metkit::mars2grib::frontend::header::detailmetkit-1.20.2/src/metkit/mars2grib/backend/0000775000175000017500000000000015246725757020644 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/encodeValues.h0000664000175000017500000002524515246725757023442 0ustar alastairalastair/* * (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 encodeValues.h /// @brief Low-level utility for GRIB payload encoding via non-owning memory spans. /// /// This header defines `encodeValues`, a **terminal encoding operation** that /// bridges raw numeric data to the physical GRIB message representation. /// /// By utilizing `metkit::codes::Span`, this utility achieves **zero-copy /// data passing** from the caller to the encoding engine. A temporary copy /// is only performed if a type conversion (e.g., `float` to `double`) is /// strictly required by the underlying ecCodes API. /// /// The logic is designed to trigger the internal ecCodes encoding machinery, /// which performs: /// - Bitmap construction from missing values /// - Value packing and compression (e.g., CCSDS, Simple Packing) /// - Message size resolution /// /// @ingroup mars2grib_backend /// #pragma once // System includes #include #include #include #include #include // Project includes #include "metkit/codes/api/CodesTypes.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend { template using Span = metkit::codes::Span; /// /// @brief Inject numeric field values and resolve data-section bitmasking. /// /// A `Values` encoding operation represents the **physical realization of /// the GRIB Data Section**. /// /// Unlike metadata encoding, which is combinatorial, value encoding is /// **procedural and performance-critical**. The use of `Span` ensures /// that the payload is passed by reference, avoiding unnecessary allocations /// on the hot path. /// /// The role of this function is to: /// - Configure the GRIB handle's **Data Representation** state /// - Bind the numeric payload to the `values` key /// - Handle **Precision Casting** only when native support is unavailable /// /// ------------------------------------------------------------------------ /// /// @section internal_behavior Internal ecCodes Behavior /// /// Upon setting the `values` key, the underlying ecCodes engine performs: /// - **Missing Value Scanning**: If `bitmapPresent` is enabled, the input /// buffer is scanned, and a bitmask is constructed using the specified /// `missingValue`. /// - **Packing Execution**: The data is compressed according to the /// `packingType` resolved during the header encoding phase. /// /// ------------------------------------------------------------------------ /// /// @tparam Val_t Numeric precision of input (must be `float` or `double`) /// @tparam MiscDict_t Type of the dictionary containing auxiliary metadata /// @tparam OptDict_t Type of the dictionary containing auxiliary metadata /// /// @param[in] values Non-owning span of numeric values (Payload) /// @param[in] misc Dictionary containing bitmap and missing value keys /// @param[in] opt Dictionary containing options /// @param[in,out] handle The GRIB handle to be updated /// /// @return The updated GRIB handle containing the encoded payload /// template void encodeValues(Span values, const MiscDict_t& misc, const OptDict_t& opt, OutDict_t& handle) { 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::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { static_assert(std::is_floating_point_v, "encodeValues: Val_t must be float or double"); // 1. Configure Bitmap and Metadata State const bool bitmapPresent = get_opt(misc, "bitmapPresent").value_or(false); set_or_throw(handle, "bitmapPresent", bitmapPresent); if (bitmapPresent) { // Resolve missing value sentinel cast to double for ecCodes compatibility const double missingValue = get_opt(misc, "missingValue").value_or(static_cast(std::numeric_limits::max())); set_or_throw(handle, "missingValue", missingValue); } // 2. Physical Value Injection // Current ecCodes implementation requires double-precision for the 'values' key. // If input is float, we perform an explicit deep-copy cast to double. if constexpr (std::is_same_v) { set_or_throw(handle, "values", values); } else { // Deep copy-cast from float to double for legacy API support std::vector dValues; dValues.reserve(values.size()); const Val_t* raw = values.data(); for (std::size_t i = 0; i < values.size(); ++i) { dValues.push_back(static_cast(raw[i])); } set_or_throw(handle, "values", Span{dValues}); } // 3. Set operation(s) to be applied on the values if (has(misc, "scaleValuesBy")) { const double scaleValuesBy = get_or_throw(misc, "scaleValuesBy"); set_or_throw(handle, "scaleValuesBy", scaleValuesBy); } if (has(misc, "offsetValuesBy")) { const double offsetValuesBy = get_or_throw(misc, "offsetValuesBy"); set_or_throw(handle, "offsetValuesBy", offsetValuesBy); } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Critical failure in SpecializedEncoder execution", Here())); } } /// /// @brief Configure section3, inject numeric field values and resolve data-section bitmasking. /// /// A `Values` encoding operation represents the **physical realization of /// the GRIB Data Section**. /// /// Unlike metadata encoding, which is combinatorial, value encoding is /// **procedural and performance-critical**. The use of `Span` ensures /// that the payload is passed by reference, avoiding unnecessary allocations /// on the hot path. /// /// The role of this function is to: /// - Configure the GRIB handle's **Data Representation** state /// - Bind the numeric payload to the `values` key /// - Handle **Precision Casting** only when native support is unavailable /// /// ------------------------------------------------------------------------ /// /// @section internal_behavior Internal ecCodes Behavior /// /// Upon setting the `values` key, the underlying ecCodes engine performs: /// - **Missing Value Scanning**: If `bitmapPresent` is enabled, the input /// buffer is scanned, and a bitmask is constructed using the specified /// `missingValue`. /// - **Packing Execution**: The data is compressed according to the /// `packingType` resolved during the header encoding phase. /// /// ------------------------------------------------------------------------ /// /// @tparam Val_t Numeric precision of input (must be `float` or `double`) /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam MiscDict_t Type of the dictionary containing auxiliary metadata /// @tparam OptDict_t Type of the dictionary containing auxiliary metadata /// /// @param[in] values Non-owning span of numeric values (Payload) /// @param[in] mars Read-only MARS dictionary /// @param[in] misc Dictionary containing bitmap and missing value keys /// @param[in] opt Dictionary containing options /// @param[in,out] handle The GRIB handle to be updated /// /// @return The updated GRIB handle containing the encoded payload /// template void encodeValuesGridSpec(Span values, const MarsDict_t& mars, const MiscDict_t& misc, const OptDict_t& opt, OutDict_t& handle) { 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::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { // Recover the value of the gridSpec from Mars dictionary auto GridSpec = get_or_throw(mars, "grid"); set_or_throw(handle, "gridSpec", GridSpec); static_assert(std::is_floating_point_v, "encodeValues: Val_t must be float or double"); // 1. Configure Bitmap and Metadata State const bool bitmapPresent = get_opt(misc, "bitmapPresent").value_or(false); set_or_throw(handle, "bitmapPresent", bitmapPresent); if (bitmapPresent) { // Resolve missing value sentinel cast to double for ecCodes compatibility const double missingValue = get_opt(misc, "missingValue").value_or(static_cast(std::numeric_limits::max())); set_or_throw(handle, "missingValue", missingValue); } // 2. Physical Value Injection // Current ecCodes implementation requires double-precision for the 'values' key. // If input is float, we perform an explicit deep-copy cast to double. if constexpr (std::is_same_v) { set_or_throw(handle, "values", values); } else { // Deep copy-cast from float to double for legacy API support std::vector dValues; dValues.reserve(values.size()); const Val_t* raw = values.data(); for (std::size_t i = 0; i < values.size(); ++i) { dValues.push_back(static_cast(raw[i])); } set_or_throw(handle, "values", Span{dValues}); } // 3. Set operation(s) to be applied on the values if (has(misc, "scaleValuesBy")) { const double scaleValuesBy = get_or_throw(misc, "scaleValuesBy"); set_or_throw(handle, "scaleValuesBy", scaleValuesBy); } if (has(misc, "offsetValuesBy")) { const double offsetValuesBy = get_or_throw(misc, "offsetValuesBy"); set_or_throw(handle, "offsetValuesBy", offsetValuesBy); } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Critical failure in SpecializedEncoder execution", Here())); } } } // namespace metkit::mars2grib::backendmetkit-1.20.2/src/metkit/mars2grib/backend/checks/0000775000175000017500000000000015246725757022104 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/checks/checkDestinELocalSection.h0000664000175000017500000001073615246725757027115 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate the DestinE Local Use Section against production status rules. /// /// This function verifies that, when a GRIB *Local Use Section* is present, /// its content is compatible with the expected DestinE conventions. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function: /// - checks that the Local Use Section is present (`LocalUsePresent != 0`); /// - reads the key `productionStatusOfProcessedData` from the output dictionary; /// - throws an exception if the production status is different from the only /// allowed DestinE value (`12`); /// /// If the Local Use Section is expected but not present, an exception is raised. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the keys /// `LocalUsePresent` and `productionStatusOfProcessedData` /// when checks are enabled /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - checks are enabled and the Local Use Section is not present /// - the production status is not compatible with DestinE rules /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void check_DestinELocalSection_or_throw(const OptDict_t& opt, const OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // If Local Use Section is present, validate production status if (long hasLocalUseSection = get_or_throw(out, "localUsePresent"); hasLocalUseSection != 0) { long actualProductionStatusOfProcessedData = get_or_throw(out, "productionStatusOfProcessedData"); // Throw if no match constexpr long kDestinEProductionStatus = 12; if (actualProductionStatusOfProcessedData != kDestinEProductionStatus) { std::string errMsg = "Invalid DestinE Local Use Section (wrong productionStatusOfProcessedData): "; errMsg += "actual=" + std::to_string(actualProductionStatusOfProcessedData); errMsg += ", expected=" + std::to_string(kDestinEProductionStatus); throw Mars2GribValidationException(errMsg, Here()); } } else { throw Mars2GribValidationException("DestinE Local Use Section not allocated in the sample", Here()); } // Useful for debugging MARS2GRIB_LOG_CHECK("Validated DestinE Local Use Section"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribValidationException("Unable to validate DestinE Local Use Section", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/checkLocalUseSection.h0000664000175000017500000000663115246725757026315 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Check that the GRIB message contains a Local Use Section. /// /// This function verifies the presence of the GRIB *Local Use Section* based on /// the runtime configuration provided in the options dictionary. /// /// The check is performed **only if** the option `applyChecks` is present in /// the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function reads the key `LocalUsePresent` from the output /// dictionary (`out`): /// - a value of `0` indicates that the Local Use Section is missing and results /// in an exception; /// - a non-zero value indicates that the section is present and the check succeeds. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary; must contain the key `LocalUsePresent` /// when checks are enabled /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and `LocalUsePresent == 0` /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void check_LocalUseSection_or_throw(const OptDict_t& opt, const OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { long localUsePresent = get_or_throw(out, "localUsePresent"); if (localUsePresent == 0) { throw Mars2GribValidationException("Local Use Section not present in the sample", Here()); } // Useful for debugging MARS2GRIB_LOG_CHECK("Local Use Section is present in the sample"); } } // Exit point with success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate presence of Local Use Section", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h0000664000175000017500000001125615246725757031575 0ustar alastairalastair/* * (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. */ #pragma once #include #include #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate the Product Definition Template Number against a set of expected values. /// /// This function verifies that the GRIB *Product Definition Template Number* /// matches one of the expected template numbers provided by the caller. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function reads the key `productDefinitionTemplateNumber` /// from the output dictionary (`out`) and compares it against the list of /// expected values supplied in `expectedProductDefinitionTemplateNumber`. /// /// If the actual template number does not match any of the expected values, /// an exception is thrown. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the key /// `productDefinitionTemplateNumber` when checks are enabled /// @param[in] expectedProductDefinitionTemplateNumbers /// List of acceptable Product Definition Template Numbers /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and the template number does not match /// any of the expected values /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void match_ProductDefinitionTemplateNumber_or_throw(const OptDict_t& opt, const OutDict_t& out, const std::vector& expectedProductDefinitionTemplateNumbers) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // Get the productDefinitionTemplateNumber long actualProductDefinitionTemplateNumber = get_or_throw(out, "productDefinitionTemplateNumber"); // Compare against expected values const bool match = std::find(expectedProductDefinitionTemplateNumbers.begin(), expectedProductDefinitionTemplateNumbers.end(), actualProductDefinitionTemplateNumber) != expectedProductDefinitionTemplateNumbers.end(); // Throw if no match if (!match) { std::string errMsg = "Product Definition Template Number does not match any of the expected values: "; errMsg += "actual=" + std::to_string(actualProductDefinitionTemplateNumber); errMsg += ", expected=" + joinNumbers(expectedProductDefinitionTemplateNumbers); throw Mars2GribValidationException(errMsg, Here()); } // Useful for debugging MARS2GRIB_LOG_MATCH("Product Definition Template Number matches expected values"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Product Definition Template Number", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h0000664000175000017500000001164215246725757027512 0ustar alastairalastair/* * (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. */ #pragma once #include #include #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate the Local Definition Number in the Local Use Section. /// /// This function verifies that, when a GRIB *Local Use Section* is present, /// its `localDefinitionNumber` matches one of the expected values provided /// by the caller. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function: /// - checks that the Local Use Section is present (`LocalUsePresent != 0`); /// - reads the key `localDefinitionNumber` from the output dictionary (`out`); /// - compares it against the list of expected local definition numbers. /// /// If the Local Use Section is missing, or if the local definition number does /// not match any of the expected values, an exception is thrown. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the keys /// `LocalUsePresent` and `localDefinitionNumber` /// when checks are enabled /// @param[in] expectedLocalDefinitionNumber /// List of acceptable Local Definition Numbers /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - checks are enabled and the Local Use Section is not present /// - the local definition number does not match any expected value /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void match_LocalDefinitionNumber_or_throw(const OptDict_t& opt, const OutDict_t& out, const std::vector& expectedLocalDefinitionNumber) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // If Local Use Section is present, check definition number if (long hasLocalUseSection = get_or_throw(out, "localUsePresent"); hasLocalUseSection != 0) { long actualLocalDefinitionNumber = get_or_throw(out, "localDefinitionNumber"); // Compare against expected values bool match = std::find(expectedLocalDefinitionNumber.begin(), expectedLocalDefinitionNumber.end(), actualLocalDefinitionNumber) != expectedLocalDefinitionNumber.end(); // Throw if no match if (!match) { std::string errMsg = "Local Definition Number mismatch in Local Use Section: "; errMsg += "actual=" + std::to_string(actualLocalDefinitionNumber); errMsg += ", expected=" + joinNumbers(expectedLocalDefinitionNumber); throw Mars2GribValidationException(errMsg, Here()); } } else { throw Mars2GribValidationException("Local Use Section not present in the sample", Here()); } // Useful for debugging MARS2GRIB_LOG_MATCH("Local Definition Number matches expected values"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Local Definition Number in Local Use Section", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/checkDerivedProductDefinitionSection.h0000664000175000017500000000754515246725757031547 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Check that the GRIB Product Definition Section corresponds to an Derived forecast, /// when validation is enabled. /// /// This function verifies that the GRIB *Product Definition Section* indicates an /// Derived forecast based on the runtime configuration provided in the options dictionary. /// /// The check is performed **only if** the option `applyChecks` is present in /// the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function checks for the presence of the following keys /// in the output dictionary (`out`): /// - `derivedForecast` /// - `numberOfForecastsInEnsemble` /// /// If any of these keys are missing, an exception is raised indicating that /// the Product Definition Section is not of Derived type. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary; must contain the keys required for Derived check /// when checks are enabled /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and any of the required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void check_DerivedProductDefinitionSection_or_throw(const OptDict_t& opt, const OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { bool hasDerivedForecast = has(out, "derivedForecast"); bool hasNumberOfForecastsInEnsemble = has(out, "numberOfForecastsInEnsemble"); // Derived forecast needs to have all 2 fields defined in the Product Definition Section if (!(hasDerivedForecast && hasNumberOfForecastsInEnsemble)) { throw Mars2GribValidationException( "Product Definition Section does not represent a Derived forecast", Here()); } // Useful for debugging MARS2GRIB_LOG_CHECK("Validated Derived Product Definition Section"); } } // Exit point with success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Derived Product Definition Section", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/checkStatisticsProductDefinitionSection.h0000664000175000017500000000755115246725757032314 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Verify that the Product Definition Section corresponds to a statistics product. /// /// This function checks whether the GRIB Product Definition Section (PDS) /// represents a *statistics product*. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// If the option is not present, no validation is performed. /// /// When enabled, the function verifies that the key `numberOfTimeRanges` /// is present in the output dictionary (`out`). /// /// The absence of this field indicates that the Product Definition Section /// does not describe a statistics product and results in an exception. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the key `numberOfTimeRanges` /// when checks are enabled /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - checks are enabled and `numberOfTimeRanges` is missing /// - any error occurs while accessing the dictionaries /// /// @todo Extend the checks to other relevant keys, e.g. `typeOfStatisticsProcessing` /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void check_StatisticsProductDefinitionSection_or_throw(const OptDict_t& opt, const OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { bool hasNumberOfTimeRanges = has(out, "numberOfTimeRanges"); bool hasTypeOfStatisticalProcessing = has(out, "typeOfStatisticalProcessing"); // Statistics product needs to have numberOfTimeRanges defined in the Product Definition Section if (!hasNumberOfTimeRanges || !hasTypeOfStatisticalProcessing) { throw Mars2GribValidationException("Product Definition Section is not of Statistics type", Here()); } // Useful for debugging MARS2GRIB_LOG_CHECK("Product Definition Section is of Statistics type"); } } // Exit point with success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Product Definition Section as Statistics type", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/matchDataset.h0000664000175000017500000001044215246725757024660 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate that the dataset identifier matches an expected value. /// /// This function checks whether the GRIB output dictionary contains a `dataset` /// entry matching the expected dataset identifier provided by the caller. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function reads the key `dataset` from the output dictionary /// (`out`) and compares it against `expectedDataset`. /// A mismatch results in an exception. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the key `dataset` /// when checks are enabled /// @param[in] expectedDatasetString Expected dataset identifier /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and the dataset does not match the expected value /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void match_Dataset_or_throw(const OptDict_t& opt, const OutDict_t& out, const std::string& expectedDatasetString) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // Convert the expected 'dataset' from string to integer long expectedDataset; if (expectedDatasetString == "climate-dt") { expectedDataset = 1; } else if (expectedDatasetString == "extremes-dt") { expectedDataset = 2; } else { throw Mars2GribValidationException( "Expected dataset '" + expectedDatasetString + "' cannot be mapped to integer representation", Here()); } // Get the `dataset` entry (expected in DestinE local use sections) const auto actualDataset = get_or_throw(out, "dataset"); // Compare against expected values if (actualDataset != expectedDataset) { throw Mars2GribValidationException( "Dataset does not match the expected value: actual=" + std::to_string(actualDataset) + ", expected=" + std::to_string(expectedDataset), Here()); } // Useful for debugging MARS2GRIB_LOG_MATCH("Dataset matches expected value"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribValidationException("Unable to validate dataset from the sample", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/matchDataRepresentationTemplateNumber.h0000664000175000017500000001124215246725757031733 0ustar alastairalastair/* * (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. */ #pragma once #include #include #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate the Data Representation Template Number against a set of expected values. /// /// This function verifies that the GRIB *Data Representation Template Number* /// matches one of the expected template numbers provided by the caller. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function reads the key `dataRepresentationTemplateNumber` /// from the output dictionary (`out`) and compares it against the list of /// expected values supplied in `expectedDataRepresentationTemplateNumber`. /// /// If the actual template number does not match any of the expected values, /// an exception is thrown. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the key /// `dataRepresentationTemplateNumber` when checks are enabled /// @param[in] expectedDataRepresentationTemplateNumber /// List of acceptable Data Representation Template Numbers /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and the actual template number does not match /// any of the expected values /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void match_DataRepresentationTemplateNumber_or_throw( const OptDict_t& opt, const OutDict_t& out, const std::vector& expectedDataRepresentationTemplateNumber) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // Get the dataRepresentationTemplateNumber long actualDataRepresentationTemplateNumber = get_or_throw(out, "dataRepresentationTemplateNumber"); // Compare against expected values bool match = std::find(expectedDataRepresentationTemplateNumber.begin(), expectedDataRepresentationTemplateNumber.end(), actualDataRepresentationTemplateNumber) != expectedDataRepresentationTemplateNumber.end(); // Throw if no match if (!match) { std::string errMsg = "Data Representation Template Number does not match any of the expected values: "; errMsg += "actual=" + std::to_string(actualDataRepresentationTemplateNumber); errMsg += ", expected=" + joinNumbers(expectedDataRepresentationTemplateNumber); throw Mars2GribValidationException(errMsg, Here()); } // Useful for debugging MARS2GRIB_LOG_MATCH("Data Representation Template Number matches expected values"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Data Representation Template Number", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/checkEnsembleProductDefinitionSection.h0000664000175000017500000001001615246725757031702 0ustar alastairalastair/* * (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. */ #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Check that the GRIB Product Definition Section corresponds to an Ensemble forecast, /// when validation is enabled. /// /// This function verifies that the GRIB *Product Definition Section* indicates an /// Ensemble forecast based on the runtime configuration provided in the options dictionary. /// /// The check is performed **only if** the option `applyChecks` is present in /// the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function checks for the presence of the following keys /// in the output dictionary (`out`): /// - `typeOfEnsembleForecast` /// - `perturbationNumber` /// - `numberOfForecastsInEnsemble` /// /// If any of these keys are missing, an exception is raised indicating that /// the Product Definition Section is not of Ensemble type. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary; must contain the keys required for Ensemble check /// when checks are enabled /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and any of the required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void check_EnsembleProductDefinitionSection_or_throw(const OptDict_t& opt, const OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { bool hasTypeOfEnsembleForecast = has(out, "typeOfEnsembleForecast"); bool hasPerturbationNumber = has(out, "perturbationNumber"); bool hasNumberOfForecastsInEnsemble = has(out, "numberOfForecastsInEnsemble"); // Ensemble forecast needs to have all 3 fields defined in the Product Definition Section if (!(hasTypeOfEnsembleForecast && hasPerturbationNumber && hasNumberOfForecastsInEnsemble)) { throw Mars2GribValidationException( "Product Definition Section does not represent an Ensemble forecast", Here()); } // Useful for debugging MARS2GRIB_LOG_CHECK("Validated Ensemble Product Definition Section"); } } // Exit point with success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Ensemble Product Definition Section", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/checks/matchGridDefinitionTemplateNumber.h0000664000175000017500000001105715246725757031041 0ustar alastairalastair/* * (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. */ #pragma once #include #include #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::validation { /// /// @brief Validate the Grid Definition Template Number against a set of expected values. /// /// This function verifies that the GRIB *Grid Definition Template Number* /// matches one of the expected template numbers provided by the caller. /// /// The validation is performed **only if** the option `applyChecks` is present /// in the options dictionary (`opt`) and evaluates to `true`. /// /// When enabled, the function reads the key `gridDefinitionTemplateNumber` /// from the output dictionary (`out`) and compares it against the list of /// expected values supplied in `expectedGridDefinitionTemplateNumber`. /// /// If the actual template number does not match any of the expected values, /// an exception is thrown. /// /// Any failure occurring during dictionary access or validation is caught and /// rethrown as a nested `Mars2GribValidationException` with additional context. /// /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// /// @param[in] opt Options dictionary; may contain the boolean key `applyChecks` /// @param[in] out Output dictionary expected to contain the key /// `gridDefinitionTemplateNumber` when checks are enabled /// @param[in] expectedGridDefinitionTemplateNumber /// List of acceptable Grid Definition Template Numbers /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribValidationException /// If: /// - `applyChecks` is `true` and the template number does not match /// any of the expected values /// - required keys are missing /// - any error occurs while accessing the dictionaries /// /// @note /// - If `applyChecks` is absent or evaluates to `false`, no validation is performed. /// - The function returns normally on success and does not produce any output. /// template void match_GridDefinitionTemplateNumber_or_throw(const OptDict_t& opt, const OutDict_t& out, const std::vector& expectedGridDefinitionTemplateNumber) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if constexpr (metkit::mars2grib::utils::dict_traits::dict_supports_checks_v) { if (get_or_throw(opt, "applyChecks")) { // Get the gridDefinitionTemplateNumber long actualGridDefinitionTemplateNumber = get_or_throw(out, "gridDefinitionTemplateNumber"); // Compare against expected values const bool match = std::find(expectedGridDefinitionTemplateNumber.begin(), expectedGridDefinitionTemplateNumber.end(), actualGridDefinitionTemplateNumber) != expectedGridDefinitionTemplateNumber.end(); // Throw if no match if (!match) { std::string errMsg = "Grid Definition Template Number does not match any of the expected values: "; errMsg += "actual=" + std::to_string(actualGridDefinitionTemplateNumber); errMsg += ", expected=" + joinNumbers(expectedGridDefinitionTemplateNumber); throw Mars2GribValidationException(errMsg, Here()); } // Useful for debugging MARS2GRIB_LOG_MATCH("Grid Definition Template Number matches expected values"); } } // Exit on success return; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribValidationException("Unable to validate Grid Definition Template Number", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::validation metkit-1.20.2/src/metkit/mars2grib/backend/concepts/0000775000175000017500000000000015246725757022462 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/nil/0000775000175000017500000000000015246725757023244 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/nil/nilEncoding.h0000664000175000017500000001055515246725757025654 0ustar alastairalastair/* * (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 nilEncoding.h /// @brief Implementation of the GRIB `nil` concept operation. /// /// This header defines the **nil concept**, a sentinel / placeholder concept /// used within the mars2grib backend. /// /// The nil concept: /// - Has no semantic meaning at the GRIB level /// - Must never be applicable /// - Must never modify the output GRIB dictionary /// /// Its primary purposes are: /// - Acting as a compile-time placeholder in concept tables /// - Providing a well-defined failure mode if accidentally invoked /// - Making concept dispatch logic total (no missing concept slots) /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/nil/nilEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `nil` concept. /// /// The nil concept is **never applicable**. /// /// This predicate always evaluates to `false` and exists only to satisfy /// the uniform concept interface expected by the concept dispatcher. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Nil concept variant /// /// @return Always `false`. /// template constexpr bool nilApplicable() { return false; } /// /// @brief Execute the `nil` concept operation. /// /// This function must never perform any operation. /// /// If invoked when not applicable (which is always the case), /// a `Mars2GribConceptException` is thrown with full contextual /// information. /// /// The existence of this function ensures that: /// - concept dispatch tables are complete, /// - accidental invocation is detected early and explicitly, /// - no silent no-op behaviour is possible. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Nil concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary (unused) /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// @param[out] out Output GRIB dictionary (unused) /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// Always thrown if this function is invoked. /// /// @note /// This function intentionally does not provide a silent no-op. /// Any invocation is treated as a programming error. /// template void NilOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) noexcept(false) { using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (nilApplicable()) { // Debug output MARS2GRIB_LOG_CONCEPT(nil); // Successful no-op return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(nil, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/nil/nilMatcher.h0000664000175000017500000000430115246725757025501 0ustar alastairalastair/* * (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 nilMatcher.h /// @brief Entry-level matcher for the GRIB `nil` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the default nil concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/nil/nilEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `nil` concept variant. /// /// The nil concept is always active and resolves to `NilType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `NilType::Default`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t nilMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { return static_cast(NilType::Default); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException("Unable to match `nil` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/nil/nilEnum.h0000664000175000017500000001026415246725757025027 0ustar alastairalastair/* * (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 nilEnum.h /// @brief Definition of the `nil` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `nil` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`nilName`) /// - the enumeration of supported nil variants (`NilType`) /// - a compile-time typelist of all variants (`NilList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `nilEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `nil` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `nil` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view nilName{"nil"}; /// /// @brief Enumeration of all supported `nil` concept variants. /// /// Each enumerator represents a placeholder or empty semantic concept /// used to explicitly denote the absence of a concrete concept value. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal and typically used as /// a sentinel or default concept in the encoder pipeline. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class NilType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `nil` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using NilList = ValueList; /// /// @brief Compile-time mapping from `NilType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given nil variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Nil variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view nilTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view nilTypeName() { \ return NAME; \ } DEF(NilType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/nil/nilConceptDescriptor.h0000664000175000017500000001256015246725757027556 0ustar alastairalastair/* * (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 nilConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `nil` concept. /// /// This header defines `NilConcept`, the **compile-time descriptor** /// that registers the GRIB `nil` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/nil/nilEncoding.h" #include "metkit/mars2grib/backend/concepts/nil/nilEnum.h" #include "metkit/mars2grib/backend/concepts/nil/nilMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `nil` concept. /// /// `NilConcept` registers the GRIB `nil` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct NilConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return nilName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return nilTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `nil` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (nilApplicable()) { return &NilOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &nilMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/0000775000175000017500000000000015246725757025700 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/referencePeriodEncoding.h0000664000175000017500000001147115246725757032625 0ustar alastairalastair/* * (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 referencePeriodEncoding.h /// @brief Implementation of the GRIB `referencePeriod` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **referencePeriod concept** within the mars2grib backend. /// /// The current implementation is an inactive skeleton that provides the /// standard compile-time and runtime hooks required by the concept registry, /// without enabling any encoding behavior yet. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `referencePeriodApplicable` /// - Runtime execution via `ReferencePeriodOp` /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `referencePeriod` concept. /// /// This predicate determines whether the `referencePeriod` concept is /// applicable for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Reference-period concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The current skeleton implementation always returns `false`. /// template constexpr bool referencePeriodApplicable() { return false; } /// /// @brief Execute the `referencePeriod` concept operation. /// /// This function provides the standard runtime hook for the /// `referencePeriod` concept. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Reference-period concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If the concept is invoked when not applicable. /// /// @see referencePeriodApplicable /// template void ReferencePeriodOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { static_cast(mars); static_cast(par); static_cast(opt); static_cast(out); using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (referencePeriodApplicable()) { try { MARS2GRIB_LOG_CONCEPT(referencePeriod); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(referencePeriod, "Unable to set `referencePeriod` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(referencePeriod, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/referencePeriodEnum.h0000664000175000017500000001076715246725757032012 0ustar alastairalastair/* * (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 referencePeriodEnum.h /// @brief Definition of the `referencePeriod` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `referencePeriod` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`referencePeriodName`) /// - the enumeration of supported reference-period variants (`ReferencePeriodType`) /// - a compile-time typelist of all variants (`ReferencePeriodList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `referencePeriodEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `referencePeriod` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `referencePeriod` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view referencePeriodName{"referencePeriod"}; /// /// @brief Enumeration of all supported `referencePeriod` concept variants. /// /// Each enumerator represents a specific reference-period classification or /// processing mode handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the reference-period concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ReferencePeriodType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `referencePeriod` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ReferencePeriodList = ValueList; /// /// @brief Compile-time mapping from `ReferencePeriodType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given reference-period variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Reference-period variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view referencePeriodTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view referencePeriodTypeName() { \ return NAME; \ } DEF(ReferencePeriodType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/referencePeriodConceptDescriptor.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/referencePeriodConceptDescripto0000664000175000017500000001323715246725757034123 0ustar alastairalastair/* * (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 referencePeriodConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `referencePeriod` concept. /// /// This header defines `ReferencePeriodConcept`, the **compile-time descriptor** /// that registers the GRIB `referencePeriod` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodEncoding.h" #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodEnum.h" #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `referencePeriod` concept. /// /// `ReferencePeriodConcept` registers the GRIB `referencePeriod` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct ReferencePeriodConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return referencePeriodName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return referencePeriodTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `referencePeriod` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (referencePeriodApplicable()) { return &ReferencePeriodOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &referencePeriodMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-period/referencePeriodMatcher.h0000664000175000017500000000610215246725757032455 0ustar alastairalastair/* * (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 referencePeriodMatcher.h /// @brief Entry-level matcher for the GRIB `referencePeriod` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether the `referencePeriod` concept is active for a request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `referencePeriod` concept variant. /// /// The `referencePeriod` concept is currently inactive and therefore always /// resolves to `compile_time_registry_engine::MISSING`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return `compile_time_registry_engine::MISSING`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t referencePeriodMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::utils::dict_traits::get_or_throw; const auto marsType = get_or_throw(mars, "type"); const auto param = get_or_throw(mars, "param"); if (marsType == "efi" || marsType == "efic" || marsType == "sot") { return static_cast(ReferencePeriodType::Default); } // Check for standardised anomaly parameters if (matchAny(param, 133093, 133094, 133095, 133096, 133097, 133098)) { return static_cast(ReferencePeriodType::Default); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `referencePeriod` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/AllConcepts.h0000664000175000017500000002026515246725757025047 0ustar alastairalastair/* * (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 AllConcepts.h /// @brief Canonical compile-time list of all mars2grib semantic concepts. /// /// This header defines the **complete, ordered universe of semantic concepts** /// known to the mars2grib backend. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file has a single, strictly limited responsibility: /// /// - to aggregate all concept descriptor types into one canonical /// compile-time `TypeList`. /// /// It does **not**: /// - define any concept logic, /// - implement any dispatch behavior, /// - perform any registry construction, /// - introduce any compile-time algorithms. /// /// All included headers are assumed to provide **concept descriptor types** /// conforming to the `RegisterEntryDescriptor` contract. /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// The `AllConcepts` typelist defined in this file is the **root input** to the /// entire compile-time registry engine. /// /// It is consumed by: /// /// - EntryVariantRegistry (index space definition), /// - EntryCallbacksRegistry (entry-level dispatch), /// - VariantCallbacksRegistry (variant-level dispatch), /// - PhaseCallbacksRegistry (phase-level dispatch). /// /// The **order** of types in this list is semantically significant and defines: /// /// - concept identifiers (conceptId), /// - the layout of flattened variant index spaces, /// - the ordering of all generated dispatch tables. /// /// Reordering entries in this list changes global indices and must therefore be /// treated as a **breaking structural change**. /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Header-only /// - Purely declarative /// - No runtime state /// - No constexpr logic beyond type aggregation /// /// This file must remain lightweight and stable, as it is included transitively /// by many translation units. /// /// ----------------------------------------------------------------------------- /// Extension policy /// ----------------------------------------------------------------------------- /// /// To introduce a new semantic concept into the system: /// /// 1. Implement a new concept descriptor conforming to /// `RegisterEntryDescriptor`. /// 2. Include its header in this file. /// 3. Append the descriptor type to the `AllConcepts` typelist. /// /// No other changes are required to integrate the new concept into the /// compile-time registry engine. /// /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/backend/concepts/analysis/analysisConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/composition/compositionConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/data-type/dataTypeConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/derived/derivedConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/destine/destineConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/ensemble/ensembleConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/iteration/iterationConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/level/levelConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/mars/marsConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/model-error/modelErrorConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/nil/nilConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/origin/originConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/packing/packingConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/param/paramConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/probability/probabilityConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/reference-period/referencePeriodConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/representation/representationConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/satellite/satelliteConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/tables/tablesConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/wave/waveConceptDescriptor.h" namespace metkit::mars2grib::backend::concepts_::detail { /// /// @brief Local alias for the canonical compile-time TypeList. /// /// This alias shortens references to the `TypeList` type provided by the /// compile-time registry engine and avoids repeating long qualified names /// within this namespace. /// /// @tparam Ts /// Pack of concept descriptor types. /// template using TypeList = metkit::mars2grib::backend::compile_time_registry_engine::TypeList; /// /// @brief Complete ordered list of all semantic concept descriptors. /// /// `AllConcepts` defines the **entire concept universe** known to the /// mars2grib backend at compile time. /// /// Each type in this list: /// - represents one semantic concept, /// - conforms to `RegisterEntryDescriptor`, /// - contributes its variants and dispatch interfaces to the registry engine. /// /// ----------------------------------------------------------------------------- /// Ordering guarantees /// ----------------------------------------------------------------------------- /// /// The order of types in this list: /// /// - defines the concept identifier (`conceptId`) assigned to each concept, /// - determines the layout of global variant indices, /// - is propagated unchanged into all generated dispatch tables. /// /// This order must therefore be: /// - stable, /// - intentional, /// - modified only with full awareness of downstream effects. /// /// ----------------------------------------------------------------------------- /// Visibility /// ----------------------------------------------------------------------------- /// /// This typelist is placed in the `detail` namespace to: /// - discourage casual use outside registry construction, /// - make its role as *structural metadata* explicit. /// /// Higher-level code should interact with concepts exclusively through /// registry APIs, not by iterating this list directly. /// using AllConcepts = TypeList; } // namespace metkit::mars2grib::backend::concepts_::detail metkit-1.20.2/src/metkit/mars2grib/backend/concepts/GeneralRegistry.h0000664000175000017500000001204315246725757025741 0ustar alastairalastair/* * (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 GeneralRegistry.h /// @brief Canonical compile-time registry instantiation for all mars2grib concepts. /// /// This header defines the **single, authoritative instantiation** of the /// compile-time registry engine over the complete set of semantic concepts /// known to the mars2grib backend. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file has one precise responsibility: /// /// - to bind the generic `EntryVariantRegistry` template to the concrete, /// ordered list of all concept descriptors (`AllConcepts`). /// /// It does **not**: /// - define any concept descriptors, /// - implement any dispatch logic, /// - perform any compile-time computation itself, /// - introduce any new abstractions. /// /// It is purely a **type aliasing and configuration point**. /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// `GeneralRegistry` is the **central structural metadata provider** for the /// entire mars2grib backend. /// /// It exposes, in a single type: /// /// - the complete concept universe, /// - the global variant index space, /// - all compile-time constants derived from concept structure /// (counts, offsets, identifiers), /// - constexpr lookup tables for concept and variant metadata. /// /// All higher-level systems depend on this registry, including: /// /// - header layout resolution, /// - encoding plan construction, /// - callback dispatch tables, /// - debugging and diagnostic utilities. /// /// ----------------------------------------------------------------------------- /// Ordering and stability guarantees /// ----------------------------------------------------------------------------- /// /// The structure of `GeneralRegistry` is entirely determined by /// `detail::AllConcepts`. /// /// Consequently: /// /// - the order of concepts in `AllConcepts` defines concept identifiers, /// - the order of variants in each concept defines global variant indices, /// - any reordering is a **breaking structural change**. /// /// Stability of this registry is therefore critical for: /// - reproducibility, /// - deterministic encoding, /// - consistency across translation units. /// /// ----------------------------------------------------------------------------- /// Namespace choice /// ----------------------------------------------------------------------------- /// /// This registry is placed in the `concepts_` namespace (with trailing /// underscore) to: /// /// - avoid collision with the C++20 `concepts` keyword, /// - clearly distinguish internal structural concepts from language features. /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Header-only /// - Purely compile-time /// - No runtime state /// - No dynamic allocation /// /// This header must remain lightweight and safe to include transitively. /// /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/EntryVariantRegistry.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/AllConcepts.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Canonical compile-time registry over all mars2grib concepts. /// /// `GeneralRegistry` is a concrete specialization of /// `EntryVariantRegistry`, instantiated with the complete, ordered list /// of all concept descriptors. /// /// This alias provides a **single point of access** to: /// /// - concept counts and identifiers, /// - variant counts and global indices, /// - compile-time offset calculations, /// - constexpr metadata tables (names, IDs), /// - sentinel-aware index utilities. /// /// It is intended to be used as: /// /// - the structural backbone of the encoding pipeline, /// - a read-only, constexpr registry, /// - a shared dependency across frontend and backend layers. /// /// @note /// There must be exactly one such registry instantiation for the entire /// mars2grib backend. Introducing additional instantiations with different /// concept lists would create incompatible index spaces. /// using GeneralRegistry = EntryVariantRegistry; } // namespace metkit::mars2grib::backend::concepts_metkit-1.20.2/src/metkit/mars2grib/backend/concepts/analysis/0000775000175000017500000000000015246725757024305 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/analysis/analysisEnum.h0000664000175000017500000001044615246725757027133 0ustar alastairalastair/* * (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 analysisEnum.h /// @brief Definition of the `analysis` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `analysis` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`analysisName`) /// - the enumeration of supported analysis variants (`AnalysisType`) /// - a compile-time typelist of all variants (`AnalysisList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `analysisEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `analysis` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `analysis` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view analysisName{"analysis"}; /// /// @brief Enumeration of all supported `analysis` concept variants. /// /// Each enumerator represents a specific analysis mode or semantic /// interpretation handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the analysis concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class AnalysisType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `analysis` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using AnalysisList = ValueList; /// /// @brief Compile-time mapping from `AnalysisType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given analysis variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Analysis variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view analysisTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view analysisTypeName() { \ return NAME; \ } DEF(AnalysisType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/analysis/analysisEncoding.h0000664000175000017500000001535415246725757027760 0ustar alastairalastair/* * (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 analysisEncoding.h /// @brief Implementation of the GRIB `analysis` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **analysis concept** within the mars2grib backend. /// /// The concept is responsible for populating GRIB keys related to the /// *Local Use Section* analysis metadata, based on information extracted /// from MARS input dictionaries and validated against GRIB constraints. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `analysisApplicable` /// - Runtime validation and deduction /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/analysis/analysisEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/lengthOfTimeWindow.h" #include "metkit/mars2grib/backend/deductions/offsetToEndOf4DvarWindow.h" // Checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time applicability predicate for the `analysis` concept. /// /// This function determines whether the `analysis` concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// The applicability is evaluated entirely at compile time and is used /// by the concept dispatcher to ensure that only valid concept invocations /// are instantiated. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Analysis concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == AnalysisType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// /// Users may override or specialize this predicate to alter applicability. /// template constexpr bool analysisApplicable() { // Conditions to apply concept return ((Variant == AnalysisType::Default) && (Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the `analysis` concept operation. /// /// This function implements the runtime logic of the GRIB `analysis` concept. /// When applicable, it: /// /// 1. Verifies GRIB preconditions for the Local Use Section. /// 2. Deduces required analysis-related values from MARS and parameter dictionaries. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Analysis concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - required GRIB preconditions are not satisfied /// - any deduction or encoding step fails /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This function does not rely on any pre-existing GRIB header state. /// /// @see analysisApplicable /// template void AnalysisOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (analysisApplicable()) { try { MARS2GRIB_LOG_CONCEPT(analysis); // Structural validation validation::match_LocalDefinitionNumber_or_throw(opt, out, {36L, 37L, 38L, 39L}); // Deductions long offsetToEndOf4DvarWindowVal = deductions::resolve_offsetToEndOf4DvarWindow_or_throw(mars, par, opt); std::optional lengthOfTimeWindowVal = deductions::resolve_LengthOfTimeWindowInSeconds_or_throw(mars, par, opt); // Encoding set_or_throw(out, "offsetToEndOf4DvarWindow", offsetToEndOf4DvarWindowVal); if (lengthOfTimeWindowVal.has_value()) { set_or_throw(out, "lengthOf4DvarWindow", lengthOfTimeWindowVal.value() / 3600); } else { long missingLengthOfTimeWindowInHours = 0xFFFF; // Missing sentinel in hours set_or_throw(out, "lengthOf4DvarWindow", missingLengthOfTimeWindowInHours); // Missing } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(analysis, "Unable to set `analysis` concept..."); } return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(analysis, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/analysis/analysisConceptDescriptor.h0000664000175000017500000001457215246725757031665 0ustar alastairalastair/* * (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 analysisConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `analysis` concept. /// /// This header defines `AnalysisConcept`, the **compile-time descriptor** /// that registers the GRIB `analysis` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/analysis/analysisEncoding.h" #include "metkit/mars2grib/backend/concepts/analysis/analysisEnum.h" #include "metkit/mars2grib/backend/concepts/analysis/analysisMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `analysis` concept. /// /// `AnalysisConcept` registers the GRIB `analysis` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct AnalysisConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return analysisName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return analysisTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `analysis` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (analysisApplicable()) { return &AnalysisOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `analysis` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `analysis` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &analysisMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/analysis/analysisMatcher.h0000664000175000017500000000512415246725757027607 0ustar alastairalastair/* * (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 analysisMatcher.h /// @brief Entry-level matcher for the GRIB `analysis` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether the **analysis concept** is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/analysis/analysisEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `analysis` concept variant. /// /// The concept is active when the MARS request contains `anoffset`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `AnalysisType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t analysisMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "anoffset")) { return static_cast(AnalysisType::Default); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `analysis` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/0000775000175000017500000000000015246725757024654 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsMatcher.h0000664000175000017500000002101415246725757030521 0ustar alastairalastair/* * (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 statisticsMatcher.h /// @brief Entry-level matcher for the GRIB `statistics` concept. /// /// This header defines the runtime matcher used by the concept registry to /// identify statistical-processing semantics from the MARS parameter code. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsEnum.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_ { /// /// @brief Match the `statistics` concept variant. /// /// The matcher maps known statistical MARS parameter codes onto the appropriate /// local `StatisticsType` variant. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local statistics variant index, or /// `compile_time_registry_engine::MISSING` when no statistical-processing /// concept is active. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If the required parameter metadata cannot be read or lower-level matcher /// evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t statisticsMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; const auto param = get_or_throw(mars, "param"); if (matchAny(param, 8, 9, 20, 44, 45, 47, 50, 57, 58, range(142, 147), 169, range(175, 182), 189, range(195, 197), 205, range(208, 213), 228, 239, 240, 3062, 3099, range(162100, 162113), range(222001, 222256), 228021, 228022, 228129, 228130, 228143, 228144, 228216, 228228, 228251, range(231001, 231003), 231005, 231010, 231012, 231057, 231058, range(233000, 233031), 260259)) { return static_cast(StatisticsType::Accumulation); } // Strike-probability products if (matchAny(param, 131060, 131061, 131062, 131063, 131064, 131085, 131098, 131099)) { return static_cast(StatisticsType::Accumulation); } if (matchAny(param, range(141101, 141105), 141208, 141209, 141215, 141216, 141220, 141229, 141231, 141232, 141233, 141245, 228004, 228005, 228051, 228053, range(228057, 228060), 235020, 235021, range(235029, 235031), range(235033, 235043), range(235048, 235053), 235055, 235058, range(235077, 235080), 235083, 235084, 235087, 235088, 235090, 235091, 235093, 235094, 235097, 235098, 235100, 235108, 235117, range(235129, 235138), 235151, 235152, 235155, 235157, 235159, 235165, 235166, 235168, 235189, 235203, 235246, 235263, 235269, 235283, 235287, 235288, 235290, 235305, 235309, 235320, 235322, 235326, 235339, 235383, 263024, 263107)) { return static_cast(StatisticsType::Average); } // Strike-probability products if (matchAny(param, 131065, 131066, 131067)) { return static_cast(StatisticsType::Average); } if (matchAny(param, 49, 121, 123, 201, range(143101, 143105), 143208, 143209, 143215, 143216, 143220, 143229, 143231, 143232, 143233, 143245, 228026, 228028, 228035, 228036, 228222, 228224, 228226, 237013, 237041, 237042, 237055, 237077, 237078, 237079, 237080, 237083, 237084, 237087, 237088, 237090, 237091, 237093, 237094, 237097, 237108, 237117, 237131, 237132, 237134, 237137, 237151, 237159, range(237165, 237168), 237203, 237207, 237263, 237287, 237288, 237290, 237305, 237309, 237318, 237320, 237321, 237322, 237326, 265024)) { return static_cast(StatisticsType::Maximum); } // Strike-probability products if (matchAny(param, 131071, 131072, 131100)) { return static_cast(StatisticsType::Maximum); } if (matchAny(param, 122, 202, range(144101, 144105), 144208, 144209, 144215, 144216, 144220, 144229, 144231, 144232, 144233, 144245, 228027, 228223, 228225, 228227, 238013, 238041, 238042, 238055, 238077, 238078, 238079, 238080, 238083, 238084, 238087, 238088, 238090, 238091, 238093, 238094, 238097, 238108, 238117, 238131, 238132, 238134, 238137, 238151, 238159, range(238165, 238168), 238203, 238207, 238263, 238287, 238288, 238290, 238305, 238309, 238320, 238322, 238326, 266024, 131070)) { return static_cast(StatisticsType::Minimum); } if (matchAny(param, 260320, 260321, 260339, 260683)) { return static_cast(StatisticsType::Mode); } if (matchAny(param, 260318, 260319, 260338, 260682)) { return static_cast(StatisticsType::Severity); } if (matchAny(param, range(145101, 145105), 145208, 145209, 145215, 145216, 145220, 145229, 145231, 145232, 145233, 145245, 239041, 239042, 239077, 239078, 239079, 239080, 239083, 239084, 239087, 239088, 239090, 239091, 239093, 239094, 239097, 239108, 239117, 239131, 239132, 239134, 239137, 239151, 239159, range(239165, 239168), 239203, 239207, 239263, 239287, 239288, 239290, 239305, 239309, 239320, 239322, 239326, 267024)) { return static_cast(StatisticsType::StandardDeviation); } // Strike-probability products // if (matchAny(param, 133093, 133094, 133095, 133096, 133097, 133098)) { // return static_cast(StatisticsType::StandardDeviation); // } // Chemical products if (matchAny(param, range(228080, 228082), range(233032, 233035), range(235062, 235064))) { return static_cast(StatisticsType::Accumulation); } // TODO: Don't handle products with timespan as non-statistical if they are not handled above! // if (has(mars, "timespan")) { // throw utils::exceptions::Mars2GribMatcherException("MARS contains `timespan` but // typeOfStatisticalProcessing is defined for param " + std::to_string(param), Here()); // } if (matchAny(param, 132044, 132045, 132049, 132059, 132144, 132165, 132167, 132201, 132202, 132228)) { return static_cast(StatisticsType::IndexProcessing); } // event probability standardised anomaly products // if (matchAny(param, 133093, 133094, 133095, 133096, 133097, 133098)) { // return static_cast(StatisticsType::StandardizedAnomaly); // } // event probability maximum // if (matchAny(param, 131089, 131090, 131091)) { // return static_cast(StatisticsType::Maximum); // } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `statistics` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsEnum.h0000664000175000017500000002421315246725757030046 0ustar alastairalastair/* * (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 statisticsEnum.h /// @brief Definition of the `statistics` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `statistics` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`statisticsName`) /// - the exhaustive enumeration of supported statistical processing variants (`StatisticsType`) /// - a compile-time typelist of all variants (`StatisticsList`) /// - a compile-time mapping from variant to string identifier /// - a compile-time mapping from variant to GRIB `typeOfStatisticalProcessing` code /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// - static mapping to GRIB statistical processing identifiers /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `statisticsEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `statistics` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `statistics` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view statisticsName{"statistics"}; /// /// @brief Enumeration of all supported `statistics` concept variants. /// /// Each enumerator represents a distinct statistical processing operation /// applied to time ranges, ensembles, or aggregated datasets, as defined /// by GRIB conventions and ECMWF usage. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both standard GRIB statistical operators /// and extended or ECMWF-specific processing types. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class StatisticsType : std::size_t { Average = 0, Accumulation, Maximum, Minimum, DifferenceFromStart, RootMeanSquare, StandardDeviation, Covariance, DifferenceFromEnd, Ratio, StandardizedAnomaly, Summation, ReturnPeriod, Median, Severity, Mode, IndexProcessing, Default }; /// /// @brief Compile-time list of all `statistics` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using StatisticsList = ValueList; /// /// @brief Compile-time mapping from `StatisticsType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given statistical processing variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Statistics variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view statisticsTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view statisticsTypeName() { \ return NAME; \ } DEF(StatisticsType::Average, "average"); DEF(StatisticsType::Accumulation, "accumulation"); DEF(StatisticsType::Maximum, "maximum"); DEF(StatisticsType::Minimum, "minimum"); DEF(StatisticsType::DifferenceFromStart, "differenceFromStart"); DEF(StatisticsType::RootMeanSquare, "rootMeanSquare"); DEF(StatisticsType::StandardDeviation, "standardDeviation"); DEF(StatisticsType::Covariance, "covariance"); DEF(StatisticsType::DifferenceFromEnd, "differenceFromEnd"); DEF(StatisticsType::Ratio, "ratio"); DEF(StatisticsType::StandardizedAnomaly, "standardizedAnomaly"); DEF(StatisticsType::Summation, "summation"); DEF(StatisticsType::ReturnPeriod, "returnPeriod"); DEF(StatisticsType::Median, "median"); DEF(StatisticsType::Severity, "severity"); DEF(StatisticsType::Mode, "mode"); DEF(StatisticsType::IndexProcessing, "indexProcessing"); DEF(StatisticsType::Default, "default"); #undef DEF /// /// @brief Compile-time mapping from `StatisticsType` to GRIB `typeOfStatisticalProcessing` code. /// /// This function returns the numeric GRIB code associated with a given /// statistical processing variant, as defined by GRIB tables and /// ECMWF extensions. /// /// The returned value is used during encoding to populate the /// `typeOfStatisticalProcessing` key in GRIB messages. /// /// @tparam T Statistics variant /// @return GRIB code corresponding to the variant /// /// @note /// The returned values must remain stable across releases to ensure /// GRIB compatibility. /// template constexpr long typeOfStatisticalProcessing(); #define DEF(T, NAME) \ template <> \ constexpr long typeOfStatisticalProcessing() { \ return NAME; \ } DEF(StatisticsType::Average, 0); DEF(StatisticsType::Accumulation, 1); DEF(StatisticsType::Maximum, 2); DEF(StatisticsType::Minimum, 3); DEF(StatisticsType::DifferenceFromStart, 4); DEF(StatisticsType::RootMeanSquare, 5); DEF(StatisticsType::StandardDeviation, 6); DEF(StatisticsType::Covariance, 7); DEF(StatisticsType::DifferenceFromEnd, 8); DEF(StatisticsType::Ratio, 9); DEF(StatisticsType::StandardizedAnomaly, 10); DEF(StatisticsType::Summation, 11); DEF(StatisticsType::ReturnPeriod, 12); DEF(StatisticsType::Median, 13); DEF(StatisticsType::Severity, 100); DEF(StatisticsType::Mode, 101); DEF(StatisticsType::IndexProcessing, 102); DEF(StatisticsType::Default, 255); #undef DEF /// /// @brief Compile-time mapping from `StatisticsType` to /// `tables::TypeOfStatisticalProcessing` (GRIB Code Table 4.10). /// /// Parallel to `typeOfStatisticalProcessing()` (which returns the raw /// `long` GRIB code). Use this overload when the call site needs the /// strongly-typed enum value — e.g. when passing the inner statistical /// operation to `resolve_TypeOfStatisticalProcessing_or_throw`. /// /// The two specialization sets MUST stay in numerical lock-step; any /// future addition to `StatisticsType` MUST update both. /// /// @note /// Three enumerator names differ between the concept-side `StatisticsType` /// and the table-side `tables::TypeOfStatisticalProcessing` (the numeric /// codes match exactly): /// - `StatisticsType::DifferenceFromStart` → `DifferenceEndMinusStart` (4) /// - `StatisticsType::DifferenceFromEnd` → `DifferenceStartMinusEnd` (8) /// - `StatisticsType::Default` → `Missing` (255) /// template constexpr tables::TypeOfStatisticalProcessing typeOfStatisticalProcessingEnum(); #define DEF(T, VAL) \ template <> \ constexpr tables::TypeOfStatisticalProcessing typeOfStatisticalProcessingEnum() { \ return tables::TypeOfStatisticalProcessing::VAL; \ } DEF(StatisticsType::Average, Average); DEF(StatisticsType::Accumulation, Accumulation); DEF(StatisticsType::Maximum, Maximum); DEF(StatisticsType::Minimum, Minimum); DEF(StatisticsType::DifferenceFromStart, DifferenceEndMinusStart); DEF(StatisticsType::RootMeanSquare, RootMeanSquare); DEF(StatisticsType::StandardDeviation, StandardDeviation); DEF(StatisticsType::Covariance, Covariance); DEF(StatisticsType::DifferenceFromEnd, DifferenceStartMinusEnd); DEF(StatisticsType::Ratio, Ratio); DEF(StatisticsType::StandardizedAnomaly, StandardizedAnomaly); DEF(StatisticsType::Summation, Summation); DEF(StatisticsType::ReturnPeriod, ReturnPeriod); DEF(StatisticsType::Median, Median); DEF(StatisticsType::Severity, Severity); DEF(StatisticsType::Mode, Mode); DEF(StatisticsType::IndexProcessing, IndexProcessing); DEF(StatisticsType::Default, Missing); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsConceptDescriptor.h0000664000175000017500000001206215246725757032573 0ustar alastairalastair/* * (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 statisticsConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `statistics` concept. /// /// This header defines `StatisticsConcept`, the **compile-time descriptor** /// that registers the GRIB `statistics` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/statistics/statisticsEncoding.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsEnum.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `statistics` concept. /// /// `StatisticsConcept` registers the GRIB `statistics` concept into the /// compile-time registry engine. /// struct StatisticsConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return statisticsName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return statisticsTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `statistics` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (statisticsApplicable()) { return &StatisticsOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `statistics` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &statisticsMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsEncoding.h0000664000175000017500000002110615246725757030666 0ustar alastairalastair/* * (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 statisticsEncoding.h /// @brief Implementation of the GRIB `statistics` concept operation. /// /// The `statistics` concept encodes GRIB Section-4 keys describing /// statistical processing over time (PDT 4.8 / 4.11). It runs in three /// stages: /// /// ### StageAllocate /// - Validates that the Product Definition Section supports statistics. /// - Builds one final immutable `ProductTimeSpec`. /// - Encodes `numberOfTimeRanges` from the corresponding /// `StatisticsProductTimeSpec` transport. /// - Sets `hoursAfterDataCutoff` / `minutesAfterDataCutoff` to missing. /// /// ### StagePreset /// - Builds one final immutable `ProductTimeSpec`. /// - Builds one `StatisticsProductTimeSpec` transport from that model. /// - Writes the 6 SoA vectors verbatim: /// * `typeOfStatisticalProcessing` /// * `typeOfTimeIncrement` /// * `indicatorOfUnitForTimeRange` /// * `lengthOfTimeRange` /// * `indicatorOfUnitForTimeIncrement` /// * `lengthOfTimeIncrement` /// No instant / single-window / multi-window branching is needed in the /// encoding layer because the transport already materializes the canonical /// vectors. /// /// ### StageRuntime /// - Builds one final immutable `ProductTimeSpec`. /// - Builds one `StatisticsProductTimeSpec` transport from that model. /// - Encodes the time-dependent keys from that transport: /// - `forecastTime` /// - `OfEndOfOverallTimeInterval` /// /// All temporal data is sourced exclusively from the final immutable /// `backend::models::product_time_spec::ProductTimeSpec` model through the /// statistics-facing transport struct built by /// `build_StatisticsProductTimeSpec_or_throw(...)`. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/statistics/impl/StatisticsProductTimeSpec.h" #include "metkit/mars2grib/backend/concepts/statistics/statisticsEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Models (new product time implementation) #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h" // Deductions #include "metkit/mars2grib/backend/deductions/common.h" // Checks #include "metkit/mars2grib/backend/checks/checkStatisticsProductDefinitionSection.h" // Tables #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `statistics` concept. /// /// The concept is applicable for the *Product Definition Section* (Section 4) /// at any encoding stage. /// template constexpr bool statisticsApplicable() { return (Section == SecProductDefinitionSection && Variant != StatisticsType::IndexProcessing); } /// /// @brief Execute the `statistics` concept operation. /// /// See file-level documentation for stage-by-stage semantics. /// /// All encoded temporal payload values and GRIB statistical-processing arrays /// are sourced from `StatisticsProductTimeSpec`, which is itself built from one /// final immutable `backend::models::product_time_spec::ProductTimeSpec`. /// template void StatisticsOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::dict_traits::setMissing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (statisticsApplicable()) { try { MARS2GRIB_LOG_CONCEPT(statistics); // ============================================================= // StageAllocate // ============================================================= if constexpr (Stage == StageAllocate) { const auto spec = models::product_time_spec::ProductTimeSpec(typeOfStatisticalProcessingEnum(), mars, par, opt); const auto pts = impl::build_StatisticsProductTimeSpec_or_throw(spec); // Checks/Validation validation::check_StatisticsProductDefinitionSection_or_throw(opt, out); // Encoding setMissing_or_throw(out, "hoursAfterDataCutoff"); setMissing_or_throw(out, "minutesAfterDataCutoff"); set_or_throw(out, "numberOfTimeRanges", pts.numberOfTimeRanges); } // ============================================================= // StagePreset // // Single uniform path. No branching for instant / single-window / // multi-window: the resolved `StatisticsProductTimeSpec` transport // already materializes the canonical vectors. // ============================================================= if constexpr (Stage == StagePreset) { const auto spec = models::product_time_spec::ProductTimeSpec(typeOfStatisticalProcessingEnum(), mars, par, opt); const auto pts = impl::build_StatisticsProductTimeSpec_or_throw(spec); set_or_throw>(out, "typeOfStatisticalProcessing", pts.typeOfStatisticalProcessing); set_or_throw>(out, "typeOfTimeIncrement", pts.typeOfTimeIncrement); set_or_throw>(out, "indicatorOfUnitForTimeRange", pts.indicatorOfUnitForTimeRange); set_or_throw>(out, "lengthOfTimeRange", pts.lengthOfTimeRange); set_or_throw>(out, "indicatorOfUnitForTimeIncrement", pts.indicatorOfUnitForTimeIncrement); set_or_throw>(out, "timeIncrement", pts.lengthOfTimeIncrement); } // ============================================================= // StageRuntime // // Time-dependent keys: forecastTime and end-of-interval date/time. // ============================================================= if constexpr (Stage == StageRuntime) { const auto spec = models::product_time_spec::ProductTimeSpec(typeOfStatisticalProcessingEnum(), mars, par, opt); const auto pts = impl::build_StatisticsProductTimeSpec_or_throw(spec); set_or_throw(out, "forecastTime", pts.forecastTime.length); const eckit::DateTime& end = pts.endOfOverallTimeInterval; set_or_throw(out, "yearOfEndOfOverallTimeInterval", end.date().year()); set_or_throw(out, "monthOfEndOfOverallTimeInterval", end.date().month()); set_or_throw(out, "dayOfEndOfOverallTimeInterval", end.date().day()); set_or_throw(out, "hourOfEndOfOverallTimeInterval", end.time().hours()); set_or_throw(out, "minuteOfEndOfOverallTimeInterval", end.time().minutes()); set_or_throw(out, "secondOfEndOfOverallTimeInterval", end.time().seconds()); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(statistics, "Unable to set `statistics` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(statistics, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/impl/0000775000175000017500000000000015246725757025615 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/statistics/impl/StatisticsProductTimeSpec.h0000664000175000017500000001545015246725757033120 0ustar alastairalastair/* * (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 impl/StatisticsProductTimeSpec.h /// @brief Statistics-specific transport struct built from `ProductTimeSpec`. /// /// Exposes `StatisticsProductTimeSpec` plus the pure builder /// `build_StatisticsProductTimeSpec_or_throw`, which turns one final immutable /// `backend::models::product_time_spec::ProductTimeSpec` into the statistics- /// facing transport struct consumed by the statistics concept. /// /// This header owns only the statistics-facing transport type and the builder. /// It does NOT perform GRIB encoding itself. /// /// The resulting representation preserves the canonical ProductTimeSpec window /// ordering (outermost -> innermost). /// /// @ingroup mars2grib_backend_concepts /// #pragma once #include #include #include "eckit/types/DateTime.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h" #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_::impl { /// /// @brief Statistics-facing transport struct derived from `ProductTimeSpec`. /// /// The struct combines: /// - runtime temporal metadata used by the statistics concept; /// - the GRIB statistical-processing arrays written during preset/allocate /// stages. /// /// All vector members have length `numberOfTimeRanges` and preserve the /// canonical `ProductTimeSpec` window ordering (outermost -> innermost). /// /// `forecastTime` is currently materialized only in hours. /// struct StatisticsProductTimeSpec { /// @brief Forecast-time offset expressed in whole hours. deductions::TimeDuration forecastTime{0, tables::TimeUnit::Hour}; /// @brief End datetime of the overall statistical interval. eckit::DateTime endOfOverallTimeInterval{}; /// @brief Number of encoded time ranges. long numberOfTimeRanges{0}; /// @brief GRIB `typeOfStatisticalProcessing` vector. std::vector typeOfStatisticalProcessing; /// @brief GRIB `typeOfTimeIncrement` vector. std::vector typeOfTimeIncrement; /// @brief GRIB `indicatorOfUnitForTimeRange` vector. std::vector indicatorOfUnitForTimeRange; /// @brief GRIB `lengthOfTimeRange` vector. std::vector lengthOfTimeRange; /// @brief GRIB `indicatorOfUnitForTimeIncrement` vector. std::vector indicatorOfUnitForTimeIncrement; /// @brief GRIB `timeIncrement` vector. std::vector lengthOfTimeIncrement; }; /// /// @brief Build the statistics transport struct from one final `ProductTimeSpec`. /// /// The builder reads only the final immutable backend-model `ProductTimeSpec` /// and materializes the statistics-facing representation consumed by the /// statistics concept. /// /// Build rules: /// - `Instant` ProductTimeSpec values are rejected; /// - `forecastTime` is the distance between /// `anchor.referenceDateTime` and `domain.domainStartDateTime`; /// - `forecastTime` must be non-negative and a whole number of hours; /// - `endOfOverallTimeInterval` is `domain.domainEndDateTime`; /// - the array members are copied directly from the canonical windows in /// outermost -> innermost order. /// /// @param[in] spec Final immutable backend-model `ProductTimeSpec`. /// @return Fully populated `StatisticsProductTimeSpec`. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribGenericException on /// instant input, invalid forecast-time arithmetic, or any unexpected /// failure, with the original cause preserved through nested /// exceptions. /// inline StatisticsProductTimeSpec build_StatisticsProductTimeSpec_or_throw( const models::product_time_spec::ProductTimeSpec& spec) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { if (spec.shapeType() == ProductTimeSpecShapeKind::Instant) { throw Mars2GribGenericException( "Statistics backend cannot build a `StatisticsProductTimeSpec` from an Instant `ProductTimeSpec`", Here()); } const long forecastTimeInSeconds = static_cast( static_cast(spec.domain().domainStartDateTime - spec.anchor().referenceDateTime)); if (forecastTimeInSeconds < 0) { throw Mars2GribGenericException("`StatisticsProductTimeSpec` forecastTime must be non-negative", Here()); } if (forecastTimeInSeconds % 3600 != 0) { throw Mars2GribGenericException("`StatisticsProductTimeSpec` forecastTime must be a whole number of hours", Here()); } StatisticsProductTimeSpec out; out.forecastTime = deductions::TimeDuration{forecastTimeInSeconds / 3600, tables::TimeUnit::Hour}; out.endOfOverallTimeInterval = spec.domain().domainEndDateTime; const auto& windows = spec.windows().values; out.numberOfTimeRanges = static_cast(windows.size()); out.typeOfStatisticalProcessing.reserve(windows.size()); out.typeOfTimeIncrement.reserve(windows.size()); out.indicatorOfUnitForTimeRange.reserve(windows.size()); out.lengthOfTimeRange.reserve(windows.size()); out.indicatorOfUnitForTimeIncrement.reserve(windows.size()); out.lengthOfTimeIncrement.reserve(windows.size()); for (const auto& window : windows) { out.typeOfStatisticalProcessing.push_back(static_cast(window.typeOfStatisticalProcessing)); out.typeOfTimeIncrement.push_back(static_cast(window.typeOfTimeIncrement)); out.indicatorOfUnitForTimeRange.push_back(static_cast(window.timeRange.unit)); out.lengthOfTimeRange.push_back(window.timeRange.length); out.indicatorOfUnitForTimeIncrement.push_back(static_cast(window.timeIncrement.unit)); out.lengthOfTimeIncrement.push_back(window.timeIncrement.length); } return out; } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to build `StatisticsProductTimeSpec` from `ProductTimeSpec`", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_::impl metkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/0000775000175000017500000000000015246725757026261 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h0000664000175000017500000001103615246725757032742 0ustar alastairalastair/* * (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 generatingProcessEnum.h /// @brief Definition of the `generatingProcess` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `generatingProcess` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`generatingProcessName`) /// - the enumeration of supported generating-process variants (`GeneratingProcessType`) /// - a compile-time typelist of all variants (`GeneratingProcessList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `generatingProcessEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `generatingProcess` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `generatingProcess` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view generatingProcessName{"generatingProcess"}; /// /// @brief Enumeration of all supported `generatingProcess` concept variants. /// /// Each enumerator represents a specific generating-process classification /// handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the generating-process concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class GeneratingProcessType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `generatingProcess` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using GeneratingProcessList = ValueList; /// /// @brief Compile-time mapping from `GeneratingProcessType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given generating-process variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Generating-process variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view generatingProcessTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view generatingProcessTypeName() { \ return NAME; \ } DEF(GeneratingProcessType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessMatcher.h0000664000175000017500000000457515246725757033433 0ustar alastairalastair/* * (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 generatingProcessMatcher.h /// @brief Entry-level matcher for the GRIB `generatingProcess` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the default generating-process concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `generatingProcess` concept variant. /// /// The generating-process concept is always active and resolves to /// `GeneratingProcessType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `GeneratingProcessType::Default`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t generatingProcessMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { return static_cast(GeneratingProcessType::Default); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `generatingProcess` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessConceptDescriptor.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessConceptDescr0000664000175000017500000001517015246725757034167 0ustar alastairalastair/* * (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 generatingProcessConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `generatingProcess` concept. /// /// This header defines `GeneratingProcessConcept`, the **compile-time descriptor** /// that registers the GRIB `generatingProcess` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessEncoding.h" #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h" #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `generatingProcess` concept. /// /// `GeneratingProcessConcept` registers the GRIB `generatingProcess` /// concept into the compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct GeneratingProcessConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return generatingProcessName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return generatingProcessTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `generatingProcess` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (generatingProcessApplicable()) { return &GeneratingProcessOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `generatingProcess` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the /// `generatingProcess` concept should be activated for a given /// encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &generatingProcessMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessEncoding.h0000664000175000017500000002137715246725757033575 0ustar alastairalastair/* * (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 generatingProcessEncoding.h /// @brief Implementation of the GRIB `generatingProcess` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **generatingProcess concept** within the mars2grib backend. /// /// The generatingProcess concept is responsible for populating GRIB keys /// related to the *origin and nature of the data generation process*, including: /// /// - `backgroundProcess` /// - `generatingProcessIdentifier` /// - `typeOfGeneratingProcess` /// /// These keys are encoded in the Product Definition Section and are tightly /// coupled to both MARS semantics and GRIB code tables. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `generatingProcessApplicable` /// - Delegation of semantic resolution to dedicated deduction functions /// - Explicit handling of legacy encoder behavior /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Tables #include "metkit/mars2grib/backend/tables/backgroundProcess.h" #include "metkit/mars2grib/backend/tables/typeOfGeneratingProcess.h" // Deductions #include "metkit/mars2grib/backend/deductions/backgroundProcess.h" #include "metkit/mars2grib/backend/deductions/generatingProcessIdentifier.h" #include "metkit/mars2grib/backend/deductions/typeOfGeneratingProcess.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `generatingProcess` concept. /// /// This predicate determines whether the generatingProcess concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Generating process concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept when: /// - the encoding stage is `StagePreset`, **or** /// - the concept variant is `GeneratingProcessType::Default` /// - and the GRIB section is `SecIdentificationSection` /// /// This permissive rule reflects the historical behavior of the encoder and /// allows the concept to participate in multiple encoding paths. /// template constexpr bool generatingProcessApplicable() { return ((Section == SecProductDefinitionSection) && (Stage == StagePreset) && (Variant == GeneratingProcessType::Default)); } /// /// @brief Execute the generatingProcess concept operation. /// /// This function implements the runtime logic of the GRIB `generatingProcess` /// concept. When applicable, it: /// /// 1. Resolves the background process associated with the data. /// 2. Optionally resolves the generating process identifier. /// 3. Optionally resolves the type of generating process. /// /// The current implementation contains **explicit legacy compatibility paths** /// that mirror the behavior of the previous encoder, including reliance on /// pre-existing GRIB header state and ecCodes side effects. /// /// These paths are clearly marked and must be removed in a future cleanup /// iteration. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Generating process concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - any deduction fails /// - a required GRIB key cannot be set /// - the concept is invoked outside its applicability domain /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - Several code paths are intentionally marked as legacy and must not /// be extended. /// /// @todo [owner: mds,dgov][scope: concept][reason: legacy][prio: high] /// - Remove all reliance on `std::optional` forwarding. /// - Remove reliance on ecCodes implicit behavior. /// - Enforce explicit defaults, mandatory values, or hard failures. /// /// @see generatingProcessApplicable /// template void GeneratingProcessOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::backend::tables::TypeOfGeneratingProcess; using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (generatingProcessApplicable()) { try { MARS2GRIB_LOG_CONCEPT(generatingProcess); // Retrieve the information std::optional generatingProcessIdentifier = deductions::resolve_GeneratingProcessIdentifier_opt(mars, par, opt); std::optional typeOfGeneratingProcess = deductions::resolve_TypeOfGeneratingProcess_opt(mars, par, opt); tables::BackgroundProcess backgroundProcess = deductions::resolve_BackgroundProcess_or_throw(mars, par, opt); set_or_throw(out, "backgroundProcess", static_cast(backgroundProcess)); /// @todo [owner: mds,dgov][scope: concept][reason: legacy][prio: high] /// Remove this logic. /// /// Deductions must not forward `std::optional` values directly. /// A proper deduction must: /// - set an explicit value (e.g. `Missing`), /// - apply a DGOV-approved default, /// - or throw if the value is mandatory. if (generatingProcessIdentifier.has_value()) { set_or_throw(out, "generatingProcessIdentifier", generatingProcessIdentifier.value()); } /// @todo [owner: mds,dgov][scope: concept][reason: legacy][prio: high] /// Remove this logic. /// /// Relying on pre-existing GRIB header values is not reproducible /// and must be eliminated. if (typeOfGeneratingProcess.has_value()) { set_or_throw(out, "typeOfGeneratingProcess", static_cast(typeOfGeneratingProcess.value())); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(generatingProcess, "Unable to set `generatingProcess` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(generatingProcess, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/param/0000775000175000017500000000000015246725757023562 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/param/paramEncoding.h0000664000175000017500000001332015246725757026501 0ustar alastairalastair/* * (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 paramEncoding.h /// @brief Implementation of the GRIB `param` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **param concept** within the mars2grib backend. /// /// The param concept is responsible for resolving and encoding the GRIB /// parameter identifier (`paramId`) in the *Product Definition Section*. /// The value is deduced from the MARS and parameter dictionaries using /// dedicated deduction logic. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `paramApplicable` /// - Runtime deduction of the parameter identifier /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/param/paramEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/paramId.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `param` concept. /// /// The default applicability enables this concept only when: /// - `Variant == ParamType::ParamId` /// - `Stage == StagePreset` or `Stage == StageRuntime` /// - `Section == SecProductDefinitionSection` /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Parameter concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The concept is intentionally enabled both at preset and runtime stages /// to allow late binding of the parameter identifier if required. /// template constexpr bool paramApplicable() { // Confitions to apply concept return ((Variant == ParamType::ParamId) && (Stage == StagePreset || Stage == StageRuntime) && (Section == SecProductDefinitionSection)); } /// /// @brief Execute the `param` concept operation. /// /// This function implements the runtime logic of the GRIB `param` concept. /// When applicable, it: /// /// 1. Deduces the GRIB parameter identifier (`paramId`) from the input /// MARS and parameter dictionaries. /// 2. Encodes the resolved `paramId` into the GRIB output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Parameter concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - the parameter identifier cannot be resolved /// - any encoding step fails /// /// @note /// - This concept performs no implicit defaulting. /// - The resolved `paramId` is expected to be fully validated by the /// underlying deduction logic. /// /// @see paramApplicable /// template void ParamOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::dump_or_ignore; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (paramApplicable()) { long paramId; try { MARS2GRIB_LOG_CONCEPT(param); // Deductions paramId = deductions::resolve_ParamId_or_throw(mars, par, opt); // Encoding set_or_throw(out, "paramId", paramId); } catch (...) { dump_or_ignore(out, "mars2grib_param_concept_failure.grib"); MARS2GRIB_CONCEPT_RETHROW( param, [&]() { return "Unable to set `param` concept with paramId=" + std::to_string(paramId); }()); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(param, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/param/paramMatcher.h0000664000175000017500000000434215246725757026342 0ustar alastairalastair/* * (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 paramMatcher.h /// @brief Entry-level matcher for the GRIB `param` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the parameter-identity concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/param/paramEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `param` concept variant. /// /// The parameter concept is always active and resolves to `ParamType::ParamId`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `ParamType::ParamId`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t paramMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { return static_cast(ParamType::ParamId); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException("Unable to match `param` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/param/paramConceptDescriptor.h0000664000175000017500000001166015246725757030412 0ustar alastairalastair/* * (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 paramConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `param` concept. /// /// This header defines `ParamConcept`, the **compile-time descriptor** /// that registers the GRIB `param` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/param/paramEncoding.h" #include "metkit/mars2grib/backend/concepts/param/paramEnum.h" #include "metkit/mars2grib/backend/concepts/param/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `param` concept. /// /// `ParamConcept` registers the GRIB `param` concept into the /// compile-time registry engine. /// struct ParamConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return paramName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return paramTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `param` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (paramApplicable()) { return &ParamOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `param` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return ¶mMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/param/paramEnum.h0000664000175000017500000001030115246725757025653 0ustar alastairalastair/* * (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 paramEnum.h /// @brief Definition of the `param` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `param` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`paramName`) /// - the enumeration of supported parameter variants (`ParamType`) /// - a compile-time typelist of all variants (`ParamList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `paramEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `param` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `param` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view paramName{"param"}; /// /// @brief Enumeration of all supported `param` concept variants. /// /// Each enumerator represents a specific parameter identification /// mechanism handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal and currently reflects /// parameter identification via paramId. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ParamType : std::size_t { ParamId = 0 }; /// /// @brief Compile-time list of all `param` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ParamList = ValueList; /// /// @brief Compile-time mapping from `ParamType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given parameter variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Parameter variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view paramTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view paramTypeName() { \ return NAME; \ } DEF(ParamType::ParamId, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/longrange/0000775000175000017500000000000015246725757024436 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeEncoding.h0000664000175000017500000001450315246725757030235 0ustar alastairalastair/* * (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 longrangeEncoding.h /// @brief Implementation of the GRIB `longrange` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **longrange concept** within the mars2grib backend. /// /// The longrange concept is responsible for encoding GRIB keys associated with /// *long-range forecast metadata* stored in the Local Use Section, specifically: /// /// - `methodNumber` /// - `systemNumber` /// /// These fields are used to identify the forecasting method and system /// used for long-range or seasonal products. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `longrangeApplicable` /// - Runtime validation of Local Definition Number /// - Explicit deduction of required values /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/methodNumber.h" #include "metkit/mars2grib/backend/deductions/systemNumber.h" // checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `longrange` concept. /// /// This predicate determines whether the longrange concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Longrange concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == LongrangeType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// template constexpr bool longrangeApplicable() { return ((Variant == LongrangeType::SeasonalForecast || Variant == LongrangeType::SeasonalForecastMonthlyMean) && (Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the `longrange` concept operation. /// /// This function implements the runtime logic of the GRIB `longrange` concept. /// When applicable, it: /// /// 1. Validates that the Local Use Section matches the expected definition. /// 2. Deduces the long-range forecasting method and system identifiers. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Longrange concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Definition Number does not match expectations, /// - required deductions fail, /// - any GRIB key cannot be set, /// - the concept is invoked when not applicable. /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on pre-existing GRIB header state. /// /// @see longrangeApplicable /// template void LongrangeOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (longrangeApplicable()) { try { MARS2GRIB_LOG_CONCEPT(longrange); // Preconditions / contracts validation::match_LocalDefinitionNumber_or_throw(opt, out, {15L, 16L}); // Deductions auto methodVal = deductions::resolve_MethodNumber_or_throw(mars, par, opt); auto systemVal = deductions::resolve_SystemNumber_or_throw(mars, par, opt); // Encoding set_or_throw(out, "methodNumber", methodVal); set_or_throw(out, "systemNumber", systemVal); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(longrange, "Unable to set `longrange` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(longrange, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeEnum.h0000664000175000017500000001103215246725757027405 0ustar alastairalastair/* * (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 longrangeEnum.h /// @brief Definition of the `longrange` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `longrange` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`longrangeName`) /// - the enumeration of supported long-range variants (`LongrangeType`) /// - a compile-time typelist of all variants (`LongrangeList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `longrangeEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `longrange` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `longrange` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view longrangeName{"longrange"}; /// /// @brief Enumeration of all supported `longrange` concept variants. /// /// Each enumerator represents a specific long-range forecasting /// classification or processing mode handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the long-range concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class LongrangeType : std::size_t { SeasonalForecast = 0, SeasonalForecastMonthlyMean }; /// /// @brief Compile-time list of all `longrange` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using LongrangeList = ValueList; /// /// @brief Compile-time mapping from `LongrangeType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given long-range variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Long-range variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view longrangeTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view longrangeTypeName() { \ return NAME; \ } DEF(LongrangeType::SeasonalForecast, "seasonal-forecast"); DEF(LongrangeType::SeasonalForecastMonthlyMean, "seasonal-forecast-monthly-mean"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeConceptDescriptor.h0000664000175000017500000001300615246725757032136 0ustar alastairalastair/* * (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 longrangeConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `longrange` concept. /// /// This header defines `LongrangeConcept`, the **compile-time descriptor** /// that registers the GRIB `longrange` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/longrange/longrangeEncoding.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeEnum.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `longrange` concept. /// /// `LongrangeConcept` registers the GRIB `longrange` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct LongrangeConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return longrangeName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return longrangeTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `longrange` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (longrangeApplicable()) { return &LongrangeOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &longrangeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeMatcher.h0000664000175000017500000000762415246725757030100 0ustar alastairalastair/* * (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 longrangeMatcher.h /// @brief Entry-level matcher for the GRIB `longrange` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether long-range forecast system metadata is active for a request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `longrange` concept variant. /// /// The concept is active when both `method` and /// `system` are present in the MARS request; When stream is equal to "" or "" /// then the concept is active with the variant LongrangeType::SeasonalForecastMonthlyMean, /// otherwise the concept is active with the variant LongrangeType::SeasonalForecast. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `LongrangeType::SeasonalForecast`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t longrangeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; const auto marsStream = get_or_throw(mars, "stream"); const auto marsClass = get_or_throw(mars, "class"); auto isSeasonal = [](const std::string& klass, const std::string& stream) { return (klass == "od" || klass == "rd" || klass == "c3") && (stream == "sfmd" || stream == "shmd"); }; if (has(mars, "method") && has(mars, "system")) { /// @todo review this logic if (has(mars, "fcmonth")) { if (isSeasonal(marsClass, marsStream)) { return static_cast(LongrangeType::SeasonalForecastMonthlyMean); } else { std::ostringstream os; os << "MARS request has `fcmonth` but is not seasonal: class=" << marsClass << ", stream=" << marsStream; throw eckit::SeriousBug(os.str(), Here()); } } else { return static_cast(LongrangeType::SeasonalForecast); } } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `longrange` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/destine/0000775000175000017500000000000015246725757024115 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/destine/destineConceptDescriptor.h0000664000175000017500000001453715246725757031306 0ustar alastairalastair/* * (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 destineConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `destine` concept. /// /// This header defines `DestineConcept`, the **compile-time descriptor** /// that registers the GRIB `destine` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/destine/destineEncoding.h" #include "metkit/mars2grib/backend/concepts/destine/destineEnum.h" #include "metkit/mars2grib/backend/concepts/destine/destineMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `destine` concept. /// /// `DestineConcept` registers the GRIB `destine` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct DestineConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return destineName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return destineTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `destine` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (destineApplicable()) { return &DestineOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `destine` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `destine` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &destineMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/destine/destineEncoding.h0000664000175000017500000002203015246725757027365 0ustar alastairalastair/* * (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 destineEncoding.h /// @brief Implementation of the GRIB `destine` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **DestinE concept** within the mars2grib backend. /// /// The concept is responsible for populating GRIB keys in the /// *Local Use Section* associated with **Destination Earth (DestinE) datasets**, /// including: /// /// - dataset identification (`dataset`) /// - experiment metadata (`activity`, `experiment`) /// - model and resolution identifiers /// - ensemble realization and generation indices /// /// The behavior of the concept depends on the selected DestinE variant: /// /// - `DestineType::ClimateDT` /// - `DestineType::ExtremesDT` /// /// Each variant enforces strict dataset consistency and encodes a /// different subset of metadata keys. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `destineApplicable` /// - Runtime validation of Local Use Section constraints /// - Delegation of semantic resolution to dedicated deduction functions /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/destine/destineEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/activity.h" #include "metkit/mars2grib/backend/deductions/experiment.h" #include "metkit/mars2grib/backend/deductions/generation.h" #include "metkit/mars2grib/backend/deductions/model.h" #include "metkit/mars2grib/backend/deductions/realization.h" #include "metkit/mars2grib/backend/deductions/resolution.h" // checks #include "metkit/mars2grib/backend/checks/checkDestinELocalSection.h" #include "metkit/mars2grib/backend/checks/matchDataset.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `destine` concept. /// /// This predicate determines whether the DestinE concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - DestinE variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant DestinE concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant` is either `ClimateDT` or `ExtremesDT` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// /// This reflects the current DestinE GRIB encoding requirements. /// template constexpr bool destineApplicable() { // Confitions to apply concept return ((Variant == DestineType::ClimateDT || Variant == DestineType::ExtremesDT) && (Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the DestinE concept operation. /// /// This function implements the runtime logic of the GRIB `destine` concept. /// When applicable, it: /// /// 1. Validates that the Local Use Section is compatible with DestinE encoding. /// 2. Enforces dataset consistency based on the selected DestinE variant. /// 3. Deduces DestinE-specific metadata from MARS and parameter dictionaries. /// 4. Encodes the corresponding GRIB keys in the output dictionary. /// /// The concept supports two variants: /// /// - **ExtremesDT** /// - Enforces dataset `"extremes-dt"` /// - Encodes only the dataset identifier /// /// - **ClimateDT** /// - Enforces dataset `"climate-dt"` /// - Encodes activity, experiment, resolution, model, /// generation, and realization metadata /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant DestinE concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Use Section does not match DestinE requirements /// - dataset consistency checks fail /// - any deduction fails /// - any GRIB key cannot be set /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on any pre-existing GRIB header state. /// /// @see destineApplicable /// template void DestineOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (destineApplicable()) { try { MARS2GRIB_LOG_CONCEPT(destine); // ============================================================= // Structural validation // ============================================================= validation::check_DestinELocalSection_or_throw(opt, out); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Variant == DestineType::ExtremesDT) { // Enforce dataset consistency validation::match_Dataset_or_throw(opt, out, "extremes-dt"); // Encode dataset identifier set_or_throw(out, "dataset", "extremes-dt"); } else if constexpr (Variant == DestineType::ClimateDT) { // Enforce dataset consistency validation::match_Dataset_or_throw(opt, out, "climate-dt"); // Encode dataset identifier set_or_throw(out, "dataset", "climate-dt"); // Deductions std::string activityVal = deductions::resolve_Activity_or_throw(mars, par, opt); std::string experimentVal = deductions::resolve_Experiment_or_throw(mars, par, opt); std::string resolutionVal = deductions::resolve_Resolution_or_throw(mars, par, opt); std::string modelVal = deductions::resolve_Model_or_throw(mars, par, opt); long generationVal = deductions::resolve_Generation_or_throw(mars, par, opt); long realizationVal = deductions::resolve_Realization_or_throw(mars, par, opt); // Encoding set_or_throw(out, "activity", activityVal); set_or_throw(out, "experiment", experimentVal); set_or_throw(out, "resolution", resolutionVal); set_or_throw(out, "model", modelVal); set_or_throw(out, "generation", generationVal); set_or_throw(out, "realization", realizationVal); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(destine, "Unable to set `destine` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(destine, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/destine/destineEnum.h0000664000175000017500000001052715246725757026553 0ustar alastairalastair/* * (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 destineEnum.h /// @brief Definition of the `destine` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `destine` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`destineName`) /// - the enumeration of supported DESTINe variants (`DestineType`) /// - a compile-time typelist of all variants (`DestineList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `destineEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `destine` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `destine` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view destineName{"destine"}; /// /// @brief Enumeration of all supported `destine` concept variants. /// /// Each enumerator represents a specific DESTINe data stream or /// processing context handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally concise and reflects the /// current DESTINe use cases supported by the encoder. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class DestineType : std::size_t { ClimateDT = 0, ExtremesDT }; /// /// @brief Compile-time list of all `destine` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using DestineList = ValueList; /// /// @brief Compile-time mapping from `DestineType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given DESTINe variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T DESTINe variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view destineTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view destineTypeName() { \ return NAME; \ } DEF(DestineType::ClimateDT, "climateDT"); DEF(DestineType::ExtremesDT, "extremesDT"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/destine/destineMatcher.h0000664000175000017500000000744015246725757027232 0ustar alastairalastair/* * (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 destineMatcher.h /// @brief Entry-level matcher for the GRIB `destine` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether DestinE digital-twin metadata is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/destine/destineEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `destine` concept variant. /// /// The concept is active for class `d1` requests without `anoffset`. The /// `dataset` keyword selects the concrete DestinE variant. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local DestinE variant index, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If required DestinE metadata is missing, unsupported `dataset` values are /// encountered, or lower-level matcher evaluation fails. Lower-level exceptions /// are preserved through `std::throw_with_nested`. /// template std::size_t destineMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { 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, "anoffset") && get_or_throw(mars, "class") == "d1") { if (has(mars, "dataset")) { if (get_or_throw(mars, "dataset") == "extremes-dt") { return static_cast(DestineType::ExtremesDT); } else if (get_or_throw(mars, "dataset") == "climate-dt") { return static_cast(DestineType::ClimateDT); } else { throw Mars2GribMatcherException{"Unknown value \"" + get_or_throw(mars, "dataset") + "\" for mars keyword \"dataset\"!", Here()}; } } else { throw Mars2GribMatcherException{ "Missing required mars keyword \"dataset\" for class \"d1\" without \"anoffset\"!", Here()}; } } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `destine` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/concepts.md0000664000175000017500000001705315246725757024630 0ustar alastairalastair# Concepts This document describes the **concept system** used in the *mars2grib* backend and how concepts integrate with the compile-time registry engine. The goal of the concept layer is to provide a **declarative, type-safe, and fully compile-time description** of all semantic dimensions that participate in GRIB header construction, matching, and encoding. --- ## 1. What is a Concept? A **concept** represents one *semantic axis* of a GRIB message. Examples include: * parameter identity (e.g. temperature, wind) * vertical level definition * time semantics * grid representation * data packing * originating centre Each concept answers the question: > *Which variant of this semantic dimension applies to the current request?* Concepts are **independent** of each other and are composed together by the encoder to form a complete GRIB message. --- ## 2. Concept Variants Each concept defines a **finite set of variants**, usually represented by a scoped enum. Examples: * `LevelType::{surface, pressure, model}` * `RepresentationType::{regular_ll, reduced_gaussian}` * `PackingType::{simple, jpeg, ccsds}` Variants are: * closed and known at compile time * ordered (the order is semantically significant) * mapped to GRIB template numbers and encoding rules The ordered list of variants for a concept defines its **local variant index space**. --- ## 3. New Concept vs New Variant When changing the concept system, first decide whether the requested behavior is a new concept or a new variant of an existing concept. Use a **new concept** when the feature is an independent semantic axis that must be composable with other concepts. Use a **new variant** when the feature is an alternative realization inside an existing semantic axis and does not require independent composability. This distinction is often a domain decision and is usually not reliably deducible from the code alone. If a request does not explicitly state whether a new concept or a new variant is required, ask before implementing. --- ## 4. Level Concept Guardrail The `level` concept is one of the most constrained concepts in mars2grib. Although GRIB ultimately represents vertical levels through six low-level fixed-surface keys: * `typeOfFirstFixedSurface` * `scaleFactorOfFirstFixedSurface` * `scaledValueOfFirstFixedSurface` * `typeOfSecondFixedSurface` * `scaleFactorOfSecondFixedSurface` * `scaledValueOfSecondFixedSurface` mars2grib must not set these keys directly. Many combinations of these keys are syntactically possible but semantically meaningless for ECMWF products. Instead, the encoder must rely on the official level abstraction: * `typeOfLevel` * `level`, when required * `topLevel` / `bottomLevel`, when required * PV-array data, when required Each supported `typeOfLevel` corresponds to a `LevelType` variant, apart from a few virtual type-of-level values kept in mars2grib because they cannot be added to ecCodes for backward-compatibility reasons. Each variant maps to a prescribed configuration of the low-level fixed-surface keys. Do not implement level fixes by injecting `typeOfFirstFixedSurface`, `scaleFactorOfFirstFixedSurface`, `scaledValueOfFirstFixedSurface`, `typeOfSecondFixedSurface`, `scaleFactorOfSecondFixedSurface`, or `scaledValueOfSecondFixedSurface`. If a new level behavior is required, add or adjust the appropriate `LevelType` variant, matcher mapping, or deduction so the level remains encoded through `typeOfLevel` and the official level interface. --- ## 5. Concept Descriptor Contract Each concept is implemented as a **descriptor type** that conforms to the `RegisterEntryDescriptor` interface. At minimum, a concept descriptor provides: * a variant enum type * a compile-time list of variants (`VariantList`) * a human-readable concept name * human-readable variant names * optional dispatch functions for: * matching * variant-level encoding * phase-level encoding The descriptor contains **no runtime state** and no virtual functions. --- ## 6. Capabilities Concepts may expose multiple independent *capabilities*. A capability is identified by a compile-time `std::size_t` index and allows the same concept to participate in different registries, such as: * semantic matching * encoding * validation * diagnostics Capabilities are selected at registry instantiation time. This avoids duplicating concept descriptors while still allowing multiple independent dispatch planes. --- ## 7. The Concept Universe (`AllConcepts`) All concepts known to the system are aggregated into a single ordered typelist: ``` AllConcepts = TypeList ``` This list is the **root input** to the compile-time registry engine. The order of concepts in this list: * defines the concept identifier (`conceptId`) * defines the layout of the global variant index space * determines the ordering of all generated dispatch tables Changing this order is a **breaking structural change**. --- ## 8. Concept Identifiers Each concept is assigned a **stable numeric identifier** based on its position in `AllConcepts`. ``` conceptId = index in AllConcepts ``` Concept identifiers are: * contiguous * zero-based * compile-time constants They are used as indices into: * matching callback tables * concept metadata tables * diagnostic utilities --- ## 9. Variant Index Spaces Variants are indexed in two ways: ### Local variant index The index of a variant *within its concept*. ``` localIndex(variant) ``` ### Global variant index A flattened index across **all concepts and all variants**. ``` globalIndex = conceptOffset + localIndex ``` The global variant index is the primary key used by: * encoding callback registries * phase dispatch tables * encoding plans --- ## 10. Matching Phase Matching determines **which concepts and variants are active** for a given input request. Each concept may optionally provide a matcher function: ``` Fm(MarsDict, OptDict) -> variantId | MISSING ``` The matching phase: 1. iterates over all concepts 2. invokes the corresponding matcher (if present) 3. records the active variant (or absence thereof) The result is an `ActiveConceptsData` structure. --- ## 11. Encoding Phases Encoding is divided into **logical stages**, such as: * allocation * preset * override * runtime For each active variant, the encoder may execute zero or more callbacks per (stage, section) pair. This produces a three-dimensional dispatch space: ``` [variant][stage][section] -> Fn | nullptr ``` All dispatch tables are generated **entirely at compile time**. --- ## 12. Design Principles The concept system is designed around the following principles: * **No runtime polymorphism** * **No dynamic allocation** * **Deterministic structure** * **Compile-time validation** * **Separation of concerns** Concepts describe *what exists*. Registries describe *how things are wired*. Execution code performs *only iteration and invocation*. --- ## 13. Adding a New Concept To add a new concept: 1. Implement a new descriptor conforming to `RegisterEntryDescriptor` 2. Define its variant enum and `VariantList` 3. Provide matching and/or encoding callbacks as needed 4. Include the descriptor header in `AllConcepts.h` 5. Append the concept to the `AllConcepts` typelist No registry code needs to be modified. --- ## 14. Summary Concepts are the **semantic backbone** of the mars2grib backend. They provide: * a declarative description of the domain * compile-time structure and guarantees * a clean separation between semantics and execution All higher-level mechanisms — matching, layout resolution, encoding — are built mechanically on top of the concept system. metkit-1.20.2/src/metkit/mars2grib/backend/concepts/composition/0000775000175000017500000000000015246725757025025 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h0000664000175000017500000001602415246725757031213 0ustar alastairalastair/* * (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 compositionEncoding.h /// @brief Implementation of the GRIB `composition` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **composition concept** within the mars2grib backend. /// /// The concept is responsible for populating GRIB keys related to the /// *composition* of the encoded product, specifically the /// `constituentType` key in the Product Definition Section. /// /// The composition concept is variant-driven. Different variants correspond /// to different composition semantics (e.g. chemical constituents), and /// only selected variants perform encoding actions. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `compositionApplicable` /// - Variant-specific runtime deduction /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/composition/compositionEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/chemId.h" #include "metkit/mars2grib/backend/deductions/firstWavelength.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `composition` concept. /// /// This predicate determines whether the `composition` concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Composition concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Stage == StagePreset` /// - `Section == SecProductDefinitionSection` /// /// Variant-specific behavior is handled inside the concept operation. /// template constexpr bool compositionApplicable() { return (Stage == StagePreset && Section == SecProductDefinitionSection); } /// /// @brief Execute the `composition` concept operation. /// /// This function implements the runtime logic of the GRIB `composition` concept. /// When applicable, it: /// /// - Performs variant-specific deductions from the MARS and parameter dictionaries /// - Encodes the corresponding GRIB keys into the output dictionary /// /// Currently, only the `CompositionType::Chem` variant performs encoding, /// setting the GRIB key `constituentType`. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Composition concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - a deduction fails /// - a GRIB key cannot be set /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - Variants not explicitly handled result in a no-op when applicable. /// /// @see compositionApplicable /// 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; if constexpr (compositionApplicable()) { try { MARS2GRIB_LOG_CONCEPT(composition); // NOTE : Can also simply check if chemId is present, and if wavelength is present and set it. if constexpr (Variant == CompositionType::AerosolOptical) { // Deductions 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", firstWavelength); } else if constexpr (Variant == CompositionType::Chem || Variant == CompositionType::Aerosol || Variant == CompositionType::ChemicalSource) { // Deductions const auto chemId = deductions::resolve_ChemId_or_throw(mars, par, opt); // Encoding set_or_throw(out, "enableChemSplit", true); set_or_throw(out, "chemId", chemId); } else { MARS2GRIB_CONCEPT_THROW(composition, "Concept variant is not implemented!"); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(composition, "Unable to set `composition` concept..."); } return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(composition, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/composition/compositionEnum.h0000664000175000017500000001163715246725757030376 0ustar alastairalastair/* * (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 compositionEnum.h /// @brief Definition of the `composition` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `composition` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`compositionName`) /// - the exhaustive enumeration of supported composition variants (`CompositionType`) /// - a compile-time typelist of all variants (`CompositionList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `compositionEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `composition` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `composition` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view compositionName{"composition"}; /// /// @brief Enumeration of all supported `composition` concept variants. /// /// Each enumerator represents a distinct physical or semantic interpretation /// of atmospheric composition handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both state variables (e.g. chemical or aerosol /// concentrations) and source-related representations. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class CompositionType : std::size_t { Chem = 0, Aerosol, AerosolOptical, ChemicalSource, AerosolOpticalSource, Default }; /// /// @brief Compile-time list of all `composition` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using CompositionList = ValueList; /// /// @brief Compile-time mapping from `CompositionType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given composition variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Composition variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view compositionTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view compositionTypeName() { \ return NAME; \ } DEF(CompositionType::Chem, "chemical"); DEF(CompositionType::Aerosol, "aerosol"); DEF(CompositionType::AerosolOptical, "aerosolOptical"); DEF(CompositionType::ChemicalSource, "chemicalSource"); DEF(CompositionType::AerosolOpticalSource, "aerosolOpticalSource"); DEF(CompositionType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/composition/compositionConceptDescriptor.h0000664000175000017500000001471315246725757033122 0ustar alastairalastair/* * (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 compositionConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `composition` concept. /// /// This header defines `CompositionConcept`, the **compile-time descriptor** /// that registers the GRIB `composition` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/composition/compositionEncoding.h" #include "metkit/mars2grib/backend/concepts/composition/compositionEnum.h" #include "metkit/mars2grib/backend/concepts/composition/compositionMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `composition` concept. /// /// `CompositionConcept` registers the GRIB `composition` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct CompositionConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return compositionName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return compositionTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `composition` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (compositionApplicable()) { return &CompositionOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `composition` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `composition` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &compositionMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h0000664000175000017500000001577615246725757031065 0ustar alastairalastair/* * (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 compositionMatcher.h /// @brief Entry-level matcher for the GRIB `composition` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether chemical or aerosol composition metadata is active for a /// MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #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_ { /// /// @brief Match the `composition` concept variant. /// /// The concept is active as `CompositionType::Chem` when `chem` is present, /// and as `CompositionType::Aerosol` when `wavelength` is present. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local composition variant index, or /// `compile_time_registry_engine::MISSING` when no composition metadata is active. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t compositionMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { 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; 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; } const auto chem = get_or_throw(mars, "chem"); const auto hasWavelength = has(mars, "wavelength"); if (hasWavelength) { if (matchAny(param, 457000)) { if (matchAny(chem, range(900, 914), 918, 922, 923, range(933, 936))) { return static_cast(CompositionType::AerosolOptical); } } else if (matchAny(param, 458000, 459000, 460000, 461000, 462000, 472000)) { if (matchAny(chem, 922)) { return static_cast(CompositionType::AerosolOptical); } } } else { if (matchAny(param, 401000)) { if (matchAny(chem, range(900, 916))) { return static_cast(CompositionType::Aerosol); } if (matchAny(chem, 2, 3, range(5, 24), range(26, 30), range(32, 50), 52, 53, range(55, 58), range(63, 80), 82, 83, 85, 86, range(99, 101), 107, 112, 159, 161, 169, range(173, 178), range(186, 204), 222, range(224, 231), 233, 311, 359, 404, 917)) { return static_cast(CompositionType::Chem); } } else if (matchAny(param, 402000)) { if (matchAny(chem, range(900, 917), 924)) { return static_cast(CompositionType::Aerosol); } if (matchAny(chem, range(2, 24), range(26, 30), range(32, 50), 52, 53, range(55, 59), range(63, 80), 82, 83, 85, 86, range(99, 101), 107, 112, 118, 159, 161, 169, range(173, 178), range(186, 204), 222, range(224, 230), 233, 236, 311)) { return static_cast(CompositionType::Chem); } } else if (matchAny(param, 406000, 407000, 410000, 411000, 451000)) { if (matchAny(chem, range(901, 916))) { return static_cast(CompositionType::Aerosol); } } else if (matchAny(param, 453000)) { if (matchAny(chem, range(901, 916), 922)) { return static_cast(CompositionType::Aerosol); } } else if (matchAny(param, 400000)) { if (matchAny(chem, range(929, 931))) { return static_cast(CompositionType::Aerosol); } } else if (matchAny(param, 444000)) { if (matchAny(chem, 6, 8, 13, 15, 17, 19, 26, 27, 33)) { return static_cast(CompositionType::Chem); } } else if (matchAny(param, 445000)) { if (matchAny(chem, 6, 8, 13, 15, 17, 19, 27, 33, 236)) { return static_cast(CompositionType::Chem); } } else if (matchAny(param, 479000)) { if (matchAny(chem, 404)) { return static_cast(CompositionType::Chem); } } else if (matchAny(param, 469000)) { if (matchAny(chem, 2, 5, 9, 10, 12, 16, 18, 19, 42, range(45, 48), 52, 99, 100, 129, 224, 226, 233, 311, 933, 934)) { return static_cast(CompositionType::ChemicalSource); } } } throw Mars2GribMatcherException( "compositionMatcher: matching logic is not implemented for param=" + std::to_string(param) + ", chem=" + std::to_string(chem) + ", hasWavelength=" + (hasWavelength ? "true" : "false"), Here()); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `composition` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/satellite/0000775000175000017500000000000015246725757024450 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteEncoding.h0000664000175000017500000002373315246725757030266 0ustar alastairalastair/* * (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 satelliteEncoding.h /// @brief Implementation of the GRIB `satellite` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **satellite concept**, which is responsible for encoding satellite-related /// metadata into GRIB messages. /// /// The concept populates keys related to: /// - satellite identification /// - instrument characteristics /// - spectral channel information /// /// The encoding is distributed across multiple GRIB sections and encoding /// stages, reflecting the structure of GRIB Product Definition Templates /// for satellite products. /// /// --- /// /// ## Encoding stages and sections /// /// The `satellite` concept operates in the following contexts: /// /// ### Local Use Section (Section 2) /// - **StagePreset** /// - Encodes the `channel` key /// - Validates the Local Definition Number /// /// ### Product Definition Section (Section 4) /// - **StageAllocate** /// - Allocates space for spectral band information /// - Sets `numberOfContributingSpectralBands` /// /// - **StagePreset** /// - Encodes satellite and instrument identifiers /// - Encodes spectral wave number information /// /// --- /// /// ## Supported Product Definition Templates /// /// The concept currently supports GRIB Product Definition Templates: /// /// - Template 32 /// - Template 33 /// /// Any other template results in a validation error. /// /// --- /// /// ## Deductions /// /// All satellite-related values are obtained via dedicated deduction /// functions, including: /// /// - channel /// - satellite series and number /// - instrument type /// - spectral wave number scaling /// /// The concept does not perform implicit defaults or fallbacks. /// Missing or inconsistent information results in a deduction error. /// /// --- /// /// ## Applicability model /// /// The satellite concept is applicable when **any** of the following holds: /// /// - `Stage == StagePreset` and `Section == SecLocalUseSection` /// - `Stage == StageAllocate` and `Section == SecProductDefinitionSection` /// - `Stage == StagePreset` and `Section == SecProductDefinitionSection` /// /// Variant-specific behavior (via `SatelliteType`) is currently not /// differentiated and may be refined in future iterations. /// /// --- /// /// ## Error handling /// /// - Structural mismatches with GRIB templates are detected via validation helpers. /// - All deduction and encoding errors are wrapped in a /// `Mars2GribConceptException` with full context. /// /// --- /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid conflicts with the C++20 `concept` language feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/satellite/satelliteEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" #include "metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h" // Deductions #include "metkit/mars2grib/backend/deductions/channel.h" #include "metkit/mars2grib/backend/deductions/instrumentType.h" #include "metkit/mars2grib/backend/deductions/numberOfFrequencies.h" #include "metkit/mars2grib/backend/deductions/satelliteNumber.h" #include "metkit/mars2grib/backend/deductions/satelliteSeries.h" #include "metkit/mars2grib/backend/deductions/scaleFactorOfCentralWaveNumber.h" #include "metkit/mars2grib/backend/deductions/scaledValueOfCentralWaveNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `satellite` concept. /// /// This predicate determines whether the `satellite` concept is instantiated /// for a given encoding stage and GRIB section. /// /// The concept is applicable in multiple stages and sections, reflecting /// the distribution of satellite metadata across the GRIB message: /// /// - Local Use Section during preset /// - Product Definition Section during allocation and preset /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Satellite concept variant /// /// @return `true` if the concept is applicable, `false` otherwise. /// template constexpr bool satelliteApplicable() { bool condition1 = (Stage == StagePreset && Section == SecLocalUseSection); bool condition2 = (Stage == StageAllocate && Section == SecProductDefinitionSection); bool condition3 = (Stage == StagePreset && Section == SecProductDefinitionSection); return (condition1 || condition2 || condition3); } /// /// @brief Execute the `satellite` concept operation. /// /// This function implements the runtime logic of the GRIB `satellite` concept. /// Depending on the encoding stage and GRIB section, it: /// /// - validates structural constraints /// - deduces satellite and instrument metadata /// - encodes satellite-related GRIB keys /// /// The operation is stage- and section-aware and performs different actions /// in each context. /// /// @tparam Stage Encoding stage /// @tparam Section GRIB section index /// @tparam Variant Satellite concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - GRIB structural validation fails /// - any required deduction fails /// - encoding of satellite metadata fails /// /// @note /// - The concept assumes that satellite products contribute exactly one /// spectral band. /// - All values are explicitly set; no reliance on pre-existing GRIB state /// is permitted. /// /// @see satelliteApplicable /// template void SatelliteOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (satelliteApplicable()) { try { MARS2GRIB_LOG_CONCEPT(satellite); if constexpr (Section == SecLocalUseSection && Stage == StagePreset) { // Check/Validation validation::match_LocalDefinitionNumber_or_throw(opt, out, {24, 37}); // Deductions long channelNumber = deductions::resolve_Channel_or_throw(mars, par, opt); // long numberOfFrequencies = deductions::resolve_NumberOfFrequencies_or_throw(mars, par, opt); // Encoding set_or_throw(out, "channelNumber", channelNumber); // set_or_throw(out, "numberOfFrequencies", numberOfFrequencies); } if constexpr (Section == SecProductDefinitionSection && Stage == StageAllocate) { // Check/Validation validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {32, 33}); // Encoding set_or_throw(out, "numberOfContributingSpectralBands", 1L); } if constexpr (Section == SecProductDefinitionSection && Stage == StagePreset) { // Check/Validation validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {32, 33}); // Deductions long satelliteNumber = deductions::resolve_satelliteNumber_or_throw(mars, par, opt); long instrumentType = deductions::resolve_InstrumentType_or_throw(mars, par, opt); long satelliteSeries = deductions::resolve_SatelliteSeries_or_throw(mars, par, opt); long scaleFactorOfCentralWaveNumber = deductions::resolve_ScaleFactorOfCentralWaveNumber_or_throw(mars, par, opt); long scaledValueOfCentralWaveNumber = deductions::resolve_ScaledValueOfCentralWaveNumber_or_throw(mars, par, opt); // Encoding set_or_throw(out, "satelliteSeries", satelliteSeries); set_or_throw(out, "satelliteNumber", satelliteNumber); set_or_throw(out, "instrumentType", instrumentType); set_or_throw(out, "scaleFactorOfCentralWaveNumber", scaleFactorOfCentralWaveNumber); set_or_throw(out, "scaledValueOfCentralWaveNumber", scaledValueOfCentralWaveNumber); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(satellite, "Unable to set `satellite` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(satellite, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteConceptDescriptor.h0000664000175000017500000001203015246725757032156 0ustar alastairalastair/* * (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 satelliteConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `satellite` concept. /// /// This header defines `SatelliteConcept`, the **compile-time descriptor** /// that registers the GRIB `satellite` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/satellite/satelliteEncoding.h" #include "metkit/mars2grib/backend/concepts/satellite/satelliteEnum.h" #include "metkit/mars2grib/backend/concepts/satellite/satelliteMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `satellite` concept. /// /// `SatelliteConcept` registers the GRIB `satellite` concept into the /// compile-time registry engine. /// struct SatelliteConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return satelliteName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return satelliteTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `satellite` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (satelliteApplicable()) { return &SatelliteOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `satellite` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &satelliteMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteEnum.h0000664000175000017500000001050015246725757027430 0ustar alastairalastair/* * (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 satelliteEnum.h /// @brief Definition of the `satellite` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `satellite` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`satelliteName`) /// - the enumeration of supported satellite variants (`SatelliteType`) /// - a compile-time typelist of all variants (`SatelliteList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `satelliteEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `satellite` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `satellite` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view satelliteName{"satellite"}; /// /// @brief Enumeration of all supported `satellite` concept variants. /// /// Each enumerator represents a specific satellite-related semantic /// classification handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as satellite handling evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class SatelliteType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `satellite` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using SatelliteList = ValueList; /// /// @brief Compile-time mapping from `SatelliteType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given satellite variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Satellite variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view satelliteTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view satelliteTypeName() { \ return NAME; \ } DEF(SatelliteType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteMatcher.h0000664000175000017500000000553615246725757030124 0ustar alastairalastair/* * (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 satelliteMatcher.h /// @brief Entry-level matcher for the GRIB `satellite` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether satellite product metadata is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/satellite/satelliteEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `satellite` concept variant. /// /// The concept is active as `SatelliteType::Default` when `channel`, `ident`, /// and `instrument` are present in the MARS request. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `SatelliteType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t satelliteMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; // Default satellite: requires full satellite identification keys if (has(mars, "channel") && has(mars, "ident") && has(mars, "instrument")) { return static_cast(SatelliteType::Default); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `satellite` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/packing/0000775000175000017500000000000015246725757024076 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/packing/packingEncoding.h0000664000175000017500000001702715246725757027341 0ustar alastairalastair/* * (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 packingEncoding.h /// @brief Implementation of the GRIB `packing` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **packing concept** within the mars2grib backend. /// /// The packing concept is responsible for configuring the GRIB /// *Data Representation Section* according to the selected packing algorithm. /// It validates the underlying data representation template and sets the /// required packing-specific GRIB keys. /// /// Supported packing variants include: /// - Simple packing /// - CCSDS packing /// - Spectral complex packing /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `packingApplicable` /// - Variant-specific runtime validation /// - Deduction of packing parameters from MARS and parameter dictionaries /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/packing/packingEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Checks #include "metkit/mars2grib/backend/checks/matchDataRepresentationTemplateNumber.h" // Deductions #include "metkit/mars2grib/backend/deductions/bitsPerValue.h" #include "metkit/mars2grib/backend/deductions/subSetTrunc.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `packing` concept. /// /// The default applicability enables this concept only when: /// - `Stage == StagePreset` /// - `Section == SecDataRepresentationSection` /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Packing concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// template constexpr bool packingApplicable() { if constexpr (Stage == StagePreset && Section == SecDataRepresentationSection) { return true; } return false; } /// /// @brief Execute the `packing` concept operation. /// /// When applicable, this concept: /// 1. Validates the GRIB data representation template number. /// 2. Deduces packing-specific parameters from the input dictionaries. /// 3. Encodes the corresponding GRIB keys into the output dictionary. /// /// The exact behavior depends on the selected packing variant: /// /// - **Simple packing** /// - Template: 0 /// - Keys set: `bitsPerValue` /// /// - **CCSDS packing** /// - Template: 42 /// - Keys set: `bitsPerValue` /// /// - **Spectral complex packing** /// - Template: 51 /// - Keys set: /// - `bitsPerValue` /// - `laplacianOperator` /// - `subSetJ`, `subSetK`, `subSetM` /// - `TS` /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Packing concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - the GRIB data representation template does not match the variant /// - any deduction or encoding step fails /// /// @note /// This concept does not rely on any pre-existing GRIB header state. /// /// @see packingApplicable /// template void PackingOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (packingApplicable()) { try { MARS2GRIB_LOG_CONCEPT(packing); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Variant == PackingType::Simple) { // Check sample structure validation::match_DataRepresentationTemplateNumber_or_throw(opt, out, {0}); // Set bits per value long bitsPerValue = deductions::resolve_BitsPerValueGridded_or_throw(mars, par, opt); set_or_throw(out, "bitsPerValue", bitsPerValue); } if constexpr (Variant == PackingType::Ccsds) { // Check sample structure validation::match_DataRepresentationTemplateNumber_or_throw(opt, out, {42}); // Set bits per value long bitsPerValue = deductions::resolve_BitsPerValueGridded_or_throw(mars, par, opt); set_or_throw(out, "bitsPerValue", bitsPerValue); } if constexpr (Variant == PackingType::SpectralComplex) { // Check sample structure validation::match_DataRepresentationTemplateNumber_or_throw(opt, out, {51}); // Set bits per value long bitsPerValue = deductions::resolve_BitsPerValueSpectral_or_throw(mars, par, opt); set_or_throw(out, "bitsPerValue", bitsPerValue); long subSetTruncation = deductions::resolve_SubSetTruncation_or_throw(mars, par, opt); set_or_throw(out, "subSetJ", subSetTruncation); set_or_throw(out, "subSetK", subSetTruncation); set_or_throw(out, "subSetM", subSetTruncation); set_or_throw(out, "TS", (subSetTruncation + 1) * (subSetTruncation + 2)); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(packing, "Unable to set `packing` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(packing, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/packing/packingConceptDescriptor.h0000664000175000017500000001174415246725757031245 0ustar alastairalastair/* * (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 packingConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `packing` concept. /// /// This header defines `PackingConcept`, the **compile-time descriptor** /// that registers the GRIB `packing` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/packing/packingEncoding.h" #include "metkit/mars2grib/backend/concepts/packing/packingEnum.h" #include "metkit/mars2grib/backend/concepts/packing/packingMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `packing` concept. /// /// `PackingConcept` registers the GRIB `packing` concept into the /// compile-time registry engine. /// struct PackingConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return packingName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return packingTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `packing` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (packingApplicable()) { return &PackingOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `packing` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &packingMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/packing/packingEnum.h0000664000175000017500000001075715246725757026522 0ustar alastairalastair/* * (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 packingEnum.h /// @brief Definition of the `packing` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `packing` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`packingName`) /// - the enumeration of supported packing variants (`PackingType`) /// - a compile-time typelist of all variants (`PackingList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `packingEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `packing` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `packing` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view packingName{"packing"}; /// /// @brief Enumeration of all supported `packing` concept variants. /// /// Each enumerator represents a specific GRIB packing or compression /// strategy used to encode field values. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both grid-point and spectral packing /// representations, as well as a default fallback. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class PackingType : std::size_t { Simple = 0, Ccsds, SpectralComplex, Default }; /// /// @brief Compile-time list of all `packing` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using PackingList = ValueList; /// /// @brief Compile-time mapping from `PackingType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given packing variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Packing variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view packingTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view packingTypeName() { \ return NAME; \ } DEF(PackingType::Simple, "simple"); DEF(PackingType::Ccsds, "ccsds"); DEF(PackingType::SpectralComplex, "spectral_complex"); DEF(PackingType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/packing/packingMatcher.h0000664000175000017500000000607215246725757027174 0ustar alastairalastair/* * (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 packingMatcher.h /// @brief Entry-level matcher for the GRIB `packing` concept. /// /// This header defines the runtime matcher used by the concept registry to /// select the GRIB data packing variant requested by MARS metadata. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/packing/packingEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `packing` concept variant. /// /// The matcher maps the MARS `packing` keyword onto the corresponding local /// packing concept variant. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local packing variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If the `packing` keyword is missing, has an unsupported value, or lower-level /// matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t packingMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribMatcherException; const auto& packing = get_or_throw(mars, "packing"); if (packing == "simple") { return static_cast(PackingType::Simple); } if (packing == "ccsds") { return static_cast(PackingType::Ccsds); } if (packing == "complex") { return static_cast(PackingType::SpectralComplex); } throw Mars2GribMatcherException{"Unknown value \"" + packing + "\" for mars keyword \"packing\"!", Here()}; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `packing` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/level/0000775000175000017500000000000015246725757023571 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/level/levelConceptDescriptor.h0000664000175000017500000001445115246725757030431 0ustar alastairalastair/* * (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 levelConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `level` concept. /// /// This header defines `LevelConcept`, the **compile-time descriptor** /// that registers the GRIB `level` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/level/levelEncoding.h" #include "metkit/mars2grib/backend/concepts/level/levelEnum.h" #include "metkit/mars2grib/backend/concepts/level/levelMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `level` concept. /// /// `LevelConcept` registers the GRIB `level` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct LevelConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return levelName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return levelTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `level` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (levelApplicable()) { return &LevelOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `level` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `level` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &levelMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/level/levelEnum.h0000664000175000017500000002435415246725757025706 0ustar alastairalastair/* * (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 levelEnum.h /// @brief Definition of the `level` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `level` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`levelName`) /// - the exhaustive enumeration of supported level variants (`LevelType`) /// - a compile-time typelist of all variants (`LevelList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `levelEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `level` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `level` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view levelName{"level"}; /// /// @brief Enumeration of all supported `level` concept variants. /// /// Each enumerator represents a distinct GRIB vertical level or layer /// interpretation as defined by the GRIB2 specification and ECMWF /// conventions. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally exhaustive and includes both: /// - concrete GRIB levels (e.g. isobaric, hybrid, heightAboveGround) /// - abstract or logical levels used internally by the encoder /// /// @note /// Model-level coordinates are split into two variants: /// - `ModelSingleLevel` for fields published as a single 2D layer on the /// model-level system (no vertical column, no PV array). /// - `ModelMultipleLevel` for full vertical columns of model-level data, /// which require allocation and population of the PV array describing /// the vertical hybrid coordinate transformation. /// Both variants share the GRIB `typeOfLevel` string `"hybrid"`; they /// differ only in encoder behaviour (PV allocation). /// /// @note /// Three abstract variants exist: /// - `AbstractSingleLevel` and `AbstractMultipleLevel`: opaque level /// identifiers without an associated numeric `level` value. /// - `AbstractLevel`: opaque level identifier that carries a numeric /// `level` value (encoded via the `level` GRIB key). /// /// @warning /// Do not reorder existing enumerators in future changes, as their /// numeric values are used in compile-time tables and registries. /// enum class LevelType : std::size_t { Surface = 0, EntireAtmosphere, EntireLake, CloudBase, Tropopause, NominalTop, MostUnstableParcel, MixedLayerParcel, Isothermal, IsobaricInPa, IsobaricInHpa, LowCloudLayer, MediumCloudLayer, HighCloudLayer, MeanSea, HeightAboveSea, HeightAboveGround, ModelSingleLevel, ///< 2D field on model-level system; no PV array. ModelMultipleLevel, ///< Full vertical column of model-level data; requires PV array. Theta, PotentialVorticity, SnowLayer, SoilLayer, SeaIceLayer, OceanSurface, DepthBelowSeaLayer, OceanSurfaceToBottom, LakeBottom, MixingLayer, OceanModel, OceanModelLayer, MixedLayerDepthByDensity, MixedLayerDepthByTemperature, SnowLayerOverIceOnWater, IceTopOnWater, IceLayerOnWater, EntireMeltPond, WaterSurfaceToIsothermalOceanLayer, AbstractSingleLevel, AbstractLevel, ///< Opaque level identifier carrying a numeric `level` value. AbstractMultipleLevel, HeightAboveSeaAt10M, HeightAboveSeaAt2M, HeightAboveGroundAt10M, HeightAboveGroundAt2M, FlightLevel, Default }; /// /// @brief Compile-time list of all `level` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using LevelList = ValueList; /// /// @brief Compile-time mapping from `LevelType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given level variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Level variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view levelTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view levelTypeName() { \ return NAME; \ } DEF(LevelType::Surface, "surface"); DEF(LevelType::EntireAtmosphere, "entireAtmosphere"); DEF(LevelType::EntireLake, "entireLake"); DEF(LevelType::CloudBase, "cloudBase"); DEF(LevelType::Tropopause, "tropopause"); DEF(LevelType::NominalTop, "nominalTop"); DEF(LevelType::MostUnstableParcel, "mostUnstableParcel"); DEF(LevelType::MixedLayerParcel, "mixedLayerParcel"); DEF(LevelType::Isothermal, "isothermal"); DEF(LevelType::IsobaricInPa, "isobaricInPa"); DEF(LevelType::IsobaricInHpa, "isobaricInhPa"); DEF(LevelType::LowCloudLayer, "lowCloudLayer"); DEF(LevelType::MediumCloudLayer, "mediumCloudLayer"); DEF(LevelType::HighCloudLayer, "highCloudLayer"); DEF(LevelType::MeanSea, "meanSea"); DEF(LevelType::HeightAboveSea, "heightAboveSea"); DEF(LevelType::HeightAboveGround, "heightAboveGround"); // ModelSingleLevel and ModelMultipleLevel both encode as GRIB // `typeOfLevel="hybrid"`; they differ only in encoder behaviour // (PV array allocation, see needPv in levelEncoding.h). DEF(LevelType::ModelSingleLevel, "hybrid"); DEF(LevelType::ModelMultipleLevel, "hybrid"); DEF(LevelType::Theta, "theta"); DEF(LevelType::PotentialVorticity, "potentialVorticity"); DEF(LevelType::SnowLayer, "snowLayer"); DEF(LevelType::SoilLayer, "soilLayer"); DEF(LevelType::SeaIceLayer, "seaIceLayer"); DEF(LevelType::OceanSurface, "oceanSurface"); DEF(LevelType::DepthBelowSeaLayer, "depthBelowSeaLayer"); DEF(LevelType::OceanSurfaceToBottom, "oceanSurfaceToBottom"); DEF(LevelType::LakeBottom, "lakeBottom"); DEF(LevelType::MixingLayer, "mixingLayer"); DEF(LevelType::OceanModel, "oceanModel"); DEF(LevelType::OceanModelLayer, "oceanModelLayer"); DEF(LevelType::MixedLayerDepthByDensity, "mixedLayerDepthByDensity"); DEF(LevelType::MixedLayerDepthByTemperature, "mixedLayerDepthByTemperature"); DEF(LevelType::SnowLayerOverIceOnWater, "snowLayerOverIceOnWater"); DEF(LevelType::IceTopOnWater, "iceTopOnWater"); DEF(LevelType::IceLayerOnWater, "iceLayerOnWater"); DEF(LevelType::EntireMeltPond, "entireMeltPond"); DEF(LevelType::WaterSurfaceToIsothermalOceanLayer, "waterSurfaceToIsothermalOceanLayer"); DEF(LevelType::AbstractSingleLevel, "abstractSingleLevel"); DEF(LevelType::AbstractLevel, "abstractLevel"); DEF(LevelType::AbstractMultipleLevel, "abstractMultipleLevel"); DEF(LevelType::HeightAboveSeaAt10M, "heightAboveSeaAt10m"); DEF(LevelType::HeightAboveSeaAt2M, "heightAboveSeaAt2m"); DEF(LevelType::HeightAboveGroundAt10M, "heightAboveGroundAt10m"); DEF(LevelType::HeightAboveGroundAt2M, "heightAboveGroundAt2m"); DEF(LevelType::FlightLevel, "flightLevel"); DEF(LevelType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h0000664000175000017500000007460015246725757026364 0ustar alastairalastair/* * (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 levelMatcher.h /// @brief Entry-level matcher for the GRIB `level` concept. /// /// This header defines the runtime matcher used by the concept registry to /// map MARS `levtype`, `param`, and, where required, `levelist` metadata onto /// GRIB level concept variants. /// /// The implementation is split into levtype-specific helper functions in the /// internal `impl` namespace and a public entry-level matcher. Each helper is /// responsible for one MARS level type and returns a local `LevelType` variant /// index. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// Nested error handling is intentionally applied both at the entry point and /// inside the levtype-specific helpers so diagnostics retain the full matcher /// call chain and the levtype-specific mapping context. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "metkit/mars2grib/backend/concepts/level/levelEnum.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_ { namespace impl { /// /// @brief Match surface-level MARS parameters. /// /// Maps `levtype=sfc` parameters onto their GRIB level concept variant. /// Wave-period parameters deliberately return `MISSING` because they are handled /// by the wave concept rather than by level encoding. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index, or /// `compile_time_registry_engine::MISSING` for surface products owned by another /// concept. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no surface-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchSFC(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 228023)) { return static_cast(LevelType::CloudBase); } if (matchAny(param, 262118)) { return static_cast(LevelType::DepthBelowSeaLayer); } if (matchAny(param, 59, 78, 79, 136, 137, 164, 194, 206, range(162059, 162063), 162071, 162072, 162093, 228001, 228044, 228050, 228052, range(228088, 228090), 228164, 235087, 235088, 235136, 235137, 235287, 235288, 235290, 235326, 235383, 237087, 237088, 237137, 237287, 237288, 237290, 237326, 238087, 238088, 238137, 238287, 238288, 238290, 238326, 239087, 239088, 239137, 239287, 239288, 239290, 239326, 260132)) { return static_cast(LevelType::EntireAtmosphere); } // efi if (matchAny(param, 132045)) { return static_cast(LevelType::EntireAtmosphere); } if (matchAny(param, 228007, 228011)) { return static_cast(LevelType::EntireLake); } if (matchAny(param, 49, 123, 165, 166, 207, 228005, 228028, 228029, 228131, 228132, 235165, 235166, 237165, 237166, 237207, 237318, 238165, 238166, 238207, 239165, 239166, 239207, 260260)) { return static_cast(LevelType::HeightAboveGroundAt10M); } // Strike-probability if (matchAny(param, 131068, 131069, 131070, 131071, 131072, 131100)) { return static_cast(LevelType::HeightAboveGroundAt10M); } // efi if (matchAny(param, 132049, 132165)) { return static_cast(LevelType::HeightAboveGroundAt10M); } if (matchAny(param, 121, 122, 167, 168, 201, 202, 174096, 228004, 228037, 235168, 237167, 237168, 238167, 238168, 239167, 239168, 260242)) { return static_cast(LevelType::HeightAboveGroundAt2M); } // Strike-probability if (matchAny(param, 131073)) { return static_cast(LevelType::HeightAboveGroundAt2M); } // efi if (matchAny(param, 132167, 132201, 132202)) { return static_cast(LevelType::HeightAboveGroundAt2M); } if (matchAny(param, 140233, 140245, 140249, 141233, 141245, 143233, 143245, 144233, 144245, 145233, 145245)) { return static_cast(LevelType::HeightAboveSeaAt10M); } if (matchAny(param, 235320, 237320, 238320, 239320)) { return static_cast(LevelType::HeightAboveSeaAt2M); } if (matchAny(param, 188, 3075)) { return static_cast(LevelType::HighCloudLayer); } if (matchAny(param, 228014, 235309, 237309, 238309, 239309)) { return static_cast(LevelType::IceLayerOnWater); } if (matchAny(param, 228013)) { return static_cast(LevelType::IceTopOnWater); } if (matchAny(param, 262104)) { return static_cast(LevelType::Isothermal); } if (matchAny(param, 228010, 235305, 237305, 238305, 239305)) { return static_cast(LevelType::LakeBottom); } if (matchAny(param, 186, 3073, 235108, 237108, 238108, 239108)) { return static_cast(LevelType::LowCloudLayer); } if (matchAny(param, 151, 235151, 237151, 238151, 239151)) { return static_cast(LevelType::MeanSea); } if (matchAny(param, 187, 3074)) { return static_cast(LevelType::MediumCloudLayer); } if (matchAny(param, range(228231, 228234))) { return static_cast(LevelType::MixedLayerParcel); } if (matchAny(param, 228008, 228009, 235090, 235091, 237090, 237091, 238090, 238091, 239090, 239091)) { return static_cast(LevelType::MixingLayer); } if (matchAny(param, range(228235, 228237), 235117, 237117, 238117, 239117)) { return static_cast(LevelType::MostUnstableParcel); } // efi if (matchAny(param, 132044, 132059)) { return static_cast(LevelType::MostUnstableParcel); } if (matchAny(param, 178, 179, 208, 209, 212, 235039, 235040, 235049, 235050, 235053)) { return static_cast(LevelType::NominalTop); } if (matchAny(param, 263024, 265024, 266024, 267024)) { return static_cast(LevelType::SeaIceLayer); } if (matchAny(param, 235077, 235094, 237077, 237094, 238077, 238094, 239077, 239094)) { return static_cast(LevelType::SoilLayer); } if (matchAny( param, 8, 9, range(15, 18), 20, range(26, 45), 47, 50, 57, 58, 66, 67, 74, 129, 134, 139, range(141, 148), range(159, 163), 169, 170, range(172, 177), range(180, 182), 189, range(195, 198), 205, 210, 211, 213, range(228, 232), range(234, 236), range(238, 240), range(243, 245), 3020, 3062, 3067, 3099, 129172, range(140098, 140105), 140112, 140113, range(140121, 140129), range(140131, 140134), range(140207, 140209), 140211, 140212, range(140214, 140232), range(140234, 140239), 140244, range(140246, 140248), range(140252, 140254), range(141101, 141105), 141208, 141209, 141215, 141216, 141220, 141229, 141232, range(143101, 143105), 143208, 143209, 143215, 143216, 143220, 143229, 143232, range(144101, 144105), 144208, 144209, 144215, 144216, 144220, 144229, 144232, range(145101, 145105), 145208, 145209, 145215, 145216, 145220, 145229, 145232, 160198, range(162100, 162113), 200199, range(210186, 210191), range(210198, 210202), range(210260, 210264), range(222001, 222256), 228002, 228003, 228012, range(228015, 228022), 228024, 228026, 228027, 228032, 228035, 228036, range(228046, 228048), 228051, 228053, range(228057, 228060), 228129, 228130, 228141, 228143, 228144, range(228216, 228228), 228251, 229001, 229007, range(231001, 231003), 231005, 231010, 231012, 231057, 231058, range(233000, 233031), 235020, 235021, range(235029, 235031), range(235033, 235038), range(235041, 235043), 235048, 235051, 235052, 235055, 235058, range(235078, 235080), 235083, 235084, 235093, 235134, 235159, 235189, 235263, 235283, 235339, 237013, 237041, 237042, 237055, 237078, 237080, 237083, 237084, 237093, 237117, 237134, 237159, 237263, 237321, 238013, 238041, 238042, 238055, 238078, 238080, 238083, 238084, 238093, 238134, 238159, 238263, 239041, 239042, 239078, 239080, 239083, 239084, 239093, 239134, 239159, 239263, 260004, 260005, 260015, 260038, 260048, 260109, 260121, 260123, 260255, 260259, 260289, 260292, 260293, range(260318, 260321), 260338, 260339, 260509, 260682, 260683, 260688, 261001, 261002, range(261014, 261016), 261018, 261023, 262000, 262100, 262124, 262139, 262140, 262144, 141231, 143231, 144231, 145231, 235079, 237079, 238079, 239079)) { return static_cast(LevelType::Surface); } if (matchAny(param, 235079, 237079, 238079, 239079)) { return static_cast(LevelType::Surface); } // Strike-probability if (matchAny(param, 131022, 131024, 131060, 131061, 131062, 131063, 131064, 131065, 131066, 131067, range(131074, 131077), 131085, 131089, 131090, 131091, 131098, 131099, 133096, 133097)) { return static_cast(LevelType::Surface); } // efi if (matchAny(param, 132228, 132144)) { return static_cast(LevelType::Surface); } if (matchAny(param, 228045, 235322, 237322, 238322, 239322)) { return static_cast(LevelType::Tropopause); } // Chemical if (matchAny(param, range(228080, 228085), range(233032, 233035), range(235062, 235064), range(400000, 499999))) { return static_cast(LevelType::Surface); } // Wave period if (matchAny(param, range(140114, 140120))) { // return compile_time_registry_engine::MISSING; return static_cast(LevelType::Surface); } // ECMWF covariance paramIds (254001..254017) are defined in // eccodes/definitions/grib2/localConcepts/{ecmf,era6}/paramId.def with // typeOfFirstFixedSurface=254, which maps to the eccodes typeOfLevel // concept "abstractLevel". if (matchAny(param, range(254001, 254017))) { return static_cast(LevelType::AbstractLevel); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype SFC", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "sfc", "Unable to match `level` concept for levtype \"sfc\"", Here())); } } /// /// @brief Match height-level MARS parameters. /// /// Maps `levtype=hl` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no height-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchHL(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 10, 54, range(130, 132), 157, 246, 247, 3031, 235097, 235131, 235132, 237097, 237131, 237132, 238097, 238131, 238132, 239097, 239131, 239132)) { return static_cast(LevelType::HeightAboveGround); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype HL", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "hl", "Unable to match `level` concept for levtype \"hl\"", Here())); } } /// /// @brief Match model-level MARS parameters. /// /// Maps `levtype=ml` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no model-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchML(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; // Single-level subset of ML params: 2D fields published on the // model-level levtype but not requiring a vertical PV array. This // guard fires before the multi-level rule below; params listed here // are removed from the multi-level set. if (matchAny(param, 22, 127, 128, 129, 152)) { return static_cast(LevelType::ModelSingleLevel); } // Multi-level model fields: full vertical column, require allocation // and population of the PV array describing the hybrid coordinate. if (matchAny(param, 21, 23, range(75, 77), range(130, 133), 135, 138, range(155, 157), 203, range(246, 248), range(162100, 162113), 260290, 260292, 260293, range(400000, 499999))) { return static_cast(LevelType::ModelMultipleLevel); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype ML", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "ml", "Unable to match `level` concept for levtype \"ml\"", Here())); } } /// /// @brief Match pressure-level MARS parameters. /// /// Maps `levtype=pl` parameters onto pressure-level GRIB variants. The MARS /// `levelist` value determines whether the pressure level is interpreted in hPa /// or Pa. /// /// @param[in] param MARS parameter identifier /// @param[in] level MARS pressure level value from `levelist` /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no pressure-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchPL(const long param, const long level) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 1, 2, 10, 60, 75, 76, range(129, 135), 138, 152, range(155, 157), 203, range(246, 248), 235100, range(235129, 235133), 235135, 235138, 235152, 235155, 235157, 235203, 235246, 260290, 263107, range(400000, 499999))) { if (level >= 100) { return static_cast(LevelType::IsobaricInHpa); } else { return static_cast(LevelType::IsobaricInPa); } } // Strike-probability if (matchAny(param, 131020, 131021, 131022, 131023, 131024, 131025, 133093, 133094, 133095, 133096, 133097, 133098)) { if (level >= 100) { return static_cast(LevelType::IsobaricInHpa); } else { return static_cast(LevelType::IsobaricInPa); } } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype PL", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "pl", "Unable to match `level` concept for levtype \"pl\" and levelist \"" + std::to_string(level) + "\"", Here())); } } /// /// @brief Match flight-level MARS parameters. /// /// Maps `levtype=fl` parameters onto their GRIB level concept variant. /// The MARS `levelist` value is not relevant for flight levels because the /// conversion from flight level to pressure level is not one-to-one and requires additional /// metadata (e.g. temperature profile) that is not available at the point of level concept matching. /// Therefore, all flight levels map to the same GRIB level concept variant regardless of `levelist`. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no flight-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchFL(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 260290)) { return static_cast(LevelType::FlightLevel); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype FL", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "fl", "Unable to match `level` concept for levtype \"fl\"", Here())); } } /// /// @brief Match potential-temperature-level MARS parameters. /// /// Maps `levtype=pt` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no potential-temperature-level mapping exists. Lower-level exceptions are /// preserved through `std::throw_with_nested`. /// inline std::size_t matchPT(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 53, 54, 60, range(131, 133), 138, 155, 203, 235100, 235203, 237203, 238203, 239203, range(400000, 499999))) { return static_cast(LevelType::Theta); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype PT", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "pt", "Unable to match `level` concept for levtype \"pt\"", Here())); } } /// /// @brief Match potential-vorticity-level MARS parameters. /// /// Maps `levtype=pv` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no potential-vorticity-level mapping exists. Lower-level exceptions are /// preserved through `std::throw_with_nested`. /// inline std::size_t matchPV(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 3, 54, 129, range(131, 133), 203, 235098, 235269, range(400000, 499999))) { return static_cast(LevelType::PotentialVorticity); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype PV", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "pv", "Unable to match `level` concept for levtype \"pv\"", Here())); } } /// /// @brief Match soil-level MARS parameters. /// /// Maps `levtype=sol` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no soil-level mapping exists. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// inline std::size_t matchSOL(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 262000, 262024)) { return static_cast(LevelType::SeaIceLayer); } if (matchAny(param, 33, 74, 238, 228038, 228141, 235078, 235080, 237080, 238080, 239080)) { return static_cast(LevelType::SnowLayer); } if (matchAny(param, 183, 235077, 260199, 260360)) { return static_cast(LevelType::SoilLayer); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype SOL", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "sol", "Unable to match `level` concept for levtype \"sol\"", Here())); } } /// /// @brief Match abstract-level MARS parameters. /// /// Maps `levtype=al` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no abstract-level mapping exists. Lower-level exceptions are preserved /// through `std::throw_with_nested`. /// inline std::size_t matchAL(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, range(213101, 213160))) { return static_cast(LevelType::AbstractSingleLevel); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype AL", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "al", "Unable to match `level` concept for levtype \"al\"", Here())); } } /// /// @brief Match two-dimensional ocean-level MARS parameters. /// /// Maps `levtype=o2d` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no two-dimensional ocean-level mapping exists. Lower-level exceptions are /// preserved through `std::throw_with_nested`. /// inline std::size_t matchO2D(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, 262000, 262003, 262004, 262008, 262014, 262023)) { return static_cast(LevelType::IceLayerOnWater); } if (matchAny(param, 262001, 262005, 262006, 262906, 262907)) { return static_cast(LevelType::IceTopOnWater); } if (matchAny(param, 262002, 262009, 262011, 262015)) { return static_cast(LevelType::SnowLayerOverIceOnWater); } if (matchAny(param, 262017, 262018)) { return static_cast(LevelType::EntireMeltPond); } if (matchAny(param, 262100, 262101, range(262108, 262112), 262124, 262125, 262130, 262139, 262140, 262143, 262900)) { return static_cast(LevelType::OceanSurface); } if (matchAny(param, range(262102, 262106))) { // Note: We assume that these fields already have the level encoded as soon as the paramId is set! return static_cast(LevelType::Isothermal); } if (matchAny(param, range(262113, 262115))) { return static_cast(LevelType::MixedLayerDepthByDensity); } if (matchAny(param, 262116)) { return static_cast(LevelType::MixedLayerDepthByTemperature); } if (matchAny(param, 262118, 262119, 262121, 262122, 262146, 262147)) { return static_cast(LevelType::DepthBelowSeaLayer); } if (matchAny(param, 262120, 262123, 262148)) { return static_cast(LevelType::OceanSurfaceToBottom); } if (matchAny(param, 262141)) { return static_cast(LevelType::WaterSurfaceToIsothermalOceanLayer); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype O2D", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "o2d", "Unable to match `level` concept for levtype \"o2d\"", Here())); } } /// /// @brief Match three-dimensional ocean-level MARS parameters. /// /// Maps `levtype=o3d` parameters onto their GRIB level concept variant. /// /// @param[in] param MARS parameter identifier /// /// @return Local `LevelType` variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If no three-dimensional ocean-level mapping exists. Lower-level exceptions /// are preserved through `std::throw_with_nested`. /// inline std::size_t matchO3D(const long param) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, range(262500, 262502), 262505, 262506)) { return static_cast(LevelType::OceanModelLayer); } if (matchAny(param, 262507)) { return static_cast(LevelType::OceanModel); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype O3D", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException( param, "o3d", "Unable to match `level` concept for levtype \"o3d\"", Here())); } } } // namespace impl /// /// @brief Match the `level` concept variant. /// /// The matcher first excludes products owned by other concepts, such as wave /// spectra and satellite products. For remaining products, it reads `param` and /// `levtype` and dispatches to the corresponding levtype-specific helper. /// Pressure-level products additionally require `levelist`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local level variant index, or /// `compile_time_registry_engine::MISSING` when the level concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If required MARS metadata is missing, `levtype` is unsupported, no mapping /// exists for the `(levtype, param)` combination, or lower-level matcher /// evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t levelMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; // Skip wave spectra and satellite products if ((has(mars, "frequency") && has(mars, "direction")) || // Wave spectra (has(mars, "channel") && has(mars, "ident") && has(mars, "instrument")) // Satellite ) { return compile_time_registry_engine::MISSING; } const auto param = get_or_throw(mars, "param"); const auto levtype = get_or_throw(mars, "levtype"); if (levtype == "sfc") { return impl::matchSFC(param); } if (levtype == "hl") { return impl::matchHL(param); } if (levtype == "ml") { return impl::matchML(param); } if (levtype == "pl") { const auto level = get_or_throw(mars, "levelist"); return impl::matchPL(param, level); } if (levtype == "fl") { return impl::matchFL(param); } if (levtype == "pt") { return impl::matchPT(param); } if (levtype == "pv") { return impl::matchPV(param); } if (levtype == "sol") { return impl::matchSOL(param); } if (levtype == "al") { return impl::matchAL(param); } if (levtype == "o2d") { return impl::matchO2D(param); } if (levtype == "o3d") { return impl::matchO3D(param); } throw utils::exceptions::Mars2GribMatcherException("Unknown levtype \"" + levtype + "\"", Here()); } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException("Unable to match `level` concept", Here())); } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/level/levelEncoding.h0000664000175000017500000003236215246725757026526 0ustar alastairalastair/* * (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 levelEncoding.h /// @brief Implementation of the GRIB `level` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **level concept** within the mars2grib backend. /// /// The level concept is responsible for encoding GRIB keys related to the /// *vertical coordinate system* of the data, including: /// /// - `typeOfLevel` /// - `level` /// - model-level coordinates and PV array (multi-level model layouts only) /// - layered levels expressed via `topLevel` / `bottomLevel` /// /// The encoder behaviour is governed by three orthogonal compile-time /// predicates over the variant: /// /// - `needPv()` : the variant requires a PV array /// (currently only `ModelMultipleLevel`). /// - `needLevel()` : the variant carries a numeric `level` /// value to be written. /// - `needTopBottomLevel()` : the variant is expressed as a /// layer with explicit `topLevel` and /// `bottomLevel` GRIB keys. /// /// These three axes are independent: any subset may apply to a given /// variant. A small number of variants additionally have hard-coded /// special cases inside `LevelOp` (for example the `*At2M` / `*At10M` /// shortcuts and the `IsobaricInHpa` Pa->hPa conversion). /// /// Depending on the selected level variant, the concept may: /// - set only the level type, /// - set both level type and numeric level, /// - allocate and populate the PV array (multi-level model layouts). /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `levelApplicable` /// - Stage-aware behavior (allocation vs preset/runtime) /// - Explicit handling of hybrid vertical coordinates /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/level/levelEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/level.h" #include "metkit/mars2grib/backend/deductions/pvArray.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time predicate indicating whether a PV array is required. /// /// Only multi-level model-level layouts require a PV array describing /// the vertical hybrid coordinate transformation. Single-level model /// fields share the GRIB `typeOfLevel="hybrid"` string but do not carry /// a vertical column and therefore do not need a PV array. /// /// @tparam Variant Level concept variant /// /// @return `true` if a PV array is required, /// `false` otherwise. /// template constexpr bool needPv() { if constexpr (Variant == LevelType::ModelMultipleLevel) { return true; } else { return false; } // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Compile-time predicate indicating whether a numeric `level` value is required. /// /// Some level types require an associated numeric level (e.g. pressure, /// height, model-level number). Both model-level variants /// (`ModelSingleLevel` and `ModelMultipleLevel`) require it; they differ /// only in whether a PV array is also needed. /// /// `AbstractLevel` carries an opaque numeric `level` value (in contrast /// to `AbstractSingleLevel` and `AbstractMultipleLevel`, which do not). /// /// @tparam Variant Level concept variant /// /// @return `true` if a numeric `level` value must be set, /// `false` otherwise. /// template constexpr bool needLevel() { if constexpr (Variant == LevelType::HeightAboveGroundAt10M || Variant == LevelType::HeightAboveGroundAt2M || Variant == LevelType::HeightAboveGround || Variant == LevelType::HeightAboveSeaAt10M || Variant == LevelType::HeightAboveSeaAt2M || Variant == LevelType::HeightAboveSea || Variant == LevelType::ModelSingleLevel || Variant == LevelType::ModelMultipleLevel || Variant == LevelType::IsobaricInHpa || Variant == LevelType::IsobaricInPa || Variant == LevelType::PotentialVorticity || Variant == LevelType::Theta || Variant == LevelType::OceanModel || Variant == LevelType::AbstractLevel || Variant == LevelType::FlightLevel) { return true; } else { return false; } // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Compile-time predicate indicating whether a numeric `topLevel` and `bottomLevel` value is required. /// /// Some level types require two associated numeric levels (e.g. soilLayer), /// while others encode only the level type and/or level. /// /// @tparam Variant Level concept variant /// /// @return `true` if a numeric `topLevel` and `bottomLevel` value must be set, /// `false` otherwise. /// template constexpr bool needTopBottomLevel() { if constexpr (Variant == LevelType::SoilLayer || Variant == LevelType::SeaIceLayer || Variant == LevelType::SnowLayer || Variant == LevelType::OceanModelLayer) { return true; } else { return false; } // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Compile-time applicability predicate for the `level` concept. /// /// This predicate determines whether the level concept is applicable for a given /// combination of: /// - encoding stage /// - GRIB section /// - level variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// Multi-level model layouts require special handling: /// - during allocation stage to reserve space for the PV array, /// - during preset/runtime stages to set the level type and parameters. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Level concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// template constexpr bool levelApplicable() { if constexpr (Section == SecProductDefinitionSection && needPv()) { // pvArray needs to be allocated at allocation stage return true; } if constexpr (Section == SecProductDefinitionSection && !needPv()) { return (Stage != StageAllocate); } return false; } /// /// @brief Execute the `level` concept operation. /// /// This function implements the runtime logic of the GRIB `level` concept. /// When applicable, it: /// /// - allocates and sets the PV array for hybrid levels during allocation stage, /// - sets the GRIB `typeOfLevel` key, /// - sets the numeric `level` key when required. /// /// The behavior is explicitly stage-dependent: /// - `StageAllocate` is used for memory allocation (PV array), /// - `StagePreset` and `StageRuntime` are used for semantic encoding. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Level concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - required deductions fail, /// - invalid stage/variant combinations are invoked, /// - any GRIB key cannot be set. /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - The concept does not rely on pre-existing GRIB header state. /// - Setting of typeOfLevel is happening at both preset and runtime stages because /// sometimes due to sideeffects in eccodes the typeOfLevel set at preset stage /// can be overwritten before runtime stage. /// /// /// @see levelApplicable /// @see needLevel /// @see needPv /// template void LevelOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (levelApplicable()) { try { MARS2GRIB_LOG_CONCEPT(level); // ============================================================= // Allocation stage (PV array) // ============================================================= if constexpr (Stage == StageAllocate && needPv()) { // Allocate space for pv array std::vector pv_array = deductions::resolve_PvArray_or_throw(mars, par, opt); // Set the PV array set_or_throw(out, "PVPresent", 1L); set_or_throw>(out, "pv", pv_array); } // ============================================================= // Preset / runtime stage // ============================================================= if constexpr (Stage == StagePreset || Stage == StageRuntime) { // Set level type (and level) if constexpr (Variant == LevelType::HeightAboveGroundAt2M) { set_or_throw(out, "typeOfLevel", "heightAboveGround"); set_or_throw(out, "level", 2L); } else if constexpr (Variant == LevelType::HeightAboveGroundAt10M) { set_or_throw(out, "typeOfLevel", "heightAboveGround"); set_or_throw(out, "level", 10L); } else if constexpr (Variant == LevelType::HeightAboveSeaAt2M) { set_or_throw(out, "typeOfLevel", "heightAboveSea"); set_or_throw(out, "level", 2L); } else if constexpr (Variant == LevelType::HeightAboveSeaAt10M) { set_or_throw(out, "typeOfLevel", "heightAboveSea"); set_or_throw(out, "level", 10L); } else if constexpr (Variant == LevelType::IsobaricInHpa) { long levelVal = deductions::resolve_Level_or_throw(mars, par, opt); set_or_throw(out, "typeOfLevel", "isobaricInhPa"); set_or_throw(out, "level", levelVal / 100); } else { set_or_throw(out, "typeOfLevel", std::string(levelTypeName())); if constexpr (needLevel()) { long levelVal = deductions::resolve_Level_or_throw(mars, par, opt); set_or_throw(out, "level", levelVal); } if constexpr (needTopBottomLevel()) { long levelVal = deductions::resolve_Level_or_throw(mars, par, opt); long topLevel = levelVal - 1; long bottomLevel = levelVal; set_or_throw(out, "topLevel", topLevel); set_or_throw(out, "bottomLevel", bottomLevel); } } } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(level, "Unable to set `level` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(level, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/0000775000175000017500000000000015246725757025354 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeConceptDescriptor.h0000664000175000017500000001220315246725757033653 0ustar alastairalastair/* * (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 referenceTimeConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `referenceTime` concept. /// /// This header defines `ReferenceTimeConcept`, the **compile-time descriptor** /// that registers the GRIB `referenceTime` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeEncoding.h" #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeEnum.h" #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `referenceTime` concept. /// /// `ReferenceTimeConcept` registers the GRIB `referenceTime` concept into the /// compile-time registry engine. /// struct ReferenceTimeConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return referenceTimeName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return referenceTimeTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `referenceTime` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (referenceTimeApplicable()) { return &ReferenceTimeOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `referenceTime` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &referenceTimeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeEncoding.h0000664000175000017500000002621615246725757031760 0ustar alastairalastair/* * (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 referenceTimeEncoding.h /// @brief Implementation of the GRIB `referenceTime` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **referenceTime concept** within the mars2grib backend. /// /// The referenceTime concept is responsible for encoding the temporal /// *reference instant* of a GRIB product, including: /// /// - the **significance of the reference time** /// - the **actual reference date/time** /// - optional **model version date/time** metadata for reforecasts /// /// The behavior depends on both the **concept variant** and the **GRIB section** /// in which the concept is applied. /// /// Supported variants: /// - `ReferenceTimeType::Standard` /// - `ReferenceTimeType::Reforecast` /// /// Supported sections: /// - Identification Section /// - Product Definition Section (reforecast only) /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `referenceTimeApplicable` /// - Variant- and section-specific deduction logic /// - Strict validation of GRIB template compatibility /// - Context-rich error handling /// /// All encoded temporal date/time payload values are sourced exclusively from /// the resolved `backend::models::product_time_spec::ProductTimeAnchorSpec` /// through `impl::build_ReferenceTimeProductTimeSpec_or_throw(...)`. The /// separate `significanceOfReferenceTime` GRIB key remains sourced from its /// dedicated deduction. /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // External libraries #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/significanceOfReferenceTime.h" // Tables #include "metkit/mars2grib/backend/tables/significanceOfReferenceTime.h" // Local details #include "metkit/mars2grib/backend/concepts/reference-time/impl/ReferenceTimeProductTimeSpec.h" // Checks #include "metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `referenceTime` concept. /// /// This predicate determines whether the referenceTime concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - reference time variant /// /// The default rules enable: /// /// - **Standard reference time** /// - Identification Section /// - Preset stage /// /// - **Reforecast reference time** /// - Identification Section (Preset stage) /// - Product Definition Section (Preset stage) /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Reference time concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// template constexpr bool referenceTimeApplicable() { // Compile time conditions to apply this concept bool condition1 = (Variant == ReferenceTimeType::Standard || Variant == ReferenceTimeType::Reforecast) && (Stage == StageRuntime) && (Section == SecIdentificationSection); bool condition2 = (Variant == ReferenceTimeType::Reforecast) && (Stage == StageRuntime) && (Section == SecProductDefinitionSection); // Confitions to apply concept return condition1 || condition2; } /// /// @brief Execute the `referenceTime` concept operation. /// /// This function implements the runtime logic of the GRIB `referenceTime` concept. /// When applicable, it: /// /// - Encodes the significance of the reference time /// - Sets the reference date and time fields /// - Optionally encodes model version date/time metadata for reforecasts /// /// Temporal date/time payload values are sourced from the reference-time-facing /// transport built from `ProductTimeAnchorSpec`. /// /// The behavior is driven by: /// - the concept variant (`Standard` vs `Reforecast`) /// - the target GRIB section /// /// Section-specific behavior: /// /// - **Identification Section** /// - Sets `significanceOfReferenceTime` /// - Sets reference date/time fields /// /// - **Product Definition Section (Reforecast only)** /// - Validates template compatibility /// - Sets model version date/time fields /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Reference time concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is invoked outside its applicability domain /// - GRIB template constraints are violated /// - any deduction or encoding step fails /// /// @see referenceTimeApplicable /// template void ReferenceTimeOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (referenceTimeApplicable()) { try { MARS2GRIB_LOG_CONCEPT(referenceTime); // Resolve the canonical ProductTimeSpec anchor state once per // concept invocation. All three date/time branches below source // their encoded values exclusively from this object. const auto anchorSpec = models::product_time_spec::ProductTimeAnchorSpec(mars, par, opt); const auto rt = impl::build_ReferenceTimeProductTimeSpec_or_throw(anchorSpec); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Section == SecIdentificationSection) { // Deductions: significanceOfReferenceTime is orthogonal to the // resolved ProductTimeSpec anchor state. tables::SignificanceOfReferenceTime significanceOfReferenceTime = deductions::resolve_SignificanceOfReferenceTime_or_throw(mars, par, opt); // Encoding set_or_throw(out, "significanceOfReferenceTime", static_cast(significanceOfReferenceTime)); } if constexpr ((Section == SecIdentificationSection) && (Variant == ReferenceTimeType::Standard)) { // For a standard product, the GRIB reference date/time is the // resolved ProductTimeSpec reference datetime. const eckit::DateTime& dateTime = rt.referenceDateTime; // Encoding set_or_throw(out, "year", dateTime.date().year()); set_or_throw(out, "month", dateTime.date().month()); set_or_throw(out, "day", dateTime.date().day()); set_or_throw(out, "hour", dateTime.time().hours()); set_or_throw(out, "minute", dateTime.time().minutes()); set_or_throw(out, "second", dateTime.time().seconds()); } if constexpr ((Section == SecIdentificationSection) && (Variant == ReferenceTimeType::Reforecast)) { // For a reforecast product, the Identification Section's // reference date/time is the resolved ProductTimeSpec label // datetime. The Product Definition Section branch below writes // the model-version date from the resolved reference datetime // instead. const eckit::DateTime& referenceDateTime = rt.labelDateTime; // Encoding set_or_throw(out, "year", referenceDateTime.date().year()); set_or_throw(out, "month", referenceDateTime.date().month()); set_or_throw(out, "day", referenceDateTime.date().day()); set_or_throw(out, "hour", referenceDateTime.time().hours()); set_or_throw(out, "minute", referenceDateTime.time().minutes()); set_or_throw(out, "second", referenceDateTime.time().seconds()); } if constexpr ((Section == SecProductDefinitionSection) && (Variant == ReferenceTimeType::Reforecast)) { // Validation validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {60L, 61L}); // Model-version date/time is the resolved ProductTimeSpec // reference datetime. const eckit::DateTime& dateTime = rt.referenceDateTime; // Encoding set_or_throw(out, "YearOfModelVersion", dateTime.date().year()); set_or_throw(out, "MonthOfModelVersion", dateTime.date().month()); set_or_throw(out, "DayOfModelVersion", dateTime.date().day()); set_or_throw(out, "HourOfModelVersion", dateTime.time().hours()); set_or_throw(out, "MinuteOfModelVersion", dateTime.time().minutes()); set_or_throw(out, "SecondOfModelVersion", dateTime.time().seconds()); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(referenceTime, "Unable to set `referenceTime` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(referenceTime, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeEnum.h0000664000175000017500000001101615246725757031126 0ustar alastairalastair/* * (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 referenceTimeEnum.h /// @brief Definition of the `referenceTime` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `referenceTime` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`referenceTimeName`) /// - the enumeration of supported reference-time variants (`ReferenceTimeType`) /// - a compile-time typelist of all variants (`ReferenceTimeList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `referenceTimeEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `referenceTime` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `referenceTime` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view referenceTimeName{"referenceTime"}; /// /// @brief Enumeration of all supported `referenceTime` concept variants. /// /// Each enumerator represents a distinct interpretation of the forecast /// reference time, as defined by ECMWF conventions and GRIB usage. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration distinguishes between operational forecasts /// and reforecast datasets. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ReferenceTimeType : std::size_t { Standard = 0, Reforecast = 1 }; /// /// @brief Compile-time list of all `referenceTime` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ReferenceTimeList = ValueList; /// /// @brief Compile-time mapping from `ReferenceTimeType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given reference-time variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Reference-time variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view referenceTimeTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view referenceTimeTypeName() { \ return NAME; \ } DEF(ReferenceTimeType::Standard, "standard"); DEF(ReferenceTimeType::Reforecast, "reforecast"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeMatcher.h0000664000175000017500000000522615246725757031613 0ustar alastairalastair/* * (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 referenceTimeMatcher.h /// @brief Entry-level matcher for the GRIB `referenceTime` concept. /// /// This header defines the runtime matcher used by the concept registry to /// choose between standard and reforecast reference-time handling. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/reference-time/referenceTimeEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `referenceTime` concept variant. /// /// The matcher selects `ReferenceTimeType::Reforecast` when `hdate` is present /// in the MARS request, and `ReferenceTimeType::Standard` otherwise. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for the selected reference-time variant. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t referenceTimeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "hdate")) { return static_cast(ReferenceTimeType::Reforecast); } return static_cast(ReferenceTimeType::Standard); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `referenceTime` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/impl/0000775000175000017500000000000015246725757026315 5ustar alastairalastair././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/impl/ReferenceTimeProductTimeSpec.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/reference-time/impl/ReferenceTimeProductTimeSpec0000664000175000017500000000771115246725757033756 0ustar alastairalastair/* * (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 impl/ReferenceTimeProductTimeSpec.h /// @brief Reference-time-specific transport struct built from /// `ProductTimeAnchorSpec`. /// /// Exposes `ReferenceTimeProductTimeSpec` plus the pure builder /// `build_ReferenceTimeProductTimeSpec_or_throw`, which turns one final /// immutable `backend::models::product_time_spec::ProductTimeAnchorSpec` into /// the reference-time-facing transport struct consumed by the reference-time /// concept. /// /// This header owns only the reference-time-facing transport type and the /// builder. It does NOT perform GRIB encoding itself. /// /// @ingroup mars2grib_backend_concepts /// #pragma once #include #include "eckit/types/DateTime.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_::impl { /// /// @brief Reference-time-facing transport struct derived from /// `ProductTimeAnchorSpec`. /// /// The struct contains the resolved anchor datetimes needed by the /// reference-time concept. Every member is copied directly from the final /// immutable ProductTimeSpec anchor artifact after any extractor-level /// validation succeeds. /// struct ReferenceTimeProductTimeSpec { /// @brief Resolved anchor label datetime. eckit::DateTime labelDateTime{}; /// @brief Resolved anchor initial-conditions datetime. eckit::DateTime initialConditionsDateTime{}; /// @brief Resolved anchor reference datetime. eckit::DateTime referenceDateTime{}; }; /// /// @brief Build the reference-time transport struct from one final /// `ProductTimeAnchorSpec`. /// /// The builder reads only the final immutable backend-model /// `ProductTimeAnchorSpec` and materializes the reference-time-facing /// representation consumed by the reference-time concept. /// /// Build rules: /// - `referenceDateTime` must equal `initialConditionsDateTime`; /// - the three anchor datetimes are then copied directly from the resolved /// anchor artifact. /// /// @param[in] spec Final immutable backend-model `ProductTimeAnchorSpec`. /// @return Fully populated `ReferenceTimeProductTimeSpec`. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribGenericException on /// invalid anchor semantics or any unexpected failure, with the /// original cause preserved through nested exceptions. /// inline ReferenceTimeProductTimeSpec build_ReferenceTimeProductTimeSpec_or_throw( const models::product_time_spec::ProductTimeAnchorSpec& spec) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const auto& anchor = spec.anchor(); if (anchor.referenceDateTime != anchor.initialConditionsDateTime) { throw Mars2GribGenericException( "`ReferenceTimeProductTimeSpec` requires `referenceDateTime` to equal `initialConditionsDateTime`", Here()); } ReferenceTimeProductTimeSpec out; out.labelDateTime = anchor.labelDateTime; out.initialConditionsDateTime = anchor.initialConditionsDateTime; out.referenceDateTime = anchor.referenceDateTime; return out; } catch (...) { std::throw_with_nested(Mars2GribGenericException( "Failed to build `ReferenceTimeProductTimeSpec` from `ProductTimeAnchorSpec`", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_::impl metkit-1.20.2/src/metkit/mars2grib/backend/concepts/derived/0000775000175000017500000000000015246725757024104 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/derived/derivedEnum.h0000664000175000017500000001062415246725757026527 0ustar alastairalastair/* * (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 derivedEnum.h /// @brief Definition of the `derived` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `derived` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`derivedName`) /// - the exhaustive enumeration of supported derived variants (`DerivedType`) /// - a compile-time typelist of all variants (`DerivedList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `derivedEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `derived` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `derived` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view derivedName{"derived"}; /// /// @brief Enumeration of all supported `derived` concept variants. /// /// Each enumerator represents a specific derived product or statistical /// transformation applied to ensemble or multi-field data. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both direct field selections and /// post-processing/statistical aggregations. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class DerivedType : std::size_t { ShiftOfTails = 0, ///< Shift-of-tails derived product Default }; /// /// @brief Compile-time list of all `derived` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using DerivedList = ValueList; /// /// @brief Compile-time mapping from `DerivedType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given derived variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Derived variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view derivedTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view derivedTypeName() { \ return NAME; \ } DEF(DerivedType::Default, "default"); DEF(DerivedType::ShiftOfTails, "shift_of_tails"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/derived/derivedConceptDescriptor.h0000664000175000017500000001453715246725757031264 0ustar alastairalastair/* * (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 derivedConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `derived` concept. /// /// This header defines `DerivedConcept`, the **compile-time descriptor** /// that registers the GRIB `derived` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/derived/derivedEncoding.h" #include "metkit/mars2grib/backend/concepts/derived/derivedEnum.h" #include "metkit/mars2grib/backend/concepts/derived/derivedMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `derived` concept. /// /// `DerivedConcept` registers the GRIB `derived` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct DerivedConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return derivedName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return derivedTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `derived` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (derivedApplicable()) { return &DerivedOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `derived` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `derived` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &derivedMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/derived/derivedMatcher.h0000664000175000017500000000646015246725757027211 0ustar alastairalastair/* * (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 derivedMatcher.h /// @brief Entry-level matcher for the GRIB `derived` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether derived ensemble-product metadata is active for a request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "metkit/mars2grib/backend/concepts/derived/derivedEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `derived` concept variant. /// /// The concept is active for ensemble-derived MARS `type` values such as /// ensemble mean, spread, EFI, and shift-of-tails products. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `DerivedType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t derivedMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; const auto& type = get_or_throw(mars, "type"); if (type == "em" || // Ensemble mean type == "es" || // Ensemble standard deviation type == "ses" || // Ensemble spread of estimation type == "taem" || // Time-averaged ensemble mean type == "taes" || // Time-averaged ensemble standard deviation type == "efi" || // Extreme forecast index type == "efic" // Extreme forecast index ) { return static_cast(DerivedType::Default); } if (type == "sot") { return static_cast(DerivedType::ShiftOfTails); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `derived` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/derived/derivedEncoding.h0000664000175000017500000002037115246725757027351 0ustar alastairalastair/* * (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 derivedEncoding.h /// @brief Implementation of the GRIB `derived` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **derived concept** within the mars2grib backend. /// /// The concept is responsible for populating GRIB keys related to /// *derived ensemble products*, including: /// /// - `derivedForecast` /// - `numberOfForecastsInEnsemble` /// /// These keys are encoded in the Product Definition Section and are used /// to describe ensemble-derived statistical products (e.g. means, spreads, /// probabilities). /// /// The concept implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `derivedApplicable` /// - Runtime structural validation of the Product Definition Section /// - Delegation of semantic deduction to dedicated deduction functions /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/derived/derivedEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/channel.h" #include "metkit/mars2grib/backend/deductions/derivedForecast.h" #include "metkit/mars2grib/backend/deductions/numberOfForecastsInEnsemble.h" #include "metkit/mars2grib/backend/deductions/numberOfFrequencies.h" // Tables #include "metkit/mars2grib/backend/tables/derivedForecast.h" // Checks #include "metkit/mars2grib/backend/checks/checkDerivedProductDefinitionSection.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `derived` concept. /// /// This predicate determines whether the `derived` concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Derived concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note The default applicability rule enables the concept only when: /// - `Stage == StagePreset` /// - `Section == SecProductDefinitionSection` /// /// @todo [owner: mds][scope: concept][reason: correctness][prio: medium] /// - Refine applicability rules once derived product usage is fully constrained /// by stage, section, or variant. /// template constexpr bool derivedApplicable() { return (Stage == StagePreset) && ((Section == SecProductDefinitionSection) || (Section == SecLocalUseSection)); } /// /// @brief Execute the `derived` concept operation. /// /// This function implements the runtime logic of the GRIB `derived` concept. /// When applicable, it: /// /// 1. Validates that the Product Definition Section is compatible with /// derived ensemble products. /// 2. Deduces the type of derived forecast from MARS and parameter dictionaries. /// 3. Deduces the number of ensemble members involved. /// 4. Encodes the corresponding GRIB keys in the output dictionary. /// /// The concept acts as a **coordination layer**: /// - Structural validation is performed explicitly. /// - Semantic deduction is delegated to backend deductions. /// - Value correctness is guaranteed by GRIB table-backed enumerations. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Derived concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - structural validation of the Product Definition Section fails /// - a deduction fails /// - a GRIB key cannot be set /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on any pre-existing GRIB header state. /// /// @see derivedApplicable /// template void DerivedOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (derivedApplicable()) { try { MARS2GRIB_LOG_CONCEPT(derived); if constexpr (Variant == DerivedType::Default) { if constexpr (Section == SecProductDefinitionSection && Stage == StagePreset) { // Structural validation validation::check_DerivedProductDefinitionSection_or_throw(opt, out); // Deductions tables::DerivedForecast derivedForecast = deductions::resolve_DerivedForecast_or_throw(mars, par, opt); long numberOfForecastsInEnsemble = deductions::resolve_NumberOfForecastsInEnsemble_or_throw(mars, par, opt); // Encoding set_or_throw(out, "derivedForecast", static_cast(derivedForecast)); set_or_throw(out, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble); } } if constexpr (Variant == DerivedType::ShiftOfTails) { if constexpr (Section == SecProductDefinitionSection && Stage == StagePreset) { // Structural validation // validation::check_DerivedProductDefinitionSection_or_throw(opt, out); // Deductions tables::DerivedForecast derivedForecast = deductions::resolve_DerivedForecast_or_throw(mars, par, opt); // long numberOfForecastsInEnsemble = // deductions::resolve_NumberOfForecastsInEnsemble_or_throw(mars, par, opt); // Encoding set_or_throw(out, "derivedForecast", static_cast(derivedForecast)); // set_or_throw(out, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble); } } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(derived, "Unable to set `derived` concept..."); } return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(derived, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/iteration/0000775000175000017500000000000015246725757024460 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/iteration/iterationConceptDescriptor.h0000664000175000017500000001300615246725757032202 0ustar alastairalastair/* * (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 iterationConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `iteration` concept. /// /// This header defines `IterationConcept`, the **compile-time descriptor** /// that registers the GRIB `iteration` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/iteration/iterationEncoding.h" #include "metkit/mars2grib/backend/concepts/iteration/iterationEnum.h" #include "metkit/mars2grib/backend/concepts/iteration/iterationMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `iteration` concept. /// /// `IterationConcept` registers the GRIB `iteration` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct IterationConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return iterationName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return iterationTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `iteration` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (iterationApplicable()) { return &IterationOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &iterationMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/iteration/iterationEncoding.h0000664000175000017500000001465215246725757030306 0ustar alastairalastair/* * (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 iterationEncoding.h /// @brief Implementation of the GRIB `iteration` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **iteration concept** within the mars2grib backend. /// /// The iteration concept is responsible for encoding GRIB keys associated with /// *long-range forecast metadata* stored in the Local Use Section, specifically: /// /// - `methodNumber` /// - `systemNumber` /// /// These fields are used to identify the forecasting method and system /// used for long-range or seasonal products. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `iterationApplicable` /// - Runtime validation of Local Definition Number /// - Explicit deduction of required values /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/iteration/iterationEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/iterationNumber.h" #include "metkit/mars2grib/backend/deductions/totalNumberOfIterations.h" // checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `iteration` concept. /// /// This predicate determines whether the iteration concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Iteration concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == IterationType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// template constexpr bool iterationApplicable() { return ((Variant == IterationType::Default) && (Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the `iteration` concept operation. /// /// This function implements the runtime logic of the GRIB `iteration` concept. /// When applicable, it: /// /// 1. Validates that the Local Use Section matches the expected definition. /// 2. Deduces the long-range forecasting method and system identifiers. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Iteration concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Definition Number does not match expectations, /// - required deductions fail, /// - any GRIB key cannot be set, /// - the concept is invoked when not applicable. /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on pre-existing GRIB header state. /// /// @see iterationApplicable /// template void IterationOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (iterationApplicable()) { try { MARS2GRIB_LOG_CONCEPT(iteration); // Preconditions / contracts validation::match_LocalDefinitionNumber_or_throw(opt, out, {20L, 38L}); // Deductions auto iterationNumberVal = deductions::resolve_IterationNumber_or_throw(mars, par, opt); auto totalNumberOfIterationsVal = deductions::resolve_TotalNumberOfIterations_opt(mars, par, opt); // Encoding set_or_throw(out, "iterationNumber", iterationNumberVal); if (totalNumberOfIterationsVal.has_value()) { set_or_throw(out, "totalNumberOfIterations", totalNumberOfIterationsVal.value()); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(iteration, "Unable to set `iteration` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(iteration, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/iteration/iterationEnum.h0000664000175000017500000001052515246725757027457 0ustar alastairalastair/* * (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 iterationEnum.h /// @brief Definition of the `iteration` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `iteration` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`iterationName`) /// - the enumeration of supported long-range variants (`IterationType`) /// - a compile-time typelist of all variants (`IterationList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `iterationEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `iteration` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `iteration` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view iterationName{"iteration"}; /// /// @brief Enumeration of all supported `iteration` concept variants. /// /// Each enumerator represents a specific long-range forecasting /// classification or processing mode handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the long-range concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class IterationType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `iteration` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using IterationList = ValueList; /// /// @brief Compile-time mapping from `IterationType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given long-range variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Long-range variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view iterationTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view iterationTypeName() { \ return NAME; \ } DEF(IterationType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/iteration/iterationMatcher.h0000664000175000017500000000345515246725757030142 0ustar alastairalastair/* * (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 iterationMatcher.h /// @brief Entry-level matcher for the GRIB `iteration` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether the **iteration concept** is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active. /// /// @note /// The namespace name `concepts_` is intentionally used instead of /// `concepts` to avoid conflicts with the C++20 `concept` language /// feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/concepts/iteration/iterationEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t iterationMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "iteration")) { return static_cast(IterationType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/0000775000175000017500000000000015246725757027165 5ustar alastairalastair././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureConceptDescriptor.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureConc0000664000175000017500000001443015246725757034303 0ustar alastairalastair/* * (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 brightnessTemperatureConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `brightnessTemperature` concept. /// /// This header defines `BrightnessTemperatureConcept`, the **compile-time /// descriptor** that registers the GRIB `brightnessTemperature` concept into /// the mars2grib compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved at /// compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts #pragma once // System includes #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEncoding.h" #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnum.h" #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// @brief Compile-time descriptor for the `brightnessTemperature` concept. /// /// `BrightnessTemperatureConcept` registers the GRIB `brightnessTemperature` /// concept into the compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. struct BrightnessTemperatureConcept : RegisterEntryDescriptor { /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities static constexpr std::string_view entryName() { return brightnessTemperatureName; } /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name template static constexpr std::string_view variantName() { return brightnessTemperatureTypeName(); } /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the callback /// implementing the `brightnessTemperature` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (brightnessTemperatureApplicable()) { return &BrightnessTemperatureOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// @brief Variant-specific callbacks. /// /// The brightnessTemperature concept does not currently require /// variant-specific runtime callbacks. All runtime behavior is handled /// through phase callbacks. /// /// @return Always `nullptr` template static constexpr Fn variantCallbacks() { return nullptr; } /// @brief Entry-level matcher callback. /// /// This callback is used by the registry engine to decide whether the /// brightnessTemperature concept is active for a given MARS request. /// /// @tparam Capability Encoding capability index /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Function pointer to the matcher, or `nullptr` template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &brightnessTemperatureMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEncoding.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnco0000664000175000017500000001540215246725757034305 0ustar alastairalastair/* * (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 brightnessTemperatureEncoding.h /// @brief Implementation of the GRIB `brightnessTemperature` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **brightnessTemperature concept** within the mars2grib backend. /// /// The brightnessTemperature concept is responsible for encoding GRIB keys /// associated with brightness-temperature metadata stored in the Local Use /// Section, specifically: /// /// - `channelNumber` /// - `numberOfFrequencies` /// /// These fields identify the satellite channel and the number of frequencies /// associated with the brightness-temperature product. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `brightnessTemperatureApplicable` /// - Runtime validation of Local Definition Number /// - Explicit deduction of required values /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` /// language feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/channel.h" #include "metkit/mars2grib/backend/deductions/numberOfFrequencies.h" // Checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// @brief Compile-time applicability predicate for the `brightnessTemperature` concept. /// /// This predicate determines whether the brightnessTemperature concept is /// applicable for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// @tparam Stage Encoding stage /// @tparam Section GRIB section index /// @tparam Variant Brightness-temperature concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == BrightnessTemperatureType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` template constexpr bool brightnessTemperatureApplicable() { return ((Stage == StagePreset) && (Section == SecLocalUseSection)); } /// @brief Execute the `brightnessTemperature` concept operation. /// /// This function implements the runtime logic of the GRIB /// `brightnessTemperature` concept. /// /// When applicable, it: /// /// 1. Validates that the Local Use Section matches the expected definition. /// 2. Deduces the brightness-temperature related identifiers. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage /// @tparam Section GRIB section index /// @tparam Variant Brightness-temperature concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Definition Number does not match expectations, /// - required deductions fail, /// - any GRIB key cannot be set, /// - the concept is invoked when not applicable. /// /// @note /// - All runtime errors are wrapped with full concept context /// concept name, variant, stage, section. /// - This concept does not decide whether the surrounding product is encoded /// through the satellite path or the derived-product path. /// /// @see brightnessTemperatureApplicable template void BrightnessTemperatureOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (brightnessTemperatureApplicable()) { try { MARS2GRIB_LOG_CONCEPT(brightnessTemperature); // Preconditions / contracts validation::match_LocalDefinitionNumber_or_throw(opt, out, {37L}); // number of frequencies is always 1 for brightness temperature products auto numberOfFrequenciesVal = deductions::resolve_NumberOfFrequencies_or_throw(mars, par, opt); set_or_throw(out, "numberOfFrequencies", numberOfFrequenciesVal); // In Ensemble Mean variant, channel number is required; in Default variant it is already set by the // satellite concept if constexpr (Variant == BrightnessTemperatureType::EnsembleMean) { auto channelNumberVal = deductions::resolve_Channel_or_throw(mars, par, opt); set_or_throw(out, "channelNumber", channelNumberVal); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(brightnessTemperature, "Unable to set `brightnessTemperature` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(brightnessTemperature, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureMatcher.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureMatc0000664000175000017500000001027015246725757034303 0ustar alastairalastair/* * (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 brightnessTemperatureMatcher.h /// @brief Entry-level matcher for the GRIB `brightnessTemperature` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether the **brightnessTemperature concept** is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active. /// /// @note /// The namespace name `concepts_` is intentionally used instead of /// `concepts` to avoid conflicts with the C++20 `concept` language /// feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// @brief Entry-level matcher for the `brightnessTemperature` concept. /// /// The concept is activated for brightness-temperature products identified by: /// /// - `param == 194` /// - `stream == "oper"` or `stream == "elda"` /// - presence of the MARS key `channel` /// /// The stream is intentionally **not** represented as a concept variant. /// It only determines which surrounding product-family concept is active: /// /// - `elda` may coexist with the satellite concept /// - `oper` may coexist with the derived-product concept /// /// The brightness-temperature concept itself owns only the common /// brightness-temperature Local Use Section metadata. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Index of the selected concept variant, or /// `compile_time_registry_engine::MISSING` if the concept does not apply /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If the request is identified as a brightness-temperature request but the /// mandatory `channel` key is missing. template std::size_t brightnessTemperatureMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; // Concept does not apply unless "param" is present and equals 194 if (!has(mars, "param") || get_or_throw(mars, "param") != 194) { return compile_time_registry_engine::MISSING; } // Concept does not apply unless the stream is explicitly supported if (!has(mars, "stream")) { return compile_time_registry_engine::MISSING; } const auto& stream = get_or_throw(mars, "stream"); if (stream != "oper" && stream != "elda") { return compile_time_registry_engine::MISSING; } // At this point the request is a brightness-temperature request: // "channel" is mandatory if (!has(mars, "channel")) { throw utils::exceptions::Mars2GribMatcherException( "brightnessTemperature concept requires MARS key \"channel\" " "when param=194 and stream is either \"oper\" or \"elda\"", Here()); } if (stream == "elda") { return static_cast(BrightnessTemperatureType::EnsembleMean); } else if (stream == "oper") { return static_cast(BrightnessTemperatureType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnum.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/brightness-temperature/brightnessTemperatureEnum0000664000175000017500000001112715246725757034325 0ustar alastairalastair/* * (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 brightnessTemperatureEnum.h /// @brief Definition of the `brightnessTemperature` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB /// `brightnessTemperature` concept used by the mars2grib backend. /// It contains: /// /// - the canonical concept name (`brightnessTemperatureName`) /// - the enumeration of supported brightness-temperature variants /// - a compile-time typelist of all variants /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// Brightness temperature is represented as an independent concept because /// it is orthogonal to the surrounding product-family concept. Depending on /// the stream, the same parameter may coexist with either the satellite path /// or the derived-product path. /// /// @ingroup mars2grib_backend_concepts #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// @brief Canonical name of the `brightnessTemperature` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the concept /// /// The value must remain stable across releases. inline constexpr std::string_view brightnessTemperatureName{"brightnessTemperature"}; /// @brief Enumeration of all supported `brightnessTemperature` concept variants. /// /// The concept currently has a single variant because both supported streams /// share the same Local Use Section encoding logic. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. enum class BrightnessTemperatureType : std::size_t { EnsembleMean = 0, Default }; /// @brief Compile-time list of all `brightnessTemperature` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order for /// registry construction and diagnostics. using BrightnessTemperatureList = ValueList; /// @brief Compile-time mapping from `BrightnessTemperatureType` to human-readable name. /// /// This function returns the canonical string identifier associated with a /// given brightness-temperature variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Brightness-temperature variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may appear /// in logs, tests, and diagnostic output. template constexpr std::string_view brightnessTemperatureTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view brightnessTemperatureTypeName() { \ return NAME; \ } DEF(BrightnessTemperatureType::EnsembleMean, "ensembleMean"); DEF(BrightnessTemperatureType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_metkit-1.20.2/src/metkit/mars2grib/backend/concepts/model-error/0000775000175000017500000000000015246725757024711 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/model-error/modelErrorEncoding.h0000664000175000017500000001741615246725757030654 0ustar alastairalastair/* * (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 modelErrorEncoding.h /// @brief Implementation of the GRIB `modelError` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **modelError concept** within the mars2grib backend. /// /// The modelError concept is responsible for encoding GRIB keys related to /// *model-error metadata* stored in the Local Use Section, specifically: /// /// - `componentIndex` /// - `numberOfComponents` /// - `modelErrorType` /// /// These fields identify the realization within a model-error ensemble, /// the total number of realizations, and the type of model error. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `modelErrorApplicable` /// - Runtime validation of Local Definition Number /// - Explicit deduction of required values /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/model-error/modelErrorEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/componentIndex.h" #include "metkit/mars2grib/backend/deductions/fourierCoefficientIndex.h" #include "metkit/mars2grib/backend/deductions/modelErrorType.h" #include "metkit/mars2grib/backend/deductions/numberOfComponents.h" #include "metkit/mars2grib/backend/deductions/numberOfFourierCoefficients.h" // checks #include "metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `modelError` concept. /// /// This predicate determines whether the modelError concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to control instantiation and execution. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant ModelError concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == ModelErrorType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// template constexpr bool modelErrorApplicable() { return ((Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the `modelError` concept operation. /// /// This function implements the runtime logic of the GRIB `modelError` concept. /// When applicable, it: /// /// 1. Validates that the Local Use Section matches the expected definition. /// 2. Deduces the model-error related identifiers. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant ModelError concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Definition Number does not match expectations, /// - required deductions fail, /// - any GRIB key cannot be set, /// - the concept is invoked when not applicable. /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on pre-existing GRIB header state. /// /// @see modelErrorApplicable /// template void ModelErrorOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (modelErrorApplicable()) { try { MARS2GRIB_LOG_CONCEPT(modelError); if (Variant == ModelErrorType::ComponentIndex) { validation::match_LocalDefinitionNumber_or_throw(opt, out, {25L, 39L}); // Deductions const auto componentIndexVal = deductions::resolve_ComponentIndex_or_throw(mars, par, opt); const auto numberOfComponentsVal = deductions::resolve_NumberOfComponents_or_throw(mars, par, opt); const auto modelErrorTypeVal = deductions::resolve_ModelErrorType_or_throw(mars, par, opt); // Encoding set_or_throw(out, "componentIndex", componentIndexVal); set_or_throw(out, "numberOfComponents", numberOfComponentsVal); set_or_throw(out, "modelErrorType", modelErrorTypeVal); } else if (Variant == ModelErrorType::FourierCoefficients) { validation::match_LocalDefinitionNumber_or_throw(opt, out, {45L}); // Deductions const auto fourierCoefficientIndex = deductions::resolve_FourierCoefficientIndex_or_throw(mars, par, opt); const auto numberOfFourierCoefficients = deductions::resolve_NumberOfFourierCoefficients_or_throw(mars, par, opt); const auto modelErrorTypeVal = deductions::resolve_ModelErrorType_or_throw(mars, par, opt); // Encoding set_or_throw(out, "fourierCoefficientIndex", fourierCoefficientIndex); set_or_throw(out, "numberOfFourierCoefficients", numberOfFourierCoefficients); set_or_throw(out, "modelErrorType", modelErrorTypeVal); } else { MARS2GRIB_CONCEPT_THROW(modelError, "Unknown variant..."); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(modelError, "Unable to set `modelError` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(modelError, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/model-error/modelErrorMatcher.h0000664000175000017500000000617315246725757030507 0ustar alastairalastair/* * (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 modelErrorMatcher.h /// @brief Entry-level matcher for the GRIB `modelError` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether the **modelError concept** is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active. /// /// @note /// The namespace name `concepts_` is intentionally used instead of /// `concepts` to avoid conflicts with the C++20 `concept` language /// feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/concepts/model-error/modelErrorEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t modelErrorMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; if (!has(mars, "type")) { throw metkit::mars2grib::utils::exceptions::Mars2GribMatcherException( "MARS key `type` is required to determine applicability of the `modelError` concept but is missing. " "This is a contract violation by the upstream tool that populates the MARS dictionary.", Here()); } // Concept does not apply unless "type" is present and equals "eme" if ((get_or_throw(mars, "type") != "eme" && get_or_throw(mars, "type") != "me")) { return compile_time_registry_engine::MISSING; } if (has(mars, "coeffindex")) { return static_cast(ModelErrorType::FourierCoefficients); } // At this point the request is a model-error request: "number" is mandatory if (has(mars, "number")) { return static_cast(ModelErrorType::ComponentIndex); } return compile_time_registry_engine::MISSING; } catch (...) { // Rethrow nested exceptions with a more specific message std::throw_with_nested(metkit::mars2grib::utils::exceptions::Mars2GribMatcherException( "An error occurred while matching the `modelError` concept. Check nested exception for details.", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/model-error/modelErrorEnum.h0000664000175000017500000001100215246725757030013 0ustar alastairalastair/* * (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 modelErrorEnum.h /// @brief Definition of the `modelError` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `modelError` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`modelErrorName`) /// - the enumeration of supported model-error variants (`ModelErrorType`) /// - a compile-time typelist of all variants (`ModelErrorList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `modelErrorEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `modelError` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `modelError` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view modelErrorName{"modelError"}; /// /// @brief Enumeration of all supported `modelError` concept variants. /// /// Each enumerator represents a specific model-error /// classification or processing mode handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the model-error concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ModelErrorType : std::size_t { ComponentIndex = 0, FourierCoefficients }; /// /// @brief Compile-time list of all `modelError` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ModelErrorList = ValueList; /// /// @brief Compile-time mapping from `ModelErrorType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given model-error variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Model-error variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view modelErrorTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view modelErrorTypeName() { \ return NAME; \ } DEF(ModelErrorType::ComponentIndex, "componentIndex"); DEF(ModelErrorType::FourierCoefficients, "fourierCoefficients"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/model-error/modelErrorConceptDescriptor.h0000664000175000017500000001304215246725757032547 0ustar alastairalastair/* * (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 modelErrorConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `modelError` concept. /// /// This header defines `ModelErrorConcept`, the **compile-time descriptor** /// that registers the GRIB `modelError` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/model-error/modelErrorEncoding.h" #include "metkit/mars2grib/backend/concepts/model-error/modelErrorEnum.h" #include "metkit/mars2grib/backend/concepts/model-error/modelErrorMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `modelError` concept. /// /// `ModelErrorConcept` registers the GRIB `modelError` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct ModelErrorConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return modelErrorName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return modelErrorTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `modelError` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (modelErrorApplicable()) { return &ModelErrorOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &modelErrorMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/origin/0000775000175000017500000000000015246725757023751 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/origin/originConceptDescriptor.h0000664000175000017500000001171215246725757030766 0ustar alastairalastair/* * (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 originConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `origin` concept. /// /// This header defines `OriginConcept`, the **compile-time descriptor** /// that registers the GRIB `origin` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/origin/originEncoding.h" #include "metkit/mars2grib/backend/concepts/origin/originEnum.h" #include "metkit/mars2grib/backend/concepts/origin/originMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `origin` concept. /// /// `OriginConcept` registers the GRIB `origin` concept into the /// compile-time registry engine. /// struct OriginConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return originName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return originTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `origin` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (originApplicable()) { return &OriginOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `origin` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &originMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/origin/originMatcher.h0000664000175000017500000000437615246725757026727 0ustar alastairalastair/* * (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 originMatcher.h /// @brief Entry-level matcher for the GRIB `origin` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the default originating-centre concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/origin/originEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `origin` concept variant. /// /// The origin concept is always active and resolves to `OriginType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `OriginType::Default`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t originMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { return static_cast(OriginType::Default); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `origin` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/origin/originEnum.h0000664000175000017500000001035015246725757026235 0ustar alastairalastair/* * (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 originEnum.h /// @brief Definition of the `origin` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `origin` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`originName`) /// - the enumeration of supported origin variants (`OriginType`) /// - a compile-time typelist of all variants (`OriginList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `originEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `origin` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `origin` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view originName{"origin"}; /// /// @brief Enumeration of all supported `origin` concept variants. /// /// Each enumerator represents a specific origin or provenance /// classification handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the origin concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class OriginType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `origin` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using OriginList = ValueList; /// /// @brief Compile-time mapping from `OriginType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given origin variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Origin variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view originTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view originTypeName() { \ return NAME; \ } DEF(OriginType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/origin/originEncoding.h0000664000175000017500000001310015246725757027053 0ustar alastairalastair/* * (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 originEncoding.h /// @brief Implementation of the GRIB `origin` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **origin concept** within the mars2grib backend. /// /// The origin concept is responsible for encoding GRIB metadata identifying /// the producing centre and sub-centre. In the current backend this is written /// into the Identification Section using: /// - `origin` (string centre identifier) /// - `subCentre` (numeric sub-centre identifier) /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `originApplicable` /// - Runtime deduction /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/origin/originEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/centre.h" #include "metkit/mars2grib/backend/deductions/subCentre.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `origin` concept. /// /// The default applicability enables this concept only when: /// - `Variant == OriginType::Default` /// - `Stage == StagePreset` /// - `Section == SecIdentificationSection` /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Origin concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// template constexpr bool originApplicable() { // Conditions to apply concept return ((Variant == OriginType::Default) && (Stage == StagePreset) && (Section == SecIdentificationSection)); } /// /// @brief Execute the `origin` concept operation. /// /// When applicable, this concept: /// 1. Deduces the producing centre (`origin`) from the MARS dictionary. /// 2. Deduces the numeric sub-centre (`subCentre`) from the parameter dictionary, /// defaulting to `0` when not provided (as implemented by the deduction). /// 3. Encodes both keys into the output GRIB dictionary. /// /// If invoked when not applicable, a `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Origin concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - any deduction or encoding step fails /// /// @note /// This concept does not rely on any pre-existing GRIB header state. /// /// @see originApplicable /// @see metkit::mars2grib::backend::deductions::resolve_Centre_or_throw /// @see metkit::mars2grib::backend::deductions::resolve_SubCentre_or_throw /// template void OriginOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) noexcept(false) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (originApplicable()) { try { MARS2GRIB_LOG_CONCEPT(origin); // Deductions std::string centre = deductions::resolve_Centre_or_throw(mars, par, opt); long subCentre = deductions::resolve_SubCentre_or_throw(mars, par, opt); // Encoding set_or_throw(out, "centre", centre); set_or_throw(out, "subCentre", subCentre); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(origin, "Unable to set `origin` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(origin, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/mars/0000775000175000017500000000000015246725757023424 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/mars/marsMatcher.h0000664000175000017500000000522715246725757026051 0ustar alastairalastair/* * (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 marsMatcher.h /// @brief Entry-level matcher for the GRIB `mars` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether MARS provenance metadata is active for a request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/mars/marsEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `mars` concept variant. /// /// The concept is active as `MarsType::Default` when `class`, `type`, `stream`, /// and `expver` are present in the MARS request. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `MarsType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t marsMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "class") && has(mars, "type") && has(mars, "stream") && has(mars, "expver")) { return static_cast(MarsType::Default); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException("Unable to match `mars` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/mars/marsConceptDescriptor.h0000664000175000017500000001261115246725757030113 0ustar alastairalastair/* * (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 marsConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `mars` concept. /// /// This header defines `MarsConcept`, the **compile-time descriptor** /// that registers the GRIB `mars` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/mars/marsEncoding.h" #include "metkit/mars2grib/backend/concepts/mars/marsEnum.h" #include "metkit/mars2grib/backend/concepts/mars/marsMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `mars` concept. /// /// `MarsConcept` registers the GRIB `mars` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct MarsConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return marsName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return marsTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `mars` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (marsApplicable()) { return &MarsOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &marsMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/mars/marsEnum.h0000664000175000017500000001027015246725757025364 0ustar alastairalastair/* * (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 marsEnum.h /// @brief Definition of the `mars` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `mars` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`marsName`) /// - the enumeration of supported MARS variants (`MarsType`) /// - a compile-time typelist of all variants (`MarsList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `marsEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `mars` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `mars` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view marsName{"mars"}; /// /// @brief Enumeration of all supported `mars` concept variants. /// /// Each enumerator represents a specific MARS-related semantic or /// processing context handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the MARS concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class MarsType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `mars` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using MarsList = ValueList; /// /// @brief Compile-time mapping from `MarsType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given MARS variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T MARS variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view marsTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view marsTypeName() { \ return NAME; \ } DEF(MarsType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/mars/marsEncoding.h0000664000175000017500000001706015246725757026212 0ustar alastairalastair/* * (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 marsEncoding.h /// @brief Implementation of the GRIB `mars` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **mars concept** within the mars2grib backend. /// /// The mars concept is responsible for encoding core MARS identity metadata /// into the GRIB *Local Use Section*, specifically: /// /// - `class` /// - `type` /// - `stream` /// - `expver` /// /// These fields collectively define the provenance and classification of the /// encoded product and are required by downstream systems and workflows. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `marsApplicable` /// - Runtime structural validation of the Local Use Section /// - Explicit deduction of all required MARS identity fields /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/mars/marsEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/class.h" #include "metkit/mars2grib/backend/deductions/expver.h" #include "metkit/mars2grib/backend/deductions/stream.h" #include "metkit/mars2grib/backend/deductions/type.h" // checks #include "metkit/mars2grib/backend/checks/checkLocalUseSection.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `mars` concept. /// /// This predicate determines whether the mars concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept invocations /// are instantiated. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Mars concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == MarsType::Default` /// - `Stage == StagePreset` /// - `Section == SecLocalUseSection` /// template constexpr bool marsApplicable() { // Confitions to apply concept return ((Variant == MarsType::Default) && (Stage == StagePreset) && (Section == SecLocalUseSection)); } /// /// @brief Execute the `mars` concept operation. /// /// This function implements the runtime logic of the GRIB `mars` concept. /// When applicable, it: /// /// 1. Validates the structural integrity of the GRIB Local Use Section. /// 2. Deduces core MARS identity fields from the input dictionaries. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// The concept establishes the fundamental identity of the GRIB message /// and is typically a prerequisite for other Local Use Section concepts. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Mars concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Local Use Section is structurally invalid, /// - any required MARS field cannot be deduced, /// - any GRIB key cannot be set, /// - the concept is invoked when not applicable. /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on pre-existing GRIB header state. /// /// @note /// The keywords [marsClass, marsType, marsStream] correspond to *raw GRIB keys* /// and are written directly without triggering additional logic. /// /// In contrast, the high-level keywords [class, type, stream] are **ecCodes /// concepts**. Setting them may implicitly modify multiple underlying GRIB /// keys in order to maintain internal consistency. /// /// As a consequence, assigning high-level keywords can have side effects. /// Examples (non-exhaustive) include: /// - setting "type" may implicitly update "typeOfProcessedData" /// - setting "stream" may implicitly change the product definition template number /// /// @see marsApplicable /// template void MarsOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; // eccodes/definitions/grib2/local.98.36.def if constexpr (marsApplicable()) { try { MARS2GRIB_LOG_CONCEPT(mars); // Preconditions/contracts validation::check_LocalUseSection_or_throw(opt, out); // Deductions std::string marsClassVal = deductions::resolve_Class_or_throw(mars, par, opt); std::string marsTypeVal = deductions::resolve_Type_or_throw(mars, par, opt); std::string marsStreamVal = deductions::resolve_Stream_or_throw(mars, par, opt); std::string marsExpverVal = deductions::resolve_Expver_or_throw(mars, par, opt); // Encoding set_or_throw(out, "marsClass", marsClassVal); set_or_throw(out, "marsType", marsTypeVal); set_or_throw(out, "marsStream", marsStreamVal); set_or_throw(out, "expver", marsExpverVal); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(mars, "Unable to set `mars` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(mars, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/tables/0000775000175000017500000000000015246725757023734 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/tables/tablesEncoding.h0000664000175000017500000001726715246725757027043 0ustar alastairalastair/* * (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 tablesEncoding.h /// @brief Implementation of the GRIB tables-versioning concept (`tables`). /// /// This header defines the applicability rules and execution logic for the /// **tables concept** within the mars2grib backend. /// /// The concept is responsible for selecting and encoding the GRIB2 tables /// versions used to interpret code tables and definitions in the produced /// message: /// - `tablesVersion` /// - `localTablesVersion` /// /// The concept is executed during the **allocation stage** for the /// *Identification Section* (Section 1). The rationale is that table versions /// influence how subsequent keys may be interpreted by ecCodes and downstream /// tools, and therefore should be established early and deterministically. /// /// Two variants are supported: /// - `TablesType::Default`: use the latest tables version provided by ecCodes. /// - `TablesType::Custom`: override the tables version from the parametrization /// dictionary. /// /// All failures are wrapped in a `Mars2GribConceptException` providing full /// concept context (concept name, variant, stage, section). /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid potential conflicts with the C++20 `concepts` language feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/tables/tablesEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/localTablesVersion.h" #include "metkit/mars2grib/backend/deductions/tablesVersion.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `tables` concept. /// /// This predicate determines whether the `tables` concept is applicable for /// a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// The default applicability rule enables this concept only when: /// - `Stage == StageAllocate` /// - `Section == SecIdentificationSection` /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Tables concept variant /// /// @return `true` if applicable, `false` otherwise. /// template constexpr bool tablesApplicable() { // Conditions to apply concept return ((Stage == StageAllocate) && (Section == SecIdentificationSection)); } /// /// @brief Execute the `tables` concept operation. /// /// This function sets GRIB table versioning keys in the output dictionary: /// - `tablesVersion` /// - `localTablesVersion` /// /// The logic is variant-dependent: /// /// ### Variant `TablesType::Default` /// - `tablesVersion` is derived from an ecCodes GRIB2 sample via /// `resolve_TablesVersionLatest_or_throw()`. /// - `localTablesVersion` is deduced via /// `resolve_LocalTablesVersion_or_throw()` (currently returns `0`). /// /// ### Variant `TablesType::Custom` /// - `tablesVersion` is taken from the parametrization dictionary (override) /// via `resolve_TablesVersionCustom_or_throw()`. /// - `localTablesVersion` is deduced via /// `resolve_LocalTablesVersion_or_throw()`. /// /// The concept is intended to be deterministic: either it explicitly sets both /// keys, or it fails with an exception. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Tables concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary (unused by the current implementation) /// @param[in] par Parameter dictionary (used for custom tables override) /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is invoked outside its applicability domain /// - any table version deduction fails /// - any output encoding step fails /// /// @note /// The `tablesVersion` key may affect downstream decoding and validation; /// therefore, it should be set before any logic that depends on specific /// table entries or local definitions. /// /// @warning /// In the provided implementation, the call: /// `resolve_TablesVersionCustom_or_throw(par, par, opt)` /// appears inconsistent with the signature previously shown for that deduction /// (expected `(mars, par, opt)`). If the intention is to read only from `par`, /// consider either: /// - adjusting the deduction signature, or /// - passing the correct `mars` dictionary as first argument. /// /// @see tablesApplicable /// @see deductions::resolve_TablesVersionLatest_or_throw /// @see deductions::resolve_TablesVersionCustom_or_throw /// @see deductions::resolve_LocalTablesVersion_or_throw /// template void TablesOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (tablesApplicable()) { try { MARS2GRIB_LOG_CONCEPT(tables); // Global deductions long localTablesVersionVal = deductions::resolve_LocalTablesVersion_or_throw(mars, par, opt); // set in output dictionary if constexpr (Variant == TablesType::Custom) { // Deductions long tablesVersionVal = deductions::resolve_TablesVersionCustom_or_throw(mars, par, opt); // Encoding set_or_throw(out, "tablesVersion", tablesVersionVal); set_or_throw(out, "localTablesVersion", localTablesVersionVal); } else if constexpr (Variant == TablesType::Default) { // Deductions long tablesVersionVal = deductions::resolve_TablesVersionLatest_or_throw(mars, par, opt); // Encoding set_or_throw(out, "tablesVersion", tablesVersionVal); set_or_throw(out, "localTablesVersion", localTablesVersionVal); } else { MARS2GRIB_CONCEPT_THROW(tables, "Unsupported variant for `tables` concept..."); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(tables, "Unable to set `tables` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(tables, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/tables/tablesConceptDescriptor.h0000664000175000017500000001171215246725757030734 0ustar alastairalastair/* * (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 tablesConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `tables` concept. /// /// This header defines `TablesConcept`, the **compile-time descriptor** /// that registers the GRIB `tables` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/tables/tablesEncoding.h" #include "metkit/mars2grib/backend/concepts/tables/tablesEnum.h" #include "metkit/mars2grib/backend/concepts/tables/tablesMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `tables` concept. /// /// `TablesConcept` registers the GRIB `tables` concept into the /// compile-time registry engine. /// struct TablesConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return tablesName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return tablesTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `tables` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (tablesApplicable()) { return &TablesOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `tables` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &tablesMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/tables/tablesMatcher.h0000664000175000017500000000506415246725757026670 0ustar alastairalastair/* * (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 tablesMatcher.h /// @brief Entry-level matcher for the GRIB `tables` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the default GRIB tables concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/tables/tablesEnum.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_ { /// /// @brief Match the `tables` concept variant. /// /// The tables concept is always active and resolves to `TablesType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `TablesType::Default`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t tablesMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; return static_cast(TablesType::Default); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `tables` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/tables/tablesEnum.h0000664000175000017500000001042515246725757026206 0ustar alastairalastair/* * (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 tablesEnum.h /// @brief Definition of the `tables` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `tables` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`tablesName`) /// - the enumeration of supported table-selection variants (`TablesType`) /// - a compile-time typelist of all variants (`TablesList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `tablesEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `tables` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `tables` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view tablesName{"tables"}; /// /// @brief Enumeration of all supported `tables` concept variants. /// /// Each enumerator represents a specific GRIB table-selection strategy /// used during encoding. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration distinguishes between default GRIB tables and /// custom or locally defined table sets. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class TablesType : std::size_t { Custom = 0, Default }; /// /// @brief Compile-time list of all `tables` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using TablesList = ValueList; /// /// @brief Compile-time mapping from `TablesType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given tables variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Tables variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view tablesTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view tablesTypeName() { \ return NAME; \ } DEF(TablesType::Custom, "custom"); DEF(TablesType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/probability/0000775000175000017500000000000015246725757025002 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/probability/probabilityEnum.h0000664000175000017500000001105015246725757030315 0ustar alastairalastair/* * (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 probabilityEnum.h /// @brief Definition of the `probability` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `probability` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`probabilityName`) /// - the enumeration of supported probability variants (`ProbabilityType`) /// - a compile-time typelist of all variants (`ProbabilityList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `probabilityEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `probability` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `probability` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view probabilityName{"probability"}; /// /// @brief Enumeration of all supported `probability` concept variants. /// /// The concept defines variants for the supported probability products. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// Additional variants may be introduced later once the probability /// semantics are finalized. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ProbabilityType : std::size_t { StrikeProbability = 0, StandardisedAnomaly, Default }; /// /// @brief Compile-time list of all `probability` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ProbabilityList = ValueList; /// /// @brief Compile-time mapping from `ProbabilityType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given probability variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Probability variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view probabilityTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view probabilityTypeName() { \ return NAME; \ } DEF(ProbabilityType::StrikeProbability, "strikeProbability"); DEF(ProbabilityType::StandardisedAnomaly, "standardisedAnomaly"); DEF(ProbabilityType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/probability/probabilityEncoding.h0000664000175000017500000001171015246725757031142 0ustar alastairalastair/* * (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 probabilityEncoding.h /// @brief Implementation of the GRIB `probability` concept operation. /// /// This header defines the **probability concept** infrastructure within /// the mars2grib backend. /// /// The probability concept currently exists only as a registered semantic /// placeholder: /// - it defines the standard compile-time concept interface, /// - it participates in the registry infrastructure, /// - it is intentionally inactive until matching and encoding rules are defined. /// /// This implementation therefore follows the conservative placeholder model: /// - Compile-time applicability via `probabilityApplicable` /// - No active encoding domain /// - Explicit failure if the concept is invoked unexpectedly /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/probability/probabilityEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `probability` concept. /// /// The probability concept is intentionally inactive until its runtime /// semantics are defined. /// /// This predicate always evaluates to `false` and exists to satisfy the /// uniform concept interface expected by the concept dispatcher. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Probability concept variant /// /// @return Always `false`. /// template constexpr bool probabilityApplicable() { return Stage == StagePreset && Section == SecProductDefinitionSection; } /// /// @brief Execute the `probability` concept operation. /// /// This function implements the registered runtime hook for the /// `probability` concept. /// /// Because the concept is currently a placeholder, it must never perform any /// encoding and any invocation is treated as a programming error. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Probability concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary (unused) /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// @param[out] out Output GRIB dictionary (unused) /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// Always thrown if this function is invoked. /// /// @note /// This function intentionally does not provide a silent no-op. /// Any invocation is treated as an unexpected use of incomplete concept logic. /// template void ProbabilityOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) noexcept(false) { using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (probabilityApplicable()) { // Debug output MARS2GRIB_LOG_CONCEPT(probability); if constexpr (Variant == ProbabilityType::StrikeProbability) { std::cerr << "WARNING:: Probability::StrikeProbability not implemented, stub only" << std::endl; } if constexpr (Variant == ProbabilityType::StandardisedAnomaly) { std::cerr << "WARNING:: Probability::StandardisedAnomaly not implemented, stub only" << std::endl; } // Successful no-op return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(probability, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/probability/probabilityMatcher.h0000664000175000017500000000667215246725757031012 0ustar alastairalastair/* * (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 probabilityMatcher.h /// @brief Entry-level matcher for the GRIB `probability` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide which probability semantics are active for a request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// The current implementation intentionally keeps the concept inactive until /// matching semantics are defined. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/probability/probabilityEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `probability` concept variant. /// /// The probability concept currently selects strike-probability products. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local `ProbabilityType` variant index or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t probabilityMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; const auto param = get_or_throw(mars, "param"); // Standard Probability // TODO: Add probability matching semantics when they are defined. if (matchAny(param, 133093, 133094, 133095, 133096, 133097, 133098)) { return static_cast(ProbabilityType::StandardisedAnomaly); } // Strike Probability if (matchAny(param, 131060, 131061, 131062, 131063, 131064, 131065, 131066, 131067, 131068, 131069, 131070, 131071, 131072, 131073, range(131074, 131077), 131085, 131089, 131090, 131091, 131098, 131099, 131100)) { return static_cast(ProbabilityType::StrikeProbability); } else { return compile_time_registry_engine::MISSING; } } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `probability` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/probability/probabilityConceptDescriptor.h0000664000175000017500000001307015246725757033047 0ustar alastairalastair/* * (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 probabilityConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `probability` concept. /// /// This header defines `ProbabilityConcept`, the **compile-time descriptor** /// that registers the GRIB `probability` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/probability/probabilityEncoding.h" #include "metkit/mars2grib/backend/concepts/probability/probabilityEnum.h" #include "metkit/mars2grib/backend/concepts/probability/probabilityMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `probability` concept. /// /// `ProbabilityConcept` registers the GRIB `probability` concept /// into the compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct ProbabilityConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return probabilityName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return probabilityTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `probability` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (probabilityApplicable()) { return &ProbabilityOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &probabilityMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/EncodingCallbacksRegistry.h0000664000175000017500000002231315246725757027713 0ustar alastairalastair/* * (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 EncodingCallbacksRegistry.h /// @brief Compile-time registry of phase-level encoding callbacks. /// /// This header defines the **fully materialized encoding dispatch table** /// used by the mars2grib backend to perform GRIB header encoding. /// /// ----------------------------------------------------------------------------- /// Conceptual model /// ----------------------------------------------------------------------------- /// /// The encoding process is modeled as a three-dimensional dispatch space: /// /// \code /// encodingCallbacks[globalVariant][stage][section] -> Fn | nullptr /// \endcode /// /// where: /// - `globalVariant` is the flattened variant index defined by `GeneralRegistry`, /// - `stage` is a logical encoding phase (allocation, preset, override, runtime), /// - `section` is a GRIB2 section identifier. /// /// Each cell contains either: /// - a fully specialized encoding function (`Fn`), or /// - `nullptr`, indicating that the combination is not applicable. /// /// ----------------------------------------------------------------------------- /// Capability selection /// ----------------------------------------------------------------------------- /// /// This registry is parameterized by a compile-time **capability index** /// (here fixed to `0`). /// /// Capabilities allow the same concept universe to expose multiple independent /// dispatch planes (e.g. encoding, matching, validation) without duplicating /// registry machinery. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file is responsible only for: /// - binding the generic phase-callback registry machinery /// (`makePhaseCallbacksRegistry`) to: /// - the complete concept universe (`AllConcepts`), /// - a specific capability index, /// - concrete dictionary types. /// /// It does **not**: /// - define any concept descriptors, /// - implement any encoding logic, /// - perform any runtime dispatch, /// - introduce new compile-time algorithms. /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// `EncodingCallbacksRegistry` is the **lowest-level executable view** /// of the compile-time registry engine. /// /// Higher-level systems (e.g. encoding plan construction and hot-path execution) /// consume this registry as immutable, constexpr data. /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Header-only /// - Fully constexpr /// - No runtime state /// - No dynamic allocation /// /// This header is safe to include in performance-critical translation units. /// /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/makePhaseCallbacksRegistry.h" #include "metkit/mars2grib/backend/concepts/AllConcepts.h" #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time registry exposing encoding callbacks for all concepts. /// /// This class template materializes a complete, three-dimensional dispatch /// table for encoding operations, specialized for a fixed set of dictionary /// types. /// /// @tparam MarsDict_t /// Type of the MARS request dictionary. /// /// @tparam ParDict_t /// Type of the parameter dictionary. /// /// @tparam OptDict_t /// Type of the encoder options dictionary. /// /// @tparam OutDict_t /// Type of the output GRIB handle or dictionary. /// /// ----------------------------------------------------------------------------- /// Lifetime and usage /// ----------------------------------------------------------------------------- /// /// All members of this registry are: /// - `static`, /// - `constexpr`, /// - immutable. /// /// No instances of this class are ever constructed. /// /// The registry is intended to be accessed as: /// /// \code /// EncodingCallbacksRegistry<...>::encodingCallbacks /// \endcode /// /// ----------------------------------------------------------------------------- /// Dependency guarantees /// ----------------------------------------------------------------------------- /// /// The structure and ordering of the registry are entirely determined by: /// - `detail::AllConcepts`, /// - the ordering of variants within each concept, /// - the canonical pipeline dimensions (`NUM_STAGES`, `NUM_SECTIONS`). /// /// Any change to these inputs results in a structurally different dispatch /// table and must be treated as a breaking change. /// template struct EncodingCallbacksRegistry { /// /// @brief Size of the registry along the variant dimension. /// /// This corresponds to the total number of flattened concept variants /// defined by `GeneralRegistry`. /// /// It defines the first (outermost) dimension of the encoding callback /// dispatch table. /// static constexpr std::size_t registry_size_along_dim0 = GeneralRegistry::NVariants; /// /// @brief Size of the registry along the encoding stage dimension. /// /// This corresponds to the number of logical encoding stages /// (e.g. allocation, preset, override, runtime) defined by /// `GeneralRegistry`. /// /// It defines the second dimension of the encoding callback /// dispatch table. /// static constexpr std::size_t registry_size_along_dim1 = GeneralRegistry::NStages; /// /// @brief Size of the registry along the GRIB section dimension. /// /// This corresponds to the number of GRIB sections handled by the /// encoding pipeline, as defined by `GeneralRegistry`. /// /// It defines the third (innermost) dimension of the encoding callback /// dispatch table. /// static constexpr std::size_t registry_size_along_dim2 = GeneralRegistry::NSections; /// /// @brief Canonical encoding function pointer type. /// /// This alias exposes the exact function signature used for all encoding /// callbacks stored in the registry. /// /// It is provided primarily for: /// - readability, /// - consistency with higher-level abstractions, /// - avoiding repetition of long qualified names. /// using Fn_t = metkit::mars2grib::backend::compile_time_registry_engine::Fn; /// /// @brief Fully materialized encoding dispatch table. /// /// This static data member contains the complete phase-level encoding /// callback registry for: /// - all concepts, /// - all variants, /// - all encoding stages, /// - all GRIB sections. /// /// The table is generated entirely at compile time by invoking /// `makePhaseCallbacksRegistry` with: /// - the full concept universe (`AllConcepts`), /// - capability index `0`, /// - the dictionary types bound to this registry. /// /// Each entry is either: /// - a valid function pointer, or /// - `nullptr` if the concept/variant/stage/section combination /// is not applicable. /// /// This table is intended to be consumed by higher-level planning /// and execution layers, not accessed directly by application code. /// static constexpr auto encodingCallbacks = metkit::mars2grib::backend::compile_time_registry_engine::makePhaseCallbacksRegistry< detail::AllConcepts, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>(); /// /// @brief Compile-time structural verification. /// /// These static assertions ensure that the dimensions of the generated /// encoding callback table exactly match the canonical sizes defined by /// `GeneralRegistry`. /// /// Any mismatch indicates a structural inconsistency between the /// registry engine and the concept universe. /// static_assert(encodingCallbacks.size() == registry_size_along_dim0, "EncodingCallbacksRegistry: size along dimension 0 does not match GeneralRegistry"); static_assert(encodingCallbacks[0].size() == registry_size_along_dim1, "EncodingCallbacksRegistry: size along dimension 1 does not match GeneralRegistry"); static_assert(encodingCallbacks[0][0].size() == registry_size_along_dim2, "EncodingCallbacksRegistry: size along dimension 2 does not match GeneralRegistry"); }; } // namespace metkit::mars2grib::backend::concepts_metkit-1.20.2/src/metkit/mars2grib/backend/concepts/wave/0000775000175000017500000000000015246725757023424 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/wave/waveEnum.h0000664000175000017500000001045015246725757025364 0ustar alastairalastair/* * (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 waveEnum.h /// @brief Definition of the `wave` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `wave` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`waveName`) /// - the enumeration of supported wave-related variants (`WaveType`) /// - a compile-time typelist of all variants (`WaveList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `waveEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `wave` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `wave` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view waveName{"wave"}; /// /// @brief Enumeration of all supported `wave` concept variants. /// /// Each enumerator represents a distinct wave-related representation /// or diagnostic handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both spectral wave representations and /// derived period-based diagnostics. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class WaveType : std::size_t { Spectra = 0, Period, Default }; /// /// @brief Compile-time list of all `wave` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using WaveList = ValueList; /// /// @brief Compile-time mapping from `WaveType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given wave variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Wave variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view waveTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view waveTypeName() { \ return NAME; \ } DEF(WaveType::Spectra, "spectra"); DEF(WaveType::Period, "period"); DEF(WaveType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/wave/waveEncoding.h0000664000175000017500000003042015246725757026205 0ustar alastairalastair/* * (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 waveEncoding.h /// @brief Implementation of the GRIB `wave` concept. /// /// This header defines the applicability rules and execution logic for the /// **wave concept** within the mars2grib backend. /// /// The `wave` concept is responsible for encoding GRIB keys related to /// wave spectral and wave period metadata, depending on: /// - the encoding stage, /// - the GRIB section, /// - the selected wave variant. /// /// The concept supports two distinct variants: /// - `WaveType::Spectra` /// - `WaveType::Period` /// /// Each variant is active at different stages of the encoding pipeline and /// operates on different subsets of GRIB keys, as dictated by the GRIB2 /// Product Definition Templates (PDTs). /// /// The implementation follows the standard mars2grib concept pattern: /// - Compile-time applicability via `waveApplicable` /// - Strict validation against expected PDTs /// - Variant- and stage-specific deductions /// - Deterministic encoding into the output dictionary /// - Context-rich error handling via concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid conflicts with the C++20 `concepts` language feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/wave/waveEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/periodItMax.h" #include "metkit/mars2grib/backend/deductions/periodItMin.h" #include "metkit/mars2grib/backend/deductions/waveDirectionGrid.h" #include "metkit/mars2grib/backend/deductions/waveDirectionNumber.h" #include "metkit/mars2grib/backend/deductions/waveFrequencyGrid.h" #include "metkit/mars2grib/backend/deductions/waveFrequencyNumber.h" // Tables #include "metkit/mars2grib/backend/tables/typeOfInterval.h" // Checks #include "metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `wave` concept. /// /// This predicate determines whether the `wave` concept is applicable for a /// given combination of: /// - encoding stage, /// - GRIB section, /// - wave variant. /// /// The default applicability rules are: /// /// - **Spectral wave data** /// - `StageAllocate`, `SecProductDefinitionSection`, `WaveType::Spectra` /// - `StageRuntime`, `SecProductDefinitionSection`, `WaveType::Spectra` /// /// - **Wave period data** /// - `StagePreset`, `SecProductDefinitionSection`, `WaveType::Period` /// /// Any other combination is considered invalid and results in a runtime /// concept error if invoked. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Wave concept variant /// /// @return `true` if the concept is applicable, `false` otherwise. /// template constexpr bool waveApplicable() { bool condition1 = (Section == SecProductDefinitionSection && Stage == StageAllocate && Variant == WaveType::Spectra); bool condition2 = (Section == SecProductDefinitionSection && Stage == StagePreset && Variant == WaveType::Period); bool condition3 = (Section == SecProductDefinitionSection && Stage == StageRuntime && Variant == WaveType::Spectra); return condition1 || condition2 || condition3; } /// /// @brief Execute the `wave` concept operation. /// /// This function implements the runtime logic for encoding wave-related /// GRIB metadata. The behavior depends on both the wave variant and the /// encoding stage. /// /// --- /// ### Variant `WaveType::Spectra` /// /// #### StageAllocate /// - Validates that the Product Definition Template Number is one of `{99, 100}`. /// - Deduces and encodes: /// - Wave direction grid (number, scale factor, scaled values) /// - Wave frequency grid (number, scale factor, scaled values) /// /// #### StageRuntime /// - Deduces and encodes: /// - `waveDirectionNumber` /// - `waveFrequencyNumber` /// /// --- /// ### Variant `WaveType::Period` /// /// #### StagePreset /// - Validates that the Product Definition Template Number is one of `{103, 104}`. /// - Deduces optional lower and/or upper wave period bounds. /// - Encodes wave period interval metadata according to the availability /// of minimum and/or maximum bounds. /// /// --- /// ### Validation /// /// Each variant is validated against the expected GRIB Product Definition /// Template Number(s) before any encoding is performed. /// /// --- /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Wave concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is invoked outside its applicability domain, /// - the Product Definition Template Number does not match expectations, /// - any wave grid or wave period deduction fails, /// - any encoding operation fails. /// /// @note /// For `WaveType::Period`, the code explicitly notes that some fields are /// already implicitly set by ecCodes via `paramId`. Overwriting these values /// may be redundant and should be reviewed once the final wave-period /// encoding policy is agreed. /// /// @warning /// If neither `periodItMin` nor `periodItMax` is present, no wave-period /// interval metadata is written. This is currently allowed but may require /// stricter validation in the future. /// /// @see waveApplicable /// @see deductions::resolve_WaveDirectionGrid_or_throw /// @see deductions::resolve_WaveFrequencyGrid_or_throw /// @see deductions::resolve_WaveDirectionNumber_or_throw /// @see deductions::resolve_WaveFrequencyNumber_or_throw /// @see deductions::resolve_PeriodItMin_opt /// @see deductions::resolve_PeriodItMax_opt /// template void WaveOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (waveApplicable()) { try { MARS2GRIB_LOG_CONCEPT(wave); // Checks/Validation if constexpr (Variant == WaveType::Spectra) { validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {99, 100}); } else if constexpr (Variant == WaveType::Period) { validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {103, 104, 144, 145}); } if constexpr (Stage == StageAllocate) { if constexpr (Variant == WaveType::Spectra) { // Deductions deductions::WaveDirectionGrid directionGrid = deductions::resolve_WaveDirectionGrid_or_throw(mars, par, opt); deductions::WaveFrequencyGrid frequencyGrid = deductions::resolve_WaveFrequencyGrid_or_throw(mars, par, opt); // Encoding set_or_throw(out, "numberOfWaveDirections", directionGrid.numDirections); set_or_throw(out, "scaleFactorOfWaveDirections", directionGrid.scaleFactorDirections); set_or_throw>(out, "scaledValuesOfWaveDirections", directionGrid.scaledValuesDirections); set_or_throw(out, "numberOfWaveFrequencies", frequencyGrid.numFrequencies); set_or_throw(out, "scaleFactorOfWaveFrequencies", frequencyGrid.scaleFactorFrequencies); set_or_throw>(out, "scaledValuesOfWaveFrequencies", frequencyGrid.scaledValuesFrequencies); } } if constexpr (Stage == StagePreset) { if constexpr (Variant == WaveType::Period) { // Deductions std::optional itMin = deductions::resolve_PeriodItMin_opt(mars, par, opt); std::optional itMax = deductions::resolve_PeriodItMax_opt(mars, par, opt); // Encoding /// @note: /// - This information is set by eccodes as part of the paramId, not really /// sure it make sense to (over)write it here... if (itMin.has_value() && itMax.has_value()) { set_or_throw( out, "typeOfWavePeriodInterval", static_cast(tables::TypeOfInterval::BetweenFirstInclusiveSecondInclusive)); set_or_throw(out, "scaleFactorOfLowerWavePeriodLimit", 0L); set_or_throw(out, "scaledValueOfLowerWavePeriodLimit", itMin.value()); set_or_throw(out, "scaleFactorOfUpperWavePeriodLimit", 0L); set_or_throw(out, "scaledValueOfUpperWavePeriodLimit", itMax.value()); } else if (itMin.has_value() && !itMax.has_value()) { set_or_throw(out, "typeOfWavePeriodInterval", static_cast(tables::TypeOfInterval::GreaterThanFirstLimit)); set_or_throw(out, "scaleFactorOfLowerWavePeriodLimit", 0L); set_or_throw(out, "scaledValueOfLowerWavePeriodLimit", itMin.value()); } else if (!itMin.has_value() && itMax.has_value()) { set_or_throw(out, "typeOfWavePeriodInterval", static_cast(tables::TypeOfInterval::SmallerThanSecondLimit)); set_or_throw(out, "scaleFactorOfUpperWavePeriodLimit", 0L); set_or_throw(out, "scaledValueOfUpperWavePeriodLimit", itMax.value()); } } // if constexpr ( Variant == WaveType::Period ) } if constexpr (Stage == StageRuntime) { if constexpr (Variant == WaveType::Spectra) { // Deduction long marsDir = deductions::resolve_WaveDirectionNumber_or_throw(mars, par, opt); long marsFreq = deductions::resolve_WaveFrequencyNumber_or_throw(mars, par, opt); // Encoding set_or_throw(out, "waveDirectionNumber", marsDir); set_or_throw(out, "waveFrequencyNumber", marsFreq); } } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(wave, "Unable to set `wave` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(wave, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/wave/waveConceptDescriptor.h0000664000175000017500000001162615246725757030120 0ustar alastairalastair/* * (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 waveConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `wave` concept. /// /// This header defines `WaveConcept`, the **compile-time descriptor** /// that registers the GRIB `wave` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/wave/waveEncoding.h" #include "metkit/mars2grib/backend/concepts/wave/waveEnum.h" #include "metkit/mars2grib/backend/concepts/wave/waveMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `wave` concept. /// /// `WaveConcept` registers the GRIB `wave` concept into the /// compile-time registry engine. /// struct WaveConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return waveName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return waveTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `wave` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (waveApplicable()) { return &WaveOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `wave` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &waveMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/wave/waveMatcher.h0000664000175000017500000000714315246725757026050 0ustar alastairalastair/* * (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 waveMatcher.h /// @brief Entry-level matcher for the GRIB `wave` concept. /// /// This header defines the runtime matcher used by the concept registry to /// identify wave-period and wave-spectra products. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "eckit/exception/Exceptions.h" #include "metkit/mars2grib/backend/concepts/wave/waveEnum.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_ { /// /// @brief Match the `wave` concept variant. /// /// Wave-period parameters resolve to `WaveType::Period`. Wave spectra resolve /// to `WaveType::Spectra` and require both `frequency` and `direction` keys. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local wave variant index, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If wave spectra metadata is incomplete, the required parameter metadata /// cannot be read, or lower-level matcher evaluation fails. Lower-level /// exceptions are preserved through `std::throw_with_nested`. /// template std::size_t waveMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribMatcherException; const auto param = get_or_throw(mars, "param"); if (matchAny(param, range(140114, 140120), 141231, 143231, 144231, 145231)) { return static_cast(WaveType::Period); } if (matchAny(param, 140251)) { if (!has(mars, "frequency")) { throw Mars2GribMatcherException("Missing required mars keyword `frequency` for wave spectra", Here()); } if (!has(mars, "direction")) { throw Mars2GribMatcherException("Missing required mars keyword `direction` for wave spectra", Here()); } return static_cast(WaveType::Spectra); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribMatcherException("Unable to match `wave` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/ensemble/0000775000175000017500000000000015246725757024254 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleEnum.h0000664000175000017500000001124415246725757027046 0ustar alastairalastair/* * (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 ensembleEnum.h /// @brief Definition of the `ensemble` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `ensemble` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`ensembleName`) /// - the enumeration of supported ensemble variants (`EnsembleType`) /// - a compile-time typelist of all variants (`EnsembleList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `ensembleEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `ensemble` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `ensemble` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view ensembleName{"ensemble"}; /// /// @brief Enumeration of all supported `ensemble` concept variants. /// /// Each enumerator represents a specific ensemble configuration or /// perturbation strategy handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes both deterministic and stochastic ensemble /// generation approaches. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class EnsembleType : std::size_t { Individual = 0, PerturbedParameters, RandomPatterns, ProbabilityLargeEnsemble }; /// /// @brief Compile-time list of all `ensemble` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using EnsembleList = ValueList; /// /// @brief Compile-time mapping from `EnsembleType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given ensemble variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Ensemble variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view ensembleTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view ensembleTypeName() { \ return NAME; \ } DEF(EnsembleType::Individual, "individual"); DEF(EnsembleType::PerturbedParameters, "perturbedParameters"); DEF(EnsembleType::RandomPatterns, "randomPatterns"); DEF(EnsembleType::ProbabilityLargeEnsemble, "probabilityLargeEnsemble"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleConceptDescriptor.h0000664000175000017500000001457215246725757031603 0ustar alastairalastair/* * (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 ensembleConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `ensemble` concept. /// /// This header defines `EnsembleConcept`, the **compile-time descriptor** /// that registers the GRIB `ensemble` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/ensemble/ensembleEncoding.h" #include "metkit/mars2grib/backend/concepts/ensemble/ensembleEnum.h" #include "metkit/mars2grib/backend/concepts/ensemble/ensembleMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `ensemble` concept. /// /// `EnsembleConcept` registers the GRIB `ensemble` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct EnsembleConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return ensembleName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return ensembleTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `ensemble` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (ensembleApplicable()) { return &EnsembleOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `ensemble` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `ensemble` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &ensembleMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleEncoding.h0000664000175000017500000002003615246725757027667 0ustar alastairalastair/* * (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 ensembleEncoding.h /// @brief Implementation of the GRIB `ensemble` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **ensemble concept** within the mars2grib backend. /// /// The ensemble concept is responsible for encoding GRIB keys related to /// ensemble forecasts in the *Product Definition Section*, including: /// /// - `typeOfEnsembleForecast` /// - `numberOfForecastsInEnsemble` /// - `perturbationNumber` /// /// The concept currently supports the **Individual ensemble variant**, which /// represents a single ensemble member within an ensemble forecast system. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `ensembleApplicable` /// - Structural validation of the Product Definition Section /// - Delegation of semantic resolution to dedicated deduction functions /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/ensemble/ensembleEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/numberOfForecastsInEnsemble.h" #include "metkit/mars2grib/backend/deductions/perturbationNumber.h" #include "metkit/mars2grib/backend/deductions/typeOfEnsembleForecast.h" // Tables #include "metkit/mars2grib/backend/tables/typeOfEnsembleForecast.h" // checks #include "metkit/mars2grib/backend/checks/checkEnsembleProductDefinitionSection.h" #include "metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `ensemble` concept. /// /// This predicate determines whether the ensemble concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - ensemble variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Ensemble concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == EnsembleType::Individual` /// - `Stage == StagePreset` /// - `Section == SecProductDefinitionSection` /// /// This reflects the current GRIB encoding rules for individual ensemble members. /// template constexpr bool ensembleApplicable() { // Confitions to apply concept return ((Variant == EnsembleType::Individual || Variant == EnsembleType::ProbabilityLargeEnsemble) && (Stage == StagePreset) && (Section == SecProductDefinitionSection)); } /// /// @brief Execute the ensemble concept operation. /// /// This function implements the runtime logic of the GRIB `ensemble` concept. /// When applicable, it: /// /// 1. Validates that the Product Definition Section is compatible with /// ensemble encoding. /// 2. Deduces ensemble-related metadata from MARS and parameter dictionaries. /// 3. Encodes the corresponding GRIB keys in the output dictionary. /// /// The concept currently supports the **Individual** and **ProbabilityLargeEnsemble** ensemble variants, which /// represent a single ensemble member. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Ensemble concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the Product Definition Section is not compatible with ensemble encoding /// - any deduction fails /// - any GRIB key cannot be set /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not rely on any pre-existing GRIB header state. /// /// @see ensembleApplicable /// template void EnsembleOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (ensembleApplicable()) { try { // Logging MARS2GRIB_LOG_CONCEPT(ensemble); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Variant == EnsembleType::Individual) { // Structural validation validation::check_EnsembleProductDefinitionSection_or_throw(opt, out); // Deductions tables::TypeOfEnsembleForecast typeOfEnsembleForecast = deductions::resolve_TypeOfEnsembleForecast_or_throw(mars, par, opt); long numberOfForecastsInEnsemble = deductions::resolve_NumberOfForecastsInEnsemble_or_throw(mars, par, opt); long marsNumber = deductions::resolve_PerturbationNumber_or_throw(mars, par, opt); // Encoding set_or_throw(out, "typeOfEnsembleForecast", static_cast(typeOfEnsembleForecast)); set_or_throw(out, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble); set_or_throw(out, "perturbationNumber", marsNumber); } if constexpr (Variant == EnsembleType::ProbabilityLargeEnsemble) { // Structural validation validation::match_ProductDefinitionTemplateNumber_or_throw(opt, out, {121L, 122L}); // Deductions std::cout << "TODO:: Resolving TypeOfEnsembleForecast for ProbabilityLargeEnsemble..." << std::endl; } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(ensemble, "Unable to set `ensemble` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(ensemble, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleMatcher.h0000664000175000017500000001003715246725757027524 0ustar alastairalastair/* * (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 ensembleMatcher.h /// @brief Entry-level matcher for the GRIB `ensemble` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether ensemble-member metadata is active for a MARS request. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "metkit/mars2grib/backend/concepts/ensemble/ensembleEnum.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_ { /// /// @brief Match the `ensemble` concept variant. /// /// The concept is active as `EnsembleType::Individual` when the MARS request /// contains `number`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `EnsembleType::Individual`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t ensembleMatcher(const MarsDict_t& mars, const OptDict_t& opt) { // Skip model-error products: in that case "number" identifies the // model-error realization, not an ensemble member. try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; long param = get_or_throw(mars, "param"); if (has(mars, "number") && !(has(mars, "type") && (get_or_throw(mars, "type") == "me" || get_or_throw(mars, "type") == "eme" || get_or_throw(mars, "type") == "efi" || get_or_throw(mars, "type") == "efic" || get_or_throw(mars, "type") == "sot"))) { return static_cast(EnsembleType::Individual); } else if (has(mars, "type") && (get_or_throw(mars, "type") == "ep")) { if (matchAny(param, 131060, 131061, 131062, 131063, 131064, 131065, 131066, 131067, 131068, 131069, 131070, 131071, 131072, 131073, range(131074, 131077), 131085, 131089, 131090, 131091, 131098, 131099, 131100)) { // }, 133093, 133094, 133095, 133096, 133097, 133098)) { return static_cast(EnsembleType::ProbabilityLargeEnsemble); } else { return compile_time_registry_engine::MISSING; } } else { return compile_time_registry_engine::MISSING; } } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `ensemble` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/0000775000175000017500000000000015246725757026043 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthMatcher.h0000664000175000017500000000622115246725757032533 0ustar alastairalastair/* * (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 shapeOfTheEarthMatcher.h /// @brief Entry-level matcher for the GRIB `shapeOfTheEarth` concept. /// /// This header defines the runtime matcher used by the concept registry to /// decide whether shape-of-the-earth metadata should be encoded. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `shapeOfTheEarth` concept variant. /// /// The concept is active as `ShapeOfTheEarthType::Default` for grid-point /// fields. It is inactive for spherical harmonics, identified by `truncation`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `ShapeOfTheEarthType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t shapeOfTheEarthMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; // This is used to fully delegate section3 setting to gridSpec if (get_or_throw(opt, "skipSection3")) { return static_cast(ShapeOfTheEarthType::Dummy); } // NOTE: Spherical harmonics is encoded without shape of the earth if (has(mars, "truncation")) { return compile_time_registry_engine::MISSING; } return static_cast(ShapeOfTheEarthType::Default); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `shapeOfTheEarth` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEncoding.h0000664000175000017500000001370615246725757032704 0ustar alastairalastair/* * (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 shapeOfTheEarthEncoding.h /// @brief Implementation of the GRIB `shapeOfTheEarth` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **shapeOfTheEarth concept** within the mars2grib backend. /// /// The concept is responsible for populating the GRIB key `shapeOfTheEarth` /// in the *Grid Definition Section* (Section 3), describing the geometric /// reference system used for the Earth model (e.g. spherical Earth, /// oblate spheroid, custom reference system). /// /// The value is deduced from MARS and parameterization dictionaries and mapped /// to the corresponding GRIB code table via a strongly-typed enumeration. /// /// The implementation follows the standard mars2grib concept pattern: /// - Compile-time applicability via `shapeOfTheEarthApplicable` /// - Centralized deduction logic /// - Strict GRIB-level encoding /// - Context-rich error handling /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid conflicts with the C++20 `concepts` language feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/shapeOfTheEarth.h" // Tables #include "metkit/mars2grib/backend/tables/shapeOfTheReferenceSystem.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `shapeOfTheEarth` concept. /// /// This predicate determines whether the `shapeOfTheEarth` concept /// is applicable for a given encoding stage, GRIB section, and concept variant. /// /// The concept is applied: /// - exclusively in the *Grid Definition Section* (Section 3) /// - during the preset stage /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Shape-of-the-Earth concept variant /// /// @return `true` if the concept is applicable, `false` otherwise. /// template constexpr bool shapeOfTheEarthApplicable() { // Disable the concept for the Dummy variant if constexpr (Variant == ShapeOfTheEarthType::Dummy) { return false; } return Section == SecGridDefinitionSection && Stage == StagePreset; } /// /// @brief Execute the `shapeOfTheEarth` concept operation. /// /// This function implements the runtime logic of the GRIB /// `shapeOfTheEarth` concept. /// /// When applicable, it: /// 1. Deduces the Earth reference system from MARS, and parametrization dictionaries. /// 2. Maps the result to a GRIB-compliant /// `ShapeOfTheReferenceSystem` enumeration. /// 3. Encodes the corresponding numeric value into the output GRIB /// dictionary. /// /// If the concept is invoked outside its applicability domain, /// a `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index /// @tparam Variant Shape-of-the-Earth concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - deduction of the Earth shape fails /// - encoding into the GRIB dictionary fails /// /// @note /// - This concept performs a **pure GRIB-level encoding**. /// - All semantic interpretation of geometry is delegated to the /// corresponding deduction. /// /// @see shapeOfTheEarthApplicable /// @see deductions::resolve_ShapeOfTheEarth_or_throw /// template void ShapeOfTheEarthOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (shapeOfTheEarthApplicable()) { try { MARS2GRIB_LOG_CONCEPT(shapeOfTheEarth); // Deductions tables::ShapeOfTheReferenceSystem shapeOfTheEarth = deductions::resolve_ShapeOfTheEarth_or_throw(mars, par, opt); // Encoding set_or_throw(out, "shapeOfTheEarth", static_cast(shapeOfTheEarth)); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(shapeOfTheEarth, "Unable to set `shapeOfTheEarth` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(shapeOfTheEarth, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescriptor.hmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescrip0000664000175000017500000001227515246725757033635 0ustar alastairalastair/* * (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 shapeOfTheEarthConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `shapeOfTheEarth` concept. /// /// This header defines `ShapeOfTheEarthConcept`, the **compile-time descriptor** /// that registers the GRIB `shapeOfTheEarth` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEncoding.h" #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h" #include "metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `shapeOfTheEarth` concept. /// /// `ShapeOfTheEarthConcept` registers the GRIB `shapeOfTheEarth` concept into the /// compile-time registry engine. /// struct ShapeOfTheEarthConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return shapeOfTheEarthName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return shapeOfTheEarthTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `shapeOfTheEarth` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (shapeOfTheEarthApplicable()) { return &ShapeOfTheEarthOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `shapeOfTheEarth` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &shapeOfTheEarthMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h0000664000175000017500000001116415246725757032056 0ustar alastairalastair/* * (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 shapeOfTheEarthEnum.h /// @brief Definition of the `shapeOfTheEarth` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `shapeOfTheEarth` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`shapeOfTheEarthName`) /// - the enumeration of supported Earth-shape variants (`ShapeOfTheEarthType`) /// - a compile-time typelist of all variants (`ShapeOfTheEarthList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `shapeOfTheEarthEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `shapeOfTheEarth` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `shapeOfTheEarth` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view shapeOfTheEarthName{"shapeOfTheEarth"}; /// /// @brief Enumeration of all supported `shapeOfTheEarth` concept variants. /// /// Each enumerator represents a specific Earth-shape model or assumption /// used when encoding geospatial information. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as geodetic support evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class ShapeOfTheEarthType : std::size_t { Dummy = 0, // Used to skip section3 resolution and delegate to gridSpec Default }; /// /// @brief Compile-time list of all `shapeOfTheEarth` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using ShapeOfTheEarthList = ValueList; /// /// @brief Compile-time mapping from `ShapeOfTheEarthType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given Earth-shape variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Shape-of-the-Earth variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view shapeOfTheEarthTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view shapeOfTheEarthTypeName() { \ return NAME; \ } DEF(ShapeOfTheEarthType::Dummy, "dummy"); DEF(ShapeOfTheEarthType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/0000775000175000017500000000000015246725757025153 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeConceptDescriptor.h0000664000175000017500000001212215246725757033134 0ustar alastairalastair/* * (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 pointInTimeConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `pointInTime` concept. /// /// This header defines `PointInTimeConcept`, the **compile-time descriptor** /// that registers the GRIB `pointInTime` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEncoding.h" #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEnum.h" #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `pointInTime` concept. /// /// `PointInTimeConcept` registers the GRIB `pointInTime` concept into the /// compile-time registry engine. /// struct PointInTimeConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return pointInTimeName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return pointInTimeTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `pointInTime` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (pointInTimeApplicable()) { return &PointInTimeOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `pointInTime` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &pointInTimeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h0000664000175000017500000001456015246725757031075 0ustar alastairalastair/* * (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 pointInTimeMatcher.h /// @brief Entry-level matcher for the GRIB `pointInTime` concept. /// /// This header defines the runtime matcher used by the concept registry to /// identify products that use point-in-time forecast semantics. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEnum.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_ { /// /// @brief Match the `pointInTime` concept variant. /// /// The matcher classifies known non-statistical, wave, satellite, and chemical /// parameters as `PointInTimeType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `PointInTimeType::Default`, or /// `compile_time_registry_engine::MISSING` when the concept is inactive. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If the required parameter metadata cannot be read or lower-level matcher /// evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t pointInTimeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; const auto param = get_or_throw(mars, "param"); if (matchAny(param, range(1, 3), 10, range(15, 18), range(21, 23), range(26, 43), 53, 54, 59, 60, 66, 67, range(74, 79), range(129, 139), 141, 148, 151, 152, range(155, 157), range(159, 168), 170, range(172, 174), 183, range(186, 188), 198, 203, 206, 207, range(229, 232), range(234, 236), 238, range(243, 248), 3020, 3031, 3067, range(3073, 3075), 129172, range(140098, 140105), 140112, 140113, range(140121, 140129), range(140131, 140134), range(140207, 140209), 140211, 140212, range(140214, 140239), range(140244, 140249), range(140252, 140254), 160198, range(162059, 162063), 162071, 162072, 162093, 174096, 200199, range(210186, 210191), range(210198, 210202), range(210260, 210264), range(213101, 213160), range(228001, 228003), range(228007, 228020), 228023, 228024, 228029, 228032, 228037, 228038, range(228044, 228048), 228050, 228052, range(228088, 228090), 228131, 228132, 228141, 228164, range(228217, 228221), range(228231, 228237), 229001, 229007, 260004, 260005, 260015, 260038, 260048, 260109, 260121, 260123, 260132, 260199, 260242, 260255, 260260, 260289, 260290, 260292, 260293, 260360, 260509, 260688, 261001, 261002, range(261014, 261016), 261018, 261023, range(262000, 262009), 262011, 262014, 262015, 262017, 262018, 262023, 262024, range(262100, 262106), range(262108, 262112), range(262113, 262116), range(262118, 262125), 262130, range(262139, 262141), 262143, 262144, range(262146, 262149), range(262500, 262502), range(262505, 262507), 262900, 262906, 262907)) { return static_cast(PointInTimeType::Default); } // Wave products if (matchAny(param, range(140114, 140120), 140251)) { return static_cast(PointInTimeType::Default); } // Satellite products if (matchAny(param, 194, range(260510, 260512))) { return static_cast(PointInTimeType::Default); } // Chemical products if (matchAny(param, range(228083, 228085), range(400000, 499999))) { return static_cast(PointInTimeType::Default); } // Strike-probability products if (matchAny(param, 131068, 131069, 131073, range(131074, 131077), 131089, 131090, 131091)) { return static_cast(PointInTimeType::Default); } // Probability products if (matchAny(param, 131020, 131021, 131022, 131023, 131024, 131025)) { return static_cast(PointInTimeType::Default); } if (matchAny(param, 133093, 133094, 133095, 133096, 133097, 133098)) { return static_cast(PointInTimeType::Default); } // ECMWF covariance / analysis-uncertainty paramIds (254001..254017). // These are point-in-time products living on the abstractLevel // (typeOfFirstFixedSurface=254) and are used with MARS type=est // (individual ensemble member, PDT=1) as well as with non-ensemble // analyses (PDT=0). Without this mapping, PointInTimeConcept is left // inactive and Section 4 recipe selection fails with "No matching recipe". if (matchAny(param, range(254001, 254017))) { return static_cast(PointInTimeType::Default); } return compile_time_registry_engine::MISSING; } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `pointInTime` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEnum.h0000664000175000017500000001057715246725757030422 0ustar alastairalastair/* * (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 pointInTimeEnum.h /// @brief Definition of the `pointInTime` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `pointInTime` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`pointInTimeName`) /// - the enumeration of supported point-in-time variants (`PointInTimeType`) /// - a compile-time typelist of all variants (`PointInTimeList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `pointInTimeEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `pointInTime` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `pointInTime` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view pointInTimeName{"pointInTime"}; /// /// @brief Enumeration of all supported `pointInTime` concept variants. /// /// Each enumerator represents a specific temporal interpretation /// corresponding to a single valid time instant. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as temporal handling evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class PointInTimeType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `pointInTime` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using PointInTimeList = ValueList; /// /// @brief Compile-time mapping from `PointInTimeType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given point-in-time variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Point-in-time variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view pointInTimeTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view pointInTimeTypeName() { \ return NAME; \ } DEF(PointInTimeType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEncoding.h0000664000175000017500000002025015246725757031231 0ustar alastairalastair/* * (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 pointInTimeEncoding.h /// @brief Implementation of the GRIB `pointInTime` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **pointInTime concept** within the mars2grib backend. /// /// The pointInTime concept is responsible for encoding GRIB keys that describe /// the temporal reference of a product expressed as a *point in time*, i.e. /// a forecast offset relative to the reference time. /// /// The concept operates across multiple encoding stages: /// - **StageAllocate**: prepares time-related keys and marks unused fields as missing /// - **StagePreset**: defines the unit of the time range (hours) /// - **StageRuntime**: sets the actual forecast time value /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `pointInTimeApplicable` /// - Runtime extraction of forecast time from one final immutable /// `backend::models::product_time_spec::ProductTimeSpec` /// - Stage-specific encoding logic /// - Strict error handling with contextual concept exceptions /// /// All encoded temporal payload values are sourced exclusively from the /// point-in-time-facing transport built by /// `impl::build_PointInTimeProductTimeSpec_or_throw(...)`. /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Tables #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" // Local details #include "metkit/mars2grib/backend/concepts/point-in-time/impl/PointInTimeProductTimeSpec.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `pointInTime` concept. /// /// The default applicability enables this concept for the /// *Product Definition Section* at all encoding stages: /// - `StageAllocate` /// - `StagePreset` /// - `StageRuntime` /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Point-in-time concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The concept is stage-aware and performs different actions depending /// on the encoding stage. /// template constexpr bool pointInTimeApplicable() { bool condition1 = Stage == StageAllocate && Section == SecProductDefinitionSection; bool condition2 = Stage == StagePreset && Section == SecProductDefinitionSection; bool condition3 = Stage == StageRuntime && Section == SecProductDefinitionSection; return (condition1 || condition2 || condition3); } /// /// @brief Execute the `pointInTime` concept operation. /// /// This function implements the runtime logic of the GRIB `pointInTime` concept. /// When applicable, it: /// /// - Resolves one final immutable `ProductTimeSpec` /// - Extracts the forecast-time offset through the point-in-time transport /// - Validates that the offset is an integer number of hours /// - Encodes time-related GRIB keys according to the current encoding stage /// /// Stage-specific behavior: /// - **StageAllocate** /// - Marks cutoff-related fields as missing /// - **StagePreset** /// - Sets the time unit to hours /// - **StageRuntime** /// - Sets the forecast time value in hours /// /// If the concept is invoked when not applicable, or if unsupported /// forecast offsets are encountered, a `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Point-in-time concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - the forecast time is not an integer number of hours /// - any deduction or encoding step fails /// /// @note /// - Currently, only full-hour forecast steps are supported. /// - Sub-hour resolutions must be handled by a different concept /// or by extending this implementation. /// /// @see pointInTimeApplicable /// template void PointInTimeOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::dict_traits::setMissing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (pointInTimeApplicable()) { try { MARS2GRIB_LOG_CONCEPT(pointInTime); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Stage == StageAllocate) { // Encoding setMissing_or_throw(out, "hoursAfterDataCutoff"); setMissing_or_throw(out, "minutesAfterDataCutoff"); } if constexpr (Stage == StagePreset) { // Resolve the `ProductTimeSpec` to ensure that the forecast // time is valid and can be expressed in hours. const auto spec = models::product_time_spec::ProductTimeSpec( metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Missing, mars, par, opt); const auto pts = impl::build_PointInTimeProductTimeSpec_or_throw(spec); (void)pts; // Encoding set_or_throw(out, "indicatorOfUnitOfTimeRange", static_cast(TimeUnit::Hour)); } if constexpr (Stage == StageRuntime) { const auto spec = models::product_time_spec::ProductTimeSpec( metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing::Missing, mars, par, opt); const auto pts = impl::build_PointInTimeProductTimeSpec_or_throw(spec); // Encoding set_or_throw(out, "forecastTime", pts.forecastTime.length); } } catch (...) { MARS2GRIB_CONCEPT_RETHROW(pointInTime, "Unable to set `pointInTime` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(pointInTime, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/impl/0000775000175000017500000000000015246725757026114 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/point-in-time/impl/PointInTimeProductTimeSpec.h0000664000175000017500000001071415246725757033462 0ustar alastairalastair/* * (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 impl/PointInTimeProductTimeSpec.h /// @brief Point-in-time-specific transport struct built from `ProductTimeSpec`. /// /// Exposes `PointInTimeProductTimeSpec` plus the pure builder /// `build_PointInTimeProductTimeSpec_or_throw`, which turns one final immutable /// `backend::models::product_time_spec::ProductTimeSpec` into the point-in- /// time-facing transport struct consumed by the point-in-time concept. /// /// This header owns only the point-in-time-facing transport type and the /// builder. It does NOT perform GRIB encoding itself. /// /// @ingroup mars2grib_backend_concepts /// #pragma once #include #include #include #include #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h" #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_::impl { /// /// @brief Point-in-time-facing transport struct derived from `ProductTimeSpec`. /// /// The struct contains only the runtime temporal metadata needed by the /// point-in-time concept. /// /// `forecastTime` is currently materialized only in hours. /// struct PointInTimeProductTimeSpec { /// @brief Forecast-time offset expressed in whole hours. deductions::TimeDuration forecastTime{0, tables::TimeUnit::Hour}; }; /// /// @brief Build the point-in-time transport struct from one final /// `ProductTimeSpec`. /// /// The builder reads only the final immutable backend-model `ProductTimeSpec` /// and materializes the point-in-time-facing representation consumed by the /// point-in-time concept. /// /// Build rules: /// - only `Instant` ProductTimeSpec values are accepted; /// - `forecastTime` is the distance between /// `anchor.referenceDateTime` and `domain.domainEndDateTime`; /// - `forecastTime` must be non-negative and a whole number of hours. /// /// @param[in] spec Final immutable backend-model `ProductTimeSpec`. /// @return Fully populated `PointInTimeProductTimeSpec`. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribGenericException on /// non-instant input, invalid forecast-time arithmetic, or any /// unexpected failure, with the original cause preserved through nested /// exceptions. /// inline PointInTimeProductTimeSpec build_PointInTimeProductTimeSpec_or_throw( const models::product_time_spec::ProductTimeSpec& spec) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { if (spec.shapeType() != ProductTimeSpecShapeKind::Instant) { std::ostringstream oss; oss << "Point-in-time backend requires an Instant `ProductTimeSpec`. Timespec is: " << spec.to_json(); throw Mars2GribGenericException(oss.str(), Here()); } const long forecastTimeInSeconds = static_cast( static_cast(spec.domain().domainEndDateTime - spec.anchor().referenceDateTime)); if (forecastTimeInSeconds < 0) { throw Mars2GribGenericException("`PointInTimeProductTimeSpec` forecastTime must be non-negative", Here()); } if (forecastTimeInSeconds % 3600 != 0) { throw Mars2GribGenericException("`PointInTimeProductTimeSpec` forecastTime must be a whole number of hours", Here()); } PointInTimeProductTimeSpec out; out.forecastTime = deductions::TimeDuration{forecastTimeInSeconds / 3600, tables::TimeUnit::Hour}; return out; } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to build `PointInTimeProductTimeSpec` from `ProductTimeSpec`", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_::impl metkit-1.20.2/src/metkit/mars2grib/backend/concepts/representation/0000775000175000017500000000000015246725757025524 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/representation/representationMatcher.h0000664000175000017500000001017515246725757032247 0ustar alastairalastair/* * (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 representationMatcher.h /// @brief Entry-level matcher for the GRIB `representation` concept. /// /// This header defines the runtime matcher used by the concept registry to /// select the GRIB grid representation variant from MARS metadata. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include #include // Utils #include "eckit/geo/Grid.h" #include "eckit/spec/Custom.h" #include "metkit/mars2grib/backend/concepts/representation/representationEnum.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `representation` concept variant. /// /// Spherical harmonics are selected when `truncation` is present. Otherwise the /// matcher builds the eckit geometry from MARS `grid` and maps the resulting /// grid type onto the corresponding representation variant. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local representation variant index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If the grid cannot be resolved, is unsupported, or lower-level matcher /// evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t representationMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; // This is used to fully delegate section3 setting to gridSpec if (get_or_throw(opt, "skipSection3")) { return static_cast(RepresentationType::Dummy); } if (has(mars, "truncation")) { return static_cast(RepresentationType::SphericalHarmonics); } const auto marsGrid = get_or_throw(mars, "grid"); const auto gridType = std::unique_ptr( eckit::geo::GridFactory::build(eckit::spec::Custom{{"grid", marsGrid}})) ->type(); if (gridType == "regular_gg") { return static_cast(RepresentationType::RegularGaussian); } else if (gridType == "reduced_gg") { return static_cast(RepresentationType::ReducedGaussian); } else if (gridType == "regular_ll") { return static_cast(RepresentationType::Latlon); } else if (gridType == "ORCA") { return static_cast(RepresentationType::Orca); } else if (gridType == "HEALPix") { return static_cast(RepresentationType::Healpix); } throw utils::exceptions::Mars2GribMatcherException( "Cannot match grid \"" + marsGrid + "\" with grid type \"" + gridType + "\"! ", Here()); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `representation` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/representation/representationEncoding.h0000664000175000017500000006133015246725757032411 0ustar alastairalastair/* * (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 representationEncoding.h /// @brief Implementation of the GRIB `representation` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **representation concept**, which is responsible for configuring the /// GRIB *Grid Definition Section (Section 3)*. /// /// The representation concept controls how the spatial grid of a field /// is described in GRIB, including: /// - grid type selection (lat/lon, Gaussian, spectral, HEALPix, …) /// - grid topology and resolution parameters /// - allocation and population of grid-related metadata /// /// The concept operates in multiple encoding stages and performs /// **variant-specific logic** depending on the selected /// `RepresentationType`. /// /// --- /// /// ## Encoding stages /// /// The concept is active in the following stages: /// /// - **StageAllocate** /// - Selection of the GRIB grid type /// - Structural validation against the Grid Definition Template /// - Allocation of representation-specific data (e.g. PL arrays) /// /// - **StagePreset** /// - Population of grid geometry parameters /// - Encoding of resolution, truncation, and coordinate metadata /// /// The exact operations performed depend on the selected representation /// variant. /// /// --- /// /// ## Supported representation variants /// /// The following `RepresentationType` variants are currently handled: /// /// - `Latlon` /// - `RegularGaussian` /// - `ReducedGaussian` /// - `SphericalHarmonics` /// - `Healpix` /// /// The following variants are recognized but **not implemented**: /// /// - `Orca` /// - `Fesom` /// /// Attempting to use unsupported variants results in a concept-level /// exception. /// /// --- /// /// ## Geometry handling /// /// Geometry parameters are in most cases retrieved from eckit::geo, based on the MARS key `grid`. /// For spherical harmonics, the geometry is based on the MARS key `truncation`. /// /// @warning /// This is a transitional design. /// /// A dedicated grid/geometry deduction layer does not exist yet. /// As a consequence: /// - The concept creates an eckit::geo::Grid object and retreives the relevant keys /// - Validation of geometry consistency is minimal /// - Responsibilities between geometry handling and encoding are not /// fully separated /// /// This will be refactored in a future iteration. /// /// --- /// /// ## Applicability model /// /// All representation variants are considered applicable in: /// /// - `StageAllocate` /// - `StagePreset` /// /// for `SecGridDefinitionSection`. /// /// Variant-specific behavior is implemented entirely inside the /// operation body using `if constexpr`. /// /// --- /// /// ## Error handling /// /// - Structural mismatches with GRIB templates are detected via /// validation helpers. /// - Unsupported or unknown representation variants result in /// concept-level exceptions. /// - All failures are rethrown with full concept context. /// /// --- /// /// @note /// The namespace name `concepts_` is intentionally used instead of /// `concepts` to avoid conflicts with the C++20 `concept` language /// feature. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include #include #include #include #include // Eckit::geo includes #include "eckit/geo/Grid.h" #include "eckit/geo/PointLonLat.h" #include "eckit/geo/grid/ORCA.h" #include "eckit/geo/grid/reduced/HEALPix.h" #include "eckit/geo/grid/reduced/ReducedGaussian.h" #include "eckit/geo/grid/regular/RegularGaussian.h" #include "eckit/geo/grid/regular/RegularLL.h" #include "eckit/spec/Custom.h" #include "metkit/mars2grib/utils/generalUtils.h" // Defintion of Span #include "metkit/codes/api/CodesTypes.h" // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/representation/representationEnum.h" // Checks #include "metkit/mars2grib/backend/checks/matchGridDefinitionTemplateNumber.h" // Deductions #include "metkit/mars2grib/backend/deductions/allowedReferenceValue.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// This helper provides a function-local static buffer initialized with a constant value. /// The buffer starts with size zero and is grown on demand when the requested /// size exceeds the currently allocated size. /// /// If the buffer is already large enough, the existing storage is reused without /// reallocation. The returned span is restricted to the requested size, even if /// the underlying buffer is larger. /// /// The returned span is read-only and is intended to initialize encoded fields /// with the value resolved by the caller. The active span is refreshed with /// `std::transform` on each call. /// /// @param requiredSize Number of entries requested. /// @param value Constant value used to initialize every entry. /// /// @return Span containing exactly `requiredSize` entries set to `value`. /// static metkit::codes::Span constValueSpan(std::size_t requiredSize, double value) { static thread_local std::vector values; if (values.size() < requiredSize) { values.resize(requiredSize); } std::transform(values.begin(), values.begin() + requiredSize, values.begin(), [value](double) { return value; }); return metkit::codes::Span{values.data(), requiredSize}; } /// /// @brief Compile-time applicability predicate for the `representation` concept. /// /// This predicate determines whether the `representation` concept is /// instantiated for a given encoding stage and GRIB section. /// /// All representation variants are applicable during: /// - `StageAllocate` /// - `StagePreset` /// /// when operating on the *Grid Definition Section*. /// /// Variant-specific behavior is handled inside the concept operation. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Representation variant /// /// @return `true` if the concept is applicable, `false` otherwise. /// template constexpr bool representationApplicable() { // Disable the concept for the Dummy variant if constexpr (Variant == RepresentationType::Dummy) { return false; } bool condition1 = Stage == StageAllocate && Section == SecGridDefinitionSection; bool condition2 = Stage == StagePreset && Section == SecGridDefinitionSection; return condition1 || condition2; } /// /// @brief Execute the `representation` concept operation. /// /// This function implements the runtime logic of the GRIB `representation` /// concept. /// /// Depending on the encoding stage and the selected `RepresentationType`, /// it performs: /// /// - validation of the Grid Definition Template /// - selection of the GRIB grid type /// - extraction of geometry parameters from eckit::geo::Grid object /// - encoding of grid topology and resolution metadata /// /// The logic is entirely **variant-specific** and selected at compile time /// using `if constexpr`. /// /// @tparam Stage Encoding stage /// @tparam Section GRIB section index /// @tparam Variant Representation variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary (currently unused) /// @param[in] par Parameter dictionary (currently unused) /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - the GRIB Grid Definition Template does not match expectations /// - required geometry information is missing or inconsistent /// - the representation variant is unsupported /// template void RepresentationOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::dict_traits::setMissing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (representationApplicable()) { try { MARS2GRIB_LOG_CONCEPT(representation); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Stage == StageAllocate) { if constexpr (Variant == RepresentationType::Latlon) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {0}); // Encoding set_or_throw(out, "gridType", "regular_ll"); } else if constexpr (Variant == RepresentationType::RegularGaussian) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {40}); // Encoding set_or_throw(out, "gridType", "regular_gg"); } else if constexpr (Variant == RepresentationType::ReducedGaussian) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {40}); // Deductions const auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const std::vector plArray = grid->pl(); const long numberOfParallelsBetweenAPoleAndTheEquator = grid->ny() / 2; // Encoding set_or_throw(out, "gridType", "reduced_gg"); set_or_throw(out, "interpretationOfNumberOfPoints", 1L); // Set already, because it is the size of the PL array! set_or_throw(out, "numberOfParallelsBetweenAPoleAndTheEquator", numberOfParallelsBetweenAPoleAndTheEquator); set_or_throw>(out, "pl", plArray); } else if constexpr (Variant == RepresentationType::SphericalHarmonics) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {50}); // Encoding set_or_throw(out, "gridType", "sh"); } else if constexpr (Variant == RepresentationType::Healpix) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {150}); // Encoding set_or_throw(out, "gridType", "healpix"); } else if constexpr (Variant == RepresentationType::Orca) { // Checks/Validation validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {101}); // Encoding set_or_throw(out, "gridType", "unstructured_grid"); } else if constexpr (Variant == RepresentationType::Fesom) { // Checks validation::match_GridDefinitionTemplateNumber_or_throw(opt, out, {101}); // Not implemented error MARS2GRIB_CONCEPT_THROW(representation, "Support for Fesom representation not implemented..."); } else { MARS2GRIB_CONCEPT_THROW(representation, "Unknown `representation` variant..."); } } // Preset data representation if constexpr (Stage == StagePreset) { // Resolve allowed reference value deduction double allowedReferenceValue = deductions::resolve_AllowedReferenceValue_or_throw(mars, par, opt); if constexpr (Variant == RepresentationType::Latlon) { // Deductions auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const long Ni = grid->nlon(); const long Nj = grid->nlat(); const auto firstPoint = std::get(grid->first_point()); const auto lastPoint = std::get(grid->last_point()); const auto latitudeOfFirstGridPointInDegrees = firstPoint.lat(); const auto longitudeOfFirstGridPointInDegrees = firstPoint.lon(); const auto latitudeOfLastGridPointInDegrees = lastPoint.lat(); const auto longitudeOfLastGridPointInDegrees = lastPoint.lon(); const auto iDirectionIncrementInDegrees = std::abs(grid->dlon()); const auto jDirectionIncrementInDegrees = std::abs(grid->dlat()); // Encoding set_or_throw(out, "resolutionAndComponentFlags", 0); // Flag table 3.3 set_or_throw(out, "Ni", Ni); set_or_throw(out, "Nj", Nj); set_or_throw(out, "latitudeOfFirstGridPointInDegrees", latitudeOfFirstGridPointInDegrees); set_or_throw(out, "longitudeOfFirstGridPointInDegrees", longitudeOfFirstGridPointInDegrees); set_or_throw(out, "latitudeOfLastGridPointInDegrees", latitudeOfLastGridPointInDegrees); set_or_throw(out, "longitudeOfLastGridPointInDegrees", longitudeOfLastGridPointInDegrees); set_or_throw(out, "iDirectionIncrementInDegrees", iDirectionIncrementInDegrees); set_or_throw(out, "jDirectionIncrementInDegrees", jDirectionIncrementInDegrees); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = grid->size(); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else if constexpr (Variant == RepresentationType::RegularGaussian) { // Deductions const auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const auto firstPoint = std::get(grid->first_point()); const auto lastPoint = std::get(grid->last_point()); const auto latitudeOfFirstGridPointInDegrees = firstPoint.lat(); const auto longitudeOfFirstGridPointInDegrees = firstPoint.lon(); const auto latitudeOfLastGridPointInDegrees = lastPoint.lat(); const auto longitudeOfLastGridPointInDegrees = lastPoint.lon(); const auto iDirectionIncrementInDegrees = std::abs(grid->dx()); // TODO (GEOM): numberOfParallelsBetweenAPoleAndTheEquator, and numberOfPointsAlongAMeridian ? // Encoding set_or_throw(out, "resolutionAndComponentFlags", 0); // Flag table 3.3 set_or_throw(out, "latitudeOfFirstGridPointInDegrees", latitudeOfFirstGridPointInDegrees); set_or_throw(out, "longitudeOfFirstGridPointInDegrees", longitudeOfFirstGridPointInDegrees); set_or_throw(out, "latitudeOfLastGridPointInDegrees", latitudeOfLastGridPointInDegrees); set_or_throw(out, "longitudeOfLastGridPointInDegrees", longitudeOfLastGridPointInDegrees); set_or_throw(out, "iDirectionIncrementInDegrees", iDirectionIncrementInDegrees); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = grid->size(); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else if constexpr (Variant == RepresentationType::ReducedGaussian) { // Deductions const auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const auto& latitudes = grid->latitudes(); const auto& longitudes = grid->longitudes(grid->ny() / 2); // at the equator // NOTE: We actually need to describe the extreme latitudes and longitudes! // These 4 values have to be seen as independent, and not as two points. const auto latitudeOfFirstGridPointInDegrees = latitudes.front(); const auto longitudeOfFirstGridPointInDegrees = longitudes.front(); const auto latitudeOfLastGridPointInDegrees = latitudes.back(); const auto longitudeOfLastGridPointInDegrees = longitudes.back(); // TODO (GEOM): numberOfPointsAlongAMeridian ? // Encoding set_or_throw(out, "resolutionAndComponentFlags", 0); // Flag table 3.3 set_or_throw(out, "latitudeOfFirstGridPointInDegrees", latitudeOfFirstGridPointInDegrees); set_or_throw(out, "longitudeOfFirstGridPointInDegrees", longitudeOfFirstGridPointInDegrees); set_or_throw(out, "latitudeOfLastGridPointInDegrees", latitudeOfLastGridPointInDegrees); set_or_throw(out, "longitudeOfLastGridPointInDegrees", longitudeOfLastGridPointInDegrees); setMissing_or_throw(out, "iDirectionIncrement"); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = grid->size(); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else if constexpr (Variant == RepresentationType::Healpix) { // Deductions const auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const auto nside = static_cast(grid->Nside()); const auto orderingConvention = grid->order(); const auto longitudeOfFirstGridPointInDegrees = std::get(grid->first_point()).lon(); // Encoding set_or_throw(out, "resolutionAndComponentFlags", 0); // Flag table 3.3 set_or_throw(out, "Nside", nside); set_or_throw(out, "orderingConvention", orderingConvention); set_or_throw(out, "longitudeOfFirstGridPointInDegrees", longitudeOfFirstGridPointInDegrees); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = grid->size(); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else if constexpr (Variant == RepresentationType::Orca) { // Deductions const auto marsGrid = get_or_throw(mars, "grid"); const eckit::spec::Custom gridSpec = {{"grid", marsGrid}}; const std::unique_ptr genericGrid(eckit::geo::GridFactory::build(gridSpec)); const auto* grid = dynamic_cast(genericGrid.get()); const auto gridType = grid->name(); const auto gridSubType = grid->arrangement(); const auto uuid = grid->uid(); // Encoding set_or_throw(out, "unstructuredGridType", gridType); set_or_throw(out, "unstructuredGridSubtype", gridSubType); set_or_throw(out, "uuidOfHGrid", uuid); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = grid->size(); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else if constexpr (Variant == RepresentationType::Fesom) { MARS2GRIB_CONCEPT_THROW(representation, "Support for Fesom representation not implemented..."); } else if constexpr (Variant == RepresentationType::SphericalHarmonics) { // Deductions const auto marsTruncation = get_or_throw(mars, "truncation"); const auto pentagonalResolutionParameterJ = marsTruncation; const auto pentagonalResolutionParameterK = marsTruncation; const auto pentagonalResolutionParameterM = marsTruncation; // Encoding set_or_throw(out, "pentagonalResolutionParameterJ", pentagonalResolutionParameterJ); set_or_throw(out, "pentagonalResolutionParameterK", pentagonalResolutionParameterK); set_or_throw(out, "pentagonalResolutionParameterM", pentagonalResolutionParameterM); // Initialize values with the deduced reference value std::size_t numberOfCoefficients = (marsTruncation + 1) * (marsTruncation + 2); set_or_throw(out, "values", constValueSpan(numberOfCoefficients, allowedReferenceValue)); } else { MARS2GRIB_CONCEPT_THROW(representation, "Unknown `representation` variant..."); }; }; } catch (...) { MARS2GRIB_CONCEPT_RETHROW(representation, "Unable to set `representation` concept..."); } // Successful operation return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(representation, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/representation/representationEnum.h0000664000175000017500000001273315246725757031572 0ustar alastairalastair/* * (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 representationEnum.h /// @brief Definition of the `representation` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `representation` /// concept used by the mars2grib backend. It contains: /// /// - the canonical concept name (`representationName`) /// - the exhaustive enumeration of supported spatial representations (`RepresentationType`) /// - a compile-time typelist of all variants (`RepresentationList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `representationEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `representation` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `representation` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view representationName{"representation"}; /// /// @brief Enumeration of all supported `representation` concept variants. /// /// Each enumerator represents a distinct spatial grid or spectral /// representation used to encode meteorological fields. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration includes structured grids, spectral representations, /// and unstructured meshes used by atmosphere and ocean models. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class RepresentationType : std::size_t { Latlon = 0, RegularGaussian, ReducedGaussian, SphericalHarmonics, GeneralUnstructured, Healpix, Orca, Fesom, Dummy, // Used to skip section3 resolution and delegate to gridSpec Default }; /// /// @brief Compile-time list of all `representation` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using RepresentationList = ValueList; /// /// @brief Compile-time mapping from `RepresentationType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given representation variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Representation variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view representationTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view representationTypeName() { \ return NAME; \ } DEF(RepresentationType::Latlon, "latlon"); DEF(RepresentationType::RegularGaussian, "regularGaussian"); DEF(RepresentationType::ReducedGaussian, "reducedGaussian"); DEF(RepresentationType::SphericalHarmonics, "sphericalHarmonics"); DEF(RepresentationType::GeneralUnstructured, "generalUnstructured"); DEF(RepresentationType::Healpix, "healpix"); DEF(RepresentationType::Orca, "orca"); DEF(RepresentationType::Fesom, "fesom"); DEF(RepresentationType::Dummy, "dummy"); DEF(RepresentationType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/representation/representationConceptDescriptor.h0000664000175000017500000001223215246725757034312 0ustar alastairalastair/* * (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 representationConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `representation` concept. /// /// This header defines `RepresentationConcept`, the **compile-time descriptor** /// that registers the GRIB `representation` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/representation/representationEncoding.h" #include "metkit/mars2grib/backend/concepts/representation/representationEnum.h" #include "metkit/mars2grib/backend/concepts/representation/representationMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `representation` concept. /// /// `RepresentationConcept` registers the GRIB `representation` concept into the /// compile-time registry engine. /// struct RepresentationConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - registry identification /// - diagnostics and logging /// - debug and introspection utilities /// static constexpr std::string_view entryName() { return representationName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return representationTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This hook is queried by the registry engine to obtain the function /// implementing the `representation` concept for a given: /// - capability /// - stage /// - GRIB section /// - variant /// /// The function returns either a valid function pointer or `nullptr` if the /// combination is not applicable. /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (representationApplicable()) { return &RepresentationOp; } else { return nullptr; } } else { return nullptr; } mars2gribUnreachable(); } template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This hook is invoked to determine whether the `representation` concept should be /// activated for a given request. /// /// @tparam Capability Matching/encoding capability index /// @return Matcher function pointer, or `nullptr` if not participating. /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &representationMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/data-type/0000775000175000017500000000000015246725757024352 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h0000664000175000017500000001044515246725757027127 0ustar alastairalastair/* * (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 dataTypeEnum.h /// @brief Definition of the `dataType` concept variants and compile-time metadata. /// /// This header defines the **static description** of the GRIB `dataType` concept /// used by the mars2grib backend. It contains: /// /// - the canonical concept name (`dataTypeName`) /// - the enumeration of supported data type variants (`DataTypeType`) /// - a compile-time typelist of all variants (`DataTypeList`) /// - a compile-time mapping from variant to string identifier /// /// This file intentionally contains **no runtime logic** and **no encoding /// behavior**. Its sole purpose is to provide compile-time metadata used by: /// /// - the concept registry /// - compile-time table generation /// - logging and diagnostics /// - static validation of concept variants /// /// @note /// This header is part of the **concept definition layer**. /// Runtime behavior is implemented separately in the corresponding /// `dataTypeEncoding.h` implementation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template using ValueList = metkit::mars2grib::backend::compile_time_registry_engine::ValueList; /// /// @brief Canonical name of the `dataType` concept. /// /// This identifier is used: /// - as the logical concept key in the concept registry /// - for logging and debugging output /// - to associate variants and capabilities with the `dataType` concept /// /// The value must remain stable across releases. /// inline constexpr std::string_view dataTypeName{"dataType"}; /// /// @brief Enumeration of all supported `dataType` concept variants. /// /// Each enumerator represents a specific data type or semantic interpretation /// handled by the encoder. /// /// The numeric values of the enumerators are **not semantically relevant**; /// they are required only to: /// - provide a stable compile-time identifier /// - allow array indexing and table generation /// /// @note /// This enumeration is intentionally minimal. Additional variants may be /// introduced in the future as the data type concept evolves. /// /// @warning /// Do not reorder existing enumerators, as they are used in compile-time /// tables and registries. /// enum class DataTypeType : std::size_t { Default = 0 }; /// /// @brief Compile-time list of all `dataType` concept variants. /// /// This typelist is used to: /// - generate concept capability tables at compile time /// - register all supported variants in the concept registry /// - enable static iteration over variants without runtime overhead /// /// @note /// The order of this list must match the intended iteration order /// for registry construction and diagnostics. /// using DataTypeList = ValueList; /// /// @brief Compile-time mapping from `DataTypeType` to human-readable name. /// /// This function returns the canonical string identifier associated /// with a given data type variant. /// /// The returned value is used for: /// - logging and debugging output /// - error reporting /// - concept registry diagnostics /// /// @tparam T Data type variant /// @return String view identifying the variant /// /// @note /// The returned string must remain stable across releases, as it may /// appear in logs, tests, and diagnostic output. /// template constexpr std::string_view dataTypeTypeName(); #define DEF(T, NAME) \ template <> \ constexpr std::string_view dataTypeTypeName() { \ return NAME; \ } DEF(DataTypeType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeConceptDescriptor.h0000664000175000017500000001457515246725757031665 0ustar alastairalastair/* * (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 dataTypeConceptDescriptor.h /// @brief Compile-time registry entry for the GRIB `dataType` concept. /// /// This header defines `DataTypeConcept`, the **compile-time descriptor** /// that registers the GRIB `dataType` concept into the mars2grib /// compile-time registry engine. /// /// The descriptor provides: /// - The concept name /// - The mapping between variants and their symbolic names /// - The set of callbacks associated with each encoding phase /// - The entry-level matcher used to activate the concept /// /// This file contains **no runtime logic**. All decisions are resolved /// at compile time through template instantiation. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include // Registry engine #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core concept includes #include "metkit/mars2grib/backend/concepts/data-type/dataTypeEncoding.h" #include "metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h" #include "metkit/mars2grib/backend/concepts/data-type/dataTypeMatcher.h" namespace metkit::mars2grib::backend::concepts_ { // Importing the compile-time registry engine namespace locally to avoid // excessive verbosity in template-heavy code. This is restricted to an // internal scope and not exposed through public headers. using namespace metkit::mars2grib::backend::compile_time_registry_engine; /// /// @brief Compile-time descriptor for the `dataType` concept. /// /// `DataTypeConcept` registers the GRIB `dataType` concept into the /// compile-time registry engine. /// /// The descriptor defines: /// - The canonical concept name /// - The mapping from variant enum values to symbolic names /// - The callbacks associated with each encoding phase /// - The entry-level matcher used to detect applicability /// /// All functions in this descriptor are `constexpr` and are evaluated /// entirely at compile time. /// struct DataTypeConcept : RegisterEntryDescriptor { /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - Registry identification /// - Diagnostics and logging /// - Debug and introspection facilities /// static constexpr std::string_view entryName() { return dataTypeName; } /// /// @brief Return the symbolic name of a concept variant. /// /// @tparam T Variant enumeration value /// /// @return String view representing the variant name /// template static constexpr std::string_view variantName() { return dataTypeTypeName(); } /// /// @brief Return the callback associated with a specific encoding phase. /// /// This function is queried by the registry engine to obtain the /// callback implementing the `dataType` concept for a given: /// /// - Capability /// - Encoding stage /// - GRIB section /// - Concept variant /// /// The function returns: /// - A valid function pointer if the concept is applicable /// - `nullptr` otherwise /// /// @tparam Capability Encoding capability index /// @tparam Stage Encoding stage /// @tparam Sec GRIB section /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Function pointer implementing the phase, or `nullptr` /// template static constexpr Fn phaseCallbacks() { if constexpr (Capability == 0) { if constexpr (dataTypeApplicable()) { return &DataTypeOp; } else { return nullptr; } } else { return nullptr; } // Avoid compiler warnings mars2gribUnreachable(); } /// /// @brief Variant-specific callbacks (not used for this concept). /// /// This hook is provided for completeness of the registry interface. /// The `dataType` concept does not define variant-level callbacks, /// so this function always returns `nullptr`. /// /// @tparam Capability Encoding capability index /// @tparam Variant Concept variant /// @tparam MarsDict_t Type of MARS dictionary /// @tparam ParDict_t Type of parameter dictionary /// @tparam OptDict_t Type of options dictionary /// @tparam OutDict_t Type of output GRIB dictionary /// /// @return Always `nullptr` /// template static constexpr Fn variantCallbacks() { return nullptr; } /// /// @brief Entry-level matcher callback. /// /// This callback is invoked to determine whether the `dataType` /// concept should be activated for a given encoding request. /// /// @tparam MarsDict_t Type of MARS dictionary /// @tparam OptDict_t Type of options dictionary /// /// @return Matcher function pointer /// template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &dataTypeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeMatcher.h0000664000175000017500000000442115246725757027603 0ustar alastairalastair/* * (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 dataTypeMatcher.h /// @brief Entry-level matcher for the GRIB `dataType` concept. /// /// This header defines the runtime matcher used by the concept registry to /// activate the default data-type concept variant. /// /// The matcher follows the standard mars2grib matching contract: /// - return a local concept variant index when the concept is active, /// - return `compile_time_registry_engine::MISSING` when it is not active, /// - wrap runtime failures as nested `Mars2GribMatcherException` instances. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System include #include #include // Utils #include "metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Match the `dataType` concept variant. /// /// The data-type concept is always active and resolves to /// `DataTypeType::Default`. /// /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam OptDict_t Type of the options dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] opt Options dictionary /// /// @return Local variant index for `DataTypeType::Default`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribMatcherException /// If matcher evaluation fails. Lower-level exceptions are preserved through /// `std::throw_with_nested`. /// template std::size_t dataTypeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { try { return static_cast(DataTypeType::Default); } catch (...) { std::throw_with_nested( utils::exceptions::Mars2GribMatcherException("Unable to match `dataType` concept", Here())); } } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeEncoding.h0000664000175000017500000001604115246725757027747 0ustar alastairalastair/* * (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 dataTypeEncoding.h /// @brief Implementation of the GRIB `dataType` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **dataType concept** within the mars2grib backend. /// /// The concept is responsible for populating GRIB keys in the /// *Identification Section* related to the classification of the processed /// data product, namely: /// /// - `typeOfProcessedData` /// - `productionStatusOfProcessedData` /// /// The concept itself does not implement semantic deduction logic. Instead, /// it delegates: /// - semantic resolution to dedicated deduction functions /// - value validation and encoding correctness to GRIB tables /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `data_typeApplicable` /// - Runtime deduction via backend deductions /// - Strict error handling with contextual concept exceptions /// /// @note /// The namespace name `concepts_` is intentionally used instead of `concepts` /// to avoid ambiguity and potential conflicts with the C++20 `concept` language /// feature and related standard headers. /// /// This is a deliberate design choice and must not be changed. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Core concept includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/productionStatusOfProcessedData.h" #include "metkit/mars2grib/backend/deductions/typeOfProcessedData.h" // Tables #include "metkit/mars2grib/backend/tables/productionStatusOfProcessedData.h" #include "metkit/mars2grib/backend/tables/typeOfProcessedData.h" // Utils #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time applicability predicate for the `dataType` concept. /// /// This predicate determines whether the `dataType` concept is applicable /// for a given combination of: /// - encoding stage /// - GRIB section /// - concept variant /// /// Applicability is evaluated entirely at compile time and is used by the /// concept dispatcher to ensure that only valid concept instantiations occur. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Data type concept variant /// /// @return `true` if the concept is applicable for the given parameters, /// `false` otherwise. /// /// @note /// The default applicability rule enables the concept only when: /// - `Variant == DataTypeType::Default` /// - `Stage == StagePreset` /// - `Section == SecIdentificationSection` /// /// Users may override or specialize this predicate to alter applicability. /// template constexpr bool dataTypeApplicable() { return ((Variant == DataTypeType::Default) && (Stage == StageOverride) && (Section == SecIdentificationSection)); } /// /// @brief Execute the `dataType` concept operation. /// /// This function implements the runtime logic of the GRIB `dataType` concept. /// When applicable, it: /// /// 1. Deduces the `typeOfProcessedData` from MARS and parameter dictionaries. /// 2. Deduces the `productionStatusOfProcessedData` from MARS and parameter dictionaries. /// 3. Encodes both values into the GRIB Identification Section. /// /// The concept acts as a **pure orchestration layer**: /// - All semantic logic is delegated to deduction functions. /// - All value correctness is guaranteed by table-backed enumerations. /// /// If the concept is invoked when not applicable, a /// `Mars2GribConceptException` is thrown. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index (compile-time constant) /// @tparam Variant Data type concept variant /// @tparam MarsDict_t Type of the MARS input dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the GRIB output dictionary /// /// @param[in] mars MARS input dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary /// @param[out] out Output GRIB dictionary to be populated /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribConceptException /// If: /// - the concept is called when not applicable /// - a deduction fails /// - a GRIB key cannot be set /// /// @note /// - All runtime errors are wrapped with full concept context /// (concept name, variant, stage, section). /// - This concept does not perform validation beyond what is enforced /// by the deduction and table layers. /// /// @see dataTypeApplicable /// template void DataTypeOp(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (dataTypeApplicable()) { try { MARS2GRIB_LOG_CONCEPT(dataType); // Deductions tables::TypeOfProcessedData typeOfProcessedData = deductions::resolve_TypeOfProcessedData_or_throw(mars, par, opt); tables::ProductionStatusOfProcessedData productionStatusOfProcessedData = deductions::resolve_ProductionStatusOfProcessedData_or_throw(mars, par, opt); // Encoding // @todo -> set_or_throw(out, "typeOfProcessedData", // enum2name_TypeOfProcessedData_or_throw(typeOfProcessedData)); set_or_throw(out, "typeOfProcessedData", static_cast(typeOfProcessedData)); set_or_throw(out, "productionStatusOfProcessedData", static_cast(productionStatusOfProcessedData)); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(dataType, "Unable to set `dataType` concept..."); } return; } // Concept invoked outside its applicability domain MARS2GRIB_CONCEPT_THROW(dataType, "Concept called when not applicable..."); // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.20.2/src/metkit/mars2grib/backend/concepts/MatchingCallbacksRegistry.h0000664000175000017500000001732415246725757027725 0ustar alastairalastair/* * (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 MatchingCallbacksRegistry.h /// @brief Compile-time registry of entry-level matching callbacks. /// /// This header defines the **fully materialized matching dispatch table** /// used by the mars2grib backend to perform *semantic matching* of input /// dictionaries against the concept universe. /// /// ----------------------------------------------------------------------------- /// Conceptual model /// ----------------------------------------------------------------------------- /// /// Matching is modeled as a one-dimensional dispatch space: /// /// \code /// matchingCallbacks[conceptId] -> Fm | nullptr /// \endcode /// /// where: /// - `conceptId` is the compile-time concept identifier defined by /// `GeneralRegistry` / `EntryVariantRegistry`, /// - `Fm` is a dictionary-specialized matcher function, /// - `nullptr` indicates that the concept does not participate in matching /// for the selected capability. /// /// Each matcher is responsible for determining whether its corresponding /// concept is *active* for a given pair of input dictionaries. /// /// ----------------------------------------------------------------------------- /// Capability selection /// ----------------------------------------------------------------------------- /// /// This registry is parameterized by a compile-time **capability index** /// (here fixed to `0`). /// /// Capabilities allow the same concept universe to expose multiple independent /// matching planes (e.g. alternative semantics, validation modes, feature /// subsets) without duplicating registry infrastructure. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file is responsible only for: /// - binding the generic entry-level registry machinery /// (`makeEntryCallbacksRegistry`) to: /// - the complete concept universe (`AllConcepts`), /// - a specific capability index, /// - concrete dictionary types. /// /// It does **not**: /// - define any concept descriptors, /// - implement matching logic, /// - perform runtime dispatch or iteration, /// - introduce new compile-time algorithms. /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// `MatchingCallbacksRegistry` provides the **semantic entry point** to the /// resolution pipeline. /// /// Its output is typically consumed by: /// - active concept resolution, /// - semantic filtering of variants, /// - downstream structural resolution stages. /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Header-only /// - Fully constexpr /// - No runtime state /// - No dynamic allocation /// /// This header is safe to include transitively in performance-critical code. /// /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/makeEntryCallbacksRegistry.h" #include "metkit/mars2grib/backend/concepts/AllConcepts.h" #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { /// /// @brief Compile-time registry exposing entry-level matching callbacks. /// /// This class template materializes a complete table of matcher functions, /// one per concept, specialized for a fixed pair of dictionary types. /// /// @tparam MarsDict_t /// Type of the MARS request dictionary. /// /// @tparam OptDict_t /// Type of the options dictionary. /// /// ----------------------------------------------------------------------------- /// Lifetime and usage /// ----------------------------------------------------------------------------- /// /// All members of this registry are: /// - `static`, /// - `constexpr`, /// - immutable. /// /// No instances of this class are ever constructed. /// /// The registry is intended to be accessed as: /// /// \code /// MatchingCallbacksRegistry<...>::matchingCallbacks /// \endcode /// /// ----------------------------------------------------------------------------- /// Structural guarantees /// ----------------------------------------------------------------------------- /// /// The ordering of the callback table is strictly defined by: /// - the order of concepts in `detail::AllConcepts`. /// /// The index into this table is therefore stable and consistent with: /// - concept identifiers returned by `GeneralRegistry::conceptId()`, /// - all downstream registry layers. /// /// Any change to the concept list or its ordering constitutes a breaking /// structural change. /// template struct MatchingCallbacksRegistry { /// /// @brief Number of concepts exposed by the registry. /// /// This constant anchors the dependency on `GeneralRegistry` and /// defines the expected size of the matching callback table. /// static constexpr std::size_t registry_size = GeneralRegistry::NConcepts; /// /// @brief Canonical encoding function pointer type. /// /// This alias exposes the exact function signature used for all encoding /// callbacks stored in the registry. /// /// It is provided primarily for: /// - readability, /// - consistency with higher-level abstractions, /// - avoiding repetition of long qualified names. /// using Fm_t = metkit::mars2grib::backend::compile_time_registry_engine::Fm; /// /// @brief Fully materialized matching dispatch table. /// /// This static data member contains the complete entry-level matching /// registry for: /// - all concepts, /// - a single capability index (`0`), /// - the dictionary types bound to this registry. /// /// The table is generated entirely at compile time by invoking /// `makeEntryCallbacksRegistry` with: /// - the full concept universe (`AllConcepts`), /// - capability index `0`, /// - the dictionary types bound to this registry. /// /// Each entry is either: /// - a valid matcher function (`Fm`), or /// - `nullptr` if the concept does not participate in matching. /// /// This table is typically consumed by the *semantic resolution* layer /// to determine which concepts and variants are active for a given /// input request. /// static constexpr auto matchingCallbacks = metkit::mars2grib::backend::compile_time_registry_engine::makeEntryCallbacksRegistry(); /// /// @brief Compile-time structural verification. /// /// Ensures that the generated callback table size matches the number /// of concepts defined by `GeneralRegistry`. /// static_assert(matchingCallbacks.size() == registry_size, "MatchingCallbacksRegistry: callback table size does not match GeneralRegistry"); }; } // namespace metkit::mars2grib::backend::concepts_metkit-1.20.2/src/metkit/mars2grib/backend/models/0000775000175000017500000000000015246725757022127 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/0000775000175000017500000000000015246725757025473 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/0000775000175000017500000000000015246725757027125 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/domain.md0000664000175000017500000002071715246725757030725 0ustar alastairalastair # Domain Logic ## Purpose The domain layer resolves the absolute temporal support interval used by the rest of `ProductTimeSpec`. It produces one resolved `ProductTimeSpecDomain` artifact containing: - `domainStartDateTime` - `domainEndDateTime` - `isSynoptic` - `startOffsetHoursFromReference` - `endOffsetHoursFromReference` These values are derived from the normalized input, the resolved anchor, and the stage-1 outer time range. ## Folder Structure The current domain folder contains: - `DomainDataTypes.h` - `DomainRegistry.h` - `DomainUtils.h` - `impl/AnalysisDomain.h` - `impl/ForecastDomain.h` - `impl/FromStartForecastDomain.h` - `impl/SeasonalForecastDomain.h` - `impl/SynopticAnalysisDomain.h` ## Resolved Domain Artifact The resolved artifact type is: - `domain::ProductTimeSpecDomain` It stores: - the absolute support start datetime; - the absolute support end datetime; - whether the domain uses synoptic placement semantics; - the signed whole-hour offset from the anchor reference datetime to the real support start; - the signed whole-hour offset from the anchor reference datetime to the support end. ## Classification Flow The domain classification entry point is: - `domain::classify_Domain_or_throw(...)` It is implemented in: - `DomainRegistry.h` Behavior: 1. evaluate every registered matcher; 2. count how many matchers returned `true`; 3. succeed only when exactly one matcher matches; 4. otherwise throw a classification failure. Matcher order is not precedence. ## Builder Flow The domain builder dispatch entry point is: - `domain::build_Domain_or_throw(...)` It is implemented in: - `DomainRegistry.h` Behavior: 1. validate the classification enum value; 2. dispatch to the registered per-case builder; 3. return the resolved `ProductTimeSpecDomain`. ## Checker Flow The domain checker dispatch entry point is: - `domain::check_Domain_or_throw(...)` It is implemented in: - `DomainRegistry.h` Behavior: 1. validate the classification enum value; 2. dispatch to the registered per-case checker; 3. return `true` on success; 4. otherwise throw a check failure. The checker infrastructure is now present for all currently implemented domain cases. Top-level orchestration may still need to call the checker stage explicitly in a later integration phase. ## Shared Utility Layer `DomainUtils.h` contains low-level helpers used by multiple domain and shape cases. Current responsibilities include: - direct normalized non-seasonal lead extraction; - direct normalized seasonal lead extraction; - conversion of duration-valued `timespan` into seconds; - shared outer-range resolution; - conversion of two absolute datetimes into a signed whole-hour offset. The key low-level helper for the current raw domain model is: - `offsetHoursFromReference(...)` It converts the difference between two datetimes into a signed whole-hour offset, which is what the raw domain artifact stores for encoder-facing use. ## Current Cases ### AnalysisDomain File: - `impl/AnalysisDomain.h` Matcher: - `match_Analysis_Domain(...)` The case matches when: - the product is not synoptic; - the regime is not AIFS; - the simulation type is analysis. Builder: - `build_Analysis_Domain(...)` Construction rule: - `domainStartDateTime = anchor.referenceDateTime` - `domainEndDateTime = anchor.referenceDateTime + outerRange` - `isSynoptic = false` - offsets are measured from `anchor.referenceDateTime` Checker: - `check_Analysis_Domain(...)` The checker validates: - non-synoptic semantics; - support start equals anchor reference; - support start does not follow support end; - offsets match the resolved datetimes. ### ForecastDomain File: - `impl/ForecastDomain.h` Matcher: - `match_Forecast_Domain(...)` The case matches when: - the product is not synoptic; - the product is not seasonal; - the simulation type is forecast; - `timespan.kind` is not `FromStart`. Builder: - `build_Forecast_Domain(...)` Construction rule: - `domainEndDateTime = anchor.referenceDateTime + step` - `domainStartDateTime = domainEndDateTime - outerRange` - `isSynoptic = false` - offsets are measured from `anchor.referenceDateTime` Checker: - `check_Forecast_Domain(...)` The checker validates: - non-synoptic semantics; - support start does not follow support end; - support end does not precede anchor reference; - offsets match the resolved datetimes. ### FromStartForecastDomain File: - `impl/FromStartForecastDomain.h` Matcher: - `match_FromStartForecast_Domain(...)` The case matches when: - the product is not synoptic; - the product is not seasonal; - the simulation type is forecast; - `timespan.kind` is `FromStart`. Builder: - `build_FromStartForecast_Domain(...)` Construction rule: - the outer time range must remain deferred at the stage-1 interface; - `domainEndDateTime = anchor.referenceDateTime + step` - `domainStartDateTime = domainEndDateTime - step` - under the current semantics this resolves to: - `domainStartDateTime = anchor.referenceDateTime` - `isSynoptic = false` - offsets are measured from `anchor.referenceDateTime` Checker: - `check_FromStartForecast_Domain(...)` The checker validates: - non-synoptic semantics; - support start does not follow support end; - support start equals anchor reference; - offsets match the resolved datetimes. ### SeasonalForecastDomain File: - `impl/SeasonalForecastDomain.h` Matcher: - `match_SeasonalForecast_Domain(...)` The case matches when: - the product is not synoptic; - the normalized input is seasonal; - the simulation type is forecast. Builder: - `build_SeasonalForecast_Domain(...)` Construction rule: - validate seasonal semantics explicitly in the callback; - validate `fcmonth` presence explicitly in the callback; - validate `fcmonth > 0` explicitly in the callback; - build a month-valued lead as `TimeDuration{fcmonth, TimeUnit::Month}`; - `domainEndDateTime = anchor.referenceDateTime + forecastLead` - `domainStartDateTime = domainEndDateTime - outerRange` - `isSynoptic = false` - offsets are measured from `anchor.referenceDateTime` Checker: - `check_SeasonalForecast_Domain(...)` The checker validates: - non-synoptic semantics; - support start does not follow support end; - support end does not precede anchor reference; - offsets match the resolved datetimes. ### SynopticAnalysisDomain File: - `impl/SynopticAnalysisDomain.h` Matcher: - `match_SynopticAnalysis_Domain(...)` The case matches when: - the product is synoptic; - the regime is IFS; - the simulation type is analysis. Builder: - `build_SynopticAnalysis_Domain(...)` Construction rule: - `domainStartDateTime` preserves the exact MARS date/time and therefore keeps the synoptic hour in its time component; - the real support start used for hour offsets is the same date forced to `00:00:00`; - `domainEndDateTime` is the first instant of the following calendar month computed from that real support start; - `isSynoptic = true` - start offset is computed from the real midnight-based support start, not from the stored synoptic timestamp; - end offset is computed from the support end. Checker: - `check_SynopticAnalysis_Domain(...)` The checker validates: - synoptic semantics; - input MARS date presence; - start date agreement with the input MARS date; - support start does not follow support end; - support end equals the next calendar-month boundary computed from the real midnight-based support start; - offsets match the resolved real support start and support end. ## Diagnostic JSON `DomainDataTypes.h` provides: - `productTimeSpecDomainJson(...)` This function is: - best-effort; - `noexcept`; - intended only for diagnostic context construction. If serialization fails, it returns a stable fallback JSON error object instead of throwing. ## Current Limitations The domain checker infrastructure is present and the registry now exposes the checker dispatch entry point. The remaining major work after the callback rewrite is now the raw-to- normalized artifact layer and the later post-normalization cross checks. During normalization, the domain artifact remains unchanged and supplies the real placement used to normalize month-based shape ranges to hours. metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h0000664000175000017500000000716315246725757032333 0ustar alastairalastair/* * (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 DomainDataTypes.h /// @brief Domain artifact and resolved ProductTimeSpec domain state. /// /// This header owns: /// - the resolved `ProductTimeSpecDomain` artifact type; /// - the best-effort diagnostic JSON serializer for the resolved domain. /// #pragma once #include "eckit/types/DateTime.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain { /// /// @brief Absolute temporal support interval of one resolved ProductTimeSpec. /// /// The domain artifact stores the start and end datetimes of the product's /// resolved support together with the synoptic-placement flag and the signed /// whole-hour offsets from the anchor reference datetime. /// /// The start and end are absolute placements, not relative durations, and are /// already fully computed by the selected domain builder. /// struct ProductTimeSpecDomain { /// @brief Absolute start datetime of the resolved product support. eckit::DateTime domainStartDateTime{}; /// @brief Absolute end datetime of the resolved product support. eckit::DateTime domainEndDateTime{}; /// @brief Whether the domain uses synoptic placement semantics. bool isSynoptic{false}; /// @brief Signed whole-hour offset from the reference to the real support start. long startOffsetHoursFromReference{0}; /// @brief Signed whole-hour offset from the reference to the support end. long endOffsetHoursFromReference{0}; }; /// @brief Serialize one resolved domain artifact as diagnostic JSON. /// /// This function is best-effort and never throws. It is intended for /// diagnostic-context construction only and therefore returns a stable fallback /// error object if serialization fails. /// /// @param[in] value Resolved domain artifact. /// @return One JSON object describing the final domain state, or a stable /// fallback error object if serialization fails. inline std::string productTimeSpecDomainJson(const ProductTimeSpecDomain& value) noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("domainStartDateTime") << ':' << detail::productTimeSpecDateTimeJson(value.domainStartDateTime) << ',' << detail::jsonQuote_modelInput("domainEndDateTime") << ':' << detail::productTimeSpecDateTimeJson(value.domainEndDateTime) << ',' << detail::jsonQuote_modelInput("isSynoptic") << ':' << (value.isSynoptic ? "true" : "false") << ',' << detail::jsonQuote_modelInput("startOffsetHoursFromReference") << ':' << value.startOffsetHoursFromReference << ',' << detail::jsonQuote_modelInput("endOffsetHoursFromReference") << ':' << value.endOffsetHoursFromReference << '}'; return out.str(); } catch (...) { return std::string{"{\"error\":\"productTimeSpecDomainJson failed while building diagnostic context\"}"}; } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h0000664000175000017500000003147015246725757031533 0ustar alastairalastair/* * (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 DomainUtils.h /// @brief Shared normalized-value primitives used by domain and shape builders. /// /// This header contains low-level helpers used by multiple domain and shape /// cases. It owns: /// - primitive lead extraction helpers retained for the remaining organization /// layer; /// - extraction of duration-valued `timespan` as elapsed seconds; /// - shared outer-range resolution helpers; /// - conversion of two absolute datetimes into a signed whole-hour reference /// offset. /// /// It does not construct complete domain artifacts and does not select a domain /// classification. /// /// Every function catches all failures and rethrows `Mars2GribModelException` /// directly. Functions receiving input attach `input.to_json()`. /// /// @ingroup mars2grib_product_time_spec_detail /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/timespan.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ForecastLeadUtils.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /// /// @brief Retrieve the normalized non-seasonal forecast lead. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Resolved `step` duration. /// @throws Mars2GribModelException If the normalized `step` is missing. /// inline metkit::mars2grib::backend::deductions::TimeDuration resolvedForecastStep(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasResolvedStep = input.step.has_value(); if (!hasResolvedStep) { throw Mars2GribModelException("Non-seasonal forecast-domain construction requires a resolved step", input.to_json(), Here()); } return *input.step; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to retrieve the non-seasonal ProductTimeSpec forecast lead", input.to_json(), Here())); } } /// /// @brief Retrieve the normalized seasonal forecast lead expressed in calendar months. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Resolved `fcmonth` duration represented as `{length, Month}`. /// @throws Mars2GribModelException If `fcmonth` is missing or locally invalid. /// inline metkit::mars2grib::backend::deductions::TimeDuration resolvedSeasonalForecastLead( const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool seasonalInputIsPresent = hasSeasonalClassStream && hasSeasonalLeadSemantics; if (!seasonalInputIsPresent) { throw Mars2GribModelException( "Seasonal forecast-domain construction requires both seasonal class/stream and seasonal lead semantics", input.to_json(), Here()); } const long fcmonth = *input.marsFcmonth; if (fcmonth <= 0) { throw Mars2GribModelException("Seasonal forecast-domain construction requires a strictly positive fcmonth", input.to_json(), Here()); } return TimeDuration{fcmonth, TimeUnit::Month}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to retrieve the seasonal ProductTimeSpec forecast lead", input.to_json(), Here())); } } /// /// @brief Retrieve a duration-valued normalized timespan. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Timespan represented as seconds. /// @throws Mars2GribModelException If no duration value is available. /// inline metkit::mars2grib::backend::deductions::TimeDuration timespanDuration(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::Timespan; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { const auto timespanKind = input.timespan.kind; if (timespanKind != TimespanKind::Duration) { throw Mars2GribModelException("Timespan is not duration-valued", input.to_json(), Here()); } const bool hasDuration = input.timespan.duration.has_value(); if (!hasDuration) { throw Mars2GribModelException("Duration-valued timespan does not contain a duration", input.to_json(), Here()); } const auto duration = input.timespan.duration.value(); long timespanInSeconds = convertToSeconds(duration); return TimeDuration{timespanInSeconds, TimeUnit::Second}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to retrieve ProductTimeSpec timespan duration", input.to_json(), Here())); } } /// /// @brief Resolve the outer support range required by normal domain builders. /// /// Resolution follows source semantics before shape construction: /// - instant products use zero; /// - from-start products use the non-seasonal forecast lead; /// - synoptic products use one calendar month; /// - products with `stattype` blocks use the outermost block range; /// - remaining duration-valued products use `timespan`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Outer range used to place normal forecast or analysis domains. /// @throws Mars2GribModelException If no supported range source is available. /// inline metkit::mars2grib::backend::deductions::TimeDuration resolveOuterDomainRange(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::Timespan; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isInstant = input.innerMostTypeOfStatisticalProcessing == tables::TypeOfStatisticalProcessing::Missing; const bool isFromStart = input.timespan.kind == TimespanKind::FromStart; const bool isSynoptic = input.isSynoptic; const bool hasOuterStattypeBlock = !input.stattype.empty(); const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; if (isInstant) { return metkit::mars2grib::utils::time_arithmetic::zeroDuration(); } if (isFromStart) { return resolvedForecastStep(input); } if (isSynoptic) { return TimeDuration{1, TimeUnit::Month}; } if (hasOuterStattypeBlock) { return input.stattype.front().timeRange; } if (hasDurationTimespan) { return timespanDuration(input); } throw Mars2GribModelException("No outer domain range can be resolved from normalized input", input.to_json(), Here()); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to resolve ProductTimeSpec outer domain range", input.to_json(), Here())); } } /// /// @brief Resolve the outer support range required by the seasonal forecast domain builder. /// /// Resolution follows the same source semantics as the normal domain path, but /// keeps the seasonal from-start branch explicitly separate so future seasonal /// outer-range changes remain local to the seasonal forecast domain. /// /// Current seasonal resolution rules are: /// - instant products use zero; /// - from-start products use the seasonal forecast lead; /// - synoptic products use one calendar month; /// - products with `stattype` blocks use the outermost block range; /// - remaining duration-valued products use `timespan`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Outer range used to place the seasonal forecast domain. /// @throws Mars2GribModelException If no supported range source is available. /// inline metkit::mars2grib::backend::deductions::TimeDuration resolveSeasonalForecastOuterDomainRange( const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isInstant = input.innerMostTypeOfStatisticalProcessing == tables::TypeOfStatisticalProcessing::Missing; const bool isFromStart = input.timespan.kind == TimespanKind::FromStart; const bool isSynoptic = input.isSynoptic; const bool hasOuterStattypeBlock = !input.stattype.empty(); const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; if (isInstant) { return metkit::mars2grib::utils::time_arithmetic::zeroDuration(); } if (isFromStart) { return resolvedSeasonalForecastLead(input); } if (isSynoptic) { return TimeDuration{1, tables::TimeUnit::Month}; } if (hasOuterStattypeBlock) { return input.stattype.front().timeRange; } if (hasDurationTimespan) { return timespanDuration(input); } throw Mars2GribModelException("No seasonal forecast outer domain range can be resolved from normalized input", input.to_json(), Here()); } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to resolve the seasonal ProductTimeSpec outer domain range", input.to_json(), Here())); } } /// /// @brief Compute the signed whole-hour offset from one datetime to another. /// /// The result is positive when `targetDateTime` follows `referenceDateTime`, /// negative when it precedes it, and zero when both datetimes are equal. /// /// @param[in] referenceDateTime Datetime from which the offset is measured. /// @param[in] targetDateTime Datetime whose signed whole-hour offset is needed. /// @return Signed whole-hour offset from `referenceDateTime` to `targetDateTime`. /// @throws Mars2GribModelException If the elapsed duration is not an exact /// whole number of hours. /// inline long offsetHoursFromReference(const eckit::DateTime& referenceDateTime, const eckit::DateTime& targetDateTime) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { const bool targetFollowsReference = targetDateTime >= referenceDateTime; const auto elapsedDuration = targetFollowsReference ? durationBetween(referenceDateTime, targetDateTime) : durationBetween(targetDateTime, referenceDateTime); const long elapsedSeconds = convertToSeconds(elapsedDuration); if (elapsedSeconds % 3600L != 0) { throw Mars2GribModelException("Reference-relative offset is not an exact whole number of hours", Here()); } const long elapsedHours = elapsedSeconds / 3600L; if (targetFollowsReference) { return elapsedHours; } if (elapsedHours == std::numeric_limits::min()) { throw Mars2GribModelException("Negative whole-hour offset is out of range for a long", Here()); } return -elapsedHours; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to compute signed whole-hour offset from reference datetime", Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/DomainRegistry.h0000664000175000017500000002705415246725757032246 0ustar alastairalastair/* * (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 DomainRegistry.h /// @brief Register, classify, build, and check ProductTimeSpec domain cases. /// /// This header centralizes the case table and the public dispatch entry points /// for ProductTimeSpec domain handling. /// /// It owns: /// - the immutable registry row type used to keep matcher, builder, and checker /// callbacks aligned with their classification values; /// - the domain classification entry point; /// - the domain builder dispatch entry point; /// - the domain checker dispatch entry point. /// /// Every domain matcher is evaluated independently. Classification succeeds only /// when exactly one matcher returns `true`; matcher order never resolves overlap. /// Zero matches identify unsupported semantics, while multiple matches identify /// overlap in matcher boundaries. /// /// The active callback-selection matrix is: /// /// | `isSynoptic` | `regime` | `simulationType` | Selected domain callback | /// |--------------|----------|------------------|--------------------------| /// | `false` | any | `Forecast`, non-seasonal, non-from-start | `ForecastDomain` | /// | `false` | any | `Forecast`, non-seasonal, from-start | `FromStartForecastDomain` | /// | `false` | any | `Forecast`, seasonal | `SeasonalForecastDomain` | /// | `false` | not `AIFS` | `Analysis` | `AnalysisDomain` | /// | `true` | `IFS` | `Analysis` | `SynopticAnalysisDomain` | /// /// Unsupported combinations include, among others: /// - synoptic forecast products; /// - synoptic AIFS analysis products; /// - non-synoptic AIFS analysis products. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/impl/AnalysisDomain.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/impl/ForecastDomain.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/impl/FromStartForecastDomain.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/impl/SeasonalForecastDomain.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/impl/SynopticAnalysisDomain.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain { namespace detail { /// @brief Function-pointer type shared by all domain matchers. using DomainMatcher = bool (*)(const ProductTimeSpecInput&); /// /// @brief Function-pointer type shared by all domain builders. /// /// A domain builder receives normalized input, the resolved classification /// bundle, the already constructed anchor, and the stage-1 outer time range. It /// returns the complete raw absolute support interval. /// using DomainBuilder = ProductTimeSpecDomain (*)(const ProductTimeSpecInput&, const ProductTimeSpecClassification&, const anchor::ProductTimeSpecAnchor&, const shape::ProductTimeSpecOuterTimeRange&); /// /// @brief Function-pointer type shared by all domain check callbacks. using DomainChecker = bool (*)(const ProductTimeSpecInput&, const anchor::ProductTimeSpecAnchor&, const ProductTimeSpecDomain&); /// @brief Immutable registry row for one domain case. /// /// The row keeps the classification value, diagnostic name, matcher, builder, /// and checker together so that independent arrays cannot become misaligned. /// struct DomainCase { ProductTimeSpecDomainKind classification; std::string_view name; DomainMatcher matcher; DomainBuilder builder; DomainChecker checker; }; /// @brief Immutable domain registry ordered exactly like `ProductTimeSpecDomainKind`. inline constexpr std::array(ProductTimeSpecDomainKind::Count)> domainCases{{ {ProductTimeSpecDomainKind::ForecastDomain, "ForecastDomain", &match_Forecast_Domain, &build_Forecast_Domain, &check_Forecast_Domain}, {ProductTimeSpecDomainKind::FromStartForecastDomain, "FromStartForecastDomain", &match_FromStartForecast_Domain, &build_FromStartForecast_Domain, &check_FromStartForecast_Domain}, {ProductTimeSpecDomainKind::SeasonalForecastDomain, "SeasonalForecastDomain", &match_SeasonalForecast_Domain, &build_SeasonalForecast_Domain, &check_SeasonalForecast_Domain}, {ProductTimeSpecDomainKind::AnalysisDomain, "AnalysisDomain", &match_Analysis_Domain, &build_Analysis_Domain, &check_Analysis_Domain}, {ProductTimeSpecDomainKind::SynopticAnalysisDomain, "SynopticAnalysisDomain", &match_SynopticAnalysis_Domain, &build_SynopticAnalysis_Domain, &check_SynopticAnalysis_Domain}, }}; static_assert(static_cast(detail::domainCases[0].classification) == static_cast(ProductTimeSpecDomainKind::ForecastDomain)); static_assert(static_cast(detail::domainCases[1].classification) == static_cast(ProductTimeSpecDomainKind::FromStartForecastDomain)); static_assert(static_cast(detail::domainCases[2].classification) == static_cast(ProductTimeSpecDomainKind::SeasonalForecastDomain)); static_assert(static_cast(detail::domainCases[3].classification) == static_cast(ProductTimeSpecDomainKind::AnalysisDomain)); static_assert(static_cast(detail::domainCases[4].classification) == static_cast(ProductTimeSpecDomainKind::SynopticAnalysisDomain)); } // namespace detail /// /// @brief Classify the normalized input against every registered domain case. /// /// @section Domain classification contract /// - Reads: normalized synoptic, regime, simulation-type, and timespan facts. /// - Evaluates: every matcher in `domainCases`. /// - Success: exactly one matcher returns `true`. /// - Failure: zero or multiple matchers return `true`. /// - Side effects: none. /// /// @param[in] input /// Fully normalized ProductTimeSpec input. /// /// @return /// Unique matching `ProductTimeSpecDomainKind` value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// If matcher evaluation fails or classification is not unique. /// inline ProductTimeSpecDomainKind classify_Domain_or_throw(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { std::array matches{}; std::size_t numberOfMatches = 0; std::size_t matchedIndex = 0; for (std::size_t i = 0; i < detail::domainCases.size(); ++i) { matches[i] = detail::domainCases[i].matcher(input); if (matches[i]) { ++numberOfMatches; matchedIndex = i; } } if (numberOfMatches != 1) { throw Mars2GribModelException("ProductTimeSpec domain classification is not unique or is unsupported (" + std::to_string(numberOfMatches) + " matches)", input.to_json(), Here()); } return detail::domainCases[matchedIndex].classification; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to classify the ProductTimeSpec domain", input.to_json(), Here())); } } /// /// @brief Dispatch the builder associated with a validated domain classification. /// /// @param[in] classification /// Unique domain classification returned by `classify_Domain_or_throw`. /// /// @param[in] input /// Fully normalized ProductTimeSpec input. /// /// @param[in] fullClassification Full resolved ProductTimeSpec classification bundle. /// @param[in] anchor /// Complete anchor constructed before domain construction. /// @param[in] outerTimeRange /// Stage-1 outer time range constructed before domain construction. /// /// @return /// Absolute ProductTimeSpec support interval. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// If the classification is invalid or the selected builder fails. /// inline ProductTimeSpecDomain build_Domain_or_throw(ProductTimeSpecDomainKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecClassification& fullClassification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::domainCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid ProductTimeSpecDomainKind value", input.to_json(), Here()); } return detail::domainCases[index].builder(input, fullClassification, anchor, outerTimeRange); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build the ProductTimeSpec domain", input.to_json(), Here())); } } /// /// @brief Dispatch the checker associated with a validated domain classification. /// /// @param[in] classification /// Unique domain classification returned by `classify_Domain_or_throw`. /// /// @param[in] input /// Fully normalized ProductTimeSpec input. /// /// @param[in] anchor /// Complete anchor constructed before domain validation. /// /// @param[in] domain /// Complete domain artifact produced by the selected domain builder. /// /// @return /// `true` when the selected checker validates the domain successfully. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// If the classification is invalid or the selected checker fails. /// inline bool check_Domain_or_throw(ProductTimeSpecDomainKind classification, const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::domainCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid ProductTimeSpecDomainKind value", input.to_json(), Here()); } return detail::domainCases[index].checker(input, anchor, domain); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to check the ProductTimeSpec domain", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/0000775000175000017500000000000015246725757030066 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/AnalysisDomain.h0000664000175000017500000002256415246725757033163 0ustar alastairalastair/* * (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 AnalysisDomain.h /// @brief Matcher, builder, and checker for the normal analysis domain. /// /// This header is the authoritative implementation of the `AnalysisDomain` /// domain case. It keeps recognition, construction, and validation together so /// that the complete case can be reviewed without following a dispatch chain. /// /// The matcher identifies the absolute-domain semantics. The builder constructs /// all raw domain members directly from the resolved anchor and outer range. /// The checker validates that the resolved domain remains consistent with both /// the case semantics and the originating normalized input. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /** * @brief Return true only when input matches the normal analysis domain. * * - the product is not synoptic; * - AIFS analysis is forbidden; * - MARS semantics classify the product as analysis. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the domain matcher fails unexpectedly. */ inline bool match_Analysis_Domain(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isNotSynoptic = !input.isSynoptic; const bool isNotAifs = input.regime != SimulationRegime::AIFS; const bool isAnalysis = input.simulationType == SimulationType::Analysis; return isNotSynoptic && isNotAifs && isAnalysis; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_Analysis_Domain`", input.to_json(), Here())); } } /** * @brief Construct the raw analysis domain from the resolved anchor and outer range. * * In this case: * - the real support start is the anchor reference datetime; * - the real support end is the reference datetime plus the resolved outer * range; * - the domain is not synoptic; * - the hour offsets are measured from the anchor reference datetime. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @return Constructed ProductTimeSpec domain for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecDomain build_Analysis_Domain(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::addDuration; try { (void)classification; const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AnalysisDomain requires an available outer time range", input.to_json(), Here()); } // In the non-synoptic analysis case, the support starts exactly at the // reference datetime because the analysis anchor already resolves the // effective reference instant. const auto domainStartDateTime = anchor.referenceDateTime; // The support end is the reference datetime extended forward by the // resolved outer range. const auto outerRange = *outerTimeRange.timeRange; const auto domainEndDateTime = addDuration(anchor.referenceDateTime, outerRange); // This domain case is never synoptic. const bool isSynoptic = false; // The start offset is measured from the reference datetime to the real // support start. In this case the support starts at the reference, so // the offset should resolve to zero. const long startOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainStartDateTime); // The end offset is measured from the reference datetime to the support // end and carries the encoder-facing duration placement information. const long endOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainEndDateTime); return ProductTimeSpecDomain{domainStartDateTime, domainEndDateTime, isSynoptic, startOffsetHoursFromReference, endOffsetHoursFromReference}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_Analysis_Domain`", input.to_json(), Here())); } } /** * @brief Validate one resolved AnalysisDomain against its source input and anchor. * * This checker verifies: * - the domain is not synoptic; * - the support starts at the anchor reference datetime; * - the recorded hour offsets agree with the resolved start and end datetimes; * - the support start does not follow the support end. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] domain Resolved domain artifact produced by the builder. * @return `true` when the domain is valid for the AnalysisDomain case. * @throws Mars2GribModelException if the resolved domain is inconsistent with * the input, anchor, or case semantics. */ inline bool check_Analysis_Domain(const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { if (domain.isSynoptic) { throw Mars2GribModelException("AnalysisDomain must not be synoptic", input.to_json(), Here()); } if (domain.domainStartDateTime != anchor.referenceDateTime) { throw Mars2GribModelException("AnalysisDomain start must equal anchor reference datetime", input.to_json(), Here()); } if (domain.domainStartDateTime > domain.domainEndDateTime) { throw Mars2GribModelException("AnalysisDomain start must not follow domain end", input.to_json(), Here()); } if (domain.startOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainStartDateTime)) { throw Mars2GribModelException("AnalysisDomain start offset does not match resolved datetime placement", input.to_json(), Here()); } if (domain.endOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainEndDateTime)) { throw Mars2GribModelException("AnalysisDomain end offset does not match resolved datetime placement", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_Analysis_Domain`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/ForecastDomain.h0000664000175000017500000002427315246725757033145 0ustar alastairalastair/* * (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 ForecastDomain.h /// @brief Matcher, builder, and checker for the normal non-seasonal forecast domain. /// /// This header is the authoritative implementation of the `ForecastDomain` /// domain case. It keeps recognition, construction, and validation together so /// that the complete case can be reviewed without following a dispatch chain. /// /// The matcher identifies the absolute-domain semantics. The builder constructs /// all raw domain members directly from the resolved anchor and outer range. The /// checker validates that the resolved domain remains consistent with both the /// case semantics and the originating normalized input. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ForecastLeadUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /** * @brief Return true only when input matches the normal non-seasonal forecast domain. * * - the product is not synoptic; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - MARS semantics classify the product as forecast. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the domain matcher fails unexpectedly. */ inline bool match_Forecast_Domain(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isNotSynoptic = !input.isSynoptic; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool isNotFromStart = input.timespan.kind != TimespanKind::FromStart; return isNotSynoptic && isNotSeasonal && isForecast && isNotFromStart; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_Forecast_Domain`", input.to_json(), Here())); } } /** * @brief Construct the raw forecast domain from the resolved anchor and outer range. * * In this case: * - the real support end is the anchor reference datetime plus the non-seasonal * forecast lead; * - the real support start is the support end minus the resolved outer range; * - the domain is not synoptic; * - the hour offsets are measured from the anchor reference datetime. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @return Constructed ProductTimeSpec domain for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecDomain build_Forecast_Domain(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::addDuration; using metkit::mars2grib::utils::time_arithmetic::subtractDuration; try { (void)classification; const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("ForecastDomain requires an available outer time range", input.to_json(), Here()); } if (!input.step.has_value()) { throw Mars2GribModelException("ForecastDomain construction requires a resolved step", input.to_json(), Here()); } // The support end is the anchor reference datetime extended by the // resolved non-seasonal forecast lead. const auto forecastLead = *input.step; const auto domainEndDateTime = addDuration(anchor.referenceDateTime, forecastLead); // The support start is the support end shifted backward by the resolved // outer range. const auto outerRange = *outerTimeRange.timeRange; const auto domainStartDateTime = subtractDuration(domainEndDateTime, outerRange); // This domain case is never synoptic. const bool isSynoptic = false; // The start offset is measured from the reference datetime to the real // support start. const long startOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainStartDateTime); // The end offset is measured from the reference datetime to the real // support end. const long endOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainEndDateTime); return ProductTimeSpecDomain{domainStartDateTime, domainEndDateTime, isSynoptic, startOffsetHoursFromReference, endOffsetHoursFromReference}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_Forecast_Domain`", input.to_json(), Here())); } } /** * @brief Validate one resolved ForecastDomain against its source input and anchor. * * This checker verifies: * - the domain is not synoptic; * - the support start does not follow the support end; * - the support end does not precede the anchor reference datetime; * - the recorded hour offsets agree with the resolved start and end datetimes. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] domain Resolved domain artifact produced by the builder. * @return `true` when the domain is valid for the ForecastDomain case. * @throws Mars2GribModelException if the resolved domain is inconsistent with * the input, anchor, or case semantics. */ inline bool check_Forecast_Domain(const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { if (domain.isSynoptic) { throw Mars2GribModelException("ForecastDomain must not be synoptic", input.to_json(), Here()); } if (domain.domainStartDateTime > domain.domainEndDateTime) { throw Mars2GribModelException("ForecastDomain start must not follow domain end", input.to_json(), Here()); } if (domain.domainEndDateTime < anchor.referenceDateTime) { throw Mars2GribModelException("ForecastDomain end must not precede anchor reference datetime", input.to_json(), Here()); } if (domain.startOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainStartDateTime)) { throw Mars2GribModelException("ForecastDomain start offset does not match resolved datetime placement", input.to_json(), Here()); } if (domain.endOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainEndDateTime)) { throw Mars2GribModelException("ForecastDomain end offset does not match resolved datetime placement", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_Forecast_Domain`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/SynopticAnalysisDomain.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/SynopticAnalysisDom0000664000175000017500000002750515246725757033776 0ustar alastairalastair/* * (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 SynopticAnalysisDomain.h /// @brief Matcher, builder, and checker for the synoptic analysis domain. /// /// This header is the authoritative implementation of the /// `SynopticAnalysisDomain` domain case. It keeps recognition, construction, /// and validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher identifies the absolute-domain semantics. The builder constructs /// all raw domain members directly from the resolved anchor and the synoptic /// source datetime, while preserving the distinction between the stored /// synoptic timestamp and the real support start at midnight. The checker /// validates that the resolved domain remains consistent with both the case /// semantics and the originating normalized input. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /** * @brief Return true only when input matches the synoptic analysis domain. * * - the MARS product is synoptic; * - the regime is IFS; * - the product is an analysis. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the domain matcher fails unexpectedly. */ inline bool match_SynopticAnalysis_Domain(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isSynoptic = input.isSynoptic; const bool isIfs = input.regime == SimulationRegime::IFS; const bool isAnalysis = input.simulationType == SimulationType::Analysis; return isSynoptic && isIfs && isAnalysis; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_SynopticAnalysis_Domain`", input.to_json(), Here())); } } /** * @brief Construct the raw synoptic analysis domain. * * In this case: * - the stored `domainStartDateTime` preserves the exact MARS date/time and * therefore retains the synoptic hour; * - the real support start used for offsets is the same date forced to * `00:00:00`; * - the support end is the first instant of the following calendar month * computed from that real midnight-based support start; * - the domain is synoptic; * - the hour offsets are measured from the anchor reference datetime to the * real support start and support end. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @return Constructed ProductTimeSpec domain for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecDomain build_SynopticAnalysis_Domain(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::beginningOfNextCalendarMonth; using metkit::mars2grib::utils::time_arithmetic::defaultMarsTime; using metkit::mars2grib::utils::time_arithmetic::makeDateTime; try { (void)classification; (void)anchor; const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SynopticAnalysisDomain requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedOuterTimeRange{1, TimeUnit::Month}; if (outerTimeRange.timeRange->length != expectedOuterTimeRange.length || outerTimeRange.timeRange->unit != expectedOuterTimeRange.unit) { throw Mars2GribModelException("SynopticAnalysisDomain requires a one-month outer time range", input.to_json(), Here()); } if (!input.marsDate.has_value()) { throw Mars2GribModelException("Synoptic analysis domain requires an explicit MARS date", input.to_json(), Here()); } // The stored domain start preserves the exact MARS date/time and // therefore carries the synoptic hour in its time component. const auto domainStartDateTime = makeDateTime(*input.marsDate, input.marsTime); // The real support start used for support placement and hour offsets is // the same date forced to midnight. const auto realDomainStartDateTime = makeDateTime(domainStartDateTime.date(), defaultMarsTime()); // The support end is the first instant of the following calendar month // computed from the real midnight-based support start. const auto domainEndDateTime = beginningOfNextCalendarMonth(realDomainStartDateTime); // This domain case is explicitly synoptic. const bool isSynoptic = true; // The start offset is measured from the reference datetime to the real // support start, not to the stored synoptic timestamp. const long startOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, realDomainStartDateTime); // The end offset is measured from the reference datetime to the real // support end. const long endOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainEndDateTime); return ProductTimeSpecDomain{domainStartDateTime, domainEndDateTime, isSynoptic, startOffsetHoursFromReference, endOffsetHoursFromReference}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_SynopticAnalysis_Domain`", input.to_json(), Here())); } } /** * @brief Validate one resolved SynopticAnalysisDomain against its source input and anchor. * * This checker verifies: * - the domain is synoptic; * - the stored domain start date matches the input MARS date; * - the support start does not follow the support end; * - the support end is the next calendar-month boundary computed from the real * midnight-based support start; * - the recorded hour offsets agree with the resolved real support start and * support end. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] domain Resolved domain artifact produced by the builder. * @return `true` when the domain is valid for the SynopticAnalysisDomain case. * @throws Mars2GribModelException if the resolved domain is inconsistent with * the input, anchor, or case semantics. */ inline bool check_SynopticAnalysis_Domain(const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::beginningOfNextCalendarMonth; using metkit::mars2grib::utils::time_arithmetic::defaultMarsTime; using metkit::mars2grib::utils::time_arithmetic::makeDateTime; try { if (!domain.isSynoptic) { throw Mars2GribModelException("SynopticAnalysisDomain must be synoptic", input.to_json(), Here()); } if (!input.marsDate.has_value()) { throw Mars2GribModelException("Input missing MARS date for SynopticAnalysisDomain", input.to_json(), Here()); } if (domain.domainStartDateTime.date() != *input.marsDate) { throw Mars2GribModelException("SynopticAnalysisDomain start date does not match input MARS date", input.to_json(), Here()); } if (domain.domainStartDateTime > domain.domainEndDateTime) { throw Mars2GribModelException("SynopticAnalysisDomain start must not follow domain end", input.to_json(), Here()); } const eckit::DateTime realDomainStartDateTime = makeDateTime(domain.domainStartDateTime.date(), defaultMarsTime()); if (domain.domainEndDateTime != beginningOfNextCalendarMonth(realDomainStartDateTime)) { throw Mars2GribModelException("SynopticAnalysisDomain end does not match the next calendar-month boundary", input.to_json(), Here()); } if (domain.startOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, realDomainStartDateTime)) { throw Mars2GribModelException( "SynopticAnalysisDomain start offset does not match resolved datetime placement", input.to_json(), Here()); } if (domain.endOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainEndDateTime)) { throw Mars2GribModelException( "SynopticAnalysisDomain end offset does not match resolved datetime placement", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_SynopticAnalysis_Domain`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/SeasonalForecastDomain.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/SeasonalForecastDom0000664000175000017500000002654715246725757033723 0ustar alastairalastair/* * (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 SeasonalForecastDomain.h /// @brief Matcher, builder, and checker for the seasonal forecast domain. /// /// This header is the authoritative implementation of the /// `SeasonalForecastDomain` domain case. It keeps recognition, construction, /// and validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher identifies the absolute-domain semantics. The builder constructs /// all raw domain members directly from the resolved anchor, the resolved outer /// range, and the month-based `fcmonth` lead semantics. The checker validates /// that the resolved domain remains consistent with both the case semantics and /// the originating normalized input. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ForecastLeadUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /** * @brief Return true only when input matches the seasonal forecast domain. * * - the product is not synoptic; * - the product satisfies both the seasonal class/stream discriminator and the * seasonal lead discriminator; * - MARS semantics classify the product as forecast. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the domain matcher fails unexpectedly. */ inline bool match_SeasonalForecast_Domain(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isNotSynoptic = !input.isSynoptic; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isSeasonalProduct = hasSeasonalClassStream && hasSeasonalLeadSemantics; const bool isForecast = input.simulationType == SimulationType::Forecast; return isNotSynoptic && isSeasonalProduct && isForecast; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_SeasonalForecast_Domain`", input.to_json(), Here())); } } /** * @brief Construct the raw seasonal forecast domain. * * In this case: * - the real support end is the anchor reference datetime plus the seasonal * forecast lead derived from `fcmonth` in calendar months; * - the real support start is the support end minus the resolved outer range; * - the domain is not synoptic; * - the hour offsets are measured from the anchor reference datetime. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @return Constructed ProductTimeSpec domain for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecDomain build_SeasonalForecast_Domain(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::addDuration; using metkit::mars2grib::utils::time_arithmetic::subtractDuration; try { (void)classification; const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SeasonalForecastDomain requires an available outer time range", input.to_json(), Here()); } const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isSeasonalProduct = hasSeasonalClassStream && hasSeasonalLeadSemantics; if (!isSeasonalProduct) { throw Mars2GribModelException( "SeasonalForecastDomain construction requires both seasonal class/stream and seasonal lead semantics", input.to_json(), Here()); } if (!input.marsFcmonth.has_value()) { throw Mars2GribModelException("SeasonalForecastDomain construction requires fcmonth", input.to_json(), Here()); } const long fcmonth = *input.marsFcmonth; if (fcmonth <= 0) { throw Mars2GribModelException("SeasonalForecastDomain construction requires a strictly positive fcmonth", input.to_json(), Here()); } // The support end is the anchor reference datetime extended by the // resolved seasonal forecast lead expressed in calendar months. const TimeDuration forecastLead{fcmonth, TimeUnit::Month}; const auto domainEndDateTime = addDuration(anchor.referenceDateTime, forecastLead); // The support start is the support end shifted backward by the resolved // outer range. const auto outerRange = *outerTimeRange.timeRange; const auto domainStartDateTime = subtractDuration(domainEndDateTime, outerRange); // This domain case is never synoptic. const bool isSynoptic = false; // The start offset is measured from the reference datetime to the real // support start. const long startOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainStartDateTime); // The end offset is measured from the reference datetime to the real // support end. const long endOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainEndDateTime); return ProductTimeSpecDomain{domainStartDateTime, domainEndDateTime, isSynoptic, startOffsetHoursFromReference, endOffsetHoursFromReference}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_SeasonalForecast_Domain`", input.to_json(), Here())); } } /** * @brief Validate one resolved SeasonalForecastDomain against its source input and anchor. * * This checker verifies: * - the domain is not synoptic; * - the support start does not follow the support end; * - the support end does not precede the anchor reference datetime; * - the recorded hour offsets agree with the resolved start and end datetimes. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] domain Resolved domain artifact produced by the builder. * @return `true` when the domain is valid for the SeasonalForecastDomain case. * @throws Mars2GribModelException if the resolved domain is inconsistent with * the input, anchor, or case semantics. */ inline bool check_SeasonalForecast_Domain(const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { if (domain.isSynoptic) { throw Mars2GribModelException("SeasonalForecastDomain must not be synoptic", input.to_json(), Here()); } if (domain.domainStartDateTime > domain.domainEndDateTime) { throw Mars2GribModelException("SeasonalForecastDomain start must not follow domain end", input.to_json(), Here()); } if (domain.domainEndDateTime < anchor.referenceDateTime) { throw Mars2GribModelException("SeasonalForecastDomain end must not precede anchor reference datetime", input.to_json(), Here()); } if (domain.startOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainStartDateTime)) { throw Mars2GribModelException( "SeasonalForecastDomain start offset does not match resolved datetime placement", input.to_json(), Here()); } if (domain.endOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainEndDateTime)) { throw Mars2GribModelException( "SeasonalForecastDomain end offset does not match resolved datetime placement", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_SeasonalForecast_Domain`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/FromStartForecastDomain.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/domains/impl/FromStartForecastDo0000664000175000017500000002515715246725757033716 0ustar alastairalastair/* * (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 FromStartForecastDomain.h /// @brief Matcher, builder, and checker for the non-seasonal from-start forecast domain. /// /// This header is the authoritative implementation of the /// `FromStartForecastDomain` domain case. It keeps recognition, construction, /// and validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher identifies the absolute-domain semantics. The builder constructs /// all raw domain members directly from the resolved anchor and the from-start /// forecast lead, while preserving the deferred outer-range contract used by the /// later shape stage. The checker validates that the resolved domain remains /// consistent with both the case semantics and the originating normalized input. /// /// @ingroup mars2grib_product_time_spec_domains /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ForecastLeadUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail { /** * @brief Return true only when input matches the non-seasonal from-start forecast domain. * * The case matches when: * - the product is not synoptic; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the simulation type is forecast; * - the source `timespan` uses from-start semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the domain matcher fails unexpectedly. */ inline bool match_FromStartForecast_Domain(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isNotSynoptic = !input.isSynoptic; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; return isNotSynoptic && isNotSeasonal && isForecast && usesFromStartTimespan; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_FromStartForecast_Domain`", input.to_json(), Here())); } } /** * @brief Construct the raw from-start forecast domain. * * In this case: * - the outer time range must remain deferred for the later shape stage; * - the real support end is the anchor reference datetime plus the resolved * non-seasonal forecast lead; * - the real support start is the support end minus that same forecast lead; * - therefore the real support start resolves back to the anchor reference * datetime; * - the domain is not synoptic; * - the hour offsets are measured from the anchor reference datetime. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @return Constructed ProductTimeSpec domain for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecDomain build_FromStartForecast_Domain( const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification, const anchor::ProductTimeSpecAnchor& anchor, const shape::ProductTimeSpecOuterTimeRange& outerTimeRange) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::addDuration; using metkit::mars2grib::utils::time_arithmetic::subtractDuration; try { (void)classification; const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("FromStartForecastDomain requires a deferred outer time range", input.to_json(), Here()); } if (!input.step.has_value()) { throw Mars2GribModelException("FromStartForecastDomain construction requires a resolved step", input.to_json(), Here()); } // The support end is the anchor reference datetime extended by the // resolved non-seasonal forecast lead. const auto forecastLead = *input.step; const auto domainEndDateTime = addDuration(anchor.referenceDateTime, forecastLead); // In the current from-start forecast semantics, the support start is // reconstructed by subtracting the same lead from the support end. const auto outerRange = *input.step; const auto domainStartDateTime = subtractDuration(domainEndDateTime, outerRange); // This domain case is never synoptic. const bool isSynoptic = false; // The start offset is measured from the reference datetime to the real // support start. For the current from-start semantics, this should // resolve back to zero. const long startOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainStartDateTime); // The end offset is measured from the reference datetime to the real // support end. const long endOffsetHoursFromReference = offsetHoursFromReference(anchor.referenceDateTime, domainEndDateTime); return ProductTimeSpecDomain{domainStartDateTime, domainEndDateTime, isSynoptic, startOffsetHoursFromReference, endOffsetHoursFromReference}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_FromStartForecast_Domain`", input.to_json(), Here())); } } /** * @brief Validate one resolved FromStartForecastDomain against its source input and anchor. * * This checker verifies: * - the domain is not synoptic; * - the support start does not follow the support end; * - the support start resolves to the anchor reference datetime under the * current from-start semantics; * - the recorded hour offsets agree with the resolved start and end datetimes. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] domain Resolved domain artifact produced by the builder. * @return `true` when the domain is valid for the FromStartForecastDomain case. * @throws Mars2GribModelException if the resolved domain is inconsistent with * the input, anchor, or case semantics. */ inline bool check_FromStartForecast_Domain(const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::domain::detail::offsetHoursFromReference; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { if (domain.isSynoptic) { throw Mars2GribModelException("FromStartForecastDomain must not be synoptic", input.to_json(), Here()); } if (domain.domainStartDateTime > domain.domainEndDateTime) { throw Mars2GribModelException("FromStartForecastDomain start must not follow domain end", input.to_json(), Here()); } if (domain.domainStartDateTime != anchor.referenceDateTime) { throw Mars2GribModelException("FromStartForecastDomain start must equal anchor reference datetime", input.to_json(), Here()); } if (domain.startOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainStartDateTime)) { throw Mars2GribModelException( "FromStartForecastDomain start offset does not match resolved datetime placement", input.to_json(), Here()); } if (domain.endOffsetHoursFromReference != offsetHoursFromReference(anchor.referenceDateTime, domain.domainEndDateTime)) { throw Mars2GribModelException( "FromStartForecastDomain end offset does not match resolved datetime placement", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_FromStartForecast_Domain`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::domain::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/Plan.md0000664000175000017500000005557015246725757026723 0ustar alastairalastair # ProductTimeSpec — Check-Wiring & Matcher-Diagnostics Plan > **Cold-start note.** This file is written to be self-contained. A fresh session > should be able to resume from this file alone. Read this whole document first, > then the "Files to read before editing" list, then begin at > **Execution Order**. Do NOT start editing before reading the referenced files; > line numbers below were accurate at authoring time but MUST be re-confirmed > since the code may have shifted. --- ## 0. Session context (how we got here) This subsystem lives under: `src/metkit/mars2grib/backend/models/product-time-spec/` Two pieces of prior work are already **complete and committed to the working tree** (do not redo): 1. **`enableOptions.h` migration (DONE).** All boolean model-policy helpers (`checksEnabled`, `skipSection3`, `bitsPerValueCompressionEnabled`, `normalizeMarsEnabled`, `normalizeMiscEnabled`, `fixMarsGridEnabled`, plus dead `overrideEnabled`) were replaced by direct `get_or_throw(opt, "")` trait calls. The header `src/metkit/mars2grib/utils/enableOptions.h` was DELETED and no longer exists. Rule going forward: use strict `get_or_throw(opt, "")`; never `.value_or(false)`. If a default is ever needed, add it inside the traits. 2. **Read-only review of `product-time-spec` (DONE).** Findings are captured in section 2 below. This Plan.md is the output of that review. **Hard constraints for the executing agent:** - The USER builds, runs, debugs, and commits. Do NOT build, run, or commit. - Make minimal, surgical changes; touch only what each phase specifies. - Every nontrivial function body is wrapped in `try { ... } catch (...) { std::throw_with_nested(Mars2GribModelException(msg, input.to_json(), Here())); }`. Input-free helpers use the location-only exception constructor. - Diagnostic `to_json()` serializers are `noexcept` with a stable fallback string. Preserve this. - Companion docs in the same directory: `Requirements.md` (authoritative task tree), `Status.md` (live status — currently STALE, see Phase 0), `Constraints.md` (hard constraints). --- ## 1. Subsystem architecture (so a fresh session need not re-read everything) **Top-level model:** `ProductTimeSpec.h` exposes two immutable classes: - `ProductTimeAnchorSpec` — anchor-only reduced model. - `ProductTimeSpec` — full model. Both use **staged immutable construction**: a public templated ctor takes `(mars, par, opt)` dicts, builds a normalized `ProductTimeSpecInput`, then a private static `build_*Components_or_throw(input)` runs the pipeline and returns a component bundle consumed by the final private ctor that initializes `const` members. **Full pipeline** — `ProductTimeSpec.h`, `build_ProductTimeSpecComponents_or_throw` (≈:366-405): 1. `anchor::classify_Anchor_or_throw(input)` -> `anchorType` 2. `domain::classify_Domain_or_throw(input)` -> `domainType` 3. `shape::classify_Shape_or_throw(input)` -> `shapeType` 4. assemble `ProductTimeSpecClassification` bundle 5. `anchor::build_Anchor_or_throw(anchorType, input, classification)` -> `anchor` 6. `shape::build_ShapeOuterTimeRange_or_throw(shapeType, input, classification)` -> `outerTimeRange` 7. `domain::build_Domain_or_throw(domainType, input, classification, anchor, outerTimeRange)` -> `domain` 8. `shape::build_ShapeWindows_or_throw(shapeType, input, classification, anchor, outerTimeRange, domain)` -> `rawWindows` 9. `detail::normalizeShape_or_throw(input, domain, rawWindows)` -> `normalisedWindows` (already wired, ≈:387-388) 10. bundle + freeze. **Registries** (`anchors/AnchorRegistry.h`, `domains/DomainRegistry.h`, `shapes/ShapeRegistry.h`): each defines an immutable `std::array` of case rows, one per enum value, each row = `{classification, name, matcher, builder(s), checker}`, with `static_assert`s locking array index to enum value. Each exposes: - `classify_*_or_throw` — evaluates EVERY matcher, requires EXACTLY one `true`; 0 or >1 is a hard `Mars2GribModelException`. Non-prioritized, order never resolves overlap. - `build_*_or_throw` — index-dispatches the builder. - `check_*_or_throw` — index-dispatches the checker. **Currently defined but NEVER CALLED anywhere.** This is the core gap Workstream A fixes. **Registry dispatcher signatures (verified — do not guess):** - `anchor::check_Anchor_or_throw(ProductTimeSpecAnchorKind, const ProductTimeSpecInput&, const anchor::ProductTimeSpecAnchor&) -> bool` (`AnchorRegistry.h:198`) - `domain::check_Domain_or_throw(ProductTimeSpecDomainKind, const ProductTimeSpecInput&, const anchor::ProductTimeSpecAnchor&, const domain::ProductTimeSpecDomain&) -> bool` (`DomainRegistry.h:239`) - `shape::check_Shape_or_throw(ProductTimeSpecShapeKind, const ProductTimeSpecInput&, const ProductTimeSpecClassification&, const anchor::ProductTimeSpecAnchor&, const ProductTimeSpecOuterTimeRange&, const domain::ProductTimeSpecDomain&, const ProductTimeSpecShape&) -> bool` (`ShapeRegistry.h:321`) All three checkers return `true` on success and `throw` on real failure. When wiring, still guard the `bool`: a non-`true` return is a hard error. **Matcher function-pointer types (current):** - `AnchorMatcher = bool (*)(const ProductTimeSpecInput&)` (`AnchorRegistry.h:65`) - `DomainMatcher = bool (*)(const ProductTimeSpecInput&)` (`DomainRegistry.h:66`) - `ShapeMatcher = bool (*)(const ProductTimeSpecInput&)` (`ShapeRegistry.h:73`) **Enum counts (for scope):** 3 anchor cases, 5 domain cases, 14 shape cases = **22 matchers total**. - Anchors: ForecastAnalysis, Hindcast, SeasonalClimate. - Domains: ForecastDomain, FromStartForecastDomain, SeasonalForecastDomain, AnalysisDomain, SynopticAnalysisDomain. - Shapes: Instant, IFSStandardSingleLoop, IFSFakeDoubleLoopSingleLoop, IFSFromStartSingleLoopAtZero, IFSFromStartSingleLoopPositive, IFSSynopticSingleLoop, AIFSStandardSingleLoop, AIFSFakeDoubleLoopSingleLoop, AIFSFromStartSingleLoopAtZero, AIFSFromStartSingleLoopPositive, SeasonalSingleLoop, SeasonalMultiloop, IFSStandardMultiLoop, IFSFakeSingleLoopDoubleLoop. **Per-case impl files** (matcher + builders + checker live together per case): - `anchors/impl/{ForecastAnalysis,Hindcast,SeasonalClimate}.h` - `domains/impl/{AnalysisDomain,ForecastDomain,FromStartForecastDomain, SeasonalForecastDomain,SynopticAnalysisDomain}.h` - `shapes/impl/*.h` (14 files, names match the shape kinds above). **Reference impl already read (canonical example):** `shapes/impl/Instant.h` contains `match_Instant_Shape`, `build_Instant_ShapeOuterTimeRange`, `build_Instant_ShapeWindows`, `check_Instant_Shape`. Use it as the pattern template for both workstreams. **Normalization:** `detail/ShapeNormalization.h`, `normalizeShape_or_throw(input, domain, rawShape)`: - `timeRange` -> whole hours; month-based ranges converted via the EXACT placed interval from the resolved domain (domain not mutated; synoptic uses `defaultMarsTime()` start). - `timeIncrement` -> whole seconds or missing. - Whitelists: sub-monthly hours `{1,3,6,12,18,24,48,72,120,168,240,360}` (:194-195); monthly hours `{672,696,720,744}` (:196). - **Contains an outermost-span/window-consistency check at :235-258** (real domain span in hours must equal outermost normalized window). Phase 2 MOVES this into the cross-check stage. **Shared helpers:** - `domains/DomainUtils.h`: `resolvedForecastStep`, `resolvedSeasonalForecastLead`, `timespanDuration`, `resolveOuterDomainRange` (:163), `resolveSeasonalForecastOuterDomainRange` (:222) [near-duplicate of the former, differs only in from-start branch], and `offsetHoursFromReference(referenceDateTime, targetDateTime) -> long` (:274) (signed whole-hour offset; throws on non-whole-hour or overflow). - `shapes/ShapeUtils.h`: `timespanIsNone(input)`, `timespanIsMissingAndAllowed(input, allow)`. - `anchors/AnchorDataTypes.h`: `ProductTimeSpecAnchor` artifact (`labelDateTime`, `initialConditionsDateTime`, `referenceDateTime`, `anchorType`) with invariant `label <= initialConditions <= reference`; `checkedAnchor(...)` (input-free, location-only exceptions); `productTimeSpecAnchorJson(...)` (`noexcept`). **Domain artifact** (`domains/DomainDataTypes.h`) carries (per agreed decisions in `Status.md:66-70`): absolute `domainStartDateTime`/`domainEndDateTime`, `bool isSynoptic`, `long startOffsetHoursFromReference`, `long endOffsetHoursFromReference`. **The seasonal discriminator predicate is copy-pasted** in at least: `DomainUtils.h:83-86`, `Instant.h:68-71` (matcher), `Instant.h:238-241` (checker), and other shape matchers/checkers. Pattern: ```cpp const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); // seasonal == hasSeasonalClassStream && hasSeasonalLeadSemantics ``` --- ## 2. Review findings (the "why" behind this plan) - **(A) Dead validation layer.** All `check_*` callbacks + `check_*_or_throw` dispatchers exist and are thorough (e.g. `check_Instant_Shape` is ~90 lines), but NONE are invoked in the pipeline. -> Workstream A, Phase 1. - **(B) `Status.md` is stale.** It claims normalization is "not wired yet" (`:32`, `:217`, `:284`), but `ProductTimeSpec.h:387-388` already calls `normalizeShape_or_throw`. -> Phase 0. - **(C) Raw-vs-normalized check ordering only half-present.** Docstrings say checkers validate the RAW shape and that normalization runs "after raw checks succeed", but no check currently runs before OR after normalization. -> Phases 1 + 3. - **(D) `DomainUtils.h` outer-range duplication** (`resolveOuterDomainRange` vs `resolveSeasonalForecastOuterDomainRange`). Intentional per its doc comment; left OUT OF SCOPE (drift risk noted). - **(E) Seasonal discriminator copy-paste.** -> Phase 4 (optional but recommended) extracts a single shared predicate. --- ## 3. Decisions locked with the user (do not re-litigate) 1. **Checker wiring = two phases:** wire per-case raw checkers right after each raw build (Phase 1), THEN add a separate post-normalization cross-check stage (Phase 3). (Chosen over a single post-normalization pass.) 2. **Span check moves** out of `normalizeShape_or_throw` into the new cross-check stage (Phase 2). Normalization only transforms; validation lives in the check stage. 3. **Only matchers are templated** on the mode enum (Workstream B). Builders and checkers keep their signatures. 4. **Explain-mode uses a mode-templated `MatchAccumulator`** (single source of truth for condition names; bool result and JSON derive from the same `record()` calls). Chosen over hand-written `if constexpr` JSON. 5. Plan file location: this file, `product-time-spec/Plan.md`. 6. Strict `get_or_throw` everywhere for options (from prior workstream). --- ## 4. Execution order (do phases top-to-bottom) Recommended sequencing so each step is independently verifiable by the user: **Phase 0 -> 1 -> 2 -> 3**, then **Phase 5 (B)**, then **Phase 4** last (broad touch, easiest to review in isolation). Phase 4 is optional; confirm with user if time-constrained. Phases 2 and 3 must land together (Phase 2 removes a check that Phase 3 re-homes) so no validation gap exists between commits. --- ## Workstream A — Checks & Cross-Checks ### Phase 0 — Reconcile `Status.md` (doc-only) - `:32` "normalization helper added in code but not wired yet" -> "wired into the top-level pipeline". - Normalization Tracker `:215-217`: "top-level wiring not implemented yet" -> "wired in `build_ProductTimeSpecComponents_or_throw`". - `Next Session Entry Point` (`:278-286`): drop "wiring normalization" (done); replace with "wire per-case raw checkers + add cross-check stage". - Leave Cross-Check Tracker (`:233-245`) "not implemented" until Phase 3 lands, then flip Future Raw Check entries (`:173-204`) from `added` -> `wired` and Cross-Check -> `implemented`. ### Phase 1 — Wire per-case raw checkers File: `ProductTimeSpec.h`, `build_ProductTimeSpecComponents_or_throw` (≈:366-405). No changes to checker code (signatures already match §1). Insert, inside the existing `try`, each guarded so a non-`true` return throws `Mars2GribModelException(..., input.to_json(), Here())`: 1. After `build_Anchor_or_throw` (≈:379): `anchor::check_Anchor_or_throw(anchorType, input, anchor)` 2. After `build_Domain_or_throw` (≈:382-383): `domain::check_Domain_or_throw(domainType, input, anchor, domain)` 3. After `build_ShapeWindows_or_throw` (raw, ≈:384-385), BEFORE `normalizeShape_or_throw`: `shape::check_Shape_or_throw(shapeType, input, classification, anchor, outerTimeRange, domain, rawWindows)` Also mirror step 1 into `ProductTimeAnchorSpec::build_ProductTimeAnchorSpecComponents_or_throw` (≈:199-220), after its `build_Anchor_or_throw` (≈:206). (Includes for the registries are already present in `ProductTimeSpec.h:51-55`.) ### Phase 2 — Move span check out of normalization File: `detail/ShapeNormalization.h`. Remove the outermost-span validation block (≈:235-258): the `realDomainStart`, `realDomainSpan`, `realDomainSpanInHours`, `outermostWindow` locals and their throws. Keep ALL pure unit transforms (timeRange->hours incl. month placement, timeIncrement->seconds/missing, whitelist checks at :194-228). The month-placement logic (:134-156) is independent and STAYS. Re-verify no other code in the function references the removed locals before deleting. ### Phase 3 — Post-normalization cross-check stage New file: `detail/ProductTimeSpecCrossChecks.h`. Conventions: license header (copy from any sibling), `@file`/`@brief`/`@ingroup mars2grib_product_time_spec_detail` doc block, namespace `metkit::mars2grib::backend::models::product_time_spec::detail`, full-body try/catch + nested `Mars2GribModelException(..., input.to_json(), Here())`. Signature: ```cpp inline void crossCheck_ProductTimeSpec_or_throw( const ProductTimeSpecInput& input, const anchor::ProductTimeSpecAnchor& anchor, const domain::ProductTimeSpecDomain& domain, const shape::ProductTimeSpecShape& normalizedWindows); ``` Checks (each a named `const bool ...` + explicit throw on violation; source: `Status.md:235-241`): 1. **Domain span vs outermost window** (relocated from Phase 2). Recompute real domain span in whole hours: `start = domain.isSynoptic ? makeDateTime(domain.domainStartDateTime.date(), defaultMarsTime()) : domain.domainStartDateTime;` `span = durationBetween(start, domain.domainEndDateTime);` assert `span.unit == Second`, `span.length % 3600 == 0`, and `normalizedWindows.values.front().timeRange == {span/3600, Hour}`. 2. **Domain offsets vs reference:** assert `domain.startOffsetHoursFromReference == offsetHoursFromReference( anchor.referenceDateTime, domain.domainStartDateTime)` and same for end. (`offsetHoursFromReference` in `DomainUtils.h:274`.) 3. **Synoptic consistency:** `domain.isSynoptic == input.isSynoptic`. 4. **Seasonal consistency:** `(domain kind == SeasonalForecastDomain) == isSeasonalProduct(input)` (use the Phase 4 predicate; if Phase 4 is skipped, inline the predicate here). 5. **Normalized-hour whitelist re-assertion** on final artifacts (defense-in-depth mirroring `ShapeNormalization.h:194-196`). Wiring: in `build_ProductTimeSpecComponents_or_throw`, call `detail::crossCheck_ProductTimeSpec_or_throw(input, anchor, domain, normalisedWindows);` immediately after `normalizeShape_or_throw` (≈:388), before assembling `result`. Add `#include ".../detail/ProductTimeSpecCrossChecks.h"` to `ProductTimeSpec.h`. ### Phase 4 — De-duplicate seasonal discriminator (recommended, do LAST) Add a single predicate — location options: a free `inline bool isSeasonalProduct(const ProductTimeSpecInput&)` in a shared detail header, or a member on `ProductTimeSpecInput`. Capture: `hasSeasonalClassStream && hasSeasonalLeadSemantics` (exact expression in §1). Replace all copies: `DomainUtils.h:83-86`, `Instant.h:68-71`, `Instant.h:238-241`, and grep the other shape/domain impl files for the same two-line pattern and replace each. Behavior must be identical — pure extraction. --- ## Workstream B — Mode-Templated Matchers with JSON Diagnostics ### Intent Each matcher runs in two modes: **Evaluate** (returns `bool`, production) and **Explain** (returns a JSON string of every named condition + value, debug, NO short-circuit). On classification failure (0 or >1 matches), the `classify_*_or_throw` failure path calls each case's Explain matcher and attaches the aggregated JSON to the thrown exception, so the stack carries exactly which conditions were true/false per case. ### Phase 5.0 — Shared mode primitives New file: `detail/MatcherMode.h`. License header + doc block + namespace `...::product_time_spec::detail`. ```cpp enum class MatchMode { Evaluate, Explain }; template using MatchResult = std::conditional_t; template class MatchAccumulator { public: // Evaluate: ANDs value into running result. // Explain: appends {"": } to an ordered JSON object. void record(std::string_view name, bool value); // Evaluate: returns the conjunction (empty == true; every matcher records // >=1 condition, so document this edge case). // Explain: returns a complete JSON object string, e.g. // {"cond1":true,"cond2":false,...,"__match__":false} MatchResult result() const; private: // Evaluate storage: bool accumulator (init true). // Explain storage: std::ostringstream + first-flag + running bool for // the trailing "__match__" summary field. }; ``` Notes: `record()` must be called for EVERY condition unconditionally (no early return) so Explain reports all of them; Evaluate still gets the correct AND because all bools are computed before recording (matchers already compute all named bools eagerly). Include ``, ``, ``, ``. Reuse existing JSON-quote helper if one exists in `detail/ProductTimeSpecJsonUtils.h` (check `jsonQuote_modelInput`) for consistent escaping. ### Phase 5.1 — Convert all 22 matcher signatures For each matcher in the 3+5+14 impl files, change `inline bool match_X(const ProductTimeSpecInput& input)` to: ```cpp template inline MatchResult match_X(const ProductTimeSpecInput& input) { using ...detail::MatchAccumulator; using ...detail::MatchMode; try { MatchAccumulator acc; // compute the SAME named bools as today acc.record("condNameA", condA); acc.record("condNameB", condB); // ... one record() per existing named bool ... return acc.result(); } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `match_X`", input.to_json(), Here())); } } ``` Reference conversion — `match_Instant_Shape` (currently `Instant.h:61-86`) records: `isNotSeasonal`, `hasAcceptedTimespanRepresentation`, `hasNoStattypeBlocks`, `hasMissingStatisticalProcessing`. Keep the exact bool computations; only replace the final `return A && B && C && D;` with four `acc.record(...)` + `return acc.result();`. Do NOT template builders or checkers. ### Phase 5.2 — Registry rows carry both instantiations In each registry (`AnchorRegistry.h`, `DomainRegistry.h`, `ShapeRegistry.h`): - Add an Explain pointer type beside the existing matcher type, e.g. `using ShapeMatcher = bool (*)(const ProductTimeSpecInput&);` `using ShapeMatcherExplain = std::string (*)(const ProductTimeSpecInput&);` - Add a `matcherExplain` field to the case struct (`AnchorCase`/`DomainCase`/ `ShapeCase`) right after `matcher`. - In each row, set `matcher = &match_X` and `matcherExplain = &match_X`. (This changes the existing `&match_X` entries — every row in `anchorCases`/`domainCases`/`shapeCases`.) - The `static_assert`s on classification index are unaffected. ### Phase 5.3 — Emit diagnostics on classification failure In each `classify_*_or_throw`, the failure branch currently builds a `name=true/false` summary (see `ShapeRegistry.h:203-218` and `AnchorRegistry.h:139-143`, `DomainRegistry.h:160-164`). Replace/augment: when `numberOfMatches != 1`, iterate all cases calling `.matcherExplain(input)` and assemble a JSON array `[{"case":"", "conditions":}, ...]`, and include it in the thrown `Mars2GribModelException` message (alongside `input.to_json()`). Keep this inside the existing failure path so the Explain cost only occurs on error. ### Design guarantees - Explain path is failure-only -> zero production success-path cost. - One function body per matcher -> Explain conditions can never drift from Evaluate conditions. - Builders/checkers untouched -> blast radius limited to matchers + registries. --- ## 5. Files to read before editing (fresh session checklist) Must-read (re-confirm line numbers): - `ProductTimeSpec.h` (pipeline; Phases 1, 3 wiring). - `anchors/AnchorRegistry.h`, `domains/DomainRegistry.h`, `shapes/ShapeRegistry.h` (Phases 1, 5.2, 5.3). - `detail/ShapeNormalization.h` (Phase 2). - `domains/DomainUtils.h` (`offsetHoursFromReference` for Phase 3; dedup source for Phase 4). - `shapes/impl/Instant.h` (reference pattern for Phases 4, 5.1). - `detail/ProductTimeSpecJsonUtils.h` (JSON-quote helper for Phase 5.0). - `Status.md` (Phase 0 edits). Skim before Phase 5.1 (22 matchers): all `anchors/impl/*.h`, `domains/impl/*.h`, `shapes/impl/*.h`. ## 6. Verification (USER runs; agent must NOT) - Build the metkit target. - Existing ProductTimeSpec tests pass; the relocated span check still fires (now from the cross-check stage); newly-wired per-case checkers do not reject previously-valid inputs. - Construct a deliberate 0-match and a >1-match input; confirm the thrown exception carries the per-condition Explain JSON for every case. ## 7. Out of scope (deferred per `Status.md`) - `DomainUtils.h` outer-range dedup (finding D). - Error-boundary audit; `TemporalArithmetic` hardening (`Status.md` phases 4-5). - Detailed paranoia/check-level option structure (`Status.md` Deferred). ## 8. Progress log (update as phases land) - [ ] Phase 0 — Status.md reconciled - [ ] Phase 1 — per-case raw checkers wired (full + anchor-only paths) - [ ] Phase 2 — span check removed from normalization - [ ] Phase 3 — ProductTimeSpecCrossChecks.h added + wired - [ ] Phase 5.0 — MatcherMode.h (enum + MatchAccumulator) - [ ] Phase 5.1 — 22 matchers templated - [ ] Phase 5.2 — registries carry Explain pointers - [ ] Phase 5.3 — classify failure emits Explain JSON - [ ] Phase 4 — seasonal predicate de-duplicated (optional, last) metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/0000775000175000017500000000000015246725757027130 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorRegistry.h0000664000175000017500000002333715246725757032254 0ustar alastairalastair/* * (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 AnchorRegistry.h /// @brief Register, classify, build, and check ProductTimeSpec anchor cases. /// /// This header centralizes the case table and the public dispatch entry points /// for ProductTimeSpec anchor handling. /// /// It owns: /// - the immutable registry row type used to keep matcher, builder, and checker /// callbacks aligned with their classification values; /// - the anchor classification entry point; /// - the anchor builder dispatch entry point; /// - the anchor checker dispatch entry point. /// /// Classification is exhaustive and non-prioritized. Every registered matcher /// is evaluated, and classification succeeds only when exactly one matcher /// returns `true`. /// /// A zero-match result means that the normalized input does not describe a /// supported implemented anchor. A multiple-match result means that two or more /// matcher contracts overlap. Both are hard classification failures. /// /// The active callback-selection matrix is: /// /// | MARS `date` | MARS `time` | MARS `hdate` | MARS `year` / `month` | Selected anchor callback | /// |-------------|-------------|--------------|------------------------|--------------------------| /// | present | optional | absent | both absent | `ForecastAnalysis` | /// | present | optional | present | both absent | `Hindcast` | /// | absent | absent | absent | one or both present | `SeasonalClimate` matcher raises `not /// implemented` | /// /// Unsupported source states include, among others: /// - partial `year` / `month` presence; /// - `time` without `date`; /// - simultaneous direct `date` / `hdate` sources together with `year` or `month`; /// - complete absence of all anchor source families. /// /// @ingroup mars2grib_product_time_spec_anchors /// #pragma once #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/impl/ForecastAnalysis.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/impl/Hindcast.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/impl/SeasonalClimate.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::anchor { namespace detail { /// @brief Function-pointer type shared by all anchor matchers. using AnchorMatcher = bool (*)(const ProductTimeSpecInput&); /// @brief Function-pointer type shared by all anchor builders. using AnchorBuilder = ProductTimeSpecAnchor (*)(const ProductTimeSpecInput&, const ProductTimeSpecClassification&); /// @brief Function-pointer type shared by all anchor check callbacks. using AnchorChecker = bool (*)(const ProductTimeSpecInput&, const ProductTimeSpecAnchor&); /// /// @brief Immutable registry row for one anchor case. /// /// The row keeps the classification value, diagnostic name, matcher, builder, /// and checker together so that independent arrays cannot become misaligned. /// struct AnchorCase { ProductTimeSpecAnchorKind classification; std::string_view name; AnchorMatcher matcher; AnchorBuilder builder; AnchorChecker checker; }; inline constexpr std::array(ProductTimeSpecAnchorKind::Count)> anchorCases{ { {ProductTimeSpecAnchorKind::ForecastAnalysis, "ForecastAnalysis", &match_ForecastAnalysis_Anchor, &build_ForecastAnalysis_Anchor, &check_ForecastAnalysis_Anchor}, {ProductTimeSpecAnchorKind::Hindcast, "Hindcast", &match_Hindcast_Anchor, &build_Hindcast_Anchor, &check_Hindcast_Anchor}, {ProductTimeSpecAnchorKind::SeasonalClimate, "SeasonalClimate", &match_SeasonalClimate_Anchor, &build_SeasonalClimate_Anchor, &check_SeasonalClimate_Anchor}, }}; static_assert(static_cast(detail::anchorCases[0].classification) == static_cast(ProductTimeSpecAnchorKind::ForecastAnalysis)); static_assert(static_cast(detail::anchorCases[1].classification) == static_cast(ProductTimeSpecAnchorKind::Hindcast)); static_assert(static_cast(detail::anchorCases[2].classification) == static_cast(ProductTimeSpecAnchorKind::SeasonalClimate)); } // namespace detail /// /// @brief Classify the normalized input against every registered anchor case. /// /// @section Anchor classification contract /// - Reads: normalized direct-source facts from `input`. /// - Evaluates: every matcher in `anchorCases`. /// - Success: exactly one matcher returns `true`. /// - Failure: zero or more than one matcher return `true`. /// - Side effects: none. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return Unique matching `ProductTimeSpecAnchorKind` value. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// if matcher evaluation fails or classification is not unique. /// inline ProductTimeSpecAnchorKind classify_Anchor_or_throw(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { std::array matches{}; std::size_t numberOfMatches = 0; std::size_t matchedIndex = 0; if (!detail::anchorCases.empty()) { for (std::size_t i = 0; i < detail::anchorCases.size(); ++i) { matches[i] = detail::anchorCases[i].matcher(input); if (matches[i]) { ++numberOfMatches; matchedIndex = i; } } } if (numberOfMatches != 1) { throw Mars2GribModelException("Anchor classification failed: expected exactly one match, but found " + std::to_string(numberOfMatches) + " matches", input.to_json(), Here()); } return detail::anchorCases[matchedIndex].classification; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to classify the ProductTimeSpec anchor", input.to_json(), Here())); } } /// /// @brief Dispatch the builder associated with a validated anchor classification. /// /// @param[in] classification Unique anchor classification returned by /// `classify_Anchor_or_throw`. /// @param[in] input Fully normalized ProductTimeSpec input supplied to the /// selected anchor builder. /// @param[in] fullClassification Full resolved ProductTimeSpec classification bundle. /// @return Complete and case-validated `ProductTimeSpecAnchor`. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException if /// the classification is invalid or the selected builder fails. /// inline ProductTimeSpecAnchor build_Anchor_or_throw(ProductTimeSpecAnchorKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecClassification& fullClassification) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::anchorCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid AnchorClassification value", input.to_json(), Here()); } return detail::anchorCases[index].builder(input, fullClassification); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build the ProductTimeSpec anchor", input.to_json(), Here())); } } /// /// @brief Dispatch the checker associated with a validated anchor classification. /// /// @param[in] classification Unique anchor classification returned by /// `classify_Anchor_or_throw`. /// @param[in] input Fully normalized ProductTimeSpec input supplied to the /// selected anchor checker. /// @param[in] anchor Complete anchor artifact produced by the selected anchor builder. /// @return `true` when the selected checker validates the anchor successfully. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException if /// the classification is invalid or the selected checker fails. /// inline bool check_Anchor_or_throw(ProductTimeSpecAnchorKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecAnchor& anchor) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::anchorCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid AnchorClassification value", input.to_json(), Here()); } return detail::anchorCases[index].checker(input, anchor); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to check the ProductTimeSpec anchor", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::anchor metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h0000664000175000017500000001274215246725757032340 0ustar alastairalastair/* * (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 AnchorDataTypes.h /// @brief Anchor artifact and low-level anchor invariant helpers. /// /// This header owns: /// - the resolved `ProductTimeSpecAnchor` artifact type; /// - the low-level `checkedAnchor(...)` helper that validates anchor ordering /// without depending on model-input debug context; /// - the best-effort diagnostic JSON serializer for the resolved anchor. /// #pragma once #include #include #include "eckit/types/DateTime.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::anchor { /// /// @brief Fully resolved ordered ProductTimeSpec anchor artifact. /// /// The artifact stores the three anchor datetimes used by later ProductTimeSpec /// stages together with the direct-source regime that produced them. The three /// values are already fully inherited/defaulted by the selected anchor builder. /// /// Valid resolved anchors satisfy: /// `labelDateTime <= initialConditionsDateTime <= referenceDateTime`. /// struct ProductTimeSpecAnchor { /// @brief Final label datetime used by downstream ProductTimeSpec logic. eckit::DateTime labelDateTime{}; /// @brief Final initial-conditions datetime used by downstream ProductTimeSpec logic. eckit::DateTime initialConditionsDateTime{}; /// @brief Final reference datetime used by downstream ProductTimeSpec logic. eckit::DateTime referenceDateTime{}; /// @brief Anchor case that produced this resolved artifact. ProductTimeSpecAnchorKind anchorType{ProductTimeSpecAnchorKind::ForecastAnalysis}; }; /// @brief Validate anchor ordering and construct a ProductTimeSpec anchor. /// /// This helper is intentionally input-free. It owns only the low-level anchor /// invariant: /// `labelDateTime <= initialConditionsDateTime <= referenceDateTime`. /// /// Higher-level callbacks remain responsible for catching failures here and /// rethrowing with richer `ProductTimeSpecInput` context. /// @param[in] label Candidate label datetime. /// @param[in] initialConditions Candidate initial-conditions datetime. /// @param[in] reference Candidate reference datetime. /// @param[in] anchorType Resolved anchor classification. /// @return Complete and order-validated ProductTimeSpec anchor. /// @throws Mars2GribGenericException If ordering is invalid or construction fails. inline ProductTimeSpecAnchor checkedAnchor(const eckit::DateTime& label, const eckit::DateTime& initialConditions, const eckit::DateTime& reference, ProductTimeSpecAnchorKind anchorType) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const bool labelDoesNotFollowInitialConditions = label <= initialConditions; const bool initialConditionsDoNotFollowReference = initialConditions <= reference; const bool orderingIsValid = labelDoesNotFollowInitialConditions && initialConditionsDoNotFollowReference; if (!orderingIsValid) { throw Mars2GribGenericException("Anchor ordering must satisfy label <= initial conditions <= reference", Here()); } return ProductTimeSpecAnchor{label, initialConditions, reference, anchorType}; } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to validate and construct ProductTimeSpec anchor", Here())); } } /// @brief Serialize one resolved anchor artifact as diagnostic JSON. /// /// This function is best-effort and never throws. It is intended for /// diagnostic-context construction only and therefore returns a stable fallback /// error object if serialization fails. /// /// @param[in] value Resolved anchor artifact. /// @return One JSON object describing the final anchor state, or a stable /// fallback error object if serialization fails. inline std::string productTimeSpecAnchorJson(const ProductTimeSpecAnchor& value) noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("labelDateTime") << ':' << detail::productTimeSpecDateTimeJson(value.labelDateTime) << ',' << detail::jsonQuote_modelInput("initialConditionsDateTime") << ':' << detail::productTimeSpecDateTimeJson(value.initialConditionsDateTime) << ',' << detail::jsonQuote_modelInput("referenceDateTime") << ':' << detail::productTimeSpecDateTimeJson(value.referenceDateTime) << ',' << detail::jsonQuote_modelInput("anchorType") << ':' << detail::jsonQuote_modelInput(productTimeSpecAnchorTypeName(value.anchorType)) << '}'; return out.str(); } catch (...) { return std::string{"{\"error\":\"productTimeSpecAnchorJson failed while building diagnostic context\"}"}; } } } // namespace metkit::mars2grib::backend::models::product_time_spec::anchor metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/anchor.md0000664000175000017500000001575215246725757030736 0ustar alastairalastair # Anchor Logic ## Purpose The anchor layer resolves the temporal anchor state used by the rest of `ProductTimeSpec`. It produces one resolved `ProductTimeSpecAnchor` artifact containing: - `labelDateTime` - `initialConditionsDateTime` - `referenceDateTime` - `anchorType` These values are derived directly from the normalized input and are later used by domain and shape construction. ## Folder Structure The current anchor folder contains: - `AnchorDataTypes.h` - `AnchorRegistry.h` - `impl/ForecastAnalysis.h` - `impl/Hindcast.h` - `impl/SeasonalClimate.h` There is currently no remaining shared `AnchorUtils.h`. The only shared low-level anchor invariant helper is now `checkedAnchor(...)` in `AnchorDataTypes.h`. ## Resolved Anchor Artifact The resolved artifact type is: - `anchor::ProductTimeSpecAnchor` It stores three absolute datetimes and the producing anchor classification. The canonical low-level invariant is: ```text labelDateTime <= initialConditionsDateTime <= referenceDateTime ``` This invariant is validated by: - `anchor::checkedAnchor(...)` `checkedAnchor(...)` is intentionally input-free. It validates the invariant and constructs the final anchor artifact. It throws a low-level generic exception. Higher-level callback code catches and rethrows with richer model-input context. ## Classification Flow The anchor classification entry point is: - `anchor::classify_Anchor_or_throw(...)` It is implemented in: - `AnchorRegistry.h` Behavior: 1. evaluate every registered matcher; 2. count how many matchers returned `true`; 3. succeed only when exactly one matcher matches; 4. otherwise throw a classification failure. Matcher order is not precedence. The current registered cases are: - `ForecastAnalysis` - `Hindcast` - `SeasonalClimate` ## Builder Flow The anchor builder dispatch entry point is: - `anchor::build_Anchor_or_throw(...)` It is implemented in: - `AnchorRegistry.h` Behavior: 1. validate the classification enum value; 2. dispatch to the registered per-case builder; 3. return the resolved `ProductTimeSpecAnchor`. ## Checker Flow The anchor checker dispatch entry point is: - `anchor::check_Anchor_or_throw(...)` It is implemented in: - `AnchorRegistry.h` Behavior: 1. validate the classification enum value; 2. dispatch to the registered per-case checker; 3. return `true` on success; 4. otherwise throw a check failure. The checker infrastructure is present in the registry and in the case files. It is intended to validate the resolved anchor against both the case semantics and the originating normalized input. ## Current Cases ### ForecastAnalysis File: - `impl/ForecastAnalysis.h` Matcher: - `match_ForecastAnalysis_Anchor(...)` The case matches when: - `marsDate` is present; - `marsHdate` is absent; - `marsYear` is absent; - `marsMonth` is absent. `marsTime` is optional. Builder: - `build_ForecastAnalysis_Anchor(...)` Construction rule: - all three anchor members are built from the same source datetime: - MARS `date` - optional MARS `time` - default time `00:00:00` when `marsTime` is absent So in this case: ```text labelDateTime == initialConditionsDateTime == referenceDateTime ``` Checker: - `check_ForecastAnalysis_Anchor(...)` The checker validates: - anchor type is `ForecastAnalysis`; - the three anchor datetimes are equal; - the resolved date matches input `marsDate`; - the resolved time matches input `marsTime` or the default time. ### Hindcast File: - `impl/Hindcast.h` Matcher: - `match_Hindcast_Anchor(...)` The case matches when: - `marsDate` is present; - `marsHdate` is present; - `marsYear` is absent; - `marsMonth` is absent. `marsTime` is optional. Builder: - `build_Hindcast_Anchor(...)` Construction rule: - `labelDateTime` is built from `marsHdate` at `00:00:00`; - `initialConditionsDateTime` is built from MARS `date` and optional MARS `time`; - `referenceDateTime` equals `initialConditionsDateTime`. So in this case: ```text labelDateTime = hdate @ 00:00:00 initialConditionsDateTime = date + optional time referenceDateTime = initialConditionsDateTime ``` Checker: - `check_Hindcast_Anchor(...)` The checker validates: - anchor type is `Hindcast`; - `initialConditionsDateTime == referenceDateTime`; - `labelDateTime <= initialConditionsDateTime`; - `labelDateTime.date()` matches `marsHdate`; - `labelDateTime.time()` is `00:00:00`; - `initialConditionsDateTime` matches MARS `date` and optional MARS `time`. ### SeasonalClimate File: - `impl/SeasonalClimate.h` Matcher: - `match_SeasonalClimate_Anchor(...)` This case is currently reserved but intentionally not implemented. Current behavior: - if `marsYear` or `marsMonth` is present, the matcher throws a `not implemented` model exception; - otherwise it returns `false`. Builder: - `build_SeasonalClimate_Anchor(...)` Current behavior: - always throws a `not implemented` model exception. Checker: - `check_SeasonalClimate_Anchor(...)` Current behavior: - always throws a `not implemented` model exception. This means the case remains visible in the registry and classification model, but is intentionally blocked until real implementation work is done. ## Registry Responsibilities `AnchorRegistry.h` owns the immutable case table. Each row stores: - classification value; - diagnostic name; - matcher callback; - builder callback; - checker callback. This keeps the three anchor callback families aligned for each case. ## Exception Layering The current anchor layer uses two levels of exception responsibility. Low-level invariant helper: - `checkedAnchor(...)` - throws `Mars2GribGenericException` - does not know about model input or JSON context Case-level callbacks: - matcher - builder - checker - catch failures and rethrow as `Mars2GribModelException` - attach `input.to_json()` for richer debug context This keeps low-level invariant code small and reusable while preserving rich diagnostics at the callback boundary. ## Diagnostic JSON `AnchorDataTypes.h` provides: - `productTimeSpecAnchorJson(...)` This function is: - best-effort; - `noexcept`; - intended only for diagnostic context construction. If serialization fails, it returns a stable fallback JSON error object instead of throwing. ## Current Limitations The main current limitation of the anchor folder is: - `SeasonalClimate` is recognized as a reserved case but intentionally not implemented. The anchor checker infrastructure is present and the registry now exposes the checker dispatch entry point. Top-level normalization and cross-artifact final checks still remain later pipeline stages outside the anchor folder. The anchor artifact itself is not normalized in the raw-to-normalized stage. metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/impl/0000775000175000017500000000000015246725757030071 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/impl/SeasonalClimate.h0000664000175000017500000001234315246725757033311 0ustar alastairalastair/* * (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 SeasonalClimate.h /// @brief Matcher, builder, and checker for the SeasonalClimate anchor case. /// /// This header is the authoritative implementation of the `SeasonalClimate` /// anchor case. It keeps recognition and construction together so that the /// complete case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and returns /// their explicit conjunction. This case is currently reserved but not /// implemented: the matcher, builder, and checker all raise explicit /// `not implemented` model exceptions when the seasonal-climate source pattern /// is encountered. /// /// All functions are deterministic, have no externally visible side effects, /// catch every failure, and rethrow a nested `Mars2GribModelException` carrying /// the normalized input snapshot. /// /// @ingroup mars2grib_product_time_spec_anchors /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail { /** * @brief Identify the reserved SeasonalClimate anchor source pattern. * * This implementation currently does not support SeasonalClimate anchors. * If either `marsYear` or `marsMonth` is present, the recognized seasonal * anchor pattern is treated as explicitly not implemented. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `false` when no seasonal-climate source fields are present. * @throws Mars2GribModelException if the seasonal-climate source pattern is * encountered, because the case is intentionally not implemented. */ inline bool match_SeasonalClimate_Anchor(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasYear = input.marsYear.has_value(); const bool hasMonth = input.marsMonth.has_value(); if (hasYear || hasMonth) { throw Mars2GribModelException("SeasonalClimate anchor is not implemented", input.to_json(), Here()); } return false; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_SeasonalClimate_Anchor`", input.to_json(), Here())); } } /** * @brief Refuse construction of the reserved SeasonalClimate anchor case. * * This case is intentionally not implemented at the moment. * * @param[in] input Fully normalized ProductTimeSpec input. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Never returns successfully. * * @throws Mars2GribModelException always, because SeasonalClimate anchor * construction is intentionally not implemented. */ inline ProductTimeSpecAnchor build_SeasonalClimate_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; throw Mars2GribModelException("SeasonalClimate anchor is not implemented", input.to_json(), Here()); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_SeasonalClimate_Anchor`", input.to_json(), Here())); } } /// @brief Refuse validation of the reserved SeasonalClimate anchor case. /// /// This checker is present so the anchor registry has a complete checker table, /// but the SeasonalClimate case is intentionally not implemented. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @param[in] anchor Resolved anchor artifact placeholder. /// @return Never returns successfully. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// always, because SeasonalClimate anchor validation is intentionally /// not implemented. inline bool check_SeasonalClimate_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecAnchor& anchor) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)anchor; throw Mars2GribModelException("SeasonalClimate anchor is not implemented", input.to_json(), Here()); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_SeasonalClimate_Anchor`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/impl/ForecastAnalysis.h0000664000175000017500000001717215246725757033524 0ustar alastairalastair/* * (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 ForecastAnalysis.h /// @brief Matcher, builder, and checker for the ForecastAnalysis anchor case. /// /// This header is the authoritative implementation of the `ForecastAnalysis` /// anchor case. It keeps recognition and construction together so that the /// complete case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and returns /// their explicit conjunction. The builder constructs the three canonical anchor /// members directly from the visible MARS `date` and optional MARS `time` /// source. The checker validates that the resolved anchor remains consistent /// with both the classification and the originating input fields. /// /// All functions are deterministic, have no externally visible side effects, /// catch every failure, and rethrow a nested `Mars2GribModelException` carrying /// the normalized input snapshot. /// /// @ingroup mars2grib_product_time_spec_anchors /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail { /** * @brief Return true only when input matches the ForecastAnalysis anchor case. * * - MARS date is present; * - MARS time is optional; * - hdate is absent; * - year and month are absent. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the anchor matcher fails unexpectedly. */ inline bool match_ForecastAnalysis_Anchor(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasDate = input.marsDate.has_value(); const bool hasNoHdate = !input.marsHdate.has_value(); const bool hasNoYear = !input.marsYear.has_value(); const bool hasNoMonth = !input.marsMonth.has_value(); return hasDate && hasNoHdate && hasNoYear && hasNoMonth; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_ForecastAnalysis_Anchor`", input.to_json(), Here())); } } /** * @brief Construct a ForecastAnalysis anchor from one MARS date/time source. * * This case has one direct source datetime: MARS `date` plus optional MARS * `time`. The three canonical anchor members are therefore identical. * * @param[in] input Fully normalized ProductTimeSpec input. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed and order-validated ProductTimeSpec anchor. * * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecAnchor build_ForecastAnalysis_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchorKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::makeDateTime; try { // This case requires a MARS date and optionally a MARS time. if (!input.marsDate.has_value()) { throw Mars2GribModelException("ForecastAnalysis anchor construction requires MARS date", input.to_json(), Here()); } // The three canonical anchor members are identical in this case because // the product exposes only one direct source datetime. // `makeDateTime(...)` injects the canonical `00:00:00` default when the // optional MARS time is absent. const eckit::DateTime labelDateTime = makeDateTime(input.marsDate.value(), input.marsTime); const eckit::DateTime initialConditionsDateTime = makeDateTime(input.marsDate.value(), input.marsTime); const eckit::DateTime referenceDateTime = makeDateTime(input.marsDate.value(), input.marsTime); // Validate the canonical ordering invariant and return the final anchor. return checkedAnchor(labelDateTime, initialConditionsDateTime, referenceDateTime, ProductTimeSpecAnchorKind::ForecastAnalysis); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_ForecastAnalysis_Anchor`", input.to_json(), Here())); } } /// @brief Validate one resolved ForecastAnalysis anchor against its source input. /// /// This checker verifies: /// - the resolved anchor type; /// - equality of the three canonical anchor datetimes for this case; /// - agreement with the originating MARS `date` and optional MARS `time`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @param[in] anchor Resolved anchor artifact produced by the builder. /// @return `true` when the anchor is valid for the ForecastAnalysis case. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException if /// the resolved anchor is inconsistent with the input or case semantics. inline bool check_ForecastAnalysis_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecAnchor& anchor) { using metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchorKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { if (anchor.anchorType != ProductTimeSpecAnchorKind::ForecastAnalysis) { throw Mars2GribModelException("Anchor type mismatch: expected ForecastAnalysis", input.to_json(), Here()); } if (anchor.labelDateTime != anchor.initialConditionsDateTime || anchor.initialConditionsDateTime != anchor.referenceDateTime) { throw Mars2GribModelException("Anchor datetimes are not equal for ForecastAnalysis", input.to_json(), Here()); } if (!input.marsDate.has_value()) { throw Mars2GribModelException("Input missing MARS date for ForecastAnalysis", input.to_json(), Here()); } if (input.marsDate.value() != anchor.labelDateTime.date()) { throw Mars2GribModelException("Anchor label date does not match input MARS date", input.to_json(), Here()); } if (input.marsTime.value_or(eckit::Time{0, 0, 0}) != anchor.labelDateTime.time()) { throw Mars2GribModelException("Anchor label time does not match input MARS time", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_ForecastAnalysis_Anchor`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/anchors/impl/Hindcast.h0000664000175000017500000002074015246725757032002 0ustar alastairalastair/* * (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 Hindcast.h /// @brief Matcher, builder, and checker for the Hindcast anchor case. /// /// This header is the authoritative implementation of the `Hindcast` anchor /// case. It keeps recognition and construction together so that the complete /// case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and returns /// their explicit conjunction. The builder constructs the three canonical anchor /// members directly from the visible MARS `hdate`, MARS `date`, and optional /// MARS `time` source fields. The checker validates that the resolved anchor /// remains consistent with both the classification and the originating input. /// /// All functions are deterministic, have no externally visible side effects, /// catch every failure, and rethrow a nested `Mars2GribModelException` carrying /// the normalized input snapshot. /// /// @ingroup mars2grib_product_time_spec_anchors /// #pragma once #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail { /** * @brief Return true only when input matches the Hindcast anchor case. * * - MARS date is present; * - MARS time is optional; * - hdate is present; * - year and month are absent. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the anchor matcher fails unexpectedly. */ inline bool match_Hindcast_Anchor(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasDate = input.marsDate.has_value(); const bool hasHdate = input.marsHdate.has_value(); const bool hasNoYear = !input.marsYear.has_value(); const bool hasNoMonth = !input.marsMonth.has_value(); return hasDate && hasHdate && hasNoYear && hasNoMonth; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_Hindcast_Anchor`", input.to_json(), Here())); } } /** * @brief Construct a Hindcast anchor from hdate and MARS date/time. * * In this case: * - `labelDateTime` is the hindcast date at `00:00:00`; * - `initialConditionsDateTime` is built from MARS `date` and optional MARS * `time`; * - `referenceDateTime` equals `initialConditionsDateTime`. * * @param[in] input Fully normalized ProductTimeSpec input. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed and order-validated ProductTimeSpec anchor. * * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecAnchor build_Hindcast_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchorKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::defaultMarsTime; using metkit::mars2grib::utils::time_arithmetic::makeDateTime; try { (void)classification; if (!input.marsHdate.has_value()) { throw Mars2GribModelException("Hindcast anchor construction requires hdate", input.to_json(), Here()); } if (!input.marsDate.has_value()) { throw Mars2GribModelException("Hindcast anchor construction requires MARS date", input.to_json(), Here()); } // In the Hindcast case, the label is the hindcast date at midnight, // while the initial conditions and reference are derived from the MARS // date plus optional MARS time. const eckit::DateTime labelDateTime = makeDateTime(*input.marsHdate, defaultMarsTime()); const eckit::DateTime initialConditionsDateTime = makeDateTime(*input.marsDate, input.marsTime); const eckit::DateTime referenceDateTime = initialConditionsDateTime; return checkedAnchor(labelDateTime, initialConditionsDateTime, referenceDateTime, ProductTimeSpecAnchorKind::Hindcast); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_Hindcast_Anchor`", input.to_json(), Here())); } } /// @brief Validate one resolved Hindcast anchor against its source input. /// /// This checker verifies: /// - the resolved anchor type; /// - the expected equality between initial-conditions and reference datetimes; /// - agreement with the input `hdate`, MARS `date`, and optional MARS `time`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @param[in] anchor Resolved anchor artifact produced by the builder. /// @return `true` when the anchor is valid for the Hindcast case. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException if /// the resolved anchor is inconsistent with the input or case semantics. inline bool check_Hindcast_Anchor(const ProductTimeSpecInput& input, const ProductTimeSpecAnchor& anchor) { using metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchorKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::defaultMarsTime; try { if (anchor.anchorType != ProductTimeSpecAnchorKind::Hindcast) { throw Mars2GribModelException("Anchor type mismatch: expected Hindcast", input.to_json(), Here()); } if (anchor.initialConditionsDateTime != anchor.referenceDateTime) { throw Mars2GribModelException( "Initial conditions datetime and reference datetime must be equal for Hindcast", input.to_json(), Here()); } if (anchor.labelDateTime > anchor.initialConditionsDateTime) { throw Mars2GribModelException("Label datetime must not follow initial conditions datetime for Hindcast", input.to_json(), Here()); } if (!input.marsHdate.has_value()) { throw Mars2GribModelException("Input missing hdate for Hindcast", input.to_json(), Here()); } if (!input.marsDate.has_value()) { throw Mars2GribModelException("Input missing MARS date for Hindcast", input.to_json(), Here()); } if (anchor.labelDateTime.date() != *input.marsHdate) { throw Mars2GribModelException("Anchor label date does not match input hdate", input.to_json(), Here()); } if (anchor.labelDateTime.time() != defaultMarsTime()) { throw Mars2GribModelException("Anchor label time must be 00:00:00 for Hindcast", input.to_json(), Here()); } if (anchor.initialConditionsDateTime.date() != *input.marsDate) { throw Mars2GribModelException("Anchor initial conditions date does not match input MARS date", input.to_json(), Here()); } if (anchor.initialConditionsDateTime.time() != input.marsTime.value_or(defaultMarsTime())) { throw Mars2GribModelException("Anchor initial conditions time does not match input MARS time", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_Hindcast_Anchor`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::anchor::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/Status.md0000664000175000017500000002432415246725757027305 0ustar alastairalastair # ProductTimeSpec Status ## Purpose This file is the live iteration tracker for the rewrite described in `Requirements.md` and constrained by `Constraints.md`. It must be updated at each significant implementation step. ## Current State - planning completed for the rewrite structure; - temporary markdown replaced with `Requirements.md`, `Status.md`, and `Constraints.md`; - early normalized-input refinement completed in code; - raw domain artifact extension completed in code; - anchor callback review and rewrite completed in code; - domain callback review and rewrite completed in code; - shape callback review and rewrite completed in code; - anchor, domain, and shape local raw check callbacks implemented in code; - shape registry checker dispatch implemented in code; - normalization helper added in code but not wired yet; - final cross-check stage not started yet. ## Completed Tasks - `0.0` Extend normalized input with precomputed step-zero statistical-processing allowance. - `0.1` Update `isAllowed_InnerTypeOfStatisticalProcessingAtStepZero` semantics. - `0.2` Add precomputed step-zero allowance to `ProductTimeSpecInput`. - `0.3` Compute the new input member during `make_ProductTimeSpecInput_or_throw`. - `0.4` Update zero-step shape callbacks to consume the input member. - `1.0` Extend raw domain artifact. - `1.1` Review `match_Analysis_Domain`. - `1.2` Review `build_Analysis_Domain`. - `1.3` Review `match_Forecast_Domain`. - `1.4` Review `build_Forecast_Domain`. - `1.5` Review `match_FromStartForecast_Domain`. - `1.6` Review `build_FromStartForecast_Domain`. - `1.7` Review `match_SeasonalForecast_Domain`. - `1.8` Review `build_SeasonalForecast_Domain`. - `1.9` Review `match_SynopticAnalysis_Domain`. - `1.10` Review `build_SynopticAnalysis_Domain`. - `2.0` Add per-anchor check callbacks. - `2.1` Review `match_ForecastAnalysis_Anchor`. - `2.2` Review `build_ForecastAnalysis_Anchor`. - `2.3` Review `match_Hindcast_Anchor`. - `2.4` Review `build_Hindcast_Anchor`. - `2.5` Review `match_SeasonalClimate_Anchor`. - `2.6` Review `build_SeasonalClimate_Anchor`. - `3.0` Add per-domain check callbacks. - `4.0` Add per-shape check callbacks. - `4.1` to `4.42` Review and refactor all shape callbacks. ## Agreed Decisions - Keep existing absolute domain datetimes. - Extend `ProductTimeSpecDomain` with: - `bool isSynoptic` - `long startOffsetHoursFromReference` - `long endOffsetHoursFromReference` - Normalize after callbacks, not inside callbacks. - Encoding needs final time ranges in hours. - Final time increments need to be seconds or missing. - Month-valued raw ranges must be converted using the real placed interval. - Sub-monthly normalized hour validation source of truth is the language timespan support set. - Monthly normalized hour validation source of truth is the ecCodes monthly concept space represented by `672`, `696`, `720`, `744` hours. - The step-zero statistical-processing allowance should become a precomputed `ProductTimeSpecInput` member. - `TypeOfStatisticalProcessing::Missing` must be treated as always valid in the step-zero allowance rule because it represents the instant case. - Detailed paranoia/check-level options are intentionally deferred. ## Active Rewrite Order 1. update the living markdown to reflect the completed callback and checker work; 2. add the raw-to-normalized artifact layer; 3. add post-normalization cross checks; 4. audit diagnostic JSON and exception boundaries; 5. harden `TemporalArithmetic`; 6. rewrite in-code documentation. ## Callback Review Tracker Use the following status values: - `pending` - `reviewed` - `rewrite_started` - `rewritten` - `checked` - `normalized_path_reviewed` - `cross_checked` ### Anchor callbacks - `match_ForecastAnalysis_Anchor` : checked - `build_ForecastAnalysis_Anchor` : checked - `match_Hindcast_Anchor` : checked - `build_Hindcast_Anchor` : checked - `match_SeasonalClimate_Anchor` : checked - `build_SeasonalClimate_Anchor` : checked ### Domain callbacks - `match_Analysis_Domain` : checked - `build_Analysis_Domain` : checked - `match_Forecast_Domain` : checked - `build_Forecast_Domain` : checked - `match_FromStartForecast_Domain` : checked - `build_FromStartForecast_Domain` : checked - `match_SeasonalForecast_Domain` : checked - `build_SeasonalForecast_Domain` : checked - `match_SynopticAnalysis_Domain` : checked - `build_SynopticAnalysis_Domain` : checked ### Shape callbacks - `match_Instant_Shape` : checked - `build_Instant_ShapeOuterTimeRange` : checked - `build_Instant_ShapeWindows` : checked - `match_IFSStandardSingleLoop_Shape` : checked - `build_IFSStandardSingleLoop_ShapeOuterTimeRange` : checked - `build_IFSStandardSingleLoop_ShapeWindows` : checked - `match_IFSFakeDoubleLoopSingleLoop_Shape` : checked - `build_IFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange` : checked - `build_IFSFakeDoubleLoopSingleLoop_ShapeWindows` : checked - `match_IFSFromStartSingleLoopAtZero_Shape` : checked - `build_IFSFromStartSingleLoopAtZero_ShapeOuterTimeRange` : checked - `build_IFSFromStartSingleLoopAtZero_ShapeWindows` : checked - `match_IFSFromStartSingleLoopPositive_Shape` : checked - `build_IFSFromStartSingleLoopPositive_ShapeOuterTimeRange` : checked - `build_IFSFromStartSingleLoopPositive_ShapeWindows` : checked - `match_IFSSynopticSingleLoop_Shape` : checked - `build_IFSSynopticSingleLoop_ShapeOuterTimeRange` : checked - `build_IFSSynopticSingleLoop_ShapeWindows` : checked - `match_AIFSStandardSingleLoop_Shape` : checked - `build_AIFSStandardSingleLoop_ShapeOuterTimeRange` : checked - `build_AIFSStandardSingleLoop_ShapeWindows` : checked - `match_AIFSFakeDoubleLoopSingleLoop_Shape` : checked - `build_AIFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange` : checked - `build_AIFSFakeDoubleLoopSingleLoop_ShapeWindows` : checked - `match_AIFSFromStartSingleLoopAtZero_Shape` : checked - `build_AIFSFromStartSingleLoopAtZero_ShapeOuterTimeRange` : checked - `build_AIFSFromStartSingleLoopAtZero_ShapeWindows` : checked - `match_AIFSFromStartSingleLoopPositive_Shape` : checked - `build_AIFSFromStartSingleLoopPositive_ShapeOuterTimeRange` : checked - `build_AIFSFromStartSingleLoopPositive_ShapeWindows` : checked - `match_SeasonalSingleLoop_Shape` : checked - `build_SeasonalSingleLoop_ShapeOuterTimeRange` : checked - `build_SeasonalSingleLoop_ShapeWindows` : checked - `match_SeasonalMultiloop_Shape` : checked - `build_SeasonalMultiloop_ShapeOuterTimeRange` : checked - `build_SeasonalMultiloop_ShapeWindows` : checked - `match_IFSStandardMultiLoop_Shape` : checked - `build_IFSStandardMultiLoop_ShapeOuterTimeRange` : checked - `build_IFSStandardMultiLoop_ShapeWindows` : checked - `match_IFSFakeSingleLoopDoubleLoop_Shape` : checked - `build_IFSFakeSingleLoopDoubleLoop_ShapeOuterTimeRange` : checked - `build_IFSFakeSingleLoopDoubleLoop_ShapeWindows` : checked ## Future Raw Check Callback Tracker ### Anchor raw checks - `check_ForecastAnalysis_Anchor` : added - `check_Hindcast_Anchor` : added - `check_SeasonalClimate_Anchor` : added ### Domain raw checks - `check_Analysis_Domain` : added - `check_Forecast_Domain` : added - `check_FromStartForecast_Domain` : added - `check_SeasonalForecast_Domain` : added - `check_SynopticAnalysis_Domain` : added ### Shape raw checks - `check_Instant_Shape` : added - `check_IFSStandardSingleLoop_Shape` : added - `check_IFSFakeDoubleLoopSingleLoop_Shape` : added - `check_IFSFromStartSingleLoopAtZero_Shape` : added - `check_IFSFromStartSingleLoopPositive_Shape` : added - `check_IFSSynopticSingleLoop_Shape` : added - `check_AIFSStandardSingleLoop_Shape` : added - `check_AIFSFakeDoubleLoopSingleLoop_Shape` : added - `check_AIFSFromStartSingleLoopAtZero_Shape` : added - `check_AIFSFromStartSingleLoopPositive_Shape` : added - `check_SeasonalSingleLoop_Shape` : added - `check_SeasonalMultiloop_Shape` : added - `check_IFSStandardMultiLoop_Shape` : added - `check_IFSFakeSingleLoopDoubleLoop_Shape` : added ## Normalization Tracker Target invariants: - all final time ranges in hours; - all final time increments in seconds or missing; - monthly values normalized from real placed intervals; - normalized hour values validated against the agreed whitelists. Current status: - helper added in `detail/ShapeNormalization.h`, top-level wiring not implemented yet. ## Early Input-Contract Refinement Tracker Target change: - update the current step-zero allowance helper so `TypeOfStatisticalProcessing::Missing` is always valid; - add the derived allowance as a dedicated `ProductTimeSpecInput` member; - consume the member from the zero-step shape callbacks instead of recomputing the rule locally. Current status: - implemented. ## Cross-Check Tracker Planned cross checks include: - domain offsets versus real reference-based datetime differences; - synoptic cross consistency; - seasonal cross consistency; - domain/span/window consistency; - normalized whitelist enforcement after artifact interaction is known. Current status: - not implemented. ## Error-Handling Tracker Target state: - all diagnostic JSON best-effort and `noexcept`; - all nontrivial code wrapped in full-body `try/catch` with nested rethrow. Current status: - audit not started. ## `TemporalArithmetic` Tracker Target state: - strong overflow and underflow protection; - month-safe arithmetic; - exact whole-hour support for normalization and domain offsets; - readable and layered failure modes. Current status: - review not started. ## Deferred Decisions Still intentionally deferred: - detailed paranoia/check option structure; - final public-facing markdown structure after the rewrite stabilizes. ## Next Session Entry Point The next session should begin by: 1. updating the living markdown to reflect the completed callback and checker work; 2. adding the raw-to-normalized artifact layer; 3. wiring normalization into the top-level ProductTimeSpec pipeline; 4. adding final cross checks on normalized artifacts; 5. only then beginning the exception-boundary and arithmetic hardening phases. metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/Requirements.md0000664000175000017500000004474715246725757030520 0ustar alastairalastair # ProductTimeSpec Requirements ## Purpose This file is the authoritative task tree for the rewrite and hardening of `mars2grib/backend/models/product-time-spec`. This file is intentionally operational. It must be sufficient to restart the work later without relying on previous chat history. This file is organized as follows: - one tree-style task list near the top; - one detailed section for every leaf task in the tree; - when one task is fully completed, remove its tree item and remove its detailed section. ## Scope This file applies to: - `src/metkit/mars2grib/backend/models/product-time-spec/` - `src/metkit/mars2grib/utils/TemporalArithmetic.h` - related option plumbing under: - `src/metkit/mars2grib/api/` - `src/metkit/mars2grib/utils/dictionary_traits/` ## Task Tree - `5.0` Add raw-to-normalized artifact layer. Normalize callback outputs only after raw build and raw checks succeed. - `5.1` Normalize time ranges to hours. Convert all final ranges to hours using exact placed intervals where monthly semantics are involved. - `5.2` Normalize time increments to seconds or missing. Convert all final increments to seconds or missing only. - `5.3` Validate normalized sub-monthly time ranges. Enforce the language-derived hour whitelist. - `5.4` Validate normalized monthly time ranges. Enforce the ecCodes-derived month-hour whitelist. - `6.0` Add cross anchor/domain/shape checks. Validate consistency only after normalization has completed. - `6.1` Validate domain offsets against reference. Ensure hour offsets exactly match the real datetime differences. - `6.2` Validate shape/domain span relationships. Ensure outer and inner windows remain consistent with domain placement. - `6.3` Validate synoptic cross semantics. Ensure synoptic anchor, domain, and shape facts agree. - `6.4` Validate seasonal cross semantics. Ensure month-based raw semantics normalize consistently. - `7.0` Audit diagnostic JSON. Make all diagnostic JSON helpers best-effort and `noexcept`. - `7.1` Audit nontrivial function exception boundaries. Ensure full-body `try/catch` with nested rethrow everywhere required. - `7.2` Add failure-only templated matcher diagnostics. Keep matcher logic single-sourced while allowing the failure path to rerun matchers in debug mode and collect the composed result plus all named conditions. - `8.0` Harden `TemporalArithmetic`. Review logic, precision, overflow, and whole-hour/whole-second safety. - `8.1` Add exact whole-hour helpers. Provide the helpers needed by normalization and domain-offset validation. - `8.2` Harden month-based arithmetic. Make month handling safe, explicit, and suitable for exact hour derivation. - `9.0` Rewrite code documentation. Update file headers and function documentation after the implementation is stabilized. ## Task Details Tasks `0.x` through `4.x` are implemented in code. The remaining active work in this file begins at the raw-to-normalized artifact layer. The normalization stage currently targets only the final shape windows. The domain artifact remains unchanged and supplies the real placement needed for month-based range normalization. ### `0.0` Extend normalized input with precomputed step-zero statistical-processing allowance Before the callback-by-callback rewrite begins, extend the normalized input contract with a precomputed boolean describing whether the innermost `TypeOfStatisticalProcessing` is allowed in the step-zero from-start special case. This is intentionally an early task because it changes the input contract and simplifies later callback reviews. This rule is considered an input policy fact rather than shape-construction logic and is therefore allowed to live in `ProductTimeSpecInput`. ### `0.1` Update `isAllowed_InnerTypeOfStatisticalProcessingAtStepZero` semantics The helper currently in `shapes/ShapeUtils.h` must be reviewed and its semantics adjusted. Required change: - if `innerTypeOfStatisticalProcessing` is `TypeOfStatisticalProcessing::Missing`, the result must be `true`. Reason: - `Missing` means the instant case; - the instant case is always valid for this allowance test; - keeping that rule explicit in the helper makes the later derived input member semantically correct. ### `0.2` Add precomputed step-zero allowance to `ProductTimeSpecInput` Add a new boolean member to `ProductTimeSpecInput` representing the result of the step-zero statistical-processing allowance rule. Preferred semantic meaning: - this member should express whether the innermost statistical processing is allowed in the zero-step from-start special case. The purpose of this member is: - remove repeated local recomputation in callbacks; - make the input contract richer and more explicit; - improve readability during callback review. ### `0.3` Compute the new input member during `make_ProductTimeSpecInput_or_throw` The new boolean member must be computed during normalized input assembly. The computation should use: - the resolved `innerMostTypeOfStatisticalProcessing`; - `allowExtendedSetOfOperationsForZeroLengthFsWindow`; - the reviewed helper semantics from task `0.1`. This derived fact should be available to later callbacks without requiring callback-local policy recomputation. ### `0.4` Update zero-step shape callbacks to consume the input member After the normalized input member exists, the step-zero shape callbacks should consume that member instead of recomputing the allowance locally. This applies at minimum to the current zero-step from-start shape matchers. The goal is: - simpler callback logic; - fewer hidden policy decisions in callbacks; - better callback reviewability. ### `1.0` Extend raw domain artifact Update `ProductTimeSpecDomain` while keeping the existing absolute fields. The raw domain artifact must retain: - `domainStartDateTime` - `domainEndDateTime` The raw domain artifact must add: - `bool isSynoptic` - `long startOffsetHoursFromReference` - `long endOffsetHoursFromReference` The purpose of the new fields is: - explicit representation of synoptic semantics; - easier comparison and reconstruction of domains; - direct availability of encoder-facing hour offsets; - support for exact month-based normalization after callback execution. This task changes the raw model contract first. It must be done before the full callback rewrite because many later tasks depend on the extended raw domain representation. ### `1.1` Review `match_Analysis_Domain` Review the matcher line by line. Verify: - whether the case boundary is complete; - whether it overlaps with any other domain matcher; - whether helper logic should be pulled into the callback for clarity; - whether the comments still match the implementation. No blind edit is allowed. ### `1.2` Review `build_Analysis_Domain` Rework the builder so it becomes leaf logic. The final callback should visibly compute: - start datetime; - end datetime; - `isSynoptic`; - start offset from reference in hours; - end offset from reference in hours. The builder should not hide semantic control flow in nested helpers except for `TemporalArithmetic` and trivial low-level primitives. ### `1.3` Review `match_Forecast_Domain` Review the matcher line by line. Verify: - the non-synoptic condition; - the non-seasonal condition; - the forecast condition; - the non-from-start condition; - exclusivity versus the other forecast-domain matchers. ### `1.4` Review `build_Forecast_Domain` Rework the builder so it visibly computes all raw domain members. The domain logic should remain readable and explicit. ### `1.5` Review `match_FromStartForecast_Domain` Review the matcher line by line. Verify: - the from-start condition; - exclusivity versus normal forecast and seasonal forecast domain cases; - whether helper logic should be local. ### `1.6` Review `build_FromStartForecast_Domain` Rework the builder so it visibly computes the raw from-start domain and all raw domain members. ### `1.7` Review `match_SeasonalForecast_Domain` Review the matcher line by line. Verify: - the seasonal discriminator use; - the non-synoptic condition; - the forecast condition; - exclusivity versus the other forecast-domain cases. ### `1.8` Review `build_SeasonalForecast_Domain` Rework the builder so it visibly computes the raw month-based domain and all raw domain members. Month semantics must remain raw here and must not be prematurely normalized. ### `1.9` Review `match_SynopticAnalysis_Domain` Review the matcher line by line. Verify: - explicit synoptic condition; - IFS restriction; - analysis restriction; - exclusivity versus all non-synoptic domain cases. ### `1.10` Review `build_SynopticAnalysis_Domain` Rework the builder so it visibly computes the raw synoptic domain and all raw domain members. The implementation should preserve the real placed datetimes while making the synoptic nature explicit in `isSynoptic` and the reference-relative hour offsets. ### `2.0` Add per-anchor check callbacks Introduce one check callback per anchor case and dispatch them immediately after raw anchor construction. These checks are raw-output checks, not normalization checks. They should validate: - required field presence; - ordering invariants; - case-specific anchor semantics. ### `2.1` Review `match_ForecastAnalysis_Anchor` Review the matcher line by line for correctness, exclusivity, and readability. ### `2.2` Review `build_ForecastAnalysis_Anchor` Rework the builder so it visibly computes each anchor field locally and clearly. ### `2.3` Review `match_Hindcast_Anchor` Review the matcher line by line for correctness, exclusivity, and readability. ### `2.4` Review `build_Hindcast_Anchor` Rework the builder so it visibly computes each anchor field locally and clearly. ### `2.5` Review `match_SeasonalClimate_Anchor` Review the matcher line by line for correctness, exclusivity, and readability. ### `2.6` Review `build_SeasonalClimate_Anchor` Rework the builder so it visibly computes each anchor field locally and clearly. ### `3.0` Add per-domain check callbacks Introduce one check callback per domain case and dispatch them immediately after raw domain construction. These checks should validate: - ordering; - synoptic flag correctness; - offset correctness at the raw level when possible; - case-specific domain semantics. ### `4.0` Add per-shape check callbacks Introduce one check callback per shape case and dispatch them immediately after raw shape construction. These checks should validate: - expected number of windows; - required presence or absence of raw values; - raw ordering; - case-specific semantic invariants. ### `4.1` to `4.42` Review shape callbacks one by one Each shape callback task listed in the tree is intentionally separate because no shape callback may be modified blindly. For every shape matcher review: - verify case boundaries; - verify exclusivity; - verify comments versus implementation; - decide whether helper logic must become local. For every shape outer-range builder review: - verify what is raw and what must remain deferred; - keep month semantics raw where needed; - keep the logic visible and local. For every shape window builder review, enforce this mandatory structure: 1. input validation; 2. one compact documented block of code for each member of `ProductTimeSpecWindow`; 3. members validation; 4. final construction of the window from already assigned local variables. When more than one window is built, apply the same pattern inside a loop. Every per-member compact block must be documented with an explanation of why that field is computed that way for that specific case. This is a hard readability and reviewability requirement. ### `5.0` Add raw-to-normalized artifact layer Introduce a clear stage that transforms raw callback outputs into normalized artifacts. Raw callback outputs are allowed to preserve natural case semantics. Normalized artifacts must be encoder-facing and canonical. This stage should run only after: - raw anchor checks succeed; - raw domain checks succeed; - raw shape checks succeed. Current implementation direction: - normalize only `shape::ProductTimeSpecShape`; - keep `domain::ProductTimeSpecDomain` unchanged; - store only normalized windows in the final `ProductTimeSpec` object. ### `5.1` Normalize time ranges to hours All final time ranges must be normalized to hours. Sub-monthly ranges must be normalized to the allowed hour values. Month-based ranges must be normalized using the exact placed interval and the real month length in that exact year and month. For month-based shape windows, the placed interval begins at the real domain start: - non-synoptic: `domain.domainStartDateTime`; - synoptic: midnight of `domain.domainStartDateTime.date()`. The outermost normalized window range must equal the real domain span in hours. ### `5.2` Normalize time increments to seconds or missing All final time increments must be represented as either: - seconds; or - missing. No final normalized increment should remain in hour, day, or month units. Trivially convertible units should be converted directly to seconds. Month-valued final increments are not supported and must be rejected. ### `5.3` Validate normalized sub-monthly time ranges Validate normalized sub-monthly hour values against the language-derived source of truth. The currently mirrored allowed values visible in `deductions/timespan.h` are: - `1` - `3` - `6` - `12` - `18` - `24` - `48` - `72` - `120` - `168` - `240` - `360` The source of truth remains the language definition. ### `5.4` Validate normalized monthly time ranges Validate normalized month-based hour values against the ecCodes-derived allowed set: - `672` - `696` - `720` - `744` These values must be derived from the exact placed interval before validation. ### `6.0` Add cross anchor/domain/shape checks Add a dedicated post-normalization validation stage that checks consistency across the three categories. These checks are cross-artifact checks and should not be mixed with the raw callback checks. ### `6.1` Validate domain offsets against reference Ensure that: - `startOffsetHoursFromReference` - `endOffsetHoursFromReference` exactly match the corresponding datetime differences from `anchor.referenceDateTime`. ### `6.2` Validate shape/domain span relationships Ensure that the normalized shapes remain consistent with the normalized domain placement and span. This includes from-start and multi-window cases. ### `6.3` Validate synoptic cross semantics Ensure that synoptic anchor, domain, and shape facts are mutually consistent. ### `6.4` Validate seasonal cross semantics Ensure that month-based seasonal semantics normalize consistently and remain valid across anchor, domain, and shape. ### `7.0` Audit diagnostic JSON Every diagnostic JSON helper used in exception enrichment must become: - best-effort; - `noexcept`; - stable under failure. No diagnostic JSON function may throw while a higher-level function is handling an exception. ### `7.1` Audit nontrivial function exception boundaries Every nontrivial function should follow the full-body pattern: ```cpp try { // possibly faulty code } catch (...) { std::throw_with_nested(...); } ``` This applies to: - callbacks; - check callbacks; - normalization helpers; - cross-check helpers; - `TemporalArithmetic`; - any other nontrivial model logic. ### `7.2` Add failure-only templated matcher diagnostics Classification failures caused by zero matches or overlapping matches are currently hard to diagnose because the implementation usually exposes only the final count and, in some places, only coarse matcher-level `true` or `false` results. Add a failure-only matcher diagnostic mechanism with the following properties: - matcher logic must remain single-sourced; - the template must remain visible in the code; - the normal matcher instantiation must return `bool`; - the debug matcher instantiation must return `std::string`; - the debug string must contain: - the matcher name; - the final composed matcher result; - the full ordered set of named matcher conditions. The intended design is one visible templated matcher per case, with two compile- time instantiations. The preferred direction is: - `match__(input)` for the standard path; - `match__(input)` for the failure-only diagnostic path. This mechanism is not a normal-path check. It must never run in the hot path. The normal workflow should use only the `bool` matcher instantiation. Only when a classification failure already happened should the error-handling path rerun the matchers in debug mode and collect the resulting strings to add more context to the exception. Because the two matcher instantiations have different return types, the case registry structs should be extended so they can store both: - the normal matcher function pointer; - the debug matcher function pointer. This applies consistently across: - anchor registries; - domain registries; - shape registries. The motivation for this requirement is: - no duplication of matcher logic; - no debug cost in the hot path; - much richer diagnostics for zero-match and overlap failures; - explicit and reviewable matcher semantics. The failure-path debug collection must itself be best-effort. If debug collection fails while enriching an existing exception, that secondary failure must not hide the original failure. ### `8.0` Harden `TemporalArithmetic` Review `TemporalArithmetic` for: - overflow and underflow; - month arithmetic correctness; - whole-second assumptions; - whole-hour support required by normalization; - precision and robustness; - readable failure modes. ### `8.1` Add exact whole-hour helpers Add the helpers required by: - domain offset computation; - normalized hour-range validation; - post-normalization cross-checking. ### `8.2` Harden month-based arithmetic Ensure month-based date arithmetic is safe and suitable for deriving exact hour lengths for concrete placed intervals. ### `9.0` Rewrite code documentation After the code has stabilized: - update file headers; - update function documentation; - update data-structure documentation; - update licenses if needed for consistency. Markdown rewrite beyond this file and `Status.md` should only happen after the implementation is stabilized. metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/Constraints.md0000664000175000017500000001521015246725757030323 0ustar alastairalastair # ProductTimeSpec Constraints ## Purpose This file lists the hard constraints that must not be violated while executing the rewrite defined in `Requirements.md`. This file is not a task list. ## Operational Constraints - Never build. - Never debug by running the code. - Never commit. - The user builds, debugs, and commits. - While the code is under testing, touch only markdown unless explicitly asked to do otherwise. - Do not change code or code behavior unless explicitly requested in a later implementation step. ## Scope Constraints - During the markdown cleanup phase, touch only markdown files. - The living markdown set for this subtree is: - `Requirements.md` - `Status.md` - `Constraints.md` - All other previous markdown in this subtree is considered temporary and outdated. ## Callback Review Constraints - No callback may be modified blindly. - Every callback must be reviewed one by one before modification. - The callback inventory in `Requirements.md` is the review checklist. - Callback logic should become leaf logic. - Hidden semantic control flow should be minimized. - Small duplication is acceptable when it improves readability, reviewability, and robustness. ## Code Style Constraints - Be consistent with the existing repository style. - Be consistent with the existing style already present in this subtree. - Code must be readable. - Prefer explicit local logic over compressed abstraction when the latter hides semantics. - Preserve readable naming and stable control flow. ## Namespace and `using` Constraints - Every function that is not in the current namespace must be explicitly brought in with a `using` declaration. - The `using` declaration must expose the full namespace path. - Do not use shortened partial namespace paths in those `using` declarations. - Follow the pattern already extensively used in the codebase. ## Shape Callback Structure Constraints For shape window-building callbacks, the preferred structure is mandatory. The callback must be organized as follows: 1. input validation; 2. one compact block of code for each member of `ProductTimeSpecWindow`; 3. members validation; 4. construction of the final window from the local variables already assigned and explained. When multiple windows are needed: - the same logic must be applied inside a loop; - each produced window must still follow the same field-by-field structure. ## Per-Member Documentation Constraints - Every compact block that computes one member of `ProductTimeSpecWindow` must be documented. - The documentation must explain why that field is computed that way for that specific case. - The code must be reviewable case by case without jumping through helper layers. - Window construction should happen only after the local member variables are already assigned and explained. ## Data Model Constraints - Keep the current absolute domain datetimes. - Extend `ProductTimeSpecDomain` with: - `bool isSynoptic` - `long startOffsetHoursFromReference` - `long endOffsetHoursFromReference` - Synoptic semantics must remain explicitly representable. - The raw model must preserve enough information for reconstruction, comparison, normalization, and cross-checking. ## Pipeline Constraints - Input-policy facts may be precomputed in `ProductTimeSpecInput` when doing so reduces callback-local recomputation and improves callback readability. - Seasonal class/stream semantics and seasonal lead semantics are distinct facts and must not be conflated. - Seasonal class/stream semantics are: - `marsClass` in `od`, `rd`, `c3`; - `marsStream` in `sfmd`, `shmd`. - Seasonal lead semantics are: - `step` absent; - `marsFcmonth` present. - Seasonal shape and domain cases must require both facts together. - Non-seasonal shape and domain cases must reject the conjunction of both facts. - Normalization must happen after raw callbacks. - Per-callback raw checks must happen before normalization. - Cross anchor/domain/shape checks must happen after normalization. - Month-based raw semantics must remain raw until the exact placed interval is available. ## Input Contract Constraints - The step-zero statistical-processing allowance may be represented as a dedicated derived member of `ProductTimeSpecInput`. - If `innerMostTypeOfStatisticalProcessing` is `TypeOfStatisticalProcessing::Missing`, the step-zero allowance must evaluate to valid because it represents the instant case. - Zero-step shape callbacks should consume the derived input fact rather than recomputing the policy locally once that member exists. ## Unit Constraints - Final encoder-facing time ranges must be in hours. - Final encoder-facing time increments must be in seconds or missing. - Trivially convertible time-range units may be converted directly, but sub-hour final ranges are not supported. - Month-valued final time ranges must be converted to hours using the exact placed interval starting at the real domain start. - For synoptic domains, the real domain start used by normalization is midnight of `domainStartDateTime.date()`. - Final month-valued time increments are not supported. - Monthly hour values must be derived from the exact placed interval, not from a fixed approximation. - Sub-monthly normalized hour validation source of truth is the language timespan support set. - Monthly normalized hour validation source of truth is the ecCodes month-length concept space represented by: - `672` - `696` - `720` - `744` ## Error-Handling Constraints - All diagnostic JSON used in exception enrichment must be best-effort. - All diagnostic JSON used in exception enrichment must be `noexcept`. - Diagnostic JSON must never throw while building exception context. - All other nontrivial code must wrap the full body in `try/catch` and rethrow with nested context. ## Helper Layer Constraints - `TemporalArithmetic` is the main allowed shared semantic helper layer. - Other helper layers should be reduced when they hide too much callback logic. - Helper extraction is acceptable only when it preserves or improves reviewability. ## Documentation Constraints - In-code documentation must be improved after the implementation stabilizes. - Markdown beyond the living files should be rewritten only after the code shape stabilizes. - License headers must remain consistent. metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/0000775000175000017500000000000015246725757026735 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/ShapeNormalization.h0000664000175000017500000003257015246725757032724 0ustar alastairalastair/* * (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 ShapeNormalization.h /// @brief Normalize raw ProductTimeSpec windows to encoder-facing units. /// /// This internal header owns the raw-to-normalized shape transformation used /// after raw callback checks succeed. The normalization is intentionally local /// and explicit: /// - `timeRange` becomes hours; /// - `timeIncrement` becomes seconds or missing; /// - month-based `timeRange` values are converted using the exact placed /// interval derived from the resolved domain. /// /// The domain artifact is not modified. It is used only to recover the real /// placement needed for month-based hour derivation and the outermost window /// span check. /// /// @ingroup mars2grib_product_time_spec_detail /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::detail { /// @brief Normalize one raw canonical shape to encoder-facing window units. /// /// Normalization rules: /// - `timeRange` becomes whole hours; /// - `timeIncrement` becomes whole seconds or missing; /// - month-based `timeRange` values are converted using the exact placed /// interval starting at the real domain start; /// - the outermost normalized `timeRange` must equal the real domain span. /// /// @param[in] input Fully normalized ProductTimeSpec input snapshot. /// @param[in] domain Resolved raw ProductTimeSpec domain. /// @param[in] rawShape Resolved raw ProductTimeSpec windows. /// @return Normalized ProductTimeSpec windows. /// @throws Mars2GribModelException If normalization or validation fails. inline shape::ProductTimeSpecShape normalizeShape_or_throw(const ProductTimeSpecInput& input, const domain::ProductTimeSpecDomain& domain, const shape::ProductTimeSpecShape& rawShape) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::addDuration; using metkit::mars2grib::utils::time_arithmetic::defaultMarsTime; using metkit::mars2grib::utils::time_arithmetic::durationBetween; using metkit::mars2grib::utils::time_arithmetic::makeDateTime; try { if (rawShape.values.empty()) { throw Mars2GribModelException("Cannot normalize an empty ProductTimeSpec shape", input.to_json(), Here()); } const auto convertTriviallyConvertibleDurationToSeconds = [&](const TimeDuration& duration, const char* name) { try { long long seconds = 0; switch (duration.unit) { case TimeUnit::Second: return duration.length; case TimeUnit::Minute: seconds = static_cast(duration.length) * 60LL; break; case TimeUnit::Hour: seconds = static_cast(duration.length) * 3600LL; break; case TimeUnit::Hours3: seconds = static_cast(duration.length) * 10800LL; break; case TimeUnit::Hours6: seconds = static_cast(duration.length) * 21600LL; break; case TimeUnit::Hours12: seconds = static_cast(duration.length) * 43200LL; break; case TimeUnit::Day: seconds = static_cast(duration.length) * 86400LL; break; case TimeUnit::Month: throw Mars2GribModelException( std::string{"Cannot trivially convert month-valued `"} + name + "` to seconds", input.to_json(), Here()); case TimeUnit::Year: case TimeUnit::Decade: case TimeUnit::Normal: case TimeUnit::Century: throw Mars2GribModelException( std::string{"Cannot trivially convert calendar-valued `"} + name + "` to seconds", input.to_json(), Here()); case TimeUnit::Missing: throw Mars2GribModelException(std::string{"Cannot normalize missing `"} + name + "`", input.to_json(), Here()); default: throw Mars2GribModelException(std::string{"Unsupported time unit in `"} + name + "`", input.to_json(), Here()); } if (seconds > static_cast(std::numeric_limits::max()) || seconds < static_cast(std::numeric_limits::min())) { throw Mars2GribModelException(std::string{"Normalized `"} + name + "` in seconds is out of range", input.to_json(), Here()); } return static_cast(seconds); } catch (...) { std::throw_with_nested(Mars2GribModelException( std::string{"Failed to normalize `"} + name + "` to seconds", input.to_json(), Here())); } }; const auto normalizeTimeRangeToHours = [&](const TimeDuration& rawTimeRange) { try { if (rawTimeRange.unit == TimeUnit::Month) { const eckit::DateTime normalizationStart = domain.isSynoptic ? makeDateTime(domain.domainStartDateTime.date(), defaultMarsTime()) : domain.domainStartDateTime; const eckit::DateTime normalizationEnd = addDuration(normalizationStart, rawTimeRange); const TimeDuration placedDuration = durationBetween(normalizationStart, normalizationEnd); if (placedDuration.unit != TimeUnit::Second) { throw Mars2GribModelException( "Placed month-based ProductTimeSpec range did not resolve to seconds", input.to_json(), Here()); } if (placedDuration.length % 3600 != 0) { throw Mars2GribModelException( "Month-based ProductTimeSpec range does not convert to whole hours", input.to_json(), Here()); } return TimeDuration{placedDuration.length / 3600, TimeUnit::Hour}; } const long seconds = convertTriviallyConvertibleDurationToSeconds(rawTimeRange, "timeRange"); if (seconds % 3600 != 0) { throw Mars2GribModelException("Sub-hour ProductTimeSpec timeRange values are not supported", input.to_json(), Here()); } return TimeDuration{seconds / 3600, TimeUnit::Hour}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to normalize ProductTimeSpec timeRange to hours", input.to_json(), Here())); } }; const auto normalizeTimeIncrementToSecondsOrMissing = [&](const TimeDuration& rawTimeIncrement) { try { if (rawTimeIncrement.unit == TimeUnit::Missing) { return missingIncrement(); } if (rawTimeIncrement.unit == TimeUnit::Month) { throw Mars2GribModelException("Month-valued ProductTimeSpec timeIncrement is not supported", input.to_json(), Here()); } const long seconds = convertTriviallyConvertibleDurationToSeconds(rawTimeIncrement, "timeIncrement"); return TimeDuration{seconds, TimeUnit::Second}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to normalize ProductTimeSpec timeIncrement to seconds or missing", input.to_json(), Here())); } }; constexpr std::array allowedSubmonthlyHours{ {1L, 3L, 6L, 12L, 18L, 24L, 48L, 72L, 120L, 168L, 240L, 360L}}; constexpr std::array allowedMonthlyHours{{672L, 696L, 720L, 744L}}; shape::ProductTimeSpecShape normalizedShape; normalizedShape.values.reserve(rawShape.values.size()); for (std::size_t i = 0; i < rawShape.values.size(); ++i) { const shape::ProductTimeSpecWindow& rawWindow = rawShape.values[i]; const TimeDuration normalizedTimeRange = normalizeTimeRangeToHours(rawWindow.timeRange); const TimeDuration normalizedTimeIncrement = normalizeTimeIncrementToSecondsOrMissing(rawWindow.timeIncrement); const bool rawTimeRangeIsMonthly = rawWindow.timeRange.unit == TimeUnit::Month; if (!input.allowNonEnumeratedPositiveIntegerTimespanHours && normalizedTimeRange.length > 0) { const bool allowedHourValue = rawTimeRangeIsMonthly ? std::find(allowedMonthlyHours.begin(), allowedMonthlyHours.end(), normalizedTimeRange.length) != allowedMonthlyHours.end() : std::find(allowedSubmonthlyHours.begin(), allowedSubmonthlyHours.end(), normalizedTimeRange.length) != allowedSubmonthlyHours.end(); if (!allowedHourValue) { throw Mars2GribModelException( rawTimeRangeIsMonthly ? "Normalized month-based ProductTimeSpec timeRange is not in the allowed monthly hour " "set: " + std::to_string(normalizedTimeRange.length) : "Normalized sub-monthly ProductTimeSpec timeRange is not in the allowed hour set: " + std::to_string(normalizedTimeRange.length), input.to_json(), Here()); } } normalizedShape.values.push_back( shape::ProductTimeSpecWindow{rawWindow.typeOfStatisticalProcessing, rawWindow.typeOfTimeIncrement, normalizedTimeRange, normalizedTimeIncrement}); } const eckit::DateTime realDomainStart = domain.isSynoptic ? makeDateTime(domain.domainStartDateTime.date(), defaultMarsTime()) : domain.domainStartDateTime; const TimeDuration realDomainSpan = durationBetween(realDomainStart, domain.domainEndDateTime); if (realDomainSpan.unit != TimeUnit::Second) { throw Mars2GribModelException("Real ProductTimeSpec domain span did not resolve to seconds", input.to_json(), Here()); } if (realDomainSpan.length % 3600 != 0) { throw Mars2GribModelException("Real ProductTimeSpec domain span does not convert to whole hours", input.to_json(), Here()); } const long realDomainSpanInHours = realDomainSpan.length / 3600; const shape::ProductTimeSpecWindow& outermostWindow = normalizedShape.values.front(); if (outermostWindow.timeRange.unit != TimeUnit::Hour || outermostWindow.timeRange.length != realDomainSpanInHours) { throw Mars2GribModelException( "Normalized outermost ProductTimeSpec timeRange does not match the real domain span", input.to_json(), Here()); } return normalizedShape; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to normalize the ProductTimeSpec shape", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h0000664000175000017500000004570215246725757031661 0ustar alastairalastair/* * (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 TimeIncrement.h /// @brief Cross-cutting time-increment semantics shared by ProductTimeSpec shapes. /// /// This internal header contains the shared ProductTimeSpec logic for /// `timeIncrement` and `typeOfTimeIncrement`. It must not hide case-level /// control flow that belongs in anchor, domain, or shape leaf builders. /// /// Every function has a documented contract, catches all failures, and rethrows /// `Mars2GribModelException` directly at the function boundary. Functions that /// receive `ProductTimeSpecInput` attach `input.to_json()`; lower-level /// functions use the location-only constructor. /// /// @ingroup mars2grib_product_time_spec_detail /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/backend/tables/typeOfTimeIntervals.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" namespace metkit::mars2grib::backend::models::product_time_spec::detail { /** * @brief Return the canonical value used when a time increment is semantically missing. * * The ProductTimeSpec representation stores a zero-second duration together with * `TypeOfTimeIntervals::Missing` when the increment is absent. * * @return Canonical missing-increment duration. * @throws Mars2GribModelException If construction of the duration unexpectedly fails. */ inline metkit::mars2grib::backend::deductions::TimeDuration missingIncrement() { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return {0, TimeUnit::Missing}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `missingIncrement`", Here())); } } /** * @brief Return true when a duration represents exactly one elapsed hour. * * Both `{Hour, 1}` and `{Second, 3600}` are accepted because normalized inputs may * preserve either unit. * * @param[in] input Normalized input used to enrich exception diagnostics. * @param[in] duration Duration to inspect. * @return `true` when the duration is exactly one hour. * @throws Mars2GribModelException If evaluation unexpectedly fails. */ inline bool isOneHour(const ProductTimeSpecInput& input, const metkit::mars2grib::backend::deductions::TimeDuration& duration) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isOneHourInHours = duration.unit == TimeUnit::Hour && duration.length == 1; const bool isOneHourInSeconds = duration.unit == TimeUnit::Second && duration.length == 3600; return isOneHourInHours || isOneHourInSeconds; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `isOneHour`", input.to_json(), Here())); } } /** * @brief Convert an elapsed duration to seconds for increment validation. * * Calendar-month ranges are intentionally rejected because their length cannot be * converted to seconds without a concrete calendar interval. * * @param[in] input Normalized input used to enrich exception diagnostics. * @param[in] duration Duration to convert. * @return Duration expressed in seconds. * @throws Mars2GribModelException For unsupported or calendar units. */ inline long durationInSecondsForValidation(const ProductTimeSpecInput& input, const metkit::mars2grib::backend::deductions::TimeDuration& duration) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::detail::checkedSecondsFromUnits; try { long long seconds = 0; switch (duration.unit) { case TimeUnit::Second: return duration.length; case TimeUnit::Hour: seconds = checkedSecondsFromUnits(duration.length, 3600LL); break; case TimeUnit::Day: seconds = checkedSecondsFromUnits(duration.length, 86400LL); break; case TimeUnit::Month: throw Mars2GribModelException("Month-range increment validation requires calendar-aware comparison", input.to_json(), Here()); default: throw Mars2GribModelException( "Unsupported metkit::mars2grib::backend::deductions::TimeDuration unit during increment validation", input.to_json(), Here()); } if (seconds > static_cast(std::numeric_limits::max()) || seconds < static_cast(std::numeric_limits::min())) { throw Mars2GribModelException("Increment-validation duration in seconds is out of range for a long", input.to_json(), Here()); } return static_cast(seconds); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `durationInSecondsForValidation`", input.to_json(), Here())); } } /** * @brief Return the GRIB missing sentinel for typeOfTimeIncrement. * @return `TypeOfTimeIntervals::Missing`. * @throws Mars2GribModelException If obtaining the sentinel unexpectedly fails. */ inline metkit::mars2grib::backend::tables::TypeOfTimeIntervals missingTypeOfTimeIncrement() { using metkit::mars2grib::backend::tables::TypeOfTimeIntervals; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return TypeOfTimeIntervals::Missing; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `missingTypeOfTimeIncrement`", Here())); } } /** * @brief Return GRIB typeOfTimeIncrement value 2 used by forecast semantics. * @return GRIB code-table value 2. * @throws Mars2GribModelException If conversion unexpectedly fails. */ inline metkit::mars2grib::backend::tables::TypeOfTimeIntervals forecastTypeOfTimeIncrement() { using metkit::mars2grib::backend::tables::TypeOfTimeIntervals; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return TypeOfTimeIntervals::SameForecastTimeStartIncremented; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `forecastTypeOfTimeIncrement`", Here())); } } /** * @brief Return GRIB typeOfTimeIncrement value 1 used by analysis semantics. * @return GRIB code-table value 1. * @throws Mars2GribModelException If conversion unexpectedly fails. */ inline metkit::mars2grib::backend::tables::TypeOfTimeIntervals analysisTypeOfTimeIncrement() { using metkit::mars2grib::backend::tables::TypeOfTimeIntervals; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return TypeOfTimeIntervals::SameStartTimeForecastIncremented; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `analysisTypeOfTimeIncrement`", Here())); } } /** * @brief Resolve typeOfTimeIncrement for one IFS canonical window. * * Forecast windows use value 2. Analysis windows normally use value 1. The only * exception is the innermost one-hour window of an analysis multi-loop product, * which uses value 2. * * @param[in] input Normalized input containing analysis/forecast classification. * @param[in] isMultiLoop `true` when the final shape contains multiple windows. * @param[in] isInnermost `true` for the innermost canonical window. * @param[in] timeRange Canonical range of the inspected window. * @return GRIB typeOfTimeIncrement for the window. * @throws Mars2GribModelException For unsupported `Other` semantics. */ inline metkit::mars2grib::backend::tables::TypeOfTimeIntervals typeOfTimeIncrementForWindow( const ProductTimeSpecInput& input, bool isMultiLoop, bool isInnermost, const metkit::mars2grib::backend::deductions::TimeDuration& timeRange) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isForecast = input.simulationType == metkit::mars2grib::backend::deductions::SimulationType::Forecast; const bool isAnalysis = input.simulationType == metkit::mars2grib::backend::deductions::SimulationType::Analysis; const bool isOneHourInnermostAnalysisLoop = isAnalysis && isMultiLoop && isInnermost && isOneHour(input, timeRange); // if (isForecast || isOneHourInnermostAnalysisLoop) { return forecastTypeOfTimeIncrement(); //} // if (isAnalysis) { // return analysisTypeOfTimeIncrement(); //} throw Mars2GribModelException("typeOfTimeIncrement cannot be assigned to AnalysisOrForecast::Other", input.to_json(), Here()); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `typeOfTimeIncrementForWindow`", input.to_json(), Here())); } } /** * @brief Fully resolved semantics of the innermost time increment. */ struct ResolvedInnerIncrement { metkit::mars2grib::backend::deductions::TimeDuration timeIncrement; metkit::mars2grib::backend::tables::TypeOfTimeIntervals typeOfTimeIncrement; }; /** * @brief Validate an explicit or defaulted increment against the innermost range. * * The increment must be positive and may not exceed a non-calendar innermost * range. A zero-length from-start field may bypass the range comparison when that * special case has already been accepted by the relevant shape builder. * * @param[in] input Normalized input used for diagnostics. * @param[in] incrementInSeconds Increment to validate. * @param[in] innerRange Innermost canonical time range. * @param[in] allowZeroLengthFromStart Whether the zero-length from-start exception applies. * @throws Mars2GribModelException If validation fails. */ inline void validateExplicitIncrement(const ProductTimeSpecInput& input, long incrementInSeconds, const metkit::mars2grib::backend::deductions::TimeDuration& innerRange, bool allowZeroLengthFromStart) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isPositive = incrementInSeconds > 0; const bool isAllowedZeroLengthFromStart = innerRange.length == 0 && allowZeroLengthFromStart; const bool isCalendarMonth = innerRange.unit == TimeUnit::Month; if (!isPositive) { throw Mars2GribModelException("Explicit or defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (isAllowedZeroLengthFromStart) { return; } if (!isCalendarMonth && incrementInSeconds > durationInSecondsForValidation(input, innerRange)) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `validateExplicitIncrement`", input.to_json(), Here())); } } /** * @brief Deduce a missing IFS increment when defaulting is enabled. * * This function is the single extension point for the intentionally complex * default-increment algorithm. The current draft consumes the normalized * `defaultTimeIncrementInSeconds` option; the final implementation may inspect * additional input and domain facts without changing any shape builder. * * @param[in] input Normalized input and embedded options. * @param[in] innerRange Innermost canonical time range. * @return Positive default increment expressed in seconds. * @throws Mars2GribModelException If no valid default can be deduced. */ inline long deduceDefaultTimeIncrement( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::deductions::TimeDuration& innerRange) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)innerRange; throw Mars2GribModelException("Default time-increment deduction not implemented", input.to_json(), Here()); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `deduceDefaultTimeIncrement`", input.to_json(), Here())); } } /** * @brief Resolve explicit, missing, or defaulted IFS innermost increment semantics. * * Resolution follows one of three paths: * * - explicit source value: validate and use it; * - missing source value with defaulting disabled: encode a missing increment; * - missing source value with defaulting enabled: call `deduceDefaultTimeIncrement`, * validate the result, and use it. * * @param[in] input Normalized input and embedded options. * @param[in] innerRange Innermost canonical time range. * @param[in] isMultiLoop Whether the final shape contains multiple windows. * @param[in] allowZeroLengthFromStart Whether the zero-length from-start exception applies. * @return Fully resolved innermost increment semantics. * @throws Mars2GribModelException If explicit or defaulted values are invalid. */ inline ResolvedInnerIncrement resolveIfsInnerIncrement( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::deductions::TimeDuration& innerRange, bool isMultiLoop, bool allowZeroLengthFromStart = false) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { const bool hasExplicitIncrement = input.timeIncrement.has_value(); const bool defaultingIsEnabled = input.allowDefaultTimeIncrement; if (hasExplicitIncrement) { auto incrementInSeconds = convertToSeconds(*input.timeIncrement); validateExplicitIncrement(input, incrementInSeconds, innerRange, allowZeroLengthFromStart); return ResolvedInnerIncrement{ metkit::mars2grib::backend::deductions::TimeDuration{incrementInSeconds, TimeUnit::Second}, typeOfTimeIncrementForWindow(input, isMultiLoop, true, innerRange)}; } if (!defaultingIsEnabled) { return ResolvedInnerIncrement{missingIncrement(), missingTypeOfTimeIncrement()}; } const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, innerRange); validateExplicitIncrement(input, defaultIncrementInSeconds, innerRange, allowZeroLengthFromStart); return ResolvedInnerIncrement{ metkit::mars2grib::backend::deductions::TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}, typeOfTimeIncrementForWindow(input, isMultiLoop, true, innerRange)}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `resolveIfsInnerIncrement`", input.to_json(), Here())); } } /** * @brief Resolve the intrinsic twenty-four-hour increment of synoptic analysis. * * A source increment is redundant. It is accepted only when redundant values are * enabled and its value is exactly 86400 seconds. The returned semantic increment * is always twenty-four hours with typeOfTimeIncrement value 1. * * @param[in] input Normalized synoptic input and embedded options. * @return Intrinsic synoptic increment semantics. * @throws Mars2GribModelException If a redundant value is forbidden or wrong. */ inline ResolvedInnerIncrement resolveSynopticIncrement( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { constexpr long expectedIncrementInSeconds = 86400L; const bool hasExplicitIncrement = input.timeIncrement.has_value(); const bool redundantIncrementIsAllowed = input.allowRedundantTimeIncrement; const bool explicitIncrementHasExpectedValue = !hasExplicitIncrement || convertToSeconds(*input.timeIncrement) == expectedIncrementInSeconds; if (hasExplicitIncrement && !redundantIncrementIsAllowed) { throw Mars2GribModelException( "Synoptic timeIncrementInSeconds is redundant but redundant values are disabled", input.to_json(), Here()); } if (!explicitIncrementHasExpectedValue) { throw Mars2GribModelException("Synoptic timeIncrementInSeconds must equal 86400", input.to_json(), Here()); } return ResolvedInnerIncrement{metkit::mars2grib::utils::time_arithmetic::twentyFourHours(), analysisTypeOfTimeIncrement()}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `resolveSynopticIncrement`", input.to_json(), Here())); } } /** * @brief Validate a redundant source increment attached to an instant product. * * Instant products do not require an increment. A present source value is * therefore accepted only when redundant increments are explicitly enabled. * * @param[in] input Normalized instant input and embedded options. * @throws Mars2GribModelException If a redundant value is forbidden. */ inline void validateInstantIncrement( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasIncrement = input.timeIncrement.has_value(); const bool redundantIncrementIsAllowed = input.allowRedundantTimeIncrement; if (hasIncrement && !redundantIncrementIsAllowed) { throw Mars2GribModelException( "Instant timeIncrementInSeconds is redundant but redundant values are disabled", input.to_json(), Here()); } } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `validateInstantIncrement`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::detail ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.0000664000175000017500000001276115246725757033672 0ustar alastairalastair/* * (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 ProductTimeSpecJsonUtils.h /// @brief Internal JSON helpers for ProductTimeSpec model-input diagnostics. /// #pragma once #include #include #include #include "eckit/types/Date.h" #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/stattype.h" #include "metkit/mars2grib/backend/deductions/timespan.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" namespace metkit::mars2grib::backend::models::product_time_spec::detail { inline std::string jsonQuote_modelInput(const std::string& value) { std::ostringstream out; out << '"'; for (unsigned char c : value) { switch (c) { case '\\': out << "\\\\"; break; case '"': out << "\\\""; break; case '\n': out << "\\n"; break; case '\r': out << "\\r"; break; case '\t': out << "\\t"; break; default: out << static_cast(c); break; } } out << '"'; return out.str(); } inline std::string productTimeSpecDateTimeJson(const eckit::DateTime& value) { return jsonQuote_modelInput(value.iso(true)); } inline std::string optionalDateTimeJson_modelInput(const std::optional& value) { if (!value.has_value()) { return "null"; } return jsonQuote_modelInput(value->iso(true)); } /// @brief Serialize an optional `eckit::Date` as JSON. /// @param[in] value Optional normalized date value. /// @return JSON `null` when absent, otherwise one quoted date string. inline std::string optionalDateJson_modelInput(const std::optional& value) { if (!value.has_value()) { return "null"; } return std::to_string(value->yyyymmdd()); } /// @brief Serialize an optional `eckit::Time` as JSON. /// @param[in] value Optional normalized time value. /// @return JSON `null` when absent, otherwise one quoted time string. inline std::string optionalTimeJson_modelInput(const std::optional& value) { if (!value.has_value()) { return "null"; } return std::to_string(value->hhmmss()); } /// @brief Serialize an optional `long` as JSON. /// @param[in] value Optional normalized integer value. /// @return JSON `null` when absent, otherwise one decimal integer. inline std::string optionalLongJson_modelInput(const std::optional& value) { return value.has_value() ? std::to_string(*value) : std::string{"null"}; } inline std::string durationJson_modelInput(const deductions::TimeDuration& value) { std::ostringstream out; out << '{' << jsonQuote_modelInput("length") << ':' << value.length << ',' << jsonQuote_modelInput("unit") << ':' << jsonQuote_modelInput(tables::enum2name_TimeUnit_or_throw(value.unit)) << '}'; return out.str(); } inline std::string optionalDurationJson_modelInput(const std::optional& value) { return value.has_value() ? durationJson_modelInput(*value) : std::string{"null"}; } inline std::string timespanKindName_modelInput(deductions::TimespanKind value) { switch (value) { case deductions::TimespanKind::Missing: return "missing"; case deductions::TimespanKind::Duration: return "duration"; case deductions::TimespanKind::None: return "none"; case deductions::TimespanKind::FromStart: return "from-start"; } return "invalid"; } inline std::string optionalTimespanJson_modelInput(const std::optional& value) { if (!value.has_value()) { return "null"; } std::ostringstream out; out << '{' << jsonQuote_modelInput("kind") << ':' << jsonQuote_modelInput(timespanKindName_modelInput(value->kind)) << ',' << jsonQuote_modelInput("duration") << ':' << (value->duration.has_value() ? durationJson_modelInput(*value->duration) : std::string{"null"}) << '}'; return out.str(); } inline std::string parsedStattypeBlocksJson_modelInput(const deductions::ParsedStatTypeBlocks& value) { std::ostringstream out; out << '['; for (std::size_t i = 0; i < value.size(); ++i) { if (i != 0) { out << ','; } out << '{' << jsonQuote_modelInput("timeRange") << ':' << durationJson_modelInput(value[i].timeRange) << ',' << jsonQuote_modelInput("typeOfStatisticalProcessing") << ':' << jsonQuote_modelInput( tables::enum2name_TypeOfStatisticalProcessing_or_throw(value[i].typeOfStatisticalProcessing)) << '}'; } out << ']'; return out.str(); } inline std::string optionalStattypeJson_modelInput(const std::optional& value) { return value.has_value() ? parsedStattypeBlocksJson_modelInput(*value) : std::string{"null"}; } } // namespace metkit::mars2grib::backend::models::product_time_spec::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/detail/ForecastLeadUtils.h0000664000175000017500000001346015246725757032467 0ustar alastairalastair/* * (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 ForecastLeadUtils.h /// @brief Shared forecast-lead predicates for ProductTimeSpec input-step semantics. /// /// This internal header centralizes the small normalized-input predicates used /// to inspect `step` and `fcmonth` presence and value shape. /// /// The helpers in this header are intentionally limited to local Boolean input /// inspection. They do not classify complete shape or domain cases and they do /// not perform any temporal arithmetic. /// /// Every function catches all failures and rethrows `Mars2GribModelException` /// directly. Functions receiving normalized input attach `input.to_json()`. /// /// @ingroup mars2grib_product_time_spec_detail /// #pragma once #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::detail { /// /// @brief Test whether the normalized input contains an explicit `step` source. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.step` is present, otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool hasStep(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return input.step.has_value(); } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to test whether ProductTimeSpec input contains step", input.to_json(), Here())); } } /// /// @brief Test whether the normalized input contains an explicit `fcmonth` source. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.marsFcmonth` is present, otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool hasFcmonth(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return input.marsFcmonth.has_value(); } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to test whether ProductTimeSpec input contains fcmonth", input.to_json(), Here())); } } /// /// @brief Test whether the normalized input lacks an explicit `step` source. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.step` is absent, otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool stepIsMissing(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return !hasStep(input); } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to test whether ProductTimeSpec input lacks step", input.to_json(), Here())); } } /// /// @brief Test whether the normalized input contains an explicit zero-valued `step`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.step` is present and its length is exactly zero, /// otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool stepIsZero(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return hasStep(input) && input.step->length == 0; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to test whether ProductTimeSpec input contains a zero step", input.to_json(), Here())); } } /// /// @brief Test whether the normalized input contains an explicit positive `step`. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.step` is present and its length is strictly /// positive, otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool stepIsPositive(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return hasStep(input) && input.step->length > 0; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to test whether ProductTimeSpec input contains a positive step", input.to_json(), Here())); } } /// /// @brief Test whether the normalized input contains an explicit `fcmonth` source. /// /// This helper exists as the positive-form companion of `stepIsMissing(...)` so /// seasonal predicates can read semantically. /// /// @param[in] input Fully normalized ProductTimeSpec input. /// @return `true` when `input.marsFcmonth` is present, otherwise `false`. /// @throws Mars2GribModelException If the check cannot be completed. /// inline bool fcmonthIsPresent(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return hasFcmonth(input); } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to test whether ProductTimeSpec input contains fcmonth", input.to_json(), Here())); } } /// } // namespace metkit::mars2grib::backend::models::product_time_spec::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h0000664000175000017500000002517415246725757033603 0ustar alastairalastair/* * (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 ProductTimeSpecClassification.h /// @brief Shared ProductTimeSpec classification enums, helpers, and bundle. /// /// This header centralizes the classification-only layer shared by anchors, /// domains, shapes, and the top-level ProductTimeSpec orchestration. It owns: /// - the anchor, shape, and domain classification enums; /// - the best-effort `noexcept` enum-name helpers used by diagnostic code; /// - the aggregate `ProductTimeSpecClassification` struct; /// - the best-effort `noexcept` JSON helper for the full classification. /// /// It intentionally does not define any resolved anchor, shape, or domain /// artifacts, so header-only callback layers can depend on classifications /// without introducing circular dependencies. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" namespace metkit::mars2grib::backend::models::product_time_spec { namespace anchor { /// /// @brief Supported temporal-anchor construction cases. /// /// The active backend-model anchor cases are: /// - `ForecastAnalysis`, for products carrying one direct MARS `date` source and /// an optional MARS `time` source; /// - `Hindcast`, for products carrying both direct MARS `date` and direct MARS /// `hdate` sources; /// - `SeasonalClimate`, for products carrying MARS `year` and `month` without /// direct `date`, `time`, or `hdate` sources. /// /// `Count` is a sentinel used exclusively to size the registry. /// enum class ProductTimeSpecAnchorKind : std::size_t { ForecastAnalysis, Hindcast, SeasonalClimate, Count // Used for sizing the registry, not a valid classification. }; /// @brief Return the stable diagnostic name of one anchor case. /// @param[in] value Anchor classification value. /// @return Stable human-readable anchor-case name, or a fallback string. inline std::string productTimeSpecAnchorTypeName(ProductTimeSpecAnchorKind value) noexcept { try { switch (value) { case ProductTimeSpecAnchorKind::ForecastAnalysis: return "ForecastAnalysis"; case ProductTimeSpecAnchorKind::Hindcast: return "Hindcast"; case ProductTimeSpecAnchorKind::SeasonalClimate: return "SeasonalClimate"; case ProductTimeSpecAnchorKind::Count: return "Count"; } return "InvalidTimeAnchorKind"; } catch (...) { return "ProductTimeSpecAnchorKindNameError"; } } } // namespace anchor namespace domain { /// /// @brief Supported absolute-domain construction strategies. /// /// The active backend-model domain cases are: /// - `ForecastDomain`, for non-synoptic non-seasonal forecast products whose /// support ends at `referenceDateTime + step` and extends backward by the /// resolved outer range; /// - `FromStartForecastDomain`, for non-synoptic non-seasonal forecast products /// using from-start shape semantics whose support ends at /// `referenceDateTime + step` and is completed by the final shape stage after /// domain construction; /// - `SeasonalForecastDomain`, for non-synoptic seasonal forecast products whose /// support ends at `referenceDateTime + fcmonth months` and extends backward /// by the resolved outer range; /// - `AnalysisDomain`, for non-synoptic analysis products whose support starts /// at `referenceDateTime` and extends forward by the resolved outer range; /// - `SynopticAnalysisDomain`, for synoptic IFS analysis products whose support /// starts at the exact MARS date/time and ends at the beginning of the next /// calendar month. /// /// `Count` is a sentinel used exclusively to size the registry. /// enum class ProductTimeSpecDomainKind : std::size_t { ForecastDomain, FromStartForecastDomain, SeasonalForecastDomain, AnalysisDomain, SynopticAnalysisDomain, Count }; /// @brief Return the stable diagnostic name of one domain case. /// @param[in] value Domain classification value. /// @return Stable human-readable domain-case name, or a fallback string. inline std::string productTimeSpecDomainTypeName(ProductTimeSpecDomainKind value) noexcept { try { switch (value) { case ProductTimeSpecDomainKind::ForecastDomain: return "ForecastDomain"; case ProductTimeSpecDomainKind::FromStartForecastDomain: return "FromStartForecastDomain"; case ProductTimeSpecDomainKind::SeasonalForecastDomain: return "SeasonalForecastDomain"; case ProductTimeSpecDomainKind::AnalysisDomain: return "AnalysisDomain"; case ProductTimeSpecDomainKind::SynopticAnalysisDomain: return "SynopticAnalysisDomain"; case ProductTimeSpecDomainKind::Count: return "Count"; } return "InvalidTimeDomainKind"; } catch (...) { return "ProductTimeSpecDomainKindNameError"; } } } // namespace domain namespace shape { /// /// @brief Supported canonical ProductTimeSpec shape cases. /// /// Shape classification describes the canonical statistical-window topology of /// the final ProductTimeSpec representation. /// /// The active cases are grouped as follows: /// - instant-product representations; /// - IFS single-loop and multi-loop statistical representations; /// - AIFS single-loop statistical representations whose increment semantics are /// always missing; /// - seasonal forecast representations bound to the dedicated /// `SeasonalForecastDomain`; /// - from-start variants distinguished by zero-length versus positive-step /// semantics; /// - fake-loop compatibility cases that preserve legacy source encodings while /// producing canonical windows. /// /// `Count` is a sentinel used exclusively to size the registry. /// enum class ProductTimeSpecShapeKind : std::size_t { Instant, IFSStandardSingleLoop, IFSFakeDoubleLoopSingleLoop, IFSFromStartSingleLoopAtZero, IFSFromStartSingleLoopPositive, IFSSynopticSingleLoop, AIFSStandardSingleLoop, AIFSFakeDoubleLoopSingleLoop, AIFSFromStartSingleLoopAtZero, AIFSFromStartSingleLoopPositive, SeasonalSingleLoop, SeasonalMultiloop, IFSStandardMultiLoop, IFSFakeSingleLoopDoubleLoop, Count // Used for sizing the registry, not a valid classification. }; /// @brief Return the stable diagnostic name of one shape case. /// @param[in] value Shape classification value. /// @return Stable human-readable shape-case name, or a fallback string. inline std::string productTimeSpecShapeTypeName(ProductTimeSpecShapeKind value) noexcept { try { switch (value) { case ProductTimeSpecShapeKind::Instant: return "Instant"; case ProductTimeSpecShapeKind::IFSStandardSingleLoop: return "IFSStandardSingleLoop"; case ProductTimeSpecShapeKind::IFSFakeDoubleLoopSingleLoop: return "IFSFakeDoubleLoopSingleLoop"; case ProductTimeSpecShapeKind::IFSFromStartSingleLoopAtZero: return "IFSFromStartSingleLoopAtZero"; case ProductTimeSpecShapeKind::IFSFromStartSingleLoopPositive: return "IFSFromStartSingleLoopPositive"; case ProductTimeSpecShapeKind::IFSSynopticSingleLoop: return "IFSSynopticSingleLoop"; case ProductTimeSpecShapeKind::AIFSStandardSingleLoop: return "AIFSStandardSingleLoop"; case ProductTimeSpecShapeKind::AIFSFakeDoubleLoopSingleLoop: return "AIFSFakeDoubleLoopSingleLoop"; case ProductTimeSpecShapeKind::AIFSFromStartSingleLoopAtZero: return "AIFSFromStartSingleLoopAtZero"; case ProductTimeSpecShapeKind::AIFSFromStartSingleLoopPositive: return "AIFSFromStartSingleLoopPositive"; case ProductTimeSpecShapeKind::SeasonalSingleLoop: return "SeasonalSingleLoop"; case ProductTimeSpecShapeKind::SeasonalMultiloop: return "SeasonalMultiloop"; case ProductTimeSpecShapeKind::IFSStandardMultiLoop: return "IFSStandardMultiLoop"; case ProductTimeSpecShapeKind::IFSFakeSingleLoopDoubleLoop: return "IFSFakeSingleLoopDoubleLoop"; case ProductTimeSpecShapeKind::Count: return "Count"; } return "InvalidShapeKind"; } catch (...) { return "ProductTimeSpecShapeKindNameError"; } } } // namespace shape /// /// @brief Full ProductTimeSpec classification bundle. /// /// This aggregate keeps the resolved anchor, shape, and domain classifications /// together so later build phases can carry one consistent classification object. /// struct ProductTimeSpecClassification { anchor::ProductTimeSpecAnchorKind anchorType{anchor::ProductTimeSpecAnchorKind::ForecastAnalysis}; shape::ProductTimeSpecShapeKind shapeType{shape::ProductTimeSpecShapeKind::Instant}; domain::ProductTimeSpecDomainKind domainType{domain::ProductTimeSpecDomainKind::ForecastDomain}; }; /// @brief Serialize one resolved ProductTimeSpec classification as diagnostic JSON. /// @param[in] value Resolved classification bundle. /// @return One JSON object describing the classification, or a fallback error object. inline std::string productTimeSpecClassificationJson(const ProductTimeSpecClassification& value) noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("anchorType") << ':' << detail::jsonQuote_modelInput(anchor::productTimeSpecAnchorTypeName(value.anchorType)) << ',' << detail::jsonQuote_modelInput("shapeType") << ':' << detail::jsonQuote_modelInput(shape::productTimeSpecShapeTypeName(value.shapeType)) << ',' << detail::jsonQuote_modelInput("domainType") << ':' << detail::jsonQuote_modelInput(domain::productTimeSpecDomainTypeName(value.domainType)) << '}'; return out.str(); } catch (...) { return std::string{ "{\"error\":\"ProductTimeSpecClassificationJson failed while building diagnostic context\"}"}; } } } // namespace metkit::mars2grib::backend::models::product_time_spec metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/0000775000175000017500000000000015246725757026756 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h0000664000175000017500000001047415246725757032014 0ustar alastairalastair/* * (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 ShapeDataTypes.h /// @brief Shape classifications and resolved ProductTimeSpec window artifacts. /// #pragma once #include "eckit/types/DateTime.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape { /// /// @brief One canonical ProductTimeSpec statistical window. /// /// Each canonical window stores the statistical processing applied over that /// window, the GRIB `typeOfTimeIncrement` associated with it, its range, and the /// increment associated with samples contributing to it. /// struct ProductTimeSpecWindow { /// @brief Statistical processing performed over this canonical window. tables::TypeOfStatisticalProcessing typeOfStatisticalProcessing{tables::TypeOfStatisticalProcessing::Missing}; /// @brief GRIB `typeOfTimeIncrement` describing the increment semantics. metkit::mars2grib::backend::tables::TypeOfTimeIntervals typeOfTimeIncrement{ metkit::mars2grib::backend::tables::TypeOfTimeIntervals::Missing}; /// @brief Length of the canonical statistical window. deductions::TimeDuration timeRange{}; /// @brief Increment associated with samples contributing to the window. deductions::TimeDuration timeIncrement{}; }; /// /// @brief Ordered canonical ProductTimeSpec window sequence. /// /// Windows are stored in outermost-to-innermost order. /// struct ProductTimeSpecShape { /// @brief Canonical windows in outermost-to-innermost order. std::vector values{}; }; /// /// @brief Availability state of the stage-1 outer time range. enum class ProductTimeSpecOuterTimeRangeAvailability { Available, Deferred, }; /// /// @brief Stage-1 outer time range required by selected domain builders. /// /// This intermediate artifact carries the one outer time range that is either /// already available before domain construction or intentionally deferred until /// the final shape stage. /// struct ProductTimeSpecOuterTimeRange { /// @brief Whether the outer time range is already available or deferred. ProductTimeSpecOuterTimeRangeAvailability availability{ProductTimeSpecOuterTimeRangeAvailability::Deferred}; /// @brief Resolved outer time range when availability is `Available`. std::optional timeRange{}; }; /// @brief Serialize one resolved shape artifact as diagnostic JSON. /// @param[in] value Resolved canonical window sequence. /// @return One JSON object describing the final shape state. inline std::string productTimeSpecShapeJson(const ProductTimeSpecShape& value) { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("windows") << ':' << '['; for (std::size_t i = 0; i < value.values.size(); ++i) { if (i != 0) { out << ','; } out << '{' << detail::jsonQuote_modelInput("typeOfStatisticalProcessing") << ':' << detail::jsonQuote_modelInput( tables::enum2name_TypeOfStatisticalProcessing_or_throw(value.values[i].typeOfStatisticalProcessing)) << ',' << detail::jsonQuote_modelInput("typeOfTimeIncrement") << ':' << detail::jsonQuote_modelInput( tables::enum2name_TypeOfTimeIntervals_or_throw(value.values[i].typeOfTimeIncrement)) << ',' << detail::jsonQuote_modelInput("timeRange") << ':' << detail::durationJson_modelInput(value.values[i].timeRange) << ',' << detail::jsonQuote_modelInput("timeIncrement") << ':' << detail::durationJson_modelInput(value.values[i].timeIncrement) << '}'; } out << ']' << '}'; return out.str(); } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeUtils.h0000664000175000017500000000634515246725757031220 0ustar alastairalastair/* * (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 ShapeUtils.h /// @brief Shape-specific policy helpers shared by a narrow subset of ProductTimeSpec shapes. /// /// This internal header contains helper logic that remains genuinely specific to /// shape matchers/builders and therefore does not belong in the generic temporal /// arithmetic or time-increment layers. /// /// Every function has a documented contract, catches all failures, and rethrows /// `Mars2GribModelException` directly at the function boundary. Lower-level /// helpers without `ProductTimeSpecInput` use the location-only constructor. /// /// @ingroup mars2grib_product_time_spec_detail /// #pragma once #include #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Test whether the normalized `timespan` is explicitly `none`. * * @param[in] input Fully normalized ProductTimeSpec input. * @return `true` when `timespan.kind` is `None`; otherwise `false`. * @throws Mars2GribModelException If evaluation unexpectedly fails. */ inline bool timespanIsNone(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return input.timespan.kind == TimespanKind::None; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `timespanIsNone`", input.to_json(), Here())); } } /** * @brief Test whether the normalized `timespan` is missing and accepted by the caller. * * @param[in] input Fully normalized ProductTimeSpec input. * @param[in] allowMissingTimespan Caller-specific policy deciding whether the * missing representation is accepted for the inspected shape. * @return `true` when `timespan.kind` is `Missing` and the caller allows it; * otherwise `false`. * @throws Mars2GribModelException If evaluation unexpectedly fails. */ inline bool timespanIsMissingAndAllowed(const ProductTimeSpecInput& input, const bool allowMissingTimespan) { using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { return input.timespan.kind == TimespanKind::Missing && allowMissingTimespan; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `timespanIsMissingAndAllowed`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeRegistry.h0000664000175000017500000004735415246725757031735 0ustar alastairalastair/* * (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 ShapeRegistry.h /// @brief Register, classify, build, and check ProductTimeSpec shape cases. /// /// Shape classification is exhaustive and non-prioritized. Every matcher is /// evaluated, all Boolean results are retained for diagnostics, and exactly one /// matcher must succeed. This turns accidental matcher overlap into a visible hard /// error rather than silently selecting the first row. /// /// The active callback-selection matrix is summarized case-by-case below. Each /// row lists the matcher facts that must simultaneously hold for that callback /// to be selected. /// /// | Shape case | Regime | Domain kind | Synoptic | `timespan.kind` | `stattype` | `timeIncrement` expectation | /// Fake-double-loop flag | Fake-second-loop flag | /// |------------|--------|-------------|----------|------------------|------------|-----------------------------|----------------------|----------------------| /// | `Instant` | any | any | any | `None`, or `Missing` when allowed | empty | redundant values validated later | n/a | /// n/a | | `IFSStandardSingleLoop` | `IFS` | `ForecastDomain` | `false` | `Duration` | empty | explicit, missing, or /// defaulted | `false` | `false` | | `IFSFakeDoubleLoopSingleLoop` | `IFS` | `ForecastDomain` | `false` | `None`, or /// `Missing` when allowed | exactly one block | explicit, missing, or defaulted | `true` | `false` | | /// `IFSFromStartSingleLoopAtZero` | `IFS` | `ForecastDomain` | `false` | `FromStart` | empty | /// explicit, missing, or defaulted | n/a | n/a | | `IFSFromStartSingleLoopPositive` | `IFS` | `ForecastDomain` | /// `false` | `FromStart` | empty | explicit, missing, or defaulted | n/a | n/a | | `IFSSynopticSingleLoop` | /// `IFS` | `SynopticAnalysisDomain` | `true` | synoptic-supported source | empty | intrinsic or redundant 24h value | /// n/a | n/a | | `AIFSStandardSingleLoop` | `AIFS` | `ForecastDomain` | `false` | `Duration` | empty | must be missing /// | `false` | `false` | | `AIFSFakeDoubleLoopSingleLoop` | `AIFS` | `ForecastDomain` | `false` | `None`, or `Missing` /// when allowed | exactly one block | must be missing | `true` | n/a | | `AIFSFromStartSingleLoopAtZero` | `AIFS` | /// `ForecastDomain` | `false` | `FromStart` | empty | must be missing | n/a | n/a | | `AIFSFromStartSingleLoopPositive` /// | `AIFS` | `ForecastDomain` | `false` | `FromStart` | empty | must be missing | n/a | n/a | | `SeasonalSingleLoop` | /// any | `SeasonalForecastDomain` | `false` | `None`, or `Missing` when allowed | empty | explicit, missing, or /// defaulted | n/a | n/a | | `SeasonalMultiloop` | any | `SeasonalForecastDomain` | `false` | `Duration` | one or more /// blocks | explicit, missing, or defaulted | n/a | n/a | | `IFSStandardMultiLoop` | `IFS` | `ForecastDomain` | `false` /// | `Duration` | one or more blocks | explicit, missing, or defaulted | n/a | n/a | | `IFSFakeSingleLoopDoubleLoop` | /// `IFS` | `ForecastDomain` | `false` | `Duration` | empty | explicit, missing, or defaulted | `false` | `true` | /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFakeDoubleLoopSingleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleLoopAtZero.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleLoopPositive.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSStandardSingleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeDoubleLoopSingleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeSingleLoopDoubleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLoopAtZero.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLoopPositive.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardMultiLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardSingleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSSynopticSingleLoop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/Instant.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/SeasonalMultiloop.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/impl/SeasonalSingleLoop.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape { namespace detail { /// @brief Function-pointer type shared by all shape matchers. using ShapeMatcher = bool (*)(const ProductTimeSpecInput&); /// @brief Function-pointer type shared by all stage-1 shape builders. using ShapeOuterTimeRangeBuilder = ProductTimeSpecOuterTimeRange (*)(const ProductTimeSpecInput&, const ProductTimeSpecClassification&); /// @brief Function-pointer type shared by all final shape builders. using ShapeWindowsBuilder = ProductTimeSpecShape (*)(const ProductTimeSpecInput&, const ProductTimeSpecClassification&, const anchor::ProductTimeSpecAnchor&, const ProductTimeSpecOuterTimeRange&, const domain::ProductTimeSpecDomain&); /// @brief Function-pointer type shared by all shape check callbacks. using ShapeChecker = bool (*)(const ProductTimeSpecInput&, const ProductTimeSpecClassification&, const anchor::ProductTimeSpecAnchor&, const ProductTimeSpecOuterTimeRange&, const domain::ProductTimeSpecDomain&, const ProductTimeSpecShape&); /// /// @brief Immutable registry row for one shape case. /// /// Keeping the classification value, diagnostic name, matcher, and builders in /// one object prevents registry arrays from drifting out of alignment. /// struct ShapeCase { ProductTimeSpecShapeKind classification; std::string_view name; ShapeMatcher matcher; ShapeOuterTimeRangeBuilder outerTimeRangeBuilder; ShapeWindowsBuilder windowsBuilder; ShapeChecker checker; }; /// @brief Immutable shape registry ordered exactly like `ProductTimeSpecShapeKind`. inline constexpr std::array(ProductTimeSpecShapeKind::Count)> shapeCases{{ {ProductTimeSpecShapeKind::Instant, "Instant", &match_Instant_Shape, &build_Instant_ShapeOuterTimeRange, &build_Instant_ShapeWindows, &check_Instant_Shape}, {ProductTimeSpecShapeKind::IFSStandardSingleLoop, "IFSStandardSingleLoop", &match_IFSStandardSingleLoop_Shape, &build_IFSStandardSingleLoop_ShapeOuterTimeRange, &build_IFSStandardSingleLoop_ShapeWindows, &check_IFSStandardSingleLoop_Shape}, {ProductTimeSpecShapeKind::IFSFakeDoubleLoopSingleLoop, "IFSFakeDoubleLoopSingleLoop", &match_IFSFakeDoubleLoopSingleLoop_Shape, &build_IFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange, &build_IFSFakeDoubleLoopSingleLoop_ShapeWindows, &check_IFSFakeDoubleLoopSingleLoop_Shape}, {ProductTimeSpecShapeKind::IFSFromStartSingleLoopAtZero, "IFSFromStartSingleLoopAtZero", &match_IFSFromStartSingleLoopAtZero_Shape, &build_IFSFromStartSingleLoopAtZero_ShapeOuterTimeRange, &build_IFSFromStartSingleLoopAtZero_ShapeWindows, &check_IFSFromStartSingleLoopAtZero_Shape}, {ProductTimeSpecShapeKind::IFSFromStartSingleLoopPositive, "IFSFromStartSingleLoopPositive", &match_IFSFromStartSingleLoopPositive_Shape, &build_IFSFromStartSingleLoopPositive_ShapeOuterTimeRange, &build_IFSFromStartSingleLoopPositive_ShapeWindows, &check_IFSFromStartSingleLoopPositive_Shape}, {ProductTimeSpecShapeKind::IFSSynopticSingleLoop, "IFSSynopticSingleLoop", &match_IFSSynopticSingleLoop_Shape, &build_IFSSynopticSingleLoop_ShapeOuterTimeRange, &build_IFSSynopticSingleLoop_ShapeWindows, &check_IFSSynopticSingleLoop_Shape}, {ProductTimeSpecShapeKind::AIFSStandardSingleLoop, "AIFSStandardSingleLoop", &match_AIFSStandardSingleLoop_Shape, &build_AIFSStandardSingleLoop_ShapeOuterTimeRange, &build_AIFSStandardSingleLoop_ShapeWindows, &check_AIFSStandardSingleLoop_Shape}, {ProductTimeSpecShapeKind::AIFSFakeDoubleLoopSingleLoop, "AIFSFakeDoubleLoopSingleLoop", &match_AIFSFakeDoubleLoopSingleLoop_Shape, &build_AIFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange, &build_AIFSFakeDoubleLoopSingleLoop_ShapeWindows, &check_AIFSFakeDoubleLoopSingleLoop_Shape}, {ProductTimeSpecShapeKind::AIFSFromStartSingleLoopAtZero, "AIFSFromStartSingleLoopAtZero", &match_AIFSFromStartSingleLoopAtZero_Shape, &build_AIFSFromStartSingleLoopAtZero_ShapeOuterTimeRange, &build_AIFSFromStartSingleLoopAtZero_ShapeWindows, &check_AIFSFromStartSingleLoopAtZero_Shape}, {ProductTimeSpecShapeKind::AIFSFromStartSingleLoopPositive, "AIFSFromStartSingleLoopPositive", &match_AIFSFromStartSingleLoopPositive_Shape, &build_AIFSFromStartSingleLoopPositive_ShapeOuterTimeRange, &build_AIFSFromStartSingleLoopPositive_ShapeWindows, &check_AIFSFromStartSingleLoopPositive_Shape}, {ProductTimeSpecShapeKind::SeasonalSingleLoop, "SeasonalSingleLoop", &match_SeasonalSingleLoop_Shape, &build_SeasonalSingleLoop_ShapeOuterTimeRange, &build_SeasonalSingleLoop_ShapeWindows, &check_SeasonalSingleLoop_Shape}, {ProductTimeSpecShapeKind::SeasonalMultiloop, "SeasonalMultiloop", &match_SeasonalMultiloop_Shape, &build_SeasonalMultiloop_ShapeOuterTimeRange, &build_SeasonalMultiloop_ShapeWindows, &check_SeasonalMultiloop_Shape}, {ProductTimeSpecShapeKind::IFSStandardMultiLoop, "IFSStandardMultiLoop", &match_IFSStandardMultiLoop_Shape, &build_IFSStandardMultiLoop_ShapeOuterTimeRange, &build_IFSStandardMultiLoop_ShapeWindows, &check_IFSStandardMultiLoop_Shape}, {ProductTimeSpecShapeKind::IFSFakeSingleLoopDoubleLoop, "IFSFakeSingleLoopDoubleLoop", &match_IFSFakeSingleLoopDoubleLoop_Shape, &build_IFSFakeSingleLoopDoubleLoop_ShapeOuterTimeRange, &build_IFSFakeSingleLoopDoubleLoop_ShapeWindows, &check_IFSFakeSingleLoopDoubleLoop_Shape}, }}; static_assert(static_cast(shapeCases[0].classification) == 0); static_assert(static_cast(shapeCases[1].classification) == 1); static_assert(static_cast(shapeCases[2].classification) == 2); static_assert(static_cast(shapeCases[3].classification) == 3); static_assert(static_cast(shapeCases[4].classification) == 4); static_assert(static_cast(shapeCases[5].classification) == 5); static_assert(static_cast(shapeCases[6].classification) == 6); static_assert(static_cast(shapeCases[7].classification) == 7); static_assert(static_cast(shapeCases[8].classification) == 8); static_assert(static_cast(shapeCases[9].classification) == 9); static_assert(static_cast(shapeCases[10].classification) == 10); static_assert(static_cast(shapeCases[11].classification) == 11); static_assert(static_cast(shapeCases[12].classification) == 12); static_assert(static_cast(shapeCases[13].classification) == 13); } // namespace detail /// /// @brief Classify the normalized input against every registered shape case. /// /// @section Shape classification contract /// - Reads: normalized source facts and embedded options. /// - Evaluates: every matcher in `shapeCases`. /// - Success: exactly one matcher returns `true`. /// - Failure: zero or multiple matchers return `true`. /// - Side effects: none. /// /// @param[in] input /// Fully normalized ProductTimeSpec input. /// /// @return /// Unique matching `ProductTimeSpecShapeKind` value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// If matcher evaluation fails or classification is not unique. /// inline ProductTimeSpecShapeKind classify_Shape_or_throw(const ProductTimeSpecInput& input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { std::array matches{}; std::size_t numberOfMatches = 0; std::size_t matchedIndex = 0; for (std::size_t i = 0; i < detail::shapeCases.size(); ++i) { matches[i] = detail::shapeCases[i].matcher(input); if (matches[i]) { ++numberOfMatches; matchedIndex = i; } } if (numberOfMatches != 1) { throw Mars2GribModelException( [&]() { std::ostringstream oss; oss << "Shape classification failed: expected exactly one match, but found " << numberOfMatches << " matches. Match results: "; oss << "{param=" << input.marsParamId << ", class=" << input.marsClass << ", stream=" << input.marsStream << ", type=" << input.marsType << "}, ["; for (std::size_t i = 0; i < matches.size(); ++i) { oss << detail::shapeCases[i].name << "=" << (matches[i] ? "true" : "false"); if (i < matches.size() - 1) { oss << ", "; } } oss << "]"; return oss.str(); }(), input.to_json(), Here()); } return detail::shapeCases[matchedIndex].classification; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to classify the ProductTimeSpec shape", input.to_json(), Here())); } } /// /// @brief Dispatch the stage-1 builder associated with a validated shape classification. /// /// @param[in] classification /// Unique shape classification returned by `classify_Shape_or_throw`. /// /// @param[in] input /// Fully normalized ProductTimeSpec input, including embedded options. /// /// @param[in] fullClassification /// Full resolved ProductTimeSpec classification bundle. /// /// @return /// Stage-1 ProductTimeSpec windows ordered outermost to innermost. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException /// If the classification is invalid or the selected stage-1 builder fails. /// inline ProductTimeSpecOuterTimeRange build_ShapeOuterTimeRange_or_throw( ProductTimeSpecShapeKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecClassification& fullClassification) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::shapeCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid ProductTimeSpecShapeKind value", input.to_json(), Here()); } return detail::shapeCases[index].outerTimeRangeBuilder(input, fullClassification); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build the ProductTimeSpec outer time range", input.to_json(), Here())); } } /// /// @brief Dispatch the final builder associated with a validated shape classification. /// /// @param[in] classification /// Unique shape classification returned by `classify_Shape_or_throw`. /// @param[in] input Fully normalized ProductTimeSpec input, including embedded options. /// @param[in] classificationBundle Full resolved ProductTimeSpec classification bundle. /// @param[in] anchor Resolved ProductTimeSpec anchor. /// @param[in] shapeStage1 Stage-1 ProductTimeSpec shape artifact. /// @param[in] domain Resolved ProductTimeSpec domain. /// @return Final canonical ProductTimeSpec shape. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException If the classification is invalid or the /// selected final builder fails. /// inline ProductTimeSpecShape build_ShapeWindows_or_throw(ProductTimeSpecShapeKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classificationBundle, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::shapeCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid ProductTimeSpecShapeKind value", input.to_json(), Here()); } return detail::shapeCases[index].windowsBuilder(input, classificationBundle, anchor, outerTimeRange, domain); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build the ProductTimeSpec windows", input.to_json(), Here())); } } /// /// @brief Dispatch the checker associated with a validated shape classification. /// /// @param[in] classification /// Unique shape classification returned by `classify_Shape_or_throw`. /// @param[in] input Fully normalized ProductTimeSpec input, including embedded options. /// @param[in] classificationBundle Full resolved ProductTimeSpec classification bundle. /// @param[in] anchor Resolved ProductTimeSpec anchor. /// @param[in] outerTimeRange Stage-1 ProductTimeSpec outer time range artifact. /// @param[in] domain Resolved ProductTimeSpec domain. /// @param[in] shape Complete ProductTimeSpec shape artifact produced by the selected final builder. /// @return `true` when the selected checker validates the shape successfully. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException If the classification is invalid or the /// selected checker fails. /// inline bool check_Shape_or_throw(ProductTimeSpecShapeKind classification, const ProductTimeSpecInput& input, const ProductTimeSpecClassification& classificationBundle, const anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::size_t index = static_cast(classification); const bool classificationIsValid = index < detail::shapeCases.size(); if (!classificationIsValid) { throw Mars2GribModelException("Invalid ProductTimeSpecShapeKind value", input.to_json(), Here()); } return detail::shapeCases[index].checker(input, classificationBundle, anchor, outerTimeRange, domain, shape); } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to check the ProductTimeSpec shape", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/0000775000175000017500000000000015246725757027717 5ustar alastairalastair././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSStandardSingleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSStandardSingleLo0000664000175000017500000004677715246725757033527 0ustar alastairalastair/* * (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 AIFSStandardSingleLoop.h /// @brief Matcher, builders, and checker for the AIFSStandardSingleLoop shape. /// /// This header is the authoritative implementation of the /// `AIFSStandardSingleLoop` shape case. It keeps recognition, construction, and /// validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range directly from the normalized duration-valued /// `timespan`. The final builder constructs the one canonical AIFS statistical /// window from visible local members. The checker validates that the resolved /// shape remains consistent with both the input semantics and the stage /// artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the AIFSStandardSingleLoop shape. * * - the regime is AIFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - the source increment is missing; * - `timespan` is duration-valued; * - no outer `stattype` blocks are present; * - the product does not require the fake-double-loop compatibility case; * - the product does not require the fake-single-loop-double-loop compatibility case. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_AIFSStandardSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; return isAifs && isForecast && isNotSeasonal && isNotSynoptic && sourceIncrementIsMissing && hasDurationTimespan && hasNoStattypeBlocks && doesNotRequireFakeDoubleLoop && doesNotRequireFakeSingleLoopDoubleLoop; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_AIFSStandardSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the AIFSStandardSingleLoop shape. * * In this case: * - `timespan` must be explicitly duration-valued; * - the source increment must be absent; * - the duration value itself is the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_AIFSStandardSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("AIFSStandardSingleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("AIFSStandardSingleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } const TimeDuration timeRange = *input.timespan.duration; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSStandardSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical AIFS single-loop window. * * In this case: * - the canonical time range is the normalized duration-valued `timespan`; * - the statistical processing type is the innermost normalized processing; * - the canonical time increment kind is missing; * - the canonical time increment value is missing; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_AIFSStandardSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; try { (void)classification; (void)anchor; (void)domain; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("AIFSStandardSingleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("AIFSStandardSingleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSStandardSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration timeRange = *input.timespan.duration; if (!compareTimeDuration(timeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException( "AIFSStandardSingleLoop timespan duration does not match the resolved outer time range", input.to_json(), Here()); } // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // Pure AIFS single-loop products always encode missing increment-kind semantics. const auto typeOfTimeIncrement = missingTypeOfTimeIncrement(); // The canonical range is exactly the normalized duration-valued timespan. const auto canonicalTimeRange = timeRange; // Pure AIFS single-loop products always encode a missing increment value. const auto timeIncrement = missingIncrement(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_AIFSStandardSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved AIFSStandardSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `AIFSStandardSingleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the direct duration-valued `timespan`; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed AIFS single-loop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the AIFSStandardSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_AIFSStandardSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::tables::TypeOfTimeIntervals; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::AIFSStandardSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected AIFSStandardSingleLoop", input.to_json(), Here()); } const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasDurationValue = input.timespan.duration.has_value(); const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; if (!isAifs || !isForecast || !isNotSeasonal || !isNotSynoptic || !sourceIncrementIsMissing || !hasDurationTimespan || !hasDurationValue || !hasNoStattypeBlocks || !doesNotRequireFakeDoubleLoop || !doesNotRequireFakeSingleLoopDoubleLoop) { throw Mars2GribModelException("AIFSStandardSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("AIFSStandardSingleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSStandardSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = *input.timespan.duration; if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException( "AIFSStandardSingleLoop outer time range does not match the direct timespan duration", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("AIFSStandardSingleLoop shape must contain exactly one window", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "AIFSStandardSingleLoop window statistical processing does not match the innermost input processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != TypeOfTimeIntervals::Missing) { throw Mars2GribModelException("AIFSStandardSingleLoop window typeOfTimeIncrement must be missing", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("AIFSStandardSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, missingIncrement())) { throw Mars2GribModelException("AIFSStandardSingleLoop window timeIncrement must be missing", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_AIFSStandardSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFakeDoubleLoopSingleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFakeDoubleLoopSi0000664000175000017500000005171115246725757033441 0ustar alastairalastair/* * (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 AIFSFakeDoubleLoopSingleLoop.h /// @brief Matcher, builders, and checker for the AIFSFakeDoubleLoopSingleLoop shape. /// /// This header is the authoritative implementation of the /// `AIFSFakeDoubleLoopSingleLoop` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range directly from the unique `stattype` block. The /// final builder constructs the one canonical AIFS statistical window from /// visible local members. The checker validates that the resolved shape remains /// consistent with both the input semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeUtils.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the AIFSFakeDoubleLoopSingleLoop shape. * * - the regime is AIFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - the source increment is missing; * - `timespan` is explicitly `none`, or it is missing and the configured * statistical-product rule allows that source representation; * - exactly one outer `stattype` block is present; * - the product requires the fake-double-loop compatibility case. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_AIFSFakeDoubleLoopSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; return isAifs && isForecast && isNotSeasonal && isNotSynoptic && sourceIncrementIsMissing && hasAcceptedTimespanRepresentation && hasExactlyOneStattypeBlock && requiresFakeDoubleLoop; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_AIFSFakeDoubleLoopSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the AIFSFakeDoubleLoopSingleLoop shape. * * In this case: * - exactly one outer `stattype` block must be present; * - that block must use the same processing type as the innermost processing; * - that block range itself is the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_AIFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } if (input.stattype.size() != 1) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "AIFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration timeRange = stattype.timeRange; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical AIFS fake-double-loop single window. * * In this case: * - the unique outer `stattype` block is promoted to the canonical window; * - the statistical processing type comes from that unique block; * - the canonical range is that unique block range; * - the canonical time increment kind is missing; * - the canonical time increment value is missing; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_AIFSFakeDoubleLoopSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; try { (void)classification; (void)anchor; (void)domain; if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } if (input.stattype.size() != 1) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "AIFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const auto timeRange = stattype.timeRange; if (!compareTimeDuration(timeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException( "AIFSFakeDoubleLoopSingleLoop stattype range does not match the resolved outer time range", input.to_json(), Here()); } // This case promotes the unique stattype block directly into the one canonical window. const auto typeOfStatisticalProcessing = stattype.typeOfStatisticalProcessing; // Pure AIFS single-loop products always encode missing increment-kind semantics. const auto typeOfTimeIncrement = missingTypeOfTimeIncrement(); // The canonical range is the unique stattype block range. const auto canonicalTimeRange = timeRange; // Pure AIFS single-loop products always encode a missing increment value. const auto timeIncrement = missingIncrement(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFakeDoubleLoopSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved AIFSFakeDoubleLoopSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `AIFSFakeDoubleLoopSingleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the unique `stattype` block range; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed fake-double-loop single-window AIFS semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the AIFSFakeDoubleLoopSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_AIFSFakeDoubleLoopSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::AIFSFakeDoubleLoopSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected AIFSFakeDoubleLoopSingleLoop", input.to_json(), Here()); } const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; if (!isAifs || !isForecast || !isNotSeasonal || !isNotSynoptic || !sourceIncrementIsMissing || !hasAcceptedTimespanRepresentation || !hasExactlyOneStattypeBlock || !requiresFakeDoubleLoop) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException( "AIFSFakeDoubleLoopSingleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "AIFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } if (!compareTimeDuration(*outerTimeRange.timeRange, stattype.timeRange)) { throw Mars2GribModelException( "AIFSFakeDoubleLoopSingleLoop outer time range does not match the unique stattype range", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop shape must contain exactly one window", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != stattype.typeOfStatisticalProcessing) { throw Mars2GribModelException( "AIFSFakeDoubleLoopSingleLoop window statistical processing does not match the stattype block", input.to_json(), Here()); } if (window.typeOfTimeIncrement != missingTypeOfTimeIncrement()) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, stattype.timeRange)) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, missingIncrement())) { throw Mars2GribModelException("AIFSFakeDoubleLoopSingleLoop window timeIncrement must be missing", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_AIFSFakeDoubleLoopSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeDoubleLoopSingleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeDoubleLoopSin0000664000175000017500000006531015246725757033516 0ustar alastairalastair/* * (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 IFSFakeDoubleLoopSingleLoop.h /// @brief Matcher, builders, and checker for the IFSFakeDoubleLoopSingleLoop shape. /// /// This header is the authoritative implementation of the /// `IFSFakeDoubleLoopSingleLoop` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range directly from the unique `stattype` block. The /// final builder constructs the one canonical IFS statistical window from /// visible local members and locally resolved increment semantics. The checker /// validates that the resolved shape remains consistent with both the input /// semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeUtils.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSFakeDoubleLoopSingleLoop shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` is explicitly `none`, or it is missing and the configured * statistical-product rule allows that source representation; * - exactly one outer `stattype` block is present; * - the product requires the fake-double-loop compatibility case; * - the product does not require the fake-single-loop-double-loop compatibility case. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSFakeDoubleLoopSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; return isIfs && isForecast && isNotSeasonal && isNotSynoptic && hasAcceptedTimespanRepresentation && hasExactlyOneStattypeBlock && requiresFakeDoubleLoop && doesNotRequireFakeSingleLoopDoubleLoop; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_IFSFakeDoubleLoopSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSFakeDoubleLoopSingleLoop shape. * * In this case: * - exactly one outer `stattype` block must be present; * - that block must use the same processing type as the innermost processing; * - that block range itself is the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.stattype.size() != 1) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "IFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration timeRange = stattype.timeRange; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFakeDoubleLoopSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical IFS fake-double-loop single window. * * In this case: * - the unique outer `stattype` block is promoted to the canonical window; * - the statistical processing type comes from that unique block; * - the canonical range is that unique block range; * - the increment semantics are resolved locally from explicit, missing, or * defaulted input rules; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSFakeDoubleLoopSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)classification; (void)anchor; (void)domain; if (input.stattype.size() != 1) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "IFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration timeRange = stattype.timeRange; if (!compareTimeDuration(timeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException( "IFSFakeDoubleLoopSingleLoop stattype range does not match the resolved outer time range", input.to_json(), Here()); } ResolvedInnerIncrement resolvedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedIncrement.timeIncrement = missingIncrement(); resolvedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFakeDoubleLoopSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, timeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } // This case promotes the unique stattype block directly into the one canonical window. const auto typeOfStatisticalProcessing = stattype.typeOfStatisticalProcessing; // The increment kind follows the locally resolved IFS increment rules. const auto typeOfTimeIncrement = resolvedIncrement.typeOfTimeIncrement; // The canonical range is the unique stattype block range. const auto canonicalTimeRange = timeRange; // The increment is the explicit, missing, or defaulted value resolved above. const auto timeIncrement = resolvedIncrement.timeIncrement; ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFakeDoubleLoopSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSFakeDoubleLoopSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSFakeDoubleLoopSingleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the unique `stattype` block range; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed fake-double-loop single-window semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSFakeDoubleLoopSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSFakeDoubleLoopSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSFakeDoubleLoopSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected IFSFakeDoubleLoopSingleLoop", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !hasAcceptedTimespanRepresentation || !hasExactlyOneStattypeBlock || !requiresFakeDoubleLoop || !doesNotRequireFakeSingleLoopDoubleLoop) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "IFSFakeDoubleLoopSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration expectedTimeRange = stattype.timeRange; if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException( "IFSFakeDoubleLoopSingleLoop outer time range does not match the unique stattype range", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop shape must contain exactly one window", input.to_json(), Here()); } ResolvedInnerIncrement expectedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedIncrement.timeIncrement = missingIncrement(); expectedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFakeDoubleLoopSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, expectedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != stattype.typeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSFakeDoubleLoopSingleLoop window statistical processing does not match the stattype block", input.to_json(), Here()); } if (window.typeOfTimeIncrement != expectedIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, expectedIncrement.timeIncrement)) { throw Mars2GribModelException("IFSFakeDoubleLoopSingleLoop window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_IFSFakeDoubleLoopSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSSynopticSingleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSSynopticSingleLoo0000664000175000017500000004330215246725757033632 0ustar alastairalastair/* * (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 IFSSynopticSingleLoop.h /// @brief Matcher, builders, and checker for the IFSSynopticSingleLoop shape. /// /// This header is the authoritative implementation of the /// `IFSSynopticSingleLoop` shape case. It keeps recognition, construction, and /// validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// intrinsic one-month outer time range directly in the callback. The final /// builder constructs the one canonical synoptic-analysis window from visible /// local members and locally resolved increment semantics. The checker /// validates that the resolved shape remains consistent with both the input /// semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSSynopticSingleLoop shape. * * - the regime is IFS; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is synoptic; * - the product is analysis; * - no outer `stattype` blocks are present. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSSynopticSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isSynoptic = input.isSynoptic; const bool isAnalysis = input.simulationType == SimulationType::Analysis; const bool hasNoStattypeBlocks = input.stattype.empty(); return isIfs && isNotSeasonal && isSynoptic && isAnalysis && hasNoStattypeBlocks; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_IFSSynopticSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSSynopticSingleLoop shape. * * In this case: * - the synoptic window range is intrinsic; * - the canonical outer time range is exactly one calendar month; * - the intrinsic range is available immediately. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSSynopticSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::oneMonth; try { (void)classification; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; const TimeDuration timeRange = oneMonth(); return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSSynopticSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical IFS synoptic-analysis window. * * In this case: * - the statistical processing type is the innermost normalized processing; * - the canonical time increment kind is the synoptic analysis value; * - the canonical time range is one calendar month; * - the canonical time increment is twenty-four hours; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSSynopticSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::analysisTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::oneMonth; using metkit::mars2grib::utils::time_arithmetic::twentyFourHours; try { (void)classification; (void)anchor; (void)domain; const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSSynopticSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = oneMonth(); if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSSynopticSingleLoop outer time range must be one calendar month", input.to_json(), Here()); } constexpr long expectedIncrementInSeconds = 86400L; const bool hasExplicitIncrement = input.timeIncrement.has_value(); const bool redundantIncrementIsAllowed = input.allowRedundantTimeIncrement; const bool explicitIncrementHasExpectedValue = !hasExplicitIncrement || convertToSeconds(*input.timeIncrement) == expectedIncrementInSeconds; if (hasExplicitIncrement && !redundantIncrementIsAllowed) { throw Mars2GribModelException( "Synoptic timeIncrementInSeconds is redundant but redundant values are disabled", input.to_json(), Here()); } if (!explicitIncrementHasExpectedValue) { throw Mars2GribModelException("Synoptic timeIncrementInSeconds must equal 86400", input.to_json(), Here()); } // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // Synoptic analysis uses the dedicated analysis increment-kind code. const auto typeOfTimeIncrement = analysisTypeOfTimeIncrement(); // The canonical range of a synoptic analysis window is one calendar month. const auto timeRange = expectedTimeRange; // The canonical synoptic increment is always twenty-four hours. const auto timeIncrement = twentyFourHours(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, timeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_IFSSynopticSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSSynopticSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSSynopticSingleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range is available and equals one calendar month; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed synoptic-analysis semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSSynopticSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSSynopticSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::analysisTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::oneMonth; using metkit::mars2grib::utils::time_arithmetic::twentyFourHours; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSSynopticSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected IFSSynopticSingleLoop", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isSynoptic = input.isSynoptic; const bool isAnalysis = input.simulationType == SimulationType::Analysis; const bool hasNoStattypeBlocks = input.stattype.empty(); if (!isIfs || !isNotSeasonal || !isSynoptic || !isAnalysis || !hasNoStattypeBlocks) { throw Mars2GribModelException("IFSSynopticSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (!domain.isSynoptic) { throw Mars2GribModelException("IFSSynopticSingleLoop shape must be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSSynopticSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = oneMonth(); if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSSynopticSingleLoop outer time range must be one calendar month", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("IFSSynopticSingleLoop shape must contain exactly one window", input.to_json(), Here()); } constexpr long expectedIncrementInSeconds = 86400L; const bool hasExplicitIncrement = input.timeIncrement.has_value(); const bool redundantIncrementIsAllowed = input.allowRedundantTimeIncrement; const bool explicitIncrementHasExpectedValue = !hasExplicitIncrement || convertToSeconds(*input.timeIncrement) == expectedIncrementInSeconds; if (hasExplicitIncrement && !redundantIncrementIsAllowed) { throw Mars2GribModelException( "Synoptic timeIncrementInSeconds is redundant but redundant values are disabled", input.to_json(), Here()); } if (!explicitIncrementHasExpectedValue) { throw Mars2GribModelException("Synoptic timeIncrementInSeconds must equal 86400", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSSynopticSingleLoop window statistical processing does not match the innermost input processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != analysisTypeOfTimeIncrement()) { throw Mars2GribModelException("IFSSynopticSingleLoop window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSSynopticSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, twentyFourHours())) { throw Mars2GribModelException("IFSSynopticSingleLoop window timeIncrement must be twenty-four hours", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_IFSSynopticSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000016200000000000011602 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleLoopPositive.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleL0000664000175000017500000004135215246725757033511 0ustar alastairalastair/* * (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 AIFSFromStartSingleLoopPositive.h /// @brief Matcher, builders, and checker for the AIFSFromStartSingleLoopPositive shape. /// /// This header is the authoritative implementation of the /// `AIFSFromStartSingleLoopPositive` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder keeps the outer time /// range deferred for this case. The final builder constructs the one canonical /// from-start AIFS statistical window from visible local members. The checker /// validates that the resolved shape remains consistent with both the input /// semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the AIFSFromStartSingleLoopPositive shape. * * - the regime is AIFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - the source increment is missing; * - `timespan` uses from-start semantics; * - no outer `stattype` blocks are present; * - step is strictly positive. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_AIFSFromStartSingleLoopPositive_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsPositive; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasPositiveStep = stepIsPositive(input); return isAifs && isForecast && isNotSeasonal && isNotSynoptic && sourceIncrementIsMissing && usesFromStartTimespan && hasNoStattypeBlocks && hasPositiveStep; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `match_AIFSFromStartSingleLoopPositive_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the AIFSFromStartSingleLoopPositive shape. * * In this case: * - the final range depends on the resolved absolute domain span; * - the stage-1 outer time range therefore remains deferred. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_AIFSFromStartSingleLoopPositive_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Deferred; return ProductTimeSpecOuterTimeRange{availability, std::nullopt}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFromStartSingleLoopPositive_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical AIFS from-start positive-step window. * * In this case: * - the canonical time range is the resolved domain span; * - the statistical processing type is the innermost normalized processing; * - the canonical time increment kind is missing; * - the canonical time increment value is missing; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_AIFSFromStartSingleLoopPositive_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)classification; (void)anchor; const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive requires a deferred outer time range", input.to_json(), Here()); } if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } const TimeDuration timeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // Pure AIFS from-start products always encode missing increment-kind semantics. const auto typeOfTimeIncrement = missingTypeOfTimeIncrement(); // The canonical range is the real domain span for the positive from-start case. const auto canonicalTimeRange = timeRange; // Pure AIFS from-start products always encode a missing increment value. const auto timeIncrement = missingIncrement(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFromStartSingleLoopPositive_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved AIFSFromStartSingleLoopPositive shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `AIFSFromStartSingleLoopPositive`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range remains deferred; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed positive-step from-start AIFS semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the AIFSFromStartSingleLoopPositive case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_AIFSFromStartSingleLoopPositive_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsPositive; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::AIFSFromStartSingleLoopPositive) { throw Mars2GribModelException("Shape classification mismatch: expected AIFSFromStartSingleLoopPositive", input.to_json(), Here()); } const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasPositiveStep = stepIsPositive(input); if (!isAifs || !isForecast || !isNotSeasonal || !isNotSynoptic || !sourceIncrementIsMissing || !usesFromStartTimespan || !hasNoStattypeBlocks || !hasPositiveStep) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException( "AIFSFromStartSingleLoopPositive shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); if (shape.values.size() != 1) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive shape must contain exactly one window", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "AIFSFromStartSingleLoopPositive window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != missingTypeOfTimeIncrement()) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, missingIncrement())) { throw Mars2GribModelException("AIFSFromStartSingleLoopPositive window timeIncrement must be missing", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `check_AIFSFromStartSingleLoopPositive_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardSingleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardSingleLoo0000664000175000017500000006056115246725757033570 0ustar alastairalastair/* * (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 IFSStandardSingleLoop.h /// @brief Matcher, builders, and checker for the IFSStandardSingleLoop shape. /// /// This header is the authoritative implementation of the /// `IFSStandardSingleLoop` shape case. It keeps recognition, construction, and /// validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range directly from the normalized duration-valued /// `timespan`. The final builder constructs the one canonical IFS statistical /// window from visible local members and locally resolved increment semantics. /// The checker validates that the resolved shape remains consistent with both /// the input semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSStandardSingleLoop shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` is duration-valued; * - no outer `stattype` blocks are present; * - the product does not require the fake-double-loop compatibility case; * - the product does not require the fake-single-loop-double-loop compatibility case. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSStandardSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; return isIfs && isForecast && isNotSeasonal && isNotSynoptic && hasDurationTimespan && hasNoStattypeBlocks && doesNotRequireFakeDoubleLoop && doesNotRequireFakeSingleLoopDoubleLoop; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_IFSStandardSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSStandardSingleLoop shape. * * In this case: * - `timespan` must be explicitly duration-valued; * - the duration value itself is the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSStandardSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("IFSStandardSingleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("IFSStandardSingleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } const TimeDuration timeRange = *input.timespan.duration; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSStandardSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical IFS single-loop window. * * In this case: * - the canonical time range is the normalized duration-valued `timespan`; * - the statistical processing type is the innermost normalized processing; * - the increment semantics are resolved locally from explicit, missing, or * defaulted input rules; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSStandardSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)classification; (void)anchor; (void)domain; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("IFSStandardSingleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("IFSStandardSingleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSStandardSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration timeRange = *input.timespan.duration; if (!compareTimeDuration(timeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException( "IFSStandardSingleLoop timespan duration does not match the resolved outer time range", input.to_json(), Here()); } ResolvedInnerIncrement resolvedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedIncrement.timeIncrement = missingIncrement(); resolvedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSStandardSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, timeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // The increment kind follows the locally resolved IFS increment rules. const auto typeOfTimeIncrement = resolvedIncrement.typeOfTimeIncrement; // The canonical range is exactly the normalized duration-valued timespan. const auto canonicalTimeRange = timeRange; // The increment is the explicit, missing, or defaulted value resolved above. const auto timeIncrement = resolvedIncrement.timeIncrement; ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_IFSStandardSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSStandardSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSStandardSingleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the direct duration-valued `timespan`; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed IFS single-loop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSStandardSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSStandardSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSStandardSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected IFSStandardSingleLoop", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasDurationValue = input.timespan.duration.has_value(); const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool doesNotRequireFakeSingleLoopDoubleLoop = !requiresFakeSecondLoop; if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !hasDurationTimespan || !hasDurationValue || !hasNoStattypeBlocks || !doesNotRequireFakeDoubleLoop || !doesNotRequireFakeSingleLoopDoubleLoop) { throw Mars2GribModelException("IFSStandardSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("IFSStandardSingleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSStandardSingleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = *input.timespan.duration; if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException( "IFSStandardSingleLoop outer time range does not match the direct timespan duration", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("IFSStandardSingleLoop shape must contain exactly one window", input.to_json(), Here()); } ResolvedInnerIncrement expectedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedIncrement.timeIncrement = missingIncrement(); expectedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSStandardSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, expectedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSStandardSingleLoop window statistical processing does not match the innermost input processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != expectedIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("IFSStandardSingleLoop window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSStandardSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, expectedIncrement.timeIncrement)) { throw Mars2GribModelException("IFSStandardSingleLoop window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_IFSStandardSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/SeasonalSingleLoop.h0000664000175000017500000006105215246725757033635 0ustar alastairalastair/* * (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 SeasonalSingleLoop.h /// @brief Matcher, builders, and checker for the SeasonalSingleLoop shape. /// /// This header is the authoritative implementation of the `SeasonalSingleLoop` /// shape case. It keeps recognition, construction, and validation together so /// that the complete case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// intrinsic one-month outer time range directly in the callback. The final /// builder constructs the one canonical seasonal window from visible local /// members and locally resolved increment semantics. The checker validates that /// the resolved shape remains consistent with both the input semantics and the /// stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeUtils.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the SeasonalSingleLoop shape. * * - the product uses the seasonal class/stream discriminator: * - `marsClass` is `od`, `rd`, or `c3`; * - `marsStream` is `sfmd` or `shmd`; * - the normalized input also carries seasonal lead semantics: * - `step` is absent; * - `fcmonth` is present; * - the product is forecast; * - the product is not synoptic; * - `timespan` is explicitly `none`, or it is missing and the configured * statistical-product rule allows that source representation; * - exactly one outer `stattype` block is present. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_SeasonalSingleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasSeasonalClass = input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3"; const bool hasSeasonalStream = input.marsStream == "sfmd" || input.marsStream == "shmd"; const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool isNotSynoptic = !input.isSynoptic; const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; return hasSeasonalClass && hasSeasonalStream && hasSeasonalLeadSemantics && isForecast && isNotSynoptic && hasAcceptedTimespanRepresentation && hasExactlyOneStattypeBlock; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_SeasonalSingleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the SeasonalSingleLoop shape. * * In this case: * - the seasonal single-loop window range is intrinsic; * - the canonical outer time range is exactly one calendar month; * - the source `stattype` block is redundant and must agree with the * innermost statistical processing. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_SeasonalSingleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::oneMonth; try { (void)classification; if (input.stattype.size() != 1) { throw Mars2GribModelException("SeasonalSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "SeasonalSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration timeRange = oneMonth(); if (!compareTimeDuration(stattype.timeRange, timeRange)) { throw Mars2GribModelException("SeasonalSingleLoop stattype range must be one calendar month", input.to_json(), Here()); } const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_SeasonalSingleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical seasonal single-loop window. * * In this case: * - the unique `stattype` block is redundant and is checked against the * intrinsic one-month seasonal range and the innermost processing type; * - the statistical processing type is the innermost normalized processing; * - the canonical time range is one calendar month; * - increment resolution follows the normal single-loop structure; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_SeasonalSingleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::oneMonth; try { (void)classification; (void)anchor; (void)domain; if (input.stattype.size() != 1) { throw Mars2GribModelException("SeasonalSingleLoop requires exactly one stattype block", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SeasonalSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "SeasonalSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration timeRange = oneMonth(); if (!compareTimeDuration(*outerTimeRange.timeRange, timeRange)) { throw Mars2GribModelException("SeasonalSingleLoop outer time range must be one calendar month", input.to_json(), Here()); } if (!compareTimeDuration(stattype.timeRange, timeRange)) { throw Mars2GribModelException("SeasonalSingleLoop stattype range must be one calendar month", input.to_json(), Here()); } ResolvedInnerIncrement resolvedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } resolvedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedIncrement.timeIncrement = missingIncrement(); resolvedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException("Default time-increment deduction for SeasonalSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, timeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } resolvedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } // The redundant stattype block must agree with the real innermost seasonal processing. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // The increment kind follows the locally resolved single-loop rules. const auto typeOfTimeIncrement = resolvedIncrement.typeOfTimeIncrement; // The canonical seasonal single-loop range is intrinsically one month. const auto canonicalTimeRange = timeRange; // The increment is the explicit, missing, or defaulted value resolved above. const auto timeIncrement = resolvedIncrement.timeIncrement; ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_SeasonalSingleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved SeasonalSingleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `SeasonalSingleLoop`; * - the originating input still satisfies the seasonal class/stream and lead semantics; * - the stage-1 outer time range is available and equals one calendar month; * - the resolved shape contains exactly one canonical window; * - the redundant `stattype` block matches the one-month range and innermost processing; * - that window matches the locally recomputed seasonal single-loop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the SeasonalSingleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_SeasonalSingleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::backend::tables::enum2name_TypeOfStatisticalProcessing_or_throw; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::oneMonth; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::SeasonalSingleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected SeasonalSingleLoop", input.to_json(), Here()); } const bool hasSeasonalClass = input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3"; const bool hasSeasonalStream = input.marsStream == "sfmd" || input.marsStream == "shmd"; const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool isNotSynoptic = !input.isSynoptic; const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForStatisticalProduct); const bool hasExactlyOneStattypeBlock = input.stattype.size() == 1; if (!hasSeasonalClass || !hasSeasonalStream || !hasSeasonalLeadSemantics || !isForecast || !isNotSynoptic || !hasAcceptedTimespanRepresentation || !hasExactlyOneStattypeBlock) { throw Mars2GribModelException("SeasonalSingleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("SeasonalSingleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SeasonalSingleLoop requires an available outer time range", input.to_json(), Here()); } const auto& stattype = input.stattype.front(); const bool processingTypesMatch = stattype.typeOfStatisticalProcessing == input.innerMostTypeOfStatisticalProcessing; if (!processingTypesMatch) { std::ostringstream os; os << "SeasonalSingleLoop stattype processing must match the innermost processing: stattype=" << enum2name_TypeOfStatisticalProcessing_or_throw(stattype.typeOfStatisticalProcessing) << ", innermost=" << enum2name_TypeOfStatisticalProcessing_or_throw(input.innerMostTypeOfStatisticalProcessing); throw Mars2GribModelException(os.str(), input.to_json(), Here()); } const TimeDuration expectedTimeRange = oneMonth(); if (!compareTimeDuration(*outerTimeRange.timeRange, expectedTimeRange)) { throw Mars2GribModelException("SeasonalSingleLoop outer time range must be one calendar month", input.to_json(), Here()); } if (!compareTimeDuration(stattype.timeRange, expectedTimeRange)) { throw Mars2GribModelException("SeasonalSingleLoop stattype range must be one calendar month", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("SeasonalSingleLoop shape must contain exactly one window", input.to_json(), Here()); } ResolvedInnerIncrement expectedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } expectedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedIncrement.timeIncrement = missingIncrement(); expectedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException("Default time-increment deduction for SeasonalSingleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, expectedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } expectedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "SeasonalSingleLoop window statistical processing does not match the innermost input processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != expectedIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("SeasonalSingleLoop window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("SeasonalSingleLoop window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, expectedIncrement.timeIncrement)) { throw Mars2GribModelException("SeasonalSingleLoop window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_SeasonalSingleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/Instant.h0000664000175000017500000003667315246725757031527 0ustar alastairalastair/* * (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 Instant.h /// @brief Matcher, builders, and checker for the Instant shape. /// /// This header is the authoritative implementation of the `Instant` shape /// case. It keeps recognition, construction, and validation together so that /// the complete case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range for this case. The final builder constructs the /// one canonical instant window directly from visible local members. The /// checker validates that the resolved shape remains consistent with both the /// input semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeUtils.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the Instant shape. * * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - `timespan` is explicitly `none`, or it is missing and the configured * instant compatibility rule allows that source representation; * - no `stattype` blocks are present; * - the innermost statistical processing is missing. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_Instant_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForInstantProduct); const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasMissingStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing == TypeOfStatisticalProcessing::Missing; return isNotSeasonal && hasAcceptedTimespanRepresentation && hasNoStattypeBlocks && hasMissingStatisticalProcessing; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_Instant_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the Instant shape. * * In this case: * - any redundant source increment must still be valid; * - the canonical outer time range is available immediately; * - the canonical outer time range is exactly zero duration. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_Instant_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::zeroDuration; try { (void)classification; // Instant products do not require a source increment. A present source // increment is therefore accepted only when redundant values are // explicitly enabled. const bool hasIncrement = input.timeIncrement.has_value(); const bool redundantIncrementIsAllowed = input.allowRedundantTimeIncrement; if (hasIncrement && !redundantIncrementIsAllowed) { throw Mars2GribModelException( "Instant timeIncrementInSeconds is redundant but redundant values are disabled", input.to_json(), Here()); } const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; const auto timeRange = zeroDuration(); return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_Instant_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical Instant window. * * In this case: * - the innermost statistical processing is missing; * - the canonical time increment kind is missing; * - the canonical time range is zero duration; * - the canonical time increment is missing. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_Instant_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::zeroDuration; try { (void)classification; (void)anchor; (void)outerTimeRange; (void)domain; // Instant products have no statistical-processing interval semantics. const TypeOfStatisticalProcessing typeOfStatisticalProcessing = TypeOfStatisticalProcessing::Missing; // Instant products therefore expose no increment-kind semantics. const auto typeOfTimeIncrement = missingTypeOfTimeIncrement(); // The canonical instant window has zero temporal extent. const auto timeRange = zeroDuration(); // Instant products do not encode a time increment value. const auto timeIncrement = missingIncrement(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, timeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `build_Instant_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved Instant shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `Instant`; * - the stage-1 outer time range is available and equals zero duration; * - the resolved shape contains exactly one canonical window; * - that window carries missing statistical-processing and increment semantics; * - the originating input still satisfies the Instant case semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the Instant case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_Instant_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsMissingAndAllowed; using metkit::mars2grib::backend::models::product_time_spec::shape::detail::timespanIsNone; using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::backend::tables::TypeOfTimeIntervals; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::zeroDuration; try { (void)anchor; (void)domain; if (classification.shapeType != ProductTimeSpecShapeKind::Instant) { throw Mars2GribModelException("Shape classification mismatch: expected Instant", input.to_json(), Here()); } const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool hasAcceptedTimespanRepresentation = timespanIsNone(input) || timespanIsMissingAndAllowed(input, input.allowMissingTimespanForInstantProduct); const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasMissingStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing == TypeOfStatisticalProcessing::Missing; if (!isNotSeasonal || !hasAcceptedTimespanRepresentation || !hasNoStattypeBlocks || !hasMissingStatisticalProcessing) { throw Mars2GribModelException("Instant input semantics are not satisfied", input.to_json(), Here()); } if (outerTimeRange.availability != ProductTimeSpecOuterTimeRangeAvailability::Available) { throw Mars2GribModelException("Instant shape requires an available outer time range", input.to_json(), Here()); } if (!outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("Instant shape requires a present outer time range value", input.to_json(), Here()); } if (!compareTimeDuration(*outerTimeRange.timeRange, zeroDuration())) { throw Mars2GribModelException("Instant outer time range must be the canonical zero duration", input.to_json(), Here()); } if (shape.values.size() != 1) { throw Mars2GribModelException("Instant shape must contain exactly one window", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != TypeOfStatisticalProcessing::Missing) { throw Mars2GribModelException("Instant window must use missing statistical processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != TypeOfTimeIntervals::Missing) { throw Mars2GribModelException("Instant window must use missing typeOfTimeIncrement", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, zeroDuration())) { throw Mars2GribModelException("Instant window timeRange must be the canonical zero duration", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, missingIncrement())) { throw Mars2GribModelException("Instant window timeIncrement must be missing", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException("Instant window timeRange must match the outer time range", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_Instant_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/SeasonalMultiloop.h0000664000175000017500000007152415246725757033553 0ustar alastairalastair/* * (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 SeasonalMultiloop.h /// @brief Matcher, builders, and checker for the SeasonalMultiloop shape. /// /// This header is the authoritative implementation of the `SeasonalMultiloop` /// shape case. It keeps recognition, construction, and validation together so /// that the complete case can be reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the outer /// seasonal loop range directly from the outermost `stattype` block. The final /// builder constructs all canonical windows from visible local members and /// locally resolved innermost increment semantics. The checker validates that /// the resolved shape remains consistent with both the input semantics and the /// stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the SeasonalMultiloop shape. * * - the product uses the seasonal class/stream discriminator: * - `marsClass` is `od`, `rd`, or `c3`; * - `marsStream` is `sfmd` or `shmd`; * - the normalized input also carries seasonal lead semantics: * - `step` is absent; * - `fcmonth` is present; * - the product is forecast; * - the product is not synoptic; * - `timespan` is duration-valued and contains the innermost range; * - one or more outer `stattype` blocks are present. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_SeasonalMultiloop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool hasSeasonalClass = input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3"; const bool hasSeasonalStream = input.marsStream == "sfmd" || input.marsStream == "shmd"; const bool hasNoStep = !input.step.has_value(); const bool hasFcmonth = input.marsFcmonth.has_value(); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasTimespanDuration = input.timespan.duration.has_value(); const bool hasOuterStattypeBlocks = !input.stattype.empty(); return hasSeasonalClass && hasSeasonalStream && hasNoStep && hasFcmonth && isForecast && isNotSynoptic && hasDurationTimespan && hasTimespanDuration && hasOuterStattypeBlocks; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_SeasonalMultiloop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the SeasonalMultiloop shape. * * In this case: * - at least one outer `stattype` block must be present; * - the outermost `stattype` block is the seasonal monthly loop; * - the outermost `stattype` range must therefore be exactly one month; * - any later monthly `stattype` block is treated as an invalid normalized state. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_SeasonalMultiloop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.stattype.empty()) { throw Mars2GribModelException("SeasonalMultiloop requires at least one stattype block", input.to_json(), Here()); } const TimeDuration outerTimeRange = input.stattype.front().timeRange; const bool outerRangeIsMonthly = outerTimeRange.unit == TimeUnit::Month && outerTimeRange.length == 1; if (!outerRangeIsMonthly) { throw Mars2GribModelException("SeasonalMultiloop outermost stattype range must be one calendar month", input.to_json(), Here()); } for (std::size_t i = 1; i < input.stattype.size(); ++i) { const auto& timeRange = input.stattype[i].timeRange; const bool isMonthly = timeRange.unit == TimeUnit::Month && timeRange.length == 1; if (isMonthly) { throw Mars2GribModelException("SeasonalMultiloop must not contain a second monthly stattype block", input.to_json(), Here()); } } const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, outerTimeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_SeasonalMultiloop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct all canonical seasonal multiloop windows in outermost-to-innermost order. * * In this case: * - the outermost `stattype` block is the seasonal monthly loop; * - any additional `stattype` blocks are inner outer-loops and must not be monthly; * - the innermost range is the normalized duration-valued `timespan` itself; * - each outer window increment equals the next inner window range; * - the innermost increment is resolved locally from explicit, missing, or * defaulted input rules. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_SeasonalMultiloop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)classification; (void)anchor; (void)domain; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("SeasonalMultiloop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("SeasonalMultiloop duration-valued timespan must contain a duration", input.to_json(), Here()); } if (input.stattype.empty()) { throw Mars2GribModelException("SeasonalMultiloop requires at least one stattype block", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SeasonalMultiloop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedOuterTimeRange = input.stattype.front().timeRange; const bool outerRangeIsMonthly = expectedOuterTimeRange.unit == TimeUnit::Month && expectedOuterTimeRange.length == 1; if (!outerRangeIsMonthly) { throw Mars2GribModelException("SeasonalMultiloop outermost stattype range must be one calendar month", input.to_json(), Here()); } if (!compareTimeDuration(*outerTimeRange.timeRange, expectedOuterTimeRange)) { throw Mars2GribModelException( "SeasonalMultiloop outer time range does not match the outermost stattype range", input.to_json(), Here()); } for (std::size_t i = 1; i < input.stattype.size(); ++i) { const auto& timeRange = input.stattype[i].timeRange; const bool isMonthly = timeRange.unit == TimeUnit::Month && timeRange.length == 1; if (isMonthly) { throw Mars2GribModelException("SeasonalMultiloop must not contain a second monthly stattype block", input.to_json(), Here()); } } std::vector windows; windows.reserve(input.stattype.size() + 1); for (std::size_t i = 0; i < input.stattype.size(); ++i) { const auto& stattypeBlock = input.stattype[i]; const auto& timeRange = stattypeBlock.timeRange; // Each outer loop preserves the source statistical processing declared by its stattype block. const auto typeOfStatisticalProcessing = stattypeBlock.typeOfStatisticalProcessing; // Outer-loop increment-kind semantics follow the multi-loop outer-window rules. const auto typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, false, timeRange); // Each outer loop range comes directly from its stattype block. const auto outerWindowTimeRange = timeRange; // The increment is filled later from the next inner window range. const auto timeIncrement = missingIncrement(); windows.push_back(ProductTimeSpecWindow{typeOfStatisticalProcessing, typeOfTimeIncrement, outerWindowTimeRange, timeIncrement}); } const TimeDuration innermostTimeRange = *input.timespan.duration; ResolvedInnerIncrement resolvedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedInnermostIncrement.timeIncrement = missingIncrement(); resolvedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException("Default time-increment deduction for SeasonalMultiloop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, innermostTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } // The innermost loop uses the real normalized innermost processing and the // duration-valued timespan supplied by the input. windows.push_back(ProductTimeSpecWindow{input.innerMostTypeOfStatisticalProcessing, resolvedInnermostIncrement.typeOfTimeIncrement, innermostTimeRange, resolvedInnermostIncrement.timeIncrement}); for (std::size_t windowIndex = 0; windowIndex + 1 < windows.size(); ++windowIndex) { windows[windowIndex].timeIncrement = windows[windowIndex + 1].timeRange; } return ProductTimeSpecShape{windows}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_SeasonalMultiloop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved SeasonalMultiloop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `SeasonalMultiloop`; * - the originating input still satisfies the seasonal class/stream and lead semantics; * - the stage-1 outer time range matches the outermost monthly stattype block; * - the resolved shape contains one window per stattype block plus one innermost window; * - each outer window increment equals the next inner window range; * - the innermost window matches the locally recomputed seasonal multiloop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the SeasonalMultiloop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_SeasonalMultiloop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::SeasonalMultiloop) { throw Mars2GribModelException("Shape classification mismatch: expected SeasonalMultiloop", input.to_json(), Here()); } const bool hasSeasonalClass = input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3"; const bool hasSeasonalStream = input.marsStream == "sfmd" || input.marsStream == "shmd"; const bool hasNoStep = !input.step.has_value(); const bool hasFcmonth = input.marsFcmonth.has_value(); const bool isForecast = input.simulationType == SimulationType::Forecast; const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasTimespanDuration = input.timespan.duration.has_value(); const bool hasOuterStattypeBlocks = !input.stattype.empty(); if (!hasSeasonalClass || !hasSeasonalStream || !hasNoStep || !hasFcmonth || !isForecast || !isNotSynoptic || !hasDurationTimespan || !hasTimespanDuration || !hasOuterStattypeBlocks) { throw Mars2GribModelException("SeasonalMultiloop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("SeasonalMultiloop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("SeasonalMultiloop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedOuterTimeRange = input.stattype.front().timeRange; const bool outerRangeIsMonthly = expectedOuterTimeRange.unit == TimeUnit::Month && expectedOuterTimeRange.length == 1; if (!outerRangeIsMonthly) { throw Mars2GribModelException("SeasonalMultiloop outermost stattype range must be one calendar month", input.to_json(), Here()); } for (std::size_t i = 1; i < input.stattype.size(); ++i) { const auto& timeRange = input.stattype[i].timeRange; const bool isMonthly = timeRange.unit == TimeUnit::Month && timeRange.length == 1; if (isMonthly) { throw Mars2GribModelException("SeasonalMultiloop must not contain a second monthly stattype block", input.to_json(), Here()); } } if (!compareTimeDuration(*outerTimeRange.timeRange, expectedOuterTimeRange)) { throw Mars2GribModelException( "SeasonalMultiloop outer time range does not match the outermost stattype range", input.to_json(), Here()); } if (shape.values.size() != input.stattype.size() + 1) { throw Mars2GribModelException( "SeasonalMultiloop shape must contain one window per stattype block plus one innermost window", input.to_json(), Here()); } const TimeDuration innermostTimeRange = *input.timespan.duration; ResolvedInnerIncrement expectedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedInnermostIncrement.timeIncrement = missingIncrement(); expectedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException("Default time-increment deduction for SeasonalMultiloop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, innermostTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } for (std::size_t i = 0; i < input.stattype.size(); ++i) { const ProductTimeSpecWindow& window = shape.values[i]; const auto& stattypeBlock = input.stattype[i]; if (window.typeOfStatisticalProcessing != stattypeBlock.typeOfStatisticalProcessing) { throw Mars2GribModelException("SeasonalMultiloop outer window statistical processing is inconsistent", input.to_json(), Here()); } if (window.typeOfTimeIncrement != typeOfTimeIncrementForWindow(input, true, false, stattypeBlock.timeRange)) { throw Mars2GribModelException("SeasonalMultiloop outer window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, stattypeBlock.timeRange)) { throw Mars2GribModelException("SeasonalMultiloop outer window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, shape.values[i + 1].timeRange)) { throw Mars2GribModelException( "SeasonalMultiloop outer window timeIncrement must equal the next inner window range", input.to_json(), Here()); } } const ProductTimeSpecWindow& innermostWindow = shape.values.back(); if (innermostWindow.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "SeasonalMultiloop innermost window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (innermostWindow.typeOfTimeIncrement != expectedInnermostIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("SeasonalMultiloop innermost window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(innermostWindow.timeRange, innermostTimeRange)) { throw Mars2GribModelException("SeasonalMultiloop innermost window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(innermostWindow.timeIncrement, expectedInnermostIncrement.timeIncrement)) { throw Mars2GribModelException("SeasonalMultiloop innermost window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_SeasonalMultiloop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLoopAtZero.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLo0000664000175000017500000005157215246725757033574 0ustar alastairalastair/* * (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 IFSFromStartSingleLoopAtZero.h /// @brief Matcher, builders, and checker for the IFSFromStartSingleLoopAtZero shape. /// /// This header is the authoritative implementation of the /// `IFSFromStartSingleLoopAtZero` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder keeps the outer time /// range deferred for this case. The final builder constructs the one canonical /// from-start IFS statistical window from visible local members and locally /// resolved increment semantics. The checker validates that the resolved shape /// remains consistent with both the input semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSFromStartSingleLoopAtZero shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` uses from-start semantics; * - no outer `stattype` blocks are present; * - step is zero; * - zero-length from-start windows are enabled; * - the innermost statistical processing is allowed at step zero. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSFromStartSingleLoopAtZero_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsZero; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasZeroStep = stepIsZero(input); const bool stepZeroIsAllowed = input.allowZeroLengthFsWindow; const bool operationIsAllowed = input.isAllowedInnerTypeOfStatisticalProcessingAtStepZero; return isIfs && isForecast && isNotSeasonal && isNotSynoptic && usesFromStartTimespan && hasNoStattypeBlocks && hasZeroStep && stepZeroIsAllowed && operationIsAllowed; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_IFSFromStartSingleLoopAtZero_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSFromStartSingleLoopAtZero shape. * * In this case: * - the final range depends on the resolved absolute domain span; * - the stage-1 outer time range therefore remains deferred. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSFromStartSingleLoopAtZero_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Deferred; return ProductTimeSpecOuterTimeRange{availability, std::nullopt}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFromStartSingleLoopAtZero_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical IFS from-start zero-step window. * * In this case: * - the canonical time range is the resolved domain span; * - the statistical processing type is the innermost normalized processing; * - increment resolution follows the normal IFS single-loop structure; * - this is the only case where increment validation does not reject values * larger than the final time range; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSFromStartSingleLoopAtZero_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)classification; (void)anchor; const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration timeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); ResolvedInnerIncrement resolvedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } resolvedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedIncrement.timeIncrement = missingIncrement(); resolvedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFromStartSingleLoopAtZero is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, timeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } resolvedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // The increment kind follows the locally resolved IFS increment rules. const auto typeOfTimeIncrement = resolvedIncrement.typeOfTimeIncrement; // The canonical range is the real domain span for the zero-step from-start case. const auto canonicalTimeRange = timeRange; // The increment is the explicit, missing, or defaulted value resolved above. const auto timeIncrement = resolvedIncrement.timeIncrement; ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFromStartSingleLoopAtZero_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSFromStartSingleLoopAtZero shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSFromStartSingleLoopAtZero`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range remains deferred; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed zero-step from-start semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSFromStartSingleLoopAtZero case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSFromStartSingleLoopAtZero_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsZero; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSFromStartSingleLoopAtZero) { throw Mars2GribModelException("Shape classification mismatch: expected IFSFromStartSingleLoopAtZero", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasZeroStep = stepIsZero(input); const bool stepZeroIsAllowed = input.allowZeroLengthFsWindow; const bool operationIsAllowed = input.isAllowedInnerTypeOfStatisticalProcessingAtStepZero; if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !usesFromStartTimespan || !hasNoStattypeBlocks || !hasZeroStep || !stepZeroIsAllowed || !operationIsAllowed) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException( "IFSFromStartSingleLoopAtZero shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); if (shape.values.size() != 1) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero shape must contain exactly one window", input.to_json(), Here()); } ResolvedInnerIncrement expectedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } expectedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedIncrement.timeIncrement = missingIncrement(); expectedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFromStartSingleLoopAtZero is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, expectedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } expectedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSFromStartSingleLoopAtZero window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != expectedIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, expectedIncrement.timeIncrement)) { throw Mars2GribModelException("IFSFromStartSingleLoopAtZero window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_IFSFromStartSingleLoopAtZero_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeSingleLoopDoubleLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFakeSingleLoopDou0000664000175000017500000006573415246725757033535 0ustar alastairalastair/* * (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 IFSFakeSingleLoopDoubleLoop.h /// @brief Matcher, builders, and checker for the IFSFakeSingleLoopDoubleLoop shape. /// /// This header is the authoritative implementation of the /// `IFSFakeSingleLoopDoubleLoop` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the /// canonical outer time range directly from the normalized duration-valued /// `timespan`. The final builder constructs the two canonical windows from /// visible local members and locally resolved increment semantics. The checker /// validates that the resolved shape remains consistent with both the input /// semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSFakeSingleLoopDoubleLoop shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` is duration-valued; * - no outer `stattype` blocks are present; * - the product does not require the fake-double-loop compatibility case; * - the product requires the fake-single-loop-double-loop compatibility case. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSFakeSingleLoopDoubleLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; return isIfs && isForecast && isNotSeasonal && isNotSynoptic && hasDurationTimespan && hasNoStattypeBlocks && doesNotRequireFakeDoubleLoop && requiresFakeSecondLoop; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_IFSFakeSingleLoopDoubleLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSFakeSingleLoopDoubleLoop shape. * * In this case: * - `timespan` must be explicitly duration-valued; * - the duration value itself is the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSFakeSingleLoopDoubleLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } const TimeDuration timeRange = *input.timespan.duration; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, timeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFakeSingleLoopDoubleLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the two canonical windows of the IFS fake-single-loop double-loop shape. * * In this case: * - the normalized duration-valued `timespan` is shared by both canonical windows; * - the synthetic outer window uses `IndexProcessing`; * - the synthetic outer window increment equals the inner window range; * - the innermost window follows normal IFS single-loop increment semantics; * - exactly two canonical windows are produced in outermost-to-innermost order. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSFakeSingleLoopDoubleLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)classification; (void)anchor; (void)domain; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration sharedTimeRange = *input.timespan.duration; if (!compareTimeDuration(sharedTimeRange, *outerTimeRange.timeRange)) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop timespan duration does not match the resolved outer time range", input.to_json(), Here()); } ResolvedInnerIncrement resolvedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (sharedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(sharedTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, sharedTimeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedInnermostIncrement.timeIncrement = missingIncrement(); resolvedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFakeSingleLoopDoubleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, sharedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (sharedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(sharedTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, sharedTimeRange); } // The synthetic outer loop is the fake compatibility shell and therefore // uses IndexProcessing. const TypeOfStatisticalProcessing outerTypeOfStatisticalProcessing = TypeOfStatisticalProcessing::IndexProcessing; // The outer synthetic window uses the multi-loop outer increment-kind semantics. const auto outerTypeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, false, sharedTimeRange); // The outer synthetic window shares the same source range as the inner one. const auto outerTimeRangeValue = sharedTimeRange; // In this fake-single-loop case, the outer increment equals the inner range, // and the inner range equals the shared outer range. const auto outerTimeIncrement = sharedTimeRange; ProductTimeSpecWindow outerWindow{outerTypeOfStatisticalProcessing, outerTypeOfTimeIncrement, outerTimeRangeValue, outerTimeIncrement}; // The innermost window preserves the real normalized statistical processing. const auto innerTypeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // The innermost increment kind follows the locally resolved IFS rules. const auto innerTypeOfTimeIncrement = resolvedInnermostIncrement.typeOfTimeIncrement; // The innermost range is the same shared source range. const auto innerTimeRange = sharedTimeRange; // The innermost increment is the explicit, missing, or defaulted resolved value. const auto innerTimeIncrement = resolvedInnermostIncrement.timeIncrement; ProductTimeSpecWindow innerWindow{innerTypeOfStatisticalProcessing, innerTypeOfTimeIncrement, innerTimeRange, innerTimeIncrement}; return ProductTimeSpecShape{{outerWindow, innerWindow}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFakeSingleLoopDoubleLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSFakeSingleLoopDoubleLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSFakeSingleLoopDoubleLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the direct duration-valued `timespan`; * - the resolved shape contains exactly two canonical windows; * - the synthetic outer window uses `IndexProcessing` and its increment equals the inner range; * - the innermost window matches the locally recomputed IFS single-loop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSFakeSingleLoopDoubleLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSFakeSingleLoopDoubleLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSFakeSingleLoopDoubleLoop) { throw Mars2GribModelException("Shape classification mismatch: expected IFSFakeSingleLoopDoubleLoop", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasDurationValue = input.timespan.duration.has_value(); const bool hasNoStattypeBlocks = input.stattype.empty(); const bool requiresFakeDoubleLoop = input.requiresFakeDoubleLoopSingleLoopRepresentation; const bool doesNotRequireFakeDoubleLoop = !requiresFakeDoubleLoop; const bool requiresFakeSecondLoop = input.requiresFakeSingleLoopDoubleLoopRepresentation; if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !hasDurationTimespan || !hasDurationValue || !hasNoStattypeBlocks || !doesNotRequireFakeDoubleLoop || !requiresFakeSecondLoop) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration sharedTimeRange = *input.timespan.duration; if (!compareTimeDuration(*outerTimeRange.timeRange, sharedTimeRange)) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop outer time range does not match the direct timespan duration", input.to_json(), Here()); } if (shape.values.size() != 2) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop shape must contain exactly two windows", input.to_json(), Here()); } ResolvedInnerIncrement expectedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (sharedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(sharedTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, sharedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedInnermostIncrement.timeIncrement = missingIncrement(); expectedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFakeSingleLoopDoubleLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, sharedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (sharedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(sharedTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, sharedTimeRange); } const ProductTimeSpecWindow& outerWindow = shape.values[0]; const ProductTimeSpecWindow& innerWindow = shape.values[1]; if (outerWindow.typeOfStatisticalProcessing != TypeOfStatisticalProcessing::IndexProcessing) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop outer window must use IndexProcessing", input.to_json(), Here()); } if (innerWindow.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop inner window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (outerWindow.typeOfTimeIncrement != typeOfTimeIncrementForWindow(input, true, false, sharedTimeRange)) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop outer window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (innerWindow.typeOfTimeIncrement != expectedInnermostIncrement.typeOfTimeIncrement) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop inner window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(outerWindow.timeRange, sharedTimeRange) || !compareTimeDuration(innerWindow.timeRange, sharedTimeRange)) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop window ranges are inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(outerWindow.timeIncrement, innerWindow.timeRange)) { throw Mars2GribModelException( "IFSFakeSingleLoopDoubleLoop outer window increment must equal the inner window range", input.to_json(), Here()); } if (!compareTimeDuration(innerWindow.timeIncrement, expectedInnermostIncrement.timeIncrement)) { throw Mars2GribModelException("IFSFakeSingleLoopDoubleLoop inner window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_IFSFakeSingleLoopDoubleLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleLoopAtZero.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/AIFSFromStartSingleL0000664000175000017500000004244015246725757033510 0ustar alastairalastair/* * (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 AIFSFromStartSingleLoopAtZero.h /// @brief Matcher, builders, and checker for the AIFSFromStartSingleLoopAtZero shape. /// /// This header is the authoritative implementation of the /// `AIFSFromStartSingleLoopAtZero` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder keeps the outer time /// range deferred for this case. The final builder constructs the one canonical /// from-start AIFS statistical window from visible local members. The checker /// validates that the resolved shape remains consistent with both the input /// semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the AIFSFromStartSingleLoopAtZero shape. * * - the regime is AIFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - the source increment is missing; * - `timespan` uses from-start semantics; * - no outer `stattype` blocks are present; * - step is zero; * - zero-length from-start windows are enabled; * - the innermost statistical processing is allowed at step zero. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_AIFSFromStartSingleLoopAtZero_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsZero; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasZeroStep = stepIsZero(input); const bool stepZeroIsAllowed = input.allowZeroLengthFsWindow; const bool operationIsAllowed = input.isAllowedInnerTypeOfStatisticalProcessingAtStepZero; return isAifs && isForecast && isNotSeasonal && isNotSynoptic && sourceIncrementIsMissing && usesFromStartTimespan && hasNoStattypeBlocks && hasZeroStep && stepZeroIsAllowed && operationIsAllowed; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_AIFSFromStartSingleLoopAtZero_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the AIFSFromStartSingleLoopAtZero shape. * * In this case: * - the final range depends on the resolved absolute domain span; * - the stage-1 outer time range therefore remains deferred. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_AIFSFromStartSingleLoopAtZero_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Deferred; return ProductTimeSpecOuterTimeRange{availability, std::nullopt}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFromStartSingleLoopAtZero_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical AIFS from-start zero-step window. * * In this case: * - the canonical time range is the resolved domain span; * - the statistical processing type is the innermost normalized processing; * - the canonical time increment kind is missing; * - the canonical time increment value is missing; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_AIFSFromStartSingleLoopAtZero_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)classification; (void)anchor; const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero requires a deferred outer time range", input.to_json(), Here()); } if (input.timeIncrement.has_value()) { throw Mars2GribModelException("AIFS statistics require timeIncrementInSeconds to be missing", input.to_json(), Here()); } const TimeDuration timeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // Pure AIFS from-start products always encode missing increment-kind semantics. const auto typeOfTimeIncrement = missingTypeOfTimeIncrement(); // The canonical range is the real domain span for the zero-step from-start case. const auto canonicalTimeRange = timeRange; // Pure AIFS from-start products always encode a missing increment value. const auto timeIncrement = missingIncrement(); ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_AIFSFromStartSingleLoopAtZero_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved AIFSFromStartSingleLoopAtZero shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `AIFSFromStartSingleLoopAtZero`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range remains deferred; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed zero-step from-start AIFS semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the AIFSFromStartSingleLoopAtZero case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_AIFSFromStartSingleLoopAtZero_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsZero; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::AIFSFromStartSingleLoopAtZero) { throw Mars2GribModelException("Shape classification mismatch: expected AIFSFromStartSingleLoopAtZero", input.to_json(), Here()); } const bool isAifs = input.regime == SimulationRegime::AIFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool sourceIncrementIsMissing = !input.timeIncrement.has_value(); const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasZeroStep = stepIsZero(input); const bool stepZeroIsAllowed = input.allowZeroLengthFsWindow; const bool operationIsAllowed = input.isAllowedInnerTypeOfStatisticalProcessingAtStepZero; if (!isAifs || !isForecast || !isNotSeasonal || !isNotSynoptic || !sourceIncrementIsMissing || !usesFromStartTimespan || !hasNoStattypeBlocks || !hasZeroStep || !stepZeroIsAllowed || !operationIsAllowed) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException( "AIFSFromStartSingleLoopAtZero shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); if (shape.values.size() != 1) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero shape must contain exactly one window", input.to_json(), Here()); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "AIFSFromStartSingleLoopAtZero window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != missingTypeOfTimeIncrement()) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, missingIncrement())) { throw Mars2GribModelException("AIFSFromStartSingleLoopAtZero window timeIncrement must be missing", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_AIFSFromStartSingleLoopAtZero_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLoopPositive.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSFromStartSingleLo0000664000175000017500000005404115246725757033566 0ustar alastairalastair/* * (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 IFSFromStartSingleLoopPositive.h /// @brief Matcher, builders, and checker for the IFSFromStartSingleLoopPositive shape. /// /// This header is the authoritative implementation of the /// `IFSFromStartSingleLoopPositive` shape case. It keeps recognition, /// construction, and validation together so that the complete case can be /// reviewed without following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder keeps the outer time /// range deferred for this case. The final builder constructs the one canonical /// from-start IFS statistical window from visible local members and locally /// resolved increment semantics. The checker validates that the resolved shape /// remains consistent with both the input semantics and the stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSFromStartSingleLoopPositive shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` uses from-start semantics; * - no outer `stattype` blocks are present; * - step is strictly positive. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSFromStartSingleLoopPositive_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsPositive; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasPositiveStep = stepIsPositive(input); return isIfs && isForecast && isNotSeasonal && isNotSynoptic && usesFromStartTimespan && hasNoStattypeBlocks && hasPositiveStep; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `match_IFSFromStartSingleLoopPositive_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSFromStartSingleLoopPositive shape. * * In this case: * - the final range depends on the resolved absolute domain span; * - the stage-1 outer time range therefore remains deferred. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSFromStartSingleLoopPositive_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Deferred; return ProductTimeSpecOuterTimeRange{availability, std::nullopt}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFromStartSingleLoopPositive_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct the canonical IFS from-start positive-step window. * * In this case: * - the canonical time range is the resolved domain span; * - the statistical processing type is the innermost normalized processing; * - increment resolution follows the normal IFS single-loop structure; * - normal increment validation applies again for positive from-start ranges; * - exactly one canonical window is produced. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSFromStartSingleLoopPositive_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)classification; (void)anchor; const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration timeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); ResolvedInnerIncrement resolvedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedIncrement.timeIncrement = missingIncrement(); resolvedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFromStartSingleLoopPositive is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, timeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (timeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(timeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, timeRange); } // This case carries the normalized innermost statistical processing // directly into the one canonical window. const auto typeOfStatisticalProcessing = input.innerMostTypeOfStatisticalProcessing; // The increment kind follows the locally resolved IFS increment rules. const auto typeOfTimeIncrement = resolvedIncrement.typeOfTimeIncrement; // The canonical range is the real domain span for the positive from-start case. const auto canonicalTimeRange = timeRange; // The increment is the explicit, missing, or defaulted value resolved above. const auto timeIncrement = resolvedIncrement.timeIncrement; ProductTimeSpecWindow window{typeOfStatisticalProcessing, typeOfTimeIncrement, canonicalTimeRange, timeIncrement}; return ProductTimeSpecShape{{window}}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSFromStartSingleLoopPositive_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSFromStartSingleLoopPositive shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSFromStartSingleLoopPositive`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range remains deferred; * - the resolved shape contains exactly one canonical window; * - that window matches the locally recomputed positive-step from-start semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSFromStartSingleLoopPositive case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSFromStartSingleLoopPositive_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::stepIsPositive; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; using metkit::mars2grib::utils::time_arithmetic::durationBetween; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSFromStartSingleLoopPositive) { throw Mars2GribModelException("Shape classification mismatch: expected IFSFromStartSingleLoopPositive", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool usesFromStartTimespan = input.timespan.kind == TimespanKind::FromStart; const bool hasNoStattypeBlocks = input.stattype.empty(); const bool hasPositiveStep = stepIsPositive(input); if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !usesFromStartTimespan || !hasNoStattypeBlocks || !hasPositiveStep) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException( "IFSFromStartSingleLoopPositive shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsDeferred = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Deferred; if (!outerTimeRangeIsDeferred || outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive requires a deferred outer time range", input.to_json(), Here()); } const TimeDuration expectedTimeRange = durationBetween(domain.domainStartDateTime, domain.domainEndDateTime); if (shape.values.size() != 1) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive shape must contain exactly one window", input.to_json(), Here()); } ResolvedInnerIncrement expectedIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedIncrement.timeIncrement = missingIncrement(); expectedIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSFromStartSingleLoopPositive is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, expectedTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (expectedTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(expectedTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, false, true, expectedTimeRange); } const ProductTimeSpecWindow& window = shape.values.front(); if (window.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSFromStartSingleLoopPositive window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (window.typeOfTimeIncrement != expectedIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, expectedTimeRange)) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, expectedIncrement.timeIncrement)) { throw Mars2GribModelException("IFSFromStartSingleLoopPositive window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `check_IFSFromStartSingleLoopPositive_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardMultiLoop.hmetkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/shapes/impl/IFSStandardMultiLoop0000664000175000017500000006453615246725757033627 0ustar alastairalastair/* * (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 IFSStandardMultiLoop.h /// @brief Matcher, builders, and checker for the IFSStandardMultiLoop shape. /// /// This header is the authoritative implementation of the /// `IFSStandardMultiLoop` shape case. It keeps recognition, construction, and /// validation together so that the complete case can be reviewed without /// following a dispatch chain. /// /// The matcher states every identifying condition as a named Boolean and /// returns their explicit conjunction. The stage-1 builder constructs the outer /// loop range directly from the outermost `stattype` block. The final builder /// constructs all canonical windows from visible local members and locally /// resolved innermost increment semantics. The checker validates that the /// resolved shape remains consistent with both the input semantics and the /// stage artifacts. /// /// Every function catches all failures and rethrows a nested /// `Mars2GribModelException` with the serialized input state. /// /// @ingroup mars2grib_product_time_spec_shapes /// #pragma once #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/TimeIncrement.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/utils/TemporalArithmetic.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail { /** * @brief Return true only when input matches the IFSStandardMultiLoop shape. * * - the regime is IFS; * - the product is forecast; * - the product does not satisfy both the seasonal class/stream discriminator * and the seasonal lead discriminator; * - the product is not synoptic; * - `timespan` is duration-valued and contains the innermost range; * - one or more outer `stattype` blocks are present. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @return `true` only when all documented conditions are satisfied; otherwise `false`. * @throws Mars2GribModelException If evaluating the shape matcher fails unexpectedly. */ inline bool match_IFSStandardMultiLoop_Shape(const ProductTimeSpecInput& input) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasTimespanDuration = input.timespan.duration.has_value(); const bool hasOuterStattypeBlocks = !input.stattype.empty(); return isIfs && isForecast && isNotSeasonal && isNotSynoptic && hasDurationTimespan && hasTimespanDuration && hasOuterStattypeBlocks; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `match_IFSStandardMultiLoop_Shape`", input.to_json(), Here())); } } /** * @brief Construct the stage-1 outer range for the IFSStandardMultiLoop shape. * * In this case: * - at least one outer `stattype` block must be present; * - the outermost `stattype` block defines the canonical outer time range; * - the original normalized duration unit is preserved. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @return Constructed stage-1 outer time range for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecOuterTimeRange build_IFSStandardMultiLoop_ShapeOuterTimeRange( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRange; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { (void)classification; if (input.stattype.empty()) { throw Mars2GribModelException("IFSStandardMultiLoop requires at least one stattype block", input.to_json(), Here()); } const TimeDuration outerTimeRange = input.stattype.front().timeRange; const auto availability = ProductTimeSpecOuterTimeRangeAvailability::Available; return ProductTimeSpecOuterTimeRange{availability, outerTimeRange}; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `build_IFSStandardMultiLoop_ShapeOuterTimeRange`", input.to_json(), Here())); } } /** * @brief Construct all canonical IFS multiloop windows in outermost-to-innermost order. * * In this case: * - the outermost `stattype` block defines the stage-1 outer range; * - every outer window range comes directly from its `stattype` block; * - the innermost range is the normalized duration-valued `timespan` itself; * - each outer window increment equals the next inner window range; * - the innermost increment is resolved locally from explicit, missing, or * defaulted input rules. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @return Constructed ProductTimeSpec shape for this unique case. * @throws Mars2GribModelException If construction detects an invalid or inconsistent state. */ inline ProductTimeSpecShape build_IFSStandardMultiLoop_ShapeWindows( const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)classification; (void)anchor; (void)domain; if (input.timespan.kind != TimespanKind::Duration) { throw Mars2GribModelException("IFSStandardMultiLoop requires a duration-valued timespan", input.to_json(), Here()); } if (!input.timespan.duration.has_value()) { throw Mars2GribModelException("IFSStandardMultiLoop duration-valued timespan must contain a duration", input.to_json(), Here()); } if (input.stattype.empty()) { throw Mars2GribModelException("IFSStandardMultiLoop requires at least one stattype block", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSStandardMultiLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedOuterTimeRange = input.stattype.front().timeRange; if (!compareTimeDuration(*outerTimeRange.timeRange, expectedOuterTimeRange)) { throw Mars2GribModelException( "IFSStandardMultiLoop outer time range does not match the outermost stattype range", input.to_json(), Here()); } std::vector windows; windows.reserve(input.stattype.size() + 1); for (const auto& stattypeBlock : input.stattype) { const auto& timeRange = stattypeBlock.timeRange; // Each outer loop preserves the source statistical processing declared by its stattype block. const auto typeOfStatisticalProcessing = stattypeBlock.typeOfStatisticalProcessing; // Outer-loop increment-kind semantics follow the multi-loop outer-window rules. const auto typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, false, timeRange); // Each outer loop range comes directly from its stattype block. const auto outerWindowTimeRange = timeRange; // The increment is filled later from the next inner window range. const auto timeIncrement = missingIncrement(); windows.push_back(ProductTimeSpecWindow{typeOfStatisticalProcessing, typeOfTimeIncrement, outerWindowTimeRange, timeIncrement}); } const TimeDuration innermostTimeRange = *input.timespan.duration; ResolvedInnerIncrement resolvedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } else if (!input.allowDefaultTimeIncrement) { resolvedInnermostIncrement.timeIncrement = missingIncrement(); resolvedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSStandardMultiLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, innermostTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } resolvedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; resolvedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } // The innermost loop uses the real normalized innermost processing and the // duration-valued timespan supplied by the input. windows.push_back(ProductTimeSpecWindow{input.innerMostTypeOfStatisticalProcessing, resolvedInnermostIncrement.typeOfTimeIncrement, innermostTimeRange, resolvedInnermostIncrement.timeIncrement}); for (std::size_t windowIndex = 0; windowIndex + 1 < windows.size(); ++windowIndex) { windows[windowIndex].timeIncrement = windows[windowIndex + 1].timeRange; } return ProductTimeSpecShape{windows}; } catch (...) { std::throw_with_nested(Mars2GribModelException("Failed to execute `build_IFSStandardMultiLoop_ShapeWindows`", input.to_json(), Here())); } } /** * @brief Validate one resolved IFSStandardMultiLoop shape against its source input and stage artifacts. * * This checker verifies: * - the resolved shape classification is `IFSStandardMultiLoop`; * - the originating input still satisfies the case semantics; * - the stage-1 outer time range matches the outermost stattype block; * - the resolved shape contains one window per stattype block plus one innermost window; * - each outer window increment equals the next inner window range; * - the innermost window matches the locally recomputed IFS multiloop semantics. * * @param[in] input Fully normalized ProductTimeSpec input snapshot. * @param[in] classification Full resolved ProductTimeSpec classification bundle. * @param[in] anchor Previously constructed ProductTimeSpec anchor. * @param[in] outerTimeRange Previously constructed stage-1 outer time range. * @param[in] domain Previously constructed ProductTimeSpec domain. * @param[in] shape Resolved shape artifact produced by the builder. * @return `true` when the shape is valid for the IFSStandardMultiLoop case. * @throws Mars2GribModelException if the resolved shape is inconsistent with * the input, classification, or case semantics. */ inline bool check_IFSStandardMultiLoop_Shape( const ProductTimeSpecInput& input, const metkit::mars2grib::backend::models::product_time_spec::ProductTimeSpecClassification& classification, const metkit::mars2grib::backend::models::product_time_spec::anchor::ProductTimeSpecAnchor& anchor, const ProductTimeSpecOuterTimeRange& outerTimeRange, const metkit::mars2grib::backend::models::product_time_spec::domain::ProductTimeSpecDomain& domain, const ProductTimeSpecShape& shape) { using metkit::mars2grib::backend::deductions::SimulationRegime; using metkit::mars2grib::backend::deductions::SimulationType; using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::deductions::TimespanKind; using metkit::mars2grib::backend::models::product_time_spec::detail::deduceDefaultTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::missingTypeOfTimeIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::ResolvedInnerIncrement; using metkit::mars2grib::backend::models::product_time_spec::detail::typeOfTimeIncrementForWindow; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecOuterTimeRangeAvailability; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecShapeKind; using metkit::mars2grib::backend::models::product_time_spec::shape::ProductTimeSpecWindow; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; using metkit::mars2grib::utils::time_arithmetic::compareTimeDuration; using metkit::mars2grib::utils::time_arithmetic::convertToSeconds; try { (void)anchor; if (classification.shapeType != ProductTimeSpecShapeKind::IFSStandardMultiLoop) { throw Mars2GribModelException("Shape classification mismatch: expected IFSStandardMultiLoop", input.to_json(), Here()); } const bool isIfs = input.regime == SimulationRegime::IFS; const bool isForecast = input.simulationType == SimulationType::Forecast; const bool hasSeasonalClassStream = (input.marsClass == "od" || input.marsClass == "rd" || input.marsClass == "c3") && (input.marsStream == "sfmd" || input.marsStream == "shmd"); const bool hasSeasonalLeadSemantics = !input.step.has_value() && input.marsFcmonth.has_value(); const bool isNotSeasonal = !(hasSeasonalClassStream && hasSeasonalLeadSemantics); const bool isNotSynoptic = !input.isSynoptic; const bool hasDurationTimespan = input.timespan.kind == TimespanKind::Duration; const bool hasTimespanDuration = input.timespan.duration.has_value(); const bool hasOuterStattypeBlocks = !input.stattype.empty(); if (!isIfs || !isForecast || !isNotSeasonal || !isNotSynoptic || !hasDurationTimespan || !hasTimespanDuration || !hasOuterStattypeBlocks) { throw Mars2GribModelException("IFSStandardMultiLoop input semantics are not satisfied", input.to_json(), Here()); } if (domain.isSynoptic) { throw Mars2GribModelException("IFSStandardMultiLoop shape must not be paired with a synoptic domain", input.to_json(), Here()); } const bool outerTimeRangeIsAvailable = outerTimeRange.availability == ProductTimeSpecOuterTimeRangeAvailability::Available; if (!outerTimeRangeIsAvailable || !outerTimeRange.timeRange.has_value()) { throw Mars2GribModelException("IFSStandardMultiLoop requires an available outer time range", input.to_json(), Here()); } const TimeDuration expectedOuterTimeRange = input.stattype.front().timeRange; if (!compareTimeDuration(*outerTimeRange.timeRange, expectedOuterTimeRange)) { throw Mars2GribModelException( "IFSStandardMultiLoop outer time range does not match the outermost stattype range", input.to_json(), Here()); } if (shape.values.size() != input.stattype.size() + 1) { throw Mars2GribModelException( "IFSStandardMultiLoop shape must contain one window per stattype block plus one innermost window", input.to_json(), Here()); } const TimeDuration innermostTimeRange = *input.timespan.duration; ResolvedInnerIncrement expectedInnermostIncrement{}; if (input.timeIncrement.has_value()) { const long incrementInSeconds = convertToSeconds(*input.timeIncrement); if (incrementInSeconds <= 0) { throw Mars2GribModelException("Explicit timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (incrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{incrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } else if (!input.allowDefaultTimeIncrement) { expectedInnermostIncrement.timeIncrement = missingIncrement(); expectedInnermostIncrement.typeOfTimeIncrement = missingTypeOfTimeIncrement(); } else { throw Mars2GribModelException( "Default time-increment deduction for IFSStandardMultiLoop is not implemented", input.to_json(), Here()); const long defaultIncrementInSeconds = deduceDefaultTimeIncrement(input, innermostTimeRange); if (defaultIncrementInSeconds <= 0) { throw Mars2GribModelException("Defaulted timeIncrementInSeconds must be positive", input.to_json(), Here()); } if (innermostTimeRange.unit != TimeUnit::Month) { const long timeRangeInSeconds = convertToSeconds(innermostTimeRange); if (defaultIncrementInSeconds > timeRangeInSeconds) { throw Mars2GribModelException("timeIncrementInSeconds exceeds the innermost time range", input.to_json(), Here()); } } expectedInnermostIncrement.timeIncrement = TimeDuration{defaultIncrementInSeconds, TimeUnit::Second}; expectedInnermostIncrement.typeOfTimeIncrement = typeOfTimeIncrementForWindow(input, true, true, innermostTimeRange); } for (std::size_t i = 0; i < input.stattype.size(); ++i) { const ProductTimeSpecWindow& window = shape.values[i]; const auto& stattypeBlock = input.stattype[i]; if (window.typeOfStatisticalProcessing != stattypeBlock.typeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSStandardMultiLoop outer window statistical processing is inconsistent", input.to_json(), Here()); } if (window.typeOfTimeIncrement != typeOfTimeIncrementForWindow(input, true, false, stattypeBlock.timeRange)) { throw Mars2GribModelException("IFSStandardMultiLoop outer window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeRange, stattypeBlock.timeRange)) { throw Mars2GribModelException("IFSStandardMultiLoop outer window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(window.timeIncrement, shape.values[i + 1].timeRange)) { throw Mars2GribModelException( "IFSStandardMultiLoop outer window timeIncrement must equal the next inner window range", input.to_json(), Here()); } } const ProductTimeSpecWindow& innermostWindow = shape.values.back(); if (innermostWindow.typeOfStatisticalProcessing != input.innerMostTypeOfStatisticalProcessing) { throw Mars2GribModelException( "IFSStandardMultiLoop innermost window statistical processing does not match the innermost input " "processing", input.to_json(), Here()); } if (innermostWindow.typeOfTimeIncrement != expectedInnermostIncrement.typeOfTimeIncrement) { throw Mars2GribModelException("IFSStandardMultiLoop innermost window typeOfTimeIncrement is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(innermostWindow.timeRange, innermostTimeRange)) { throw Mars2GribModelException("IFSStandardMultiLoop innermost window timeRange is inconsistent", input.to_json(), Here()); } if (!compareTimeDuration(innermostWindow.timeIncrement, expectedInnermostIncrement.timeIncrement)) { throw Mars2GribModelException("IFSStandardMultiLoop innermost window timeIncrement is inconsistent", input.to_json(), Here()); } return true; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to execute `check_IFSStandardMultiLoop_Shape`", input.to_json(), Here())); } } } // namespace metkit::mars2grib::backend::models::product_time_spec::shape::detail metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpec.h0000664000175000017500000005123415246725757030723 0ustar alastairalastair/* * (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 ProductTimeSpec.h /// @brief Immutable backend-model `ProductTimeSpec` assembled from classified artifacts. /// /// Exposes: /// - `ProductTimeAnchorSpec`, a reduced backend-model type that resolves only /// the temporal anchor state from input dictionaries; /// - `ProductTimeSpec`, the full backend-model type whose public templated /// constructor starts from input dictionaries, assembles a normalized /// `ProductTimeSpecInput`, classifies the temporal semantics, builds the /// staged artifacts, and then stores the final immutable model state. /// /// This header owns: /// - the immutable backend-model `ProductTimeAnchorSpec` class; /// - the immutable backend-model `ProductTimeSpec` class; /// - the public dictionary-taking constructor entry point; /// - the private staged-construction flow from normalized input to final stored /// components. /// /// This header does NOT: /// - implement the long classification, build, or arithmetic /// helper logic inline; those responsibilities are delegated to the dedicated /// public/detail stage headers. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecClassification.h" #include "metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeDataTypes.h" #include "metkit/mars2grib/backend/models/product-time-spec/anchors/AnchorRegistry.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ShapeNormalization.h" #include "metkit/mars2grib/backend/models/product-time-spec/domains/DomainRegistry.h" #include "metkit/mars2grib/backend/models/product-time-spec/shapes/ShapeRegistry.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec { class ProductTimeAnchorSpec { public: /// /// @brief Build one backend-model `ProductTimeAnchorSpec` from input /// dictionaries. /// /// @section Model-build contract /// - Reads (MARS): direct anchor source keys through anchor-only /// deductions /// - Reads (par): direct anchor source keys through anchor-only /// deductions /// - Reads (opt): only through deductions that require options /// - Writes: none /// - Side effects: deduction-layer logging only /// - Failure mode: throws `Mars2GribModelException` (nested-with) /// /// This reduced public constructor is the anchor-only backend-model entry /// point. It assembles a minimal normalized input snapshot containing only /// the direct anchor sources and then delegates classification and anchor /// materialization to the existing ProductTimeSpec anchor pipeline. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// any deduction or anchor-build failure, with the original cause /// attached via `std::throw_with_nested`. /// template ProductTimeAnchorSpec(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) : ProductTimeAnchorSpec(make_ProductTimeAnchorSpecInput_or_throw(mars, par, opt)) {} /// @brief Return the resolved anchor classification. anchor::ProductTimeSpecAnchorKind anchorType() const noexcept { return anchorType_; } /// @brief Return the resolved anchor artifact. const anchor::ProductTimeSpecAnchor& anchor() const noexcept { return anchor_; } /// /// @brief Serialize the final immutable ProductTimeAnchorSpec as diagnostic /// JSON. /// /// This function is best-effort and never throws. It is intended for upper- /// layer diagnostic context so concept-level exceptions can attach the final /// resolved anchor state without risking a secondary exception. /// /// @return One JSON object string on success, or a stable fallback JSON /// error object if serialization itself fails. /// std::string to_json() const noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("anchorType") << ':' << detail::jsonQuote_modelInput(anchor::productTimeSpecAnchorTypeName(anchorType_)) << ',' << detail::jsonQuote_modelInput("anchor") << ':' << anchor::productTimeSpecAnchorJson(anchor_) << '}'; return out.str(); } catch (...) { return std::string{ "{\"error\":\"ProductTimeAnchorSpec::to_json() failed while building diagnostic context\"}"}; } } private: struct ProductTimeAnchorSpecComponents { anchor::ProductTimeSpecAnchorKind anchorType{anchor::ProductTimeSpecAnchorKind::ForecastAnalysis}; anchor::ProductTimeSpecAnchor anchor{}; }; /// /// @brief Build one reduced normalized `ProductTimeSpecInput` for anchor resolution. /// /// This helper resolves only the direct anchor-source fields required by the /// shared ProductTimeSpec anchor classifier and builder. All non-anchor /// fields remain default-initialized, except /// `innerMostTypeOfStatisticalProcessing`, which is explicitly set to /// `Missing` so the shared input object remains fully initialized. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// @return Complete normalized anchor-only `ProductTimeSpecInput` snapshot. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// any deduction failure, with the original cause attached via /// `std::throw_with_nested`. /// template static ProductTimeSpecInput make_ProductTimeAnchorSpecInput_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { ProductTimeSpecInput input; input.marsYear = deductions::resolve_Year_opt(mars, par, opt); input.marsMonth = deductions::resolve_Month_opt(mars, par, opt); input.marsDate = deductions::resolve_Date_opt(mars, par, opt); input.marsTime = deductions::resolve_Time_opt(mars, par, opt); input.marsHdate = deductions::resolve_Hdate_opt(mars, par, opt); input.innerMostTypeOfStatisticalProcessing = tables::TypeOfStatisticalProcessing::Missing; MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`ProductTimeAnchorSpecInput` built from deductions: "} + input.to_json(); }()); return input; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build `ProductTimeAnchorSpecInput` from deduction outputs", Here())); } mars2gribUnreachable(); } /// /// @brief Build the staged initialization components from reduced normalized input. /// /// This helper executes the anchor-only backend-model pipeline after a /// normalized input snapshot already exists: /// 1. classify anchor; /// 2. build anchor; /// 3. return the immutable member bundle used by the final constructor. /// /// @param[in] input Complete reduced normalized `ProductTimeSpecInput` snapshot. /// @return Complete staged component bundle for final member initialization. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// any classification or build failure, with the original cause /// attached via `std::throw_with_nested`. /// static ProductTimeAnchorSpecComponents build_ProductTimeAnchorSpecComponents_or_throw(ProductTimeSpecInput input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const anchor::ProductTimeSpecAnchorKind anchorType = anchor::classify_Anchor_or_throw(input); ProductTimeSpecClassification classification; classification.anchorType = anchorType; anchor::ProductTimeSpecAnchor anchor = anchor::build_Anchor_or_throw(anchorType, input, classification); ProductTimeAnchorSpecComponents result; result.anchorType = anchorType; result.anchor = std::move(anchor); return result; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to build `ProductTimeAnchorSpec` staged components from normalized input", input.to_json(), Here())); } mars2gribUnreachable(); } /// /// @brief Materialize the anchor-only model from an already-built /// normalized input. /// /// @param[in] input Complete normalized anchor-only input snapshot. /// explicit ProductTimeAnchorSpec(ProductTimeSpecInput input) : ProductTimeAnchorSpec(build_ProductTimeAnchorSpecComponents_or_throw(std::move(input))) {} /// /// @brief Final private construction stage from staged components. /// /// @param[in] components Staged component bundle used for final member /// initialization. /// explicit ProductTimeAnchorSpec(ProductTimeAnchorSpecComponents components) : anchorType_(components.anchorType), anchor_(std::move(components.anchor)) {} const anchor::ProductTimeSpecAnchorKind anchorType_; const anchor::ProductTimeSpecAnchor anchor_; }; class ProductTimeSpec { public: /// /// @brief Build one backend-model `ProductTimeSpec` from input dictionaries. /// /// @section Model-build contract /// - Reads (MARS): all ProductTimeSpec-related source keys via /// `make_ProductTimeSpecInput_or_throw` /// - Reads (par): all ProductTimeSpec-related parameter keys via /// `make_ProductTimeSpecInput_or_throw` /// - Reads (opt): all ProductTimeSpec model-policy booleans and any /// deduction-owned options through the normalized input /// builder /// - Writes: none /// - Side effects: deduction-layer logging only /// - Failure mode: throws `Mars2GribModelException` (nested-with) /// /// This public constructor is the backend-model entry point. It first /// assembles a normalized `ProductTimeSpecInput` snapshot from the deduction /// layer and then delegates all classification, build, and final member /// initialization to private construction stages. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] innerMostTypeOfStatisticalProcessing Caller-supplied /// innermost statistical processing type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// any deduction or normalized-input assembly failure, with the /// original cause attached via `std::throw_with_nested`. /// template ProductTimeSpec(tables::TypeOfStatisticalProcessing innerMostTypeOfStatisticalProcessing, const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) : ProductTimeSpec(make_ProductTimeSpecInput_or_throw(innerMostTypeOfStatisticalProcessing, mars, par, opt)) {} /// @brief Return the resolved anchor classification. anchor::ProductTimeSpecAnchorKind anchorType() const noexcept { return anchorType_; } /// @brief Return the resolved shape classification. shape::ProductTimeSpecShapeKind shapeType() const noexcept { return shapeType_; } /// @brief Return the resolved domain classification. domain::ProductTimeSpecDomainKind domainType() const noexcept { return domainType_; } /// @brief Return the resolved anchor artifact. const anchor::ProductTimeSpecAnchor& anchor() const noexcept { return anchor_; } /// @brief Return the resolved absolute support domain. const domain::ProductTimeSpecDomain& domain() const noexcept { return domain_; } /// @brief Return the resolved canonical window sequence. const shape::ProductTimeSpecShape& windows() const noexcept { return windows_; } /// /// @brief Serialize the final immutable ProductTimeSpec as diagnostic JSON. /// /// This function is best-effort and never throws. It is intended for upper- /// layer diagnostic context so concept-level exceptions can attach the final /// resolved ProductTimeSpec state without risking a secondary exception. /// /// @return One JSON object string on success, or a stable fallback JSON /// error object if serialization itself fails. /// std::string to_json() const noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("anchorType") << ':' << detail::jsonQuote_modelInput(anchor::productTimeSpecAnchorTypeName(anchorType_)) << ',' << detail::jsonQuote_modelInput("shapeType") << ':' << detail::jsonQuote_modelInput(shape::productTimeSpecShapeTypeName(shapeType_)) << ',' << detail::jsonQuote_modelInput("domainType") << ':' << detail::jsonQuote_modelInput(domain::productTimeSpecDomainTypeName(domainType_)) << ',' << detail::jsonQuote_modelInput("anchor") << ':' << anchor::productTimeSpecAnchorJson(anchor_) << ',' << detail::jsonQuote_modelInput("domain") << ':' << domain::productTimeSpecDomainJson(domain_) << ',' << detail::jsonQuote_modelInput("windows") << ':' << shape::productTimeSpecShapeJson(windows_) << '}'; return out.str(); } catch (...) { return std::string{"{\"error\":\"ProductTimeSpec::to_json() failed while building diagnostic context\"}"}; } } private: struct ProductTimeSpecComponents { anchor::ProductTimeSpecAnchorKind anchorType{anchor::ProductTimeSpecAnchorKind::ForecastAnalysis}; shape::ProductTimeSpecShapeKind shapeType{shape::ProductTimeSpecShapeKind::Instant}; domain::ProductTimeSpecDomainKind domainType{domain::ProductTimeSpecDomainKind::ForecastDomain}; anchor::ProductTimeSpecAnchor anchor{}; domain::ProductTimeSpecDomain domain{}; shape::ProductTimeSpecShape windows{}; }; /// /// @brief Build the staged initialization components from normalized input. /// /// This helper executes the backend-model ProductTimeSpec pipeline after a /// normalized input snapshot already exists: /// 1. classify anchor; /// 2. classify shape; /// 3. classify domain; /// 4. assemble the classification bundle; /// 5. build anchor; /// 6. build the stage-1 shape; /// 7. build domain; /// 8. build the final shape; /// 9. return the immutable member bundle used by the final constructor. /// /// @param[in] input Complete normalized ProductTimeSpec input snapshot. /// @return Complete staged component bundle for final member initialization. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// any classification or build failure, with the original cause /// attached via `std::throw_with_nested`. /// static ProductTimeSpecComponents build_ProductTimeSpecComponents_or_throw(ProductTimeSpecInput input) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const anchor::ProductTimeSpecAnchorKind anchorType = anchor::classify_Anchor_or_throw(input); const domain::ProductTimeSpecDomainKind domainType = domain::classify_Domain_or_throw(input); const shape::ProductTimeSpecShapeKind shapeType = shape::classify_Shape_or_throw(input); ProductTimeSpecClassification classification; classification.anchorType = anchorType; classification.shapeType = shapeType; classification.domainType = domainType; anchor::ProductTimeSpecAnchor anchor = anchor::build_Anchor_or_throw(anchorType, input, classification); const shape::ProductTimeSpecOuterTimeRange outerTimeRange = shape::build_ShapeOuterTimeRange_or_throw(shapeType, input, classification); domain::ProductTimeSpecDomain domain = domain::build_Domain_or_throw(domainType, input, classification, anchor, outerTimeRange); const shape::ProductTimeSpecShape rawWindows = shape::build_ShapeWindows_or_throw(shapeType, input, classification, anchor, outerTimeRange, domain); const shape::ProductTimeSpecShape normalisedWindows = detail::normalizeShape_or_throw(input, domain, rawWindows); ProductTimeSpecComponents result; result.anchorType = anchorType; result.shapeType = shapeType; result.domainType = domainType; result.anchor = std::move(anchor); result.domain = std::move(domain); result.windows = std::move(normalisedWindows); return result; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to build `ProductTimeSpec` staged components from normalized input", input.to_json(), Here())); } mars2gribUnreachable(); } /// /// @brief Materialize the model from an already-built normalized input. /// /// This private constructor isolates the transition between the normalized /// input snapshot and the later staged component construction. It performs /// no direct dictionary access. /// /// @param[in] input Complete normalized ProductTimeSpec input snapshot. /// explicit ProductTimeSpec(ProductTimeSpecInput input) : ProductTimeSpec(build_ProductTimeSpecComponents_or_throw(std::move(input))) {} /// /// @brief Final private construction stage from staged components. /// /// This constructor is the single point at which immutable members are /// initialized from the fully resolved staged component bundle. /// /// @param[in] components Staged component bundle used for final member /// initialization. /// explicit ProductTimeSpec(ProductTimeSpecComponents components) : anchorType_(components.anchorType), shapeType_(components.shapeType), domainType_(components.domainType), anchor_(std::move(components.anchor)), domain_(std::move(components.domain)), windows_(std::move(components.windows)) {} const anchor::ProductTimeSpecAnchorKind anchorType_; const shape::ProductTimeSpecShapeKind shapeType_; const domain::ProductTimeSpecDomainKind domainType_; const anchor::ProductTimeSpecAnchor anchor_; const domain::ProductTimeSpecDomain domain_; const shape::ProductTimeSpecShape windows_; }; } // namespace metkit::mars2grib::backend::models::product_time_spec metkit-1.20.2/src/metkit/mars2grib/backend/models/product-time-spec/ProductTimeSpecInput.h0000664000175000017500000005705415246725757031751 0ustar alastairalastair/* * (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 ProductTimeSpecInput.h /// @brief Model-local normalized input aggregate for `build_ProductTimeSpec_or_throw`. /// /// Exposes `ProductTimeSpecInput` and `make_ProductTimeSpecInput_or_throw`, the /// Stage-1 backend-model entry point that assembles one normalized temporal /// input snapshot from deduction outputs plus the model-level boolean policy /// fields still needed after deduction. /// /// This header owns: /// - one model-local aggregate carrying already-resolved deduction outputs; /// - copying of the flat ProductTimeSpec model-policy booleans from the typed /// options dictionary; /// - best-effort diagnostic JSON serialization of that aggregate; /// - the deduction-driven input assembly step of ProductTimeSpec model /// construction. /// /// This header does NOT: /// - classify ProductTimeSpec anchor, shape, or increment semantics; /// - construct ProductTimeSpec semantic artifacts; /// - canonicalize the final ProductTimeSpec model. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include #include #include #include "eckit/types/Date.h" #include "eckit/types/Time.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/class.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/date.h" #include "metkit/mars2grib/backend/deductions/fcmonth.h" #include "metkit/mars2grib/backend/deductions/hdate.h" #include "metkit/mars2grib/backend/deductions/isSynoptic.h" #include "metkit/mars2grib/backend/deductions/month.h" #include "metkit/mars2grib/backend/deductions/paramId.h" #include "metkit/mars2grib/backend/deductions/simulationRegime.h" #include "metkit/mars2grib/backend/deductions/simulationType.h" #include "metkit/mars2grib/backend/deductions/stattype.h" #include "metkit/mars2grib/backend/deductions/step.h" #include "metkit/mars2grib/backend/deductions/stream.h" #include "metkit/mars2grib/backend/deductions/time.h" #include "metkit/mars2grib/backend/deductions/timeIncrement.h" #include "metkit/mars2grib/backend/deductions/timespan.h" #include "metkit/mars2grib/backend/deductions/type.h" #include "metkit/mars2grib/backend/deductions/typeOfTimeIncrement.h" #include "metkit/mars2grib/backend/deductions/year.h" #include "metkit/mars2grib/backend/models/product-time-spec/detail/ProductTimeSpecJsonUtils.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/backend/tables/typeOfTimeIntervals.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::models::product_time_spec { namespace detail { /// /// @brief Resolve whether the product requires the fake-double-loop single-loop source form. /// /// The rule is driven by the normalized `(class, stream)` product whitelist. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// @return `true` when the product requires the fake-double-loop source representation. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// evaluation failure. /// template bool requiresFakeDoubleLoopSingleLoopRepresentation_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { const std::string marsClass = deductions::resolve_Class_or_throw(mars, par, opt); const std::string marsStream = deductions::resolve_Stream_or_throw(mars, par, opt); const bool isE6Statistics = marsClass == "e6" && (marsStream == "sttd" || marsStream == "stte"); const bool isSeas6Statistics = (marsClass == "od" || marsClass == "rd" || marsClass == "c3") && (marsStream == "sfmd" || marsStream == "shmd"); const bool isHistoricalStatistics = (marsClass == "gh" || marsClass == "eh") && (marsStream == "msmm" || marsStream == "rfsd"); return isE6Statistics || isSeas6Statistics || isHistoricalStatistics; } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribModelException( "Failed to execute `requiresFakeDoubleLoopSingleLoopRepresentation_or_throw`", Here())); } } /// /// @brief Resolve whether the product requires a fake second canonical loop. /// /// The rule is driven by the normalized `(type, paramId)` product whitelist. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// @return `true` when the product requires a fake second canonical loop. /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on /// evaluation failure. /// template bool requiresFakeSingleLoopDoubleLoopRepresentation_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { constexpr std::array indexStatisticsTypes = {{"efi", "efic", "sot", "cpf"}}; const std::string marsType = deductions::resolve_Type_or_throw(mars, par, opt); const long marsParamId = deductions::resolve_ParamId_or_throw(mars, par, opt); const bool typeIsValid = std::any_of(indexStatisticsTypes.begin(), indexStatisticsTypes.end(), [&marsType](auto value) { return marsType == value; }); const bool paramIdIsValid = marsParamId / 1000 == 132; return typeIsValid && paramIdIsValid; } catch (...) { std::throw_with_nested(utils::exceptions::Mars2GribModelException( "Failed to execute `requiresFakeSingleLoopDoubleLoopRepresentation_or_throw`", Here())); } } /** * @brief Verify whether the innermost statistical processing is allowed at step zero. * * The operation is accepted when: * - the processing type is `Missing`, representing the instant case; or * - the processing type is `Accumulation`; or * - the extended zero-length from-start operation set is enabled and the * processing type is `Average`, `Minimum`, or `Maximum`. * * @param[in] innerTypeOfStatisticalProcessing Innermost processing type to validate. * @param[in] allowExtendedSetOfOperationsForZeroLengthFsWindow Whether the * extended zero-length from-start operation set is enabled. * @return `true` when the processing type is allowed at step zero; otherwise `false`. * @throws Mars2GribModelException If evaluation unexpectedly fails. */ inline bool isAllowed_InnerTypeOfStatisticalProcessingAtStepZero( const metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing& innerTypeOfStatisticalProcessing, const bool allowExtendedSetOfOperationsForZeroLengthFsWindow) { using metkit::mars2grib::backend::tables::TypeOfStatisticalProcessing; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { static constexpr std::array extendedSetOfOperation = { {TypeOfStatisticalProcessing::Average, TypeOfStatisticalProcessing::Minimum, TypeOfStatisticalProcessing::Maximum}}; const bool isMissing = innerTypeOfStatisticalProcessing == TypeOfStatisticalProcessing::Missing; const bool isAccumulation = innerTypeOfStatisticalProcessing == TypeOfStatisticalProcessing::Accumulation; const bool isInExtendedOperationSet = std::any_of( extendedSetOfOperation.begin(), extendedSetOfOperation.end(), [&innerTypeOfStatisticalProcessing](auto value) { return innerTypeOfStatisticalProcessing == value; }); if (isMissing) { return true; } if (isAccumulation) { return true; } if (isInExtendedOperationSet) { return allowExtendedSetOfOperationsForZeroLengthFsWindow; } return false; } catch (...) { std::throw_with_nested(Mars2GribModelException( "Failed to execute `isAllowed_InnerTypeOfStatisticalProcessingAtStepZero`", Here())); } } } // namespace detail struct ProductTimeSpecInput { /// @brief Normalized MARS `class` used by representation policies. std::string marsClass{}; /// @brief Normalized MARS `stream` used by representation policies. std::string marsStream{}; /// @brief Normalized MARS `type` used by local temporal consistency rules. std::string marsType{}; /// @brief Normalized MARS `paramId` used by shape-specific policy hooks. long marsParamId{-1}; /// @brief Product whitelist requires the fake-double-loop single-loop source form. bool requiresFakeDoubleLoopSingleLoopRepresentation{false}; /// @brief Product whitelist requires a fake second canonical loop. bool requiresFakeSingleLoopDoubleLoopRepresentation{false}; /// @brief Optional normalized MARS `year` source retained as a raw calendar year. std::optional marsYear{}; /// @brief Optional normalized MARS `month` source retained as a raw calendar month. std::optional marsMonth{}; /// @brief Optional normalized MARS `date` source. std::optional marsDate{}; /// @brief Optional normalized MARS `time` source. std::optional marsTime{}; /// @brief Optional normalized MARS `hdate` source. std::optional marsHdate{}; /// @brief Optional normalized MARS `fcmonth` source retained as a raw forecast month. std::optional marsFcmonth{}; /// @brief Normalized synoptic/non-synoptic product flag. bool isSynoptic{false}; /// @brief Normalized simulation regime used by domain and shape matchers. deductions::SimulationRegime regime{deductions::SimulationRegime::IFS}; /// @brief Normalized simulation type used by domain and shape matchers. deductions::SimulationType simulationType{deductions::SimulationType::Forecast}; /// @brief Optional normalized `step` duration. std::optional step{}; /// @brief Optional normalized `timespan` source representation. deductions::Timespan timespan{}; /// @brief Optional parsed `stattype` block sequence. deductions::ParsedStatTypeBlocks stattype{}; /// @brief Optional normalized explicit time increment. std::optional timeIncrement{}; /// @brief type of time increment // tables::TypeOfTimeIntervals typeOfTimeIncrement{tables::TypeOfTimeIntervals::Missing}; /// @brief Permit policy-defaulted increments for eligible products. bool allowDefaultTimeIncrement{false}; /// @brief Permit the zero-length from-start window case. bool allowZeroLengthFsWindow{true}; /// @brief Permit extended set of operations for zero-length from-start window. bool allowExtendedSetOfOperationsForZeroLengthFsWindow{false}; /// @brief Retained only as diagnostic input context; not used by model logic. bool allowNonEnumeratedPositiveIntegerTimespanHours{true}; /// @brief Permit explicit increments that are semantically redundant. bool allowRedundantTimeIncrement{false}; /// @brief Permit missing `timespan` as the instant-product representation. bool allowMissingTimespanForInstantProduct{false}; /// @brief Permit missing `timespan` as a compatibility form for eligible statistical products. bool allowMissingTimespanForStatisticalProduct{false}; /// @brief Caller-supplied innermost statistical processing type. tables::TypeOfStatisticalProcessing innerMostTypeOfStatisticalProcessing{ tables::TypeOfStatisticalProcessing::Missing}; /// @brief Whether the innermost statistical processing is allowed in the step-zero from-start special case. bool isAllowedInnerTypeOfStatisticalProcessingAtStepZero{false}; /// /// @brief Serialize this normalized input snapshot as JSON. /// /// This function is best-effort and never throws. When serialization fails, /// a stable fallback sentence is returned so diagnostics can still attach /// context without risking a secondary exception. /// /// @return One JSON object string on success, or a stable fallback sentence /// if serialization itself fails. /// std::string to_json() const noexcept { try { std::ostringstream out; out << '{' << detail::jsonQuote_modelInput("marsClass") << ':' << detail::jsonQuote_modelInput(marsClass) << ',' << detail::jsonQuote_modelInput("marsStream") << ':' << detail::jsonQuote_modelInput(marsStream) << ',' << detail::jsonQuote_modelInput("marsType") << ':' << detail::jsonQuote_modelInput(marsType) << ',' << detail::jsonQuote_modelInput("marsParamId") << ':' << marsParamId << ',' << detail::jsonQuote_modelInput("requiresFakeDoubleLoopSingleLoopRepresentation") << ':' << (requiresFakeDoubleLoopSingleLoopRepresentation ? "true" : "false") << ',' << detail::jsonQuote_modelInput("requiresFakeSingleLoopDoubleLoopRepresentation") << ':' << (requiresFakeSingleLoopDoubleLoopRepresentation ? "true" : "false") << ',' << detail::jsonQuote_modelInput("marsYear") << ':' << detail::optionalLongJson_modelInput(marsYear) << ',' << detail::jsonQuote_modelInput("marsMonth") << ':' << detail::optionalLongJson_modelInput(marsMonth) << ',' << detail::jsonQuote_modelInput("marsDate") << ':' << detail::optionalDateJson_modelInput(marsDate) << ',' << detail::jsonQuote_modelInput("marsTime") << ':' << detail::optionalTimeJson_modelInput(marsTime) << ',' << detail::jsonQuote_modelInput("marsHdate") << ':' << detail::optionalDateJson_modelInput(marsHdate) << ',' << detail::jsonQuote_modelInput("marsFcmonth") << ':' << detail::optionalLongJson_modelInput(marsFcmonth) << ',' << detail::jsonQuote_modelInput("isSynoptic") << ':' << (isSynoptic ? "true" : "false") << ',' << detail::jsonQuote_modelInput("regime") << ':' << detail::jsonQuote_modelInput(regime == deductions::SimulationRegime::AIFS ? "AIFS" : "IFS") << ',' << detail::jsonQuote_modelInput("simulationType") << ':' << detail::jsonQuote_modelInput(simulationType == deductions::SimulationType::Analysis ? "Analysis" : "Forecast") << ',' << detail::jsonQuote_modelInput("step") << ':' << detail::optionalDurationJson_modelInput(step) << ',' << detail::jsonQuote_modelInput("timespan") << ':' << detail::optionalTimespanJson_modelInput(timespan) << ',' << detail::jsonQuote_modelInput("stattype") << ':' << detail::optionalStattypeJson_modelInput(stattype) << ',' << detail::jsonQuote_modelInput("timeIncrement") << ':' << detail::optionalDurationJson_modelInput(timeIncrement) << ',' << detail::jsonQuote_modelInput("allowDefaultTimeIncrement") << ':' << (allowDefaultTimeIncrement ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowZeroLengthFsWindow") << ':' << (allowZeroLengthFsWindow ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowExtendedSetOfOperationsForZeroLengthFsWindow") << ':' << (allowExtendedSetOfOperationsForZeroLengthFsWindow ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowNonEnumeratedPositiveIntegerTimespanHours") << ':' << (allowNonEnumeratedPositiveIntegerTimespanHours ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowRedundantTimeIncrement") << ':' << (allowRedundantTimeIncrement ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowMissingTimespanForInstantProduct") << ':' << (allowMissingTimespanForInstantProduct ? "true" : "false") << ',' << detail::jsonQuote_modelInput("allowMissingTimespanForStatisticalProduct") << ':' << (allowMissingTimespanForStatisticalProduct ? "true" : "false") << ',' << detail::jsonQuote_modelInput("innerMostTypeOfStatisticalProcessing") << ':' << detail::jsonQuote_modelInput( tables::enum2name_TypeOfStatisticalProcessing_or_throw(innerMostTypeOfStatisticalProcessing)) << ',' << detail::jsonQuote_modelInput("isAllowedInnerTypeOfStatisticalProcessingAtStepZero") << ':' << (isAllowedInnerTypeOfStatisticalProcessingAtStepZero ? "true" : "false") << '}'; return out.str(); } catch (...) { return std::string{ "{\"error\":\"ProductTimeSpecInput::to_json() failed while building diagnostic context\"}"}; } } }; /// /// @brief Build one normalized ProductTimeSpec model input snapshot. /// /// @section Model-input assembly contract /// - Reads (MARS): all ProductTimeSpec-related source keys via deductions /// - Reads (par): all ProductTimeSpec-related parameter keys via deductions /// - Reads (opt): through deductions that require options and directly for /// ProductTimeSpec model-policy booleans via typed bool reads /// - Writes: none /// - Side effects: deduction-layer logging only /// - Failure mode: throws `Mars2GribModelException` (nested-with) /// /// Stage 1 assembles one model-local input aggregate from the deduction layer /// and from the subset of typed boolean options that remain relevant after /// deduction. It performs no ProductTimeSpec semantic classification or final /// model construction. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] innerMostTypeOfStatisticalProcessing Caller-supplied innermost /// statistical processing type. /// @param[in] mars MARS dictionary. /// @param[in] par Parameter dictionary. /// @param[in] opt Options dictionary. /// /// @return Complete normalized ProductTimeSpec input snapshot. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribModelException on any /// deduction, typed-option read, or aggregation failure, with the /// original cause attached via `std::throw_with_nested`. /// template ProductTimeSpecInput make_ProductTimeSpecInput_or_throw( tables::TypeOfStatisticalProcessing innerMostTypeOfStatisticalProcessing, const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::backend::models::product_time_spec::detail:: isAllowed_InnerTypeOfStatisticalProcessingAtStepZero; using metkit::mars2grib::backend::models::product_time_spec::detail:: requiresFakeDoubleLoopSingleLoopRepresentation_or_throw; using metkit::mars2grib::backend::models::product_time_spec::detail:: requiresFakeSingleLoopDoubleLoopRepresentation_or_throw; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribModelException; try { ProductTimeSpecInput input; input.marsClass = deductions::resolve_Class_or_throw(mars, par, opt); input.marsStream = deductions::resolve_Stream_or_throw(mars, par, opt); input.marsType = deductions::resolve_Type_or_throw(mars, par, opt); input.marsParamId = deductions::resolve_ParamId_or_throw(mars, par, opt); input.requiresFakeDoubleLoopSingleLoopRepresentation = requiresFakeDoubleLoopSingleLoopRepresentation_or_throw(mars, par, opt); input.requiresFakeSingleLoopDoubleLoopRepresentation = requiresFakeSingleLoopDoubleLoopRepresentation_or_throw(mars, par, opt); input.marsYear = deductions::resolve_Year_opt(mars, par, opt); input.marsMonth = deductions::resolve_Month_opt(mars, par, opt); input.marsDate = deductions::resolve_Date_opt(mars, par, opt); input.marsTime = deductions::resolve_Time_opt(mars, par, opt); input.marsHdate = deductions::resolve_Hdate_opt(mars, par, opt); input.marsFcmonth = deductions::resolve_Fcmonth_opt(mars, par, opt); input.step = deductions::resolve_Step_opt(mars, par, opt); input.timespan = deductions::resolve_Timespan_or_throw(mars, par, opt); input.stattype = deductions::resolve_Stattype_or_throw(mars, par, opt); input.timeIncrement = deductions::resolve_TimeIncrement_opt(mars, par, opt); input.isSynoptic = deductions::resolve_IsSynoptic_or_throw(mars, par, opt); input.regime = deductions::resolve_SimulationRegime_or_throw(mars, par, opt); input.simulationType = deductions::resolve_SimulationType_or_throw(mars, par, opt); input.allowDefaultTimeIncrement = get_or_throw(opt, "allowDefaultTimeIncrement"); input.allowZeroLengthFsWindow = get_or_throw(opt, "allowZeroLengthFsWindow"); input.allowExtendedSetOfOperationsForZeroLengthFsWindow = get_or_throw(opt, "allowExtendedSetOfOperationsForZeroLengthFsWindow"); input.allowNonEnumeratedPositiveIntegerTimespanHours = get_or_throw(opt, "allowNonEnumeratedPositiveIntegerTimespanHours"); input.allowRedundantTimeIncrement = get_or_throw(opt, "allowRedundantTimeIncrement"); input.allowMissingTimespanForInstantProduct = get_or_throw(opt, "allowMissingTimespanForInstantProduct"); input.allowMissingTimespanForStatisticalProduct = get_or_throw(opt, "allowMissingTimespanForStatisticalProduct"); input.innerMostTypeOfStatisticalProcessing = innerMostTypeOfStatisticalProcessing; input.isAllowedInnerTypeOfStatisticalProcessingAtStepZero = isAllowed_InnerTypeOfStatisticalProcessingAtStepZero( input.innerMostTypeOfStatisticalProcessing, input.allowExtendedSetOfOperationsForZeroLengthFsWindow); MARS2GRIB_LOG_RESOLVE( [&]() { return std::string{"`ProductTimeSpecInput` built from deductions: "} + input.to_json(); }()); return input; } catch (...) { std::throw_with_nested( Mars2GribModelException("Failed to build `ProductTimeSpecInput` from deduction outputs", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::models::product_time_spec metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/0000775000175000017500000000000015246725757026341 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeEntryCallbacksRegistry.h0000664000175000017500000001716115246725757034010 0ustar alastairalastair/* * (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 EntryCallbacksRegistry.h /// @brief Compile-time global registry for entry-level callbacks. /// /// This header defines the machinery required to **materialize, at compile time, /// a dense lookup table of entry-level callbacks**, indexed by a *capability ID* /// and by *entry index*. /// /// ----------------------------------------------------------------------------- /// Conceptual model /// ----------------------------------------------------------------------------- /// /// Given: /// /// - a compile-time list of Entry descriptors (`EntriesList`) /// - a compile-time capability identifier (`Capability`) /// - concrete dictionary types (`MarsDict_t`, `OptDict_t`) /// /// this facility builds a constexpr table of the form: /// /// @code /// entryCallbacks[entryIndex] -> Fm | nullptr /// @endcode /// /// where: /// /// - `entryIndex` is the index of the Entry in `EntriesList` /// - `Fm` is a function pointer type /// - `nullptr` denotes “capability not supported by this entry” /// /// ----------------------------------------------------------------------------- /// Design goals /// ----------------------------------------------------------------------------- /// /// - **Pure compile-time construction** /// All tables are built via constexpr recursion and concatenation. /// /// - **Zero runtime branching** /// Capability selection is resolved entirely at compile time. /// /// - **Dense layout** /// One callback slot per entry, preserving EntryList order. /// /// - **Header-only** /// Safe for inclusion in multiple translation units. /// /// ----------------------------------------------------------------------------- /// Assumptions and invariants /// ----------------------------------------------------------------------------- /// /// Each Entry type in `EntriesList` is assumed to provide: /// /// @code /// template /// static constexpr Fm entryCallbacks(); /// @endcode /// /// returning either: /// - a valid function pointer implementing the capability, or /// - `nullptr` if the capability is not supported. /// /// Failure to meet this contract results in a compile-time error. /// #pragma once /// /// @file EntryCallbacksRegistry.h /// @brief Compile-time global registry for entry-level callbacks. /// /// Builds a compile-time table: /// /// entryCallbacks[entryIndex] -> Fm | nullptr /// /// Capability is selected via a compile-time std::size_t index. /// // System includes #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/utils.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @namespace detail /// @brief Internal implementation details of the entry callbacks registry. /// /// This namespace contains low-level templates used to: /// - instantiate per-entry callbacks /// - recursively concatenate them into a flat constexpr array /// /// All entities in this namespace are considered **implementation details** /// and must not be used directly outside this header. /// namespace detail { /// /// @brief Instantiate the callback for a single Entry and capability. /// /// This function delegates to the Entry’s static `entryCallbacks` interface /// and returns the function pointer corresponding to the requested capability. /// /// @tparam Entry Entry descriptor type /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam OptDict_t Options dictionary type /// /// @return /// A function pointer of type `Fm`, or `nullptr` /// if the Entry does not implement the given capability. /// /// @note /// This function is `constexpr` and intended to be invoked only during /// compile-time table construction. /// template constexpr Fm makeEntryCallback() { return Entry::template entryCallbacks(); } /// /// @brief Compile-time builder for the entry callbacks table. /// /// This metafunction recursively traverses the `EntriesList` typelist and /// concatenates the callback corresponding to each Entry into a flat array. /// /// The resulting array has: /// - size equal to `EntriesList::size` /// - element `i` corresponding to the i-th Entry in the typelist /// /// @tparam EntriesList TypeList of Entry descriptors /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam OptDict_t Options dictionary type /// template struct BuildEntryCallbacks; /// /// @brief Base case for an empty EntriesList. /// /// Produces an empty constexpr array. /// template struct BuildEntryCallbacks, Capability, MarsDict_t, OptDict_t> { static constexpr std::array, 0> value() { return {}; } }; /// /// @brief Recursive case: prepend callback for Head and recurse on Tail. /// /// This specialization: /// - builds a one-element array containing Head’s callback /// - recursively builds the tail array /// - concatenates the two using `concat` /// /// The order of Entries in the TypeList is strictly preserved. /// template struct BuildEntryCallbacks, Capability, MarsDict_t, OptDict_t> { static constexpr auto value() { constexpr auto head = std::array, 1>{makeEntryCallback()}; constexpr auto tail = BuildEntryCallbacks, Capability, MarsDict_t, OptDict_t>::value(); return concat(head, tail); } }; } // namespace detail /// /// @brief Construct the compile-time entry callbacks registry. /// /// This is the **public API** of the entry callbacks registry. /// /// It materializes, at compile time, a dense array of entry-level callbacks /// corresponding to the specified capability and dictionary types. /// /// @tparam EntriesList TypeList of Entry descriptors /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam OptDict_t Options dictionary type /// /// @return /// A `constexpr std::array, N>` where: /// - `N == EntriesList::size` /// - element `i` is the callback for the i-th Entry /// /// @note /// The returned array is intended to be: /// - stored as a `static constexpr` object /// - indexed directly in hot paths without branching /// template constexpr auto makeEntryCallbacksRegistry() { return detail::BuildEntryCallbacks::value(); } } // namespace metkit::mars2grib::backend::compile_time_registry_engine metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/common.h0000664000175000017500000001260415246725757030005 0ustar alastairalastair/* * (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 common.h /// @brief Canonical compile-time vocabulary shared by all concept dispatch registries. /// /// This header defines the **fundamental compile-time building blocks** used /// uniformly across the mars2grib dispatch infrastructure. /// /// Specifically, it provides: /// - fixed pipeline dimensions (`NUM_STAGES`, `NUM_SECTIONS`), /// - canonical numeric identifiers for encoding stages and GRIB sections, /// - sentinel value representing non-semantic or invalid states (`MISSING`), /// - canonical function pointer types (`Fn`, `Fm`) for dispatch interfaces, /// - minimal compile-time containers (`ValueList`, `TypeList`) used to model /// value and type universes. /// /// This header is intentionally **purely declarative**: /// - it contains no registry construction logic, /// - no compile-time recursion or algorithms, /// - and no concept-specific specialization. /// /// All higher-level registry engines rely on the definitions in this file /// as a **stable, shared vocabulary**. /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include #include #include // Project includes #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @name Sentinel value /// @{ /// /// This constant defines a **sentinel value** used throughout the mars2grib /// codebase to represent a special, non-semantic state. /// /// It is defined as an `inline constexpr` value rather than an enum to: /// - allow direct use in compile-time contexts, /// - avoid implicit conversions or scoped enum verbosity, /// - support use as array indices or placeholders. /// /// @note /// This value must remain outside the valid range of any real index, /// identifier, or enumerated domain it is compared against. /// inline constexpr std::size_t MISSING = 9999999; /// @} /// /// @name Encoding pipeline dimensions /// @{ /// /// These constants define the fixed dimensions of the mars2grib encoding pipeline. /// /// They are deliberately defined as `inline constexpr` values rather than enums, /// because they are iterated over at compile time using index sequences. /// /// Changing these values directly affects: /// - the size of all generated dispatch tables, /// - the number of compile-time instantiations. /// inline constexpr std::size_t NUM_STAGES = 4; inline constexpr std::size_t NUM_SECTIONS = 6; /// @} /// /// @name Encoding stages /// @{ /// /// Logical stages of the encoding pipeline. /// /// Each concept may participate in zero or more stages, as determined by its /// compile-time applicability predicate (encoded by returning nullptr in /// concept `encodingCallbacks()` entries). /// inline constexpr std::size_t StageAllocate = 0; ///< Structure allocation stage inline constexpr std::size_t StagePreset = 1; ///< Metadata preset stage inline constexpr std::size_t StageOverride = 2; ///< Metadata override stage inline constexpr std::size_t StageRuntime = 3; ///< Runtime-dependent encoding /// @} /// /// @name GRIB2 sections /// @{ /// /// Numeric identifiers for GRIB2 sections, aligned with the GRIB2 specification: /// https://codes.ecmwf.int/grib/format/grib2/sections/ /// /// These values are used as compile-time indices into dispatch tables. /// inline constexpr std::size_t SecIndicatorSection = 0; inline constexpr std::size_t SecIdentificationSection = 1; inline constexpr std::size_t SecLocalUseSection = 2; inline constexpr std::size_t SecGridDefinitionSection = 3; inline constexpr std::size_t SecProductDefinitionSection = 4; inline constexpr std::size_t SecDataRepresentationSection = 5; /// @} /// /// @brief Canonical function pointer type for concept encoding operations. /// /// Each dispatch table cell contains a pointer to a fully specialized concept operation /// for a fixed (stage, section, variant). Inapplicable combinations return nullptr. /// template using Fn = void (*)(const MarsDict_t&, const ParDict_t&, const OptDict_t&, OutDict_t&); /// /// @brief Canonical function pointer type for concept matcher operations. /// /// Matchers are dictionary-specialized (no stage/section/variant dimensions). /// template using Fm = std::size_t (*)(const MarsDict_t&, const OptDict_t&); /// /// @brief Compile-time list of values. /// /// A lightweight container for a pack of compile-time constants (typically enum values). /// Iteration is performed via template pack expansion (no runtime loops). /// template struct ValueList { static constexpr std::size_t size = sizeof...(Vals); }; /// /// @brief Compile-time list of types. /// /// A lightweight container for a pack of types. Used to represent the concept universe. /// template struct TypeList { static constexpr std::size_t size = sizeof...(Ts); }; } // namespace metkit::mars2grib::backend::compile_time_registry_engine metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/EntryVariantRegistry.h0000664000175000017500000005626715246725757032711 0ustar alastairalastair/* * (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 EntryVariantRegistry.h /// @brief Compile-time registry engine for concept/variant indexing and metadata. /// /// This header implements the **core compile-time indexing machinery** used by /// mars2grib to map *concepts* and *their variants* onto stable, contiguous /// integer domains. /// /// The registry is entirely **header-only** and operates exclusively at /// compile time, producing constexpr tables that are later consumed by /// high-performance runtime code (dispatch tables, encoding plans, etc.). /// /// ----------------------------------------------------------------------------- /// Design overview /// ----------------------------------------------------------------------------- /// /// The fundamental abstraction is a **TypeList of Entry descriptors**, where /// each Entry represents: /// /// - one semantic concept (e.g. "levelType", "timeRange") /// - a fixed number of variants /// - an associated enum type (`Entry::Variant`) /// /// From this list, the registry derives: /// /// - global variant indices (flattened space) /// - per-concept local indices /// - concept identifiers /// - name lookup tables (concept names, variant names) /// /// All tables are: /// /// - constexpr /// - contiguous /// - allocation-free /// - index-stable /// /// ----------------------------------------------------------------------------- /// Two-stage model /// ----------------------------------------------------------------------------- /// /// The registry is conceptually split into two stages: /// /// - **Stage 1 (Index arithmetic)**: /// Computes offsets, indices, and relationships between concepts and /// variants. /// /// - **Stage 2 (Table materialization)**: /// Builds constexpr arrays that can be used directly at runtime. /// /// ----------------------------------------------------------------------------- /// Constraints and invariants /// ----------------------------------------------------------------------------- /// /// The following invariants are assumed and enforced: /// /// - Every Entry type provides: /// - `static constexpr std::size_t variant_count` /// - `using Variant = ` /// - `using VariantList = ValueList<...>` /// - `static constexpr std::string_view entryName()` /// - `template static constexpr std::string_view variantName()` /// /// - The order of Entries in the TypeList defines: /// - concept identifiers /// - block ordering in flattened tables /// /// - All variant enums are disjoint by type. /// /// Violation of these assumptions results in a compile-time error. /// #pragma once // system includes #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/utils.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @namespace detail /// @brief Internal implementation details of the compile-time registry engine. /// /// This namespace contains **low-level meta-programming utilities** that are /// not part of the public API and must not be relied upon directly by users. /// /// All entities in this namespace are subject to change without notice. /// namespace detail { // ================================================================================================= // 0) Helper: dependent_false // ================================================================================================= /// /// @brief Helper metafunction to trigger dependent compile-time errors. /// /// `dependent_false` always evaluates to `false`, but is dependent on /// its template parameters. This allows `static_assert` to be placed in /// templates without causing immediate hard errors during parsing. /// /// Typical use: /// - provide meaningful diagnostics in impossible template branches /// template struct dependent_false : std::false_type {}; // ================================================================================================= // 1) TypeList utilities // ================================================================================================= /// /// @brief Compile-time index lookup of a type inside a TypeList. /// /// `IndexOf::value` yields the zero-based index of `T` within `List`. /// /// @tparam T Type to search for /// @tparam List TypeList to scan /// /// @note /// The behavior is undefined if `T` does not appear in `List`. /// Such misuse is considered a logic error in registry construction. /// template struct IndexOf; /// /// @brief Recursive case: head matches the searched type. /// template struct IndexOf> : std::integral_constant {}; /// /// @brief Recursive case: head does not match, continue scanning. /// template struct IndexOf> : std::integral_constant>::value> {}; /// /// @brief Compute the total number of variants across all entries. /// /// This metafunction folds over the TypeList and accumulates /// `Entry::variant_count` for each Entry. /// /// @tparam List TypeList of Entry descriptors /// template struct TotalVariantCount; template <> struct TotalVariantCount> : std::integral_constant {}; template struct TotalVariantCount> : std::integral_constant>::value> {}; // ================================================================================================= // 2) CSR-style offsets: EntryOffset // ================================================================================================= /// /// @brief Compute the global offset of the I-th Entry in the flattened variant space. /// /// The offset is defined as the sum of `variant_count` of all preceding Entries. /// /// This is analogous to CSR (Compressed Sparse Row) prefix sums. /// /// @tparam I Entry index /// @tparam List TypeList of Entries /// template struct EntryOffset; template struct EntryOffset<0, TypeList> : std::integral_constant {}; template struct EntryOffset> : std::integral_constant>::value> {}; // ================================================================================================= // 3) Map Variant enum type -> Entry by scanning the typelist // ================================================================================================= /// /// @brief Map a variant enum type to its owning Entry descriptor. /// /// This metafunction scans the Entries TypeList and selects the Entry whose /// `Entry::Variant` type exactly matches the given enum. /// /// @tparam Enum Variant enum type /// @tparam List Entries TypeList /// /// @note /// Failure to find a matching Entry results in a compile-time error. /// template struct EntryFromVariantEnum; template struct EntryFromVariantEnum, void> { static_assert(dependent_false::value, "Variant enum type not associated to any entry in EntriesList. " "Check: (a) entry is in the TypeList, (b) Entry::Variant matches exactly."); using type = void; }; template struct EntryFromVariantEnum, std::enable_if_t>> { using type = Head; }; template struct EntryFromVariantEnum, std::enable_if_t>> { using type = typename EntryFromVariantEnum>::type; }; // ================================================================================================= // 4) VariantList-driven local index (constexpr search) // ================================================================================================= /// /// @brief Compute the local index of a variant within its owning concept. /// /// This is a constexpr linear search over a ValueList. /// /// @param v Variant enum value /// @return Local index, or `-1` cast to `std::size_t` if not found /// /// @note /// Returning `-1` is intentional; callers translate this into the `missing` /// sentinel. /// template constexpr std::size_t value_index_impl(Enum, std::size_t, ValueList<>) { return static_cast(-1); } template constexpr std::size_t value_index_impl(Enum v, std::size_t pos, ValueList) { return (v == static_cast(Head)) ? pos : value_index_impl(v, pos + 1, ValueList{}); } template constexpr std::size_t value_index(Enum v, VariantListT) { return value_index_impl(v, 0, VariantListT{}); } // ================================================================================================= // 6) Per-entry blocks for Stage2 // (NOTE: ConceptsT is passed explicitly => no "Concepts" free name) // ================================================================================================= /// /// @brief Generate a block of repeated concept identifiers. /// /// Each concept contributes a contiguous block of size `variant_count`, /// where every element equals the concept identifier. /// /// This is used to build the flattened concept-id table. /// template constexpr std::array concept_id_block_impl(std::index_sequence) { return {{(static_cast(Is), ConceptIndex)...}}; } template constexpr std::array concept_id_block() { return concept_id_block_impl(std::make_index_sequence{}); } template constexpr std::array variant_id_block_impl(std::index_sequence) { return {{Is...}}; } template constexpr std::array variant_id_block() { return variant_id_block_impl(std::make_index_sequence{}); } template constexpr std::array concept_name_block_impl(std::index_sequence) { return {{(static_cast(Is), Concept::entryName())...}}; } template constexpr std::array concept_name_block() { return concept_name_block_impl(std::make_index_sequence{}); } template constexpr std::array variant_name_block_impl(ValueList) { return {{Concept::template variantName()...}}; } template constexpr auto variant_name_block() { return variant_name_block_impl(typename Concept::VariantList{}); } // ================================================================================================= // 7) Build full tables by recursion over ConceptsT // ================================================================================================= /// /// @brief Build full registry tables by recursion over the Entries TypeList. /// /// Each builder recursively concatenates per-entry blocks to form a /// flattened table aligned with the global variant index space. /// /// All builders terminate on `TypeList<>`. /// template struct BuildConceptIdTableWithBase; template struct BuildConceptIdTableWithBase, BaseIndex> { static constexpr std::array value() { return {}; } }; template struct BuildConceptIdTableWithBase, BaseIndex> { static constexpr auto value() { return concat(concept_id_block(), BuildConceptIdTableWithBase, BaseIndex + 1>::value()); } }; template struct BuildVariantIdTable; template <> struct BuildVariantIdTable> { static constexpr std::array value() { return {}; } }; template struct BuildVariantIdTable> { static constexpr auto value() { return concat(variant_id_block(), BuildVariantIdTable>::value()); } }; template struct BuildConceptNameTable; template <> struct BuildConceptNameTable> { static constexpr std::array value() { return {}; } }; template struct BuildConceptNameTable> { static constexpr auto value() { return concat(concept_name_block(), BuildConceptNameTable>::value()); } }; template struct BuildVariantNameTable; template <> struct BuildVariantNameTable> { static constexpr std::array value() { return {}; } }; template struct BuildVariantNameTable> { static constexpr auto value() { return concat(variant_name_block(), BuildVariantNameTable>::value()); } }; template struct BuildConceptNames; template <> struct BuildConceptNames> { static constexpr std::array value() { return {}; } }; template struct BuildConceptNames> { static constexpr auto value() { return concat(std::array{{Head::entryName()}}, BuildConceptNames>::value()); } }; template struct BuildConceptOffsetsTable; template <> struct BuildConceptOffsetsTable> { static constexpr std::array value() { return {{0}}; } }; template struct BuildConceptOffsetsTable> { static constexpr auto value() { constexpr auto tail = BuildConceptOffsetsTable>::value(); std::array result{}; result[0] = 0; for (std::size_t i = 0; i < tail.size(); ++i) { result[i + 1] = tail[i] + Head::variant_count; } return result; } }; } // namespace detail // ================================================================================================= // 8) EntryVariantRegistry // - Same public API names as your original VariantRegistry // - ConceptId is the derived concept number (IndexOf::value) // ================================================================================================= /// /// @brief Compile-time registry providing concept/variant indexing and metadata. /// /// `EntryVariantRegistry` exposes a **stable public API** for: /// /// - computing global and local variant indices /// - retrieving concept identifiers /// - mapping enums and strings to indices /// - accessing constexpr metadata tables /// /// @tparam EntriesListT TypeList of Entry descriptors /// /// This type is: /// - stateless /// - constexpr-friendly /// - safe to use in headers /// template struct EntryVariantRegistry { using Concepts = EntriesListT; /// /// @name Registry dimensions and sentinels /// @{ /// /// These constants define: /// - sentinel values for error handling /// - fixed structural dimensions of the encoding pipeline /// static constexpr std::size_t missing = MISSING; static constexpr std::size_t NSections = NUM_SECTIONS; static constexpr std::size_t NStages = NUM_STAGES; static constexpr std::size_t NConcepts = Concepts::size; static constexpr std::size_t NVariants = detail::TotalVariantCount::value; /// @} // ---- STAGE 1 ---- static constexpr std::size_t numberOfVariants() { return NVariants; } template static constexpr std::size_t offset() { return detail::EntryOffset::value, Concepts>::value; } template static constexpr std::size_t offset_from_enum_type() { using Concept = typename detail::EntryFromVariantEnum::type; return offset(); } template static constexpr std::size_t offset(Enum) { return offset_from_enum_type(); } template static constexpr std::size_t conceptId(Enum) { using Concept = typename detail::EntryFromVariantEnum::type; return detail::IndexOf::value; } template static constexpr std::size_t localIndex(Enum v) { using Concept = typename detail::EntryFromVariantEnum::type; constexpr auto list = typename Concept::VariantList{}; const std::size_t idx = detail::value_index(v, list); return (idx == static_cast(-1)) ? missing : idx; } template static constexpr std::size_t globalIndex(Enum v) { using Concept = typename detail::EntryFromVariantEnum::type; const std::size_t li = localIndex(v); return (li == missing) ? missing : (offset() + li); } // ---- STAGE 2 ---- /// /// @brief Precomputed constexpr lookup tables. /// /// These arrays are indexed by **global variant index** and provide /// O(1) access to: /// /// - concept identifiers (variantId -> conceptId) /// - local variant indices (variantId -> localIndex) /// - concept names (variantId -> conceptName) /// - variant names (variantId -> variantName) /// - concept name lookup (conceptId -> conceptName) /// /// They are safe to expose as `inline constexpr` objects. /// static constexpr auto conceptIdTable() { return detail::BuildConceptIdTableWithBase::value(); } static constexpr auto variantIdTable() { return detail::BuildVariantIdTable::value(); } static constexpr auto conceptNameTable() { return detail::BuildConceptNameTable::value(); } static constexpr auto variantNameTable() { return detail::BuildVariantNameTable::value(); } static constexpr auto conceptNamesTable() { return detail::BuildConceptNames::value(); } /// /// @brief Concept offset table (CSR-style). /// /// This array has size `NConcepts + 1` and defines half-open /// global variant index ranges for each concept: /// /// \code /// concept i → [ offsets[i], offsets[i+1] ) /// \endcode /// /// The last entry equals `NVariants`. /// static constexpr auto conceptOffsetsTable() { return detail::BuildConceptOffsetsTable::value(); } static inline constexpr auto conceptIdArr = conceptIdTable(); static inline constexpr auto variantIdArr = variantIdTable(); static inline constexpr auto conceptNameArr = conceptNameTable(); static inline constexpr auto variantNameArr = variantNameTable(); static inline constexpr auto conceptNames = conceptNamesTable(); static inline constexpr auto conceptOffsets = conceptOffsetsTable(); private: template static constexpr std::array make_id_array_from_concept_impl( std::index_sequence) { // VariantList order => contiguous [offset .. offset+variant_count) constexpr std::size_t off = offset(); return {{(static_cast(Is), off + Is)...}}; } public: template static constexpr std::array make_id_array_from_concept() { return make_id_array_from_concept_impl(std::make_index_sequence{}); } template static constexpr std::array make_id_array_from_variants() { using Enum = typename Concept::Variant; static_assert((std::is_same_v && ...), "All variant values must belong to Concept::Variant"); return {{globalIndex(static_cast(Vs))...}}; } // ---- STAGE 3 ---- /// /// @brief Runtime utilities for string-based lookup. /// /// These functions provide a bridge between dynamic inputs (strings) /// and the compile-time registry. /// /// They are intentionally linear-time and intended for: /// - diagnostics /// - configuration parsing /// - debugging paths /// /// They must not be used in hot paths. /// static std::size_t conceptId(const std::string& name) { for (std::size_t i = 0; i < NConcepts; ++i) { if (conceptNames[i] == name) { return i; } } return missing; } static std::size_t localIndex(const std::string& conceptName, const std::string& variantName) { const std::size_t cid = conceptId(conceptName); if (cid == missing) return missing; const std::size_t begin = offset(cid); const std::size_t end = (cid + 1 < NConcepts) ? offset(cid + 1) : NVariants; for (std::size_t i = begin; i < end; ++i) { if (variantNameArr[i] == variantName) { return variantIdArr[i]; // local index } } return missing; } static std::size_t globalIndex(const std::string& conceptName, const std::string& variantName) { const std::size_t cid = conceptId(conceptName); if (cid == missing) return missing; const std::size_t begin = offset(cid); const std::size_t end = (cid + 1 < NConcepts) ? offset(cid + 1) : NVariants; for (std::size_t i = begin; i < end; ++i) { if (variantNameArr[i] == variantName) { return i; } } return missing; } }; } // namespace metkit::mars2grib::backend::compile_time_registry_engine ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootmetkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeVariantCallbacksRegistry.hmetkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeVariantCallbacksRegistry0000664000175000017500000002672415246725757034072 0ustar alastairalastair/* * (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 VariantCallbacksRegistry.h /// @brief Compile-time global registry for variant-level callbacks. /// /// This header defines the machinery required to **materialize, entirely at /// compile time, a dense dispatch table of variant-level callbacks**. /// /// ----------------------------------------------------------------------------- /// Conceptual result /// ----------------------------------------------------------------------------- /// /// The primary product of this header is a constexpr array with the following /// logical structure: /// /// @code /// variantCallbacks[globalVariantIndex] -> Fn | nullptr /// @endcode /// /// where: /// /// - `globalVariantIndex` is a **flattened index** spanning *all variants of all /// entries*, in a single contiguous index space. /// - `Fn` is a function pointer type implementing a concrete encoding operation. /// - `nullptr` denotes that the requested capability is **not supported** for /// that specific variant. /// /// ----------------------------------------------------------------------------- /// Definition of the global variant index space /// ----------------------------------------------------------------------------- /// /// The global variant index space is defined *structurally* and *deterministically* /// by two independent compile-time orderings: /// /// 1. **Entry ordering** /// The order of Entry descriptors in `EntriesList` (a `TypeList`). /// /// 2. **Variant ordering** /// For each Entry, the order of values in `Entry::VariantList` /// (a `ValueList`). /// /// The resulting index space is: /// /// @code /// [ Entry0::Variant0, /// Entry0::Variant1, /// ... /// Entry1::Variant0, /// Entry1::Variant1, /// ... /// ] /// @endcode /// /// This ordering is guaranteed to be: /// - contiguous /// - stable across translation units /// - independent of compilation order /// /// ----------------------------------------------------------------------------- /// Capability selection model /// ----------------------------------------------------------------------------- /// /// Each variant may expose zero or more *capabilities*. /// /// A capability is identified by a **compile-time `std::size_t` constant**. /// Capability selection is resolved entirely at compile time, producing a /// specialized dispatch table for each capability. /// /// ----------------------------------------------------------------------------- /// Design goals /// ----------------------------------------------------------------------------- /// /// - **Zero runtime branching** /// No conditionals are required when invoking variant callbacks. /// /// - **No dynamic allocation** /// All data structures are `constexpr std::array`. /// /// - **Header-only** /// Safe to include in any number of translation units. /// /// - **Dense layout** /// One slot per variant, indexed directly by global variant ID. /// /// ----------------------------------------------------------------------------- /// Assumptions and invariants /// ----------------------------------------------------------------------------- /// /// Each Entry type appearing in `EntriesList` is assumed to provide: /// /// @code /// template < /// std::size_t Capability, /// auto Variant, /// class MarsDict_t, /// class ParDict_t, /// class OptDict_t, /// class OutDict_t /// > /// static constexpr Fn /// variantCallbacks(); /// @endcode /// /// returning either: /// - a valid function pointer implementing the capability for that variant, or /// - `nullptr` if the capability is not supported. /// /// Failure to meet this contract results in a compile-time error. /// /// ----------------------------------------------------------------------------- /// Relationship to other registries /// ----------------------------------------------------------------------------- /// /// This registry operates at **variant granularity**. /// /// It is orthogonal to: /// - entry-level callback registries /// - concept/variant indexing registries /// /// Those registries define *index spaces*; this registry defines *behavior*. /// /// @ingroup mars2grib_backend_compile_time_registry_engine /// #pragma once // System includes #include #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @namespace detail /// @brief Internal implementation details of the variant callbacks registry. /// /// This namespace contains the low-level template machinery required to: /// - instantiate per-variant callbacks /// - concatenate per-entry variant blocks /// - produce the final flattened dispatch table /// /// All entities in this namespace are **implementation details** and must not be /// used directly outside this header. /// namespace detail { /// /// @brief Alias for a variant-level callback function pointer. /// /// This alias represents the *callable unit* stored in the variant callbacks /// registry. /// /// The function signature is determined by the concrete dictionary types. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output dictionary /// template using VariantCallback = Fn; /// /// @brief Build the variant-level callback block for a single Entry. /// /// This function materializes a contiguous array of callbacks corresponding /// to **all variants of a single Entry**, in the order defined by /// `Entry::VariantList`. /// /// Each element of the returned array corresponds to exactly one variant. /// /// @tparam Entry Entry descriptor type /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Options dictionary type /// @tparam OutDict_t Output dictionary type /// @tparam Variants Pack of variant enum values /// /// @param[in] ValueList /// Compile-time list defining variant order /// /// @return /// A `constexpr std::array` of size `sizeof...(Variants)` containing /// the callbacks for each variant. /// /// @note /// Each callback may be `nullptr` if the capability is not supported /// for the corresponding variant. /// template constexpr std::array, sizeof...(Variants)> makeEntryVariantCallbacks(ValueList) { return {{Entry::template variantCallbacks()...}}; } /// /// @brief Convenience wrapper to build the variant callback block for an Entry. /// /// This overload extracts `Entry::VariantList` automatically and forwards /// to the ValueList-based implementation. /// template constexpr auto makeEntryVariantCallbacks() { return makeEntryVariantCallbacks( typename Entry::VariantList{}); } /// /// @brief Compile-time builder for the full variant callbacks table. /// /// This metafunction recursively traverses the `EntriesList` typelist and /// concatenates the per-entry variant callback blocks into a single, /// flattened dispatch table. /// /// The resulting table: /// - has one slot per global variant /// - preserves Entry ordering /// - preserves VariantList ordering within each Entry /// /// @tparam EntriesList TypeList of Entry descriptors /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Options dictionary type /// @tparam OutDict_t Output dictionary type /// template struct BuildVariantCallbacks; /// /// @brief Base case for an empty EntriesList. /// /// Produces an empty constexpr array. /// /// This specialization terminates the recursion. /// template struct BuildVariantCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t> { static constexpr std::array, 0> value() { return {}; } }; /// /// @brief Recursive case: prepend Head’s variant block and recurse on Tail. /// /// This specialization: /// - builds the variant callback block for `Head` /// - recursively builds the table for `Tail...` /// - concatenates the two blocks using `concat` /// /// The order of the resulting array is strictly deterministic. /// template struct BuildVariantCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t> { static constexpr auto value() { constexpr auto head = makeEntryVariantCallbacks(); constexpr auto tail = BuildVariantCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>::value(); return concat(head, tail); } }; } // namespace detail /// /// @brief Construct the compile-time variant callbacks registry. /// /// This function is the **public API** of the variant callbacks registry. /// /// It materializes, at compile time, a dense array of variant-level callbacks /// corresponding to: /// - a fixed EntriesList /// - a fixed capability /// - concrete dictionary types /// /// @tparam EntriesList TypeList of Entry descriptors /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Options dictionary type /// @tparam OutDict_t Output dictionary type /// /// @return /// A `constexpr std::array, N>` where: /// - `N` equals the total number of variants across all Entries /// - index `i` corresponds to global variant index `i` /// /// @note /// The returned array is intended to be: /// - stored as a `static constexpr` object /// - indexed directly in hot paths /// - never modified /// template constexpr auto makeVariantCallbacksRegistry() { return detail::BuildVariantCallbacks::value(); } } // namespace metkit::mars2grib::backend::compile_time_registry_engine metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/compileTimeRegistryEngine.md0000664000175000017500000001653515246725757034023 0ustar alastairalastair# Compile-Time Registry Engine ## Overview The **compile-time registry engine** is the backbone of the mars2grib dispatch and encoding infrastructure. Its purpose is to transform *static knowledge about concepts, variants, stages, sections, and capabilities* into **fully materialized, constexpr dispatch tables** that can be used directly at runtime with: * no dynamic allocation, * no virtual dispatch, * no string-based lookup in hot paths, * no runtime branching. In short: > **All decisions are made at compile time. Runtime code only indexes arrays and calls function pointers.** This document explains the engine **from first principles**, starting with vocabulary and gradually building up to the full phase-level dispatch table. --- ## Design Goals The engine is designed to satisfy the following constraints: 1. **Zero runtime overhead** * All tables are `constexpr` and fully materialized at compile time. * Runtime code performs only indexed lookups and function calls. 2. **Deterministic structure** * All indices are stable across translation units. * Table layouts depend only on type lists and value lists. 3. **Header-only** * Safe to include in any number of translation units. * No ODR issues, no global state. 4. **Strict layering** * Each registry layer has a single responsibility. * Higher layers build on lower ones without circular dependencies. 5. **Explicit contracts** * Concept authors must implement a precise compile-time interface. * Violations fail at compile time. --- ## Core Vocabulary All registries share a common vocabulary defined in `callbacksRegistry_common.h`. ### Pipeline Dimensions The encoding pipeline is defined along two fixed axes: * **Stages** (`NUM_STAGES`) * **Sections** (`NUM_SECTIONS`) These values are compile-time constants and are used to dimension all dispatch tables. Stages represent *when* an operation occurs (e.g. allocation, preset, runtime), while sections represent *where* in the GRIB message the operation applies. ### Sentinel Values One sentinel constant is used throughout the engine: * `MISSING` It carries different semantic meaning depending on context. They are used to represent: * absent indices, * invalid lookups, * structurally inapplicable situations. ### Function Pointer Types Two canonical function pointer types are used: * `Fn<...>` — encoding / transformation callbacks * `Fm<...>` — matcher / predicate callbacks All dispatch tables store one of these two types (or `nullptr`). ### Compile-Time Containers Two lightweight compile-time containers are used everywhere: * `ValueList` — a list of compile-time values (usually enum values) * `TypeList` — a list of types They provide *ordering*, *cardinality*, and *structure*, but no runtime behavior. --- ## The Entry Descriptor Contract Every concept that participates in the registry engine must provide a **descriptor** conforming to `RegisterEntryDescriptor`. Conceptually, an *Entry* represents: * one semantic concept (e.g. level type, grid type), * a finite set of variants, * a family of dispatch functions at different granularities. An Entry descriptor specifies: 1. **Variant enum type** 2. **Ordered list of variants** (`VariantList`) 3. **Concept name** (`entryName()`) 4. **Variant names** (`variantName()`) 5. **Dispatch interfaces** The dispatch interfaces are layered: * entry-level (`entryCallbacks`) * variant-level (`variantCallbacks`) * phase-level (`phaseCallbacks`) Each interface is selected by: * a compile-time capability ID, * optional variant, stage, and section indices, * concrete dictionary types. Returning `nullptr` means *not supported*. --- ## Registry Layers (Bottom-Up) The compile-time registry engine is composed of several layers. Each layer builds on the previous one. --- ### 1. EntryVariantRegistry **Purpose:** define the *index space*. This registry computes: * the total number of concepts, * the total number of variants, * the mapping between: * concept → offset * variant → local index * variant → global index It also materializes constexpr tables: * `conceptIdArr[globalVariant]` * `variantIdArr[globalVariant]` * `conceptNameArr[globalVariant]` * `variantNameArr[globalVariant]` This layer contains **no behavior**. It is purely structural and arithmetic. Think of it as the *schema* of the system. --- ### 2. EntryCallbacksRegistry **Purpose:** entry-level behavior. This registry builds a table: ``` entryCallbacks[entryIndex] -> Fm | nullptr ``` Each entry contributes exactly one slot. This layer answers questions like: * “Does this concept support capability X?” * “Which matcher applies for this dictionary pair?” Granularity: **per concept**. --- ### 3. VariantCallbacksRegistry **Purpose:** variant-level behavior. This registry refines dispatch to the variant level: ``` variantCallbacks[globalVariant] -> Fn | nullptr ``` Each variant contributes exactly one slot. Granularity: **per variant**. This layer is typically used when: * behavior depends on the chosen variant, * but not on stage or section. --- ### 4. PhaseCallbacksRegistry (Final Layer) **Purpose:** full phase resolution. This registry builds the final, fully expanded dispatch table: ``` phaseCallbacks[globalVariant][stage][section] -> Fn | nullptr ``` This table answers the question: > “For this variant, at this stage, in this section — what do I do?” All dimensions are fixed at compile time. This is the **final product** consumed by the encoding pipeline. --- ## Capability Model A *capability* is represented by a compile-time `std::size_t` constant. Capabilities are not enums by design: * they are used as non-type template parameters, * they index families of dispatch tables, * they allow separate compilation of unrelated behaviors. Each capability produces a **distinct instantiation** of every registry layer. There is no runtime capability switching. --- ## How Everything Fits Together 1. Concepts define Entry descriptors. 2. EntryVariantRegistry defines the index space. 3. EntryCallbacksRegistry defines per-concept behavior. 4. VariantCallbacksRegistry defines per-variant behavior. 5. PhaseCallbacksRegistry defines full phase behavior. 6. Runtime code: * computes indices once, * indexes into constexpr tables, * calls function pointers. No maps. No conditionals. No polymorphism. --- ## Mental Model You can think of the engine as a **compile-time code generator** written in C++ templates. At runtime, the system behaves as if someone had manually written: * giant `switch` statements, * fully unrolled loops, * precomputed dispatch matrices. Except: * it is guaranteed correct by the type system, * it is impossible to forget a case silently, * and the compiler optimizes it aggressively. --- ## When to Use (and Not Use) This Engine Use it when: * the domain is finite and known at compile time, * performance matters, * behavior is highly structured. Do **not** use it when: * the domain is dynamic or user-extensible at runtime, * configurability matters more than performance, * compile times are a critical concern. --- ## Final Notes This engine trades: * longer compile times, * heavier template instantiations, for: * minimal runtime overhead, * maximal structural correctness, * explicit, auditable behavior. It is intentionally **not clever at runtime**. All the cleverness happens at compile time. metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h0000664000175000017500000002240315246725757033360 0ustar alastairalastair/* * (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 callbacksRegistry_common.h /// @brief Common compile-time vocabulary for concept dispatch registries. /// /// This header defines the **shared compile-time interface and vocabulary** /// used by all dispatch registries in the mars2grib backend. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file is intentionally **minimal and declarative**. /// /// It provides: /// - a canonical *Entry descriptor interface*, /// - shared naming and typing conventions, /// - compile-time constants derived from structural metadata. /// /// It explicitly does **not**: /// - build dispatch tables, /// - perform compile-time recursion, /// - define registry logic, /// - contain any metaprogramming algorithms. /// /// Those responsibilities are delegated to higher-level registry headers /// (EntryVariantRegistry, EntryCallbacksRegistry, VariantCallbacksRegistry, /// PhaseCallbacksRegistry). /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// Conceptually, this header defines the **contract** that every concept /// participating in the encoding pipeline must satisfy. /// /// All registry engines assume that Entry descriptors conform exactly to /// the interface specified here. /// /// Any deviation from this contract results in: /// - compilation failure, or /// - silent misalignment of registry tables (undefined behavior). /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Header-only /// - No state /// - No runtime logic /// - No ownership /// - No dynamic allocation /// /// Everything defined here must be: /// - usable in constant expressions, /// - safe to include in any translation unit, /// - independent of include order (modulo dependent headers). /// /// @ingroup mars2grib_backend_concepts /// #pragma once // System includes #include #include #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @brief Descriptor interface for a single semantic concept entry. /// /// `RegisterEntryDescriptor` defines the **compile-time interface contract** /// that every concept entry must implement in order to participate in: /// /// - concept/variant indexing, /// - entry-level dispatch, /// - variant-level dispatch, /// - phase-level (stage × section) dispatch. /// /// This struct is **never instantiated**. /// It is used purely as a *compile-time interface specification*. /// /// ----------------------------------------------------------------------------- /// Template parameters /// ----------------------------------------------------------------------------- /// /// @tparam VariantEnum /// Enum type enumerating all variants of the concept. /// /// @tparam VariantListT /// A `ValueList<...>` containing all values of `VariantEnum` /// that represent valid variants. /// /// The order of values in `VariantListT` is **semantically significant** and /// defines: /// - local variant indices, /// - ordering in flattened variant tables. /// /// ----------------------------------------------------------------------------- /// Semantic meaning /// ----------------------------------------------------------------------------- /// /// Each specialization of `RegisterEntryDescriptor` represents exactly: /// /// - one *concept* (e.g. levelType, timeRange, gridType), /// - a finite, ordered set of *variants*, /// - a family of dispatch points parameterized by: /// - capability, /// - variant, /// - stage, /// - section, /// - dictionary types. /// /// ----------------------------------------------------------------------------- /// Required invariants /// ----------------------------------------------------------------------------- /// /// Implementations must guarantee: /// /// - `VariantEnum` is an enum type. /// - `VariantListT` lists *only* values of `VariantEnum`. /// - Every value in `VariantListT` is unique. /// - `VariantListT::size` accurately reflects the number of variants. /// /// Violating these invariants results in undefined behavior in registry engines. /// template struct RegisterEntryDescriptor { /// /// @brief Enum type representing the variants of this concept. /// using Variant = VariantEnum; /// /// @brief Compile-time list of all valid variant values. /// /// This list defines the *local ordering* of variants within the concept. /// using VariantList = VariantListT; /// /// @brief Return the canonical name of the concept. /// /// This name is used for: /// - diagnostics, /// - debugging output, /// - string-based lookup paths. /// /// @return /// A stable, null-terminated string view identifying the concept. /// /// @note /// The returned string must have static storage duration. /// static constexpr std::string_view entryName(); /// /// @brief Return the canonical name of a variant. /// /// This function maps a compile-time variant value to its /// human-readable identifier. /// /// @tparam V /// A value of `VariantEnum` identifying the variant. /// /// @return /// A stable, null-terminated string view identifying the variant. /// /// @note /// This function is used exclusively in constexpr contexts and /// in diagnostic utilities. /// template static constexpr std::string_view variantName(); /// /// @brief Number of variants supported by this concept. /// /// This value is derived directly from `VariantList`. /// /// @note /// This constant is consumed by registry engines to: /// - allocate compile-time tables, /// - compute offsets in flattened index spaces. /// static constexpr std::size_t variant_count = VariantList::size; /// /// @brief Phase-level dispatch interface. /// /// This function provides the most granular level of dispatch: /// it selects a callback based on: /// - capability, /// - stage, /// - section, /// - variant, /// - concrete dictionary types. /// /// @tparam Capability /// Compile-time capability identifier. /// /// @tparam Stage /// Encoding stage index. /// /// @tparam Section /// GRIB section index. /// /// @tparam Variant /// Variant value of this concept. /// /// @tparam MarsDict_t /// MARS dictionary type. /// /// @tparam ParDict_t /// Parameter dictionary type. /// /// @tparam OptDict_t /// Options dictionary type. /// /// @tparam OutDict_t /// Output dictionary type. /// /// @return /// A function pointer implementing the requested phase-level behavior, /// or `nullptr` if the combination is not supported. /// template static constexpr Fn phaseCallbacks(); /// /// @brief Variant-level dispatch interface. /// /// This function selects a callback based on: /// - capability, /// - variant, /// - concrete dictionary types. /// /// It is less granular than `phaseCallbacks` and is typically used /// in earlier or coarser dispatch layers. /// /// @return /// A function pointer implementing the variant-level behavior, /// or `nullptr` if unsupported. /// template static constexpr Fn variantCallbacks(); /// /// @brief Entry-level dispatch interface. /// /// This function selects a callback based only on: /// - capability, /// - dictionary types. /// /// It represents the coarsest dispatch granularity and is typically /// used for: /// - dictionary matching, /// - capability probing, /// - preflight checks. /// /// @return /// A function pointer implementing the entry-level behavior, /// or `nullptr` if unsupported. /// template static constexpr Fm entryCallbacks(); }; } // namespace metkit::mars2grib::backend::compile_time_registry_engine metkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/utils.h0000664000175000017500000001266115246725757027660 0ustar alastairalastair/* * (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 utils.h /// @brief Compile-time concatenation utilities for fixed-size arrays. /// /// This header provides a minimal, constexpr-capable utility to **concatenate /// two `std::array` objects into a single `std::array`**, entirely at compile time. /// /// ----------------------------------------------------------------------------- /// Scope and responsibility /// ----------------------------------------------------------------------------- /// /// This file is intentionally narrow in scope. It provides: /// /// - a constexpr implementation of array concatenation, /// - suitable for use in template metaprogramming contexts, /// - with no dynamic allocation and no runtime overhead. /// /// It does **not**: /// - perform bounds checking, /// - provide runtime utilities, /// - introduce any registry-specific semantics. /// /// ----------------------------------------------------------------------------- /// Architectural role /// ----------------------------------------------------------------------------- /// /// This utility is a foundational building block used by higher-level /// compile-time registry engines to: /// /// - assemble flattened lookup tables, /// - concatenate per-entry or per-variant blocks, /// - materialize large constexpr dispatch tables. /// /// It lives in the `detail` namespace because it is considered an /// **implementation primitive**, not part of the public API. /// /// ----------------------------------------------------------------------------- /// Design constraints /// ----------------------------------------------------------------------------- /// /// - Fully `constexpr` (usable in constant expressions) /// - Header-only /// - Allocation-free /// - Type- and size-safe /// - Compatible with C++17 /// /// ----------------------------------------------------------------------------- /// Complexity guarantees /// ----------------------------------------------------------------------------- /// /// - Compile-time complexity: O(N1 + N2) /// - Runtime complexity: zero (fully evaluated at compile time) /// /// ----------------------------------------------------------------------------- /// Safety and invariants /// ----------------------------------------------------------------------------- /// /// - Both input arrays must have the same element type `T`. /// - The resulting array has size `N1 + N2`. /// - Element order is strictly preserved: /// - all elements of `a` precede all elements of `b`. /// #pragma once // system includes #include #include #include #include // Project includes #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine::detail { /// /// @brief Implementation helper for compile-time array concatenation. /// /// This function performs the actual concatenation by expanding two /// `std::index_sequence`s corresponding to the indices of the input arrays. /// /// It is intentionally separated from the public interface to: /// - avoid exposing index sequence details, /// - allow clean forwarding from the simpler `concat` wrapper. /// /// @tparam T Element type of the arrays /// @tparam N1 Size of the first array /// @tparam N2 Size of the second array /// @tparam I1 Index pack for the first array /// @tparam I2 Index pack for the second array /// /// @param[in] a First input array /// @param[in] b Second input array /// @param[in] Index sequence for array `a` /// @param[in] Index sequence for array `b` /// /// @return /// A `std::array` containing: /// `{ a[0], a[1], ..., a[N1-1], b[0], b[1], ..., b[N2-1] }` /// /// @note /// This function is intended to be invoked only from `concat`. /// template constexpr std::array concat_impl(const std::array& a, const std::array& b, std::index_sequence, std::index_sequence) { return {{a[I1]..., b[I2]...}}; } /// /// @brief Concatenate two `std::array` objects at compile time. /// /// This function provides a simple, user-facing interface for array /// concatenation, hiding all index-sequence machinery. /// /// @tparam T Element type of the arrays /// @tparam N1 Size of the first array /// @tparam N2 Size of the second array /// /// @param[in] a First input array /// @param[in] b Second input array /// /// @return /// A `std::array` containing the elements of `a` /// followed by the elements of `b`. /// /// @note /// This function is `constexpr` and may be used in: /// - compile-time table construction, /// - static data member initialization, /// - other constant-expression contexts. /// template constexpr std::array concat(const std::array& a, const std::array& b) { return concat_impl(a, b, std::make_index_sequence{}, std::make_index_sequence{}); } } // namespace metkit::mars2grib::backend::compile_time_registry_engine::detailmetkit-1.20.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makePhaseCallbacksRegistry.h0000664000175000017500000003043015246725757033741 0ustar alastairalastair /* * (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 PhaseCallbacksRegistry.h /// @brief Compile-time global registry for phase-level callbacks. /// /// This header defines the machinery required to **materialize, entirely at /// compile time, a three-dimensional dispatch table of phase-level callbacks**. /// /// ----------------------------------------------------------------------------- /// Conceptual result /// ----------------------------------------------------------------------------- /// /// The primary product of this header is a constexpr data structure with the /// following logical shape: /// /// @code /// phaseCallbacks[globalVariant][stage][section] -> Fn | nullptr /// @endcode /// /// where: /// /// - `globalVariant` is a flattened index over *all variants of all entries* /// (as defined by the compile-time registry engine). /// - `stage` is an encoding stage index in the range `[0, NUM_STAGES)`. /// - `section` is a GRIB section index in the range `[0, NUM_SECTIONS)`. /// - `Fn` is a function pointer implementing a concrete encoding action. /// - `nullptr` denotes that the requested capability is **not implemented** /// for the given `(variant, stage, section)` triple. /// /// ----------------------------------------------------------------------------- /// Structural definition of the index space /// ----------------------------------------------------------------------------- /// /// The three axes of the dispatch table are defined as follows: /// /// 1. **Global variant axis** /// Determined by: /// - the order of Entry descriptors in `EntriesList` (TypeList order), /// - the order of variant values in each `Entry::VariantList` (ValueList order). /// /// 2. **Stage axis** /// A fixed compile-time dimension of size `NUM_STAGES`, representing /// the sequential encoding phases of the GRIB header construction pipeline. /// /// 3. **Section axis** /// A fixed compile-time dimension of size `NUM_SECTIONS`, representing /// GRIB message sections. /// /// The resulting layout is: /// /// @code /// [ /// Variant0 -> [ Stage0 -> [Sec0, Sec1, ...], /// Stage1 -> [Sec0, Sec1, ...], /// ... ], /// Variant1 -> [ ... ], /// ... /// ] /// @endcode /// /// All dimensions are: /// - contiguous, /// - fixed at compile time, /// - deterministic, /// - identical across all translation units. /// /// ----------------------------------------------------------------------------- /// Capability selection model /// ----------------------------------------------------------------------------- /// /// Phase-level behavior is parameterized by a **compile-time capability /// identifier**, represented as a `std::size_t` template parameter. /// /// Each capability produces a *distinct* registry instantiation. /// Capability selection therefore incurs: /// - zero runtime cost, /// - zero branching, /// - zero dynamic dispatch. /// /// ----------------------------------------------------------------------------- /// Design goals /// ----------------------------------------------------------------------------- /// /// - **Zero runtime overhead** /// All decisions are resolved at compile time. /// /// - **Dense, indexable layout** /// Direct indexing without indirection or lookup structures. /// /// - **Strict structural alignment** /// Indices are guaranteed to match those produced by the variant and /// entry registries. /// /// - **Header-only, constexpr-only** /// Safe for inclusion in any translation unit. /// /// ----------------------------------------------------------------------------- /// Assumptions and invariants /// ----------------------------------------------------------------------------- /// /// Each Entry type appearing in `EntriesList` is assumed to provide: /// /// @code /// template < /// std::size_t Capability, /// auto Stage, /// auto Section, /// auto Variant, /// class MarsDict_t, /// class ParDict_t, /// class OptDict_t, /// class OutDict_t /// > /// static constexpr Fn /// phaseCallbacks(); /// @endcode /// /// returning either: /// - a valid function pointer implementing the phase-level operation, or /// - `nullptr` if the operation is not defined. /// /// Any deviation from this contract results in a compile-time error. /// /// ----------------------------------------------------------------------------- /// Relationship to other registries /// ----------------------------------------------------------------------------- /// /// This registry represents the **final and most granular dispatch layer**. /// /// It composes and refines: /// - EntryVariantRegistry (index space definition), /// - EntryCallbacksRegistry (entry-level behavior), /// - VariantCallbacksRegistry (variant-level behavior). /// /// This layer adds: /// - stage awareness, /// - section awareness, /// - full phase resolution. /// #pragma once // System includes #include #include #include // Project includes #include "metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h" #include "metkit/mars2grib/backend/compile-time-registry-engine/common.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::compile_time_registry_engine { /// /// @namespace detail /// @brief Internal implementation details of the phase callbacks registry. /// /// This namespace contains low-level template machinery used to: /// - build per-section rows, /// - assemble per-stage planes, /// - concatenate all variant blocks across all entries. /// /// All entities in this namespace are **implementation details** and must not be /// referenced directly by user code. /// namespace detail { /// /// @brief One row of phase callbacks for a fixed stage. /// /// A PhaseRow represents all section-level callbacks for a single /// `(variant, stage)` pair. /// /// Layout: /// @code /// PhaseRow[section] -> Fn | nullptr /// @endcode /// template using PhaseRow = std::array, NUM_SECTIONS>; /// /// @brief One plane of phase callbacks for a fixed variant. /// /// A PhasePlane aggregates all stages and sections for a single variant. /// /// Layout: /// @code /// PhasePlane[stage][section] -> Fn | nullptr /// @endcode /// template using PhasePlane = std::array, NUM_STAGES>; /// /// @brief Build a single phase row for a fixed Entry, Variant, and Stage. /// /// This function instantiates all section-level callbacks corresponding to: /// - one Entry, /// - one Variant, /// - one Stage, /// across all sections. /// /// The resulting PhaseRow has exactly `NUM_SECTIONS` elements. /// template constexpr PhaseRow makePhaseRow(std::index_sequence) { return {{Entry::template phaseCallbacks()...}}; } /// /// @brief Build a full phase plane for a single variant. /// /// This function builds: /// - one PhaseRow per stage, /// - for all stages `[0, NUM_STAGES)`, /// producing a complete PhasePlane. /// template constexpr PhasePlane makePhasePlane(std::index_sequence) { return {{// For every stage in Stages..., build a full row of sections makePhaseRow( std::make_index_sequence{})...}}; } /// /// @brief Build phase planes for all variants of a single Entry. /// /// This function expands over `Entry::VariantList` and produces /// one PhasePlane per variant. /// /// The order of planes exactly matches the VariantList order. /// template constexpr std::array, sizeof...(Variants)> makeEntryPhaseCallbacks(ValueList) { return {{makePhasePlane( std::make_index_sequence{})...}}; } template constexpr auto makeEntryPhaseCallbacks() { return makeEntryPhaseCallbacks( typename Entry::VariantList{}); } /// /// @brief Compile-time builder for the full phase callbacks registry. /// /// This metafunction recursively traverses the EntriesList typelist and /// concatenates the per-entry variant phase blocks into a single, /// flattened registry indexed by global variant ID. /// /// The resulting array has: /// - one PhasePlane per global variant, /// - total size equal to the total number of variants across all entries. /// template struct BuildPhaseCallbacks; /// /// @brief Base case for an empty EntriesList. /// /// Terminates recursion and produces an empty registry. /// template struct BuildPhaseCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t> { static constexpr std::array, 0> value() { return {}; } }; /// /// @brief Recursive case: prepend Head’s phase blocks and recurse on Tail. /// /// This preserves: /// - Entry ordering, /// - Variant ordering, /// - Stage ordering, /// - Section ordering. /// template struct BuildPhaseCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t> { static constexpr auto value() { constexpr auto head = makeEntryPhaseCallbacks(); constexpr auto tail = BuildPhaseCallbacks, Capability, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>::value(); return concat(head, tail); } }; } // namespace detail /// /// @brief Construct the compile-time phase callbacks registry. /// /// This function is the **public API** of the phase-level registry. /// /// It materializes, at compile time, a fully expanded three-dimensional /// dispatch table indexed by: /// - global variant, /// - stage, /// - section. /// /// @tparam EntriesList TypeList of Entry descriptors /// @tparam Capability Compile-time capability identifier /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Options dictionary type /// @tparam OutDict_t Output dictionary type /// /// @return /// A `constexpr std::array, N>` where: /// - `N` is the total number of global variants, /// - element `i` corresponds to global variant index `i`. /// /// @note /// The returned object is intended to be: /// - stored as a `static constexpr` variable, /// - indexed directly in hot paths, /// - never modified. /// template constexpr auto makePhaseCallbacksRegistry() { return detail::BuildPhaseCallbacks::value(); } } // namespace metkit::mars2grib::backend::compile_time_registry_engine metkit-1.20.2/src/metkit/mars2grib/backend/sections/0000775000175000017500000000000015246725757022473 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/0000775000175000017500000000000015246725757025201 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer2.h0000664000175000017500000001200215246725757031277 0ustar alastairalastair/* * (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 section2Initializer.h /// @brief Initializer for GRIB Section 2 (Local Use Section). /// /// This header defines a **section initializer** for GRIB **Section 2**, /// responsible for allocating and populating the *Local Use Section* /// based on the resolved template number. /// /// Section 2 is used to encode local or centre-specific extensions that /// are not part of the official GRIB specification. In the mars2grib /// backend, this initializer supports both: /// /// - standard local definition numbers /// - *virtual* template numbers used internally by the encoder /// /// In particular, this initializer contains special handling for /// DestinE-related virtual template numbers that are mapped onto /// valid local definition sections with additional metadata. /// /// This file performs controlled mutation of the output GRIB dictionary /// and includes structured exception handling to ensure consistent /// error propagation across section initialization boundaries. /// /// @ingroup mars2grib_backend_sections /// #pragma once #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Initializer for GRIB Section 2 (Local Use Section). /// /// This function allocates and configures GRIB Section 2 by: /// - enabling the local definition section /// - selecting the appropriate local definition number /// - optionally injecting additional metadata for special template numbers /// /// Two *virtual* template numbers are handled explicitly: /// - `1001`: DestinE / ClimateDT local definition /// - `1002`: DestinE / ExtremesDT local definition /// /// These template numbers are **not part of the official ecCodes tables**. /// They are used internally by the mars2grib encoder to emulate the /// behavior of standard templates while injecting additional semantics /// through Section 2. /// /// All dictionary mutations are performed via `set_or_throw` to ensure /// strict error checking. /// /// @tparam SectionNumber GRIB section number (expected to be 2) /// @tparam TemplateNumber GRIB template number or virtual template identifier /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary /// @param par Read-only parameter dictionary /// @param opt Read-only options dictionary /// @param out Output GRIB dictionary to be populated /// /// @throws Mars2GribGenericException /// If any dictionary operation fails while preparing Section 2. /// /// @note /// Existing local definition content is not checked and may be overwritten. /// template void allocateTemplateNumber2(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // Dictionary traits using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { // Enable local definition section (overwrite if already present) set_or_throw(out, "setLocalDefinition", 1); // Select local definition number based on template number // Special handling for DestinE virtual templates if constexpr (TemplateNumber == 1001) { // Minimal Section 2 set_or_throw(out, "localDefinitionNumber", 1L); // DestinE metadata set_or_throw(out, "productionStatusOfProcessedData", 12L); // ClimateDT dataset tag set_or_throw(out, "dataset", "climate-dt"); } else if constexpr (TemplateNumber == 1002) { // Minimal Section 2 set_or_throw(out, "localDefinitionNumber", 1L); // DestinE metadata set_or_throw(out, "productionStatusOfProcessedData", 12L); // ExtremesDT dataset tag set_or_throw(out, "dataset", "extremes-dt"); } else { set_or_throw(out, "localDefinitionNumber", TemplateNumber); } return; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Error preparing section 2 with template number", Here())); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer3.h0000664000175000017500000001261715246725757031314 0ustar alastairalastair/* * (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 section3Initializer.h /// @brief Initializer for GRIB Section 3 (Grid Definition Section). /// /// This header defines a **section initializer** for GRIB **Section 3**, /// responsible for configuring the *Grid Definition Section* according to /// the selected grid definition template. /// /// The initializer supports: /// - standard grid definition templates, mapped directly from the template number /// - special-case handling for selected templates that require explicit /// preconditioning of the GRIB handle prior to encoding /// /// In particular, Template **50** (spectral representation) requires /// explicit initialization of several keys (values, truncation parameters, /// spectral mode, etc.) to satisfy ecCodes constraints and enable correct /// conversion to spherical harmonics. /// /// All dictionary mutations are performed via checked dictionary traits, /// and failures are wrapped in a mars2grib-specific exception with /// preserved exception nesting. /// /// @ingroup mars2grib_backend_sections /// #pragma once // System includes #include #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Initializer for GRIB Section 3 (Grid Definition Section). /// /// This function prepares Section 3 by selecting and configuring the /// appropriate *Grid Definition Template*. /// /// Behaviour depends on the template number: /// - **Template 50** (spectral grid): requires explicit initialization of /// grid size, spectral truncation parameters, representation mode, and /// placeholder values, following ecCodes recommendations /// - **All other templates**: the grid definition template number is set /// directly with no additional preprocessing /// /// @tparam SectionNumber GRIB section number (expected to be 3) /// @tparam TemplateNumber Grid definition template number /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary /// @param par Read-only parameter dictionary /// @param opt Read-only options dictionary /// @param out Output GRIB dictionary to be populated /// /// @throws Mars2GribGenericException /// If any dictionary operation fails while preparing Section 3. /// /// @note /// The special handling for Template 50 follows ecCodes guidance for /// spectral GRIB messages: /// https://confluence.ecmwf.int/display/ECC/ecCodes+developer+FAQ+-+GRIB#ecCodesdeveloperFAQGRIB-GRIB:HowcanIconvertthesampleGRIB2.tmpltosphericalharmonics? /// template void allocateTemplateNumber3(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // Dictionary traits using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { // Special handling for spectral grids (Template 50) if constexpr (TemplateNumber == 50) { // Precondition GRIB handle for spectral representation set_or_throw(out, "numberOfDataPoints", 6L); set_or_throw(out, "numberOfValues", 6L); set_or_throw(out, "bitsPerValue", 16L); set_or_throw(out, "typeOfFirstFixedSurface", 105L); set_or_throw>(out, "values", std::vector{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}); set_or_throw(out, "scaleFactorOfFirstFixedSurface", 0L); set_or_throw(out, "scaledValueOfFirstFixedSurface", 0L); set_or_throw(out, "gridDefinitionTemplateNumber", 50L); set_or_throw(out, "J", 1L); set_or_throw(out, "K", 1L); set_or_throw(out, "M", 1L); set_or_throw(out, "spectralType", 1L); set_or_throw(out, "spectralMode", 1L); set_or_throw(out, "numberOfOctectsForNumberOfPoints", 0L); set_or_throw(out, "interpretationOfNumberOfPoints", 0L); set_or_throw(out, "dataRepresentationTemplateNumber", 51L); } if constexpr (TemplateNumber != 50 && TemplateNumber != 1000) { // Standard grid definition template long drt = static_cast(TemplateNumber); set_or_throw(out, "gridDefinitionTemplateNumber", drt); } return; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Error preparing section 3 with template number", Here())); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h0000664000175000017500000000700115246725757032031 0ustar alastairalastair/* * (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 sectionInitializerTypes.h /// @brief Core type definitions for GRIB section initializer registries. /// /// This header defines the **fundamental type aliases** used by the /// mars2grib backend to describe section initializer registries. /// /// Section initializers are lightweight, stateless functions responsible for /// populating GRIB sections based on the resolved dictionaries produced by /// the frontend and concept layers. /// /// This file provides: /// - a canonical function pointer type (`Fn`) for section initializers /// - a registry entry type (`Entry`) pairing a template number with an initializer /// /// These types are used to build compile-time or static registries mapping /// GRIB template numbers to the corresponding initialization routines. /// /// This file contains **no logic**, **no state**, and **no runtime behavior**. /// It is purely a type-definition header. /// /// @ingroup mars2grib_backend_sections /// #pragma once // System includes #include #include #include "metkit/mars2grib/backend/concepts/EncodingCallbacksRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Function pointer type for GRIB section initializers. /// /// This alias defines the canonical signature for all section initializer /// functions used by the mars2grib backend. /// /// A section initializer: /// - consumes read-only frontend dictionaries (`Mars`, `Param`, `Options`) /// - mutates the output GRIB dictionary corresponding to a specific section /// /// Initializers are invoked by the encoder once the appropriate GRIB /// template has been selected. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// template using Fn = metkit::mars2grib::backend::compile_time_registry_engine::Fn; /// /// @brief Registry entry associating a GRIB template number with an initializer. /// /// An `Entry` represents a single row in a section initializer registry. /// It binds: /// - a GRIB template number (e.g. Product Definition Template, Grid Definition Template) /// - the corresponding section initializer function /// /// Registries composed of these entries are used to: /// - select the correct initializer based on the resolved template number /// - dispatch section initialization at runtime with zero dynamic allocation /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// template struct Entry { using Fn_t = Fn; std::size_t templateNumber; Fn_t callback; }; } // namespace metkit::mars2grib::backend::sections::initializersmetkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer1.h0000664000175000017500000000600115246725757031300 0ustar alastairalastair/* * (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 section1Initializer.h /// @brief Placeholder initializer for GRIB Section 1. /// /// This header defines a **section initializer** associated with /// GRIB **Section 1** and **Template Number 1**. /// /// Section 1 in GRIB messages contains identification and origin /// metadata and, in the current mars2grib architecture, does not /// require any concept-driven or dictionary-based initialization. /// /// The initializer provided here is therefore a **no-op**, introduced /// to preserve uniformity in the section-initializer registry and /// dispatch infrastructure. /// /// This file contains **no state**, **no encoding logic**, and performs /// **no mutation** of the output dictionary. /// /// @note /// This initializer is expected to be removed or replaced once /// Section 1 handling is either specialized or excluded from the /// generic initialization pipeline. /// /// @ingroup mars2grib_backend_sections /// #pragma once #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief No-op initializer for GRIB Section 1, Template 1. /// /// This function conforms to the standard section initializer /// signature but intentionally performs no operations. /// /// It allows Section 1 to participate in the same compile-time /// registry and dispatch mechanisms as other GRIB sections, /// despite requiring no initialization logic. /// /// @tparam SectionNumber GRIB section number (expected to be 1) /// @tparam TemplateNumber GRIB template number (expected to be 1) /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary (unused) /// @param par Read-only parameter dictionary (unused) /// @param opt Read-only options dictionary (unused) /// @param out Output GRIB dictionary (unused) /// /// @note /// This function currently exists as a placeholder and may be /// removed or specialized once Section 1 initialization is /// handled explicitly. /// template void allocateTemplateNumber1(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // No-op for Section 1 — placeholder initializer } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer0.h0000664000175000017500000000572215246725757031310 0ustar alastairalastair/* * (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 section0Initializer.h /// @brief Placeholder initializer for GRIB Section 0. /// /// This header defines a **section initializer** associated with /// GRIB **Section 0** and **Template Number 0**. /// /// Section 0 in GRIB messages contains only metadata related to the /// GRIB edition and message structure and does not require any /// concept-driven encoding or dictionary-based initialization. /// /// As such, the initializer provided here is intentionally a **no-op**. /// It exists solely to satisfy the uniform section-initializer /// infrastructure used by the mars2grib backend. /// /// This file contains **no state**, **no logic**, and performs /// **no mutation** of the output dictionary. /// /// @note /// This initializer is temporary and marked for removal once /// Section 0 handling is fully excluded from the generic /// section initialization pipeline. /// /// @ingroup mars2grib_backend_sections /// #pragma once #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief No-op initializer for GRIB Section 0, Template 0. /// /// This function conforms to the standard section initializer /// signature but intentionally performs no operations. /// /// It is provided to allow Section 0 to participate in the same /// compile-time registry and dispatch mechanisms as other GRIB /// sections, even though no initialization is required. /// /// @tparam SectionNumber GRIB section number (expected to be 0) /// @tparam TemplateNumber GRIB template number (expected to be 0) /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary (unused) /// @param par Read-only parameter dictionary (unused) /// @param opt Read-only options dictionary (unused) /// @param out Output GRIB dictionary (unused) /// /// @note /// This function currently exists as a placeholder and is expected /// to be removed. /// template void allocateTemplateNumber0(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // No-op for Section 0 — placeholder initializer } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer4.h0000664000175000017500000000646615246725757031322 0ustar alastairalastair/* * (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 section4Initializer.h /// @brief Initializer for GRIB Section 4 (Product Definition Section). /// /// This header defines a **section initializer** for GRIB **Section 4**, /// responsible for selecting and setting the *Product Definition Template* /// (PDT) number. /// /// Section 4 describes the scientific meaning of the data (forecast, /// analysis, ensemble, statistics, etc.). In the mars2grib backend, /// the template number is resolved by the concept layer and passed /// unchanged to the GRIB output dictionary by this initializer. /// /// This initializer performs a single, well-defined mutation of the /// output dictionary and relies on strict error checking and structured /// exception propagation. /// /// @ingroup mars2grib_backend_sections /// #pragma once #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Initializer for GRIB Section 4 (Product Definition Section). /// /// This function configures Section 4 by setting the /// **Product Definition Template Number** corresponding to the /// resolved product type. /// /// No additional preprocessing is performed at this stage; all /// concept-specific logic affecting Section 4 is handled elsewhere /// in the encoding pipeline. /// /// @tparam SectionNumber GRIB section number (expected to be 4) /// @tparam TemplateNumber Product Definition Template number /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary /// @param par Read-only parameter dictionary /// @param opt Read-only options dictionary /// @param out Output GRIB dictionary to be populated /// /// @throws Mars2GribGenericException /// If setting the product definition template number fails. /// template void allocateTemplateNumber4(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // Dictionary traits using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { long pdt = static_cast(TemplateNumber); set_or_throw(out, "productDefinitionTemplateNumber", pdt); return; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Error preparing section 4 with template number", Here())); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionRegistry.h0000664000175000017500000003003215246725757030545 0ustar alastairalastair/* * (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 sectionInitializerRegistry.h /// @brief Static registries and dispatch logic for GRIB section initializers. /// /// This header defines the **section initializer registry layer** of the /// mars2grib backend. /// /// It provides: /// - compile-time registries mapping *(section, template number)* pairs /// to concrete section initializer functions /// - a generic lookup utility for registry tables /// - a unified dispatch function used by the encoder to resolve /// the correct initializer at runtime /// /// Each GRIB section exposes a sorted, constexpr registry associating /// template numbers with their corresponding initializer functions. /// These registries are intentionally static, allocation-free, and /// trivially inspectable. /// /// This file contains **no encoding logic** itself; it only orchestrates /// the selection of the appropriate initializer. /// /// @ingroup mars2grib_backend_sections /// #pragma once // System includes #include #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer0.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer1.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer2.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer3.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer4.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializer5.h" #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Registry for GRIB Section 0 initializers. /// /// Section 0 does not require initialization logic; this registry /// contains a single placeholder entry. /// template inline constexpr Entry Sec0Reg[] = { {0, &allocateTemplateNumber0<0, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Registry for GRIB Section 1 initializers. /// template inline constexpr Entry Sec1Reg[] = { {0, &allocateTemplateNumber1<1, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Registry for GRIB Section 2 (Local Use Section) initializers. /// /// Includes both official and *virtual* template numbers used internally /// by the encoder (e.g. DestinE extensions). /// template inline constexpr Entry Sec2Reg[] = { {1, &allocateTemplateNumber2<2, 1, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {15, &allocateTemplateNumber2<2, 15, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {16, &allocateTemplateNumber2<2, 16, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {20, &allocateTemplateNumber2<2, 20, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {24, &allocateTemplateNumber2<2, 24, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {25, &allocateTemplateNumber2<2, 25, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {36, &allocateTemplateNumber2<2, 36, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {37, &allocateTemplateNumber2<2, 37, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {38, &allocateTemplateNumber2<2, 38, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {39, &allocateTemplateNumber2<2, 39, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {45, &allocateTemplateNumber2<2, 45, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1000, &allocateTemplateNumber2<2, 1000, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1001, &allocateTemplateNumber2<2, 1001, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1002, &allocateTemplateNumber2<2, 1002, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1004, &allocateTemplateNumber2<2, 1004, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Registry for GRIB Section 3 (Grid Definition Section) initializers. /// template inline constexpr Entry Sec3Reg[] = { {0, &allocateTemplateNumber3<3, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {40, &allocateTemplateNumber3<3, 40, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {50, &allocateTemplateNumber3<3, 50, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {101, &allocateTemplateNumber3<3, 101, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {150, &allocateTemplateNumber3<3, 150, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1000, &allocateTemplateNumber3<3, 1000, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Registry for GRIB Section 4 (Product Definition Section) initializers. /// template inline constexpr Entry Sec4Reg[] = { {0, &allocateTemplateNumber4<4, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {1, &allocateTemplateNumber4<4, 1, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {2, &allocateTemplateNumber4<4, 2, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {8, &allocateTemplateNumber4<4, 8, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, // {9, &allocateTemplateNumber4<4, 9, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {11, &allocateTemplateNumber4<4, 11, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {12, &allocateTemplateNumber4<4, 12, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {32, &allocateTemplateNumber4<4, 32, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {33, &allocateTemplateNumber4<4, 33, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {40, &allocateTemplateNumber4<4, 40, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {41, &allocateTemplateNumber4<4, 41, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {42, &allocateTemplateNumber4<4, 42, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {43, &allocateTemplateNumber4<4, 43, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {50, &allocateTemplateNumber4<4, 50, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {45, &allocateTemplateNumber4<4, 45, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {46, &allocateTemplateNumber4<4, 46, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {85, &allocateTemplateNumber4<4, 85, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {48, &allocateTemplateNumber4<4, 48, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {49, &allocateTemplateNumber4<4, 49, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {60, &allocateTemplateNumber4<4, 60, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {61, &allocateTemplateNumber4<4, 61, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {76, &allocateTemplateNumber4<4, 76, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {77, &allocateTemplateNumber4<4, 77, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {78, &allocateTemplateNumber4<4, 78, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {79, &allocateTemplateNumber4<4, 79, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {99, &allocateTemplateNumber4<4, 99, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {100, &allocateTemplateNumber4<4, 100, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {103, &allocateTemplateNumber4<4, 103, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {104, &allocateTemplateNumber4<4, 104, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {107, &allocateTemplateNumber4<4, 107, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {121, &allocateTemplateNumber4<4, 121, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {122, &allocateTemplateNumber4<4, 122, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {131, &allocateTemplateNumber4<4, 131, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {142, &allocateTemplateNumber4<4, 142, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {143, &allocateTemplateNumber4<4, 143, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {144, &allocateTemplateNumber4<4, 144, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {145, &allocateTemplateNumber4<4, 145, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Registry for GRIB Section 5 (Data Representation Section) initializers. /// template inline constexpr Entry Sec5Reg[] = { {0, &allocateTemplateNumber5<5, 0, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {42, &allocateTemplateNumber5<5, 42, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {51, &allocateTemplateNumber5<5, 51, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}}; /// /// @brief Lookup a section initializer function by template number. /// /// Performs a linear search over a compile-time registry table and /// returns the corresponding initializer function pointer. /// /// @tparam EntryT Registry entry type /// @tparam N Registry size /// /// @param table Registry table /// @param templ Template number /// /// @return Initializer function pointer, or `nullptr` if not found. /// template constexpr auto lookup(const EntryT (&table)[N], std::size_t templ) -> decltype(table[0].callback) { for (std::size_t i = 0; i < N; ++i) { if (table[i].templateNumber == templ) return table[i].callback; } return nullptr; } /// /// @brief Resolve a section initializer function. /// /// Dispatches to the appropriate section registry based on the /// GRIB section number and resolves the initializer corresponding /// to the provided template number. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param section GRIB section number /// @param templ Template number /// /// @return Initializer function pointer, or `nullptr` if not found. /// /// @throws Mars2GribGenericException /// If an error occurs during dispatch. /// template Fn sectionRegistry(std::size_t section, std::size_t templ) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { switch (section) { case 0: return lookup(Sec0Reg, templ); case 1: return lookup(Sec1Reg, templ); case 2: return lookup(Sec2Reg, templ); case 3: return lookup(Sec3Reg, templ); case 4: return lookup(Sec4Reg, templ); case 5: return lookup(Sec5Reg, templ); default: return nullptr; } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Error getting section initializer function for section " + std::to_string(section) + " template " + std::to_string(templ), Here())); } } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer5.h0000664000175000017500000000656215246725757031320 0ustar alastairalastair/* * (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 section5Initializer.h /// @brief Initializer for GRIB Section 5 (Data Representation Section). /// /// This header defines a **section initializer** for GRIB **Section 5**, /// responsible for selecting the *Data Representation Template* (DRT) /// used to encode the data values. /// /// Section 5 controls how the field values are packed (e.g. simple packing, /// complex packing, JPEG, PNG, spectral, etc.). In the mars2grib backend, /// the concrete template number is resolved by the concept layer and passed /// directly to the GRIB output dictionary by this initializer. /// /// This initializer performs a single, well-defined mutation of the output /// dictionary and relies on strict error checking and structured exception /// propagation. /// /// @ingroup mars2grib_backend_sections /// #pragma once #include "metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::initializers { /// /// @brief Initializer for GRIB Section 5 (Data Representation Section). /// /// This function configures Section 5 by setting the /// **Data Representation Template Number** corresponding to the /// selected packing or representation method. /// /// No additional preprocessing is performed at this stage; all /// concept-specific logic affecting Section 5 is handled elsewhere /// in the encoding pipeline. /// /// @tparam SectionNumber GRIB section number (expected to be 5) /// @tparam TemplateNumber Data Representation Template number /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary /// @tparam OutDict_t Type of the output GRIB dictionary /// /// @param mars Read-only MARS dictionary /// @param par Read-only parameter dictionary /// @param opt Read-only options dictionary /// @param out Output GRIB dictionary to be populated /// /// @throws Mars2GribGenericException /// If setting the data representation template number fails. /// template void allocateTemplateNumber5(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, OutDict_t& out) { // Dictionary traits using metkit::mars2grib::utils::dict_traits::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { long pdt = static_cast(TemplateNumber); set_or_throw(out, "dataRepresentationTemplateNumber", pdt); return; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Error preparing section 5 with template number", Here())); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::sections::initializers metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/0000775000175000017500000000000015246725757024334 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/Recipe.h0000664000175000017500000002441515246725757025722 0ustar alastairalastair/* * (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 Recipe.h /// @brief Runtime representation of a fully expanded section recipe. /// /// This header defines `Recipe`, an **immutable runtime object** produced from /// the compile-time section-recipe DSL. /// /// A `Recipe` encapsulates: /// - A GRIB **template number** /// - A multidimensional selection space derived from `Select<>` grammar nodes /// - The total number of **valid variant combinations** /// /// Conceptually, a recipe represents the *Cartesian product* of a sequence of /// concept-variant selectors. Each point in this space corresponds to a /// concrete encoding configuration and can be materialized on demand as a /// `ResolvedTemplateData` instance. /// /// The class is designed for: /// - Fast lookup /// - Predictable iteration /// - Deterministic decoding via mixed-radix arithmetic /// /// Instances are immutable after construction and are intended to be stored /// and traversed in hot paths during encoding plan construction. /// /// Debug and introspection facilities are provided for diagnostics only and /// are not part of the performance-critical API. /// /// @ingroup mars2grib_backend_section_recipes /// #pragma once // System includes #include #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h" #include "metkit/mars2grib/backend/sections/resolver/Select.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::resolver::dsl { /// /// @brief Runtime, immutable container defining a GRIB section template number. /// /// A `Recipe` represents the **runtime realization of a GRIB section template /// definition**. /// /// In the GRIB model, a *template number* is not defined by a single choice, /// but by an **ordered set of concepts** contributing to the same section. /// Each concept may participate in the definition of the template using /// **different variants**, and different combinations of variants may map /// to the same template number. /// /// As a consequence, the process of defining a template number is /// **inherently combinatorial**. /// /// The role of a `Recipe` is to: /// - Bind a specific GRIB template number /// - Describe the complete space of valid concept-variant combinations /// that realize that template /// /// The valid variants for each concept are expressed at compile time using /// the `Select<>` DSL object, which specifies: /// - Which concept participates /// - Which variants of that concept are admissible for the template /// /// At runtime, the recipe materializes this information as a multidimensional /// selection space, where: /// - Each dimension corresponds to one concept /// - Each dimension contains the list of allowed global variant identifiers /// /// Individual encoding configurations are obtained by enumerating this space /// using mixed-radix decoding, preserving the original concept order. /// /// The class is intentionally opaque and immutable: /// - No mutation after construction /// - No exposure of internal storage /// - Construction only via the `make_recipe` factory /// /// This design ensures deterministic behavior, efficient lookup, and /// suitability for hot-path execution during encoding plan construction. /// class Recipe { public: /// /// @brief Return the total number of valid variant combinations. /// std::size_t numberOfCombinations() const noexcept { return nCombinations_; } /// /// @brief Materialize a resolved recipe entry. /// /// This function decodes the given linear index into a concrete /// combination of concept variants and returns it as a /// `ResolvedTemplateData` payload. /// /// Mixed-radix decoding is used, preserving the original selector order. /// The rightmost selector varies fastest. /// /// @param[in] i Linear combination index /// /// @return Fully populated resolved template payload /// /// @throws std::out_of_range /// If @p i is greater than or equal to the number of combinations /// ResolvedTemplateData getEntry(std::size_t i) const { if (i >= nCombinations_) { throw std::out_of_range("Recipe::getEntry index out of range"); } ResolvedTemplateData entry; entry.templateNumber = templateNumber_; entry.count = variants_.size(); std::size_t remainder = i; // Mixed-radix decoding: // selector order preserved // rightmost selector varies fastest for (std::size_t d = variants_.size(); d-- > 0;) { const std::size_t radix = sizes_[d]; const std::size_t idx = remainder % radix; remainder /= radix; entry.variantIndices[d] = variants_[d][idx]; } return entry; } /// /// @brief Print a human-readable description of the recipe. /// /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// void debug_print(const std::string& prefix, std::ostream& os) const { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: Recipe\n"; os << prefix << " :: templateNumber : " << templateNumber_ << std::endl; os << prefix << " :: dimensions : " << variants_.size() << std::endl; os << prefix << " :: nCombinations : " << nCombinations_ << std::endl; for (std::size_t d = 0; d < variants_.size(); ++d) { os << prefix << " :: dimension[" << d << "]" << std::endl; os << prefix << " :: radix : " << sizes_[d] << std::endl; os << prefix << " :: variants glbId : [ "; const auto& dim = variants_[d]; for (std::size_t i = 0; i < dim.size(); ++i) { os << dim[i]; if (i + 1 < dim.size()) os << ", "; } os << " ] " << std::endl; os << prefix << " :: variants names : [ "; for (std::size_t i = 0; i < dim.size(); ++i) { std::size_t id = dim[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); os << "\"" << cname << "::" << vname << "\""; if (i + 1 < dim.size()) os << ", "; } os << " ]" << std::endl; } } /// /// @brief Convert the recipe to a JSON-like string. /// /// Intended exclusively for diagnostics and debugging. /// /// @return JSON-style string representation /// std::string debug_to_json() const { using metkit::mars2grib::backend::concepts_::GeneralRegistry; std::ostringstream oss; oss << "{ \"Recipe\":{"; oss << "\"templateNumber\":" << templateNumber_ << ", "; oss << "\"dimensions\":" << variants_.size() << ", "; oss << "\"nCombinations\":" << nCombinations_ << ", "; oss << "\"selectors\":["; for (std::size_t d = 0; d < variants_.size(); ++d) { oss << "{\"dimension\":" << d << ", "; oss << "\"radix\":" << sizes_[d] << ", "; oss << "\"variantIndices\": ["; const auto& dim = variants_[d]; for (std::size_t i = 0; i < dim.size(); ++i) { oss << dim[i]; if (i + 1 < dim.size()) oss << ", "; } oss << "], " << std::endl; oss << "\"variantNames\": ["; for (std::size_t i = 0; i < dim.size(); ++i) { std::size_t id = dim[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); oss << "\"" << cname << "::" << vname << "\""; if (i + 1 < dim.size()) oss << ", "; } oss << " ]}" << std::endl; if (d + 1 < variants_.size()) oss << ", "; } oss << "]}}"; return oss.str(); } private: using Dimension = std::vector; using Dimensions = std::vector; using Sizes = std::vector; const std::size_t templateNumber_; const Dimensions variants_; const Sizes sizes_; const std::size_t nCombinations_; private: Recipe(std::size_t tpl, Dimensions&& variants, Sizes&& sizes, std::size_t nComb) : templateNumber_(tpl), variants_(std::move(variants)), sizes_(std::move(sizes)), nCombinations_(nComb) {} template friend Recipe make_recipe(); }; /// /// @brief Factory function converting DSL grammar to a runtime recipe. /// /// This function erases the compile-time `Select<>` grammar and produces /// a fully materialized `Recipe` object suitable for runtime use. /// /// @tparam TemplateNumber GRIB template number /// @tparam Selects Sequence of selector grammar nodes /// /// @return Immutable runtime recipe instance /// template inline Recipe make_recipe() { constexpr std::size_t N = sizeof...(Selects); Recipe::Dimensions variants; Recipe::Sizes sizes; variants.reserve(N); sizes.reserve(N); // Erase Select<> grammar here ( [&] { variants.emplace_back(Selects::ids.begin(), Selects::ids.end()); sizes.push_back(Selects::ids.size()); }(), ...); std::size_t nComb = 1; for (std::size_t s : sizes) { nComb *= s; } return Recipe{TemplateNumber, std::move(variants), std::move(sizes), nComb}; } } // namespace metkit::mars2grib::backend::sections::resolver::dsl metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/Select.h0000664000175000017500000002007315246725757025726 0ustar alastairalastair/* * (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 Select.h /// @brief Compile-time selector for concept variants in section recipe definitions. /// /// This header defines the `Select` template, a **compile-time DSL building block** /// used by section recipes to specify *which variants of a given concept* are /// applicable for a recipe entry. /// /// The selector supports two usage modes: /// - **Explicit selection** of a fixed set of concept variants /// - **Wildcard selection** (`any`), meaning *all variants* of the concept /// /// The selection is resolved entirely at compile time and materialized as a /// constant array of *global variant identifiers*, as defined by the /// `GeneralRegistry`. /// /// This mechanism allows section recipes to: /// - Remain declarative and concise /// - Avoid runtime branching or lookups /// - Integrate seamlessly with the compile-time recipe expansion pipeline /// /// @note /// This type is part of the *section recipe DSL* and is not intended to be used /// directly by runtime encoding logic. /// /// @ingroup mars2grib_backend_section_recipes /// #pragma once // System includes #include #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::resolver::dsl { /// /// @file Select.h /// @brief Compile-time selector defining admissible variants of a concept. /// /// This header defines `Select`, a **compile-time DSL primitive** used to /// express *which variants of a given concept* are admissible when defining /// a GRIB section template. /// /// A `Select` always refers to **exactly one concept** and defines a /// **subset of its variants**. This subset represents the variants of the /// concept that are allowed to participate in the definition of a specific /// template number. /// /// The selector supports two explicit modes: /// /// - **Explicit selection** /// When one or more variant tags are provided, only those variants are /// considered valid for the concept. /// /// - **Implicit full selection (wildcard)** /// When no variants are provided, *all variants* of the concept are /// implicitly selected. /// /// In other words: /// - `Select` selects **only** variants `V1` and `V2` /// - `Select` selects **all** variants of `Concept` /// /// `Select` objects are the fundamental building blocks used by every /// `Recipe` to define the **rules governing template-number selection**. /// /// A recipe is defined by an ordered list of `Select` objects, one per /// participating concept. Together, these selectors describe the full /// combinatorial space of admissible concept-variant combinations that /// realize a given GRIB template number. /// /// The selection is resolved entirely at compile time and materialized /// as a constant list of global variant identifiers, ensuring: /// - Zero runtime overhead /// - Deterministic behavior /// - Early validation of recipe definitions /// /// @note /// `Select` does not perform any runtime logic. It is a declarative, /// compile-time construct whose sole responsibility is to describe /// admissible variant subsets for a concept. /// /// @see Recipe /// @ingroup mars2grib_backend_section_resolver /// template struct Select { /// Concept associated with this selector using concept_type = Concept; /// True if the selector matches all variants of the concept static constexpr bool is_any = (sizeof...(Vs) == 0); /// Number of selected variants static constexpr std::size_t count = is_any ? Concept::variant_count : sizeof...(Vs); private: /// /// @brief Generate the array of global variant identifiers. /// /// This helper resolves the selection rule to a concrete array of /// variant IDs using the `GeneralRegistry`. /// /// The resolution is performed entirely at compile time. /// /// @return `std::array` containing global variant IDs /// static constexpr auto generate_ids() { using metkit::mars2grib::backend::concepts_::GeneralRegistry; if constexpr (is_any) { return GeneralRegistry::make_id_array_from_concept(); } else { return GeneralRegistry::make_id_array_from_variants(); } // Remove compiler warning mars2gribUnreachable(); } public: /// Compile-time array of selected global variant identifiers inline static constexpr auto ids = generate_ids(); /// /// @brief Print a human-readable description of the selector. /// /// This function emits a structured textual representation of the /// selector configuration, including: /// - Concept type /// - Wildcard status /// - Number of selected variants /// - Variant indices /// - Fully-qualified variant names /// /// @param[in] prefix Prefix string prepended to each output line /// @param[in,out] os Output stream /// static void debug_print(const std::string& prefix, std::ostream& os) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: Select<" << typeid(Concept).name() << ">" << std::endl; os << prefix << " :: is_any : " << is_any << std::endl; os << prefix << " :: count : " << count << std::endl; os << prefix << " :: variantIndices : [ "; for (std::size_t i = 0; i < ids.size(); ++i) { os << ids[i]; if (i + 1 < ids.size()) os << ", "; } os << " ]" << std::endl; os << prefix << " :: variantNames : [ "; for (std::size_t i = 0; i < ids.size(); ++i) { std::size_t id = ids[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); os << "\"" << cname << "::" << vname << "\""; if (i + 1 < ids.size()) os << ", "; } os << " ]" << std::endl; } /// /// @brief Serialize the selector state to a JSON-like string. /// /// This method produces a compact JSON-style representation intended /// solely for debugging and diagnostics. /// /// @return String representation of the selector state /// /// @note /// The returned string is not guaranteed to be valid strict JSON and /// must not be used for machine parsing. /// static std::string debug_to_json() { using metkit::mars2grib::backend::concepts_::GeneralRegistry; std::ostringstream oss; oss << "{ \"Select\":{ \"typeId\": " << typeid(Concept).name(); oss << ", \"is_any\": " << is_any; oss << ", \"count\":" << count; oss << ", \"variantIndices\": ["; for (std::size_t i = 0; i < ids.size(); ++i) { oss << ids[i]; if (i + 1 < ids.size()) oss << ", "; } oss << "]"; oss << ", \"variantIndices\": ["; for (std::size_t i = 0; i < ids.size(); ++i) { std::size_t id = ids[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); oss << "\"" << cname << "::" << vname << "\""; if (i + 1 < ids.size()) oss << ", "; } oss << "]}}"; return oss.str(); } }; } // namespace metkit::mars2grib::backend::sections::resolver::dslmetkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/resolver.md0000664000175000017500000001755315246725757026532 0ustar alastairalastair@page mars2grib_section_resolver Section Resolver Subsystem @tableofcontents @section resolver_overview Overview The **Section Resolver** subsystem is responsible for determining the concrete layout of a GRIB section given: - A set of *declarative template rules* (recipes) - A *runtime description of active concepts* Its purpose is to select, in a deterministic and efficient way, a `SectionLayoutData` that fully defines how a GRIB section must be encoded. Conceptually, the resolver answers the question: @verbatim “Given the active concepts for this request, which section template applies?” @endverbatim The resolver is a **pure backend subsystem**. It contains no frontend orchestration logic and no configuration policy. It operates solely on: - Immutable configuration data - Immutable runtime state and produces deterministic results. -------------------------------------------------------------------------- @section resolver_big_picture Big picture Section resolution is defined by the interaction of three orthogonal elements: - **Declarative rules** describing which combinations of concept variants are admissible for a template - **Runtime state** describing which concept variants are active - **Resolution algorithms** that match the runtime state against the admissible rule space These responsibilities are intentionally separated to ensure: - Clear ownership of concerns - Predictable behavior - Efficient hot-path execution - Long-term maintainability -------------------------------------------------------------------------- @section resolver_namespace_structure Namespace organization The resolver subsystem is organized into a hierarchy of namespaces, each with a clearly defined role. @subsection resolver_ns_root backend::sections::resolver This is the **public API** of the resolver subsystem. It exposes only the types required to *use* the resolver: - `ActiveConceptsData` Runtime description of which concept variants are active - `SectionLayoutData` Result of section resolution; fully describes how a section must be encoded - `SectionTemplateSelector` The main algorithmic entry point performing section resolution Frontend code must depend **only** on this namespace. -------------------------------------------------------------------------- @subsection resolver_ns_dsl backend::sections::resolver::dsl This namespace defines the **declarative grammar** used to describe section template rules. It contains no algorithms and performs no resolution. Types in this namespace are used to *describe*: - Which concepts participate in defining a template - Which variants of each concept are admissible The main types are: - `Select` Compile-time selector defining a subset (possibly all) of variants for a single concept - `Recipe` Runtime representation of a single GRIB template number together with the full combinatorial space of admissible concept-variant combinations - `Recipes` Section-scoped container aggregating all `Recipe` instances defining all template numbers valid for a section The DSL namespace is intentionally expressive and declarative. It defines *what is allowed*, not *what is selected*. -------------------------------------------------------------------------- @subsection resolver_ns_detail backend::sections::resolver::detail This namespace contains **internal infrastructure** used by the resolver algorithms. It includes: - Key representations and compression utilities - Lookup tables and indices - Intermediate data structures - Performance-oriented helpers Types in this namespace are: - Not part of the public API - Free to change - Optimized for internal use No frontend code should depend on this namespace. -------------------------------------------------------------------------- @subsection resolver_ns_debug backend::sections::resolver::debug This namespace contains **debugging and introspection utilities**. Typical contents include: - Human-readable printers - JSON-like serialization helpers - Diagnostic formatting utilities Debug code is intentionally isolated to ensure that: - Hot-path code remains minimal - Debug facilities do not pollute public APIs - Debug functionality can be gated or compiled out if needed -------------------------------------------------------------------------- @section resolver_dsl The recipe DSL @subsection resolver_select Select: defining admissible variants A `Select` object defines a **subset of variants of a single concept**. It always refers to exactly one concept and expresses which of its variants are admissible when defining a GRIB template. Two modes are supported: - **Explicit selection** When one or more variants are specified, only those variants are admissible. - **Implicit full selection (wildcard)** When no variants are specified, *all variants* of the concept are assumed to be admissible. `Select` is a compile-time construct and introduces no runtime overhead. -------------------------------------------------------------------------- @subsection resolver_recipe Recipe: defining a template number A `Recipe` represents the runtime realization of a **single GRIB template number**. A template number is defined by: - An ordered set of concepts - For each concept, a set of admissible variants Because each concept may participate with multiple variants, the process of defining a template number is **inherently combinatorial**. A `Recipe` captures this combinatorial space and provides a deterministic way to enumerate all admissible combinations. -------------------------------------------------------------------------- @subsection resolver_recipes Recipes: section-scoped template definitions A `Recipes` object represents the **complete set of template definitions** valid for a single GRIB section. Each `Recipes` instance: - Is defined for exactly one section - Aggregates multiple `Recipe` objects - Represents all template numbers admissible for that section It provides a uniform expansion mechanism producing `ResolvedTemplateData` payloads. -------------------------------------------------------------------------- @section resolver_runtime Active concept state `ActiveConceptsData` represents the **runtime state** of concept activation for a specific encoding request. It answers questions such as: - Which concepts are active? - Which variant of a concept is active? - Is a concept missing or unspecified? This runtime state is matched against the declarative recipe space during resolution. -------------------------------------------------------------------------- @section resolver_algorithm Resolution algorithm The resolution process performed by `SectionTemplateSelector` can be summarized as: 1. Build a signature from the active concept state 2. Match this signature against the admissible recipe space 3. Select the corresponding section layout The algorithm is: - Deterministic - Stateless - Optimized for hot-path execution -------------------------------------------------------------------------- @section resolver_dependency_rules Dependency rules The following dependency rules are enforced by design: - Frontend code depends only on `backend::sections::resolver` - Resolver algorithms may depend on `dsl`, `detail`, and `debug` - DSL types do not depend on resolver algorithms - Debug utilities do not affect hot-path logic These rules ensure a clean layering and prevent accidental coupling. -------------------------------------------------------------------------- @section resolver_summary Summary The section resolver subsystem provides a compiler-like architecture for GRIB section template selection: - Declarative grammar (`dsl`) - Runtime state (`ActiveConceptsData`) - Deterministic resolution algorithms This design ensures correctness, performance, and long-term maintainability. metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/TemplateSignatureKey.h0000664000175000017500000001621215246725757030615 0ustar alastairalastair/* * (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 TemplateSignatureKey.h /// @brief Compact key representing an active concept-variant signature. /// /// This header defines `TemplateSignatureKey`, an **internal, fixed-size key** /// used by the section resolver to represent the *active concept-variant /// signature* of an encoding request. /// /// A signature key is constructed from the runtime active concept state and /// encodes, in a compact and ordered form, the set of **global variant /// identifiers** that characterize the request. /// /// The key is used for: /// - Efficient comparison /// - Ordered lookup /// - Hash-based indexing /// /// It is designed for hot-path usage and introduces no dynamic allocation. /// /// @note /// This type is an internal implementation detail of the resolver and is not /// part of the public API. /// /// @ingroup mars2grib_backend_section_resolver /// #pragma once // System includes #include #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::resolver::detail { /// /// @brief Fixed-size signature key for concept-variant combinations. /// /// `TemplateSignatureKey` represents an **ordered sequence of global /// variant identifiers** describing the active concept state. /// /// The key is: /// - Dense and contiguous /// - Order-sensitive /// - Free of dynamic allocation /// /// The `size` field indicates how many entries in @ref data are valid. /// /// Ordering and equality are defined lexicographically and are consistent /// with the semantics of concept-variant matching. /// struct TemplateSignatureKey { /// Registry providing global concept and variant identifiers using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Maximum number of variant identifiers that can be stored. /// /// This corresponds to the total number of registered variants. /// static constexpr std::size_t maxSize = GeneralRegistry::NVariants; /// /// @brief Ordered list of global variant identifiers. /// /// Only the first @ref size entries are valid. /// std::array data{}; /// /// @brief Number of active entries in @ref data. /// std::uint16_t size{0}; /// /// @brief Equality comparison. /// /// Two keys are equal if they have the same size and identical /// variant identifiers in the same order. /// bool operator==(const TemplateSignatureKey& other) const noexcept { if (size != other.size) { return false; } for (std::size_t i = 0; i < size; ++i) { if (data[i] != other.data[i]) { return false; } } return true; } /// /// @brief Strict weak ordering. /// /// Lexicographical comparison on the stored variant identifiers, /// with shorter keys ordered before longer ones when prefixes match. /// bool operator<(const TemplateSignatureKey& other) const noexcept { const std::size_t n = std::min(size, other.size); for (std::size_t i = 0; i < n; ++i) { if (data[i] < other.data[i]) { return true; } if (data[i] > other.data[i]) { return false; } } return size < other.size; } }; /// /// @brief Hash functor for `TemplateSignatureKey`. /// /// This hash combines the variant identifiers using a standard /// hash-mixing scheme suitable for unordered containers. /// struct TemplateSignatureKeyHash { std::size_t operator()(const TemplateSignatureKey& key) const noexcept { std::size_t h = 0; for (std::size_t i = 0; i < key.size; ++i) { h ^= key.data[i] + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2); } return h; } }; /// /// @namespace metkit::mars2grib::backend::sections::resolver::detail::debug /// /// @brief Debug and introspection utilities for template signature keys. /// /// These utilities are intended exclusively for diagnostics and debugging. /// They must not be used in performance-critical code paths. /// namespace debug { /// /// @brief Print a human-readable representation of a template signature key. /// /// @param[in] key Signature key /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// inline void debug_print_Key(const TemplateSignatureKey& key, std::string_view prefix, std::ostream& os) { using GeneralRegistry = TemplateSignatureKey::GeneralRegistry; TemplateSignatureKeyHash hasher; os << prefix << " :: TemplateSignatureKey\n"; os << prefix << " :: maxSize : " << key.maxSize << "\n"; os << prefix << " :: size : " << key.size << "\n"; os << prefix << " :: hash : " << hasher(key) << "\n"; os << prefix << " :: variantIndices : [ "; for (std::size_t i = 0; i < key.size; ++i) { os << key.data[i]; if (i + 1 < key.size) { os << ", "; } } os << " ]\n"; os << prefix << " :: variantNames : [ "; for (std::size_t i = 0; i < key.size; ++i) { const std::size_t id = key.data[i]; os << "\"" << GeneralRegistry::conceptNameArr[id] << "::" << GeneralRegistry::variantNameArr[id] << "\""; if (i + 1 < key.size) { os << ", "; } } os << " ]\n"; } /// /// @brief Convert a template signature key to a JSON-like string. /// /// Intended exclusively for debugging and diagnostics. /// /// @param[in] key Signature key /// /// @return JSON-style string representation /// inline std::string debug_convert_Key_to_json(const TemplateSignatureKey& key) { using GeneralRegistry = TemplateSignatureKey::GeneralRegistry; TemplateSignatureKeyHash hasher; std::ostringstream oss; oss << "{ \"TemplateSignatureKey\": { " << "\"maxSize\": " << key.maxSize << ", " << "\"size\": " << key.size << ", " << "\"hash\": " << hasher(key) << ", " << "\"variantIndices\": [ "; for (std::size_t i = 0; i < key.size; ++i) { oss << key.data[i]; if (i + 1 < key.size) { oss << ", "; } } oss << " ], \"variantNames\": [ "; for (std::size_t i = 0; i < key.size; ++i) { const std::size_t id = key.data[i]; oss << "\"" << GeneralRegistry::conceptNameArr[id] << "::" << GeneralRegistry::variantNameArr[id] << "\""; if (i + 1 < key.size) { oss << ", "; } } oss << " ] } }"; return oss.str(); } } // namespace debug } // namespace metkit::mars2grib::backend::sections::resolver::detail metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/dsl.h0000664000175000017500000000037415246725757025273 0ustar alastairalastair#pragma once #include "metkit/mars2grib/backend/sections/resolver/Recipe.h" #include "metkit/mars2grib/backend/sections/resolver/Recipes.h" #include "metkit/mars2grib/backend/sections/resolver/Select.h" #include "metkit/mars2grib/utils/generalUtils.h"metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h0000664000175000017500000001770715246725757030225 0ustar alastairalastair/* * (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 ActiveConceptsData.h /// @brief Runtime representation of active concept variants inferred from a MARS dictionary. /// /// This header defines `ActiveConceptsData`, a **runtime data structure** /// representing the set of concepts that are *semantically active* for a /// given encoding request. /// /// An instance of this structure is **directly inferred from a MARS input /// dictionary** during the normalization and sanitization phases of the /// encoding pipeline. /// /// It captures, in a compact and lookup-friendly form: /// - Which concepts are required to semantically describe the MARS request /// - Which variant of each required concept must be used /// /// The structure is designed to be consumed by the section resolver /// subsystem as input state for template resolution. /// /// @ingroup mars2grib_backend_section_resolver /// #pragma once // System includeds #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::resolver { /// /// @brief Runtime container describing active concepts and their variants. /// /// `ActiveConceptsData` is a **pure data carrier** that represents the /// semantic interpretation of a MARS dictionary in terms of concepts and /// concept variants. /// /// The structure answers two fundamental questions for each registered concept: /// /// 1. **Is this concept required** to semantically describe the MARS request? /// 2. **If required, which variant** of the concept must be used? /// /// This information is produced by analyzing the MARS dictionary and is /// consumed by the section resolver to select appropriate section templates. /// /// ------------------------------------------------------------------------ /// /// @section activeconcepts_representation Internal representation /// /// The data is stored using two complementary arrays: /// /// - `activeVariantIndices` /// A dense array indexed by **concept identifier** /// /// - `activeConceptsIndices` /// A sparse list containing only the identifiers of **active concepts** /// /// The `count` field specifies the number of active concepts. /// /// The two arrays are used together as follows: /// /// @code /// for (std::size_t i = 0; i < count; ++i) { /// std::size_t conceptId = activeConceptsIndices[i]; /// std::size_t globalVariantId = activeVariantIndices[conceptId]; /// } /// @endcode /// /// ------------------------------------------------------------------------ /// /// @section activeconcepts_semantics Semantics of activeVariantIndices /// /// Each entry in `activeVariantIndices` encodes **both presence and choice**: /// /// - If a concept is **not required** to describe the MARS dictionary, /// its corresponding entry is set to a special sentinel value /// exposed by the `GeneralRegistry` (typically referred to as `Missing`) /// /// - If a concept **is required**, the entry contains the **global variant /// identifier** corresponding to the variant that must be used /// /// This design allows: /// - O(1) access by concept identifier /// - Explicit representation of inactive concepts /// - Efficient iteration over only active concepts /// /// ------------------------------------------------------------------------ /// /// @section activeconcepts_design Design considerations /// /// - No dynamic allocation /// - Fixed-capacity storage /// - Trivially copyable /// - Suitable for hot-path usage /// /// The structure performs **no validation** and enforces **no policy**. /// It is assumed to be fully consistent when handed to the resolver. /// struct ActiveConceptsData { /// Registry providing global concept and variant identifiers using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Maximum number of concepts that can be represented. /// static constexpr std::size_t maxCapacity = GeneralRegistry::NConcepts; /// /// @brief Mapping from concept identifier to active variant identifier. /// /// Indexed by **concept identifier**. /// /// Semantics: /// - `Missing` value → concept not required /// - otherwise → global variant identifier to be used /// std::array activeVariantIndices{}; /// /// @brief Sparse list of active concept identifiers. /// /// Only the first @ref count entries are valid. /// std::array activeConceptsIndices{}; /// /// @brief Number of active concepts. /// std::size_t count{0}; }; /// /// @namespace metkit::mars2grib::backend::sections::resolver::debug /// /// @brief Internal utilities for diagnostics and introspection. /// /// This namespace contains debug-only helpers used to inspect /// `ActiveConceptsData` instances. These utilities are not part of the /// public resolver API and must not be used in performance-critical paths. /// namespace debug { /// /// @brief Print a human-readable representation of active concept data. /// /// The output explicitly reflects the canonical access pattern used by /// the resolver and highlights both: /// - which concepts are active /// - which variants are selected /// /// @param[in] data Active concept state /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// inline void debug_print_ActiveConceptsData(const ActiveConceptsData& data, std::string_view prefix, std::ostream& os) { using GeneralRegistry = ActiveConceptsData::GeneralRegistry; os << prefix << " :: ActiveConceptsData\n"; os << prefix << " :: count : " << data.count << "\n"; for (std::size_t i = 0; i < data.count; ++i) { const std::size_t conceptId = data.activeConceptsIndices[i]; const std::size_t variantId = data.activeVariantIndices[conceptId]; if (variantId == GeneralRegistry::missing) { os << prefix << " :: concept[" << i << "] : Missing\n"; } else { os << prefix << " :: concept[" << i << "] : " << GeneralRegistry::conceptNameArr[variantId] << " -> " << GeneralRegistry::variantNameArr[variantId] << "\n"; } } os << std::flush; } /// /// @brief Convert active concept data to a JSON-like string. /// /// The resulting string is intended exclusively for debugging and /// diagnostics. It is not guaranteed to conform to strict JSON. /// /// @param[in] data Active concept state /// /// @return JSON-style string representation /// inline std::string debug_convert_ActiveConceptsData_to_json(const ActiveConceptsData& data) { using GeneralRegistry = ActiveConceptsData::GeneralRegistry; std::ostringstream oss; oss << "{ \"ActiveConceptsData\": { " << "\"count\": " << data.count << ", " << "\"concepts\": [ "; for (std::size_t i = 0; i < data.count; ++i) { const std::size_t conceptId = data.activeConceptsIndices[i]; const std::size_t variantId = data.activeVariantIndices[conceptId]; oss << "{ \"concept\": \"" << GeneralRegistry::conceptNameArr[variantId] << "\""; if (variantId == GeneralRegistry::missing) { oss << ", \"variant\": \"Missing\" }"; } else { oss << ", \"variant\": \"" << GeneralRegistry::variantNameArr[variantId] << "\" }"; } if (i + 1 < data.count) { oss << ", "; } } oss << " ] } }"; return oss.str(); } } // namespace debug } // namespace metkit::mars2grib::backend::sections::resolver metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/SectionTemplateSelector.h0000664000175000017500000004377315246725757031324 0ustar alastairalastair/* * (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 SectionTemplateSelector.h /// @brief Resolver responsible for selecting the template number of a GRIB section. /// /// This header defines `SectionTemplateSelector`, the **central algorithmic /// component** of the section resolver subsystem. /// /// A `SectionTemplateSelector`: /// - Is constructed once per section from declarative recipe definitions /// - Precomputes all data structures required for efficient lookup /// - Selects, at runtime, the correct section template number given the /// active concept state /// /// The selector is immutable after construction and optimized for hot-path /// usage during encoding. /// /// @ingroup mars2grib_backend_section_resolver /// #pragma once // System includes #include #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h" #include "metkit/mars2grib/backend/sections/resolver/CompressionMask.h" #include "metkit/mars2grib/backend/sections/resolver/Recipes.h" #include "metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h" #include "metkit/mars2grib/backend/sections/resolver/TemplateSignatureKey.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::resolver { // SectionTemplateSelector /// /// @brief Section-local resolver for GRIB template selection. /// /// `SectionTemplateSelector` encapsulates all logic required to select /// the appropriate GRIB template number for a **single section**, given /// the runtime active concept state. /// /// The selector operates in two phases: /// /// - **Construction phase (offline / once per section)** /// - Expand declarative recipes into a flat payload /// - Build a section-specific compression mask /// - Precompute lookup indices /// - Choose the optimal search strategy /// /// - **Resolution phase (runtime / hot path)** /// - Build a signature key from active concepts /// - Apply section-specific compression /// - Perform lookup using the preselected strategy /// - Produce a `SectionLayoutData` /// /// After construction, the selector is fully immutable. /// class SectionTemplateSelector { public: /// Runtime representation of active concepts using ActiveConcepts = ActiveConceptsData; /// Section-scoped recipe configuration using Recipes = metkit::mars2grib::backend::sections::resolver::dsl::Recipes; /// Section-scoped compression mask using CompressionMask = metkit::mars2grib::backend::sections::resolver::detail::CompressionMask; /// Expanded recipe entry produced by recipe expansion using ResolvedTemplateData = metkit::mars2grib::backend::sections::resolver::dsl::ResolvedTemplateData; /// Signature key type used for lookup using TemplateSignatureKey = metkit::mars2grib::backend::sections::resolver::detail::TemplateSignatureKey; /// Hash functor for signature keys using TemplateSignatureKeyHash = metkit::mars2grib::backend::sections::resolver::detail::TemplateSignatureKeyHash; /// /// @brief Select the section layout corresponding to the active concept state. /// /// @param[in] active Runtime active concept data /// /// @return Resolved section layout /// /// @throws Mars2GribGenericException /// If no matching template can be found /// const SectionLayoutData select_or_throw(const ActiveConcepts& active) const { using metkit::mars2grib::backend::sections::resolver::detail::make_SectionLayoutData_or_throw; const std::size_t id = searchFn_(*this, active); return make_SectionLayoutData_or_throw(sectionNumber_, payloads_[id]); } /// /// @brief Construct a selector from section recipes. /// /// This is the **only construction entry point**. /// /// @param[in] recipes Declarative recipe definitions for a section /// /// @return Fully constructed, immutable selector /// /// @throws Mars2GribGenericException /// If recipe expansion or index construction fails /// static SectionTemplateSelector make(const Recipes& recipes) { using metkit::mars2grib::backend::sections::resolver::detail::make_CompressionMask_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; // -------------------------------------------------------------------- // 1. Expand recipes → resolved payload // -------------------------------------------------------------------- std::vector payload = recipes.getPayload(); if (payload.empty()) { throw Mars2GribGenericException("SectionTemplateSelector: empty payload", Here()); } // -------------------------------------------------------------------- // 2. Build section-specific compression mask // -------------------------------------------------------------------- CompressionMask compressionMask = make_CompressionMask_or_throw(payload); // -------------------------------------------------------------------- // 3. Build (compressedKey, payloadIndex) lookup index // -------------------------------------------------------------------- std::vector> index; index.reserve(payload.size()); for (std::size_t i = 0; i < payload.size(); ++i) { const auto& entry = payload[i]; TemplateSignatureKey globalKey{}; globalKey.size = entry.count; for (std::size_t k = 0; k < entry.count; ++k) { globalKey.data[k] = entry.variantIndices[k]; } TemplateSignatureKey compressedKey = compressionMask.compressKey(globalKey); index.emplace_back(std::move(compressedKey), i); } std::sort(index.begin(), index.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); // -------------------------------------------------------------------- // 4. Reorder payload according to sorted index // -------------------------------------------------------------------- std::vector orderedPayload; orderedPayload.reserve(index.size()); for (const auto& [key, payloadIdx] : index) { orderedPayload.emplace_back(payload[payloadIdx]); } // -------------------------------------------------------------------- // 5. Choose lookup strategy // -------------------------------------------------------------------- const std::size_t N = index.size(); Index indexVariant; SearchFn searchFn = nullptr; if (N == 1) { indexVariant = SingleIndex{index.front().first, 0}; searchFn = &SectionTemplateSelector::search_single; } else if (N < 16) { ArrayIndex vec; vec.reserve(N); for (std::size_t i = 0; i < N; ++i) { vec.emplace_back(index[i].first, i); } indexVariant = std::move(vec); searchFn = &SectionTemplateSelector::search_linear; } else if (N < 256) { ArrayIndex vec; vec.reserve(N); for (std::size_t i = 0; i < N; ++i) { vec.emplace_back(index[i].first, i); } indexVariant = std::move(vec); searchFn = &SectionTemplateSelector::search_binary; } else { HashIndex map; map.reserve(N); for (std::size_t i = 0; i < N; ++i) { map.emplace(index[i].first, i); } indexVariant = std::move(map); searchFn = &SectionTemplateSelector::search_hash; } // -------------------------------------------------------------------- // 6. Construct immutable selector // -------------------------------------------------------------------- return SectionTemplateSelector{recipes.sectionId(), std::move(compressionMask), std::move(orderedPayload), std::move(indexVariant), searchFn}; } private: /// /// @brief Function pointer implementing the lookup strategy. /// /// This pointer selects the concrete search algorithm used at runtime /// to resolve a compressed signature key into a payload index. /// /// The function is chosen once during construction based on the /// number of template combinations and never changes afterward. /// /// All search functions: /// - Are pure /// - Are stateless /// - Throw if no matching template is found /// using SearchFn = std::size_t (*)(const SectionTemplateSelector&, const ActiveConcepts&); /// /// @brief Index optimized for a single template. /// /// Stores exactly one `(key, payloadIndex)` pair. /// Used when the section admits only one possible template. /// using SingleIndex = std::pair; /// /// @brief Linear / binary searchable index. /// /// Stores `(key, payloadIndex)` pairs in sorted order. /// Used for small to medium template spaces. /// using ArrayIndex = std::vector; /// /// @brief Hash-based index for large template spaces. /// /// Used when the number of admissible templates exceeds /// the threshold for efficient array-based search. /// using HashIndex = std::unordered_map; /// /// @brief Variant type holding the concrete index representation. /// /// Exactly one alternative is active at runtime, selected during /// construction and never changed afterward. /// using Index = std::variant; /// /// @brief GRIB section number handled by this selector. /// /// This value is immutable and propagated into the resulting /// `SectionLayoutData`. /// const std::size_t sectionNumber_; /// /// @brief Section-specific compression mask. /// /// Used to normalize signature keys before lookup by removing /// variants that never participate in this section. /// /// The mask is computed once during construction and reused /// for every lookup. /// const CompressionMask compressionMask_; /// /// @brief Ordered payload entries corresponding to resolved templates. /// /// The payload vector is reordered during construction so that /// its indices match those stored in the lookup index. /// /// The order of variants inside each payload entry is preserved /// and later used during encoding. /// const std::vector payloads_; /// /// @brief Lookup index mapping compressed keys to payload indices. /// /// The concrete representation is stored in @ref index_ and /// interpreted by the selected search function. /// const Index index_; /// /// @brief Pointer to the active search function. /// /// This pointer is guaranteed to be non-null after construction. /// const SearchFn searchFn_; /// /// @brief Resolve template index using a single-entry index. /// /// This search strategy is used when the section admits exactly /// one possible template. /// /// The active concept state is compressed and compared directly /// against the single stored key. /// /// @param[in] self Selector instance /// @param[in] active Runtime active concept data /// /// @return Index of the matching payload entry /// /// @throws Mars2GribGenericException /// If the compressed key does not match the stored key /// SectionTemplateSelector(std::size_t sectionNumber, CompressionMask&& mask, std::vector&& payloads, Index&& index, SearchFn fn) : sectionNumber_(sectionNumber), compressionMask_(std::move(mask)), payloads_(std::move(payloads)), index_(std::move(index)), searchFn_(fn) {} /// /// @brief Resolve template index using a single-entry index. /// /// This search strategy is used when the section admits exactly /// one possible template. /// /// The active concept state is compressed and compared directly /// against the single stored key. /// /// @param[in] self Selector instance /// @param[in] active Runtime active concept data /// /// @return Index of the matching payload entry /// /// @throws Mars2GribGenericException /// If the compressed key does not match the stored key /// static std::size_t search_single(const SectionTemplateSelector& self, const ActiveConcepts& active) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; const auto& [key, id] = std::get(self.index_); TemplateSignatureKey k = self.compressionMask_.compressKey(make_key(active)); if (k == key) { return id; } throw Mars2GribGenericException("No matching recipe", Here()); } /// /// @brief Resolve template index using linear search. /// /// This strategy performs a linear scan over a small number of /// `(key, payloadIndex)` pairs. /// /// It is selected when the template space is small enough that /// linear search is faster than binary or hash-based lookup. /// /// @param[in] self Selector instance /// @param[in] active Runtime active concept data /// /// @return Index of the matching payload entry /// /// @throws Mars2GribGenericException /// If no matching key is found /// static std::size_t search_linear(const SectionTemplateSelector& self, const ActiveConcepts& active) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; const auto& vec = std::get(self.index_); TemplateSignatureKey k = self.compressionMask_.compressKey(make_key(active)); for (const auto& [kk, id] : vec) { if (kk == k) { return id; } } throw Mars2GribGenericException("No matching recipe", Here()); } /// /// @brief Resolve template index using binary search. /// /// This strategy performs a binary search over a sorted array /// of `(key, payloadIndex)` pairs. /// /// It is selected for medium-sized template spaces where /// logarithmic lookup outperforms linear scanning. /// /// @param[in] self Selector instance /// @param[in] active Runtime active concept data /// /// @return Index of the matching payload entry /// /// @throws Mars2GribGenericException /// If no matching key is found /// static std::size_t search_binary(const SectionTemplateSelector& self, const ActiveConcepts& active) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; const auto& vec = std::get(self.index_); TemplateSignatureKey k = self.compressionMask_.compressKey(make_key(active)); auto it = std::lower_bound(vec.begin(), vec.end(), k, [](const auto& p, const TemplateSignatureKey& key) { return p.first < key; }); if (it != vec.end() && it->first == k) { return it->second; } throw Mars2GribGenericException("No matching recipe", Here()); } /// /// @brief Resolve template index using hash-based lookup. /// /// This strategy performs an average O(1) lookup using an /// unordered map keyed by compressed signature keys. /// /// It is selected for large template spaces where array-based /// search would be inefficient. /// /// @param[in] self Selector instance /// @param[in] active Runtime active concept data /// /// @return Index of the matching payload entry /// /// @throws Mars2GribGenericException /// If no matching key is found /// static std::size_t search_hash(const SectionTemplateSelector& self, const ActiveConcepts& active) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; const auto& map = std::get(self.index_); TemplateSignatureKey k = self.compressionMask_.compressKey(make_key(active)); auto it = map.find(k); if (it != map.end()) { return it->second; } throw Mars2GribGenericException("No matching recipe", Here()); } /// /// @brief Build a template signature key from active concept data. /// /// The key is constructed by iterating over the list of active /// concept identifiers and collecting the corresponding global /// variant identifiers. /// /// The resulting key: /// - Reflects the active semantic state /// - Preserves no ordering guarantees /// - Must be normalized using the section compression mask /// /// @param[in] active Runtime active concept data /// /// @return Uncompressed template signature key /// static TemplateSignatureKey make_key(const ActiveConcepts& active) { TemplateSignatureKey key{}; key.size = 0; for (std::size_t i = 0; i < active.count; ++i) { const std::size_t conceptId = active.activeConceptsIndices[i]; key.data[key.size++] = active.activeVariantIndices[conceptId]; } return key; } }; } // namespace metkit::mars2grib::backend::sections::resolver metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/Recipes.h0000664000175000017500000001344015246725757026101 0ustar alastairalastair/* * (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 Recipes.h /// @brief Runtime container for all template recipes of a GRIB section. /// /// This header defines `Recipes`, a **section-scoped runtime container** /// representing the complete set of GRIB template definitions applicable /// to a single GRIB section. /// /// A `Recipes` object is defined **per section** and aggregates all /// `Recipe` instances contributing to that section. Each `Recipe` /// defines one GRIB template number together with the full combinatorial /// space of concept-variant combinations that realize that template. /// /// Conceptually: /// - A GRIB section may admit multiple template numbers /// - Each template number is defined by an ordered set of concepts /// - Each concept may participate with multiple admissible variants /// /// The role of `Recipes` is to: /// - Collect all template definitions for a given section /// - Preserve their ordering /// - Provide a uniform way to expand them into a flat list of /// `ResolvedTemplateData` payloads /// /// The container is immutable after construction and is designed to be /// traversed in hot paths during section resolution and encoding plan /// construction. /// /// @ingroup mars2grib_backend_section_recipes /// #pragma once // System includes #include #include // Project includes #include "metkit/mars2grib/backend/sections/resolver/Recipe.h" #include "metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::resolver::dsl { /// /// @brief Runtime, immutable container for all recipes of a single GRIB section. /// /// A `Recipes` instance represents the **complete template-definition space** /// for one specific GRIB section. /// /// It owns: /// - The section identifier /// - An ordered list of `Recipe` objects, each corresponding to one /// GRIB template number valid for that section /// /// The container provides a single expansion operation that materializes /// all possible resolved templates (`ResolvedTemplateData`) for the section, /// by expanding each recipe and concatenating their combinatorial spaces. /// /// No mutation or filtering is performed at this level. /// class Recipes { public: /// /// @brief Construct a section-scoped recipe container. /// /// @param[in] sectionId GRIB section identifier /// @param[in] recipes Ordered list of recipe definitions for the section /// Recipes(std::size_t sectionId, std::vector&& recipes) : sectionId_(sectionId), recipes_(std::move(recipes)) {} /// /// @brief Return the GRIB section identifier. /// std::size_t sectionId() const noexcept { return sectionId_; } /// /// @brief Expand all recipes into resolved template payloads. /// /// This function materializes the full set of resolved templates /// defined for the section by: /// - Iterating over all recipes /// - Expanding each recipe’s combinatorial space /// - Concatenating the results in recipe order /// /// @return Vector of resolved template payloads /// std::vector getPayload() const { // ---- compute total size ---- std::size_t total = 0; for (const Recipe* r : recipes_) { total += r->numberOfCombinations(); } // ---- allocate payload ---- std::vector payload; payload.reserve(total); // ---- expand recipes in order ---- for (const Recipe* r : recipes_) { const std::size_t n = r->numberOfCombinations(); for (std::size_t i = 0; i < n; ++i) { payload.push_back(r->getEntry(i)); } } return payload; } /// /// @brief Print a human-readable description of the section recipes. /// /// This function prepends the section identifier and delegates the /// detailed output to the nested `Recipe` objects. /// /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// void debug_print(const std::string& prefix, std::ostream& os) const { os << prefix << " :: Recipes\n"; os << prefix << " :: sectionId : " << sectionId_ << "\n"; os << prefix << " :: nRecipes : " << recipes_.size() << "\n"; for (std::size_t i = 0; i < recipes_.size(); ++i) { os << prefix << " :: recipe[" << i << "]\n"; recipes_[i]->debug_print(prefix + std::string(" :: "), os); } } /// /// @brief Convert the section recipes to a JSON-like string. /// /// The output includes the section identifier and the JSON /// representation of each nested recipe. /// /// @return JSON-style string representation /// std::string debug_to_json() const { std::ostringstream oss; oss << "{ \"Recipes\": { " << "\"sectionId\": " << sectionId_ << ", " << "\"recipes\": [ "; for (std::size_t i = 0; i < recipes_.size(); ++i) { oss << recipes_[i]->debug_to_json(); if (i + 1 < recipes_.size()) { oss << ", "; } } oss << " ] } }"; return oss.str(); } private: const std::size_t sectionId_; const std::vector recipes_; }; } // namespace metkit::mars2grib::backend::sections::resolver::dsl metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h0000664000175000017500000001421515246725757030561 0ustar alastairalastair/* * (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 ResolvedTemplateData.h /// @brief Runtime container for parsed and resolved section recipes. /// /// This header defines `ResolvedTemplateData`, the **primary in-memory container** /// used by the section-recipe resolution subsystem to store the outcome of /// recipe parsing and template selection. /// /// A `ResolvedTemplateData` instance represents a fully resolved recipe entry /// and encodes, in a compact and cache-friendly form: /// - The GRIB **template number** to be applied /// - The ordered list of **global concept-variant identifiers** that define /// the exact encoding logic for that template /// /// This structure is designed to be: /// - Traversed frequently /// - Compared and searched efficiently /// - Passed through hot execution paths during encoding /// /// For this reason, the type is intentionally minimal, flat, and free of /// ownership or dynamic allocation. /// /// Debug and introspection functionality is provided externally and granted /// access via explicit friendship, ensuring that: /// - The public interface remains minimal /// - No debug-related code or symbols interfere with the hot path /// /// @ingroup mars2grib_backend_section_recipes /// #pragma once // System includes #include #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::sections::resolver::dsl { /// /// @brief Main container for parsed and resolved section recipes. /// /// This structure is the **central storage unit** produced by the recipe /// parsing and resolution pipeline. /// /// Each instance corresponds to a single resolved recipe and captures all /// information required to: /// - Identify the GRIB template to be used /// - Drive the ordered execution of concept operations during encoding /// /// The container is explicitly optimized for **hot-path usage**: /// - Fixed-capacity storage /// - No dynamic memory allocation /// - Trivial data layout /// /// Instances of this type are frequently accessed during recipe lookup /// and encoding plan construction. As a consequence, no runtime validation /// or defensive checks are performed inside the structure itself. /// /// Debug and introspection facilities are intentionally implemented as /// external friend functions to avoid polluting the public API and to /// ensure that debug-related code does not impact performance-critical /// execution paths. /// /// The layout and semantics of this structure are considered part of a /// **stable internal contract** for the section-recipe subsystem. /// struct ResolvedTemplateData { using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Maximum number of concept variants that can be stored. /// /// This corresponds to the total number of registered concepts and /// defines the fixed capacity of the container. /// static constexpr std::size_t maxCapacity = GeneralRegistry::NConcepts; /// /// @brief Ordered list of global concept-variant identifiers. /// /// Only the first @ref count entries are valid. /// std::array variantIndices{}; /// /// @brief Number of active entries in @ref variantIndices. /// std::size_t count{0}; /// /// @brief GRIB template number associated with this resolved recipe. /// std::size_t templateNumber{0}; }; namespace debug { inline void debug_print_ResolvedTemplateData(const ResolvedTemplateData& tdata, const std::string& prefix, std::ostream& os) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: ResolvedTemplateData" << std::endl; os << prefix << " :: templateNumber : " << tdata.templateNumber << std::endl; os << prefix << " :: count : " << tdata.count << std::endl; os << prefix << " :: variantIndices : [ "; for (std::size_t i = 0; i < tdata.count; ++i) { os << tdata.variantIndices[i]; if (i + 1 < tdata.count) os << ", "; } os << " ]" << std::endl; os << prefix << " :: variantNames : [ "; for (std::size_t i = 0; i < tdata.count; ++i) { std::size_t id = tdata.variantIndices[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); os << "\"" << cname << "::" << vname << "\""; if (i + 1 < tdata.count) os << ", "; } os << " ]" << std::endl; } inline std::string debug_convert_ResolvedTemplateData_to_json(const ResolvedTemplateData& tdata) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; std::ostringstream oss; oss << "{ \"ResolvedTemplateData\":{" << "\"templateNumber\":" << tdata.templateNumber << ", " << "\"count\": " << tdata.count << ", " << "\"variantIndices\":["; for (std::size_t i = 0; i < tdata.count; ++i) { oss << tdata.variantIndices[i]; if (i + 1 < tdata.count) oss << ", "; } oss << "], "; oss << "\"variantNames\":[ "; for (std::size_t i = 0; i < tdata.count; ++i) { std::size_t id = tdata.variantIndices[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); oss << "\"" << cname << "::" << vname << "\""; if (i + 1 < tdata.count) oss << ", "; } oss << " ]}}"; return oss.str(); } } // namespace debug } // namespace metkit::mars2grib::backend::sections::resolver::dsl metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h0000664000175000017500000001746315246725757030114 0ustar alastairalastair/* * (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 SectionLayoutData.h /// @brief Runtime description of a resolved GRIB section layout. /// /// This header defines `SectionLayoutData`, the **final product** of the /// section resolver subsystem. /// /// A `SectionLayoutData` instance represents a fully resolved and /// deterministic description of how a GRIB section must be encoded. /// /// It is produced by: /// - Resolving declarative recipe definitions /// - Matching them against the active concept state /// - Selecting the appropriate template number /// /// The structure is subsequently consumed by the header encoder to /// drive the execution of concept operations for the section. /// /// @ingroup mars2grib_backend_section_resolver /// #pragma once // System includes #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::resolver { /// /// @brief Final resolved layout for a GRIB section. /// /// `SectionLayoutData` is a **runtime data container** that fully describes /// the encoding layout of a GRIB section. /// /// It captures: /// - The GRIB section number /// - The selected GRIB template number /// - The ordered list of global concept-variant identifiers that must be /// applied when encoding the section /// /// The structure is: /// - Immutable once constructed /// - Flat and cache-friendly /// - Free of ownership and dynamic allocation /// /// Instances of this type represent the **terminal output** of the section /// resolution process. /// struct SectionLayoutData { /// Registry providing global concept and variant identifiers using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Maximum number of concept variants that can be stored. /// /// This corresponds to the total number of registered concepts. /// static constexpr std::size_t maxCapacity = GeneralRegistry::NConcepts; /// /// @brief Ordered list of global variant identifiers defining the layout. /// /// Only the first @ref count entries are valid. /// std::array variantIndices{}; /// /// @brief Number of active variants in @ref variantIndices. /// std::size_t count{0}; /// /// @brief Selected GRIB template number for the section. /// std::size_t templateNumber{0}; /// /// @brief GRIB section number this layout applies to. /// std::size_t sectionNumber{0}; }; /// /// @namespace metkit::mars2grib::backend::sections::resolver::detail /// /// @brief Internal helpers for constructing section layout data. /// /// This namespace contains non-public utilities used during section /// resolution. Functions here are not part of the public API and may /// change without notice. /// namespace detail { /// /// @brief Construct a `SectionLayoutData` from a resolved recipe entry. /// /// This function converts a `ResolvedTemplateData` payload into a /// `SectionLayoutData` instance by: /// - Copying the ordered list of variant identifiers /// - Assigning the selected template number /// - Binding the layout to a specific GRIB section /// /// @param[in] sectionNumber GRIB section number /// @param[in] recipeEntry Resolved recipe entry /// /// @return Fully populated section layout data /// /// @throws Mars2GribGenericException /// If construction fails for any reason /// inline SectionLayoutData make_SectionLayoutData_or_throw(std::size_t sectionNumber, const dsl::ResolvedTemplateData& recipeEntry) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { SectionLayoutData layoutData{}; layoutData.count = recipeEntry.count; for (std::size_t i = 0; i < recipeEntry.count; ++i) { layoutData.variantIndices[i] = recipeEntry.variantIndices[i]; } layoutData.templateNumber = recipeEntry.templateNumber; layoutData.sectionNumber = sectionNumber; return layoutData; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Unable to create SectionLayoutData", Here())); } mars2gribUnreachable(); } } // namespace detail /// /// @namespace metkit::mars2grib::backend::sections::resolver::debug /// /// @brief Debug and introspection utilities for section layout data. /// /// This namespace contains diagnostic helpers for inspecting /// `SectionLayoutData` instances. These utilities are not intended for /// performance-critical use. /// namespace debug { /// /// @brief Print a human-readable description of a section layout. /// /// @param[in] data Section layout data /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// inline void debug_print_SectionLayoutData(const SectionLayoutData& data, std::string_view prefix, std::ostream& os) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: SectionLayoutData" << std::endl; os << prefix << " :: sectionNumber : " << data.sectionNumber << std::endl; os << prefix << " :: templateNumber : " << data.templateNumber << std::endl; os << prefix << " :: count : " << data.count << std::endl; os << prefix << " :: variantIndices : [ "; for (std::size_t i = 0; i < data.count; ++i) { os << data.variantIndices[i]; if (i + 1 < data.count) { os << ", "; } } os << " ] " << std::endl; os << prefix << " :: variantNames : [ "; for (std::size_t i = 0; i < data.count; ++i) { std::size_t id = data.variantIndices[i]; std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); os << "\"" << cname << "::" << vname << "\""; if (i + 1 < data.count) { os << ", "; } } os << " ]\n"; } /// /// @brief Convert section layout data to a JSON-like string. /// /// Intended exclusively for debugging and diagnostics. /// /// @param[in] data Section layout data /// /// @return JSON-style string representation /// inline std::string debug_convert_SectionLayoutData_to_json(const SectionLayoutData& data) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; std::ostringstream oss; oss << "{ \"SectionLayoutData\": { " << "\"sectionNumber\": " << data.sectionNumber << ", " << "\"templateNumber\": " << data.templateNumber << ", " << "\"count\": " << data.count << ", " << "\"variantIndices\": [ "; for (std::size_t i = 0; i < data.count; ++i) { oss << data.variantIndices[i]; if (i + 1 < data.count) { oss << ", "; } } oss << " ], \"variantNames\": [ "; for (std::size_t i = 0; i < data.count; ++i) { const std::size_t id = data.variantIndices[i]; oss << "\"" << GeneralRegistry::conceptNameArr[id] << "::" << GeneralRegistry::variantNameArr[id] << "\""; if (i + 1 < data.count) { oss << ", "; } } oss << " ] } }"; return oss.str(); } } // namespace debug } // namespace metkit::mars2grib::backend::sections::resolver metkit-1.20.2/src/metkit/mars2grib/backend/sections/resolver/CompressionMask.h0000664000175000017500000003240515246725757027626 0ustar alastairalastair/* * (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 CompressionMask.h /// @brief Section-specific mask used to normalize template signature keys. /// /// This header defines `CompressionMask`, an **internal, immutable semantic /// object** used by the section resolver to *filter and normalize* /// `TemplateSignatureKey` instances prior to lookup. /// /// -------------------------------------------------------------------------- /// /// @section compressionmask_what What is a compression mask? /// /// A compression mask is a **section-specific filter** applied to a /// `TemplateSignatureKey` in order to remove concept variants that are /// *irrelevant* for a given GRIB section. /// /// Not all concepts participate in all sections. If irrelevant variants /// were left in the key, every comparison against section-specific template /// definitions would fail, and the combinatorial space would explode. /// /// For example: /// - When resolving **Section 4**, concepts that never participate in /// Section 4 must be ignored. /// - Otherwise, template lookup would implicitly require considering /// combinations across *different sections*, which is semantically wrong. /// /// The compression mask ensures that only the variants that *can actually /// appear* in a given section contribute to key comparison. /// /// -------------------------------------------------------------------------- /// /// @section compressionmask_why Why is it needed? /// /// A `TemplateSignatureKey` represents the **full active concept state**. /// However: /// /// - Section template definitions are **section-local** /// - Key comparison must therefore be **section-local** /// /// The compression mask: /// - Removes variants that never appear in any recipe for the section /// - Normalizes keys so that lookup depends only on relevant information /// /// This step is **mandatory** for correct and efficient section resolution. /// /// -------------------------------------------------------------------------- /// /// @section compressionmask_how How is it computed? /// /// The compression mask is computed *once per section* from the section /// recipe payload, using a two-phase process. /// /// @subsection compressionmask_phase1 Phase 1: variant collection /// /// All resolved template entries for the section are scanned, and the set /// of **all variant identifiers that ever participate** in the section is /// collected. /// /// Variants that never appear in any recipe entry are marked as invalid/missing. /// /// @subsection compressionmask_phase2 Phase 2: index assignment /// /// The collected variants are assigned **dense compressed indices**. /// /// This step finalizes the mask by mapping: /// - irrelevant variants → `missing` /// - relevant variants → dense indices `[0, compressedSize)` /// /// The resulting mask is immutable and section-specific. /// /// -------------------------------------------------------------------------- /// /// @section compressionmask_sorting Order normalization vs encoding order /// /// A crucial design point is the distinction between: /// /// - **Variant identity** (used for template lookup) /// - **Variant order** (used for encoding) /// /// The compression mask supports two compression modes: /// /// - `compressKey` /// Produces a **sorted** compressed key. /// Sorting ensures that template lookup depends *only on which variants /// are present*, not on their order. /// /// - `compressUnsortedKey` /// Produces an **order-preserving** compressed key. /// This is useful when order must be retained. /// /// Importantly: /// - **Order is intentionally ignored during lookup** /// - **Order is still preserved in the payload** /// /// Encoding relies on the ordered variant list stored in /// `ResolvedTemplateData`, not on the compressed key. /// /// This asymmetry is deliberate and fundamental to the resolver design. /// /// -------------------------------------------------------------------------- /// /// @section compressionmask_relationship Relationship with Select and Recipes /// /// - `Select` defines: /// - Which variants are admissible /// - The order in which concepts must be encoded /// /// - `Recipe` and `ResolvedTemplateData` preserve this order /// /// - `CompressionMask`: /// - Ignores order /// - Retains only variant identity /// - Operates strictly at the lookup level /// /// Together, these components allow: /// - Declarative, ordered encoding /// - Order-independent, efficient template matching /// /// @note /// This type is an internal implementation detail of the resolver and is not /// part of the public API. /// /// @ingroup mars2grib_backend_section_resolver /// #pragma once // System includes #include #include #include #include #include // Project includes #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h" #include "metkit/mars2grib/backend/sections/resolver/TemplateSignatureKey.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::sections::resolver::detail { /// /// @brief Section-specific compression mask for template signature keys. /// /// `CompressionMask` is an immutable object that filters and normalizes /// `TemplateSignatureKey` instances so that they can be compared and /// matched against section-local template definitions. /// /// The mask is: /// - Computed once per section /// - Derived solely from the section recipe payload /// - Independent of the active concept state /// class CompressionMask { public: /// Registry providing global variant identifiers using GeneralRegistry = metkit::mars2grib::backend::concepts_::GeneralRegistry; /// /// @brief Number of variants retained after compression. /// std::size_t compressedSize() const noexcept { return compressedSize_; } /// /// @brief Compress a signature key while preserving variant order. /// /// Irrelevant variants are removed, but the relative order of the /// remaining variants is preserved. /// /// @param[in] in Input signature key /// /// @return Compressed key with preserved order /// TemplateSignatureKey compressUnsortedKey(const TemplateSignatureKey& in) const noexcept { TemplateSignatureKey out{}; out.size = 0; for (std::size_t i = 0; i < in.size; ++i) { const std::size_t v = in.data[i]; if (mask_[v] != GeneralRegistry::missing) { out.data[out.size++] = v; } } return out; } /// /// @brief Compress a signature key and normalize its order. /// /// Irrelevant variants are removed and the remaining variants are /// inserted into the output key in sorted order. /// /// This guarantees that key comparison depends only on *which variants /// are present*, not on their order. /// /// @param[in] in Input signature key /// /// @return Sorted, compressed key /// TemplateSignatureKey compressKey(const TemplateSignatureKey& in) const noexcept { TemplateSignatureKey out{}; out.size = 0; for (std::size_t i = 0; i < in.size; ++i) { const std::size_t v = in.data[i]; if (mask_[v] == GeneralRegistry::missing) { continue; } std::size_t j = out.size; while (j > 0 && out.data[j - 1] > v) { out.data[j] = out.data[j - 1]; --j; } out.data[j] = v; ++out.size; } return out; } /// /// @brief Print a human-readable description of the compression mask. /// /// @param[in] prefix Line prefix used for indentation /// @param[out] os Output stream /// void debug_print(const std::string& prefix, std::ostream& os) const { using metkit::mars2grib::backend::concepts_::GeneralRegistry; os << prefix << " :: Compressed size: " << compressedSize_ << "\n"; os << prefix << " :: Compression mask indices: [ "; for (std::size_t v = 0; v < mask_.size(); ++v) { os << mask_[v]; if (v + 1 < mask_.size()) { os << ", "; } } os << " ]\n"; os << prefix << " :: Compression mask names: [ "; for (std::size_t v = 0; v < mask_.size(); ++v) { std::size_t id = mask_[v]; if (id != GeneralRegistry::missing) { std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); os << "\"" << cname << "::" << vname << "\""; } else { os << "\"invalid\""; } if (v + 1 < mask_.size()) { os << ", "; } } os << " ]" << std::endl; } /// /// @brief Convert the compression mask to a JSON-like string. /// /// This method produces a diagnostic, human-readable representation of the /// compression mask. The output is intended exclusively for debugging and /// introspection and is not guaranteed to be valid JSON. /// /// The representation exposes: /// - the compressed size /// - the full variant-to-compressed-index mapping /// /// Variants mapped to `missing` explicitly indicate concepts that never /// participate in the section and are therefore removed during key compression. /// /// @return JSON-style string describing the compression mask /// std::string debug_to_json() const { using metkit::mars2grib::backend::concepts_::GeneralRegistry; std::ostringstream oss; oss << "{ \"CompressionMask\": { " << "\"compressedSize\": " << compressedSize_ << ", " << "\"maskIndices\": [ "; for (std::size_t v = 0; v < mask_.size(); ++v) { oss << mask_[v]; if (v + 1 < mask_.size()) { oss << ", "; } } oss << " ], "; oss << "\"maskNames\": [ "; for (std::size_t v = 0; v < mask_.size(); ++v) { std::size_t id = mask_[v]; if (id != GeneralRegistry::missing) { std::string cname = std::string(GeneralRegistry::conceptNameArr[id]); std::string vname = std::string(GeneralRegistry::variantNameArr[id]); oss << "\"" << cname << "::" << vname << "\""; } else { oss << "\"invalid\""; } if (v + 1 < mask_.size()) { oss << ", "; } } oss << " ] } }"; return oss.str(); } private: /// Mapping from global variant identifier to compressed index or invalid const std::array mask_; /// Number of retained variants const std::size_t compressedSize_; CompressionMask(std::array&& mask, std::size_t compressedSize) : mask_(std::move(mask)), compressedSize_(compressedSize) {} friend CompressionMask make_CompressionMask_or_throw( const std::vector&); }; /// /// @brief Build a compression mask from section recipe payload. /// /// This factory computes a section-specific compression mask by scanning /// all resolved template entries and collecting the set of variants that /// ever participate in the section. /// /// @param[in] payload Resolved template payload for the section /// /// @return Fully constructed compression mask /// /// @throws Mars2GribGenericException /// If the payload is empty or inconsistent /// inline CompressionMask make_CompressionMask_or_throw( const std::vector& payload) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (payload.empty()) { throw Mars2GribGenericException("CompressionMask: empty payload", Here()); } std::array mask{}; mask.fill(0); for (const auto& entry : payload) { for (std::size_t i = 0; i < entry.count; ++i) { const std::size_t v = entry.variantIndices[i]; if (v >= GeneralRegistry::NVariants) { throw Mars2GribGenericException("CompressionMask: variant index out of range", Here()); } ++mask[v]; } } std::size_t cnt = 0; for (std::size_t v = 0; v < GeneralRegistry::NVariants; ++v) { if (mask[v] == 0) { mask[v] = GeneralRegistry::missing; } else { mask[v] = cnt++; } } return CompressionMask{std::move(mask), cnt}; } } // namespace metkit::mars2grib::backend::sections::resolver::detail metkit-1.20.2/src/metkit/mars2grib/backend/tables/0000775000175000017500000000000015246725757022116 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/tables/timeUnits.h0000664000175000017500000001216115246725757024251 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB time units (Code Table 4.4). /// /// This enumeration represents the GRIB code values associated with /// time units as defined in GRIB2 Code Table 4.4. /// /// The numeric values map **directly** to the official GRIB code table /// and must not be changed manually. /// /// @section Source of truth /// GRIB2 Code Table 4.4: /// Units of time range /// /// @note /// This enum is a pure GRIB table representation. /// No semantic interpretation or policy decisions are encoded here. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: critical] /// - Generate this enum automatically from ecCodes GRIB tables /// to guarantee alignment with the runtime ecCodes version. /// enum class TimeUnit : long { Minute = 0, Hour = 1, Day = 2, Month = 3, Year = 4, Decade = 5, // 10 years Normal = 6, // 30 years Century = 7, // 100 years Hours3 = 10, Hours6 = 11, Hours12 = 12, Second = 13, Missing = 255 }; /// /// @brief Convert a symbolic time-unit name to a GRIB `TimeUnit`. /// /// Performs a strict mapping from a string identifier to the corresponding /// GRIB time unit code. /// /// Supported names: /// - "minute" /// - "hour" /// - "day" /// - "month" /// - "year" /// - "decade" /// - "normal" /// - "century" /// - "3h" /// - "6h" /// - "12h" /// - "second" /// - "missing" /// /// @param[in] name Symbolic name of the time unit /// /// @return Corresponding `TimeUnit` enumeration value /// /// @throws Mars2GribTableException /// If the name is not a supported GRIB time unit /// /// @note /// - Mapping is case-sensitive by design. /// - No normalization or aliasing is performed. /// inline TimeUnit name2enum_TimeUnit_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "minute") return TimeUnit::Minute; if (name == "hour") return TimeUnit::Hour; if (name == "day") return TimeUnit::Day; if (name == "month") return TimeUnit::Month; if (name == "year") return TimeUnit::Year; if (name == "decade") return TimeUnit::Decade; if (name == "normal") return TimeUnit::Normal; if (name == "century") return TimeUnit::Century; if (name == "3h") return TimeUnit::Hours3; if (name == "6h") return TimeUnit::Hours6; if (name == "12h") return TimeUnit::Hours12; if (name == "second") return TimeUnit::Second; if (name == "missing") return TimeUnit::Missing; std::string err = "Invalid TimeUnit name: actual='" + name + "', expected={minute,hour,day,month,year,decade,normal,century," "3h,6h,12h,second,missing}"; throw Mars2GribTableException(err, Here()); mars2gribUnreachable(); } /// /// @brief Convert a GRIB `TimeUnit` enumeration to its symbolic name. /// /// Performs a strict mapping from a GRIB time unit code to its /// canonical string representation. /// /// @param[in] value GRIB `TimeUnit` enumeration value /// /// @return Canonical symbolic name of the time unit /// /// @throws Mars2GribTableException /// If the enum value is not supported /// /// @note /// - Returned strings are stable and suitable for logging, YAML, /// diagnostics, and round-tripping via `name2enum_TimeUnit_or_throw`. /// inline std::string enum2name_TimeUnit_or_throw(TimeUnit value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TimeUnit::Minute: return "minute"; case TimeUnit::Hour: return "hour"; case TimeUnit::Day: return "day"; case TimeUnit::Month: return "month"; case TimeUnit::Year: return "year"; case TimeUnit::Decade: return "decade"; case TimeUnit::Normal: return "normal"; case TimeUnit::Century: return "century"; case TimeUnit::Hours3: return "3h"; case TimeUnit::Hours6: return "6h"; case TimeUnit::Hours12: return "12h"; case TimeUnit::Second: return "second"; case TimeUnit::Missing: return "missing"; } std::string err = "Invalid TimeUnit enum value: actual='" + std::to_string(static_cast(value)) + "'"; throw Mars2GribTableException(err, Here()); mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/backgroundProcess.h0000664000175000017500000002573315246725757025757 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB background process classification. /// /// This enumeration represents the GRIB code values associated with the /// `backgroundProcess` key in the Product Definition Section. /// /// Each enumerator corresponds to a distinct model configuration or /// post-processing workflow as defined by ECMWF conventions and encoded /// in GRIB local concepts. /// /// The numeric values of the enumerators map **directly** to the GRIB /// code table values and must not be changed manually. /// /// @note /// The value `255` usually corresponds to the GRIB *missing* value. /// In this specific case, it is semantically interpreted as the /// deterministic IFS workflow (`ifs`) for compatibility with existing /// production data. /// /// @important /// This enum is a **GRIB-level representation**, not a policy decision. /// All semantic validation, defaulting, and resolution logic must be /// implemented in the corresponding deduction functions. /// /// @section Source of truth /// The authoritative definition of supported background processes and /// their GRIB encodings is maintained in: /// /// `definitions/grib2/localConcepts/ecmf/modelNameConcept.def` /// /// This enumeration must remain consistent with that definition. /// /// @todo [owner: mds,dgov][scope: deduction][reason: correctness][prio: critical] /// - Replace this manually maintained enumeration with code generated /// automatically from ecCodes GRIB code tables / definitions. /// - The generation should occur at build or configure time (e.g. via a /// Python code-generation step) to prevent silent divergence between /// the encoder and the ecCodes library. /// enum class BackgroundProcess : long { aifs_single = 1, aifs_ens = 2, aifs_single_mse = 3, aifs_ens_crps = 4, aifs_ens_diff = 5, aifs_compo_single = 6, aifs_compo_ens = 7, aifs_compo_single_mse = 8, aifs_compo_ens_crps = 9, aifs_subs = 10, aifs_subs_crps = 11, ifs = 255 }; /// /// @brief Map a MARS model identifier to a GRIB `BackgroundProcess` enumeration. /// /// This function converts a string-based model identifier, typically obtained /// from the MARS key `mars::model`, into the corresponding GRIB /// `BackgroundProcess` enumeration value. /// /// The mapping is explicit and strict. Only the following identifiers are /// supported: /// /// - `"aifs-single"` → `BackgroundProcess::aifs_single` // 1 /// - `"aifs-ens"` → `BackgroundProcess::aifs_ens` // 2 /// - `"aifs-single-mse"` → `BackgroundProcess::aifs_single_mse` // 3 /// - `"aifs-ens-crps"` → `BackgroundProcess::aifs_ens_crps` // 4 /// - `"aifs-ens-diff"` → `BackgroundProcess::aifs_ens_diff` // 5 /// - `"aifs-compo-single"` → `BackgroundProcess::aifs_compo_single` // 6 /// - `"aifs-compo-ens"` → `BackgroundProcess::aifs_compo_ens` // 7 /// - `"aifs-compo-single-mse"` → `BackgroundProcess::aifs_compo_single_mse` // 8 /// - `"aifs-compo-ens-crps"` → `BackgroundProcess::aifs_compo_ens_crps` // 9 /// - `"aifs-subs"` → `BackgroundProcess::aifs_subs` // 10 /// - `"aifs-subs-crps"` → `BackgroundProcess::aifs_subs_crps` // 11 /// - `"IFS"` → `BackgroundProcess::ifs` // 255 /// /// Any other value is considered invalid and results in a deduction error. /// /// @param[in] value String value of the MARS `model` key to be mapped /// /// @return The corresponding `BackgroundProcess` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the provided string does not correspond to a supported /// MARS model identifier. /// /// @note /// - This function performs a **pure mapping** from MARS semantics to /// GRIB background process codes. /// - No implicit normalization, fallback, or defaulting is performed. /// /// @important /// The authoritative definition of valid model identifiers and their /// mapping to GRIB background process codes is maintained in: /// /// `definitions/grib2/localConcepts/ecmf/modelNameConcept.def` /// /// This function must remain consistent with that definition. /// /// @todo [owner: mds,dgov][scope: deduction][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes definitions to prevent divergence between software stacks. /// inline BackgroundProcess name2enum_BackgroundProcess_or_throw(const std::string& value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (value == "aifs-single") { return BackgroundProcess::aifs_single; } else if (value == "aifs-ens") { return BackgroundProcess::aifs_ens; } else if (value == "aifs-single-mse") { return BackgroundProcess::aifs_single_mse; } else if (value == "aifs-ens-crps") { return BackgroundProcess::aifs_ens_crps; } else if (value == "aifs-ens-diff") { return BackgroundProcess::aifs_ens_diff; } else if (value == "aifs-compo-single") { return BackgroundProcess::aifs_compo_single; } else if (value == "aifs-compo-ens") { return BackgroundProcess::aifs_compo_ens; } else if (value == "aifs-compo-single-mse") { return BackgroundProcess::aifs_compo_single_mse; } else if (value == "aifs-compo-ens-crps") { return BackgroundProcess::aifs_compo_ens_crps; } else if (value == "aifs-subs") { return BackgroundProcess::aifs_subs; } else if (value == "aifs-subs-crps") { return BackgroundProcess::aifs_subs_crps; } else if (value == "IFS") { return BackgroundProcess::ifs; } else { std::string errMsg = "Invalid BackgroundProcess value: "; errMsg += "actual='" + value + "', "; errMsg += "expected={'aifs-single', 'aifs-ens', 'aifs-single-mse', 'aifs-ens-crps', 'aifs-ens-diff', " "'aifs-compo-single', 'aifs-compo-ens', 'aifs-compo-single-mse', 'aifs-compo-ens-crps', " "'aifs-subs', 'aifs-subs-crps', 'IFS'}"; throw Mars2GribTableException(errMsg, Here()); } // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Map a GRIB `BackgroundProcess` enumeration to its canonical MARS model identifier. /// /// This function converts a GRIB-level `BackgroundProcess` enumeration value /// into the corresponding canonical string identifier used by MARS /// (e.g. `mars::model`). /// /// The mapping is explicit and strict. Only officially supported enumeration /// values are accepted. /// /// Supported mappings: /// /// - `BackgroundProcess::aifs_single` → `"aifs-single"` // 1 /// - `BackgroundProcess::aifs_ens` → `"aifs-ens"` // 2 /// - `BackgroundProcess::aifs_single_mse` → `"aifs-single-mse"` // 3 /// - `BackgroundProcess::aifs_ens_crps` → `"aifs-ens-crps"` // 4 /// - `BackgroundProcess::aifs_ens_diff` → `"aifs-ens-diff"` // 5 /// - `BackgroundProcess::aifs_compo_single` → `"aifs-compo-single"` // 6 /// - `BackgroundProcess::aifs_compo_ens` → `"aifs-compo-ens"` // 7 /// - `BackgroundProcess::aifs_compo_single_mse` → `"aifs-compo-single-mse"` // 8 /// - `BackgroundProcess::aifs_compo_ens_crps` → `"aifs-compo-ens-crps"` // 9 /// - `BackgroundProcess::aifs_subs` → `"aifs-subs"` // 10 /// - `BackgroundProcess::aifs_subs_crps` → `"aifs-subs-crps"` // 11 /// - `BackgroundProcess::ifs` → `"IFS"` // 255 /// /// Any other enumeration value is considered invalid and results in a /// table-mapping error. /// /// @param[in] value GRIB `BackgroundProcess` enumeration value /// /// @return Canonical string representation corresponding to the MARS /// `model` identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value does not correspond to a supported /// background process. /// /// @note /// - This function performs a **pure reverse mapping** of /// `mapto_BackgroundProcess_or_throw(const std::string&)`. /// - No implicit normalization, fallback, or defaulting is performed. /// /// @important /// The authoritative definition of background process identifiers and their /// string representations is maintained in: /// /// `definitions/grib2/localConcepts/ecmf/modelNameConcept.def` /// /// This function must remain strictly consistent with that definition. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes definitions to guarantee bidirectional consistency. /// inline std::string enum2name_BackgroundProcess_or_throw(BackgroundProcess value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case BackgroundProcess::aifs_single: return "aifs-single"; // 1 case BackgroundProcess::aifs_ens: return "aifs-ens"; // 2 case BackgroundProcess::aifs_single_mse: return "aifs-single-mse"; // 3 case BackgroundProcess::aifs_ens_crps: return "aifs-ens-crps"; // 4 case BackgroundProcess::aifs_ens_diff: return "aifs-ens-diff"; // 5 case BackgroundProcess::aifs_compo_single: return "aifs-compo-single"; // 6 case BackgroundProcess::aifs_compo_ens: return "aifs-compo-ens"; // 7 case BackgroundProcess::aifs_compo_single_mse: return "aifs-compo-single-mse"; // 8 case BackgroundProcess::aifs_compo_ens_crps: return "aifs-compo-ens-crps"; // 9 case BackgroundProcess::aifs_subs: return "aifs-subs"; // 10 case BackgroundProcess::aifs_subs_crps: return "aifs-subs-crps"; // 11 case BackgroundProcess::ifs: return "IFS"; // 255 default: std::string errMsg = "Invalid BackgroundProcess enum value: "; errMsg += std::to_string(static_cast(value)); throw Mars2GribTableException(errMsg, Here()); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tables metkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfGeneratingProcess.h0000664000175000017500000002326715246725757027112 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB Code Table 4.3 – Type of generating process. /// /// This enumeration represents the GRIB2 *Type of Generating Process* /// as defined in Section 4, Code Table 3. /// /// The numeric values map **directly** to the official GRIB code table /// and must not be changed manually. /// /// @section Source of truth /// GRIB2 Code Table 4.3: /// Type of generating process /// /// @note /// This enum is a pure GRIB table representation. /// No semantic interpretation or policy decisions are encoded here. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: critical] /// - Generate this enum automatically from ecCodes GRIB tables /// to guarantee alignment with the runtime ecCodes version. /// enum class TypeOfGeneratingProcess : long { Analysis = 0, Initialization = 1, Forecast = 2, BiasCorrectedForecast = 3, EnsembleForecast = 4, ProbabilityForecast = 5, ForecastError = 6, AnalysisError = 7, Observation = 8, Climatological = 9, ProbabilityWeightedForecast = 10, BiasCorrectedEnsembleForecast = 11, PostProcessedAnalysis = 12, PostProcessedForecast = 13, Nowcast = 14, Hindcast = 15, PhysicalRetrieval = 16, RegressionAnalysis = 17, DifferenceBetweenTwoForecasts = 18, FirstGuess = 19, AnalysisIncrement = 20, InitializationIncrementForAnalysis = 21, BlendedForecast = 22, Missing = 255 }; /// /// @brief Map a canonical string identifier to a GRIB `TypeOfGeneratingProcess`. /// /// This function converts a string-based identifier into the corresponding /// GRIB `TypeOfGeneratingProcess` enumeration value. /// /// The mapping is explicit and strict. Only officially supported identifiers /// are accepted. /// /// @param[in] value Canonical string identifier /// /// @return Corresponding `TypeOfGeneratingProcess` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided string does not correspond to a supported /// generating process type. /// /// @note /// - This is a **pure table mapping**. /// - No normalization, fallback, or defaulting is performed. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes GRIB code tables. /// inline TypeOfGeneratingProcess name2enum_TypeOfGeneratingProcess_or_throw(const std::string& value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (value == "analysis") return TypeOfGeneratingProcess::Analysis; else if (value == "initialization") return TypeOfGeneratingProcess::Initialization; else if (value == "forecast") return TypeOfGeneratingProcess::Forecast; else if (value == "biasCorrectedForecast") return TypeOfGeneratingProcess::BiasCorrectedForecast; else if (value == "ensembleForecast") return TypeOfGeneratingProcess::EnsembleForecast; else if (value == "probabilityForecast") return TypeOfGeneratingProcess::ProbabilityForecast; else if (value == "forecastError") return TypeOfGeneratingProcess::ForecastError; else if (value == "analysisError") return TypeOfGeneratingProcess::AnalysisError; else if (value == "observation") return TypeOfGeneratingProcess::Observation; else if (value == "climatological") return TypeOfGeneratingProcess::Climatological; else if (value == "probabilityWeightedForecast") return TypeOfGeneratingProcess::ProbabilityWeightedForecast; else if (value == "biasCorrectedEnsembleForecast") return TypeOfGeneratingProcess::BiasCorrectedEnsembleForecast; else if (value == "postProcessedAnalysis") return TypeOfGeneratingProcess::PostProcessedAnalysis; else if (value == "postProcessedForecast") return TypeOfGeneratingProcess::PostProcessedForecast; else if (value == "nowcast") return TypeOfGeneratingProcess::Nowcast; else if (value == "hindcast") return TypeOfGeneratingProcess::Hindcast; else if (value == "physicalRetrieval") return TypeOfGeneratingProcess::PhysicalRetrieval; else if (value == "regressionAnalysis") return TypeOfGeneratingProcess::RegressionAnalysis; else if (value == "differenceBetweenTwoForecasts") return TypeOfGeneratingProcess::DifferenceBetweenTwoForecasts; else if (value == "firstGuess") return TypeOfGeneratingProcess::FirstGuess; else if (value == "analysisIncrement") return TypeOfGeneratingProcess::AnalysisIncrement; else if (value == "initializationIncrementForAnalysis") return TypeOfGeneratingProcess::InitializationIncrementForAnalysis; else if (value == "blendedForecast") return TypeOfGeneratingProcess::BlendedForecast; else if (value == "missing") return TypeOfGeneratingProcess::Missing; else { std::string errMsg = "Invalid TypeOfGeneratingProcess value: "; errMsg += "actual='" + value + "'"; throw Mars2GribTableException(errMsg, Here()); } mars2gribUnreachable(); } /// /// @brief Map a GRIB `TypeOfGeneratingProcess` enumeration to its canonical string identifier. /// /// This function converts a GRIB-level `TypeOfGeneratingProcess` enumeration /// value into its canonical string representation. /// /// @param[in] value GRIB `TypeOfGeneratingProcess` enumeration value /// /// @return Canonical string identifier corresponding to the enumeration /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is not supported. /// /// @note /// - This function is the strict inverse of /// `mapto_TypeOfGeneratingProcess_or_throw(const std::string&)`. /// - No fallback or defaulting is performed. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes GRIB code tables. /// inline std::string enum2name_TypeOfGeneratingProcess_or_throw(TypeOfGeneratingProcess value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfGeneratingProcess::Analysis: return "analysis"; case TypeOfGeneratingProcess::Initialization: return "initialization"; case TypeOfGeneratingProcess::Forecast: return "forecast"; case TypeOfGeneratingProcess::BiasCorrectedForecast: return "biasCorrectedForecast"; case TypeOfGeneratingProcess::EnsembleForecast: return "ensembleForecast"; case TypeOfGeneratingProcess::ProbabilityForecast: return "probabilityForecast"; case TypeOfGeneratingProcess::ForecastError: return "forecastError"; case TypeOfGeneratingProcess::AnalysisError: return "analysisError"; case TypeOfGeneratingProcess::Observation: return "observation"; case TypeOfGeneratingProcess::Climatological: return "climatological"; case TypeOfGeneratingProcess::ProbabilityWeightedForecast: return "probabilityWeightedForecast"; case TypeOfGeneratingProcess::BiasCorrectedEnsembleForecast: return "biasCorrectedEnsembleForecast"; case TypeOfGeneratingProcess::PostProcessedAnalysis: return "postProcessedAnalysis"; case TypeOfGeneratingProcess::PostProcessedForecast: return "postProcessedForecast"; case TypeOfGeneratingProcess::Nowcast: return "nowcast"; case TypeOfGeneratingProcess::Hindcast: return "hindcast"; case TypeOfGeneratingProcess::PhysicalRetrieval: return "physicalRetrieval"; case TypeOfGeneratingProcess::RegressionAnalysis: return "regressionAnalysis"; case TypeOfGeneratingProcess::DifferenceBetweenTwoForecasts: return "differenceBetweenTwoForecasts"; case TypeOfGeneratingProcess::FirstGuess: return "firstGuess"; case TypeOfGeneratingProcess::AnalysisIncrement: return "analysisIncrement"; case TypeOfGeneratingProcess::InitializationIncrementForAnalysis: return "initializationIncrementForAnalysis"; case TypeOfGeneratingProcess::BlendedForecast: return "blendedForecast"; case TypeOfGeneratingProcess::Missing: return "missing"; default: std::string errMsg = "Invalid TypeOfGeneratingProcess enum value: "; errMsg += std::to_string(static_cast(value)); throw Mars2GribTableException(errMsg, Here()); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfInterval.h0000664000175000017500000001663615246725757025256 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief Type of interval. /// /// This enumeration represents GRIB code values defining how an interval /// is interpreted with respect to its first and second limits. /// /// The numeric values map **directly** to ecCodes GRIB table 4.91 /// and must not be changed manually. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @important /// This enum is a **GRIB-table representation only**. /// No policy, defaulting, or deduction logic belongs here. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enum and all conversion helpers automatically from /// ecCodes definitions at build time. /// enum class TypeOfInterval : long { SmallerThanFirstLimit = 0, GreaterThanSecondLimit = 1, BetweenFirstInclusiveSecondExclusive = 2, GreaterThanFirstLimit = 3, SmallerThanSecondLimit = 4, SmallerOrEqualFirstLimit = 5, GreaterOrEqualSecondLimit = 6, BetweenFirstInclusiveSecondInclusive = 7, GreaterOrEqualFirstLimit = 8, SmallerOrEqualSecondLimit = 9, BetweenFirstExclusiveSecondInclusive = 10, EqualFirstLimit = 11, Missing = 255 }; /// /// @brief Convert `TypeOfInterval` to its canonical name. /// /// @param[in] value Enumeration value /// /// @return Canonical string name /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is invalid. /// inline std::string enum2name_TypeOfInterval_or_throw(TypeOfInterval value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfInterval::SmallerThanFirstLimit: return "smaller-than-first-limit"; case TypeOfInterval::GreaterThanSecondLimit: return "greater-than-second-limit"; case TypeOfInterval::BetweenFirstInclusiveSecondExclusive: return "between-first-inclusive-second-exclusive"; case TypeOfInterval::GreaterThanFirstLimit: return "greater-than-first-limit"; case TypeOfInterval::SmallerThanSecondLimit: return "smaller-than-second-limit"; case TypeOfInterval::SmallerOrEqualFirstLimit: return "smaller-or-equal-first-limit"; case TypeOfInterval::GreaterOrEqualSecondLimit: return "greater-or-equal-second-limit"; case TypeOfInterval::BetweenFirstInclusiveSecondInclusive: return "between-first-inclusive-second-inclusive"; case TypeOfInterval::GreaterOrEqualFirstLimit: return "greater-or-equal-first-limit"; case TypeOfInterval::SmallerOrEqualSecondLimit: return "smaller-or-equal-second-limit"; case TypeOfInterval::BetweenFirstExclusiveSecondInclusive: return "between-first-exclusive-second-inclusive"; case TypeOfInterval::EqualFirstLimit: return "equal-first-limit"; case TypeOfInterval::Missing: return "missing"; default: throw Mars2GribTableException("Invalid TypeOfInterval enum value", Here()); } } /// /// @brief Convert a canonical name to `TypeOfInterval`. /// /// @param[in] name Canonical string name /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the name is not recognized. /// inline TypeOfInterval name2enum_TypeOfInterval_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "smaller-than-first-limit") return TypeOfInterval::SmallerThanFirstLimit; else if (name == "greater-than-second-limit") return TypeOfInterval::GreaterThanSecondLimit; else if (name == "between-first-inclusive-second-exclusive") return TypeOfInterval::BetweenFirstInclusiveSecondExclusive; else if (name == "greater-than-first-limit") return TypeOfInterval::GreaterThanFirstLimit; else if (name == "smaller-than-second-limit") return TypeOfInterval::SmallerThanSecondLimit; else if (name == "smaller-or-equal-first-limit") return TypeOfInterval::SmallerOrEqualFirstLimit; else if (name == "greater-or-equal-second-limit") return TypeOfInterval::GreaterOrEqualSecondLimit; else if (name == "between-first-inclusive-second-inclusive") return TypeOfInterval::BetweenFirstInclusiveSecondInclusive; else if (name == "greater-or-equal-first-limit") return TypeOfInterval::GreaterOrEqualFirstLimit; else if (name == "smaller-or-equal-second-limit") return TypeOfInterval::SmallerOrEqualSecondLimit; else if (name == "between-first-exclusive-second-inclusive") return TypeOfInterval::BetweenFirstExclusiveSecondInclusive; else if (name == "equal-first-limit") return TypeOfInterval::EqualFirstLimit; else if (name == "missing") return TypeOfInterval::Missing; else throw Mars2GribTableException("Invalid TypeOfInterval name: '" + name + "'", Here()); } /// /// @brief Convert a numeric GRIB code to `TypeOfInterval`. /// /// @param[in] value Numeric GRIB code /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the value is not defined in the GRIB table. /// inline TypeOfInterval long2enum_TypeOfInterval_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return TypeOfInterval::SmallerThanFirstLimit; case 1: return TypeOfInterval::GreaterThanSecondLimit; case 2: return TypeOfInterval::BetweenFirstInclusiveSecondExclusive; case 3: return TypeOfInterval::GreaterThanFirstLimit; case 4: return TypeOfInterval::SmallerThanSecondLimit; case 5: return TypeOfInterval::SmallerOrEqualFirstLimit; case 6: return TypeOfInterval::GreaterOrEqualSecondLimit; case 7: return TypeOfInterval::BetweenFirstInclusiveSecondInclusive; case 8: return TypeOfInterval::GreaterOrEqualFirstLimit; case 9: return TypeOfInterval::SmallerOrEqualSecondLimit; case 10: return TypeOfInterval::BetweenFirstExclusiveSecondInclusive; case 11: return TypeOfInterval::EqualFirstLimit; case 255: return TypeOfInterval::Missing; default: throw Mars2GribTableException( "Invalid TypeOfInterval numeric value: actual='" + std::to_string(value) + "', expected={0..11,255}", Here()); } } } // namespace metkit::mars2grib::backend::tables metkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfEnsembleForecast.h0000664000175000017500000002111515246725757026677 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB Type of Ensemble Forecast. /// /// This enumeration represents the GRIB code values defined in /// **GRIB2 Code Table 4.6 – Type of ensemble forecast**. /// /// The values describe how a forecast member relates to the ensemble /// generation strategy (control, perturbation type, multi-model, etc.). /// /// The numeric values of the enumerators map **directly** to the GRIB /// code table and must not be modified manually. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @important /// This enum is a **GRIB-level representation** only. /// No policy, deduction, or defaulting logic must be embedded here. /// All semantic decisions must be implemented in the corresponding /// deduction functions. /// /// @section Source of truth /// The authoritative definition of this table is: /// /// GRIB2 Code Table 4.6 – Type of ensemble forecast /// /// as implemented in ecCodes. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enum and its conversion helpers automatically from /// ecCodes GRIB code tables at build or configure time to avoid drift. /// enum class TypeOfEnsembleForecast : long { UnperturbedHighResControl = 0, UnperturbedLowResControl = 1, NegativelyPerturbed = 2, PositivelyPerturbed = 3, MultiModel = 4, Unperturbed = 5, Perturbed = 6, InitialConditionsPerturbations = 7, ModelPhysicsPerturbations = 8, InitialAndModelPhysicsPerturbations = 9, Missing = 255 }; /// /// @brief Convert `TypeOfEnsembleForecast` to its canonical string name. /// /// The returned string corresponds to the symbolic GRIB meaning of the /// enumerator and is intended for logging, diagnostics, and validation. /// /// @param[in] value Enumeration value /// /// @return Canonical string representation /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enum value is not recognized. /// inline std::string enum2name_TypeOfEnsembleForecast_or_throw(TypeOfEnsembleForecast value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfEnsembleForecast::UnperturbedHighResControl: return "unperturbed-high-res-control"; case TypeOfEnsembleForecast::UnperturbedLowResControl: return "unperturbed-low-res-control"; case TypeOfEnsembleForecast::NegativelyPerturbed: return "negatively-perturbed"; case TypeOfEnsembleForecast::PositivelyPerturbed: return "positively-perturbed"; case TypeOfEnsembleForecast::MultiModel: return "multi-model"; case TypeOfEnsembleForecast::Unperturbed: return "unperturbed"; case TypeOfEnsembleForecast::Perturbed: return "perturbed"; case TypeOfEnsembleForecast::InitialConditionsPerturbations: return "initial-conditions-perturbations"; case TypeOfEnsembleForecast::ModelPhysicsPerturbations: return "model-physics-perturbations"; case TypeOfEnsembleForecast::InitialAndModelPhysicsPerturbations: return "initial-and-model-physics-perturbations"; case TypeOfEnsembleForecast::Missing: return "missing"; default: throw Mars2GribTableException("Invalid TypeOfEnsembleForecast enum value", Here()); } } /// /// @brief Convert a canonical string name to `TypeOfEnsembleForecast`. /// /// This function performs a strict mapping from a string identifier /// to the corresponding GRIB enumeration value. /// /// @param[in] name Canonical string name /// /// @return Corresponding `TypeOfEnsembleForecast` value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided name is not supported. /// inline TypeOfEnsembleForecast name2enum_TypeOfEnsembleForecast_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "unperturbed-high-res-control") { return TypeOfEnsembleForecast::UnperturbedHighResControl; } else if (name == "unperturbed-low-res-control") { return TypeOfEnsembleForecast::UnperturbedLowResControl; } else if (name == "negatively-perturbed") { return TypeOfEnsembleForecast::NegativelyPerturbed; } else if (name == "positively-perturbed") { return TypeOfEnsembleForecast::PositivelyPerturbed; } else if (name == "multi-model") { return TypeOfEnsembleForecast::MultiModel; } else if (name == "unperturbed") { return TypeOfEnsembleForecast::Unperturbed; } else if (name == "perturbed") { return TypeOfEnsembleForecast::Perturbed; } else if (name == "initial-conditions-perturbations") { return TypeOfEnsembleForecast::InitialConditionsPerturbations; } else if (name == "model-physics-perturbations") { return TypeOfEnsembleForecast::ModelPhysicsPerturbations; } else if (name == "initial-and-model-physics-perturbations") { return TypeOfEnsembleForecast::InitialAndModelPhysicsPerturbations; } else if (name == "missing") { return TypeOfEnsembleForecast::Missing; } else { throw Mars2GribTableException("Invalid TypeOfEnsembleForecast name: '" + name + "'", Here()); } } /// /// @brief Convert a numeric GRIB code to `TypeOfEnsembleForecast`. /// /// This function validates and converts a raw numeric value associated /// with **GRIB2 Code Table 4.6 – Type of ensemble forecast** into the /// corresponding `TypeOfEnsembleForecast` enumeration. /// /// Only officially defined GRIB values are accepted. Any other value /// is considered invalid and results in an exception. /// /// @param[in] value Numeric GRIB code /// /// @return Corresponding `TypeOfEnsembleForecast` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided value does not correspond to a valid /// GRIB Code Table 4.6 entry. /// /// @note /// - The value `255` corresponds to the GRIB *missing* value. /// - No implicit normalization, fallback, or defaulting is performed. /// /// @important /// This function performs **pure table validation**. /// Any semantic interpretation must be handled by the calling deduction. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes GRIB code tables. /// inline TypeOfEnsembleForecast long2enum_TypeOfEnsembleForecast_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return TypeOfEnsembleForecast::UnperturbedHighResControl; case 1: return TypeOfEnsembleForecast::UnperturbedLowResControl; case 2: return TypeOfEnsembleForecast::NegativelyPerturbed; case 3: return TypeOfEnsembleForecast::PositivelyPerturbed; case 4: return TypeOfEnsembleForecast::MultiModel; case 5: return TypeOfEnsembleForecast::Unperturbed; case 6: return TypeOfEnsembleForecast::Perturbed; case 7: return TypeOfEnsembleForecast::InitialConditionsPerturbations; case 8: return TypeOfEnsembleForecast::ModelPhysicsPerturbations; case 9: return TypeOfEnsembleForecast::InitialAndModelPhysicsPerturbations; case 255: return TypeOfEnsembleForecast::Missing; default: throw Mars2GribTableException("Invalid TypeOfEnsembleForecast numeric value: actual='" + std::to_string(value) + "', expected={0..9,255}", Here()); } } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/shapeOfTheReferenceSystem.h0000664000175000017500000001774015246725757027352 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief Shape of the reference system. /// /// This enumeration represents GRIB code values defining the geometric /// model of the Earth (or Sun) and the associated reference system. /// /// The numeric values map **directly** to ecCodes GRIB table 3.2 /// and must not be changed manually. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @important /// This enum is a **GRIB-table representation only**. /// No policy, defaulting, or deduction logic belongs here. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enum and all conversion helpers automatically from /// ecCodes definitions at build time. /// enum class ShapeOfTheReferenceSystem : long { EarthSphericalRadius6367470 = 0, EarthSphericalRadiusSpecified = 1, EarthOblateIAU1965 = 2, EarthOblateAxesKmSpecified = 3, EarthOblateIAGGRS80 = 4, EarthWGS84 = 5, EarthSphericalRadius6371229 = 6, EarthOblateAxesMetersSpecified = 7, EarthSphericalRadius6371200WGS84Datum = 8, EarthOSGB1936Airy1830 = 9, EarthWGS84CorrectedGeomagnetic = 10, SunSphericalStonyhurst = 11, Missing = 255 }; /// /// @brief Convert `ShapeOfTheReferenceSystem` to its canonical name. /// /// @param[in] value Enumeration value /// /// @return Canonical string name /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is invalid. /// inline std::string enum2name_ShapeOfTheReferenceSystem_or_throw(ShapeOfTheReferenceSystem value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case ShapeOfTheReferenceSystem::EarthSphericalRadius6367470: return "earth-spherical-radius-6367470"; case ShapeOfTheReferenceSystem::EarthSphericalRadiusSpecified: return "earth-spherical-radius-specified"; case ShapeOfTheReferenceSystem::EarthOblateIAU1965: return "earth-oblate-iau-1965"; case ShapeOfTheReferenceSystem::EarthOblateAxesKmSpecified: return "earth-oblate-axes-km-specified"; case ShapeOfTheReferenceSystem::EarthOblateIAGGRS80: return "earth-oblate-iag-grs80"; case ShapeOfTheReferenceSystem::EarthWGS84: return "earth-wgs84"; case ShapeOfTheReferenceSystem::EarthSphericalRadius6371229: return "earth-spherical-radius-6371229"; case ShapeOfTheReferenceSystem::EarthOblateAxesMetersSpecified: return "earth-oblate-axes-m-specified"; case ShapeOfTheReferenceSystem::EarthSphericalRadius6371200WGS84Datum: return "earth-spherical-radius-6371200-wgs84-datum"; case ShapeOfTheReferenceSystem::EarthOSGB1936Airy1830: return "earth-osgb1936-airy1830"; case ShapeOfTheReferenceSystem::EarthWGS84CorrectedGeomagnetic: return "earth-wgs84-corrected-geomagnetic"; case ShapeOfTheReferenceSystem::SunSphericalStonyhurst: return "sun-spherical-stonyhurst"; case ShapeOfTheReferenceSystem::Missing: return "missing"; default: throw Mars2GribTableException("Invalid ShapeOfTheReferenceSystem enum value", Here()); } } /// /// @brief Convert a canonical name to `ShapeOfTheReferenceSystem`. /// /// @param[in] name Canonical string name /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the name is not recognized. /// inline ShapeOfTheReferenceSystem name2enum_ShapeOfTheReferenceSystem_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "earth-spherical-radius-6367470") return ShapeOfTheReferenceSystem::EarthSphericalRadius6367470; else if (name == "earth-spherical-radius-specified") return ShapeOfTheReferenceSystem::EarthSphericalRadiusSpecified; else if (name == "earth-oblate-iau-1965") return ShapeOfTheReferenceSystem::EarthOblateIAU1965; else if (name == "earth-oblate-axes-km-specified") return ShapeOfTheReferenceSystem::EarthOblateAxesKmSpecified; else if (name == "earth-oblate-iag-grs80") return ShapeOfTheReferenceSystem::EarthOblateIAGGRS80; else if (name == "earth-wgs84") return ShapeOfTheReferenceSystem::EarthWGS84; else if (name == "earth-spherical-radius-6371229") return ShapeOfTheReferenceSystem::EarthSphericalRadius6371229; else if (name == "earth-oblate-axes-m-specified") return ShapeOfTheReferenceSystem::EarthOblateAxesMetersSpecified; else if (name == "earth-spherical-radius-6371200-wgs84-datum") return ShapeOfTheReferenceSystem::EarthSphericalRadius6371200WGS84Datum; else if (name == "earth-osgb1936-airy1830") return ShapeOfTheReferenceSystem::EarthOSGB1936Airy1830; else if (name == "earth-wgs84-corrected-geomagnetic") return ShapeOfTheReferenceSystem::EarthWGS84CorrectedGeomagnetic; else if (name == "sun-spherical-stonyhurst") return ShapeOfTheReferenceSystem::SunSphericalStonyhurst; else if (name == "missing") return ShapeOfTheReferenceSystem::Missing; else throw Mars2GribTableException("Invalid ShapeOfTheReferenceSystem name: '" + name + "'", Here()); } /// /// @brief Convert a numeric GRIB code to `ShapeOfTheReferenceSystem`. /// /// @param[in] value Numeric GRIB code /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the value is not defined in the GRIB table. /// inline ShapeOfTheReferenceSystem long2enum_ShapeOfTheReferenceSystem_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return ShapeOfTheReferenceSystem::EarthSphericalRadius6367470; case 1: return ShapeOfTheReferenceSystem::EarthSphericalRadiusSpecified; case 2: return ShapeOfTheReferenceSystem::EarthOblateIAU1965; case 3: return ShapeOfTheReferenceSystem::EarthOblateAxesKmSpecified; case 4: return ShapeOfTheReferenceSystem::EarthOblateIAGGRS80; case 5: return ShapeOfTheReferenceSystem::EarthWGS84; case 6: return ShapeOfTheReferenceSystem::EarthSphericalRadius6371229; case 7: return ShapeOfTheReferenceSystem::EarthOblateAxesMetersSpecified; case 8: return ShapeOfTheReferenceSystem::EarthSphericalRadius6371200WGS84Datum; case 9: return ShapeOfTheReferenceSystem::EarthOSGB1936Airy1830; case 10: return ShapeOfTheReferenceSystem::EarthWGS84CorrectedGeomagnetic; case 11: return ShapeOfTheReferenceSystem::SunSphericalStonyhurst; case 255: return ShapeOfTheReferenceSystem::Missing; default: throw Mars2GribTableException("Invalid ShapeOfTheReferenceSystem numeric value: actual='" + std::to_string(value) + "', expected={0..11,255}", Here()); } } } // namespace metkit::mars2grib::backend::tables metkit-1.20.2/src/metkit/mars2grib/backend/tables/significanceOfReferenceTime.h0000664000175000017500000001457715246725757027652 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB significance of reference time. /// /// This enumeration represents the GRIB code table values associated with /// the `significanceOfReferenceTime` key in the Product Definition Section. /// /// The significance describes the semantic meaning of the reference time /// used in the GRIB message (e.g. analysis time, forecast start time, /// observation time). /// /// The numeric values map **directly** to the GRIB Code Table /// “Significance of reference time”. /// /// @important /// This enum is a **pure GRIB-level representation**. /// It does not encode policy decisions or deduction logic. /// All semantic resolution must be implemented in dedicated deduction /// functions. /// /// @section Source of truth /// GRIB2 Code Table 1.2: /// Significance of reference time /// /// @todo [owner: mival,mds,dgov][scope: tables][reason: correctness][prio: critical] /// - Generate this enumeration automatically from ecCodes definitions /// at build or configure time (e.g. via a Python code-generation step). /// - Avoid manual duplication of GRIB tables to prevent semantic drift /// between mars2grib and ecCodes. /// /// @note /// There is currently no known ecCodes API to set or retrieve these values /// via symbolic names; numeric values must therefore be used. /// enum class SignificanceOfReferenceTime : long { Analysis = 0, ForecastStart = 1, ForecastVerification = 2, ObservationTime = 3, LocalTime = 4, SimulationStart = 5, AssimilationStart = 6, Missing = 255 }; /// /// @brief Map a canonical string identifier to `SignificanceOfReferenceTime`. /// /// This function converts a canonical string representation into the /// corresponding GRIB `SignificanceOfReferenceTime` enumeration value. /// /// The mapping is explicit and strict. /// /// Supported identifiers: /// - `"analysis"` /// - `"forecastStart"` /// - `"forecastVerification"` /// - `"observationTime"` /// - `"localTime"` /// - `"simulationStart"` /// - `"missing"` /// /// @param[in] value Canonical string identifier /// /// @return Corresponding `SignificanceOfReferenceTime` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided string is not supported. /// /// @note /// - This is a **pure table mapping**. /// - No normalization, fallback, or defaulting is performed. /// /// @todo [owner: mival,mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes GRIB code tables. /// inline SignificanceOfReferenceTime name2enum_SignificanceOfReferenceTime_or_throw(const std::string& value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (value == "analysis") return SignificanceOfReferenceTime::Analysis; else if (value == "forecastStart") return SignificanceOfReferenceTime::ForecastStart; else if (value == "forecastVerification") return SignificanceOfReferenceTime::ForecastVerification; else if (value == "observationTime") return SignificanceOfReferenceTime::ObservationTime; else if (value == "localTime") return SignificanceOfReferenceTime::LocalTime; else if (value == "simulationStart") return SignificanceOfReferenceTime::SimulationStart; else if (value == "assimilationStart") return SignificanceOfReferenceTime::SimulationStart; else if (value == "missing") return SignificanceOfReferenceTime::Missing; else { std::string errMsg = "Invalid SignificanceOfReferenceTime value: "; errMsg += "actual='" + value + "'"; throw Mars2GribTableException(errMsg, Here()); } mars2gribUnreachable(); } /// /// @brief Convert `SignificanceOfReferenceTime` to its canonical string identifier. /// /// This function converts a GRIB-level `SignificanceOfReferenceTime` /// enumeration value into its canonical string representation. /// /// @param[in] value GRIB `SignificanceOfReferenceTime` enumeration value /// /// @return Canonical string identifier /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is not supported. /// /// @note /// - This function is the strict inverse of /// `mapto_SignificanceOfReferenceTime_or_throw(const std::string&)`. /// /// @todo [owner: mival,mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated directly from /// ecCodes GRIB code tables. /// inline std::string enum2name_SignificanceOfReferenceTime_or_throw(SignificanceOfReferenceTime value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case SignificanceOfReferenceTime::Analysis: return "analysis"; case SignificanceOfReferenceTime::ForecastStart: return "forecastStart"; case SignificanceOfReferenceTime::ForecastVerification: return "forecastVerification"; case SignificanceOfReferenceTime::ObservationTime: return "observationTime"; case SignificanceOfReferenceTime::LocalTime: return "localTime"; case SignificanceOfReferenceTime::SimulationStart: return "simulationStart"; case SignificanceOfReferenceTime::AssimilationStart: return "assimilationStart"; case SignificanceOfReferenceTime::Missing: return "missing"; default: { std::string errMsg = "Invalid SignificanceOfReferenceTime enum value: "; errMsg += std::to_string(static_cast(value)); throw Mars2GribTableException(errMsg, Here()); } } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h0000664000175000017500000001630715246725757030006 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB Table 4.10 – Type of statistical processing. /// /// This enumeration represents the GRIB code values used to describe /// the statistical processing applied over a time range. /// /// @section Source of truth /// GRIB2 Code Table 4.10 (ecCodes) /// /// @note /// - Numeric values map **directly** to GRIB code table entries. /// - Value `255` corresponds to GRIB *missing*. /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: critical] /// - Generate this enum and its mappings automatically from ecCodes /// definitions at build time to avoid divergence. /// enum class TypeOfStatisticalProcessing : long { Average = 0, Accumulation = 1, Maximum = 2, Minimum = 3, DifferenceEndMinusStart = 4, RootMeanSquare = 5, StandardDeviation = 6, Covariance = 7, DifferenceStartMinusEnd = 8, Ratio = 9, StandardizedAnomaly = 10, Summation = 11, ReturnPeriod = 12, Median = 13, Severity = 100, Mode = 101, IndexProcessing = 102, Missing = 255 }; /// /// @brief Convert a symbolic name to a GRIB TypeOfStatisticalProcessing. /// /// Supported names: /// /// - "average" /// - "accumulation" /// - "maximum" /// - "minimum" /// - "difference_end_minus_start" /// - "root_mean_square" /// - "standard_deviation" /// - "covariance" /// - "difference_start_minus_end" /// - "ratio" /// - "standardized_anomaly" /// - "summation" /// - "return_period" /// - "median" /// - "severity" /// - "mode" /// - "index_processing" /// - "missing" /// /// @param[in] name Canonical symbolic name /// /// @return Corresponding `TypeOfStatisticalProcessing` /// /// @throws Mars2GribTableException /// If the name is not supported /// /// @note /// - Case-sensitive /// - No aliasing or normalization /// inline TypeOfStatisticalProcessing name2enum_TypeOfStatisticalProcessing_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "average") return TypeOfStatisticalProcessing::Average; if (name == "accumulation") return TypeOfStatisticalProcessing::Accumulation; if (name == "maximum") return TypeOfStatisticalProcessing::Maximum; if (name == "minimum") return TypeOfStatisticalProcessing::Minimum; if (name == "difference_end_minus_start") return TypeOfStatisticalProcessing::DifferenceEndMinusStart; if (name == "root_mean_square") return TypeOfStatisticalProcessing::RootMeanSquare; if (name == "standard_deviation") return TypeOfStatisticalProcessing::StandardDeviation; if (name == "covariance") return TypeOfStatisticalProcessing::Covariance; if (name == "difference_start_minus_end") return TypeOfStatisticalProcessing::DifferenceStartMinusEnd; if (name == "ratio") return TypeOfStatisticalProcessing::Ratio; if (name == "standardized_anomaly") return TypeOfStatisticalProcessing::StandardizedAnomaly; if (name == "summation") return TypeOfStatisticalProcessing::Summation; if (name == "return_period") return TypeOfStatisticalProcessing::ReturnPeriod; if (name == "median") return TypeOfStatisticalProcessing::Median; if (name == "severity") return TypeOfStatisticalProcessing::Severity; if (name == "mode") return TypeOfStatisticalProcessing::Mode; if (name == "index_processing") return TypeOfStatisticalProcessing::IndexProcessing; if (name == "missing") return TypeOfStatisticalProcessing::Missing; std::string err = "Invalid TypeOfStatisticalProcessing name: actual='" + name + "', expected={average,accumulation,maximum,minimum," "difference_end_minus_start,root_mean_square,standard_deviation," "covariance,difference_start_minus_end,ratio,standardized_anomaly," "summation,return_period,median,severity,mode,index_processing,missing}"; throw Mars2GribTableException(err, Here()); mars2gribUnreachable(); } /// /// @brief Convert a GRIB TypeOfStatisticalProcessing to its symbolic name. /// /// @param[in] value GRIB statistical processing code /// /// @return Canonical symbolic name /// /// @throws Mars2GribTableException /// If the enum value is not supported /// /// @note /// - Returned strings are stable and suitable for round-tripping /// inline std::string enum2name_TypeOfStatisticalProcessing_or_throw(TypeOfStatisticalProcessing value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfStatisticalProcessing::Average: return "average"; case TypeOfStatisticalProcessing::Accumulation: return "accumulation"; case TypeOfStatisticalProcessing::Maximum: return "maximum"; case TypeOfStatisticalProcessing::Minimum: return "minimum"; case TypeOfStatisticalProcessing::DifferenceEndMinusStart: return "difference_end_minus_start"; case TypeOfStatisticalProcessing::RootMeanSquare: return "root_mean_square"; case TypeOfStatisticalProcessing::StandardDeviation: return "standard_deviation"; case TypeOfStatisticalProcessing::Covariance: return "covariance"; case TypeOfStatisticalProcessing::DifferenceStartMinusEnd: return "difference_start_minus_end"; case TypeOfStatisticalProcessing::Ratio: return "ratio"; case TypeOfStatisticalProcessing::StandardizedAnomaly: return "standardized_anomaly"; case TypeOfStatisticalProcessing::Summation: return "summation"; case TypeOfStatisticalProcessing::ReturnPeriod: return "return_period"; case TypeOfStatisticalProcessing::Median: return "median"; case TypeOfStatisticalProcessing::Severity: return "severity"; case TypeOfStatisticalProcessing::Mode: return "mode"; case TypeOfStatisticalProcessing::IndexProcessing: return "index_processing"; case TypeOfStatisticalProcessing::Missing: return "missing"; } std::string err = "Invalid TypeOfStatisticalProcessing enum value: actual='" + std::to_string(static_cast(value)) + "'"; throw Mars2GribTableException(err, Here()); mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfProcessedData.h0000664000175000017500000002270115246725757026201 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB classification of processed data products. /// /// This enumeration represents the GRIB code table values associated with /// *Type of processed data* (GRIB2, Code Table 1.4). /// /// Each enumerator describes the nature of the data contained in the GRIB /// message, distinguishing between analysis, forecast, ensemble components, /// observational products, and derived or experimental datasets. /// /// The numeric values of the enumerators map **directly** to the GRIB /// code table values and must not be changed manually. /// /// @important /// This enum is a **GRIB-level representation**, not a policy decision. /// All semantic validation, defaulting, and deduction logic must be handled /// in the corresponding deduction layer. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @section Source of truth /// The authoritative definition of this table is maintained by WMO / ecCodes: /// /// GRIB2 — Code Table 1.4 (Type of processed data) /// /// @todo [owner: mds,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enumeration automatically from ecCodes GRIB tables /// at build or configure time to avoid divergence across software stacks. /// enum class TypeOfProcessedData : long { AnalysisProducts = 0, ForecastProducts = 1, AnalysisAndForecastProducts = 2, ControlForecastProducts = 3, PerturbedForecastProducts = 4, ControlAndPerturbedForecastProducts = 5, ProcessedSatelliteObservations = 6, ProcessedRadarObservations = 7, EventProbability = 8, ExperimentalData = 9, MlBasedForecast = 10, Missing = 255 }; /// /// @brief Convert a symbolic name to `TypeOfProcessedData`. /// /// This function maps a string identifier to the corresponding /// `TypeOfProcessedData` enumeration value. /// /// The mapping is explicit and strict. Only supported names are accepted. /// /// @param[in] value Symbolic name of the processed data type /// /// @return Corresponding `TypeOfProcessedData` enumerator /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided name is not supported. /// /// @note /// - No normalization or fallback is performed. /// - Intended for configuration, testing, and diagnostics. /// inline TypeOfProcessedData name2enum_TypeOfProcessedData_or_throw(const std::string& value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (value == "an") return TypeOfProcessedData::AnalysisProducts; if (value == "fc") return TypeOfProcessedData::ForecastProducts; if (value == "af") return TypeOfProcessedData::AnalysisAndForecastProducts; if (value == "cf") return TypeOfProcessedData::ControlForecastProducts; if (value == "pf") return TypeOfProcessedData::PerturbedForecastProducts; if (value == "cp") return TypeOfProcessedData::ControlAndPerturbedForecastProducts; if (value == "sa") return TypeOfProcessedData::ProcessedSatelliteObservations; if (value == "ra") return TypeOfProcessedData::ProcessedRadarObservations; if (value == "ep") return TypeOfProcessedData::EventProbability; if (value == "9") return TypeOfProcessedData::ExperimentalData; if (value == "10") return TypeOfProcessedData::MlBasedForecast; if (value == "missing") return TypeOfProcessedData::Missing; std::string errMsg = "Invalid TypeOfProcessedData name: "; errMsg += "actual='" + value + "'"; throw Mars2GribTableException(errMsg, Here()); mars2gribUnreachable(); } /// /// @brief Map a numeric GRIB value to `TypeOfProcessedData`. /// /// This function validates and converts a raw numeric GRIB value /// associated with the `typeOfProcessedData` key into the corresponding /// `TypeOfProcessedData` enumeration. /// /// The mapping is **explicit and strict**. Only numeric values defined by /// GRIB2 Code Table 1.4 and supported by this encoder are accepted. /// Any other value is considered invalid and results in an exception. /// /// @section Accepted values /// The following mappings are supported: /// /// - `0` → `AnalysisProducts` /// - `1` → `ForecastProducts` /// - `2` → `AnalysisAndForecastProducts` /// - `3` → `ControlForecastProducts` /// - `4` → `PerturbedForecastProducts` /// - `5` → `ControlAndPerturbedForecastProducts` /// - `6` → `ProcessedSatelliteObservations` /// - `7` → `ProcessedRadarObservations` /// - `8` → `EventProbability` /// - `9` → `ExperimentalData` /// - `10` → `MlBasedForecast` /// - `255` → `Missing` /// /// @param[in] value Raw numeric GRIB value to be validated and mapped /// /// @return The corresponding `TypeOfProcessedData` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the provided numeric value does not correspond to a supported /// GRIB code. /// /// @note /// - This function performs **no deduction** and **no defaulting**. /// - It must not be used to infer semantics from MARS metadata. /// - It is intended for validation of existing GRIB state or explicit overrides. /// /// @important /// This function is part of the **tables layer**. /// Policy decisions and semantic deductions must be implemented elsewhere. /// /// @section Source of truth /// WMO GRIB2 Code Table 1.4 – Type of processed data. /// /// @todo [owner: mival][scope: tables][reason: correctness][prio: medium] /// - Replace this hard-coded mapping with code generated automatically /// from ecCodes GRIB tables to prevent divergence between software stacks. /// inline TypeOfProcessedData long2enum_TypeOfProcessedData_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return TypeOfProcessedData::AnalysisProducts; case 1: return TypeOfProcessedData::ForecastProducts; case 2: return TypeOfProcessedData::AnalysisAndForecastProducts; case 3: return TypeOfProcessedData::ControlForecastProducts; case 4: return TypeOfProcessedData::PerturbedForecastProducts; case 5: return TypeOfProcessedData::ControlAndPerturbedForecastProducts; case 6: return TypeOfProcessedData::ProcessedSatelliteObservations; case 7: return TypeOfProcessedData::ProcessedRadarObservations; case 8: return TypeOfProcessedData::EventProbability; case 9: return TypeOfProcessedData::ExperimentalData; case 10: return TypeOfProcessedData::MlBasedForecast; case 255: return TypeOfProcessedData::Missing; default: throw Mars2GribTableException("Invalid GRIB value for `typeOfProcessedData`: " + std::to_string(value), Here()); } mars2gribUnreachable(); } /// /// @brief Convert `TypeOfProcessedData` to a symbolic name. /// /// This function maps a `TypeOfProcessedData` enumeration value to its /// canonical string representation. /// /// @param[in] value Enumeration value /// /// @return Canonical string name /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the value is not recognized. /// /// @note /// - Intended for logging, debugging, and diagnostics. /// - The returned names are stable identifiers, not user-facing text. /// inline std::string enum2name_TypeOfProcessedData_or_throw(TypeOfProcessedData value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfProcessedData::AnalysisProducts: return "an"; case TypeOfProcessedData::ForecastProducts: return "fc"; case TypeOfProcessedData::AnalysisAndForecastProducts: return "af"; case TypeOfProcessedData::ControlForecastProducts: return "cf"; case TypeOfProcessedData::PerturbedForecastProducts: return "pf"; case TypeOfProcessedData::ControlAndPerturbedForecastProducts: return "cp"; case TypeOfProcessedData::ProcessedSatelliteObservations: return "sa"; case TypeOfProcessedData::ProcessedRadarObservations: return "ra"; case TypeOfProcessedData::EventProbability: return "ep"; case TypeOfProcessedData::ExperimentalData: return "9"; case TypeOfProcessedData::MlBasedForecast: return "10"; case TypeOfProcessedData::Missing: return "missing"; } throw Mars2GribTableException("Invalid TypeOfProcessedData enum value", Here()); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/derivedForecast.h0000664000175000017500000001543715246725757025412 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief Ensemble statistical processing type. /// /// This enumeration represents GRIB code values defining how ensemble /// members are statistically processed to produce the encoded field. /// /// The numeric values map **directly** to the GRIB code table definitions /// and must not be changed manually. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @important /// This enum is a **GRIB-table representation only**. /// No policy, defaulting, or deduction logic belongs here. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enum and all conversion helpers automatically from /// ecCodes definitions at build time. /// enum class DerivedForecast : long { UnweightedMeanAllMembers = 0, WeightedMeanAllMembers = 1, StdDevClusterMean = 2, StdDevClusterMeanNormalized = 3, SpreadAllMembers = 4, LargeAnomalyIndexAllMembers = 5, UnweightedMeanClusterMembers = 6, InterquartileRange = 7, MinimumAllMembers = 8, MaximumAllMembers = 9, VarianceAllMembers = 10, Missing = 255 }; /// /// @brief Convert `DerivedForecast` to its canonical name. /// /// @param[in] value Enumeration value /// /// @return Canonical string name /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is invalid. /// inline std::string enum2name_DerivedForecast_or_throw(DerivedForecast value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case DerivedForecast::UnweightedMeanAllMembers: return "unweighted-mean-all-members"; case DerivedForecast::WeightedMeanAllMembers: return "weighted-mean-all-members"; case DerivedForecast::StdDevClusterMean: return "stddev-cluster-mean"; case DerivedForecast::StdDevClusterMeanNormalized: return "stddev-cluster-mean-normalized"; case DerivedForecast::SpreadAllMembers: return "spread-all-members"; case DerivedForecast::LargeAnomalyIndexAllMembers: return "large-anomaly-index-all-members"; case DerivedForecast::UnweightedMeanClusterMembers: return "unweighted-mean-cluster-members"; case DerivedForecast::InterquartileRange: return "interquartile-range"; case DerivedForecast::MinimumAllMembers: return "minimum-all-members"; case DerivedForecast::MaximumAllMembers: return "maximum-all-members"; case DerivedForecast::VarianceAllMembers: return "variance-all-members"; case DerivedForecast::Missing: return "missing"; default: throw Mars2GribTableException("Invalid DerivedForecast enum value", Here()); } } /// /// @brief Convert a canonical name to `DerivedForecast`. /// /// @param[in] name Canonical string name /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the name is not recognized. /// inline DerivedForecast name2enum_DerivedForecast_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "unweighted-mean-all-members") return DerivedForecast::UnweightedMeanAllMembers; else if (name == "weighted-mean-all-members") return DerivedForecast::WeightedMeanAllMembers; else if (name == "stddev-cluster-mean") return DerivedForecast::StdDevClusterMean; else if (name == "stddev-cluster-mean-normalized") return DerivedForecast::StdDevClusterMeanNormalized; else if (name == "spread-all-members") return DerivedForecast::SpreadAllMembers; else if (name == "large-anomaly-index-all-members") return DerivedForecast::LargeAnomalyIndexAllMembers; else if (name == "unweighted-mean-cluster-members") return DerivedForecast::UnweightedMeanClusterMembers; else if (name == "interquartile-range") return DerivedForecast::InterquartileRange; else if (name == "minimum-all-members") return DerivedForecast::MinimumAllMembers; else if (name == "maximum-all-members") return DerivedForecast::MaximumAllMembers; else if (name == "variance-all-members") return DerivedForecast::VarianceAllMembers; else if (name == "missing") return DerivedForecast::Missing; else throw Mars2GribTableException("Invalid DerivedForecast name: '" + name + "'", Here()); } /// /// @brief Convert a numeric GRIB code to `DerivedForecast`. /// /// @param[in] value Numeric GRIB code /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the value is not defined in the GRIB table. /// inline DerivedForecast long2enum_DerivedForecast_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return DerivedForecast::UnweightedMeanAllMembers; case 1: return DerivedForecast::WeightedMeanAllMembers; case 2: return DerivedForecast::StdDevClusterMean; case 3: return DerivedForecast::StdDevClusterMeanNormalized; case 4: return DerivedForecast::SpreadAllMembers; case 5: return DerivedForecast::LargeAnomalyIndexAllMembers; case 6: return DerivedForecast::UnweightedMeanClusterMembers; case 7: return DerivedForecast::InterquartileRange; case 8: return DerivedForecast::MinimumAllMembers; case 9: return DerivedForecast::MaximumAllMembers; case 10: return DerivedForecast::VarianceAllMembers; case 255: return DerivedForecast::Missing; default: throw Mars2GribTableException( "Invalid DerivedForecast numeric value: actual='" + std::to_string(value) + "', expected={0..10,255}", Here()); } } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/productionStatusOfProcessedData.h0000664000175000017500000002134115246725757030611 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief GRIB Production Status of Processed Data. /// /// This enumeration represents GRIB Code Table values describing the /// *production status* of the processed data, as encoded in the /// Product Definition Section. /// /// The values distinguish between operational, test, research, /// reanalysis, and programme-specific data streams (e.g. TIGGE, S2S, /// Copernicus, Destination Earth). /// /// The numeric values map **directly** to GRIB code table entries and /// must not be modified manually. /// /// @section Source of truth /// The authoritative definition of these values is maintained in /// the GRIB code tables distributed with ecCodes. /// /// @todo [owner: mival][scope: tables][reason: correctness][prio: high] /// - Generate this enumeration and all associated mappings /// automatically from ecCodes GRIB code tables at build time. /// - This avoids silent divergence between the encoder and the /// ecCodes version used at runtime. /// enum class ProductionStatusOfProcessedData : long { OperationalProducts = 0, OperationalTestProducts = 1, ResearchProducts = 2, ReanalysisProducts = 3, TiggeOperational = 4, TiggeTest = 5, S2SOperationalProducts = 6, S2STestProducts = 7, UerraOperational = 8, UerraTest = 9, CopernicusRegionalReanalysis = 10, CopernicusRegionalReanalysisTest = 11, DestinationEarth = 12, DestinationEarthTest = 13, Missing = 255 }; /// /// @brief Convert `ProductionStatusOfProcessedData` to its symbolic name. /// /// @param[in] value Enumeration value /// /// @return Canonical symbolic name corresponding to the enum value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is not recognised /// inline std::string enum2name_ProductionStatusOfProcessedData_or_throw(ProductionStatusOfProcessedData value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case ProductionStatusOfProcessedData::OperationalProducts: return "OperationalProducts"; case ProductionStatusOfProcessedData::OperationalTestProducts: return "OperationalTestProducts"; case ProductionStatusOfProcessedData::ResearchProducts: return "ResearchProducts"; case ProductionStatusOfProcessedData::ReanalysisProducts: return "ReanalysisProducts"; case ProductionStatusOfProcessedData::TiggeOperational: return "TiggeOperational"; case ProductionStatusOfProcessedData::TiggeTest: return "TiggeTest"; case ProductionStatusOfProcessedData::S2SOperationalProducts: return "S2SOperationalProducts"; case ProductionStatusOfProcessedData::S2STestProducts: return "S2STestProducts"; case ProductionStatusOfProcessedData::UerraOperational: return "UerraOperational"; case ProductionStatusOfProcessedData::UerraTest: return "UerraTest"; case ProductionStatusOfProcessedData::CopernicusRegionalReanalysis: return "CopernicusRegionalReanalysis"; case ProductionStatusOfProcessedData::CopernicusRegionalReanalysisTest: return "CopernicusRegionalReanalysisTest"; case ProductionStatusOfProcessedData::DestinationEarth: return "DestinationEarth"; case ProductionStatusOfProcessedData::DestinationEarthTest: return "DestinationEarthTest"; case ProductionStatusOfProcessedData::Missing: return "Missing"; default: throw Mars2GribTableException("Invalid ProductionStatusOfProcessedData enum value", Here()); } mars2gribUnreachable(); } /// /// @brief Convert a symbolic name to `ProductionStatusOfProcessedData`. /// /// @param[in] name Canonical symbolic name /// /// @return Corresponding `ProductionStatusOfProcessedData` enum value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the name is not recognised /// inline ProductionStatusOfProcessedData name2enum_ProductionStatusOfProcessedData_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "OperationalProducts") return ProductionStatusOfProcessedData::OperationalProducts; if (name == "OperationalTestProducts") return ProductionStatusOfProcessedData::OperationalTestProducts; if (name == "ResearchProducts") return ProductionStatusOfProcessedData::ResearchProducts; if (name == "ReanalysisProducts") return ProductionStatusOfProcessedData::ReanalysisProducts; if (name == "TiggeOperational") return ProductionStatusOfProcessedData::TiggeOperational; if (name == "TiggeTest") return ProductionStatusOfProcessedData::TiggeTest; if (name == "S2SOperationalProducts") return ProductionStatusOfProcessedData::S2SOperationalProducts; if (name == "S2STestProducts") return ProductionStatusOfProcessedData::S2STestProducts; if (name == "UerraOperational") return ProductionStatusOfProcessedData::UerraOperational; if (name == "UerraTest") return ProductionStatusOfProcessedData::UerraTest; if (name == "CopernicusRegionalReanalysis") return ProductionStatusOfProcessedData::CopernicusRegionalReanalysis; if (name == "CopernicusRegionalReanalysisTest") return ProductionStatusOfProcessedData::CopernicusRegionalReanalysisTest; if (name == "DestinationEarth") return ProductionStatusOfProcessedData::DestinationEarth; if (name == "DestinationEarthTest") return ProductionStatusOfProcessedData::DestinationEarthTest; if (name == "Missing") return ProductionStatusOfProcessedData::Missing; throw Mars2GribTableException("Invalid ProductionStatusOfProcessedData name: '" + name + "'", Here()); mars2gribUnreachable(); } /// /// @brief Convert a numeric GRIB code to `ProductionStatusOfProcessedData`. /// /// @param[in] value Numeric GRIB code table value /// /// @return Corresponding `ProductionStatusOfProcessedData` enum value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the numeric value is not valid /// inline ProductionStatusOfProcessedData long2enum_ProductionStatusOfProcessedData_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return ProductionStatusOfProcessedData::OperationalProducts; case 1: return ProductionStatusOfProcessedData::OperationalTestProducts; case 2: return ProductionStatusOfProcessedData::ResearchProducts; case 3: return ProductionStatusOfProcessedData::ReanalysisProducts; case 4: return ProductionStatusOfProcessedData::TiggeOperational; case 5: return ProductionStatusOfProcessedData::TiggeTest; case 6: return ProductionStatusOfProcessedData::S2SOperationalProducts; case 7: return ProductionStatusOfProcessedData::S2STestProducts; case 8: return ProductionStatusOfProcessedData::UerraOperational; case 9: return ProductionStatusOfProcessedData::UerraTest; case 10: return ProductionStatusOfProcessedData::CopernicusRegionalReanalysis; case 11: return ProductionStatusOfProcessedData::CopernicusRegionalReanalysisTest; case 12: return ProductionStatusOfProcessedData::DestinationEarth; case 13: return ProductionStatusOfProcessedData::DestinationEarthTest; case 255: return ProductionStatusOfProcessedData::Missing; default: throw Mars2GribTableException( "Invalid ProductionStatusOfProcessedData numeric value: " + std::to_string(value), Here()); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::tablesmetkit-1.20.2/src/metkit/mars2grib/backend/tables/typeOfTimeIntervals.h0000664000175000017500000001352615246725757026253 0ustar alastairalastair/* * (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. */ #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::tables { /// /// @brief Type of time intervals. /// /// This enumeration represents GRIB code values defining how successive /// time intervals are processed in time-dependent fields. /// /// The numeric values map **directly** to ecCodes GRIB table 4.11 /// and must not be changed manually. /// /// @note /// The value `255` corresponds to the GRIB *missing* value. /// /// @important /// This enum is a **GRIB-table representation only**. /// No policy, defaulting, or deduction logic belongs here. /// /// @todo [owner: mival,dgov][scope: tables][reason: correctness][prio: medium] /// - Generate this enum and all conversion helpers automatically from /// ecCodes definitions at build time. /// enum class TypeOfTimeIntervals : long { Reserved = 0, SameForecastTimeStartIncremented = 1, SameStartTimeForecastIncremented = 2, StartIncrementedForecastDecrementedConstantValid = 3, StartDecrementedForecastIncrementedConstantValid = 4, FloatingSubinterval = 5, Missing = 255 }; /// /// @brief Convert `TypeOfTimeIntervals` to its canonical name. /// /// @param[in] value Enumeration value /// /// @return Canonical string name /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the enumeration value is invalid. /// inline std::string enum2name_TypeOfTimeIntervals_or_throw(TypeOfTimeIntervals value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case TypeOfTimeIntervals::Reserved: return "reserved"; case TypeOfTimeIntervals::SameForecastTimeStartIncremented: return "same-forecast-time-start-incremented"; case TypeOfTimeIntervals::SameStartTimeForecastIncremented: return "same-start-time-forecast-incremented"; case TypeOfTimeIntervals::StartIncrementedForecastDecrementedConstantValid: return "start-incremented-forecast-decremented-constant-valid"; case TypeOfTimeIntervals::StartDecrementedForecastIncrementedConstantValid: return "start-decremented-forecast-incremented-constant-valid"; case TypeOfTimeIntervals::FloatingSubinterval: return "floating-subinterval"; case TypeOfTimeIntervals::Missing: return "missing"; default: throw Mars2GribTableException("Invalid TypeOfTimeIntervals enum value", Here()); } } /// /// @brief Convert a canonical name to `TypeOfTimeIntervals`. /// /// @param[in] name Canonical string name /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the name is not recognized. /// inline TypeOfTimeIntervals name2enum_TypeOfTimeIntervals_or_throw(const std::string& name) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; if (name == "reserved") return TypeOfTimeIntervals::Reserved; else if (name == "same-forecast-time-start-incremented") return TypeOfTimeIntervals::SameForecastTimeStartIncremented; else if (name == "same-start-time-forecast-incremented") return TypeOfTimeIntervals::SameStartTimeForecastIncremented; else if (name == "start-incremented-forecast-decremented-constant-valid") return TypeOfTimeIntervals::StartIncrementedForecastDecrementedConstantValid; else if (name == "start-decremented-forecast-incremented-constant-valid") return TypeOfTimeIntervals::StartDecrementedForecastIncrementedConstantValid; else if (name == "floating-subinterval") return TypeOfTimeIntervals::FloatingSubinterval; else if (name == "missing") return TypeOfTimeIntervals::Missing; else throw Mars2GribTableException("Invalid TypeOfTimeIntervals name: '" + name + "'", Here()); } /// /// @brief Convert a numeric GRIB code to `TypeOfTimeIntervals`. /// /// @param[in] value Numeric GRIB code /// /// @return Corresponding enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribTableException /// If the value is not defined in the GRIB table. /// inline TypeOfTimeIntervals long2enum_TypeOfTimeIntervals_or_throw(long value) { using metkit::mars2grib::utils::exceptions::Mars2GribTableException; switch (value) { case 0: return TypeOfTimeIntervals::Reserved; case 1: return TypeOfTimeIntervals::SameForecastTimeStartIncremented; case 2: return TypeOfTimeIntervals::SameStartTimeForecastIncremented; case 3: return TypeOfTimeIntervals::StartIncrementedForecastDecrementedConstantValid; case 4: return TypeOfTimeIntervals::StartDecrementedForecastIncrementedConstantValid; case 5: return TypeOfTimeIntervals::FloatingSubinterval; case 255: return TypeOfTimeIntervals::Missing; default: throw Mars2GribTableException("Invalid TypeOfTimeIntervals numeric value: actual='" + std::to_string(value) + "', expected={0..5,255}", Here()); } } } // namespace metkit::mars2grib::backend::tables metkit-1.20.2/src/metkit/mars2grib/backend/deductions/0000775000175000017500000000000015246725757023005 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/deductions/offsetToEndOf4DvarWindow.h0000664000175000017500000001037515246725757027762 0ustar alastairalastair/* * (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 offsetToEndOf4DvarWindow.h /// @brief Deduction of the offset to the end of the 4D-Var analysis window. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **offset to the end of the 4D-Var assimilation window** /// from input dictionaries. /// /// The deduction retrieves the offset explicitly from the MARS dictionary. /// No inference, defaulting, normalization, or validation of temporal /// semantics is performed. /// /// 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 resolved from one or more input dictionaries /// /// @section References /// Concept: /// - @ref analysisEncoding.h /// /// Related deductions: /// - @ref lengthOfTimeWindow.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 offset to the end of the 4D-Var analysis window. /// /// @section Deduction contract /// - Reads: `mars["anoffset"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the temporal offset between the analysis /// reference time and the end of the 4D-Var assimilation window. /// /// The returned value is treated as an opaque numeric quantity. Its unit /// and interpretation are defined by upstream MARS/IFS conventions and /// are not interpreted by this deduction. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `anoffset`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the offset is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The offset to the end of the 4D-Var analysis window. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `anoffset` is missing, cannot be converted to `long`, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the offset is explicitly provided by /// MARS and does not attempt any inference or defaulting. /// template long resolve_offsetToEndOf4DvarWindow_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 anoffset auto offsetToEndOf4DvarWindow = get_or_throw(mars, "anoffset"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`offsetToEndOf4DvarWindow` resolved from input dictionaries: value='"; logMsg += std::to_string(offsetToEndOf4DvarWindow) + "'"; return logMsg; }()); // Success exit point return offsetToEndOf4DvarWindow; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `offsetToEndOf4DvarWindow` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/backgroundProcess.h0000664000175000017500000001510715246725757026640 0ustar alastairalastair/* * (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 backgroundProcess.h /// @brief Deduction of the GRIB `backgroundProcess` attribute. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB Background Process** from input dictionaries. /// /// The deduction maps the MARS model identifier to a GRIB-compliant /// `BackgroundProcess` value according to predefined, authoritative /// mapping rules. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying deterministic deduction logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply semantic defaults beyond explicit rules /// - do NOT perform GRIB table validation /// /// Error handling follows a strict fail-fast strategy: /// - missing or unsupported 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 resolved via deduction logic from input dictionaries /// - DEFAULT: value defaulted to a predefined constant due to missing input /// /// @section References /// Concept: /// - @ref generatingProcessEncoding.h /// /// Related deductions: /// - @ref generatingProcessIdentifier.h /// - @ref typeOfGeneratingProcess.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Tables #include "metkit/mars2grib/backend/tables/backgroundProcess.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `backgroundProcess` value from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["model"]` /// - Writes: none /// - Side effects: logging (RESOLVE or DEFAULT) /// - Failure mode: throws /// /// This deduction resolves the GRIB `backgroundProcess` by mapping the /// MARS model identifier to the corresponding /// `tables::BackgroundProcess` enumeration value. /// /// The mapping is explicit and strict: only supported model identifiers /// are accepted. Unsupported or unknown values result in an immediate /// deduction failure. /// If the key `model` is missing, a default value of "ifs" is used, /// which is then mapped to the corresponding `backgroundProcess`. /// /// This function acts as the single authoritative deduction point for /// `backgroundProcess`. All mapping rules and consistency checks for /// this GRIB key must be implemented here. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `model` /// and conversion to `std::string`. If the key `model` is missing, /// a default value of "ifs" is used. /// /// @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 model identifier is read. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved GRIB `BackgroundProcess` enumeration value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `model` cannot be mapped to a /// valid GRIB background process, or if any unexpected error occurs /// during deduction. /// /// @note /// This deduction is deterministic and does not rely on any /// pre-existing GRIB header state. /// template tables::BackgroundProcess resolve_BackgroundProcess_or_throw(const MarsDict_t& mars, [[maybe_unused]] const ParDict_t& par, [[maybe_unused]] const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { if (has(mars, "model")) { // Retrieve mandatory MARS model identifier std::string marsModelVal = get_or_throw(mars, "model"); // Use IFS as backgroundProcess for NEMO and FESOM if (marsModelVal == "IFS-NEMO" || marsModelVal == "IFS-FESOM") { marsModelVal = "IFS"; } // Apply BackgroundProcess mapping logic tables::BackgroundProcess backgroundProcess = tables::name2enum_BackgroundProcess_or_throw(marsModelVal); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`backgroundProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_BackgroundProcess_or_throw(backgroundProcess) + "'"; return logMsg; }()); // Success exit point return backgroundProcess; } else { // Retrieve mandatory MARS model identifier std::string marsModelVal = "IFS"; // Apply BackgroundProcess mapping logic tables::BackgroundProcess backgroundProcess = tables::name2enum_BackgroundProcess_or_throw(marsModelVal); // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`backgroundProcess` defaulted to: value='"; logMsg += tables::enum2name_BackgroundProcess_or_throw(backgroundProcess) + "'"; return logMsg; }()); // Success exit point return backgroundProcess; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `backgroundProcess` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/waveFrequencyNumber.h0000664000175000017500000000767215246725757027167 0ustar alastairalastair/* * (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 waveFrequencyNumber.h /// @brief Deduction of the GRIB wave frequency number. /// /// This header defines the deduction responsible for resolving the /// wave frequency index used in spectral wave products. /// /// The deduction extracts the frequency index directly from the /// MARS dictionary and exposes it for use in GRIB encoding. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate semantic correctness of indices /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMin.h /// - @ref periodItMax.h /// - @ref waveDirectionNumber.h /// - @ref waveFrequencyGrid.h /// - @ref waveFrequencyGrid.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 GRIB wave frequency number. /// /// This deduction resolves the wave frequency index required for /// spectral wave encoding by retrieving it from the MARS dictionary. /// /// The value is treated as mandatory and must be provided explicitly /// via the MARS key `frequency`. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain the key `frequency` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved wave frequency number /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `frequency` is missing from the MARS dictionary /// - the value cannot be converted to `long` /// - any unexpected error occurs during deduction /// template long resolve_WaveFrequencyNumber_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 wave frequency number from MARS dictionary auto waveFrequencyNumber = get_or_throw(mars, "frequency"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveFrequencyNumber` resolved from input dictionaries: value='"; logMsg += std::to_string(waveFrequencyNumber); logMsg += "'"; return logMsg; }()); // Success exit point return waveFrequencyNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `waveFrequencyNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/model.h0000664000175000017500000001057015246725757024261 0ustar alastairalastair/* * (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 model.h /// @brief Deduction of the MARS model identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **model identifier** from MARS metadata. /// /// The deduction retrieves the model identifier explicitly from the /// MARS dictionary and returns it verbatim. No inference, defaulting, /// normalization, or validation against GRIB tables is performed. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying deterministic deduction logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT infer defaults /// - 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref destineEncoding.h /// /// Related deductions: /// - @ref experiment.h /// - @ref expver.h /// - @ref generation.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 model identifier from the MARS dictionary. /// /// @section Deduction contract /// - Reads: `mars["model"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the model identifier from the MARS dictionary. /// /// The value is treated as mandatory and is returned verbatim as a string. /// No semantic interpretation, validation, or normalization is applied. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `model`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the model identifier is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The model identifier as provided by the MARS dictionary. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `model` is missing, cannot be converted to `std::string`, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the model identifier is explicitly /// provided by MARS and does not attempt any inference or defaulting. /// template std::string resolve_Model_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 model std::string marsModelVal = get_or_throw(mars, "model"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`model` resolved from input dictionaries: value='" + marsModelVal + "'"; return logMsg; }()); // Success exit point return marsModelVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `model` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/hdate.h0000664000175000017500000001045615246725757024251 0ustar alastairalastair/* * (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 hdate.h /// @brief Public deduction header for the optional raw MARS `hdate` key. /// /// Exposes `resolve_Hdate_opt`, the canonical entry point that resolves the raw /// optional MARS `hdate` source into a validated `eckit::Date`. /// /// This deduction owns: /// - direct `hdate` dictionary access; /// - lexical parsing of the supported raw MARS `hdate` representations; /// - local validation that the parsed fields form a valid calendar date. /// /// This deduction does NOT: /// - read any other temporal key; /// - apply defaulting or cross-key validation; /// - construct a `DateTime` or any higher-level temporal artifact. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include "eckit/types/Date.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/dateTimeHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 optional raw MARS `hdate` source. /// /// @section Deduction contract /// - Reads (MARS): `hdate` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `hdate` absent -> `std::nullopt`; /// - numeric `hdate` -> parsed as `YYYYMMDD`; /// - string `hdate` -> parsed as `YYYYMMDD` or `YYYY-MM-DD`; /// - malformed, unsupported, or invalid values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `hdate`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the validated raw MARS /// hindcast date when the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or invalid raw `hdate` input, with the /// original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Hdate_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "hdate")) { return std::nullopt; } eckit::Date result{}; if (auto value = get_opt(mars, "hdate")) { result = detail::parseDateLong(*value, "hdate"); } else if (auto value = get_opt(mars, "hdate")) { result = detail::parseDateString(*value, "hdate"); } else { throw Mars2GribDeductionException("Unsupported type for `hdate`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { std::ostringstream out; out << result; return std::string{"`hdate` resolved from input dictionaries: value='"} + out.str() + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `hdate` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/stattype.h0000664000175000017500000002354315246725757025042 0ustar alastairalastair/* * (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 stattype.h /// @brief Public deduction header for `stattype`. /// /// Exposes `resolve_Stattype_opt` and `resolve_Stattype_or_throw`, the /// canonical entry points that resolve the optional parsed MARS `stattype` /// source from input dictionaries. /// /// This deduction owns: /// - direct `stattype` dictionary access; /// - whitelist validation of the currently supported MARS `stattype` language; /// - parsing of each compact block into one time range and one statistical /// processing type; /// - preservation of parsed block order. /// /// This deduction does NOT: /// - combine `stattype` with `timespan` or caller-supplied inner processing; /// - classify ProductTimeSpec semantics; /// - construct the final ProductTimeSpec model. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { struct ParsedStatTypeBlock00 { TimeDuration timeRange{}; tables::TypeOfStatisticalProcessing typeOfStatisticalProcessing{tables::TypeOfStatisticalProcessing::Missing}; }; using ParsedStatTypeBlocks = std::vector; namespace detail { /// /// @brief Test whether one lowercase `stattype` token is whitelisted. /// /// @param[in] value Lowercase `stattype` token. /// @return `true` when the token belongs to the currently supported MARS /// whitelist. /// inline bool isWhitelistedStatType(const std::string& value) { constexpr std::array whitelist{ "moav", "momn", "momx", "mosd", "daac", "daav", "damn", "damx", "dasd", "moav_daav", "moav_damn", "moav_damx", "moav_dasd", "momn_daav", "momn_damn", "momn_damx", "momn_dasd", "momx_daav", "momx_damn", "momx_damx", "momx_dasd", "mosd_daav", "mosd_damn", "mosd_damx", "mosd_dasd"}; return std::find(whitelist.begin(), whitelist.end(), value) != whitelist.end(); } /// /// @brief Parse one compact `stattype` operation suffix. /// /// @param[in] operation Two-character operation code. /// @return Corresponding GRIB statistical processing type. /// @throws Mars2GribDeductionException if the operation code is unsupported. /// inline tables::TypeOfStatisticalProcessing parseStatOperation(const std::string& operation) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (operation == "av") { return tables::TypeOfStatisticalProcessing::Average; } if (operation == "ac") { return tables::TypeOfStatisticalProcessing::Accumulation; } if (operation == "mn") { return tables::TypeOfStatisticalProcessing::Minimum; } if (operation == "mx") { return tables::TypeOfStatisticalProcessing::Maximum; } if (operation == "sd") { return tables::TypeOfStatisticalProcessing::StandardDeviation; } throw Mars2GribDeductionException("Unsupported stattype operation: '" + operation + "'", Here()); } /// /// @brief Parse one complete compact `stattype` value. /// /// The input is validated against the supported whitelist first, then split into /// one or more underscore-separated four-character blocks. Each block yields one /// parsed time range and one statistical processing type. /// /// @param[in] raw Raw `stattype` string from MARS. /// @return Parsed blocks in stored textual order. /// @throws Mars2GribDeductionException if the value is malformed, unsupported, /// or not in the supported whitelist. /// inline ParsedStatTypeBlocks parseStattypeValue(const std::string& raw) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; const std::string value = lower(raw); if (!isWhitelistedStatType(value)) { throw Mars2GribDeductionException("`stattype` is not in the supported MARS whitelist: '" + raw + "'", Here()); } ParsedStatTypeBlocks result; std::size_t start = 0; while (start < value.size()) { const std::size_t end = value.find('_', start); const std::string block = value.substr(start, end == std::string::npos ? std::string::npos : end - start); if (block.size() != 4) { throw Mars2GribDeductionException("Invalid stattype block: '" + block + "'", Here()); } const std::string period = block.substr(0, 2); const std::string operation = block.substr(2, 2); TimeDuration range; if (period == "mo") { range = TimeDuration{1, tables::TimeUnit::Month}; } else if (period == "da") { range = TimeDuration{1, tables::TimeUnit::Day}; } else { throw Mars2GribDeductionException("Unsupported stattype period: '" + period + "'", Here()); } result.push_back(ParsedStatTypeBlock00{range, parseStatOperation(operation)}); if (end == std::string::npos) { break; } start = end + 1; } return result; } } // namespace detail /// /// @brief Resolve `stattype` as an optional parsed block sequence. /// /// @section Deduction contract /// - Reads (MARS): `stattype` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `stattype` absent -> `std::nullopt`; /// - `stattype` present -> parse one or more compact blocks in textual order; /// - unsupported whitelist entries, periods, operations, or malformed blocks -> /// hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `stattype`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the parsed block /// sequence when `stattype` is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or non-whitelisted `stattype` input, with /// the original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Stattype_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "stattype")) { return std::nullopt; } if (auto value = get_opt(mars, "stattype")) { auto result = detail::parseStattypeValue(*value); MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`stattype` resolved from input dictionaries: blocks='"} + std::to_string(result.size()) + "'"; }()); return result; } throw Mars2GribDeductionException("`stattype` must be a string", Here()); } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `stattype` from input dictionaries", Here())); } mars2gribUnreachable(); } /// /// @brief Resolve `stattype` or throw if absent. /// /// Thin wrapper around `resolve_Stattype_opt` that converts `std::nullopt` into /// a hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary providing `stattype`. /// @param[in] par Parameter dictionary (forwarded). /// @param[in] opt Options dictionary (forwarded). /// /// @return The parsed `stattype` block sequence. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// if the source is absent, malformed, unsupported, or non-whitelisted; /// failures are wrapped via `std::throw_with_nested`. /// template ParsedStatTypeBlocks resolve_Stattype_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { const auto result = resolve_Stattype_opt(mars, par, opt); if (result.has_value()) { return *result; } else { return ParsedStatTypeBlocks{}; } } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Unable to get `stattype` from Mars dictionary", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/expver.h0000664000175000017500000001131415246725757024467 0ustar alastairalastair/* * (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 expver.h /// @brief Deduction of the MARS `expver` (experiment version) identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **experiment version identifier (expver)** from /// MARS metadata. /// /// The deduction retrieves the expver 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, normalization, or defaulting /// - 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 marsEncoding.h /// /// Related deductions: /// - @ref class.h /// - @ref stream.h /// - @ref type.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 experiment version (expver) identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["expver"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the experiment version identifier by retrieving /// the mandatory MARS key `expver` and returning its value as a /// `std::string`. /// /// No semantic interpretation, normalization, or validation is applied. /// The meaning and allowed values of the experiment version identifier /// are defined by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `expver` /// and conversion to `std::string`. /// /// @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 experiment version identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved experiment version identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `expver` is missing, cannot be retrieved as a string, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult experiment registries or GRIB tables. /// template std::string resolve_Expver_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 expver std::string marsExpverVal = get_or_throw(mars, "expver"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`expver` resolved from input dictionaries: value='" + marsExpverVal + "'"; return logMsg; }()); // Success exit point return marsExpverVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `expver` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/numberOfForecastsInEnsemble.h0000664000175000017500000001404415246725757030552 0ustar alastairalastair/* * (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 numberOfForecastsInEnsemble.h /// @brief Deduction of the GRIB `numberOfForecastsInEnsemble` key. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **total number of forecasts in an ensemble**. /// /// The value cannot be inferred from the MARS request alone and must be /// provided explicitly via the parameter dictionary. /// /// The MARS key `number` (perturbation number) is used exclusively for /// consistency validation and does not affect the returned value. /// /// 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 resolved from one or more input dictionaries /// /// @section References /// Concept: /// - @ref ensembleEncoding.h /// /// Related deductions: /// - @ref perturbationNumber.h /// - @ref typeOfEnsembleForecast.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `numberOfForecastsInEnsemble` key. /// /// @section Deduction contract /// - Reads: `par["numberOfForecastsInEnsemble"]`, `mars["number"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the total number of ensemble forecasts. /// /// The value is taken verbatim from the parameter dictionary. /// No inference, defaulting, or heuristic logic is applied. /// /// The MARS perturbation number (`mars["number"]`) is optional and /// used only for consistency validation. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Optionally provide `number`. /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide `numberOfForecastsInEnsemble`. /// Since no defaulting is implemented for this key yet, it must be explicitly provided. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary providing the perturbation number. /// /// @param[in] par /// Parameter dictionary providing the ensemble size. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The total number of forecasts in the ensemble. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If required keys are missing, if the perturbation number is outside /// the valid range, or if any unexpected error occurs during deduction. /// /// @note /// This deduction is fully deterministic and does not depend on /// pre-existing GRIB header state. /// template long resolve_NumberOfForecastsInEnsemble_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { 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::Mars2GribDeductionException; try { if (has(par, "numberOfForecastsInEnsemble")) { // The only way to infer this is from parametrization const auto numberOfForecastsInEnsemble = get_or_throw(par, "numberOfForecastsInEnsemble"); const auto perturbationNumber = get_opt(mars, "number"); // Basic validation if (perturbationNumber.has_value()) { if (*perturbationNumber < 0) { std::string errMsg = "`perturbationNumber` ("; errMsg += std::to_string(*perturbationNumber); errMsg += ") is negative"; throw Mars2GribDeductionException(errMsg, Here()); } if (*perturbationNumber > numberOfForecastsInEnsemble) { std::string errMsg = "`perturbationNumber` ("; errMsg += std::to_string(*perturbationNumber); errMsg += ") is bigger than `numberOfForecastsInEnsemble` ("; errMsg += std::to_string(numberOfForecastsInEnsemble); errMsg += ")"; throw Mars2GribDeductionException(errMsg, Here()); } } // Logging of the par::numberOfForecastsInEnsemble MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`numberOfForecastsInEnsemble` resolved from input dictionaries: value='"; logMsg += std::to_string(numberOfForecastsInEnsemble); logMsg += "'"; return logMsg; }()); return numberOfForecastsInEnsemble; } else { /// @todo Implement a defaulting strategy for `numberOfForecastsInEnsemble` when the key is missing from the /// parameter dictionary. std::string errMsg = "Default value for `numberOfForecastsInEnsemble` not implemented"; throw Mars2GribDeductionException(errMsg, Here()); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `numberOfForecastsInEnsemble` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/typeOfGeneratingProcess.h0000664000175000017500000002440015246725757027767 0ustar alastairalastair/* * (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 typeOfGeneratingProcess.h /// @brief Optional deduction of the GRIB `typeOfGeneratingProcess` identifier. /// /// This header defines the deduction responsible for *optionally* resolving /// the GRIB `typeOfGeneratingProcess` key (Code Table 3) from MARS metadata. /// /// The deduction is intentionally conservative and returns a value only when /// a formally defined and unambiguous mapping applies. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic and explicitly defined mappings /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - guess or approximate generating process semantics /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: supported mapping applied /// - RESOLVE (skip): deduction intentionally not applied /// /// @section References /// Concept: /// - @ref generatingProcessEncoding.h /// /// Related deductions: /// - @ref generatingProcessIdentifier.h /// - @ref backgroundProcess.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include // Tables includes #include "metkit/mars2grib/backend/tables/typeOfGeneratingProcess.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Optionally resolve the GRIB `typeOfGeneratingProcess` key. /// /// This deduction attempts to infer the GRIB /// `typeOfGeneratingProcess` value from MARS metadata. /// /// The deduction is **non-mandatory** and applies only when a formally /// specified and explicitly supported mapping is identified. /// If no such mapping exists, the deduction returns `std::nullopt` /// without raising an error. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary providing metadata used for deduction /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return /// - `tables::TypeOfGeneratingProcess` if the deduction applies /// - `std::nullopt` if no supported deduction is identified /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If required MARS keys are missing or dictionary access fails /// /// @note /// This deduction does not rely on pre-existing GRIB header state and /// does not apply defaults. /// template std::optional resolve_TypeOfGeneratingProcess_opt( const MarsDict_t& mars, const ParDict_t& par, [[maybe_unused]] const OptDict_t& opt) { using metkit::mars2grib::backend::tables::TypeOfGeneratingProcess; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; // N.B. Sometimes this is overwritten by eccodes as a side effect of setting `param` try { // Retrieve mandatory type from MARS dictionary std::string marsTypeVal = get_or_throw(mars, "type"); // std::string marsStreamVal = get_or_throw(mars, "stream"); // std::string marsClassVal = get_or_throw(mars, "class"); // long paramIdVal = get_or_throw(mars, "param"); // Deduce the typeOfGeneratingProcess if (marsTypeVal == "4i") { tables::TypeOfGeneratingProcess result = TypeOfGeneratingProcess::AnalysisIncrement; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfGeneratingProcess_or_throw(result); logMsg += "'"; return logMsg; }()); // Success exit point return {result}; } else if (marsTypeVal == "pf" || marsTypeVal == "cf") { tables::TypeOfGeneratingProcess result = TypeOfGeneratingProcess::EnsembleForecast; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfGeneratingProcess_or_throw(result); logMsg += "'"; return logMsg; }()); // Success exit point return {result}; } else if (marsTypeVal == "fc") { // Detect ensemble evidence even when MARS `type` is the generic // `fc`. Legacy GRIB1 data (and some rewritten streams) may carry // `type=fc` together with ensemble-describing keys; in that case // the correct GRIB2 `typeOfGeneratingProcess` is EnsembleForecast // (4), not Forecast (2). // // Signals (any of): // - par.numberOfForecastsInEnsemble > 1 // - par.typeOfEnsembleForecast present // - mars.number > 0 const bool hasEnsembleSize = has(par, "numberOfForecastsInEnsemble") && (get_or_throw(par, "numberOfForecastsInEnsemble") > 1); const bool hasEnsembleType = has(par, "typeOfEnsembleForecast"); const bool hasEnsembleNumber = has(mars, "number") && (get_or_throw(mars, "number") > 0); const bool isEnsemble = hasEnsembleSize || hasEnsembleType || hasEnsembleNumber; tables::TypeOfGeneratingProcess result = isEnsemble ? TypeOfGeneratingProcess::EnsembleForecast : TypeOfGeneratingProcess::Forecast; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfGeneratingProcess_or_throw(result); logMsg += "'"; if (isEnsemble) { logMsg += " (type='fc' with ensemble evidence:"; if (hasEnsembleSize) { logMsg += " numberOfForecastsInEnsemble>1"; } if (hasEnsembleType) { logMsg += " typeOfEnsembleForecast-present"; } if (hasEnsembleNumber) { logMsg += " number>0"; } logMsg += ")"; } else { logMsg += " (type='fc', no ensemble evidence)"; } return logMsg; }()); // Success exit point return {result}; } else if (marsTypeVal == "est" || marsTypeVal == "es" || marsTypeVal == "em" || marsTypeVal == "ses") { // Ensemble-derived statistical products (ensemble statistics, // ensemble standard deviation, ensemble mean, ensemble spread // of estimation). No dedicated code table entry exists for // "ensemble-derived analysis"; EnsembleForecast (4) is the // established convention to signal ensemble provenance. tables::TypeOfGeneratingProcess result = TypeOfGeneratingProcess::EnsembleForecast; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfGeneratingProcess_or_throw(result); logMsg += "' (type=" + marsTypeVal + ")"; return logMsg; }()); // Success exit point return {result}; } else if (marsTypeVal == "eme" || marsTypeVal == "me") { // 4D-Var model-error fields (eme = ensemble model errors, // me = model errors). Generated as part of the analysis // system; the canonical ECMWF GRIB2 value is Analysis (0). // Without an explicit mapping here the encoder fell back to the // GRIB sample default, which only happened to be 0 by accident. // Grouped with eme to mirror the {4i, 4v, me, eme} grouping // already used in significanceOfReferenceTime. tables::TypeOfGeneratingProcess result = TypeOfGeneratingProcess::Analysis; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfGeneratingProcess_or_throw(result); logMsg += "' (type=eme/me)"; return logMsg; }()); // Success exit point return {result}; } else { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfGeneratingProcess` not resolved from input dictionaries: no supported mapping"; return logMsg; }()); // Success exit point return std::nullopt; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `typeOfGeneratingProcess` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/numberOfFourierCoefficients.h0000664000175000017500000001164615246725757030621 0ustar alastairalastair/* * (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 numberOfFourierCoefficients.h /// @brief Deduction of the total number of Fourier coefficients. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **total number of Fourier coefficients** /// (`numberOfFourierCoefficients`) from the parameter dictionary. /// /// The value is not derivable from MARS alone. It must be supplied via /// the parameter dictionary by the upstream tool, typically read from /// the input GRIB1 handle being re-encoded. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref modelErrorEncoding.h /// /// Related deductions: /// - @ref fourierCoefficientIndex.h /// - @ref modelErrorType.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the total number of Fourier coefficients. /// /// @section Deduction contract /// - Reads: `par["numberOfFourierCoefficients"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the total number of Fourier coefficients /// from the parameter dictionary. /// /// The value is treated as mandatory: it cannot be derived from MARS /// metadata alone and must be supplied by the upstream tool that /// populates the parameter dictionary (typically read from the input /// GRIB1 handle being re-encoded). /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide the key /// `numberOfFourierCoefficients`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which the total number of Fourier /// coefficients is retrieved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The total number of Fourier coefficients. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `numberOfFourierCoefficients` is missing from the parameter /// dictionary, cannot be converted to `long`, or if any unexpected error /// occurs during deduction. /// /// @note /// This deduction does not infer or default the value. Absence of the /// key in the parameter dictionary is considered a contract violation /// by the upstream tool. /// template long resolve_NumberOfFourierCoefficients_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 parameter-dictionary numberOfFourierCoefficients long numberOfFourierCoefficients = get_or_throw(par, "numberOfFourierCoefficients"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`numberOfFourierCoefficients` resolved from input dictionaries: value="; logMsg += std::to_string(numberOfFourierCoefficients); return logMsg; }()); // Success exit point return numberOfFourierCoefficients; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `numberOfFourierCoefficients` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/satelliteNumber.h0000664000175000017500000001027315246725757026320 0ustar alastairalastair/* * (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 satelliteNumber.h /// @brief Deduction of the satellite platform identifier from MARS metadata. /// /// This header defines the deduction responsible for resolving the /// satellite identifier used in satellite-based products. /// /// The deduction extracts the identifier from the MARS dictionary /// and returns it verbatim without interpretation or validation. /// /// Deductions: /// - extract values from MARS, parameter, or option dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - normalize or validate semantics /// - perform GRIB table validation /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained through deduction logic from input dictionaries /// /// @section References /// Concept: /// - @ref satelliteEncoding.h /// /// Related deductions: /// - @ref instrumentType.h /// - @ref channel.h /// - @ref satelliteSeries.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System include #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 satellite identifier from the MARS dictionary. /// /// @section Deduction contract /// - Reads: `mars["ident"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the mandatory `ident` entry from the /// MARS dictionary and returns it as a numeric satellite identifier. /// /// No inference, defaulting, normalization, or semantic validation /// is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `ident`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary providing the satellite identifier. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Satellite identifier resolved from the MARS dictionary. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `ident` is missing, cannot be retrieved as a `long`, /// or if any unexpected error occurs. /// /// @note /// This deduction is deterministic and does not depend on any /// pre-existing GRIB header state. /// template long resolve_satelliteNumber_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 satellite identifier from MARS dictionary long satelliteNumber = get_or_throw(mars, "ident"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`satelliteNumber` resolved from MARS dictionary: value='"; logMsg += std::to_string(satelliteNumber); logMsg += "'"; return logMsg; }()); // Success exit point return satelliteNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `satelliteNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/activity.h0000664000175000017500000001121415246725757025011 0ustar alastairalastair/* * (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 activity.h /// @brief Deduction of the MARS `activity` attribute. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **MARS activity identifier** from input dictionaries. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying minimal deduction logic where required /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply semantic defaults beyond explicit rules /// - 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 resolved via deduction logic from input dictionaries /// - OVERRIDE: value provided by parameter dictionary overriding deduction logic /// /// @section References /// Concept: /// - @ref destineEncoding.h /// /// Related deductions: /// - @ref experiment.h /// - @ref generation.h /// - @ref model.h /// - @ref realization.h /// - @ref resolution.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 MARS activity identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["activity"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the mandatory key `activity` from the MARS /// dictionary and returns its value as a `std::string`. /// /// The value is resolved directly from the input dictionaries without /// semantic interpretation, validation, or defaulting. The meaning of /// the activity identifier is defined by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `activity` /// and conversion to `std::string`. /// /// @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 activity identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved MARS activity identifier as a `std::string`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `activity` is missing, cannot be converted to /// `std::string`, or if any unexpected error occurs during access. /// /// @note /// This deduction follows a fail-fast strategy and emits exactly one /// RESOLVE log entry on successful execution. /// template std::string resolve_Activity_or_throw(const MarsDict_t& mars, [[maybe_unused]] const ParDict_t& par, [[maybe_unused]] const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory MARS activity std::string marsActivityVal = get_or_throw(mars, "activity"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`activity` resolved from input dictionary: value='" + marsActivityVal + "'"; return logMsg; }()); // Success exit point return marsActivityVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `activity` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/timeIncrement.h0000664000175000017500000001563315246725757025771 0ustar alastairalastair/* * (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 timeIncrement.h /// @brief Public deduction header for `timeIncrement`. /// /// Exposes `resolve_TimeIncrement_opt` and `resolve_TimeIncrement_or_throw`, /// the canonical entry points that resolve the optional normalized parameter- /// side time increment duration. /// /// This deduction owns: /// - direct `timeIncrementInSeconds` dictionary access; /// - lexical parsing of numeric and string integer representations; /// - local positivity validation; /// - materialization of the deduction-local `TimeDuration` output. /// /// This deduction is intentionally independent from the legacy /// `timeIncrementInSeconds.h` deduction. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { namespace detail { /// /// @brief Canonicalize one strictly positive time increment expressed in seconds. /// /// Positive whole-hour values are represented in hours. Other strictly positive /// values are represented in seconds. /// /// @param[in] seconds Elapsed increment in seconds. /// @return Canonical time increment duration. /// @throws Mars2GribDeductionException if `seconds <= 0`. /// inline TimeDuration canonicalTimeIncrementDuration(long seconds) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (seconds <= 0) { throw Mars2GribDeductionException("`timeIncrementInSeconds` must be strictly positive when present", Here()); } if (seconds % 3600L == 0) { return TimeDuration{seconds / 3600L, tables::TimeUnit::Hour}; } return TimeDuration{seconds, tables::TimeUnit::Second}; } } // namespace detail /// /// @brief Resolve `timeIncrement` as an optional normalized duration. /// /// @section Deduction contract /// - Reads (MARS): none (signature-only, reserved) /// - Reads (par): `timeIncrementInSeconds` /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `timeIncrementInSeconds` absent -> `std::nullopt`; /// - present valid positive value -> normalized `TimeDuration`; /// - present zero, negative, malformed, or unsupported value -> hard error. /// /// @tparam MarsDict_t MARS dictionary type (currently unused). /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary (signature-only). /// @param[in] par Parameter dictionary providing `timeIncrementInSeconds`. /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the normalized time /// increment when the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed or locally invalid parameter input, with the original /// cause attached via `std::throw_with_nested`. /// template std::optional resolve_TimeIncrement_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)mars; (void)opt; try { if (!has(par, "timeIncrementInSeconds")) { return std::nullopt; } long seconds = 0; if (auto value = get_opt(par, "timeIncrementInSeconds")) { seconds = *value; } else if (auto value = get_opt(par, "timeIncrementInSeconds")) { seconds = detail::parseLongStrict(*value, "timeIncrementInSeconds"); } else { throw Mars2GribDeductionException("Unsupported type for `timeIncrementInSeconds`", Here()); } const TimeDuration result = detail::canonicalTimeIncrementDuration(seconds); MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`timeIncrement` resolved from input dictionaries: value='"} + std::to_string(result.length) + "' unit='" + tables::enum2name_TimeUnit_or_throw(result.unit) + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `timeIncrement` from input dictionaries", Here())); } mars2gribUnreachable(); } /// /// @brief Resolve `timeIncrement` or throw if absent. /// /// Thin wrapper around `resolve_TimeIncrement_opt` that converts `std::nullopt` /// into a hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary (forwarded). /// @param[in] par Parameter dictionary providing `timeIncrementInSeconds`. /// @param[in] opt Options dictionary (forwarded). /// /// @return The normalized time increment duration. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// if the source is absent, malformed, unsupported, or locally invalid; /// failures are wrapped via `std::throw_with_nested`. /// template TimeDuration resolve_TimeIncrement_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { const auto result = resolve_TimeIncrement_opt(mars, par, opt); if (result.has_value()) { return *result; } throw Mars2GribDeductionException("`timeIncrement` is not defined in the Par dictionary", Here()); } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Unable to get `timeIncrement` from Par dictionary", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/typeOfEnsembleForecast.h0000664000175000017500000001371415246725757027574 0ustar alastairalastair/* * (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 typeOfEnsembleForecast.h /// @brief Deduction of the GRIB `typeOfEnsembleForecast` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `typeOfEnsembleForecast` key (Code Table 4.6), which describes /// the nature of ensemble perturbations. /// /// The value may be provided explicitly via parametrization or /// deduced deterministically from MARS metadata. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values beyond defined rules /// - apply silent fallbacks /// - rely on pre-existing GRIB header state /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value deduced from input dictionaries /// - OVERRIDE: explicit user override via parameter dictionary /// /// @section References /// Concept: /// - @ref ensembleEncoding.h /// /// Related deductions: /// - @ref perturbationNumber.h /// - @ref numberOfForecastsInEnsemble.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Tables includes #include "metkit/mars2grib/backend/tables/typeOfEnsembleForecast.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `typeOfEnsembleForecast` key. /// /// Resolution follows a strict precedence order: /// /// 1. **Explicit override** /// If `par::typeOfEnsembleForecast` is present, its value is taken /// as authoritative and validated against GRIB Code Table 4.6. /// /// 2. **Automatic deduction** /// If no override is provided, the value is deduced from `mars::type`: /// - `"cf"` → `Unperturbed` /// - `"pf"` → `Perturbed` /// /// Any unsupported input results in a deduction failure. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary (unused) /// /// @return The resolved `TypeOfEnsembleForecast` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the value cannot be resolved /// /// @note /// This deduction is deterministic and authoritative for /// `typeOfEnsembleForecast`. /// template tables::TypeOfEnsembleForecast resolve_TypeOfEnsembleForecast_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Get mars type from dictionary if (has(par, "typeOfEnsembleForecast")) { // Retrieve mandatory typeOfEnsembleForecast from parameter dictionary long typeOfEnsembleForecastVal = get_or_throw(par, "typeOfEnsembleForecast"); // Get the enum value tables::TypeOfEnsembleForecast typeOfEnsembleForecast = tables::long2enum_TypeOfEnsembleForecast_or_throw(typeOfEnsembleForecastVal); // Emit RESOLVE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`typeOfEnsembleForecast` overridden from parameter dictionary: value='"; logMsg += tables::enum2name_TypeOfEnsembleForecast_or_throw(typeOfEnsembleForecast); logMsg += "'"; return logMsg; }()); // Success exit point return typeOfEnsembleForecast; } else { // Retrieve mandatory type from MARS dictionary std::string marsType = get_or_throw(mars, "type"); tables::TypeOfEnsembleForecast typeOfEnsembleForecast = tables::TypeOfEnsembleForecast::Missing; if (marsType == "cf") { typeOfEnsembleForecast = tables::TypeOfEnsembleForecast::Unperturbed; } else if (marsType == "pf") { typeOfEnsembleForecast = tables::TypeOfEnsembleForecast::Perturbed; } else { throw Mars2GribDeductionException( "`type` value '" + marsType + "' is not mapped to any known `typeOfEnsembleForecast`", Here()); } // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfEnsembleForecast` resolved from input dictionaries: value='"; logMsg += tables::enum2name_TypeOfEnsembleForecast_or_throw(typeOfEnsembleForecast); logMsg += "'"; return logMsg; }()); // Success exit point return typeOfEnsembleForecast; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `typeOfEnsembleForecast` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/lengthOfTimeWindow.h0000664000175000017500000001422715246725757026741 0ustar alastairalastair/* * (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 lengthOfTimeWindow.h /// @brief Deduction of the GRIB lengthOfTimeWindow (in seconds). /// /// This header defines the deduction used by the mars2grib backend to resolve /// the GRIB lengthOfTimeWindow key from input dictionaries. /// /// The deduction reads the parameter dictionary entry lengthOfTimeWindow, /// interprets it as hours, and converts it to seconds. If the key is missing, /// the deduction returns `std::nullopt`. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying explicit, deterministic deduction logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys /// - do NOT infer units or values beyond the documented rule /// - 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 from the parameter dictionary /// - DEFAULT: value defaulted to the GRIB missing code /// /// @section References /// Concept: /// - @ref analysisEncoding.h /// /// Related deductions: /// - @ref offsetToEndOf4DvarWindow.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System Include #include #include #include // Other project includes #include "eckit/log/Log.h" // 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 GRIB `lengthOfTimeWindow` expressed in seconds. /// /// This deduction determines the value of the GRIB `lengthOfTimeWindow` /// (in seconds) based on the parameter dictionary key `lengthOfTimeWindow`. /// /// The deduction follows these rules: /// /// - If the key `lengthOfTimeWindow` is present in the parameter dictionary, /// its value is interpreted as **hours** and converted to seconds. /// - If the key is absent, `std::nullopt` is used, which should be handled /// by the encoding layer as the GRIB missing code (0xFFFF). /// /// @important /// This deduction currently relies on **implicit assumptions** about /// units and defaults that are not explicitly encoded in MARS metadata. /// These assumptions are documented but not enforced via validation. /// /// @assumptions /// - `par::lengthOfTimeWindow` is expressed in **hours** /// - Default value is `std::nullopt` when the key is missing /// /// @warning /// - These assumptions may not be valid for all datasets. /// - Relying on implicit defaults may lead to non-reproducible GRIB output /// if upstream conventions change. /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary (unused) /// /// @return The length of time window in seconds. If `par::lengthOfTimeWindow` is missing, /// returns `std::nullopt`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - access to the parameter dictionary fails /// - the retrieved value cannot be interpreted as a valid integer /// - any unexpected error occurs during deduction /// /// @todo [owner: mds,dgov][scope: deduction][reason: correctness][prio: medium] /// - Make the unit of `lengthOfTimeWindow` explicit instead of assuming hours. /// - Add explicit validation of allowed ranges and units. /// /// @note /// - This deduction does not rely on any pre-existing GRIB header state. /// - Logging intentionally emits RESOLVE/DEFAULT entries to highlight implicit assumptions. /// template std::optional resolve_LengthOfTimeWindowInSeconds_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Big assumption here: // - lengthOfTimeWindow is in hours if (has(par, "lengthOfTimeWindow")) { long lengthOfTimeWindowInHoursVal = get_or_throw(par, "lengthOfTimeWindow"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`lengthOfTimeWindow` resolved from input dictionaries: value='"; logMsg += std::to_string(lengthOfTimeWindowInHoursVal) + "' [hours]"; return logMsg; }()); // Success exit point return {lengthOfTimeWindowInHoursVal * 3600}; // Convert hours to seconds } else { // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`lengthOfTimeWindow` defaulted to MISSING (nullopt)"; return logMsg; }()); // Success exit point return std::nullopt; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Unable to get `lengthOfTimeWindow` from Par dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/periodItMin.h0000664000175000017500000001125015246725757025400 0ustar alastairalastair/* * (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 periodItMin.h /// @brief Deduction of the minimum wave period index (`iTmin`). /// /// This header defines deduction utilities used by the mars2grib backend /// to retrieve the **minimum wave period index** (`iTmin`) from input /// dictionaries. /// /// The deduction treats `iTmin` as an optional parameter: /// - if present in the parameter dictionary, the value is returned /// - if absent, no default is applied and an empty optional is returned /// /// No semantic validation or consistency checking is performed at this /// level. /// /// Error handling follows a strict fail-fast strategy: /// - unexpected access errors 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 presence or absence resolved from input dictionaries /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMax.h /// - @ref waveFrequencyGrid.h /// - @ref waveFrequencyNumber.h /// - @ref waveDirectionGrid.h /// - @ref waveDirectionNumber.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #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 optional minimum wave period index (`iTmin`). /// /// @section Deduction contract /// - Reads: `par["iTmin"]` (optional) /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws on unexpected errors /// /// This deduction retrieves the optional wave period index `iTmin` /// from the parameter dictionary. /// /// If the key is present, the value is returned wrapped in a /// `std::optional`. If the key is absent, an empty optional is returned. /// No defaulting, inference, or semantic validation is applied. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. May contain `iTmin`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which `iTmin` may be retrieved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// An optional containing `iTmin` if present; otherwise an empty /// optional. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If any unexpected error occurs during dictionary access. /// /// @note /// This deduction performs no semantic validation of the retrieved /// value. /// template std::optional resolve_PeriodItMin_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve optional minimum wave period index from parameter dictionary std::optional itMinOpt = get_opt(par, "iTmin"); if (itMinOpt.has_value()) { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`iTmin` resolved from input dictionaries: value='"; logMsg += std::to_string(itMinOpt.value()); logMsg += "'"; return logMsg; }()); } else { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`iTmin` resolved from input dictionaries: value not present"; return logMsg; }()); } // Success exit point return itMinOpt; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `iTmin` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/significanceOfReferenceTime.h0000664000175000017500000001526515246725757030534 0ustar alastairalastair/* * (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 significanceOfReferenceTime.h /// @brief Deduction of the GRIB `significanceOfReferenceTime` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `significanceOfReferenceTime` key, which describes the semantic /// meaning of the GRIB reference time. /// /// The value is deterministically deduced from the MARS request, /// specifically from the `mars::type` key, according to established /// ECMWF/MARS conventions. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate against external GRIB code tables beyond explicit mappings /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value deduced deterministically from input dictionaries /// /// @section References /// Concept: /// - @ref referenceTimeEncoding.h /// /// Related deductions: /// - @ref productTime.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include // Tables includes #include "metkit/mars2grib/backend/tables/significanceOfReferenceTime.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `significanceOfReferenceTime` key. /// /// This deduction resolves the GRIB `significanceOfReferenceTime` /// identifier based on the value of the MARS key `type`. /// /// Resolution rules: /// - Analysis-like MARS types map to /// `SignificanceOfReferenceTime::Analysis` /// - Forecast-like MARS types map to /// `SignificanceOfReferenceTime::ForecastStart` /// /// The mapping is explicit and exhaustive. Any unsupported MARS /// `type` value results in a deduction error. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain the key `type` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved `SignificanceOfReferenceTime` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - `mars::type` is missing /// - `mars::type` cannot be mapped to a supported significance /// - any unexpected error occurs during deduction /// /// @note /// This deduction is fully deterministic and does not rely on any /// pre-existing GRIB header state. /// template tables::SignificanceOfReferenceTime resolve_SignificanceOfReferenceTime_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 { auto significanceOfReferenceTime = tables::SignificanceOfReferenceTime::Missing; // Retrieve mandatory type from Mars dictionary auto marsStream = get_or_throw(mars, "stream"); auto marsType = get_or_throw(mars, "type"); constexpr std::array analysisTypes = { {"an", "ia", "oi", "3v", "3g", "4g", "ea", "pa", "tpa", "ga", "gai", "ai", "af", "ab", "oai", "ga", "gai"}}; constexpr std::array forecastTypes = { {"fc", "cf", "pf", "cm", "fp", "em", "ep", "es", "fa", "efi", "efic", "bf", "cd", "wem", "wes", "cr", "ses", "taem", "taes", "sg", "sf", "if", "fcmean", "fcmax", "fcmin", "fcstdev", "ssd", "tf", "bf", "cd", "hcmean", "s3", "si", "gbf", "gwt", "est", "icp", "pfc", "sot"}}; constexpr std::array startOfDataAssimilationTypes = {{"4i", "4v", "me", "eme"}}; if (marsType == "fc" && (marsStream == "clte" || marsStream == "clmn")) { // ClimateDT and ExtremesDT significanceOfReferenceTime = tables::SignificanceOfReferenceTime::ForecastVerification; } else if (std::any_of(analysisTypes.begin(), analysisTypes.end(), [&marsType](auto v) { return marsType == v; })) { significanceOfReferenceTime = tables::SignificanceOfReferenceTime::Analysis; } else if (std::any_of(forecastTypes.begin(), forecastTypes.end(), [&marsType](auto v) { return marsType == v; })) { significanceOfReferenceTime = tables::SignificanceOfReferenceTime::ForecastStart; } else if (std::any_of(startOfDataAssimilationTypes.begin(), startOfDataAssimilationTypes.end(), [&marsType](auto v) { return marsType == v; })) { significanceOfReferenceTime = tables::SignificanceOfReferenceTime::AssimilationStart; } else { // Unhandled cases throw Mars2GribDeductionException( "Failed to resolve `significanceOfReferenceTime` from MARS type: " + marsType, Here()); } // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`significanceOfReferenceTime` resolved from input dictionaries: value='"; logMsg += tables::enum2name_SignificanceOfReferenceTime_or_throw(significanceOfReferenceTime); logMsg += "'"; return logMsg; }()); /// Success exit point return significanceOfReferenceTime; } catch (...) { std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `significanceOfReferenceTime` from input dictionaries", Here())); } // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/common.h0000664000175000017500000000444215246725757024452 0ustar alastairalastair/* * (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 common.h /// @brief Shared deduction-local temporal DTOs. /// /// This header contains only the small value types shared by multiple temporal /// deductions. It intentionally does NOT depend on ProductTimeSpec model types, /// so deductions remain buildable without the backend model layer. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/tables/timeUnits.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Deduction-local time duration. /// /// This type represents one non-negative time length paired with the GRIB time /// unit used by the deduction layer. It is shared by `step`, `timespan`, /// `stattype`, and `timeIncrement` deductions. /// struct TimeDuration { long length{0}; tables::TimeUnit unit{tables::TimeUnit::Second}; }; enum class TimespanKind { Missing, Duration, None, FromStart }; struct Timespan { TimespanKind kind{TimespanKind::Missing}; std::optional duration{}; }; /// /// @brief Supported simulation-regime classifications shared by temporal deductions. /// /// The classification distinguishes the IFS and AIFS product families consumed /// by ProductTimeSpec model construction. /// /// `Count` is a sentinel used exclusively to size fixed lookup tables. /// enum class SimulationRegime : std::size_t { IFS = 0, AIFS, Count }; /// /// @brief Supported simulation-type classifications shared by temporal deductions. /// /// The classification distinguishes forecast-like and analysis-like MARS /// product types consumed by ProductTimeSpec model construction. /// /// `Count` is a sentinel used exclusively to size fixed lookup tables. /// enum class SimulationType : std::size_t { Forecast = 0, Analysis, Count }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/subCentre.h0000664000175000017500000000771415246725757025121 0ustar alastairalastair/* * (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 subCentre.h /// @brief Deduction of the GRIB `subCentre` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `subCentre` key, which identifies the originating sub-centre /// within a GRIB-producing centre. /// /// The value is obtained from the parameter dictionary when provided. /// If absent, the deduction applies an explicit and deterministic /// default according to GRIB conventions. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values from MARS metadata /// - apply implicit or hidden defaults /// - validate against official GRIB code tables /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained or defaulted from input dictionaries /// /// @section References /// Concept: /// - @ref originEncoding.h /// /// Related deductions: /// - @ref centre.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 GRIB `subCentre` key. /// /// This deduction resolves the GRIB `subCentre` identifier using the /// parameter dictionary. /// /// Resolution rules: /// - If `par::subCentre` is present, its value is used directly. /// - If `par::subCentre` is absent, the value defaults explicitly to `0`, /// corresponding to the GRIB convention for an unspecified sub-centre. /// /// No inference from MARS metadata is performed. /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary; may contain `subCentre` /// @param[in] opt Options dictionary (unused) /// /// @return The resolved GRIB `subCentre` value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If an unexpected error occurs during dictionary access /// /// @note /// This deduction is fully deterministic and does not depend on /// any pre-existing GRIB header state. /// template long resolve_SubCentre_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve optional subCentre from parameter dictionary long subCentre = get_opt(par, "subCentre").value_or(0L); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`subCentre` resolved from input dictionaries: value='"; logMsg += std::to_string(subCentre); logMsg += "'"; return logMsg; }()); // Success exit point return subCentre; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `subCentre` from input dictionaries", Here())); } }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/typeOfStatisticalProcessing.h0000664000175000017500000003437515246725757030702 0ustar alastairalastair/* * (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 typeOfStatisticalProcessing.h /// @brief Public deduction header for `typeOfStatisticalProcessing`. /// /// Exposes `resolve_TypeOfStatisticalProcessing_or_throw`, which produces a /// `std::vector` describing the /// statistical processing applied at each time loop of a statistical /// product, in outer→inner order. /// /// **Self-contained**: this deduction does NOT depend on `ProductTime`. /// It depends only on: /// - `detail/StatType.h` — the shared `stattype` parser (§22) /// - `tables/typeOfStatisticalProcessing.h` — GRIB Code Table 4.10 enum /// /// **Implicit invariant** (well-documented; not enforced at runtime): /// the output `std::vector` has length and order **identical** to /// `ProductTime::statisticalWindows` resolved from the same MARS input — /// outermost loop at index 0, innermost loop at index `size() - 1`. /// Callers MUST rely on this invariant when zipping the two vectors. /// /// **Precondition discharged by the type system**: the function takes a /// `tables::TypeOfStatisticalProcessing` argument; a caller cannot /// construct this argument without having already identified the product as /// statistical (i.e., having decoded the inner statistical operation from /// `paramId`). Therefore this deduction is structurally unreachable for /// instant products (§9.1); no runtime check is performed for that case. /// /// See `deductions/timeProducts.md` §23 for the full normative /// specification. /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #include #include // eckit #include "eckit/exception/Exceptions.h" // Project utilities #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" // Detail (shared parser) #include "metkit/mars2grib/backend/deductions/detail/StatType.h" // Tables #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the per-loop GRIB `typeOfStatisticalProcessing` array for /// one MARS statistical product. /// /// @section Deduction contract /// - Reads (MARS): `stattype`, `timespan` (presence only — for case /// classification per §23.5) /// - Reads (par): none /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// @section Output sizing per case (§23.5) /// /// Local classification of MARS into one of the §9 cases (no `ProductTime` /// dependency): /// /// | MARS state | §9 case | Output size | Output content | /// |---------------------------------------------------------|---------|------------:|---------------------------------------------------------------------------------| /// | `stattype` absent, `timespan` is a Duration | §9.2 | 1 | /// `[innerTypeOfStatisticalProcessing]` | | `stattype` has N blocks, /// `timespan` is a Duration | §9.3 | N + 1 | `[op(block_0), …, op(block_{N-1}), /// innerTypeOfStatisticalProcessing]` | | `stattype` has 1 block, `timespan = "none"` | §9.4 | /// 1 | `[innerTypeOfStatisticalProcessing]` (after equality assertion, §23.6) | | `stattype` absent, /// `timespan` absent | §9.1 | unreachable | (precondition violated, see §23.4 — no defensive check) | /// /// The innermost slot is **always** filled by the /// `innerTypeOfStatisticalProcessing` argument — never by a parsed block. /// /// @section fakeDoubleLoop equality assertion (§23.6) /// /// In the §9.4 case, the single parsed `stattype` block carries a /// redundantly encoded operation. This deduction asserts: /// /// parsedBlocks[0].typeOfStatisticalProcessing /// == innerTypeOfStatisticalProcessing /// /// Disagreement is hard error §10.12. /// /// @section Outer/inner asymmetry (§23.7) /// /// - **Outer slots** (indices 0..size()-2 in the §9.3 case) are /// constrained to `{Average, Minimum, Maximum, StandardDeviation}` by /// the parser-level op allow-list (§22.5). /// - **Innermost slot** is **unrestricted** within GRIB Code Table 4.10; /// it can be any value the caller passes. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] innerTypeOfStatisticalProcessing /// The pre-resolved inner statistical processing type, derived by /// the caller from `paramId`. /// @param[in] mars /// MARS dictionary providing `stattype` and `timespan`. /// @param[in] par /// Parameter dictionary (signature-only). /// @param[in] opt /// Options dictionary (signature-only). /// /// @return A `std::vector` whose length /// and order match `ProductTime::statisticalWindows` for the same /// MARS input (implicit invariant, §23.3). /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on: /// - §10.12 — fakeDoubleLoop equality violation; /// - §10.16, §10.17, §10.18 (a) — propagated from /// `parse_StatType_or_throw` (§22.7). /// All failures are wrapped via `std::throw_with_nested`. /// /// @note This function does NOT classify `timespan` exhaustively against the /// §9 case table; it only distinguishes the three presence-states /// (absent, Duration, "none") needed for §23.5 sizing. Misclassification /// at this level (e.g. inputs that would fail §10.7 at the `ProductTime` /// resolver) is undefined behavior here; callers are expected to invoke /// this deduction only on inputs that also resolve cleanly through /// `resolve_ProductTime_or_throw`. /// template std::vector resolve_TypeOfStatisticalProcessing_or_throw( tables::TypeOfStatisticalProcessing innerTypeOfStatisticalProcessing, const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { 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::Mars2GribDeductionException; // Suppress "unused parameter" warnings while preserving the documented // signature (par and opt are reserved for future use). (void)par; (void)opt; try { // ========================================================= // Local case classification (§23.5) // // We look at presence of `stattype` and the textual value of // `timespan` to distinguish §9.2 / §9.3 / §9.4. Full §9-case // dispatch (with hard errors §10.6 / §10.7 / §10.8) remains the // responsibility of the `ProductTime` resolver; this deduction // only needs enough classification to size its output. // ========================================================= const bool hasStatType = has(mars, "stattype"); // Classify timespan: missing | "none" | Duration. // We do NOT parse the duration value here; only its presence and // the literal "none" matter for §23.5 sizing. bool hasTimespanNone = false; bool hasTimespanDuration = false; if (has(mars, "timespan")) { std::optional tsStr = get_opt(mars, "timespan"); if (tsStr.has_value()) { if (tsStr.value() == "none") { hasTimespanNone = true; } else { hasTimespanDuration = true; } } else { // Numeric-only timespan: a Duration per §7.6. hasTimespanDuration = true; } } // ========================================================= // Parse `stattype` once (shared parser, §22). // ========================================================= std::vector blocks; if (hasStatType) { const std::string statTypeVal = get_or_throw(mars, "stattype"); blocks = detail::parse_StatType_or_throw(statTypeVal); } // ========================================================= // Output assembly per §23.5. // // Default-fallback path: when MARS does not match any of the // statistical cases (§9.2 / §9.3 / §9.4) we still emit a size-1 // vector containing the inner type. Other case-table violations // (e.g. §10.6 / §10.7 / §10.8) are not raised here; they are // raised by the `ProductTime` resolver when the caller invokes it // on the same MARS input (per the contract in §23.8 and the // class-level note above). // ========================================================= std::vector out; if (!hasStatType && hasTimespanDuration) { // ----- §9.2: Old-style single-loop ----- out.reserve(1); out.push_back(innerTypeOfStatisticalProcessing); } else if (hasStatType && hasTimespanDuration) { // ----- §9.3: Old-style multi-loop ----- out.reserve(blocks.size() + 1); for (const detail::ParsedStatTypeBlock& b : blocks) { out.push_back(b.typeOfStatisticalProcessing); } out.push_back(innerTypeOfStatisticalProcessing); } else if (hasStatType && hasTimespanNone) { // ----- §9.4: New-style fakeDoubleLoop ----- // // Equality assertion (§23.6 / §10.12). Block count is // structurally guaranteed to be 1 here under the spec; if the // caller has supplied input that would fail §10.8 at the // `ProductTime` resolver (more than one block), the assertion // below acts on blocks[0] only — the multi-block error itself // is reported by the `ProductTime` resolver. if (blocks.empty()) { // §10.7: timespan='none' with no stattype block. // Defensive only — the parser rejects empty `stattype`, // and `hasStatType` is true here, so blocks.size() >= 1. throw Mars2GribDeductionException( "typeOfStatisticalProcessing invariant violated [§10.7]: " "timespan='none' requires exactly one stattype block, got 0", Here()); } const tables::TypeOfStatisticalProcessing parsedOp = blocks[0].typeOfStatisticalProcessing; if (parsedOp != innerTypeOfStatisticalProcessing) { throw Mars2GribDeductionException( std::string("typeOfStatisticalProcessing invariant violated " "[§10.12]: fakeDoubleLoop disagreement: parsed " "stattype block operation ('") + tables::enum2name_TypeOfStatisticalProcessing_or_throw(parsedOp) + "') != innerTypeOfStatisticalProcessing argument ('" + tables::enum2name_TypeOfStatisticalProcessing_or_throw(innerTypeOfStatisticalProcessing) + "')", Here()); } out.reserve(1); out.push_back(innerTypeOfStatisticalProcessing); } else { // Fallback: any other MARS shape (instant, or shapes that // would fail at the `ProductTime` resolver). Per §23.4 the // function is not expected to be called on such inputs; // produce a size-1 vector with the inner type so the caller // sees a defined result if they invoke us defensively. // // Strict §9-case enforcement is the `ProductTime` resolver's // responsibility; we deliberately do not duplicate §10.6 / // §10.7 / §10.8 here. out.reserve(1); out.push_back(innerTypeOfStatisticalProcessing); } // ========================================================= // §23.10: composite RESOLVE log line (success path only) // ========================================================= MARS2GRIB_LOG_RESOLVE([&]() { std::string msg = "`typeOfStatisticalProcessing` resolved from input dictionaries: "; msg += "innerTypeOfStatisticalProcessing='" + tables::enum2name_TypeOfStatisticalProcessing_or_throw(innerTypeOfStatisticalProcessing) + "'"; msg += " size='" + std::to_string(out.size()) + "'"; msg += " typesOfStatisticalProcessing=["; for (std::size_t i = 0; i < out.size(); ++i) { if (i) { msg += ","; } msg += "'" + tables::enum2name_TypeOfStatisticalProcessing_or_throw(out[i]) + "'"; } msg += "]"; return msg; }()); return out; } catch (...) { // §11 / §23.9: nested rethrow with context. std::throw_with_nested(Mars2GribDeductionException("Unable to resolve typeOfStatisticalProcessing", Here())); } // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/channel.h0000664000175000017500000001131615246725757024570 0ustar alastairalastair/* * (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 channel.h /// @brief Deduction of the instrument channel identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **instrument channel identifier** from MARS metadata. /// /// The deduction retrieves the channel 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 satelliteEncoding.h /// /// Related deductions: /// - @ref instrumentType.h /// - @ref satelliteNumber.h /// - @ref satelliteSeries.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 instrument channel identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["channel"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the instrument channel identifier by retrieving /// the mandatory MARS key `channel` and returning its value as a `long`. /// /// No semantic interpretation, normalization, or validation is performed /// beyond basic type conversion. The meaning of the channel identifier is /// defined by upstream metadata conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `channel` /// 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 channel identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved instrument channel identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `channel` 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 instrument metadata or GRIB tables. /// template long resolve_Channel_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 channel long channel = get_or_throw(mars, "channel"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`channel` resolved from input dictionaries: value='"; logMsg += std::to_string(channel); logMsg += "'"; return logMsg; }()); // Success exit point return channel; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `channel` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/paramId.h0000664000175000017500000001006615246725757024536 0ustar alastairalastair/* * (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 paramId.h /// @brief Deduction of the GRIB `paramId` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB parameter identifier** (`paramId`) from input /// dictionaries. /// /// The deduction retrieves the parameter identifier explicitly from the /// MARS dictionary and returns it verbatim as a numeric value. /// No inference, defaulting, normalization, or GRIB table validation is /// performed at this stage. /// /// 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 resolved from one or more input dictionaries /// /// @section References /// Concept: /// - @ref paramEncoding.h /// /// Related deductions: /// - @ref level.h /// - @ref pvArray.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // exception and logging #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 GRIB parameter identifier (`paramId`). /// /// @section Deduction contract /// - Reads: `mars["param"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the GRIB parameter identifier associated with /// the field being encoded. /// /// The value is treated as mandatory and is returned verbatim as a /// numeric identifier. No semantic interpretation or validation against /// GRIB parameter tables is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `param`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the parameter identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved GRIB parameter identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `param` is missing, cannot be converted to `long`, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the parameter identifier is explicitly /// provided by MARS and does not attempt any inference or defaulting. /// template long resolve_ParamId_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 param long paramId = get_or_throw(mars, "param"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`paramId` resolved from input dictionaries: value='"; logMsg += std::to_string(paramId) + "'"; return logMsg; }()); // Success exit point return paramId; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `paramId` from input dictionaries", Here())); } }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/waveFrequencyGrid.h0000664000175000017500000003617515246725757026624 0ustar alastairalastair/* * (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 waveFrequencyGrid.h /// @brief Deduction of the GRIB wave frequency grid. /// /// This header defines the deduction responsible for resolving the /// wave frequency discretization used in spectral wave products. /// /// The deduction produces a frequency grid together with its scaled /// integer representation, suitable for GRIB encoding. /// /// Deductions: /// - extract values from input dictionaries /// - reconstruct frequency grids deterministically when required /// - apply fixed scaling rules /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing reconstruction parameters /// - apply heuristic defaults beyond explicitly defined ones /// - validate physical correctness of frequency values /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: grid obtained directly or deterministically reconstructed /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMin.h /// - @ref periodItMax.h /// - @ref waveDirectionGrid.h /// - @ref waveDirectionNumber.h /// - @ref waveFrequencyNumber.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #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 Metadata and scaled representation of a wave frequency discretization. /// /// This structure describes a wave frequency grid together with its integer, /// scaled representation. It is intended for use in contexts where frequencies /// must be stored or transmitted as integers (e.g. GRIB encoding), while /// retaining a well-defined mapping to physical frequency values in Hz. /// /// The scaling convention used is logarithmic: /// - `scaleFactorFrequencies` represents the base-10 logarithm of the real /// scaling factor applied to the physical frequencies. /// - A value of `scaleFactorFrequencies = 6` corresponds to a real scaling /// factor of \f$10^6\f$. /// /// Scaled integer values are obtained by: /// \f[ /// \text{scaledValue}_i = /// \mathrm{round}\!\left( f_i \times 10^{\text{scaleFactorFrequencies}} \right) /// \f] /// where \f$f_i\f$ is the physical frequency in Hz. /// /// The inverse operation (reconstruction of physical frequencies) is: /// \f[ /// f_i = /// \frac{\text{scaledValue}_i}{10^{\text{scaleFactorFrequencies}}} /// \f] /// /// @note /// This structure is a plain data container and does not enforce consistency /// between `numFrequencies` and the size of `scaledValuesFrequencies`. /// Such validation is expected to be performed by the caller. /// struct WaveFrequencyGrid { /// /// @brief Number of discrete wave frequencies. /// long numFrequencies; /// /// @brief Base-10 logarithm of the real frequency scaling factor. /// /// This value defines the scaling applied to physical frequencies prior to /// integer encoding. For example: /// - `scaleFactorFrequencies = 6` implies a real scaling factor of \f$10^6\f$. /// long scaleFactorFrequencies; /// /// @brief Scaled integer representation of wave frequencies. /// /// Each element is the rounded value of the corresponding physical frequency /// (in Hz) multiplied by \f$10^{\text{scaleFactorFrequencies}}\f$. /// std::vector scaledValuesFrequencies; }; namespace wave_frequency_detail { /// /// @brief Compute a geometrically spaced wave frequency grid. /// /// This function constructs a frequency grid using geometric spacing, /// centered around a reference wave frequency. The reference frequency /// is placed at a specified index, and lower and higher frequencies are /// obtained by successive division or multiplication by a constant ratio. /// /// The discretization follows: /// - fr[indexOfReferenceWaveFrequency] = referenceWaveFrequency /// - fr[i-1] = fr[i] / waveFrequencySpacingRatio /// - fr[i+1] = fr[i] * waveFrequencySpacingRatio /// /// This function is a direct C++ translation of the original Fortran routine /// `MFR` located in: /// - `ecwam/src/ecwam/mfr.F90` /// /// from the ECMWF ECWAM model repository: /// - https://github.com/ecmwf-ifs/ecwam.git /// /// The numerical behavior and discretization logic are preserved exactly. /// /// @param[in] numberOfWaveFrequencies /// Total number of wave frequencies to generate. /// /// @param[in] indexOfReferenceWaveFrequency /// Index (1-based) of the reference wave frequency. /// /// @param[in] referenceWaveFrequency /// Reference wave frequency value. /// /// @param[in] waveFrequencySpacingRatio /// Geometric ratio between two consecutive wave frequencies. /// /// @return /// Vector containing the discretized wave frequency grid. /// /// @throws std::out_of_range /// If `indexOfReferenceWaveFrequency` is outside the valid range. /// /// @note /// The reference index follows Fortran conventions (1-based) to preserve /// compatibility with legacy configurations. /// /// @note /// This implementation assumes geometric spacing of frequencies and does /// not perform validation of the physical consistency of the input values. /// inline std::vector compute_WaveFrequencyGrid(long numberOfWaveFrequencies, long indexOfReferenceWaveFrequency, double referenceWaveFrequency, double waveFrequencySpacingRatio) { if (indexOfReferenceWaveFrequency == 0 || indexOfReferenceWaveFrequency > numberOfWaveFrequencies) { throw std::out_of_range("indexOfReferenceWaveFrequency out of range"); } std::vector waveFrequencies(numberOfWaveFrequencies); // Convert reference index from 1-based (Fortran-style) to 0-based const long ref = indexOfReferenceWaveFrequency - 1; // Reference frequency waveFrequencies[ref] = referenceWaveFrequency; // Frequencies below the reference for (long i = ref; i-- > 0;) { waveFrequencies[i] = waveFrequencies[i + 1] / waveFrequencySpacingRatio; } // Frequencies above the reference for (long i = ref + 1; i < numberOfWaveFrequencies; ++i) { waveFrequencies[i] = waveFrequencySpacingRatio * waveFrequencies[i - 1]; } return waveFrequencies; } /// /// @brief Construct a scaled wave frequency grid from physical frequency values. /// /// This function builds a `WaveFrequencyGrid` structure from a vector of /// physical wave frequencies expressed in Hertz. The physical values are /// converted to an integer representation using a base-10 logarithmic /// scaling factor. /// /// The scaling convention is defined as follows: /// - `scaleFactorOfWaveFrequencies` represents the base-10 logarithm of the /// real scaling factor applied to the physical frequencies. /// - A value of `scaleFactorOfWaveFrequencies = 6` corresponds to a real /// scaling factor of \f$10^6\f$. /// /// Each scaled integer value is computed as: /// \f[ /// \text{scaledValue}_i = /// \mathrm{round}\!\left( f_i \times 10^{\text{scaleFactorOfWaveFrequencies}} \right) /// \f] /// where \f$f_i\f$ is the physical frequency in Hz. /// /// The resulting structure stores: /// - the total number of frequencies, /// - the scaling factor (logarithmic form), /// - the vector of scaled integer frequency values. /// /// This representation is typically used for serialization or encoding /// purposes (e.g. GRIB), where integer storage with a known scaling factor /// is required. /// /// @param[in] waveFrequenciesInHz /// Vector of physical wave frequencies expressed in Hertz. /// /// @param[in] scaleFactorOfWaveFrequencies /// Base-10 logarithm of the scaling factor applied to the frequencies /// prior to integer encoding. /// /// @return /// A `WaveFrequencyGrid` structure containing the scaled integer /// representation of the input frequency grid. /// /// @note /// No validation is performed on the input frequencies (e.g. positivity, /// monotonicity) or on the scaling factor. The caller is responsible for /// ensuring that the provided values are physically meaningful and that /// the scaled values fit within the range of the target integer type. /// inline WaveFrequencyGrid compute_WaveScaledFrequencyGrid(const std::vector& waveFrequenciesInHz, long scaleFactorOfWaveFrequencies) { WaveFrequencyGrid out{}; out.numFrequencies = static_cast(waveFrequenciesInHz.size()); out.scaleFactorFrequencies = scaleFactorOfWaveFrequencies; out.scaledValuesFrequencies.resize(waveFrequenciesInHz.size()); for (long i = 0; i < static_cast(waveFrequenciesInHz.size()); ++i) { out.scaledValuesFrequencies[i] = static_cast(std::round(waveFrequenciesInHz[i] * std::pow(10.0, scaleFactorOfWaveFrequencies))); } return out; } } // namespace wave_frequency_detail /// /// @brief Resolve the GRIB wave frequency grid. /// /// This deduction resolves the wave frequency discretization required /// for spectral wave encoding and produces a scaled integer /// representation suitable for GRIB. /// /// Resolution follows a strict precedence: /// /// 1. **Explicit grid** /// If `par::waveFrequencies` is present, it is interpreted as the /// full frequency grid expressed in Hz. /// /// 2. **Deterministic reconstruction** /// If the explicit grid is not present, the grid is reconstructed /// provided that all of the following keys exist: /// - `numberOfWaveFrequencies` /// - `indexOfReferenceWaveFrequency` /// - `referenceWaveFrequency` /// - `waveFrequencySpacingRatio` /// /// The resulting grid is scaled using: /// - `scaleFactorOfWaveFrequencies` (optional, defaults to `6`) /// /// @tparam OptDict_t Type of the options dictionary (unused) /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// /// @param[in] opt Options dictionary (unused) /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary providing frequency information /// /// @return A fully populated `WaveFrequencyGrid` /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - neither explicit frequencies nor reconstruction parameters exist /// - a required key is missing or has an invalid type /// - reconstruction or scaling fails /// template WaveFrequencyGrid resolve_WaveFrequencyGrid_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { 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::Mars2GribDeductionException; try { WaveFrequencyGrid out{}; std::vector waveFrequenciesInHz; // Retrieve optional scaling factor from parameter dictionary long scaleFactorOfWaveFrequencies = get_opt(par, "scaleFactorOfWaveFrequencies").value_or(6L); // Check for explicit frequency grid bool hasWaveFrequencies = has(par, "waveFrequencies"); // Check for reconstruction parameters bool hasNumberOfWaveFrequencies = has(par, "numberOfWaveFrequencies"); bool hasIndexOfReferenceWaveFrequency = has(par, "indexOfReferenceWaveFrequency"); bool hasReferenceWaveFrequency = has(par, "referenceWaveFrequency"); bool hasWaveFrequencySpacingRatio = has(par, "waveFrequencySpacingRatio"); bool canReconstructWaveFrequencies = hasNumberOfWaveFrequencies && hasIndexOfReferenceWaveFrequency && hasReferenceWaveFrequency && hasWaveFrequencySpacingRatio; if (hasWaveFrequencies) { // Retrieve mandatory wave frequencies from parameter dictionary waveFrequenciesInHz = get_or_throw>(par, "waveFrequencies"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveFrequencyGrid` resolved from input dictionaries"; return logMsg; }()); } else if (canReconstructWaveFrequencies && !hasWaveFrequencies) { // Retrieve mandatory reconstruction parameters from parameter dictionary long numberOfWaveFrequencies = get_or_throw(par, "numberOfWaveFrequencies"); long indexOfReferenceWaveFrequency = get_or_throw(par, "indexOfReferenceWaveFrequency"); double referenceWaveFrequency = get_or_throw(par, "referenceWaveFrequency"); double waveFrequencySpacingRatio = get_or_throw(par, "waveFrequencySpacingRatio"); // Reconstruct frequency grid deterministically waveFrequenciesInHz = wave_frequency_detail::compute_WaveFrequencyGrid(numberOfWaveFrequencies, indexOfReferenceWaveFrequency, referenceWaveFrequency, waveFrequencySpacingRatio); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveFrequencyGrid` reconstructed from input dictionaries with params={"; logMsg += std::to_string(numberOfWaveFrequencies) + ", "; logMsg += std::to_string(indexOfReferenceWaveFrequency) + ", "; logMsg += std::to_string(referenceWaveFrequency) + ", "; logMsg += std::to_string(waveFrequencySpacingRatio) + "}"; return logMsg; }()); } else { /// @todo Need a defaulting strategy for: [`numberOfWaveFrequencies`, `indexOfReferenceWaveFrequency`, /// `referenceWaveFrequency`, `waveFrequencySpacingRatio`] throw Mars2GribDeductionException( "Default value NOT IMPLEMENTED for: [`numberOfWaveFrequencies`, `indexOfReferenceWaveFrequency`, " "`referenceWaveFrequency`, `waveFrequencySpacingRatio`]", Here()); } // Build the scaled frequency grid out = wave_frequency_detail::compute_WaveScaledFrequencyGrid(waveFrequenciesInHz, scaleFactorOfWaveFrequencies); // Success exit point return out; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `waveFrequencyGrid` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductionsmetkit-1.20.2/src/metkit/mars2grib/backend/deductions/centre.h0000664000175000017500000001074715246725757024447 0ustar alastairalastair/* * (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 centre.h /// @brief Deduction of the GRIB `centre` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB centre identifier** from MARS metadata. /// /// The deduction retrieves the originating centre identifier directly /// from the MARS dictionary and exposes it to the encoding layer without /// transformation. /// /// 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 normalization or defaulting unless explicitly defined /// - 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 originEncoding.h /// /// Related deductions: /// - @ref subCentre.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 GRIB `centre` identifier from MARS metadata. /// /// @section Deduction contract /// - Reads: `mars["origin"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the GRIB `centre` identifier by retrieving the /// optional MARS key `origin` and returning its value verbatim. /// /// No normalization, translation, or defaulting is applied at this stage. /// Any semantic interpretation or mapping to numeric GRIB centre codes /// must be handled by downstream encoding logic. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `origin` /// and conversion to `std::string`. /// /// @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 originating centre identifier is read. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The originating centre identifier as provided by MARS. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If any unexpected error occurs during deduction. /// /// @note /// This deduction enforces presence-only validation and does not /// consult GRIB centre code tables. /// template std::string resolve_Centre_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve (optional) MARS origin std::string origin = get_opt(mars, "origin").value_or("ecmf"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`centre` resolved from input dictionaries: value='"; logMsg += origin + "'"; return logMsg; }()); // Success exit point return origin; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `origin` from input dictionaries", Here())); } }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/shapeOfTheEarth.h0000664000175000017500000001240115246725757026166 0ustar alastairalastair/* * (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 shapeOfTheEarth.h /// @brief Deduction of the GRIB shape of the Earth. /// /// This header defines the deduction responsible for resolving the /// GRIB `shapeOfTheEarth` key used to describe the reference system /// of the Earth in GRIB messages. /// /// The deduction currently applies a fixed, deterministic value /// corresponding to a spherical Earth with radius 6371229 m. /// /// Deductions: /// - extract values from input dictionaries (currently unused) /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer values from MARS metadata /// - apply configuration-based defaults /// - validate against GRIB code tables /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - DEFAULT: value defaulted by the deduction /// /// @section References /// Concept: /// - @ref shapeOfTheEarthEncoding.h /// /// Related deductions: /// (none) /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Tables #include "metkit/mars2grib/backend/tables/shapeOfTheReferenceSystem.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `shapeOfTheEarth` key. /// /// @section Deduction contract /// - Reads: none /// - Writes: none /// - Side effects: logging (DEFAULT) /// - Failure mode: none (deterministic) /// /// This deduction resolves the GRIB `shapeOfTheEarth` key by /// applying a fixed, deterministic value corresponding to /// a spherical Earth with radius 6371229 m when the value is not specified in the parameter dictionary. /// /// No inference from MARS, or options dictionaries is currently performed. /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return /// The resolved `ShapeOfTheReferenceSystem` enumeration value. /// /// @note /// This function is the **single authoritative deduction** /// for `shapeOfTheEarth`. /// /// @todo [owner: mds,dgov][scope: deduction][reason: extensibility][prio: low] /// - Introduce inference or configuration-based selection when /// non-spherical Earth representations are required. /// template tables::ShapeOfTheReferenceSystem resolve_ShapeOfTheEarth_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { if (has(par, "shapeOfTheEarth")) { // Retrieve the value from the parameter dictionary long shapeOfTheEarthId = get_or_throw(par, "shapeOfTheEarth"); // Convert to enumeration tables::ShapeOfTheReferenceSystem shapeOfTheEarth = tables::long2enum_ShapeOfTheReferenceSystem_or_throw(shapeOfTheEarthId); // Logging of the channel MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`shapeOfTheEarth` resolved from PAR dictionary: value='"; logMsg += tables::enum2name_ShapeOfTheReferenceSystem_or_throw(shapeOfTheEarth); logMsg += "'"; return logMsg; }()); // Success exit point return shapeOfTheEarth; } else { // Default value tables::ShapeOfTheReferenceSystem shapeOfTheEarth = tables::ShapeOfTheReferenceSystem::EarthSphericalRadius6371229; // Logging of the channel MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`shapeOfTheEarth` defaulted with value='"; logMsg += tables::enum2name_ShapeOfTheReferenceSystem_or_throw(shapeOfTheEarth); logMsg += "'"; return logMsg; }()); // Success exit point return shapeOfTheEarth; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `shapeOfTheEarth` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/typeOfTimeIncrement.h0000664000175000017500000001042215246725757027107 0ustar alastairalastair/* * (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 typeOfTimeIncrement.h /// @brief Public deduction header for `typeOfTimeIncrement`. /// /// Exposes `resolve_TypeOfTimeIncrement_or_throw`, the designated deduction /// boundary for GRIB Code Table 4.11 resolution used by ProductTimeSpec. /// /// The concrete deduction logic is intentionally still undefined. The current /// implementation fails loudly so callers do not accidentally depend on /// unspecified behavior while the boundary already exists in the architecture. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/tables/typeOfTimeIntervals.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve `typeOfTimeIncrement`. /// /// @section Deduction contract /// - Reads (MARS): none yet /// - Reads (par): none yet /// - Reads (opt): none yet /// - Writes: none /// - Side effects: none /// - Failure mode: throws `Mars2GribDeductionException` /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary (reserved for future logic). /// @param[in] par Parameter dictionary (reserved for future logic). /// @param[in] opt Options dictionary (reserved for future logic). /// /// @return The resolved GRIB `tables::TypeOfTimeIntervals` value. /// /// @note The current implementation is a placeholder. It always returns /// `tables::TypeOfTimeIntervals::SameStartTimeForecastIncremented` and does not /// perform any deduction logic. /// template metkit::mars2grib::backend::tables::TypeOfTimeIntervals resolve_TypeOfTimeIncrement_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory class from MARS dictionary std::string marsClass = get_or_throw(mars, "class"); // Retrieve mandatory type from MARS dictionary std::string marsType = get_or_throw(mars, "type"); // Retrieve mandatory stream from MARS dictionary std::string marsStream = get_or_throw(mars, "stream"); /// Initialize default metkit::mars2grib::backend::tables::TypeOfTimeIntervals result = metkit::mars2grib::backend::tables::TypeOfTimeIntervals::SameStartTimeForecastIncremented; if (marsClass == "a5" && marsStream == "sttd") { result = metkit::mars2grib::backend::tables::TypeOfTimeIntervals::SameForecastTimeStartIncremented; } if (marsClass == "e6" && (marsStream == "sttd" || marsStream == "stte")) { result = metkit::mars2grib::backend::tables::TypeOfTimeIntervals::SameForecastTimeStartIncremented; } if (marsClass == "od" && marsStream == "mnth") { result = metkit::mars2grib::backend::tables::TypeOfTimeIntervals::SameForecastTimeStartIncremented; } // Success exit point return result; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `typeOfTimeIncrement` from input dictionaries", Here())); } return metkit::mars2grib::backend::tables::TypeOfTimeIntervals::SameStartTimeForecastIncremented; } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/allowedReferenceValue.h0000664000175000017500000002161115246725757027422 0ustar alastairalastair/* * (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 allowedReferenceValue.h /// @brief Deduction of the GRIB `allowedReferenceValue`. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB allowed reference value** associated with a given /// MARS parameter identifier. /// /// The deduction retrieves the MARS parameter code from the input dictionaries /// and derives a representative reference value based on a statically defined /// set of admissible value ranges. /// /// 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, normalization, or defaulting beyond explicit rules /// - 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 /// /// @section References /// Concept: /// - @ref marsEncoding.h /// /// Related deductions: /// - @ref class.h /// - @ref expver.h /// - @ref stream.h /// - @ref type.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #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 GRIB allowed reference value from input dictionaries. /// /// @section Deduction contract /// - Reads: exactly one of `mars["grid"]` or `mars["truncation"]`; `mars["param"]` for grid-point products /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the GRIB `allowedReferenceValue` by first determining /// whether the request describes a grid-point or spectral representation. Exactly /// one of `grid` or `truncation` must be present in the MARS dictionary. /// /// For grid-point products, the mandatory MARS parameter identifier (`param`) is /// retrieved and used to consult a statically defined table of admissible /// reference value ranges. For parameters with an explicit range definition, the /// resolved reference value is chosen as the midpoint of the corresponding /// `[min, max]` interval. If no explicit range is defined for the parameter, a /// default reference value of `0.0` is returned. /// /// For spectral products identified by `truncation`, the resolved reference /// value is `0.0`. /// /// No semantic interpretation beyond the explicit range table is applied. /// The admissible ranges are defined locally and are not validated against /// external GRIB tables. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support presence checks for `grid` and /// `truncation`; grid-point products must also support keyed access to `param` /// and conversion to an integral type. /// /// @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 parameter identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved allowed reference value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If neither or both of `grid` and `truncation` are present, if `param` is /// missing or malformed for a grid-point product, or if any unexpected error /// occurs during deduction. /// /// @note /// This deduction applies a local, table-driven rule and does not /// consult GRIB metadata tables or apply parameter-specific semantics. /// template double resolve_AllowedReferenceValue_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Static map of allowed reference value ranges keyed by parameter code static const std::unordered_map> param_ranges = { {3, {170.0, 1200.0}}, {10, {0.0, 300.0}}, {31, {-0.00001, 1.001}}, {33, {10.0, 1000.0}}, {34, {160.0, 320.0}}, {43, {0.0, 10.0}}, {49, {0.0, 100.0}}, {54, {100.0, 108000.0}}, {59, {0.0, 40000.0}}, {60, {-1.0, 1.0}}, {121, {160.0, 380.0}}, {122, {150.0, 330.0}}, {129, {-13000.0, 3500000.0}}, {130, {140.0, 400.0}}, {131, {-250.0, 250.0}}, {132, {-250.0, 250.0}}, {133, {-0.1, 0.1}}, {134, {43000.0, 115000.0}}, {135, {-30.0, 30.0}}, {136, {-50.0, 220.0}}, {151, {85000.0, 125000.0}}, {156, {-1300.0, 35000.0}}, {157, {0.0, 180.0}}, {164, {0.0, 1.0}}, {165, {-150.0, 150.0}}, {166, {-100.0, 100.0}}, {167, {160.0, 370.0}}, {168, {25.0, 350.0}}, {172, {0.0, 1.0}}, {173, {0.0, 10.0}}, {186, {0.0, 1.0}}, {187, {0.0, 1.0}}, {188, {0.0, 1.0}}, {207, {0.0, 300.0}}, {235, {120.0, 380.0}}, {246, {-0.001, 1e6}}, {247, {-0.001, 0.01}}, {3031, {0.0, 360.1}}, {3062, {-0.05, 130.0}}, {3066, {0.0, 5.0}}, {3073, {0.0, 100.0}}, {3074, {0.0, 100.0}}, {3075, {0.0, 100.0}}, {140230, {-1.0, 360.5}}, {151131, {-3.5, 3.5}}, {151132, {-3.5, 3.5}}, {151145, {-4.0, 4.0}}, {228001, {-60000.0, 1000.0}}, {228002, {-1300.0, 8888.0}}, {228004, {160.0, 370.0}}, {228005, {0.0, 300.0}}, {228006, {0.0, 1.0}}, {228141, {-1e-10, 15000.0}}, {260057, {-3.0, 150.0}}, {260259, {-10.0, 5.0}}, {260260, {0.0, 360.1}}, {262101, {160.0, 320.0}}, {262140, {-3.5, 3.5}}, {262501, {173.0, 1000.0}}, {263101, {160.0, 320.0}}, {263140, {-3.5, 3.5}}, {263501, {173.0, 1000.0}}, }; const bool hasGrid = has(mars, "grid"); const bool hasTruncation = has(mars, "truncation"); if (hasGrid == hasTruncation) { throw Mars2GribDeductionException( "`allowedReferenceValue` requires exactly one of MARS keys `grid` or `truncation`", Here()); } if (hasTruncation) { MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`allowedReferenceValue` resolved for spectral representation: value='0.000000'"}; }()); return 0.0; } // Default reference value for grid-point products double ret = 0.0; // Retrieve mandatory MARS parameter identifier long marsParamVal = get_or_throw(mars, "param"); // Lookup allowed value in the mid of the allowed range if (auto rangeIt = param_ranges.find(marsParamVal); rangeIt != param_ranges.end()) { const auto& [minVal, maxVal] = rangeIt->second; ret = 0.5 * (minVal + maxVal); } // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`allowedReferenceValue` resolved from input dictionaries: value='" + std::to_string(ret) + "'"; return logMsg; }()); // Success exit point return ret; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `allowedReferenceValue` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/typeOfProcessedData.h0000664000175000017500000001650615246725757027076 0ustar alastairalastair/* * (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 typeOfProcessedData.h /// @brief Deduction of the GRIB `typeOfProcessedData` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `typeOfProcessedData` key (GRIB2 Code Table 4.10). /// /// The value may be explicitly provided via parametrization or, /// if absent, deterministically deduced from MARS metadata. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values beyond documented rules /// - guess semantics not defined by policy /// - apply implicit defaults without explicit mapping /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - OVERRIDE: explicit value provided via parameter dictionary /// - RESOLVE: value deduced from MARS metadata /// /// @section References /// Concept: /// - @ref dataTypeEncoding.h /// /// Related deductions: /// - @ref productionStatusOfProcessedData.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Tables includes #include "metkit/mars2grib/backend/tables/typeOfProcessedData.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `typeOfProcessedData` key. /// /// This deduction determines the value of the GRIB /// `typeOfProcessedData` key using the following precedence: /// /// 1. **User override (parameter dictionary)** /// If the key `typeOfProcessedData` is present in the parameter /// dictionary, its value is taken as authoritative. The value may /// be provided either as: /// - a numeric GRIB code (`long`) /// - a symbolic GRIB name (`string`) /// /// 2. **Automatic deduction (MARS dictionary)** /// If no override is provided, the value is deduced from /// `mars::type` using a fixed, explicitly defined mapping. /// /// Unsupported or unmapped MARS types result in the value /// `TypeOfProcessedData::Missing`. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary providing `class`, `type`, and `stream` /// @param[in] par Parameter dictionary providing optional override /// @param[in] opt Options dictionary (unused) /// /// @return The resolved `TypeOfProcessedData` enumeration value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - override value is present but invalid /// - dictionary access fails /// - any unexpected error occurs during deduction /// template tables::TypeOfProcessedData resolve_TypeOfProcessedData_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory class from MARS dictionary std::string marsClass = get_or_throw(mars, "class"); // Retrieve mandatory type from MARS dictionary std::string marsType = get_or_throw(mars, "type"); // Retrieve mandatory stream from MARS dictionary std::string marsStream = get_or_throw(mars, "stream"); // Return value tables::TypeOfProcessedData result = tables::TypeOfProcessedData::Missing; if (has(par, "typeOfProcessedData")) { // Retrieve mandatory override from parameter dictionary if (has(par, "typeOfProcessedData")) { long typeOfProcessedDataVal = get_or_throw(par, "typeOfProcessedData"); // Convert long to enum (validate) result = tables::long2enum_TypeOfProcessedData_or_throw(typeOfProcessedDataVal); } else if (has(par, "typeOfProcessedData")) { std::string typeOfProcessedDataVal = get_or_throw(par, "typeOfProcessedData"); // Convert string to enum (validate) result = tables::name2enum_TypeOfProcessedData_or_throw(typeOfProcessedDataVal); } else { throw Mars2GribDeductionException( "Key `typeOfProcessedData` is not of expected type `long` or `string`", Here()); } // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`typeOfProcessedData` overridden from parameter dictionary: value='"; logMsg += enum2name_TypeOfProcessedData_or_throw(result); logMsg += "'"; return logMsg; }()); } else { // Deduce typeOfProcessedData from mars class and type if (marsClass == "ai") { // Mars class only for AI output result = tables::TypeOfProcessedData::MlBasedForecast; } else { // Mars type for everything else if (marsType == "an") { result = tables::TypeOfProcessedData::AnalysisProducts; } else if (marsType == "fc" || marsType == "ssd") { result = tables::TypeOfProcessedData::ForecastProducts; } else if (marsType == "pf") { result = tables::TypeOfProcessedData::PerturbedForecastProducts; } else if (marsType == "cf") { result = tables::TypeOfProcessedData::ControlForecastProducts; } else if (marsType == "gsd") { result = tables::TypeOfProcessedData::ProcessedSatelliteObservations; } else { result = tables::TypeOfProcessedData::Missing; } } // Emit OVERRIDE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`typeOfProcessedData` resolved from input dictionaries: value='"; logMsg += enum2name_TypeOfProcessedData_or_throw(result); logMsg += "'"; return logMsg; }()); } // Success exit point return result; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `typeOfProcessedData` from input dictionaries", Here())); } }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/totalNumberOfIterations.h0000664000175000017500000001427715246725757030014 0ustar alastairalastair/* * (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 totalNumberOfIterations.h /// @brief Deduction of the GRIB totalNumberOfIterations (in seconds). /// /// This header defines the deduction used by the mars2grib backend to resolve /// the GRIB totalNumberOfIterations key from input dictionaries. /// /// The deduction reads the parameter dictionary entry totalNumberOfIterations, /// interprets it as hours, and converts it to seconds. If the key is missing, /// the deduction returns `std::nullopt`. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying explicit, deterministic deduction logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys /// - do NOT infer units or values beyond the documented rule /// - 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 from the parameter dictionary /// - DEFAULT: value defaulted to the GRIB missing code /// /// @section References /// Concept: /// - @ref analysisEncoding.h /// /// Related deductions: /// - @ref offsetToEndOf4DvarWindow.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System Include #include #include #include // Other project includes #include "eckit/log/Log.h" // 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 GRIB `totalNumberOfIterations` expressed in seconds. /// /// This deduction determines the value of the GRIB `totalNumberOfIterations` /// (in seconds) based on the parameter dictionary key `totalNumberOfIterations`. /// /// The deduction follows these rules: /// /// - If the key `totalNumberOfIterations` is present in the parameter dictionary, /// its value is interpreted as **hours** and converted to seconds. /// - If the key is absent, `std::nullopt` is used, which should be handled /// by the encoding layer as the GRIB missing code (0xFFFF). /// /// @important /// This deduction currently relies on **implicit assumptions** about /// units and defaults that are not explicitly encoded in MARS metadata. /// These assumptions are documented but not enforced via validation. /// /// @assumptions /// - `par::totalNumberOfIterations` is expressed in **hours** /// - Default value is `std::nullopt` when the key is missing /// /// @warning /// - These assumptions may not be valid for all datasets. /// - Relying on implicit defaults may lead to non-reproducible GRIB output /// if upstream conventions change. /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary /// @param[in] opt Options dictionary (unused) /// /// @return The length of time window in seconds. If `par::totalNumberOfIterations` is missing, /// returns `std::nullopt`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - access to the parameter dictionary fails /// - the retrieved value cannot be interpreted as a valid integer /// - any unexpected error occurs during deduction /// /// @todo [owner: mds,dgov][scope: deduction][reason: correctness][prio: medium] /// - Make the unit of `totalNumberOfIterations` explicit instead of assuming hours. /// - Add explicit validation of allowed ranges and units. /// /// @note /// - This deduction does not rely on any pre-existing GRIB header state. /// - Logging intentionally emits RESOLVE/DEFAULT entries to highlight implicit assumptions. /// template std::optional resolve_TotalNumberOfIterations_opt(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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Big assumption here: // - totalNumberOfIterations is in hours if (has(par, "totalNumberOfIterations")) { long totalNumberOfIterationsVal = get_or_throw(par, "totalNumberOfIterations"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`totalNumberOfIterations` resolved from input dictionaries: value='"; logMsg += std::to_string(totalNumberOfIterationsVal); return logMsg; }()); // Success exit point return {totalNumberOfIterationsVal}; // Convert hours to seconds } else { // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`totalNumberOfIterations` defaulted to MISSING (nullopt)"; return logMsg; }()); // Success exit point return std::nullopt; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Unable to get `totalNumberOfIterations` from Par dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/derivedForecast.h0000664000175000017500000001502315246725757026270 0ustar alastairalastair/* * (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 derivedForecast.h /// @brief Deduction of the GRIB `derivedForecast` key. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB derived forecast type** (GRIB2 Code Table 4.7). /// /// The deduction supports both explicit user override and automatic /// deduction from MARS metadata, following a strict precedence order. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying explicit validation and mapping logic /// - returning strongly typed GRIB table values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply implicit defaults outside explicitly defined rules /// - do NOT perform GRIB table validation beyond table lookups /// /// Error handling follows a strict fail-fast strategy: /// - missing or unsupported 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 derivedEncoding.h /// /// Related deductions: /// - @ref numberOfForecastsInEnsemble.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Tables #include "metkit/mars2grib/backend/tables/derivedForecast.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `derivedForecast` key. /// /// @section Deduction contract /// - Reads: /// - `par["derivedForecast"]` (if present), /// - otherwise `mars["type"]` /// - Writes: none /// - Side effects: logging (RESOLVE or OVERRIDE) /// - Failure mode: throws /// /// This deduction resolves the GRIB `derivedForecast` value following a /// strict precedence order. /// /// If the parameter dictionary provides `derivedForecast`, the value is /// treated as authoritative and validated via the GRIB code table. /// Otherwise, the value is intended to be deduced from the MARS key /// `type`. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide `type` if automatic /// deduction is required. /// /// @tparam ParDict_t /// Type of the parameter dictionary. May contain `derivedForecast` /// as a numeric override. /// /// @tparam OptDict_t /// Type of the options dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary providing metadata for automatic deduction. /// /// @param[in] par /// Parameter dictionary optionally providing `derivedForecast`. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved `tables::DerivedForecast` enumeration value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If an override value is invalid, if required MARS metadata is /// missing, if automatic deduction is not supported, or if any /// unexpected error occurs. /// template tables::DerivedForecast resolve_DerivedForecast_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Default to Missing tables::DerivedForecast derivedForecast = tables::DerivedForecast::Missing; if (has(par, "derivedForecast")) { // Retrieve override from parameter dictionary const auto derivedForecastVal = get_or_throw(par, "derivedForecast"); // Lookup and validate enum value derivedForecast = tables::long2enum_DerivedForecast_or_throw(derivedForecastVal); // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`derivedForecast` overridden from parameter dictionary: value='"; logMsg += tables::enum2name_DerivedForecast_or_throw(derivedForecast); logMsg += "'"; return logMsg; }()); // Success exit point return derivedForecast; } else { const auto marsType = get_or_throw(mars, "type"); if (marsType == "em" || marsType == "taem") { derivedForecast = tables::DerivedForecast::UnweightedMeanAllMembers; } else if (marsType == "es" || marsType == "ses" || marsType == "taes") { derivedForecast = tables::DerivedForecast::SpreadAllMembers; } else if (marsType == "efi" || marsType == "efic" || marsType == "sot") { derivedForecast = tables::DerivedForecast::LargeAnomalyIndexAllMembers; } else { throw Mars2GribDeductionException( "Mapping from mars type " + marsType + " to derivedForecast is not implemented", Here()); } // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`derivedForecast` resolved from input dictionaries: value='"; logMsg += tables::enum2name_DerivedForecast_or_throw(derivedForecast); logMsg += "'"; return logMsg; }()); // Success exit point return derivedForecast; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `derivedForecast` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/timespan.h0000664000175000017500000002655315246725757025011 0ustar alastairalastair/* * (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 timespan.h /// @brief Public deduction header for `timespan`. /// /// Exposes `resolve_Timespan_opt` and `resolve_Timespan_or_throw`, the /// canonical entry points that resolve the normalized MARS `timespan` /// representation from input dictionaries. /// /// This deduction owns: /// - direct `timespan` dictionary access; /// - classification of the supported `timespan` source language into /// `Missing`, `Duration`, `None`, or `FromStart`; /// - local lexical validation of string and integer representations; /// - application of the compatibility option /// `allowNonEnumeratedPositiveIntegerTimespanHours`. /// /// This deduction does NOT: /// - classify ProductTimeSpec shape semantics beyond the local source form; /// - combine `timespan` with `stattype` or `step`; /// - construct the final ProductTimeSpec model. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/backend/deductions/detail/timeDurationHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { namespace detail { /// /// @brief Test whether an integer-hour timespan belongs to the supported set. /// /// @param[in] hours Integer-hour duration. /// @return `true` when `hours` is language-enumerated for the supported domain. /// inline bool isSupportedTimespanHours(long hours) { constexpr std::array supported{1, 3, 6, 12, 18, 24, 48, 72, 120, 168, 240, 360}; return std::find(supported.begin(), supported.end(), hours) != supported.end(); } /// /// @brief Test whether a textual timespan belongs to the supported set. /// /// @param[in] value Lowercase `timespan` token. /// @return `true` when `value` is language-enumerated for the supported domain. /// inline bool isSupportedTimespanString(const std::string& value) { constexpr std::array supported{"1h", "3h", "6h", "12h", "18h", "24h", "48h", "72h", "120h", "168h", "240h", "360h"}; return std::find(supported.begin(), supported.end(), value) != supported.end(); } /// /// @brief Test whether a textual timespan is recognized but unsupported. /// /// @param[in] value Lowercase `timespan` token. /// @return `true` when `value` is known but intentionally outside the current /// supported domain. /// inline bool isRecognizedUnsupportedTimespan(const std::string& value) { return value == "inst" || value == "instantaneous" || value == "10m" || value == "15m" || value == "20m" || value == "30m"; } /// /// @brief Read the integer-hour compatibility policy for `timespan`. /// /// The option accepts boolean, `0`/`1`, or boolean-like string forms. /// /// @tparam OptDict_t Options dictionary type. /// @param[in] opt Options dictionary. /// @return The resolved compatibility flag. /// @throws Mars2GribDeductionException if the option is present but not a valid /// boolean representation. /// template inline bool allowNonEnumeratedPositiveIntegerTimespanHours(const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; constexpr std::string_view key = "allowNonEnumeratedPositiveIntegerTimespanHours"; if (!has(opt, key)) { return metkit::mars2grib::defaults::allowNonEnumeratedPositiveIntegerTimespanHours; } if (auto value = get_opt(opt, key)) { return *value; } throw Mars2GribDeductionException("Invalid boolean option `allowNonEnumeratedPositiveIntegerTimespanHours`", Here()); } } // namespace detail /// /// @brief Resolve `timespan` as an optional normalized source representation. /// /// @section Deduction contract /// - Reads (MARS): `timespan` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): `allowNonEnumeratedPositiveIntegerTimespanHours` /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `timespan` absent -> `std::nullopt`; /// - `timespan="none"` -> `Timespan{None, nullopt}`; /// - `timespan` from-start aliases -> `Timespan{FromStart, nullopt}`; /// - supported duration-valued `timespan` -> `Timespan{Duration, duration}`; /// - recognized but unsupported or invalid values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary providing `timespan`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary providing the integer-hour compatibility /// policy. /// /// @return `std::optional` containing the normalized source /// representation when `timespan` is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or locally invalid `timespan` input, with /// the original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Timespan_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; try { if (!has(mars, "timespan")) { return std::nullopt; } Timespan result; if (auto value = get_opt(mars, "timespan")) { const std::string normalized = detail::lower(*value); if (normalized == "none") { result.kind = TimespanKind::None; } else if (normalized == "fs" || normalized == "from-start" || normalized == "fromstart") { result.kind = TimespanKind::FromStart; } else { if (detail::isRecognizedUnsupportedTimespan(normalized)) { throw Mars2GribDeductionException("Recognized but unsupported `timespan`: '" + *value + "'", Here()); } if (!detail::isSupportedTimespanString(normalized)) { throw Mars2GribDeductionException( "String `timespan` is not in the supported language-defined set: '" + *value + "'", Here()); } const long seconds = detail::parseDurationStringSeconds(normalized, "timespan"); if (seconds <= 0 || seconds % 3600L != 0) { throw Mars2GribDeductionException( "String `timespan` is not in the supported language-defined set: '" + *value + "'", Here()); } result.kind = TimespanKind::Duration; result.duration = detail::canonicalElapsedDuration(seconds, "timespan"); } } else if (auto value = get_opt(mars, "timespan")) { if (*value < 1) { throw Mars2GribDeductionException("Integer `timespan` must be >= 1 hour", Here()); } if (!detail::allowNonEnumeratedPositiveIntegerTimespanHours(opt) && !detail::isSupportedTimespanHours(*value)) { throw Mars2GribDeductionException( "Integer `timespan` is not language-enumerated and compatibility option is disabled", Here()); } result.kind = TimespanKind::Duration; result.duration = TimeDuration{*value, tables::TimeUnit::Hour}; } else { throw Mars2GribDeductionException("Unsupported type for `timespan`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { std::string msg = "`timespan` resolved from input dictionaries: kind='"; switch (result.kind) { case TimespanKind::Missing: msg += "missing"; break; case TimespanKind::Duration: msg += "duration"; break; case TimespanKind::None: msg += "none"; break; case TimespanKind::FromStart: msg += "from-start"; break; } msg += "'"; if (result.duration.has_value()) { msg += " length='" + std::to_string(result.duration->length) + "' unit='" + tables::enum2name_TimeUnit_or_throw(result.duration->unit) + "'"; } return msg; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `timespan` from input dictionaries", Here())); } mars2gribUnreachable(); } /// /// @brief Resolve `timespan` or throw if absent. /// /// Thin wrapper around `resolve_Timespan_opt` that converts `std::nullopt` into /// a hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary providing `timespan`. /// @param[in] par Parameter dictionary (forwarded). /// @param[in] opt Options dictionary (forwarded). /// /// @return The normalized `timespan` source representation. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// if the source is absent, malformed, unsupported, or locally invalid; /// failures are wrapped via `std::throw_with_nested`. /// template Timespan resolve_Timespan_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { const auto result = resolve_Timespan_opt(mars, par, opt); if (result.has_value()) { return *result; } else { return Timespan{TimespanKind::Missing, std::nullopt}; } } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Unable to get `timespan` from Mars dictionary", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/level.h0000664000175000017500000001155215246725757024271 0ustar alastairalastair/* * (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 level.h /// @brief Deduction of the vertical level identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **vertical level identifier** from MARS metadata. /// /// The deduction retrieves the level identifier directly from the /// MARS dictionary and exposes it to the encoding layer without /// transformation or interpretation. /// /// The semantic interpretation of the level value (e.g. pressure level, /// model level index) depends on the associated level type (`levtype`) /// and is handled by downstream encoding logic. /// /// 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, normalization, or defaulting /// - 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 levelEncoding.h /// /// Related deductions: /// - @ref levtype.h /// - @ref pvArray.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 vertical level identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["levelist"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the vertical level identifier by retrieving /// the mandatory MARS key `levelist` and returning its value as a /// `long`. /// /// No semantic interpretation or validation of the level value is /// performed. The meaning of the level identifier depends on the /// associated level type (`levtype`) and is handled elsewhere. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to /// `levelist` 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 level identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved vertical level identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `levelist` is missing, cannot be retrieved as a /// `long`, or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult level definition tables or apply unit conversions. /// template long resolve_Level_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 level identifier long marsLevelistVal = get_or_throw(mars, "levelist"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`level` resolved from input dictionaries: value='"; logMsg += std::to_string(marsLevelistVal) + "'"; return logMsg; }()); // Success exit point return marsLevelistVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `levelist` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/scaledValueOfCentralWaveNumber.h0000664000175000017500000001062715246725757031206 0ustar alastairalastair/* * (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 scaledValueOfCentralWaveNumber.h /// @brief Deduction of the GRIB `scaledValueOfCentralWaveNumber` key. /// /// This file defines the deduction responsible for resolving the GRIB /// `scaledValueOfCentralWaveNumber` key used in satellite-based products. /// /// The value is provided explicitly via the parameter dictionary and is /// combined at encoding time with `scaleFactorOfCentralWaveNumber` to /// represent the central wave number. /// /// This deduction: /// - reads exclusively from the parameter dictionary /// - applies no inference, defaulting, or validation /// - emits structured diagnostic logging /// /// Error handling follows a fail-fast strategy with nested exception /// propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref satelliteEncoding.h /// /// Related deductions: /// - @ref scaleFactorOfCentralWaveNumber.h /// - @ref channel.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 GRIB `scaledValueOfCentralWaveNumber` identifier. /// /// @section Deduction contract /// - Reads: `par["scaledValueOfCentralWaveNumber"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the scaled value used to encode the central /// wave number in satellite products. /// /// The value is retrieved verbatim from the parameter dictionary. /// No inference from MARS metadata and no consistency validation with /// `scaleFactorOfCentralWaveNumber` is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide /// `scaledValueOfCentralWaveNumber`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary containing the scaled value. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Scaled value of the central wave number. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `scaledValueOfCentralWaveNumber` is missing, cannot be /// retrieved as a `long`, or if any unexpected error occurs. /// /// @note /// This deduction is deterministic and independent of GRIB header state. /// template long resolve_ScaledValueOfCentralWaveNumber_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 scaled value of central wave number from parameter dictionary auto scaledValueOfCentralWaveNumberVal = get_or_throw(par, "scaledValueOfCentralWaveNumber"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`scaledValueOfCentralWaveNumber` resolved from parameter dictionary: value='"; logMsg += std::to_string(scaledValueOfCentralWaveNumberVal); logMsg += "'"; return logMsg; }()); // Success exit point return scaledValueOfCentralWaveNumberVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `scaledValueOfCentralWaveNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/year.h0000664000175000017500000001000715246725757024114 0ustar alastairalastair/* * (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 year.h /// @brief Public deduction header for the optional raw MARS `year` key. /// /// Exposes `resolve_Year_opt`, the canonical entry point that resolves the raw /// optional MARS `year` source into a normalized integer value. /// /// This deduction owns: /// - direct `year` dictionary access; /// - lexical parsing of integer and string integer representations. /// /// This deduction does NOT: /// - validate the relationship between `year` and `month`; /// - construct a date or datetime; /// - apply any ProductTimeSpec-specific semantics. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 optional raw MARS `year` source. /// /// @section Deduction contract /// - Reads (MARS): `year` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `year` absent -> `std::nullopt`; /// - numeric `year` -> returned verbatim; /// - string `year` -> parsed as one complete decimal integer; /// - malformed or unsupported values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `year`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the normalized raw MARS year when /// the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed or unsupported raw `year` input, with the original /// cause attached via `std::throw_with_nested`. /// template std::optional resolve_Year_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "year")) { return std::nullopt; } long result = 0; if (auto value = get_opt(mars, "year")) { result = *value; } else if (auto value = get_opt(mars, "year")) { result = detail::parseLongStrict(*value, "year"); } else { throw Mars2GribDeductionException("Unsupported type for `year`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`year` resolved from input dictionaries: value='"} + std::to_string(result) + "'"; }()); return result; } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Failed to resolve `year` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/generation.h0000664000175000017500000001136715246725757025321 0ustar alastairalastair/* * (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 generation.h /// @brief Deduction of the MARS `generation` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **generation identifier** from MARS metadata. /// /// The deduction retrieves the generation 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, normalization, or defaulting /// - 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 destineEncoding.h /// /// Related deductions: /// - @ref activity.h /// - @ref dataset.h /// - @ref experiment.h /// - @ref model.h /// - @ref realization.h /// - @ref resolution.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 generation identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["generation"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the generation identifier by retrieving the /// mandatory MARS key `generation` and returning its value as a `long`. /// /// No semantic interpretation, normalization, or validation is applied. /// The meaning and allowed values of the generation identifier are /// defined by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to /// `generation` 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 generation identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved generation identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `generation` is missing, cannot be retrieved as a /// `long`, or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult generation registries or GRIB tables. /// template long resolve_Generation_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 generation long marsGenerationVal = get_or_throw(mars, "generation"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`generation` resolved from input dictionaries: value='" + std::to_string(marsGenerationVal) + "'"; return logMsg; }()); // Success exit point return marsGenerationVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `generation` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/timeIncrementInSeconds.h0000664000175000017500000001345315246725757027575 0ustar alastairalastair/* * (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 timeIncrementInSeconds.h /// @brief Deduction of the optional `timeIncrementInSeconds` parameter. /// /// Resolves the `timeIncrementInSeconds` key from the parameter dictionary. /// The value, when present and strictly positive, denotes the inner time /// increment (in seconds) of a statistical product (the spacing between /// the samples consumed by the innermost statistical operation, e.g. the /// 1-hour spacing of an hourly accumulation). /// /// Normalization rules: /// - absent key -> `std::nullopt` /// - present, value `0` -> `std::nullopt` (legacy normalization) /// - present, value `> 0` -> `value` /// - present, value `< 0` -> hard error /// /// Deductions: /// - extract value from the parameter dictionary /// - apply normalization rule above /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate against external GRIB code tables /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// @section References /// Consumer: /// - @ref productTime.h (consumes `timeIncrementInSeconds_opt` to populate /// the `ProductTime::timeIncrementInSeconds` field) /// /// Specification: /// - `deductions/timeProducts.md` §7.8 (`timeIncrementInSeconds` keyword) /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #include #include #include // Core deduction includes #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve `timeIncrementInSeconds` as an optional value. /// /// Reads the `timeIncrementInSeconds` key from the parameter dictionary and /// applies the normalization rules described in the file-level /// documentation. /// /// @tparam MarsDict_t MARS dictionary type (unused; kept for symmetry with /// sibling deductions). /// @tparam ParDict_t Parameter dictionary type. /// @param mars MARS dictionary (unused). /// @param par Parameter dictionary; queried for /// `timeIncrementInSeconds`. /// @return `std::optional` holding the strictly-positive seconds value /// when present, `std::nullopt` otherwise (key absent or value `0`). /// @throws Mars2GribDeductionException if the key is present and the value /// is strictly negative, or if any underlying dictionary access /// throws (rethrown nested with diagnostic context). /// template std::optional timeIncrementInSeconds_opt(const MarsDict_t& mars, const ParDict_t& par) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { auto lengthOfTimeStepInSeconds_opt = get_opt(par, "timeIncrementInSeconds"); if (lengthOfTimeStepInSeconds_opt.has_value()) { if (lengthOfTimeStepInSeconds_opt.value() < 0) { throw Mars2GribDeductionException("`timeIncrementInSeconds` must be > 0 if present", Here()); } else if (lengthOfTimeStepInSeconds_opt.value() == 0) { lengthOfTimeStepInSeconds_opt = std::nullopt; } } return lengthOfTimeStepInSeconds_opt; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Unable to get `timeIncrementInSeconds` from Mars dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; /// /// @brief Resolve `timeIncrementInSeconds` or throw if absent. /// /// Thin wrapper around `timeIncrementInSeconds_opt` that converts a /// `std::nullopt` result into a hard error. Use when the consumer requires /// a definite value. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @param mars MARS dictionary (forwarded). /// @param par Parameter dictionary (forwarded). /// @return The strictly-positive seconds value. /// @throws Mars2GribDeductionException if the value is absent (key missing /// or normalized to `std::nullopt`), strictly negative, or if any /// underlying dictionary access throws. /// template long timeIncrementInSeconds_or_throw(const MarsDict_t& mars, const ParDict_t& par) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { std::optional timeIncrementInSecondsOpt = timeIncrementInSeconds_opt(mars, par); if (timeIncrementInSecondsOpt.has_value()) { return timeIncrementInSecondsOpt.value(); } else { throw Mars2GribDeductionException("`timeIncrementInSeconds` is not defined in Mars/Par dictionary", Here()); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Unable to get `timeIncrementInSeconds` from Mars dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/realization.h0000664000175000017500000001162415246725757025503 0ustar alastairalastair/* * (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 realization.h /// @brief Deduction of the GRIB realization identifier. /// /// This file defines the deduction responsible for resolving the /// **realization identifier**, which distinguishes individual realizations /// (members) within an ensemble, stochastic system, or DestinE workflow. /// /// The realization identifier is retrieved explicitly from the MARS /// dictionary and passed verbatim to the encoder. No inference, defaulting, /// or normalization is performed. /// /// Deductions in this file are responsible for: /// - extracting realization metadata from the MARS dictionary /// - applying deterministic, fail-fast resolution rules /// - returning a strongly typed value to the encoding layer /// /// Deductions: /// - do NOT infer realization semantics /// - do NOT validate ensemble consistency /// - do NOT depend on pre-existing GRIB header state /// /// Error handling follows a strict fail-fast policy: /// - missing or invalid inputs cause immediate failure /// - errors are reported using Mars2Grib deduction exceptions /// - original errors are preserved via nested exception propagation /// /// Logging follows the mars2grib deduction policy: /// - RESOLVE: value retrieved directly from the MARS dictionary /// /// @section References /// Concept: /// - @ref destineEncoding.h /// /// Related deductions: /// - @ref activity.h /// - @ref experiment.h /// - @ref generation.h /// - @ref model.h /// - @ref resolution.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 realization identifier from the MARS dictionary. /// /// @section Deduction contract /// - Reads: `mars["realization"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the mandatory `realization` key from the /// MARS dictionary and returns it as a numeric identifier. /// /// The realization identifier is used to distinguish individual /// realizations within ensemble or DestinE products. Its numerical /// semantics are defined by upstream MARS and encoding conventions /// and are not interpreted here. /// /// No inference, defaulting, or validation of the realization value /// is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `realization`. /// /// @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 providing the realization identifier. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The realization identifier as provided by the MARS dictionary. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `realization` is missing from the MARS dictionary /// - the value cannot be converted to `long` /// - any unexpected error occurs during deduction /// /// @note /// - This deduction is fully deterministic. /// - The returned value is passed verbatim to downstream encoding logic. /// template long resolve_Realization_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 realization long marsRealizationVal = get_or_throw(mars, "realization"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`realization` resolved from MARS dictionary: value='"; logMsg += std::to_string(marsRealizationVal); logMsg += "'"; return logMsg; }()); // Success exit point return marsRealizationVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `realization` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/experiment.h0000664000175000017500000001137615246725757025346 0ustar alastairalastair/* * (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 experiment.h /// @brief Deduction of the MARS `experiment` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **experiment identifier** from MARS metadata. /// /// The deduction retrieves the experiment 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, normalization, or defaulting /// - 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 destineEncoding.h /// /// Related deductions: /// - @ref activity.h /// - @ref dataset.h /// - @ref generation.h /// - @ref model.h /// - @ref realization.h /// - @ref resolution.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 experiment identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["experiment"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the experiment identifier by retrieving the /// mandatory MARS key `experiment` and returning its value as a /// `std::string`. /// /// No semantic interpretation, normalization, or validation is applied. /// The meaning and allowed values of the experiment identifier are /// defined by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to /// `experiment` and conversion to `std::string`. /// /// @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 experiment identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved experiment identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `experiment` is missing, cannot be retrieved as a /// string, or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult experiment registries or GRIB tables. /// template std::string resolve_Experiment_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 experiment std::string marsExperimentVal = get_or_throw(mars, "experiment"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`experiment` resolved from input dictionaries: value='" + marsExperimentVal + "'"; return logMsg; }()); // Success exit point return marsExperimentVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `experiment` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/productionStatusOfProcessedData.h0000664000175000017500000001541515246725757031505 0ustar alastairalastair/* * (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 productionStatusOfProcessedData.h /// @brief Deduction of the GRIB `productionStatusOfProcessedData` key (Code Table 1.3). /// /// This header defines the deduction used by the mars2grib backend to resolve /// `productionStatusOfProcessedData`, which characterises the production status /// of the processed data product. /// /// The deduction currently implements a minimal mapping based on MARS metadata: /// - `mars["class"] == "d1"` → `DestinationEarth` /// - `mars["class"] == "e6"` → `ReanalysisProducts` /// - otherwise → `OperationalProducts` (default) /// /// The resolved value is returned as a strongly typed table enum and is intended /// to be consumed by concept operations (deductions do not encode GRIB keys). /// /// Logging policy: /// - RESOLVE: value derived via deduction logic from input dictionaries /// - DEFAULT: value defaulted to a predefined constant when no specific mapping rule applies /// /// Error handling: /// - missing required inputs or unexpected failures throw `Mars2GribDeductionException` /// - underlying errors are preserved via nested exception propagation /// /// @section References /// Concept: /// - @ref dataTypeEncoding.h /// /// Related deductions: /// - @ref typeOfProcessedData.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include // Table includes #include "metkit/mars2grib/backend/tables/productionStatusOfProcessedData.h" #include "metkit/mars2grib/utils/generalUtils.h" // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the GRIB `productionStatusOfProcessedData` key from input dictionaries. /// /// This deduction resolves `productionStatusOfProcessedData` using MARS metadata. /// /// Current rules: /// - If `mars["class"] == "d1"`, return `DestinationEarth`. /// - If `mars["class"] == "e6"`, return `ReanalysisProducts`. /// - Otherwise, return `OperationalProducts`. /// /// @section Deduction contract /// - Reads: `mars["class"]` /// - Writes: none /// - Side effects: logging (RESOLVE,DEFAULT) /// - Failure mode: throws /// /// @tparam MarsDict_t /// Type of the MARS dictionary; must provide key `class`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (currently unused). /// /// @tparam OptDict_t /// Type of the options dictionary (currently unused). /// /// @param[in] mars /// MARS dictionary used to resolve `productionStatusOfProcessedData`. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved `tables::ProductionStatusOfProcessedData` enumeration value. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If required inputs are missing or if any unexpected error occurs during deduction. /// /// @note /// This deduction currently does not implement any override path via `par`. /// If an override mechanism is introduced, successful override must emit an OVERRIDE log entry. /// template tables::ProductionStatusOfProcessedData resolve_ProductionStatusOfProcessedData_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 { // TODO: Here we set the default to operational product; will need to clarify exact logic with DGOV // It will probably need to be inferred from "type", "class", "stream" auto productionStatusOfProcessedDataDefault = tables::ProductionStatusOfProcessedData::OperationalProducts; std::optional productionStatusOfProcessedData = std::nullopt; // Retrieve mandatory MARS class const auto marsClass = get_or_throw(mars, "class"); // Deduce productionStatusOfProcessedData from mars class if (marsClass == "d1") { // This is mandatory for DestinE because it is used inside eccodes to allocate the proper "localUseSection" // template. Setting this keyword reallocate the local use section. productionStatusOfProcessedData = tables::ProductionStatusOfProcessedData::DestinationEarth; } else if (marsClass == "e6") { // Special handling for ERA6 productionStatusOfProcessedData = tables::ProductionStatusOfProcessedData::ReanalysisProducts; } // Emit log entry based on deduction outcome if (productionStatusOfProcessedData.has_value()) { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`productionStatusOfProcessedData` resolved from input dictionaries: value='"; logMsg += enum2name_ProductionStatusOfProcessedData_or_throw(productionStatusOfProcessedData.value()); logMsg += "'"; return logMsg; }()); // Success exit point return productionStatusOfProcessedData.value(); } else { // Default to operational product productionStatusOfProcessedData = productionStatusOfProcessedDataDefault; // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`productionStatusOfProcessedData` defaulted to operational product: value='"; logMsg += enum2name_ProductionStatusOfProcessedData_or_throw(productionStatusOfProcessedData.value()); logMsg += "'"; return logMsg; }()); // Success exit point return productionStatusOfProcessedData.value(); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `productionStatusOfProcessedData` from input dictionaries", Here())); } // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/perturbationNumber.h0000664000175000017500000000677515246725757027064 0ustar alastairalastair/* * (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 perturbationNumber.h /// @brief Resolve the perturbation (ensemble member) number from the MARS dictionary. /// /// This deduction retrieves the value associated with the MARS key `number`, /// which identifies the perturbation (ensemble member) for the encoded field. /// /// The value is treated as mandatory and is retrieved verbatim from the input /// dictionaries without inference, defaulting, or transformation. /// /// @section Deduction contract /// - Reads: `mars["number"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// @tparam MarsDict_t /// Type of the MARS dictionary; must provide the key `number`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the perturbation number is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The perturbation number resolved from input dictionaries. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `number` is missing, cannot be converted to `long`, or if any /// unexpected error occurs during deduction. /// /// @note /// This deduction performs no consistency validation against ensemble size. /// Cross-field constraints are handled by other ensemble deductions. /// /// @section References /// Concept: /// - @ref ensembleEncoding.h /// /// Related deductions: /// - @ref numberOfForecastsInEnsemble.h /// - @ref typeOfEnsembleForecast.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 { template long resolve_PerturbationNumber_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 perturbation number from input dictionaries auto perturbationNumber = get_or_throw(mars, "number"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`number` resolved from input dictionaries: value='"; logMsg += std::to_string(perturbationNumber); logMsg += "'"; return logMsg; }()); // Success exit point return perturbationNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `number` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/isSynoptic.h0000664000175000017500000000714615246725757025332 0ustar alastairalastair/* * (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 isSynoptic.h /// @brief Public deduction header for the normalized ProductTimeSpec synoptic flag. /// /// Exposes `resolve_IsSynoptic_or_throw`, the canonical entry point that derives /// the normalized synoptic/non-synoptic ProductTimeSpec classification from the /// raw MARS `stream` key. /// /// This deduction owns: /// - direct `stream` dictionary access; /// - the ProductTimeSpec-specific mapping from MARS stream to the normalized /// synoptic flag. /// /// This deduction does NOT: /// - classify simulation regime or simulation type; /// - construct ProductTimeSpec model artifacts; /// - validate unrelated product policies. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 whether the normalized product is synoptic. /// /// @section Deduction contract /// - Reads (MARS): `stream` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - MARS stream `mnth` -> `true`; /// - any other present MARS stream -> `false`; /// - missing or malformed `stream` -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `stream`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `true` when the normalized product is synoptic, `false` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on missing, malformed, or unsupported raw `stream` input, with the /// original cause attached via `std::throw_with_nested`. /// template bool resolve_IsSynoptic_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; (void)par; (void)opt; try { const std::string stream = get_or_throw(mars, "stream"); const bool result = stream == "mnth"; MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`isSynoptic` resolved from input dictionaries: value='"} + (result ? "true" : "false") + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `isSynoptic` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/fcmonth.h0000664000175000017500000001304515246725757024617 0ustar alastairalastair/* * (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 fcmonth.h /// @brief Public deduction header for the optional raw MARS `fcmonth` key. /// /// Exposes `resolve_Fcmonth_opt`, the canonical entry point that resolves the /// raw optional MARS `fcmonth` source into a validated canonical integer /// forecast lead count expressed in calendar months. /// /// This deduction owns: /// - direct `fcmonth` dictionary access; /// - lexical parsing of integer and textual decimal representations; /// - local validation that the resolved forecast lead count is strictly /// positive. /// /// This deduction does NOT: /// - materialize a `TimeDuration` value; /// - validate the relationship between `fcmonth` and any other temporal key; /// - apply ProductTimeSpec model semantics beyond local source validation. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { namespace detail { /// /// @brief Validate one canonical integer forecast lead count expressed in months. /// /// The MARS `fcmonth` keyword is interpreted as a strictly positive integer lead /// count measured in calendar months. /// /// @param[in] value Canonical integer forecast lead count. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return `value` unchanged when it is strictly positive. /// @throws Mars2GribDeductionException if `value` is not strictly positive. /// inline long checkedPositiveFcmonthLeadCount(long value, const std::string& key) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { if (value <= 0) { throw Mars2GribDeductionException("`" + key + "` must be a strictly positive integer", Here()); } return value; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to validate `fcmonth` as a positive forecast lead count", Here())); } } } // namespace detail /// /// @brief Resolve the optional raw MARS `fcmonth` source. /// /// @section Deduction contract /// - Reads (MARS): `fcmonth` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `fcmonth` absent -> `std::nullopt`; /// - numeric `fcmonth` -> validated as a strictly positive integer lead count; /// - string `fcmonth` -> parsed as a decimal integer, then validated as a /// strictly positive integer lead count; /// - malformed, unsupported, or out-of-range values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `fcmonth`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the canonical integer forecast lead /// count in months when the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or locally invalid raw `fcmonth` input, /// with the original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Fcmonth_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "fcmonth")) { return std::nullopt; } long result = 0; if (auto value = get_opt(mars, "fcmonth")) { result = detail::checkedPositiveFcmonthLeadCount(*value, "fcmonth"); } else if (auto value = get_opt(mars, "fcmonth")) { result = detail::checkedPositiveFcmonthLeadCount(detail::parseLongStrict(*value, "fcmonth"), "fcmonth"); } else { throw Mars2GribDeductionException("Unsupported type for `fcmonth`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`fcmonth` resolved from input dictionaries: value='"} + std::to_string(result) + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `fcmonth` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/numberOfComponents.h0000664000175000017500000001144215246725757027003 0ustar alastairalastair/* * (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 numberOfComponents.h /// @brief Deduction of the model-error ensemble size. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **total number of model-error realizations** /// (`numberOfComponents`) from the parameter dictionary. /// /// The value is not derivable from MARS alone. It must be supplied via /// the parameter dictionary by the upstream tool, typically read from /// the input GRIB1 handle being re-encoded. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref modelErrorEncoding.h /// /// Related deductions: /// - @ref componentIndex.h /// - @ref modelErrorType.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the total number of model-error realizations. /// /// @section Deduction contract /// - Reads: `par["numberOfComponents"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the total number of realizations in the /// model-error ensemble from the parameter dictionary. /// /// The value is treated as mandatory: it cannot be derived from MARS /// metadata alone and must be supplied by the upstream tool that /// populates the parameter dictionary (typically read from the input /// GRIB1 handle being re-encoded). /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide the key /// `numberOfComponents`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which the ensemble size is retrieved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The total number of model-error realizations. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `numberOfComponents` is missing from the parameter /// dictionary, cannot be converted to `long`, or if any unexpected error /// occurs during deduction. /// /// @note /// This deduction does not infer or default the value. Absence of the /// key in the parameter dictionary is considered a contract violation /// by the upstream tool. /// template long resolve_NumberOfComponents_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 parameter-dictionary numberOfComponents long numberOfComponents = get_or_throw(par, "numberOfComponents"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`numberOfComponents` resolved from input dictionaries: value="; logMsg += std::to_string(numberOfComponents); return logMsg; }()); // Success exit point return numberOfComponents; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `numberOfComponents` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/deductions.md0000664000175000017500000001002715246725757025470 0ustar alastairalastair@page mars2grib_deductions Deductions Deductions are small, single-purpose components used by the MARS-to-GRIB backend to resolve GRIB keys from input metadata. Each deduction reads specific keys from one or more dictionaries and deterministically produces a single GRIB value (or an optional value when the mapping is intentionally conservative). Each deduction is responsible for **exactly one GRIB key**. This one-to-one mapping makes the resolution logic explicit, traceable, and easy to audit. @section mars2grib_deductions_role Role in the pipeline Deductions sit between input metadata (MARS, parameter, and options dictionaries) and GRIB encoding. Their responsibilities are intentionally narrow: - Extract required inputs from dictionaries. - Apply explicit, deterministic mapping rules. - Return resolved values or raise a detailed error when resolution is not possible. - Emit structured diagnostic logs describing how a value was obtained. Deductions do **not**: - Infer missing values unless explicitly defined. - Apply silent fallbacks. - Validate physical or semantic correctness beyond documented rules. - Rely on pre-existing GRIB header state. @section mars2grib_deductions_inputs Input dictionaries Most deductions operate on these dictionary types: - **MARS dictionary**: request metadata (e.g. @c type, @c stream, @c param, @c timespan). - **Parameter dictionary**: user overrides or explicit encoding choices (e.g. @c tablesVersion, @c typeOfProcessedData). - **Options dictionary**: configuration flags controlling optional behavior. Unless a deduction explicitly declares a default or an optional mapping, required inputs must be present. @section mars2grib_deductions_errors Error handling Deductions follow a fail-fast strategy. When a required input is missing or invalid, the deduction throws a nested exception that preserves context. This makes failures traceable while avoiding silent behavior changes. @section mars2grib_deductions_logging Logging policy Deductions emit structured logs for traceability. Typical policy categories include: - **RESOLVE**: value derived from input dictionaries or deterministic mappings. - **OVERRIDE**: value explicitly set by the parameter dictionary. - **DEFAULT**: value supplied by a documented default rule. Logging statements must match the actual path taken in the code. @section mars2grib_deductions_debugging Debugging advantages The one-to-one relationship between a deduction and a GRIB key significantly simplifies debugging. Most error reports have the form: @code grib key "xxx" has the wrong value, it is supposed to be "a" instead is "b" @endcode Because each GRIB key is produced by a single, well-identified deduction: - The faulty component is immediately known from the key name. - The resolution path is localized and deterministic. - The input dictionaries and mapping rules involved are limited and explicit. - The structured logs describe exactly how the incorrect value was obtained. In practice, this makes it almost immediate to identify the logic that generated the error and to reproduce the issue. @section mars2grib_deductions_determinism Determinism and reproducibility Given the same inputs, deductions return the same outputs. Any dependence on runtime state (e.g. reading ecCodes defaults) must be explicit and documented in the deduction that performs it. @section mars2grib_deductions_new How to add a new deduction When implementing a new deduction: 1. Define the required inputs and where they are sourced. 2. Document mapping rules and defaults (if any). 3. Implement a single, deterministic resolution path. 4. Emit the correct log category based on how the value is obtained. 5. Throw a detailed exception on failure. 6. Bind the deduction to the single GRIB key it is responsible for. @section mars2grib_deductions_summary Summary Deductions are the authoritative mapping layer between MARS metadata and GRIB encoding. They are intentionally strict, deterministic, and auditable, ensuring reproducible behavior, clear diagnostics, and fast error localization across the backend.metkit-1.20.2/src/metkit/mars2grib/backend/deductions/firstWavelength.h0000664000175000017500000001161615246725757026337 0ustar alastairalastair/* * (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 metkit-1.20.2/src/metkit/mars2grib/backend/deductions/methodNumber.h0000664000175000017500000001076715246725757025622 0ustar alastairalastair/* * (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 methodNumber.h /// @brief Deduction of the wave processing method identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **wave processing method identifier** (`methodNumber`) /// from MARS metadata. /// /// The deduction retrieves the method identifier explicitly from the /// MARS dictionary and returns it verbatim, without applying inference, /// defaulting, or semantic interpretation. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref longrangeEncoding.h /// /// Related deductions: /// - @ref systemNumber.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the wave processing method identifier. /// /// @section Deduction contract /// - Reads: `mars["method"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the wave processing method identifier /// from the MARS dictionary. /// /// The value is treated as mandatory and is returned verbatim as a /// numeric identifier. No inference, defaulting, or validation against /// GRIB code tables is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `method`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the method identifier is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The wave processing method identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `method` is missing, cannot be converted to `long`, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the method identifier is explicitly /// provided by the MARS dictionary and does not attempt any semantic /// interpretation or consistency checking. /// template long resolve_MethodNumber_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 method long methodNumber = get_or_throw(mars, "method"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`methodNumber` resolved from input dictionaries: value="; logMsg += std::to_string(methodNumber); return logMsg; }()); // Success exit point return methodNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `method` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/date.h0000664000175000017500000001040215246725757024070 0ustar alastairalastair/* * (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 date.h /// @brief Public deduction header for the optional raw MARS `date` key. /// /// Exposes `resolve_Date_opt`, the canonical entry point that resolves the raw /// optional MARS `date` source into a validated `eckit::Date`. /// /// This deduction owns: /// - direct `date` dictionary access; /// - lexical parsing of the supported raw MARS `date` representations; /// - local validation that the parsed fields form a valid calendar date. /// /// This deduction does NOT: /// - read any other temporal key; /// - apply defaulting or cross-key validation; /// - construct a `DateTime` or any higher-level temporal artifact. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include "eckit/types/Date.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/dateTimeHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 optional raw MARS `date` source. /// /// @section Deduction contract /// - Reads (MARS): `date` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `date` absent -> `std::nullopt`; /// - numeric `date` -> parsed as `YYYYMMDD`; /// - string `date` -> parsed as `YYYYMMDD` or `YYYY-MM-DD`; /// - malformed, unsupported, or invalid values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `date`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the validated raw MARS date /// when the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or invalid raw `date` input, with the /// original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Date_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "date")) { return std::nullopt; } eckit::Date result{}; if (auto value = get_opt(mars, "date")) { result = detail::parseDateLong(*value, "date"); } else if (auto value = get_opt(mars, "date")) { result = detail::parseDateString(*value, "date"); } else { throw Mars2GribDeductionException("Unsupported type for `date`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { std::ostringstream out; out << result; return std::string{"`date` resolved from input dictionaries: value='"} + out.str() + "'"; }()); return result; } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Failed to resolve `date` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/0000775000175000017500000000000015246725757024247 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/pv_137_be.h0000664000175000017500000015421315246725757026113 0ustar alastairalastair/* * (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 detail/pv_137_be.h /// @brief Predefined PV coefficient table extracted from IFS binary output. /// /// This file contains a statically defined table of PV coefficients used by /// the PV lookup and decoding infrastructure in the mars2grib backend. /// /// The values in this table have been obtained by performing a **binary dump /// in the IFS** of the PV array associated with a specific hybrid vertical /// coordinate configuration. /// /// The dumped binary values were: /// - captured verbatim from IFS runtime memory, /// - interpreted as IEEE754 double-precision floating-point values, /// - serialized into a C++ source representation without any numerical /// transformation or rounding. /// /// ## Encoding format /// - Each coefficient is stored as an **8-byte IEEE754 binary64 value** /// - Byte order is **big-endian**, independent of the host architecture /// - The table is therefore **endianness-neutral** and requires explicit /// decoding at runtime /// /// Endianness handling is performed explicitly by the PV decoding utilities /// in the mars2grib backend, which: /// - detect the host byte order at runtime, /// - apply byte swapping when required, /// - reconstruct native `double` values in a portable and deterministic way /// /// ## Design constraints /// - This file contains **data only** /// - No logic, validation, or interpretation is performed here /// - The numerical meaning of the PV coefficients is **not validated** /// - The data are assumed to be physically correct as provided by IFS /// /// ## Usage /// This table is intended to be: /// - included verbatim into the PV lookup infrastructure, /// - accessed via compile-time lookup tables, /// - decoded into native floating-point values only at the point of use /// /// @note /// The content of this file is tightly coupled to the IFS binary format /// and the specific PV configuration from which it was extracted. /// Any change in IFS vertical coordinate definitions requires regenerating /// this table from a fresh binary dump. /// /// @note /// No attempt must be made to modify, regenerate, or reinterpret the /// coefficients manually. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include "metkit/mars2grib/utils/generalUtils.h" static constexpr std::array pv_137_1002_be = { {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x00, 0x00, 0xbf, 0x60, 0x00, 0x00, 0x00}, {0x40, 0x08, 0xd1, 0x63, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x12, 0xaa, 0x11, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0x1b, 0x4f, 0xd9, 0x40, 0x00, 0x00, 0x00}, {0x40, 0x23, 0x7e, 0x72, 0x60, 0x00, 0x00, 0x00}, {0x40, 0x2b, 0x35, 0xfa, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x32, 0x9b, 0xe2, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0x38, 0xfc, 0x58, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x40, 0x7e, 0x2b, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x45, 0x70, 0x8b, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x4b, 0x7a, 0x4c, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0x51, 0x61, 0x51, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x55, 0xb9, 0x56, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x5a, 0xda, 0x9b, 0x80, 0x00, 0x00, 0x00}, {0x40, 0x60, 0x6d, 0x9d, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x63, 0xe8, 0xf0, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0x67, 0xea, 0xd5, 0x80, 0x00, 0x00, 0x00}, {0x40, 0x6c, 0x7f, 0x01, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0x70, 0xd8, 0xa2, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x73, 0xc6, 0xbb, 0x60, 0x00, 0x00, 0x00}, {0x40, 0x77, 0x0f, 0xb7, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x7a, 0xb9, 0x7a, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0x7e, 0xc9, 0xdb, 0x40, 0x00, 0x00, 0x00}, {0x40, 0x81, 0xa3, 0x4e, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x84, 0x1a, 0xb8, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x86, 0xcd, 0xf4, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x89, 0xbf, 0xbe, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x8c, 0xf2, 0xc2, 0x60, 0x00, 0x00, 0x00}, {0x40, 0x90, 0x34, 0xce, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x92, 0x13, 0x6a, 0x20, 0x00, 0x00, 0x00}, {0x40, 0x94, 0x16, 0x71, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x96, 0x3f, 0x14, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0x98, 0x8e, 0x7d, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0x9b, 0x05, 0xcb, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0x9d, 0xa6, 0x13, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xa0, 0x38, 0x31, 0x20, 0x00, 0x00, 0x00}, {0x40, 0xa1, 0xb2, 0xdd, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xa3, 0x43, 0x8a, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xa4, 0xea, 0xb2, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xa6, 0xa8, 0xc8, 0x60, 0x00, 0x00, 0x00}, {0x40, 0xa8, 0x7e, 0x3d, 0x20, 0x00, 0x00, 0x00}, {0x40, 0xaa, 0x6b, 0x7c, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xac, 0x70, 0xef, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xae, 0x8e, 0xfb, 0x20, 0x00, 0x00, 0x00}, {0x40, 0xb0, 0x62, 0xee, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xb1, 0x8a, 0xd1, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xb2, 0xbf, 0x26, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xb3, 0xff, 0xe5, 0x20, 0x00, 0x00, 0x00}, {0x40, 0xb5, 0x4c, 0xfd, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xb6, 0xa6, 0x58, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xb8, 0x0c, 0x13, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xb9, 0x7e, 0xf2, 0x60, 0x00, 0x00, 0x00}, {0x40, 0xba, 0xff, 0xde, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0xbc, 0x8f, 0xde, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xbe, 0x2f, 0x69, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xbf, 0xdf, 0x5a, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xc0, 0xd0, 0x43, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xc1, 0xba, 0x33, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xc2, 0xad, 0x57, 0x60, 0x00, 0x00, 0x00}, {0x40, 0xc3, 0xa8, 0xfd, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xc4, 0xac, 0x50, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0xc5, 0xb6, 0x54, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xc6, 0xc6, 0x08, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xc7, 0xd9, 0xc6, 0x20, 0x00, 0x00, 0x00}, {0x40, 0xc8, 0xef, 0x6f, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xca, 0x06, 0x55, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xcb, 0x1c, 0xaa, 0x60, 0x00, 0x00, 0x00}, {0x40, 0xcc, 0x30, 0x11, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0xcd, 0x3f, 0xce, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xce, 0x4a, 0x20, 0xe0, 0x00, 0x00, 0x00}, {0x40, 0xcf, 0x4d, 0x0e, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd0, 0x23, 0xd4, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xd0, 0x9c, 0x32, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd1, 0x0e, 0xe7, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xd1, 0x7b, 0x67, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd1, 0xe1, 0x1b, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0x3f, 0x6e, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0x95, 0xd2, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0xe3, 0xe0, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x29, 0x02, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x64, 0xd9, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x96, 0xfb, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xbe, 0xea, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xdc, 0x77, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xef, 0x13, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xf6, 0x85, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xf2, 0x6e, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xe2, 0x74, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0xc6, 0x60, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x9d, 0xc5, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x68, 0x81, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xd3, 0x26, 0x24, 0xa0, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0xd6, 0x8e, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0x79, 0x5d, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xd2, 0x0e, 0x6d, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xd1, 0x95, 0xbb, 0x40, 0x00, 0x00, 0x00}, {0x40, 0xd1, 0x0f, 0xf5, 0xc0, 0x00, 0x00, 0x00}, {0x40, 0xd0, 0x7e, 0x2c, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xcf, 0xc3, 0x06, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xce, 0x76, 0x59, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xcd, 0x19, 0x3a, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xcb, 0xae, 0xa9, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xca, 0x39, 0xe2, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xc8, 0xbe, 0x21, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xc7, 0x3e, 0xab, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xc5, 0xbe, 0xa7, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xc4, 0x41, 0x16, 0x80, 0x00, 0x00, 0x00}, {0x40, 0xc2, 0xc8, 0xc2, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xc1, 0x58, 0x3a, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xbf, 0xe3, 0x60, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xbd, 0x2e, 0x58, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xba, 0x94, 0x6c, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xb8, 0x18, 0x88, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xb5, 0xbc, 0x62, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xb3, 0x81, 0xcc, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xb1, 0x69, 0x60, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xae, 0xe7, 0xec, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xab, 0x42, 0x78, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xa7, 0xe2, 0x88, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xa4, 0xc6, 0x48, 0x00, 0x00, 0x00, 0x00}, {0x40, 0xa1, 0xec, 0x7c, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x9e, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x99, 0xed, 0xe8, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x95, 0xae, 0x30, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x91, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x8c, 0xf4, 0x10, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x86, 0xf7, 0xf0, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x81, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x7a, 0x86, 0xa0, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x72, 0xe7, 0xa0, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x69, 0x4f, 0x80, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x5e, 0x86, 0x80, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x4f, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x36, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x40, 0x0e, 0x10, 0x00, 0x40, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x3e, 0x64, 0x82, 0x2b, 0x00, 0x00, 0x00, 0x00}, {0x3e, 0xdc, 0x5b, 0x3f, 0x20, 0x00, 0x00, 0x00}, {0x3e, 0xf9, 0x87, 0xdd, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x0e, 0xe4, 0x60, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0x1d, 0x56, 0x71, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0x2a, 0x07, 0x26, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x36, 0x4e, 0x9f, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0x42, 0x66, 0xaa, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x4d, 0x27, 0x53, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0x56, 0x2a, 0x13, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x60, 0x51, 0x2f, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x67, 0x67, 0xd2, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0x70, 0x43, 0xd8, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0x76, 0x07, 0x0c, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0x7d, 0x37, 0xe3, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0x82, 0xf7, 0xac, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0x88, 0x2d, 0xc1, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0x8e, 0x57, 0xa8, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0x92, 0xc2, 0x12, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0x96, 0xe4, 0x30, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0x9b, 0x9c, 0x53, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xa0, 0x79, 0x62, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xa3, 0x78, 0x32, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xa6, 0xce, 0xfd, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xaa, 0x81, 0xfe, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xae, 0x94, 0xb9, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xb1, 0x85, 0xd3, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xb3, 0xf5, 0x13, 0x60, 0x00, 0x00, 0x00}, {0x3f, 0xb6, 0x99, 0xe4, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0xb9, 0x76, 0x51, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0xbc, 0x8b, 0x91, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xbf, 0xdb, 0xd5, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xc1, 0xb4, 0x3c, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xc3, 0x99, 0x99, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0xc5, 0x9e, 0xdb, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xc7, 0xc4, 0xac, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xca, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xcc, 0x75, 0x67, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xcf, 0x01, 0xda, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xd0, 0xd8, 0xf4, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xd2, 0x43, 0x3d, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xd3, 0xc0, 0x13, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xd5, 0x4e, 0xdf, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xd6, 0xed, 0xa3, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xd8, 0x99, 0x68, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xda, 0x4f, 0xde, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xdc, 0x0e, 0x9a, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xdd, 0xd2, 0xff, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xdf, 0x9a, 0x6c, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xe0, 0xb1, 0x1a, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xe1, 0x93, 0xe0, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xe2, 0x74, 0x43, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xe3, 0x51, 0x15, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xe4, 0x29, 0x40, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0xe4, 0xfb, 0xca, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xe5, 0xc7, 0xd3, 0xe0, 0x00, 0x00, 0x00}, {0x3f, 0xe6, 0x8c, 0xa6, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xe7, 0x49, 0xa2, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xe7, 0xfe, 0x55, 0x60, 0x00, 0x00, 0x00}, {0x3f, 0xe8, 0xaa, 0x5f, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xe9, 0x4d, 0x8d, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xe9, 0xe7, 0xb8, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xea, 0x78, 0xe1, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xeb, 0x01, 0x13, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xeb, 0x80, 0x77, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xeb, 0xf7, 0x3a, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xec, 0x65, 0xa4, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xec, 0xcb, 0xfc, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xed, 0x2a, 0x95, 0x60, 0x00, 0x00, 0x00}, {0x3f, 0xed, 0x81, 0xce, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xed, 0xd1, 0xf7, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xee, 0x1b, 0x85, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xee, 0x5e, 0xbc, 0x60, 0x00, 0x00, 0x00}, {0x3f, 0xee, 0x9c, 0x0d, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xee, 0xd3, 0xc7, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0x06, 0x41, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0x33, 0xd7, 0xa0, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0x5c, 0xbf, 0x20, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0x81, 0x5e, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0xa1, 0xc9, 0xc0, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0xbe, 0x55, 0x40, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0xd7, 0x0f, 0x80, 0x00, 0x00, 0x00}, {0x3f, 0xef, 0xec, 0x96, 0x00, 0x00, 0x00, 0x00}, {0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x37, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x36, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x34, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x32, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x30, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x2f, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x2d, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x2b, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x29, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x28, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x26, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x24, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x22, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x21, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x1f, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x1d, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x1b, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x1a, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x18, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x16, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x14, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x13, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x11, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x0f, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x0d, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x0c, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x0a, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x08, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x06, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x05, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x03, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x83, 0x01, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xff, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xfe, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xfc, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xfa, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf8, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf7, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf5, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf3, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf1, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xf0, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xee, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xec, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xea, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe9, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe7, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe5, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe3, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe2, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xe0, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xde, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xdc, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xdb, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd9, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd7, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd5, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd4, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd2, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xd0, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xce, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xcd, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xcb, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc9, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc7, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc6, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc4, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc2, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xc0, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xbf, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xbd, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xbb, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb9, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb8, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb6, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb4, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb2, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xb1, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xaf, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xad, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xab, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xaa, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xa8, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xa6, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xa4, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xa3, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0xa1, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x9f, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x9d, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x9c, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x9a, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x98, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x96, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x95, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x93, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x91, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x8f, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x8e, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x8c, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x8a, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x88, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x87, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x85, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x83, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x81, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x80, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x7e, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x7c, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x7a, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x79, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x77, 0x50}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x75, 0x90}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x73, 0xd0}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x72, 0x10}, {0x00, 0x00, 0x15, 0x3e, 0x5f, 0x5b, 0x76, 0x30}, {0x00, 0x00, 0x15, 0x3d, 0x2c, 0x82, 0x70, 0x50}}}; // } // namespace metkit::mars2grib::backend::deductions::pv_detail::data metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/StatisticalWindow.h0000664000175000017500000000542415246725757030101 0ustar alastairalastair/* * (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 detail/StatisticalWindow.h /// @brief Shared `StatisticalWindow` type used by `ProductTime` and the /// shared `stattype` parser. /// /// `StatisticalWindow` represents one statistical window as a /// `(unit, count)` pair, reusing the GRIB-aligned `tables::TimeUnit` enum /// (GRIB2 Code Table 4.4) rather than introducing a parallel "kind" enum. /// /// This header is **shared infrastructure**, not a deduction. It is consumed /// by: /// - `deductions/detail/ProductTime.h` (stores values inside `ProductTime`) /// - `deductions/detail/StatType.h` (the shared `stattype` parser) /// /// The two-level allow-list enforcement is documented in /// `deductions/timeProducts.md` §10.18: /// - the parser (§22) enforces the narrow `stattype`-grammar allow-list /// `{Day, Month}` at parse time; /// - the factory `make_ProductTime_or_throw` (§detail/ProductTime.h) /// enforces the extended assembled-window allow-list `{Second, Day, /// Month}` after window assembly. /// /// See `deductions/timeProducts.md` §3, §21 for the full normative /// specification. /// /// @ingroup mars2grib_backend_deductions /// #pragma once // Project #include "metkit/mars2grib/backend/tables/timeUnits.h" namespace metkit::mars2grib::backend::deductions::detail { /// /// @brief One statistical window: a `tables::TimeUnit` + a count. /// /// Reuses the GRIB-aligned `tables::TimeUnit` enum (GRIB2 Code Table 4.4) /// rather than introducing a parallel "kind" enum. /// /// **Allow-list (assembled `ProductTime::statisticalWindows` entry)**: /// only `Second`, `Day`, `Month` are legal. Any other `TimeUnit` value (in /// particular `Hour`, `Minute`, `Hours3/6/12`, `Year`, `Decade`, `Normal`, /// `Century`, `Missing`) is rejected by the factory `make_ProductTime_or_throw` /// (§10.18 (b)). /// /// **Allow-list (parser-produced entry)**: only `Day`, `Month` are legal. /// The narrower `stattype`-grammar allow-list is enforced inside /// `parse_StatType_or_throw` (§10.18 (a)). /// /// **Invariant**: `count > 0` for any window stored inside a `ProductTime` /// (§3.2 / §10.11). The default-constructed value (`count == 0`) is legal at /// the C++ language level but illegal as a `ProductTime` member. /// struct StatisticalWindow { tables::TimeUnit unit{tables::TimeUnit::Second}; long count{0}; }; } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/StatType.h0000664000175000017500000002364415246725757026206 0ustar alastairalastair/* * (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 detail/StatType.h /// @brief Shared `stattype` parser used by both `ProductTime` and /// `typeOfStatisticalProcessing`. /// /// This header is **shared infrastructure**, not a deduction. It exposes: /// - the `ParsedStatTypeBlock` value type (per-block, pre-mapped to GRIB /// table values); /// - the `parse_StatType_or_throw` function, which decodes the locked /// `stattype` grammar (§7.7) into a vector of blocks in MARS textual /// order (outermost → innermost). /// /// **Why shared**: both `ProductTime` (which needs the *period* part) and /// `typeOfStatisticalProcessing` (which needs the *operation* part) consume /// the same `stattype` string. A single shared parser eliminates the drift /// risk between them — both deductions size their output arrays in lock-step /// from the same parse result. /// /// **Allow-lists enforced by this parser** (parser-level hard errors): /// - period unit ∈ `{mo, da}` → §10.16 / §10.18 (a) /// - operation ∈ `{av, mn, mx, sd}` → §10.16 /// - block ordering: `mo` precedes `da` → §10.17 /// - block count ∈ `{1, 2}` → §10.16 /// /// The extended `Second`-inclusive allow-list applies only to the assembled /// `ProductTime::statisticalWindows` array (where the innermost window may /// originate from `timespan`); that check lives in /// `make_ProductTime_or_throw`. /// /// See `deductions/timeProducts.md` §22 for the full normative /// specification. /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #include #include // eckit #include "eckit/exception/Exceptions.h" // Project #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/StatisticalWindow.h" #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions::detail { // ============================================================= // 1. ParsedStatTypeBlock (§22.2) // ============================================================= /// /// @brief One decoded `stattype` block, pre-mapped to GRIB table values. /// /// - `timeWindow` carries the **period** part of the block (the /// stattype-grammar pair `(period_unit, count)`). For locked grammar /// tokens `mo` / `da`, the parser emits `count = 1` and /// `unit = tables::TimeUnit::Month` / `tables::TimeUnit::Day`. /// - `typeOfStatisticalProcessing` carries the **operation** part of the /// block, already mapped to a value of GRIB Code Table 4.10: /// - `av` → `Average` /// - `mn` → `Minimum` /// - `mx` → `Maximum` /// - `sd` → `StandardDeviation` /// struct ParsedStatTypeBlock { StatisticalWindow timeWindow; tables::TypeOfStatisticalProcessing typeOfStatisticalProcessing; }; // ============================================================= // 2. Single-token decoders (private helpers) // ============================================================= namespace impl { /// /// @brief Decode a single period token to a `StatisticalWindow` /// with `count = 1`. /// /// @throws Mars2GribDeductionException on unknown token (§10.16) or /// narrow-allow-list violation (§10.18 (a)). /// inline StatisticalWindow decodePeriod_or_throw(std::string_view s, const std::string& fullStatType) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (s == "da") return StatisticalWindow{tables::TimeUnit::Day, 1}; if (s == "mo") return StatisticalWindow{tables::TimeUnit::Month, 1}; throw Mars2GribDeductionException("Invalid stattype period token [§10.16/§10.18(a)]: actual='" + std::string(s) + "', expected={'da','mo'} (in stattype='" + fullStatType + "')", Here()); } /// /// @brief Decode a single operation token to a /// `tables::TypeOfStatisticalProcessing`. /// /// @throws Mars2GribDeductionException on unknown token (§10.16). /// inline tables::TypeOfStatisticalProcessing decodeOp_or_throw(std::string_view s, const std::string& fullStatType) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (s == "av") return tables::TypeOfStatisticalProcessing::Average; if (s == "mn") return tables::TypeOfStatisticalProcessing::Minimum; if (s == "mx") return tables::TypeOfStatisticalProcessing::Maximum; if (s == "sd") return tables::TypeOfStatisticalProcessing::StandardDeviation; throw Mars2GribDeductionException("Invalid stattype operation token [§10.16]: actual='" + std::string(s) + "', expected={'av','mn','mx','sd'} (in stattype='" + fullStatType + "')", Here()); } } // namespace impl // ============================================================= // 3. parse_StatType_or_throw (§22.3) // ============================================================= /// /// @brief Parse a `stattype` string per the locked grammar (§7.7). /// /// Grammar: /// @code /// stattype := block ('_' block)* /// block := period operation (4 chars) /// period := 'mo' | 'da' /// operation := 'av' | 'mn' | 'mx' | 'sd' /// @endcode /// /// Semantic constraints (parser-level hard errors): /// - block count MUST be in `{1, 2}` (§10.16); /// - if both `mo` and `da` blocks are present, `mo` MUST precede `da` /// (§10.17); /// - all unit and operation tokens MUST belong to the locked allow-lists /// (§10.16, §10.18 (a)). /// /// @param[in] stattype Raw textual value of the MARS `stattype` keyword. /// MUST be non-empty; callers MUST NOT invoke the /// parser when the `stattype` key is absent from the /// MARS dictionary. /// /// @return A vector of size 1 or 2, in MARS textual order (outermost first, /// innermost last). /// /// @throws Mars2GribDeductionException on any grammar (§10.16), /// ordering (§10.17), or narrow-allow-list (§10.18 (a)) violation. /// /// @note Each block's `typeOfStatisticalProcessing` is already mapped to a /// value of GRIB Code Table 4.10; consumers do not need to perform /// any further mapping. /// inline std::vector parse_StatType_or_throw(const std::string& stattype) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (stattype.empty()) { throw Mars2GribDeductionException( "Invalid stattype: empty string (caller MUST NOT invoke " "parse_StatType_or_throw when 'stattype' is absent)", Here()); } std::vector blocks; std::size_t pos = 0; while (pos < stattype.size()) { if (pos + 4 > stattype.size()) { throw Mars2GribDeductionException( "Invalid stattype format [§10.16]: incomplete 4-char block " "at position " + std::to_string(pos) + " in '" + stattype + "'", Here()); } StatisticalWindow window = impl::decodePeriod_or_throw(stattype.substr(pos, 2), stattype); tables::TypeOfStatisticalProcessing op = impl::decodeOp_or_throw(stattype.substr(pos + 2, 2), stattype); blocks.push_back(ParsedStatTypeBlock{window, op}); pos += 4; if (pos < stattype.size()) { if (stattype[pos] != '_') { throw Mars2GribDeductionException("Invalid stattype separator [§10.16] at position " + std::to_string(pos) + " in '" + stattype + "': expected '_'", Here()); } ++pos; } } // Block-count limit: 1 or 2 blocks (§22.4 / §10.16). if (blocks.empty() || blocks.size() > 2) { throw Mars2GribDeductionException("Invalid stattype [§10.16]: block count (" + std::to_string(blocks.size()) + ") outside allowed range {1, 2} (in stattype='" + stattype + "')", Here()); } // Semantic validation (§10.17): at most one mo, at most one da, mo precedes da. int moIndex = -1; int daIndex = -1; for (std::size_t i = 0; i < blocks.size(); ++i) { if (blocks[i].timeWindow.unit == tables::TimeUnit::Month) { if (moIndex != -1) { throw Mars2GribDeductionException( "Invalid stattype [§10.17] '" + stattype + "': more than one 'mo' block", Here()); } moIndex = static_cast(i); } if (blocks[i].timeWindow.unit == tables::TimeUnit::Day) { if (daIndex != -1) { throw Mars2GribDeductionException( "Invalid stattype [§10.17] '" + stattype + "': more than one 'da' block", Here()); } daIndex = static_cast(i); } } if (moIndex != -1 && daIndex != -1 && moIndex > daIndex) { throw Mars2GribDeductionException("Invalid stattype [§10.17] '" + stattype + "': blocks not in outermost-to-innermost order " "('mo' must precede 'da')", Here()); } return blocks; } } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/dateTimeHelpers.h0000664000175000017500000001412515246725757027502 0ustar alastairalastair/* * (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 dateTimeHelpers.h /// @brief Shared date/time parsing helpers for temporal deductions. /// /// This header contains the local date and time parsing helpers shared by the /// temporal datetime deductions. The functions here own only lexical parsing and /// local value validation; they do not own deduction-level dictionary access or /// temporal-source composition. /// #pragma once #include #include #include #include "eckit/types/Date.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" namespace metkit::mars2grib::backend::deductions::detail { /// /// @brief Parse a numeric date encoded as `YYYYMMDD`. /// /// The year, month, and day fields are extracted arithmetically and validated /// by the `eckit::Date` constructor. /// /// @param[in] value Numeric date value. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Validated calendar date. /// @throws Mars2GribDeductionException if the extracted fields do not form a /// valid `eckit::Date`. /// inline eckit::Date parseDateLong(long value, const std::string& key) { const long year = value / 10000L; const long month = (value / 100L) % 100L; const long day = value % 100L; try { return eckit::Date(year, month, day); } catch (...) { throw Mars2GribDeductionException("Invalid date in `" + key + "`: '" + std::to_string(value) + "'", Here()); } } /// /// @brief Parse a compact or hyphenated date string. /// /// Accepted forms are `YYYYMMDD` and `YYYY-MM-DD`. /// /// @param[in] value Date token. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Validated calendar date. /// @throws Mars2GribDeductionException for invalid syntax or invalid calendar /// values. /// inline eckit::Date parseDateString(std::string value, const std::string& key) { value = digitsOnly(std::move(value), '-'); if (value.size() != 8 || !std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c); })) { throw Mars2GribDeductionException("Invalid date syntax in `" + key + "`", Here()); } return parseDateLong(parseLongStrict(value, key), key); } /// /// @brief Parse a numeric MARS time in `HHMM` form. /// /// Integer transport cannot preserve leading zeroes. Therefore values such as /// `12` are interpreted as the compact MARS time `0012`, which corresponds to /// `00:12:00`. /// /// Only `HHMM` semantics are accepted. Values greater than `2359` or values /// whose final two digits do not form a valid minute field are rejected. /// Seconds are never accepted by this parser. /// /// @param[in] value Numeric time representation. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Validated time of day. /// @throws Mars2GribDeductionException for negative or invalid time values. /// inline eckit::Time parseTimeLong(long value, const std::string& key) { if (value < 0) { throw Mars2GribDeductionException("Negative time in `" + key + "`", Here()); } if (value > 2359L || value % 100L >= 60L) { throw Mars2GribDeductionException("Invalid time in `" + key + "`: '" + std::to_string(value) + "'", Here()); } const long hour = value / 100L; const long minute = value % 100L; try { return eckit::Time(hour, minute, 0); } catch (...) { throw Mars2GribDeductionException("Invalid time in `" + key + "`: '" + std::to_string(value) + "'", Here()); } } /// /// @brief Parse a textual MARS time in compact or colon-separated form. /// /// Accepted textual forms are `HHMM` and `HH:MM`. Compact textual times must /// contain exactly four decimal digits. Colon-separated times must contain /// exactly two decimal fields and exactly one colon separator. Seconds are never /// accepted by this parser. /// /// @param[in] value Time token. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Validated time of day. /// @throws Mars2GribDeductionException for malformed syntax or invalid time /// values. /// inline eckit::Time parseTimeString(std::string value, const std::string& key) { if (value.find(':') != std::string::npos) { const std::size_t separator = value.find(':'); const bool hasExactlyOneColon = separator != std::string::npos && value.find(':', separator + 1) == std::string::npos; const bool hasExpectedLength = value.size() == 5; if (!hasExactlyOneColon || !hasExpectedLength) { throw Mars2GribDeductionException("Invalid colon-separated time in `" + key + "`", Here()); } const std::string hourToken = value.substr(0, separator); const std::string minuteToken = value.substr(separator + 1); const bool tokensAreTwoDigits = hourToken.size() == 2 && minuteToken.size() == 2; if (!tokensAreTwoDigits) { throw Mars2GribDeductionException("Invalid colon-separated time in `" + key + "`", Here()); } try { return eckit::Time(parseLongStrict(hourToken, key), parseLongStrict(minuteToken, key), 0); } catch (...) { throw Mars2GribDeductionException("Invalid time in `" + key + "`: '" + value + "'", Here()); } } const bool hasExactlyFourDigits = value.size() == 4 && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c); }); if (!hasExactlyFourDigits) { throw Mars2GribDeductionException("Invalid time syntax in `" + key + "`", Here()); } return parseTimeLong(parseLongStrict(value, key), key); } /// } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/parseHelpers.h0000664000175000017500000001706615246725757027067 0ustar alastairalastair/* * (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 parseHelpers.h /// @brief Shared lexical helpers for temporal deductions. /// /// This header contains small parsing and normalization helpers shared by /// multiple temporal deductions. These helpers perform only local lexical or /// arithmetic work; they do not own deduction-level dictionary access, /// classification, or cross-field semantics. /// #pragma once #include #include #include #include #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions::detail { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; /// /// @brief Convert one token to lowercase using the C locale character mapping. /// /// This helper is used by deductions that accept case-insensitive textual /// enumerations. It performs no semantic validation and preserves every /// non-alphabetic character unchanged. /// /// @param[in] value Source token. /// @return Lowercased token. /// inline std::string lower(std::string value) { std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return value; } /// /// @brief Remove one allowed separator character from a token. /// /// This helper is used by deductions that accept compact and separator-based /// source syntaxes for the same underlying value. /// /// @param[in] value Source token. /// @param[in] ignored Separator character to remove. /// @return `value` with every occurrence of `ignored` removed. /// inline std::string digitsOnly(std::string value, char ignored) { value.erase(std::remove(value.begin(), value.end(), ignored), value.end()); return value; } /// /// @brief Parse one complete decimal integer token as `long`. /// /// Partial parses are rejected. The whole token must be consumed by the integer /// conversion. /// /// @param[in] value Input token. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Parsed integer value. /// @throws Mars2GribDeductionException if the token is not a valid complete /// `long` representation. /// inline long parseLongStrict(const std::string& value, const std::string& key) { std::size_t used = 0; long result = 0; try { result = std::stol(value, &used); } catch (...) { throw Mars2GribDeductionException("Invalid integer value for `" + key + "`: '" + value + "'", Here()); } if (used != value.size()) { throw Mars2GribDeductionException("Invalid trailing characters in `" + key + "`: '" + value + "'", Here()); } return result; } /// /// @brief Convert hours to seconds with overflow checking. /// /// @param[in] hours Hour count. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return `hours * 3600`. /// @throws Mars2GribDeductionException if the multiplication overflows `long`. /// inline long checkedHoursToSeconds(long hours, const std::string& key) { if (hours > std::numeric_limits::max() / 3600L || hours < std::numeric_limits::min() / 3600L) { throw Mars2GribDeductionException("Duration overflow while converting `" + key + "` from hours", Here()); } return hours * 3600L; } /// /// @brief Parse one month-like enumeration token into its canonical integer value. /// /// Accepted textual aliases are the month abbreviations and full English month /// names defined by the MARS keyword specification. Numeric strings are parsed /// as decimal integers and returned without range validation so that the caller /// can own the final keyword-contract check. /// /// Accepted textual aliases are: /// - `jan`, `january`; /// - `feb`, `february`; /// - `mar`, `march`; /// - `apr`, `april`; /// - `may`; /// - `jun`, `june`; /// - `jul`, `july`; /// - `aug`, `august`; /// - `sep`, `september`; /// - `oct`, `october`; /// - `nov`, `november`; /// - `dec`, `december`. /// /// @param[in] value Source token. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Canonical integer month value. /// @throws Mars2GribDeductionException if the token is neither a supported /// month alias nor a valid decimal integer. /// inline long parseMonthEnum(const std::string& value, const std::string& key) { const std::string normalized = lower(value); if (normalized == "jan" || normalized == "january") { return 1; } if (normalized == "feb" || normalized == "february") { return 2; } if (normalized == "mar" || normalized == "march") { return 3; } if (normalized == "apr" || normalized == "april") { return 4; } if (normalized == "may") { return 5; } if (normalized == "jun" || normalized == "june") { return 6; } if (normalized == "jul" || normalized == "july") { return 7; } if (normalized == "aug" || normalized == "august") { return 8; } if (normalized == "sep" || normalized == "september") { return 9; } if (normalized == "oct" || normalized == "october") { return 10; } if (normalized == "nov" || normalized == "november") { return 11; } if (normalized == "dec" || normalized == "december") { return 12; } return parseLongStrict(normalized, key); } /// /// @brief Parse a compact duration string and normalize it to seconds. /// /// Accepted suffixes are: /// - `h`: hours; /// - `m`: minutes; /// - `s`: seconds; /// - `d`: fixed 86400-second days. /// /// When no suffix is present, hours are assumed. /// /// @param[in] raw Duration token. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Duration in seconds. /// @throws Mars2GribDeductionException for empty input, invalid integer syntax, /// unsupported units, or arithmetic overflow. /// inline long parseDurationStringSeconds(const std::string& raw, const std::string& key) { if (raw.empty()) { throw Mars2GribDeductionException("Empty duration for `" + key + "`", Here()); } std::string value = lower(raw); char unit = 'h'; if (std::isalpha(static_cast(value.back()))) { unit = value.back(); value.pop_back(); } const long count = parseLongStrict(value, key); switch (unit) { case 'h': return checkedHoursToSeconds(count, key); case 'm': if (count > std::numeric_limits::max() / 60L || count < std::numeric_limits::min() / 60L) { throw Mars2GribDeductionException("Duration overflow in `" + key + "`", Here()); } return count * 60L; case 's': return count; case 'd': if (count > std::numeric_limits::max() / 86400L || count < std::numeric_limits::min() / 86400L) { throw Mars2GribDeductionException("Duration overflow in `" + key + "`", Here()); } return count * 86400L; default: throw Mars2GribDeductionException("Unsupported duration unit in `" + key + "`: '" + raw + "'", Here()); } } } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.20.2/src/metkit/mars2grib/backend/deductions/detail/timeDurationHelpers.h0000664000175000017500000000371415246725757030414 0ustar alastairalastair/* * (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 timeDurationHelpers.h /// @brief Shared deduction-local temporal DTOs. /// /// This header contains only the small value types shared by multiple temporal /// deductions. It intentionally does NOT depend on ProductTimeSpec model types, /// so deductions remain buildable without the backend model layer. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/backend/deductions/common.h" namespace metkit::mars2grib::backend::deductions::detail { /// /// @brief Canonicalize a non-negative elapsed duration expressed in seconds. /// /// Positive whole-hour values are represented in hours. Zero and non-hour- /// aligned values are represented in seconds. /// /// @param[in] seconds Elapsed duration in seconds. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return Canonical elapsed duration. /// @throws Mars2GribDeductionException if `seconds` is negative. /// inline TimeDuration canonicalElapsedDuration(long seconds, const std::string& key) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (seconds < 0) { throw Mars2GribDeductionException("`" + key + "` must be non-negative", Here()); } if (seconds > 0 && seconds % 3600L == 0) { return TimeDuration{seconds / 3600L, tables::TimeUnit::Hour}; } return TimeDuration{seconds, tables::TimeUnit::Second}; } } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.20.2/src/metkit/mars2grib/backend/deductions/bitsPerValue.h0000664000175000017500000003335515246725757025574 0ustar alastairalastair/* * (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 bitsPerValue.h /// @brief Deduction of the GRIB `bitsPerValue` packing parameter. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB bits-per-value packing parameter** used by the /// Data Representation Section. /// /// The deduction logic is **explicitly split by data representation**: /// - **Gridded data** use a metadata-driven default mapping derived from /// legacy MultIO behavior, with optional user override. /// - **Spectral data** use a fixed default packing precision, with /// optional user override. /// /// This separation is intentional and reflects fundamentally different /// packing policies for gridded and spectral fields. The two code paths /// are implemented as distinct deductions to avoid hidden coupling, /// conditional complexity, and ambiguous semantics. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying deterministic deduction and validation logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - 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: /// - OVERRIDE: value provided by parameter dictionary overriding deduction logic /// - DEFAULT: value defaulted to a predefined constant or derived via default deduction /// logic from input dictionaries due to missing input /// @section References /// Concept: /// - @ref packingEncoding.h /// /// Related deductions: /// - @ref subSetTrunc.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 { namespace details { /// /// @brief Determine the default GRIB packing precision (bitsPerValue). /// /// This function returns the number of bits per value used for GRIB /// data packing based on: /// - the GRIB parameter identifier (`paramId`) /// - the vertical level prefix (`prefix`) /// - whether compression is enabled (`enableCompression`) /// /// The logic implemented here is directly derived from the legacy function /// `LOOKUP_BITS_PER_VALUE_DEFAULT` code path in **MultIO**, and is preserved to ensure /// bit-for-bit compatibility with existing production workflows. /// - /src/multiom/ifs2mars/ifs2mars/ifs2mars_mod.F90 /// /// @param paramId /// GRIB parameter identifier (discipline/category/number flattened). /// /// @param prefix /// Vertical level prefix: /// - `"pl"` : pressure levels /// - `"ml"` : model levels /// /// @param enableCompression /// If true, reduced precision may be applied for model-level fields. /// /// @return /// Number of bits per value to be used for GRIB packing. /// /// @details /// Decision logic (evaluated in order): /// /// - `paramId == 248` (Cloud cover) /// → 8 bits /// /// - `paramId == 141` (backward compatibility), /// `paramId == 228141` (Snow depth), /// `paramId == 244` (Forecast surface roughness) /// → 24 bits /// /// - `paramId == 246` (Cloud liquid water content) on pressure levels /// → 12 bits /// /// - `paramId == 247` (Cloud ice water content) on pressure levels /// → 12 bits /// /// - `210000 < paramId < 228000` /// → 24 bits /// /// - `paramId == 260510` (Cloudy brightness temperature), /// `paramId == 260511` (Clear-sky brightness temperature) /// → 10 bits /// /// - Compression enabled on model levels /// → 10 bits /// /// - Default case /// → 16 bits /// /// @note /// The function is purely deterministic and has no side effects. /// No validation of `paramId` or `prefix` is performed. /// /// @note /// The logic is not 100% identical to the original MultIO code path. /// Logic for parameters between 80 and 120 has been removed, as it /// requires additional parameters, and in this case the override can /// be used anyway. /// inline long lookup_bitsPerValueGridded_default(long paramId, std::string prefix, bool enableCompression) { // Parameter IDs (hard-coded) constexpr long CLOUD_COVER = 248; constexpr long SNOW_DEPTH_BACKWARD_COMPAT = 141; constexpr long SNOW_DEPTH = 228141; constexpr long FORECAST_SURFACE_ROUGHNESS = 244; constexpr long CLOUD_LIQUID_WATER_CONTENT = 246; constexpr long CLOUD_ICE_WATER_CONTENT = 247; constexpr long CLOUDY_BRIGHTNESS_TEMPERATURE = 260510; constexpr long CLEAR_SKY_BRIGHTNESS_TEMPERATURE = 260511; if (paramId == CLOUD_COVER) { return 8; } else if (paramId == SNOW_DEPTH_BACKWARD_COMPAT || paramId == SNOW_DEPTH || paramId == FORECAST_SURFACE_ROUGHNESS) { return 24; } else if (paramId == CLOUD_LIQUID_WATER_CONTENT && prefix == "pl") { return 12; } else if (paramId == CLOUD_ICE_WATER_CONTENT && prefix == "pl") { return 12; } else if (paramId > 210000 && paramId < 228000) { return 24; } else if (paramId == CLOUDY_BRIGHTNESS_TEMPERATURE || paramId == CLEAR_SKY_BRIGHTNESS_TEMPERATURE) { return 10; } else if (enableCompression && prefix == "ml") { return 10; } else { return 16; } } } // namespace details /// /// @brief Resolve the GRIB `bitsPerValue` packing parameter for gridded data. /// /// @section Deduction contract /// - Reads: /// - `par["bitsPerValue"]` (if present), /// - otherwise `mars["param"]`, `mars["levtype"]`, /// and `opt["enableBitsPerValueCompression"]` /// - Writes: none /// - Side effects: logging (OVERRIDE or DEFAULT) /// - Failure mode: throws /// /// This deduction resolves the number of bits per value used for GRIB /// numeric packing of **gridded fields**. /// /// If `bitsPerValue` is explicitly provided in the parameter dictionary, /// it is taken verbatim and overrides any default deduction logic. /// Otherwise, a deterministic default mapping is applied based on /// MARS metadata and encoder options. /// /// The default mapping logic is delegated to /// `details::lookup_bitsPerValueGridded_default` and is designed to preserve /// bit-for-bit compatibility with legacy MultIO workflows. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide `param` and `levtype` /// if default deduction is required. /// /// @tparam ParDict_t /// Type of the parameter dictionary. May contain `bitsPerValue`. /// /// @tparam OptDict_t /// Type of the options dictionary. May contain /// `enableBitsPerValueCompression`. /// /// @param[in] mars /// MARS dictionary providing metadata for default deduction. /// /// @param[in] par /// Parameter dictionary optionally providing `bitsPerValue`. /// /// @param[in] opt /// Options dictionary controlling default deduction behavior. /// /// @return /// The resolved number of bits per value to be used for GRIB packing /// of gridded data. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If required inputs are missing, if the resolved value is outside /// the supported range, or if any unexpected error occurs. /// /// @note /// This deduction enforces strict numeric validation but does not /// attempt to optimize packing precision based on data statistics /// or parameter semantics. /// template long resolve_BitsPerValueGridded_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { 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::Mars2GribDeductionException; try { long bitsPerValue; if (has(par, "bitsPerValue")) { // Retrieve mandatory bitsPerValue from parameter dictionary bitsPerValue = get_or_throw(par, "bitsPerValue"); // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`bitsPerValue` overridden by parameter dictionary: value='"; logMsg += std::to_string(bitsPerValue) + "'"; return logMsg; }()); } else { // Retrieve auxiliary values for default lookup long param = get_or_throw(mars, "param"); std::string levtype = get_or_throw(mars, "levtype"); bool applyCompression = get_or_throw(opt, "enableBitsPerValueCompression"); // Resolve bitsPerValue from default mapping bitsPerValue = details::lookup_bitsPerValueGridded_default(param, levtype, applyCompression); // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`bitsPerValue` defaulted to: value='"; logMsg += std::to_string(bitsPerValue) + "'"; return logMsg; }()); } // Validate bits per value if (bitsPerValue < 0 || bitsPerValue > 64) { std::string errMsg = "Invalid `bitsPerValue`: value='"; errMsg += std::to_string(bitsPerValue) + "'"; throw Mars2GribDeductionException(errMsg, Here()); } // Success exit point return bitsPerValue; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `bitsPerValue` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; /// /// @brief Resolve the GRIB `bitsPerValue` packing parameter for spectral data. /// /// @section Deduction contract /// - Reads: `par["bitsPerValue"]` (if present) /// - Writes: none /// - Side effects: logging (OVERRIDE or DEFAULT) /// - Failure mode: throws /// /// This deduction resolves the number of bits per value used for GRIB /// numeric packing of **spectral fields**. /// /// If `bitsPerValue` is explicitly provided in the parameter dictionary, /// it is taken verbatim and overrides any deduction logic. /// Otherwise, a fixed default value of `16` bits is applied. /// /// No MARS metadata is consulted for spectral packing. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused by this deduction). /// /// @tparam ParDict_t /// Type of the parameter dictionary. May contain `bitsPerValue`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary optionally providing `bitsPerValue`. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved number of bits per value to be used for GRIB packing /// of spectral data. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the resolved value is outside the supported range or if any /// unexpected error occurs. /// /// @note /// This deduction applies a fixed default packing precision for /// spectral data and enforces strict numeric validation. /// template long resolve_BitsPerValueSpectral_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { long bitsPerValue; if (has(par, "bitsPerValue")) { // Retrieve mandatory bitsPerValue from parameter dictionary bitsPerValue = get_or_throw(par, "bitsPerValue"); // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`bitsPerValue` overridden by parameter dictionary: value='"; logMsg += std::to_string(bitsPerValue) + "'"; return logMsg; }()); } else { // Resolve bitsPerValue from default mapping bitsPerValue = 16; // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`bitsPerValue` defaulted to: value='"; logMsg += std::to_string(bitsPerValue) + "'"; return logMsg; }()); } // Validate bits per value if (bitsPerValue < 0 || bitsPerValue > 64) { std::string errMsg = "Invalid `bitsPerValue`: value='"; errMsg += std::to_string(bitsPerValue) + "'"; throw Mars2GribDeductionException(errMsg, Here()); } // Success exit point return bitsPerValue; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `bitsPerValue` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/stream.h0000664000175000017500000000765615246725757024467 0ustar alastairalastair/* * (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 stream.h /// @brief Deduction of the GRIB `stream` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `stream` key used to describe the MARS data stream associated /// with the encoded product. /// /// The value is not inferred or transformed and must be provided /// explicitly by the MARS dictionary. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate stream values against controlled vocabularies /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref marsEncoding.h /// /// Related deductions: /// - @ref class.h /// - @ref expver.h /// - @ref type.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 GRIB `stream` key. /// /// This deduction retrieves the value of the MARS key `stream` /// from the input MARS dictionary and exposes it directly /// as the GRIB `stream` identifier. /// /// The value is treated as mandatory and no inference, /// defaulting, or validation is performed. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain the key `stream` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved MARS stream identifier /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - `stream` is missing from the MARS dictionary /// - the value cannot be retrieved as `std::string` /// - any unexpected error occurs during deduction /// /// @note /// This deduction is fully deterministic and does not depend on /// any pre-existing GRIB header state. /// template std::string resolve_Stream_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 stream from Mars dictionary std::string marsStreamVal = get_or_throw(mars, "stream"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`stream` resolved from input dictionaries: value='"; logMsg += marsStreamVal; logMsg += "'"; return logMsg; }()); // Success exit point return marsStreamVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `stream` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/componentIndex.h0000664000175000017500000001443715246725757026161 0ustar alastairalastair/* * (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 componentIndex.h /// @brief Deduction of the model-error realization identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **model-error realization identifier** (`componentIndex`) /// from MARS metadata. /// /// The deduction retrieves the realization identifier explicitly from the /// MARS dictionary and returns it verbatim, without applying inference, /// defaulting, or semantic interpretation. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref modelErrorEncoding.h /// /// Related deductions: /// - @ref numberOfComponents.h /// - @ref modelErrorType.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the model-error realization identifier. /// /// @section Deduction contract /// - Reads: `mars["type"]`, `mars["number"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the model-error realization identifier from /// the MARS dictionary. For requests with `type=eme`, the MARS key /// `number` identifies the realization within the model-error ensemble /// (not an ensemble-forecast member). /// /// The value is treated as mandatory and is returned verbatim as a /// numeric identifier. No inference, defaulting, or validation against /// GRIB code tables is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `number`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the realization identifier is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The model-error realization identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `type` is missing or not equal to `"eme"` (defence-in-depth: /// `componentIndex` is only meaningful for model-error products), /// - the key `number` is missing, cannot be converted to `long`, /// - or any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the realization identifier is explicitly /// provided by the MARS dictionary and does not attempt any semantic /// interpretation or consistency checking. /// template long resolve_ComponentIndex_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 { // Defence-in-depth: componentIndex is only meaningful for type=eme // (model-error products). Resolving it for any other request type // indicates a serious upstream contract violation (wrong recipe, // matcher bypass, etc.) and must be surfaced as a hard failure // with an unambiguous diagnostic. const std::string typeVal = get_or_throw(mars, "type"); if (typeVal != "eme" && typeVal != "me") { throw Mars2GribDeductionException( std::string("`componentIndex` requested for a non-`eme` or non-`me` request: " "`mars[\"type\"]` is `") + typeVal + "` but only `eme` or `me` is supported. This is a serious upstream " "contract violation: the model-error deduction was reached " "for a request that is not a model-error product. Check " "recipe selection and matcher dispatch.", Here()); } // Warning due to the hack if (typeVal == "me") { eckit::Log::warning() << "MARS `type` value `me` is treated as a synonym for `eme` to accommodate legacy requests. " << "This is a temporary compatibility hack and support for `me` will be removed in the future. " << std::endl; } // Retrieve mandatory MARS number (model-error realization id) long componentIndex = get_or_throw(mars, "number"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`componentIndex` resolved from input dictionaries: value="; logMsg += std::to_string(componentIndex); return logMsg; }()); // Success exit point return componentIndex; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `componentIndex` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/chemId.h0000664000175000017500000001106315246725757024350 0ustar alastairalastair/* * (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 metkit-1.20.2/src/metkit/mars2grib/backend/deductions/periodItMax.h0000664000175000017500000001122415246725757025403 0ustar alastairalastair/* * (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 periodItMax.h /// @brief Deduction of the maximum wave period index (`iTmax`). /// /// This header defines deduction utilities used by the mars2grib backend /// to retrieve the **maximum wave period index** (`iTmax`) from input /// dictionaries. /// /// The deduction treats `iTmax` as an optional parameter: /// - if present in the parameter dictionary, the value is returned /// - if absent, no default is applied and an empty optional is returned /// /// No semantic validation or consistency checking is performed at this /// level. /// /// Error handling follows a strict fail-fast strategy: /// - unexpected access errors 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 presence or absence resolved from input dictionaries /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMin.h /// - @ref waveFrequencyGrid.h /// - @ref waveFrequencyNumber.h /// - @ref waveDirectionGrid.h /// - @ref waveDirectionNumber.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #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 optional maximum wave period index (`iTmax`). /// /// @section Deduction contract /// - Reads: `par["iTmax"]` (optional) /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws on unexpected errors /// /// This deduction retrieves the optional wave period index `iTmax` /// from the parameter dictionary. /// /// If the key is present, the value is returned wrapped in a /// `std::optional`. If the key is absent, an empty optional is returned. /// No defaulting or inference is applied. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. May contain `iTmax`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which `iTmax` may be retrieved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// An optional containing `iTmax` if present; otherwise an empty /// optional. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If any unexpected error occurs during dictionary access. /// /// @note /// This deduction performs no semantic validation of the retrieved /// value. /// template std::optional resolve_PeriodItMax_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve optional maximum wave period index from parameter dictionary std::optional itMaxOpt = get_opt(par, "iTmax"); if (itMaxOpt.has_value()) { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`iTmax` resolved from input dictionaries: value='"; logMsg += std::to_string(itMaxOpt.value()); logMsg += "'"; return logMsg; }()); } else { // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`iTmax` resolved from input dictionaries: value not present"; return logMsg; }()); } // Success exit point return itMaxOpt; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `iTmax` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/satelliteSeries.h0000664000175000017500000001025415246725757026321 0ustar alastairalastair/* * (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 satelliteSeries.h /// @brief Deduction of the GRIB `satelliteSeries` identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB `satelliteSeries` key used in satellite-based products. /// /// The value is not inferable from MARS metadata and must be provided /// explicitly via the parameter dictionary. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate against GRIB code tables /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref satelliteEncoding.h /// /// Related deductions: /// - @ref satelliteNumber.h /// - @ref instrumentType.h /// - @ref channel.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 GRIB `satelliteSeries` identifier. /// /// @section Deduction contract /// - Reads: `par["satelliteSeries"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the mandatory `satelliteSeries` entry from /// the parameter dictionary and returns it verbatim. /// /// No defaulting, inference, or semantic validation is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide `satelliteSeries`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary providing the satellite series identifier. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Satellite series identifier to be encoded in the GRIB message. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `satelliteSeries` is missing, cannot be retrieved as a /// `long`, or if any unexpected error occurs. /// /// @note /// This deduction is deterministic and does not depend on any /// pre-existing GRIB header state. /// template long resolve_SatelliteSeries_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 satellite series identifier from parameter dictionary long marsSatelliteSeriesVal = get_or_throw(par, "satelliteSeries"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`satelliteSeries` resolved from parameter dictionary: value='"; logMsg += std::to_string(marsSatelliteSeriesVal); logMsg += "'"; return logMsg; }()); // Success exit point return marsSatelliteSeriesVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `satelliteSeries` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/localTablesVersion.h0000664000175000017500000000717615246725757026764 0ustar alastairalastair/* * (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 localTablesVersion.h /// @brief Deduction of the GRIB Local Tables Version Number. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **GRIB Local Tables Version Number** /// (`localTablesVersionNumber`). /// /// At present, the deduction returns a fixed value indicating that /// **no local GRIB tables are in use** and that only standard GRIB /// tables apply. /// /// This deduction exists to provide a single, explicit control point /// for future support of local GRIB tables. /// /// Deductions are responsible for: /// - providing deterministic values for encoding logic /// - centralizing policy decisions related to GRIB metadata /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply inference or data-driven logic unless explicitly required /// /// Error handling: /// - this deduction is total and cannot fail /// /// Logging follows the mars2grib deduction policy: /// - RESOLVE: value derived via deduction logic (including fixed defaults) /// /// @section References /// Concept: /// - @ref identificationEncoding.h /// /// Related deductions: /// - @ref centre.h /// - @ref subCentre.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 GRIB Local Tables Version Number. /// /// @section Deduction contract /// - Reads: none /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: none /// /// This deduction resolves the GRIB /// `localTablesVersionNumber`. /// /// The value is currently **fixed to `0`**, indicating that /// no local GRIB tables are in use and that only standard /// GRIB tables apply. /// /// This function serves as the single authoritative location /// for future logic related to local GRIB tables. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The local tables version number. /// Currently always returns `0`. /// /// @note /// A return value of `0` explicitly signals that /// no local GRIB tables are active. /// template long resolve_LocalTablesVersion_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { // Defaulting local Tables version to 0 long localTablesVersion = 0L; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`localTablesVersionNumber` resolved from input dictionaries: value='"; logMsg += std::to_string(localTablesVersion) + "'"; return logMsg; }()); // Success exit point return localTablesVersion; }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/dataset.h0000664000175000017500000001127715246725757024613 0ustar alastairalastair/* * (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 dataset.h /// @brief Deduction of the MARS `dataset` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **dataset identifier** from MARS metadata. /// /// The deduction retrieves the dataset 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, normalization, or defaulting /// - 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 destineEncoding.h /// /// Related deductions: /// - @ref activity.h /// - @ref experiment.h /// - @ref generation.h /// - @ref model.h /// - @ref realization.h /// - @ref resolution.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 dataset identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["dataset"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the dataset identifier by retrieving the /// mandatory MARS key `dataset` and returning its value as a /// `std::string`. /// /// No semantic interpretation, normalization, or validation is applied. /// The meaning and allowed values of the dataset identifier are defined /// by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `dataset` /// and conversion to `std::string`. /// /// @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 dataset identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved dataset identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `dataset` is missing, cannot be retrieved as a string, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult dataset registries or GRIB tables. /// template std::string resolve_Dataset_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 dataset std::string marsDatasetVal = get_or_throw(mars, "dataset"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`dataset` resolved from input dictionaries: value='" + marsDatasetVal + "'"; return logMsg; }()); // Success exit point return marsDatasetVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `dataset` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/fourierCoefficientIndex.h0000664000175000017500000001146215246725757027764 0ustar alastairalastair/* * (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 fourierCoefficientIndex.h /// @brief Deduction of the fourier coefficient index. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **fourier coefficient index** (`fourierCoefficientIndex`) /// from MARS metadata. /// /// The deduction retrieves the coefficient index explicitly from the MARS /// dictionary (key `coeffindex`) and returns it verbatim, without applying /// inference, defaulting, or semantic interpretation. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref modelErrorEncoding.h /// /// Related deductions: /// - @ref numberOfFourierCoefficients.h /// - @ref modelErrorType.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the fourier coefficient index. /// /// @section Deduction contract /// - Reads: `mars["coeffindex"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the fourier coefficient index from the /// MARS dictionary. For model-error Fourier-coefficients products, the /// MARS key `coeffindex` identifies the Fourier coefficient encoded in /// the field. /// /// The value is treated as mandatory and is returned verbatim as a /// numeric identifier. No inference, defaulting, or validation against /// GRIB code tables is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `coeffindex`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the fourier coefficient index is /// retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The fourier coefficient index. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `coeffindex` is missing from the MARS dictionary, cannot /// be converted to `long`, or if any unexpected error occurs during /// deduction. /// /// @note /// This deduction does not infer or default the value. Absence of the /// key in the MARS dictionary is considered a contract violation by /// the upstream tool. /// template long resolve_FourierCoefficientIndex_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 MARS coeffindex (fourier coefficient index) long fourierCoefficientIndex = get_or_throw(mars, "coeffindex"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`fourierCoefficientIndex` resolved from input dictionaries: value="; logMsg += std::to_string(fourierCoefficientIndex); return logMsg; }()); // Success exit point return fourierCoefficientIndex; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `fourierCoefficientIndex` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/waveDirectionGrid.h0000664000175000017500000003452515246725757026600 0ustar alastairalastair/* * (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 waveDirectionGrid.h /// @brief Deduction of the GRIB wave direction grid. /// /// This header defines the deduction responsible for resolving the /// wave direction grid used in spectral wave products. /// /// The deduction supports two equivalent input representations: /// - an explicit vector of wave directions (in radians) /// - a reconstruction from the number of wave directions /// /// The resulting grid is converted into a scaled integer /// representation suitable for GRIB encoding. /// /// Deductions: /// - extract values from input dictionaries /// - reconstruct physical wave directions deterministically /// - apply logarithmic scaling /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing wave direction metadata /// - apply implicit defaults beyond documented rules /// - validate physical consistency of wave directions /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: wave direction grid obtained or reconstructed from input dictionaries /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMin.h /// - @ref periodItMax.h /// - @ref waveDirectionNumber.h /// - @ref waveFrequencyGrid.h /// - @ref waveFrequencyNumber.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #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 { namespace math { constexpr long double pi = 3.141592653589793238462643383279502884L; constexpr long double deg2rad = pi / 180.0L; constexpr long double rad2deg = 180.0L / pi; } // namespace math /// /// @brief Metadata and scaled representation of a wave direction grid. /// /// This structure describes a discretized wave direction grid together with /// its scaled integer representation. It is intended for use in contexts where /// wave propagation directions must be stored, transmitted, or encoded as /// integers (e.g. GRIB encoding), while preserving a clear mapping to physical /// directional angles expressed in radians. /// /// The scaling convention is logarithmic and based on powers of ten: /// - `scaleFactorDirections` represents the base-10 logarithm of the real /// scaling factor applied to the physical direction values converted to degrees. /// - For example, a value of `scaleFactorDirections = 2` corresponds to a /// real scaling factor of \f$10^2\f$. /// /// Scaled integer values are obtained as: /// \f[ /// \text{scaledValue}_i = /// \mathrm{round}\!\left( \theta_i \times 10^{\text{scaleFactorDirections}} \times \text{rad2deg} \right) /// \f] /// where \f$\theta_i\f$ is the physical wave direction expressed in radians /// and \f$\text{rad2deg} = 180/\pi\f$ converts radians to degrees. /// /// The inverse operation, used to reconstruct physical directions, is: /// \f[ /// \theta_i = /// \frac{\text{scaledValue}_i}{10^{\text{scaleFactorDirections}} \cdot \text{rad2deg}} /// \f] /// /// This structure is a plain data container and does not enforce internal /// consistency between the number of directions and the size of the associated /// value vector. Such validation is expected to be performed by the caller or /// by higher-level deduction logic. /// /// @note /// The directional angles are assumed to span the interval \f$[0, 2\pi)\f$ /// and to follow the discretization conventions of spectral wave models. /// struct WaveDirectionGrid { /// /// @brief Number of discrete wave directions in the grid. /// long numDirections; /// /// @brief Base-10 logarithm of the real direction scaling factor. /// /// This value defines the scaling applied to physical wave direction /// values after conversion to degrees. For example: /// - `scaleFactorDirections = 2` implies a real scaling factor of /// \f$10^2\f$. /// long scaleFactorDirections; /// /// @brief Scaled integer representation of wave directions. /// /// Each element corresponds to a wave direction encoded as an integer, /// obtained by converting the physical direction from radians to degrees, /// then scaling by \f$10^{\text{scaleFactorDirections}}\f$ and rounding. std::vector scaledValuesDirections; }; namespace wave_direction_detail { /// /// @brief Compute a uniformly discretized wave direction grid. /// /// This function computes the angular discretization of wave propagation /// directions over the interval \f$[0, 2\pi)\f$ using a midpoint rule. /// The full circle is divided into `numberOfWaveDirections` equal angular /// sectors, and each returned direction corresponds to the center of a sector. /// /// The discretization follows: /// \f[ /// \Delta\theta = \frac{2\pi}{N}, \qquad /// \theta_k = k\,\Delta\theta + \tfrac{1}{2}\Delta\theta, /// \quad k = 0,\dots,N-1 /// \f] /// /// This implementation is a direct C++ translation of the directional /// discretization logic used in the ECMWF ECWAM wave model, originally /// implemented in Fortran in: /// - `ecwam/src/ecwam/mfredir.F90` /// /// (see section "2. COMPUTATION OF DIRECTIONS, BANDWIDTH, SIN AND COS", /// around line ~100 in the original source), /// from the ECMWF ECWAM repository: /// - https://github.com/ecmwf-ifs/ecwam.git /// /// The numerical behavior of the original Fortran implementation is preserved /// exactly, with the exception that only the directional angles are returned. /// The computation of sine and cosine terms is intentionally omitted, as these /// can be derived trivially by the caller if needed. /// /// @param[in] numberOfWaveDirections /// Total number of wave propagation directions used to discretize the /// directional space. /// /// @return /// Vector of size `numberOfWaveDirections` containing the wave directions /// in radians, uniformly distributed over \f$(0, 2\pi)\f$. /// /// @note /// The returned angles correspond to the centers of the angular bins /// (midpoint discretization), which is standard in spectral wave models. /// /// @note /// This function assumes a full directional coverage of \f$2\pi\f$ and /// does not support partial angular sectors. /// inline std::vector compute_WaveDirectionGrid(long numberOfWaveDirections) { std::vector th; th.resize(numberOfWaveDirections); const double delth = 2.0 * M_PI / static_cast(numberOfWaveDirections); for (long k = 0; k < numberOfWaveDirections; ++k) { th[k] = static_cast(k) * delth + 0.5 * delth; } return th; } /// /// @brief Construct a scaled wave direction grid from physical direction values. /// /// This function builds a `WaveDirectionGrid` structure from a vector of /// physical wave propagation directions expressed in radians. The physical /// direction values are converted to an integer representation using a /// base-10 logarithmic scaling factor after conversion to degrees. /// /// The scaling convention is defined as follows: /// - `scaleFactorOfWaveDirections` represents the base-10 logarithm of the /// real scaling factor applied to the physical direction values. /// - A value of `scaleFactorOfWaveDirections = 6` corresponds to a real /// scaling factor of \f$10^6\f$. /// /// Each scaled integer value is computed as: /// \f[ /// \text{scaledValue}_i = /// \mathrm{round}\!\left( \theta_i \times 10^{\text{scaleFactorOfWaveDirections}} \times rad2deg\right) /// \f] /// where \f$\theta_i\f$ is the physical wave direction in radians. /// /// The resulting structure stores: /// - the total number of wave directions, /// - the logarithmic scaling factor, /// - the vector of scaled integer direction values. /// /// This representation is typically used for serialization or encoding /// purposes (e.g. GRIB), where integer storage with a known scaling factor /// is required. /// /// @param[in] waveDirectionsInRadians /// Vector of physical wave propagation directions expressed in radians. /// /// @param[in] scaleFactorOfWaveDirections /// Base-10 logarithm of the scaling factor applied to the direction values /// prior to integer encoding. /// /// @return /// A `WaveDirectionGrid` structure containing the scaled integer /// representation of the input direction grid. /// /// @note /// No validation is performed on the input direction values (e.g. range /// checks within \f$[0, 2\pi)\f$) or on the scaling factor. The caller is /// responsible for ensuring that the provided values are physically /// meaningful and that the scaled values fit within the range of the /// target integer type. /// inline WaveDirectionGrid compute_WaveScaledDirectionGrid(const std::vector& waveDirectionsInRadians, long scaleFactorOfWaveDirections) { WaveDirectionGrid out{}; out.numDirections = static_cast(waveDirectionsInRadians.size()); out.scaleFactorDirections = scaleFactorOfWaveDirections; out.scaledValuesDirections.resize(waveDirectionsInRadians.size()); for (long i = 0; i < static_cast(waveDirectionsInRadians.size()); ++i) { out.scaledValuesDirections[i] = static_cast( std::round(waveDirectionsInRadians[i] * std::pow(10.0, scaleFactorOfWaveDirections) * math::rad2deg)); } return out; } } // namespace wave_direction_detail /// /// @brief Resolve the wave direction grid. /// /// This deduction resolves the wave direction grid using the parameter /// dictionary (`par`) and returns a scaled integer representation suitable /// for GRIB encoding. /// /// Resolution follows a strict precedence order: /// /// 1. **Explicit wave directions** /// If `par::waveDirections` is present, it is interpreted as a vector /// of physical wave directions expressed in radians. /// /// 2. **Reconstruction from direction count** /// If `par::numberOfWaveDirections` is present, the wave direction /// grid is reconstructed deterministically using a uniform midpoint /// discretization over the interval \f$[0, 2\pi)\f$. /// /// The scaling factor applied to wave directions is taken from /// `par::scaleFactorOfWaveDirections` and defaults explicitly to `2` /// if not provided. /// /// @tparam OptDict_t Type of the options dictionary (unused) /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// /// @param[in] opt Options dictionary (unused) /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary providing wave direction metadata /// /// @return A `WaveDirectionGrid` containing: /// - number of directions /// - scaling factor /// - scaled integer direction values /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - neither `waveDirections` nor `numberOfWaveDirections` is present /// - dictionary access fails /// - any unexpected error occurs during deduction /// template WaveDirectionGrid resolve_WaveDirectionGrid_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { 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::Mars2GribDeductionException; try { WaveDirectionGrid out{}; std::vector waveDirectionsInRadians; // Retrieve optional scale factor from parameter dictionary long scaleFactorOfWaveDirections = get_opt(par, "scaleFactorOfWaveDirections").value_or(2L); // Check presence of explicit wave directions bool hasWaveDirections = has(par, "waveDirections"); // Check presence of number of wave directions bool hasNumberOfWaveDirections = has(par, "numberOfWaveDirections"); if (hasWaveDirections) { // Retrieve mandatory wave directions from parameter dictionary waveDirectionsInRadians = get_or_throw>(par, "waveDirections"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveDirectionGrid` resolved from input dictionaries"; return logMsg; }()); } else if (hasNumberOfWaveDirections) { // Retrieve mandatory number of wave directions from parameter dictionary long numberOfWaveDirections = get_or_throw(par, "numberOfWaveDirections"); waveDirectionsInRadians = wave_direction_detail::compute_WaveDirectionGrid(static_cast(numberOfWaveDirections)); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveDirectionGrid` reconstructed from input dictionaries with parameters={"; logMsg += "numberOfWaveDirections=" + std::to_string(numberOfWaveDirections) + "}"; return logMsg; }()); } else { /// @todo Need a defaulting strategy for `numberOfWaveDirections` throw Mars2GribDeductionException("Default value NOT IMPLEMENTED for `numberOfWaveDirections`", Here()); } // Build the scaled direction grid out = wave_direction_detail::compute_WaveScaledDirectionGrid(waveDirectionsInRadians, scaleFactorOfWaveDirections); // Success exit point return out; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `waveDirectionGrid` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductionsmetkit-1.20.2/src/metkit/mars2grib/backend/deductions/simulationRegime.h0000664000175000017500000000735615246725757026506 0ustar alastairalastair/* * (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 simulationRegime.h /// @brief Public deduction header for the normalized ProductTimeSpec simulation regime. /// /// Exposes `resolve_SimulationRegime_or_throw`, the canonical entry point that /// derives the normalized ProductTimeSpec simulation regime from the raw MARS /// `class` key. /// /// This deduction owns: /// - direct `class` dictionary access; /// - the ProductTimeSpec-specific mapping from MARS class to simulation regime. /// /// This deduction does NOT: /// - classify simulation type; /// - construct ProductTimeSpec model artifacts; /// - validate unrelated product policies. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 normalized ProductTimeSpec simulation regime. /// /// @section Deduction contract /// - Reads (MARS): `class` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - MARS class `ml` -> `SimulationRegime::AIFS`; /// - any other present MARS class -> `SimulationRegime::IFS`; /// - missing or malformed `class` -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `class`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return The normalized ProductTimeSpec simulation regime. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on missing, malformed, or unsupported raw `class` input, with the /// original cause attached via `std::throw_with_nested`. /// template SimulationRegime resolve_SimulationRegime_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; (void)par; (void)opt; try { const std::string klass = get_or_throw(mars, "class"); const SimulationRegime result = klass == "ml" ? SimulationRegime::AIFS : SimulationRegime::IFS; MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`simulationRegime` resolved from input dictionaries: value='"} + (result == SimulationRegime::AIFS ? "AIFS" : "IFS") + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `simulationRegime` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/numberOfFrequencies.h0000664000175000017500000000644715246725757027140 0ustar alastairalastair/* * (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 numberOfFrequencies.h /// @brief Deduction of the GRIB `numberOfFrequencies` key. /// /// Resolve the wave spectrum discretization size (`numberOfFrequencies`). /// /// @section Deduction contract /// - Reads: `par["numberOfFrequencies"]` (optional) /// - Writes: none /// - Side effects: logging (OVERRIDE or DEFAULT) /// - Failure mode: throws on unexpected errors /// /// If the parameter dictionary provides `numberOfFrequencies`, the value is /// used verbatim. Otherwise a documented default (`54`) is applied. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #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 GRIB `numberOfFrequencies` key. /// /// @tparam MarsDict_t Type of the MARS dictionary (unused). /// @tparam ParDict_t Type of the parameter dictionary. /// @tparam OptDict_t Type of the options dictionary (unused). /// /// @param[in] mars MARS dictionary (unused). /// @param[in] par Parameter dictionary; may contain `numberOfFrequencies`. /// @param[in] opt Options dictionary (unused). /// /// @return Resolved number of frequencies. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If an unexpected error occurs while accessing the parameter dictionary. /// template long resolve_NumberOfFrequencies_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { if (has(par, "numberOfFrequencies")) { long numberOfFrequencies = get_or_throw(par, "numberOfFrequencies"); MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`numberOfFrequencies` resolved from input dictionaries: value="; logMsg += std::to_string(numberOfFrequencies); return logMsg; }()); return numberOfFrequencies; } else { long numberOfFrequencies = 54; MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`numberOfFrequencies` resolved from input dictionaries: value="; logMsg += std::to_string(numberOfFrequencies); return logMsg; }()); return numberOfFrequencies; } } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `numberOfFrequencies` from input dictionaries", Here())); }; mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/time.h0000664000175000017500000001037115246725757024116 0ustar alastairalastair/* * (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 time.h /// @brief Public deduction header for the optional raw MARS `time` key. /// /// Exposes `resolve_Time_opt`, the canonical entry point that resolves the raw /// optional MARS `time` source into a validated `eckit::Time`. /// /// This deduction owns: /// - direct `time` dictionary access; /// - lexical parsing of the supported raw MARS `time` representations; /// - local validation of compact and colon-separated hour/minute forms. /// /// This deduction does NOT: /// - read or validate the raw MARS `date` key; /// - apply defaulting; /// - construct a `DateTime` or enforce cross-key temporal invariants. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include "eckit/types/Time.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/dateTimeHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 optional raw MARS `time` source. /// /// @section Deduction contract /// - Reads (MARS): `time` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `time` absent -> `std::nullopt`; /// - numeric `time` -> parsed as compact MARS `HHMM`; /// - string `time` -> parsed as `HHMM` or `HH:MM`; /// - malformed, unsupported, or invalid values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `time`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the validated raw MARS time /// when the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or invalid raw `time` input, with the /// original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Time_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "time")) { return std::nullopt; } eckit::Time result{}; if (auto value = get_opt(mars, "time")) { result = detail::parseTimeLong(*value, "time"); } else if (auto value = get_opt(mars, "time")) { result = detail::parseTimeString(*value, "time"); } else { throw Mars2GribDeductionException("Unsupported type for `time`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { std::ostringstream out; out << result; return std::string{"`time` resolved from input dictionaries: value='"} + out.str() + "'"; }()); return result; } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Failed to resolve `time` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/tablesVersion.h0000664000175000017500000001372715246725757026010 0ustar alastairalastair/* * (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 tablesVersion.h /// @brief Deduction of the GRIB tables version identifiers. /// /// This header defines the deductions responsible for resolving the /// GRIB tables version identifiers used during GRIB encoding. /// /// Two resolution strategies are provided: /// - automatic resolution of the latest tables version supported by ecCodes /// - explicit user override via the parameter dictionary /// /// Deductions: /// - extract values from input dictionaries or runtime environment /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply silent fallbacks /// - validate semantic correctness against GRIB specifications /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries or runtime /// - OVERRIDE: value explicitly provided by the user /// /// @section References /// Concept: /// - @ref tablesEncoding.h /// /// Related deductions: /// - @ref localTablesVersion.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Core deduction includes #include "metkit/codes/api/CodesAPI.h" #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 latest GRIB tables version supported by ecCodes. /// /// This deduction resolves the GRIB `tablesVersionLatest` identifier by /// querying the ecCodes runtime environment. /// /// Resolution rules: /// - the value is obtained directly from an ecCodes GRIB2 sample /// - no MARS or parameter input is used /// - no defaulting or inference is applied /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The latest GRIB tables version supported by ecCodes /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the value cannot be resolved from the runtime environment /// /// @note /// The returned value is deterministic for a given ecCodes installation. /// template long resolve_TablesVersionLatest_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory tablesVersionLatest from ecCodes runtime long tablesVersionLatestVal = metkit::codes::codesHandleFromSample("GRIB2")->getLong("tablesVersionLatest"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`tablesVersionLatest` resolved from ecCodes runtime: value='"; logMsg += std::to_string(tablesVersionLatestVal); logMsg += "'"; return logMsg; }()); // Success exit point return tablesVersionLatestVal; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `tablesVersionLatest` from ecCodes runtime", Here())); } }; /// /// @brief Resolve a user-defined GRIB tables version. /// /// This deduction resolves the GRIB `tablesVersion` identifier from the /// parameter dictionary. /// /// Resolution rules: /// - `par::tablesVersion` MUST be present /// - the value is treated as an explicit user override /// - no validation against ecCodes capabilities is performed /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary; must contain `tablesVersion` /// @param[in] opt Options dictionary (unused) /// /// @return The GRIB tables version explicitly requested by the user /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the value cannot be resolved /// /// @note /// Callers requiring strict reproducibility must ensure compatibility /// with the ecCodes runtime environment. /// template long resolve_TablesVersionCustom_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 tablesVersion from parameter dictionary long tablesVersionCustomVal = get_or_throw(par, "tablesVersion"); // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`tablesVersion` overridden from parameter dictionary: value='"; logMsg += std::to_string(tablesVersionCustomVal); logMsg += "'"; return logMsg; }()); // Success exit point return tablesVersionCustomVal; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `tablesVersion` from parameter dictionary", Here())); } }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/simulationType.h0000664000175000017500000001325115246725757026206 0ustar alastairalastair/* * (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 simulationType.h /// @brief Public deduction header for the normalized ProductTimeSpec simulation type. /// /// Exposes `resolve_SimulationType_or_throw`, the canonical entry point that /// derives the normalized ProductTimeSpec simulation type from the raw MARS /// `type` key. /// /// This deduction owns: /// - direct `type` dictionary access; /// - the ProductTimeSpec-specific whitelist that classifies MARS type values as /// forecast-like or analysis-like. /// /// This deduction does NOT: /// - classify simulation regime; /// - construct ProductTimeSpec model artifacts; /// - factor its whitelist with other deductions at this stage. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 normalized ProductTimeSpec simulation type. /// /// @section Deduction contract /// - Reads (MARS): `type` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - analysis-like MARS `type` values -> `SimulationType::Analysis`; /// - forecast-like MARS `type` values -> `SimulationType::Forecast`; /// - start-of-data-assimilation MARS `type` values -> `SimulationType::Analysis`; /// - any other value -> hard error. /// /// The whitelist is intentionally kept identical to the current ProductTimeSpec /// model-local logic. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `type`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return The normalized ProductTimeSpec simulation type. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on missing, malformed, unrecognized, or unsupported raw `type` /// input, with the original cause attached via /// `std::throw_with_nested`. /// template SimulationType resolve_SimulationType_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; (void)par; (void)opt; constexpr std::array analysisTypes = { {"an", "ia", "oi", "3v", "3g", "4g", "ea", "pa", "tpa", "ga", "gai", "ai", "af", "ab", "oai", "ga", "gai"}}; constexpr std::array forecastTypes = { {"fc", "cf", "pf", "cm", "fp", "em", "ep", "es", "fa", "efi", "efic", "bf", "cd", "wem", "wes", "cr", "ses", "taem", "taes", "sg", "sf", "if", "fcmean", "fcmax", "fcmin", "fcstdev", "ssd", "tf", "bf", "cd", "hcmean", "s3", "si", "gbf", "gwt", "est", "icp", "pfc", "sot", "4v"}}; constexpr std::array startOfDataAssimilationTypes = {{"4i", "me", "eme"}}; try { const std::string type = get_or_throw(mars, "type"); const bool isAnalysis = std::any_of(analysisTypes.begin(), analysisTypes.end(), [&type](auto value) { return type == value; }); const bool isForecast = std::any_of(forecastTypes.begin(), forecastTypes.end(), [&type](auto value) { return type == value; }); const bool isAssimilationStart = std::any_of(startOfDataAssimilationTypes.begin(), startOfDataAssimilationTypes.end(), [&type](auto value) { return type == value; }); if (!isAnalysis && !isForecast && !isAssimilationStart) { throw Mars2GribDeductionException( "Failed to resolve `simulationType`: MARS dictionary has unrecognized value for key `type`: " + type, Here()); } // const SimulationType result = // (isAnalysis || isAssimilationStart) ? SimulationType::Analysis : SimulationType::Forecast; const SimulationType result = (isAnalysis) ? SimulationType::Analysis : SimulationType::Forecast; MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`simulationType` resolved from input dictionaries: value='"} + (result == SimulationType::Analysis ? "Analysis" : "Forecast") + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `simulationType` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/iterationNumber.h0000664000175000017500000001030315246725757026322 0ustar alastairalastair/* * (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 iterationNumber.h /// @brief Deduction of the offset to the end of the 4D-Var analysis window. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **offset to the end of the 4D-Var assimilation window** /// from input dictionaries. /// /// The deduction retrieves the offset explicitly from the MARS dictionary. /// No inference, defaulting, normalization, or validation of temporal /// semantics is performed. /// /// 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 resolved from one or more input dictionaries /// /// @section References /// Concept: /// - @ref analysisEncoding.h /// /// Related deductions: /// - @ref lengthOfTimeWindow.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 offset to the end of the 4D-Var analysis window. /// /// @section Deduction contract /// - Reads: `mars["iteration"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the temporal offset between the analysis /// reference time and the end of the 4D-Var assimilation window. /// /// The returned value is treated as an opaque numeric quantity. Its unit /// and interpretation are defined by upstream MARS/IFS conventions and /// are not interpreted by this deduction. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `iteration`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary from which the offset is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The offset to the end of the 4D-Var analysis window. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `iteration` is missing, cannot be converted to `long`, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction assumes that the offset is explicitly provided by /// MARS and does not attempt any inference or defaulting. /// template long resolve_IterationNumber_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 iteration auto iterationNumber = get_or_throw(mars, "iteration"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`iterationNumber` resolved from input dictionaries: value='"; logMsg += std::to_string(iterationNumber) + "'"; return logMsg; }()); // Success exit point return iterationNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `iterationNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/resolution.h0000664000175000017500000001036615246725757025367 0ustar alastairalastair/* * (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 resolution.h /// @brief Deduction of the MARS spatial resolution identifier. /// /// This header defines the deduction responsible for resolving the /// spatial resolution identifier from the MARS dictionary. /// /// The resolved value is passed unchanged to downstream concept /// encoders and is not interpreted or validated at this stage. /// /// Deductions: /// - extract values from MARS, parameter, or option dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - perform semantic interpretation /// - apply defaults or inference /// - validate GRIB table correctness /// /// Error handling follows a fail-fast strategy with nested exception /// propagation to preserve diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained through deduction logic from input dictionaries /// /// @section References /// Concept: /// - @ref destineEncoding.h /// /// Related deductions: /// - @ref activity.h /// - @ref experiment.h /// - @ref generation.h /// - @ref model.h /// - @ref realization.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 spatial resolution identifier from the MARS dictionary. /// /// @section Deduction contract /// - Reads: `mars["resolution"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the mandatory `resolution` entry from the /// MARS dictionary and returns it verbatim. /// /// No inference, defaulting, normalization, or validation of the /// resolution semantics is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must provide the key `resolution`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused). /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary providing the spatial resolution identifier. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Spatial resolution identifier as provided by the MARS dictionary. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `resolution` is missing, cannot be retrieved as a string, /// or if any unexpected error occurs. /// /// @note /// This deduction is deterministic and does not depend on any /// pre-existing GRIB header state. /// template std::string resolve_Resolution_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 resolution identifier from MARS dictionary std::string marsResolutionVal = get_or_throw(mars, "resolution"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`resolution` resolved from MARS dictionary: value='"; logMsg += marsResolutionVal; logMsg += "'"; return logMsg; }()); // Success exit point return marsResolutionVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `resolution` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/pvArray.h0000664000175000017500000005513015246725757024606 0ustar alastairalastair/* * (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 pvArray.h /// @brief Deduction of the GRIB vertical coordinate PV array (`pv`). /// /// This file defines the deduction responsible for resolving the /// **GRIB PV array** (`pv`), which encodes the vertical coordinate /// transformation parameters used by hybrid vertical level definitions. /// /// The deduction supports two mutually exclusive input mechanisms: /// /// 1. **Explicit override** /// - The full PV array is provided directly via the parameter /// dictionary (`par["pv"]`). /// /// 2. **Table-based construction** /// - The PV array is constructed from a declared size /// (`par["pvSize"]`) using a predefined lookup table. /// /// 3. **Defaulting** /// - If neither `par["pv"]` nor `par["pvSize"]` is provided, a default /// PV array is constructed using a predefined lookup with a fixed /// default size (currently 137). /// /// ### Responsibilities /// Deductions in this file are responsible for: /// - extracting PV-related metadata from input dictionaries /// - applying deterministic construction rules /// - validating presence and structural consistency /// - returning a fully constructed PV array to the encoder /// /// Deductions: /// - do NOT infer PV values from GRIB templates /// - do NOT modify or validate physical meaning of PV coefficients /// - do NOT depend on pre-existing GRIB header state /// /// Error handling follows a strict fail-fast policy: /// - missing or ambiguous inputs cause immediate failure /// - errors are reported using Mars2Grib deduction exceptions /// - original errors are preserved via nested exception propagation /// /// Logging follows the mars2grib deduction policy: /// - OVERRIDE: PV array explicitly provided by the parameter dictionary /// - RESOLVE: PV array constructed via deterministic lookup /// - DEFAULT: PV array defaulted to a predefined size due to missing input /// /// @section References /// Concept: /// - @ref levelEncoding.h /// /// Related deductions: /// - @ref level.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #include #include #include #include #include #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 { namespace pv_detail { /// /// @brief Fixed-size hexadecimal representation of an IEEE754 double value. /// /// This alias represents the raw byte layout of a double-precision floating-point /// value encoded as 8 bytes. It is used to store predefined floating-point /// constants in a portable, byte-exact form, independent of the host endianness. /// /// The interpretation of the byte order is handled explicitly by decoding /// utilities elsewhere in the code. /// using HexDouble = std::array; namespace data { // This is pure data hence I just put the include here #include "metkit/mars2grib/backend/deductions/detail/pv_137_be.h" } // namespace data /// /// @brief Entry describing a predefined PV coefficient table. /// /// This structure defines the metadata and storage required to associate a /// logical lookup key with a statically defined array of PV coefficients. /// /// Members: /// - `key` : Logical identifier used to select the PV table (e.g. value /// provided by the caller). This key is not required to match the /// number of coefficients stored. /// - `size` : Number of PV coefficients available in the referenced data array. /// - `data` : Pointer to the first element of a statically allocated array of /// `HexDouble` values encoding the PV coefficients. /// /// The pointer stored in `data` is non-owning and is expected to reference /// memory with static storage duration. /// struct PvEntry { long key; long size; const HexDouble* data; }; /// /// @brief Compile-time lookup table mapping PV logical keys to predefined PV data arrays. /// /// This table defines the association between a logical PV lookup key and a /// statically defined array of PV coefficients encoded as hexadecimal /// IEEE754 double-precision values. /// /// Each entry in the table specifies: /// - a logical lookup key (`PvEntry::key`) used to identify the PV array, /// - the number of PV coefficients stored in the array (`PvEntry::size`), /// - a pointer to the first element of the corresponding static data array. /// /// The data arrays referenced by this table are expected to: /// - have static storage duration, /// - contain exactly `size` elements, /// - store each coefficient as an 8-byte IEEE754 double in big-endian byte order. /// /// This table is defined as `constexpr` to ensure zero runtime initialization /// overhead and to guarantee that the mapping is immutable. /// /// @todo [owner: mival,dgov][scope: deduction][reason: completeness][prio: low] /// - Extend this table with additional PV arrays as required by the /// application domain. /// /// @note /// The logical lookup key (`key`) is not required to match the number of /// coefficients (`size`). This allows decoupling of external identifiers /// from the physical layout of the PV data. /// /// @note /// The lifetime of the referenced data arrays is guaranteed for the entire /// program execution, and the pointers stored in this table are non-owning. /// /// @note /// Data that are currently injected in the table are for demonstration and /// testing purposes only. /// constexpr std::array pv_tables = {{{137, 1002, data::pv_137_1002_be.data()}}}; /// /// @brief Decode a double value from an 8-byte sequence in native byte order. /// /// This helper function converts a sequence of 8 bytes into a native /// double-precision floating-point value by copying the raw byte representation /// directly into a `double` object. /// /// The conversion is performed using `std::memcpy` to avoid strict-aliasing /// violations and undefined behavior associated with pointer reinterpreting. /// /// @param[in] p /// Pointer to an array of 8 bytes representing an IEEE754 double-precision /// floating-point value in the host native byte order. /// /// @return /// The decoded double-precision floating-point value. /// /// @note /// This function assumes: /// - `sizeof(double) == 8` /// - IEEE754 binary64 floating-point format /// /// @note /// The caller is responsible for ensuring that `p` points to at least /// `sizeof(double)` valid bytes. /// inline double bytesToDouble(const uint8_t* p) { double v; std::memcpy(&v, p, sizeof(double)); return v; } /// /// @brief Decode a double value from an 8-byte sequence with reversed byte order. /// /// This helper function converts an array of 8 bytes into a native /// double-precision floating-point value, first reversing the byte order. /// It is intended to handle conversion from a byte sequence whose endianness /// differs from the host representation. /// /// The input byte sequence is copied into a temporary buffer with its byte /// order reversed, after which it is decoded using `bytesToDouble()`. /// /// @param[in] p /// Pointer to an array of 8 bytes representing an IEEE754 double-precision /// floating-point value in non-native byte order. /// /// @return /// The decoded double-precision floating-point value in native representation. /// /// @note /// This function assumes: /// - `sizeof(double) == 8` /// - IEEE754 binary64 floating-point format /// /// @note /// No validation of the input byte sequence is performed. The caller is /// responsible for ensuring that `p` points to at least 8 valid bytes. /// inline double bytesToDoubleSwapped(const uint8_t* p) { uint8_t tmp[8]; std::reverse_copy(p, p + 8, tmp); return bytesToDouble(tmp); } /// /// @brief Determine the host byte order for double-precision floating-point values. /// /// This function detects the native endianness of the host system by interpreting /// a known IEEE754 double-precision value encoded in big-endian byte order. /// The detection is performed by decoding the sentinel byte sequence both with /// native byte order and with reversed byte order, and comparing the results /// against the known reference value. /// /// The function returns whether the host system uses little-endian byte order /// for `double` values. If neither interpretation matches the reference value, /// the host platform is assumed to be unsupported (e.g. non-IEEE754 or unexpected /// `double` layout), and an exception is thrown. /// /// @return /// `true` if the host uses little-endian representation for `double`, /// `false` if the host uses big-endian representation. /// /// @throws Mars2GribDeductionException /// If the host floating-point representation is incompatible with IEEE754 /// binary64 or cannot be reliably interpreted. /// /// @note /// This function assumes: /// - `sizeof(double) == 8` /// - IEEE754 binary64 floating-point format /// /// @note /// The detection is runtime-based and should typically be executed once and /// cached, as the result is invariant for the lifetime of the process. /// inline bool hostIsLittleEndian_or_throw() { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; constexpr double sentinel = 1.23456789; constexpr std::array sentinel_be = {0x3F, 0xF3, 0xC0, 0xCA, 0x42, 0x83, 0xDE, 0x1B}; const double v0 = bytesToDouble(sentinel_be.data()); if (v0 == sentinel) return false; // host BE const double v1 = bytesToDoubleSwapped(sentinel_be.data()); if (v1 == sentinel) return true; // host LE throw Mars2GribDeductionException("Unsupported floating-point representation (non IEEE754 double?)", Here()); // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Decode a double value from an 8-byte hexadecimal representation, with optional byte swapping. /// /// This helper function converts a fixed-size array of 8 bytes representing an /// IEEE754 double-precision floating-point value into a native `double`. /// The input byte sequence is interpreted either directly or with reversed byte /// order depending on the `swap` flag. /// /// The function is typically used when decoding statically defined, big-endian /// hexadecimal tables on hosts whose native endianness may differ. /// /// @param[in] p /// Array of 8 bytes containing the raw IEEE754 representation of a double value. /// /// @param[in] swap /// If `true`, the byte order of `p` is reversed before decoding. If `false`, /// the byte order is used as-is. /// /// @return /// The decoded double-precision floating-point value in native representation. /// /// @note /// This function assumes: /// - `sizeof(double) == 8` /// - IEEE754 binary64 floating-point format /// /// @note /// No validation of the floating-point representation is performed here; the /// correctness of the input bytes is assumed. /// inline double readDoubleMaybeSwapped(const std::array& p, bool swap) { return swap ? bytesToDoubleSwapped(p.data()) : bytesToDouble(p.data()); } /// /// @brief Lookup and decode a predefined PV coefficient array from its logical size. /// /// This function performs a lookup in a compile-time table of predefined PV /// coefficient arrays using the provided logical key (`pvArraySize`). Each table /// entry maps a logical key to a statically-defined array of IEEE754 double values /// encoded as big-endian hexadecimal bytes. /// /// The function: /// 1. Searches the PV lookup table for an entry whose key matches `pvArraySize`. /// 2. Determines the host endianness at runtime using a sentinel-based check. /// 3. Decodes the corresponding hexadecimal byte arrays into native `double` /// values, applying byte-swapping if required. /// 4. Returns the decoded PV coefficients as a `std::vector`. /// /// If the lookup fails or the host floating-point representation is unsupported, /// a domain-specific exception is thrown. All errors are wrapped using /// `std::throw_with_nested` to preserve the full error context. /// /// @param[in] pvArraySize /// Logical lookup key identifying the PV array to retrieve. This key does not /// necessarily correspond to the number of coefficients stored in the array. /// /// @return /// A vector containing the decoded PV coefficients associated with the given /// lookup key. /// /// @throws Mars2GribDeductionException /// - If no PV array is associated with the provided lookup key. /// - If the host floating-point representation is unsupported (non-IEEE754 or /// incompatible endianness). /// - If any error occurs during lookup or decoding; in this case, the original /// exception is preserved via nested exceptions. /// /// @note /// The PV tables are assumed to be stored in big-endian IEEE754 format. /// Host endianness is detected at runtime and decoding is adapted accordingly. /// /// @note /// This function is intended for use at API or backend boundaries and follows /// a fail-fast strategy with rich error context propagation. /// inline std::vector lookup_PvArrayFromSize_or_throw(long pvArraySize) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // 1) Lookup const PvEntry* entry = nullptr; for (const auto& e : pv_tables) { if (e.key == pvArraySize) { entry = &e; break; } } // Not found if (!entry) { std::string errMsg = "No PV array found for size: " + std::to_string(pvArraySize); errMsg += ". Supported sizes is: {137}"; throw Mars2GribDeductionException(errMsg, Here()); } // 2) Sentinel endian detection (need to be done only once, hence `static`) static const bool swap = hostIsLittleEndian_or_throw(); // 3) Decode std::vector out; out.reserve(entry->size); for (long i = 0; i < entry->size; ++i) { out.push_back(readDoubleMaybeSwapped(entry->data[i], swap)); } // RReturn decoded sentinel return out; } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Unable to lookup PV array from size", Here())); } // Remove compiler warning mars2gribUnreachable(); } /// /// @brief Convert a vector of double values into big-endian hexadecimal form. /// /// This function converts each input double into its IEEE754 binary64 /// representation encoded in big-endian byte order, independently of the /// host native endianness. /// /// @param[in] values /// Vector of double-precision floating-point values. /// /// @return /// Vector of HexDouble values encoded in big-endian byte order. /// /// @throws Mars2GribDeductionException /// If the host floating-point representation is unsupported. /// inline std::vector toHexDoubleBE(const std::vector& values) { static_assert(sizeof(double) == 8, "Unsupported double size"); // Determine host endian once static const bool host_is_le = hostIsLittleEndian_or_throw(); std::vector out; out.reserve(values.size()); for (double v : values) { uint8_t raw[8]; std::memcpy(raw, &v, 8); HexDouble h{}; if (host_is_le) { // native LE → write BE for (int i = 0; i < 8; ++i) { h[i] = raw[7 - i]; } } else { // native BE → already BE for (int i = 0; i < 8; ++i) { h[i] = raw[i]; } } out.push_back(h); } return out; } /// /// @brief Generate a C++ include file containing a constexpr HexDouble table. /// /// This function writes a header file defining a `static constexpr` /// `std::array` initialized with the provided hexadecimal data. /// /// The generated file is intended to be included by the PV lookup /// infrastructure and should contain data only (no logic). /// /// @param[in] hex_data /// Vector of HexDouble values to emit. /// /// @param[in] array_name /// Name of the generated C++ array (e.g. "pv_137_be"). /// /// @param[in] filename /// Path to the output include file. /// /// @throws std::runtime_error /// If the output file cannot be created or written. /// inline void writeHexTableInclude(const std::vector& hex_data, const std::string& array_name, const std::string& filename) { std::ofstream os(filename); if (!os) { throw std::runtime_error("Cannot open output file: " + filename); } os << "#pragma once\n\n"; os << "#include \n"; os << "#include \n\n"; os << "using HexDouble = std::array;\n\n"; os << "static constexpr std::array " << array_name << " = {{\n"; os << std::hex << std::setfill('0'); for (const auto& h : hex_data) { os << " {"; for (size_t i = 0; i < 8; ++i) { os << "0x" << std::setw(2) << static_cast(h[i]); if (i != 7) os << ", "; } os << "},\n"; } os << "}};\n"; } } // namespace pv_detail /// /// @brief Resolve the GRIB vertical coordinate PV array (`pv`). /// /// @section Deduction contract /// - Reads: /// - `par["pv"]` (explicit override), OR /// - `par["pvSize"]` (table-based construction) /// - Writes: none /// - Side effects: logging (OVERRIDE, RESOLVE, DEFAULT) /// - Failure mode: throws /// /// This deduction resolves the PV array defining the vertical /// coordinate transformation used for hybrid vertical levels. /// /// Resolution follows a strict precedence order: /// /// 1. **Explicit override** /// If `par["pv"]` is present, the PV array is taken verbatim /// from the parameter dictionary and treated as authoritative. /// /// 2. **Deterministic construction** /// If `par["pv"]` is absent and `par["pvSize"]` is present, /// the PV array is constructed using a predefined lookup /// based solely on the requested size. /// /// 3. **Defaulting** /// If neither `par["pv"]` nor `par["pvSize"]` is provided, a default /// PV array is constructed using a predefined lookup with a fixed /// default size (currently 137). /// /// 4. **Ambiguity error** /// If both `par["pv"]` and `par["pvSize"]` are present, this is treated /// as an error due to conflicting input, and an exception is thrown. /// /// No attempt is made to validate the physical meaning, /// monotonicity, or numerical consistency of the PV values. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused by this deduction). /// /// @tparam ParDict_t /// Type of the parameter dictionary providing `pv` or `pvSize`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary providing PV configuration. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Fully resolved PV array as a vector of double-precision values. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the PV array cannot be retrieved or constructed /// - any unexpected error occurs during deduction /// /// @note /// - This deduction is deterministic for a given parameter dictionary. /// - The returned PV array is passed verbatim to GRIB encoding logic. /// template std::vector resolve_PvArray_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Checks for presence of keys bool hasPV = has(par, "pv"); bool hasPVSize = has(par, "pvSize"); std::vector pvArrayVal; if (hasPV && !hasPVSize) { // Get the pv array directly pvArrayVal = get_or_throw>(par, "pv"); // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`pvArray` overridden from input dictionaries: size='"; logMsg += std::to_string(pvArrayVal.size()); logMsg += "'"; return logMsg; }()); } else if (!hasPV && hasPVSize) { // Get the pvArray size for lookup long pvArraySize = get_or_throw(par, "pvSize"); // Lookup of the pv array from size pvArrayVal = pv_detail::lookup_PvArrayFromSize_or_throw(pvArraySize); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`pvArray` resolved from input dictionaries: size='"; logMsg += std::to_string(pvArrayVal.size()); logMsg += "' (lookup from `pvSize`=" + std::to_string(pvArraySize) + ")"; return logMsg; }()); } else if (!hasPV && !hasPVSize) { // Get the pvArray size for lookup // Value 137 is the default value in simulations and must always be present in the lookup table long pvArraySize = 137; // Lookup of the pv array from size pvArrayVal = pv_detail::lookup_PvArrayFromSize_or_throw(pvArraySize); // Emit DEFAULT log entry MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`pvArray` defaulted to size='"; logMsg += std::to_string(pvArrayVal.size()); logMsg += "' (lookup from `pvSize`=" + std::to_string(pvArraySize) + ")"; return logMsg; }()); } else { // Both `pv` and `pvSize` are present: this is an error due to ambiguity std::string logMsg = "Ambiguous PV array configuration: both `pv` and `pvSize` are present in the parameter dictionary"; throw Mars2GribDeductionException(logMsg, Here()); } // Exit with success return pvArrayVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `pvArray` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/generatingProcessIdentifier.h0000664000175000017500000001322215246725757030643 0ustar alastairalastair/* * (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 generatingProcessIdentifier.h /// @brief Optional deduction of the GRIB `generatingProcessIdentifier` key. /// /// This header provides an optional passthrough deduction for the GRIB /// `generatingProcessIdentifier` key. /// /// @section Deduction contract /// - Reads: `par["generatingProcessIdentifier"]` (optional) /// - Writes: none /// - Side effects: logging (RESOLVE when present, otherwise DEFAULT-style skip message) /// - Failure mode: returns `std::nullopt` when absent; throws on unexpected errors /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Optionally resolve the GRIB `generatingProcessIdentifier` key from parameters. /// /// This deduction provides an **optional passthrough resolution** for the GRIB /// `generatingProcessIdentifier` key. /// /// When present, the value is read **verbatim** from the parameter dictionary /// (`par`) and returned without modification or validation. /// If the key is not present, the function returns `std::nullopt`. /// /// @important /// This function performs **no deduction logic** and **no semantic validation**. /// It exists solely to allow expert or legacy workflows to explicitly inject /// a GRIB `generatingProcessIdentifier` value via the parameter dictionary. /// /// The use of this mechanism is **discouraged** for production workflows, as it /// may lead to inconsistent or non-reproducible GRIB headers if not coordinated /// with the rest of the encoding logic. /// /// @section Semantics /// - Input source: parameter dictionary (`par`) /// - Resolution type: optional passthrough /// - Validation: none /// - Defaulting: none /// /// @tparam MarsDict_t Type of the MARS dictionary (unused) /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary (unused) /// @param[in] par Parameter dictionary; may contain `generatingProcessIdentifier` /// @param[in] opt Options dictionary (unused) /// /// @return An optional `long`: /// - the value of `generatingProcessIdentifier` if present in `par` /// - `std::nullopt` otherwise /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If any unexpected error occurs while accessing the parameter dictionary. /// /// @warning /// This deduction must **not** be relied upon as the primary mechanism for setting /// `generatingProcessIdentifier`. /// A proper, deterministic deduction based on MARS semantics or encoder policy /// should be preferred whenever possible. /// /// @todo [owner: mds,dgov][scope: deduction][reason: legacy][prio: medium] /// - Need to define a proper table and a proper logic to deduce the `generatingProcessIdentifier` /// - Evaluate whether this passthrough deduction can be removed once all /// generating process identifiers are derived deterministically. /// - Consider replacing this with a validated, table-driven deduction. /// /// template std::optional resolve_GeneratingProcessIdentifier_opt([[maybe_unused]] const MarsDict_t& mars, const ParDict_t& par, [[maybe_unused]] const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Get generatingProcessIdentifier from mars dictionary std::optional generatingProcessIdentifierVal = get_opt(par, "generatingProcessIdentifier"); if (generatingProcessIdentifierVal.has_value()) { // Logging of the par::generatingProcessIdentifier MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`generatingProcessIdentifier`: mapped from `par::generatingProcessIdentifier`: actual='"; logMsg += std::to_string(generatingProcessIdentifierVal.value()) + "'"; return logMsg; }()); // Return the generatingProcessIdentifier from the parameter dictionary return {generatingProcessIdentifierVal}; } else { // Logging of the par::generatingProcessIdentifier absence MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`generatingProcessIdentifier`: `par::generatingProcessIdentifier` not present, return " "std::nullopt to skip deduction"; return logMsg; }()); // Key not present; return std::nullopt return std::nullopt; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Unable to get `generatingProcessIdentifier` from parameter dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/type.h0000664000175000017500000000735715246725757024153 0ustar alastairalastair/* * (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 type.h /// @brief Deduction of the MARS `type` identifier. /// /// This header defines the deduction responsible for resolving the /// MARS `type` key used to classify the nature of a field /// (e.g. analysis, forecast, ensemble member). /// /// The value is retrieved directly from the MARS dictionary and is /// treated as mandatory. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate semantic correctness of the returned value /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref marsEncoding.h /// /// Related deductions: /// - @ref class.h /// - @ref stream.h /// - @ref expver.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 MARS `type` identifier. /// /// This deduction resolves the MARS `type` key from the MARS dictionary. /// /// Resolution rules: /// - `mars::type` MUST be present /// - the value is retrieved verbatim as a string /// - no inference, defaulting, or validation is applied /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain `type` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved MARS `type` identifier /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the value cannot be resolved /// /// @note /// The returned value is not interpreted by this deduction and is /// assumed to follow MARS conventions. /// template std::string resolve_Type_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 type from MARS dictionary std::string marsTypeVal = get_or_throw(mars, "type"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`type` resolved from input dictionaries: value='"; logMsg += marsTypeVal; logMsg += "'"; return logMsg; }()); // Success exit point return marsTypeVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException("Failed to resolve `type` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/scaleFactorOfCentralWaveNumber.h0000664000175000017500000001050615246725757031200 0ustar alastairalastair/* * (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 scaleFactorOfCentralWaveNumber.h /// @brief Deduction of the GRIB `scaleFactorOfCentralWaveNumber` key. /// /// This file defines the deduction responsible for resolving the GRIB /// `scaleFactorOfCentralWaveNumber` key used in satellite-based products. /// /// Together with `scaledValueOfCentralWaveNumber`, this value is used /// to encode the central wave number according to the GRIB specification. /// /// The deduction: /// - reads exclusively from the parameter dictionary /// - performs no inference or defaulting /// - emits structured diagnostic logging /// /// Error handling follows a fail-fast strategy with nested exception /// propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref satelliteEncoding.h /// /// Related deductions: /// - @ref scaledValueOfCentralWaveNumber.h /// - @ref channel.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 GRIB `scaleFactorOfCentralWaveNumber` identifier. /// /// @section Deduction contract /// - Reads: `par["scaleFactorOfCentralWaveNumber"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the scale factor applied to the central wave /// number in satellite products. /// /// The value is retrieved verbatim from the parameter dictionary. /// No inference from MARS metadata and no validation of the numerical /// range is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide /// `scaleFactorOfCentralWaveNumber`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary containing the scale factor. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// Scale factor of the central wave number. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `scaleFactorOfCentralWaveNumber` is missing, cannot be /// retrieved as a `long`, or if any unexpected error occurs. /// /// @note /// This deduction is deterministic and independent of GRIB header state. /// template long resolve_ScaleFactorOfCentralWaveNumber_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 scale factor of central wave number from parameter dictionary auto scaleFactorOfCentralWaveNumberVal = get_or_throw(par, "scaleFactorOfCentralWaveNumber"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`scaleFactorOfCentralWaveNumber` resolved from parameter dictionary: value='"; logMsg += std::to_string(scaleFactorOfCentralWaveNumberVal); logMsg += "'"; return logMsg; }()); // Success exit point return scaleFactorOfCentralWaveNumberVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Failed to resolve `scaleFactorOfCentralWaveNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/instrumentType.h0000664000175000017500000001151215246725757026230 0ustar alastairalastair/* * (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 instrumentType.h /// @brief Deduction of the instrument type identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **instrument type identifier** from MARS metadata. /// /// The deduction retrieves the instrument 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, normalization, or defaulting /// - 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 satelliteEncoding.h /// /// Related deductions: /// - @ref channel.h /// - @ref satelliteNumber.h /// - @ref satelliteSeries.h /// - @ref scaleFactorOfCentralWaveNumber.h /// - @ref scaledValueOfCentralWaveNumber.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 instrument type identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["instrument"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the instrument type identifier by retrieving /// the mandatory MARS key `instrument` and returning its value as a /// `long`. /// /// No semantic interpretation, normalization, or validation is applied. /// The meaning and allowed values of the instrument type identifier are /// defined by upstream MARS conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to /// `instrument` 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 instrument type identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved instrument type identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `instrument` is missing, cannot be retrieved as a /// `long`, or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult instrument registries or GRIB tables. /// template long resolve_InstrumentType_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 instrument type long instrumentType = get_or_throw(mars, "instrument"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`instrumentType` resolved from input dictionaries: value='"; logMsg += std::to_string(instrumentType) + "'"; return logMsg; }()); // Success exit point return instrumentType; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `instrumentType` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/class.h0000664000175000017500000001126715246725757024272 0ustar alastairalastair/* * (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 class.h /// @brief Deduction of the MARS `class` identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **MARS data class identifier** from input dictionaries. /// /// The deduction retrieves the class 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, normalization, or defaulting /// - 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 marsEncoding.h /// /// Related deductions: /// - @ref expver.h /// - @ref stream.h /// - @ref type.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 MARS data class identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["class"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the MARS data class identifier by retrieving /// the mandatory MARS key `class` and returning its value as a /// `std::string`. /// /// No semantic interpretation, normalization, or defaulting is applied. /// The meaning of the class identifier is defined by upstream MARS /// conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `class` /// and conversion to `std::string`. /// /// @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 data class identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved MARS data class identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `class` is missing, cannot be retrieved as a string, /// or if any unexpected error occurs during deduction. /// /// @note /// This deduction performs presence-only validation and does not /// consult GRIB tables or apply semantic constraints. /// template std::string resolve_Class_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 class std::string marsClassVal = get_or_throw(mars, "class"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`class` resolved from input dictionaries: value='" + marsClassVal + "'"; return logMsg; }()); // Success exit point return marsClassVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `class` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/systemNumber.h0000664000175000017500000000726015246725757025660 0ustar alastairalastair/* * (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 systemNumber.h /// @brief Deduction of the GRIB wave system identifier. /// /// This header defines the deduction responsible for resolving the /// GRIB wave system identifier used in wave-related products. /// /// The value is obtained directly from MARS metadata and is treated /// as mandatory. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate semantic correctness /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref longrangeEncoding.h /// /// Related deductions: /// - @ref methodNumber.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 GRIB wave system identifier. /// /// This deduction resolves the wave system identifier by retrieving /// the mandatory MARS key `system`. /// /// Resolution rules: /// - `mars::system` MUST be present /// - No defaulting or inference is applied /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain `system` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved wave system identifier /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the system identifier cannot be resolved /// /// @note /// This deduction is deterministic and does not rely on any /// pre-existing GRIB header state. /// template long resolve_SystemNumber_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 systemNumber from MARS dictionary auto systemNumber = get_or_throw(mars, "system"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`systemNumber` resolved from input dictionaries: value='"; logMsg += std::to_string(systemNumber); logMsg += "'"; return logMsg; }()); // Success exit point return systemNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `systemNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/waveDirectionNumber.h0000664000175000017500000000761415246725757027142 0ustar alastairalastair/* * (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 waveDirectionNumber.h /// @brief Deduction of the GRIB wave direction number. /// /// This header defines the deduction responsible for resolving the /// GRIB wave direction number used in spectral wave products. /// /// The value is obtained directly from MARS metadata and represents /// the wave direction index associated with the encoded field. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Deductions do NOT: /// - infer missing values /// - apply defaults or fallbacks /// - validate semantic correctness /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - RESOLVE: value obtained directly from input dictionaries /// /// @section References /// Concept: /// - @ref waveEncoding.h /// /// Related deductions: /// - @ref periodItMin.h /// - @ref periodItMax.h /// - @ref waveDirectionGrid.h /// - @ref waveFrequencyGrid.h /// - @ref waveFrequencyNumber.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 GRIB wave direction number. /// /// This deduction retrieves the wave direction number from the /// MARS dictionary using the mandatory key `direction`. /// /// The resolved value represents the wave direction index used /// in spectral wave products and GRIB encoding. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary (unused) /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary; must contain the key `direction` /// @param[in] par Parameter dictionary (unused) /// @param[in] opt Options dictionary (unused) /// /// @return The resolved wave direction number /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `direction` is missing /// - the value cannot be converted to `long` /// - any unexpected error occurs /// template long resolve_WaveDirectionNumber_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 wave direction number from MARS dictionary auto waveDirectionNumber = get_or_throw(mars, "direction"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`waveDirectionNumber` resolved from input dictionaries: value='"; logMsg += std::to_string(waveDirectionNumber); logMsg += "'"; return logMsg; }()); // Success exit point return waveDirectionNumber; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `waveDirectionNumber` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/step.h0000664000175000017500000001440315246725757024133 0ustar alastairalastair/* * (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 step.h /// @brief Public deduction header for `step`. /// /// Exposes `resolve_Step_opt` and `resolve_Step_or_throw`, the canonical entry /// points that resolve the optional normalized MARS `step` duration. /// /// This deduction owns: /// - direct `step` dictionary access; /// - lexical parsing of numeric and unit-suffixed step forms; /// - local support-domain validation of the resolved duration; /// - materialization of the deduction-local `TimeDuration` output. /// /// This deduction does NOT: /// - infer step from other temporal sources; /// - classify ProductTimeSpec semantics; /// - construct the final ProductTimeSpec model. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/backend/deductions/detail/timeDurationHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.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 `step` as an optional normalized duration. /// /// @section Deduction contract /// - Reads (MARS): `step` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `step` absent -> `std::nullopt`; /// - numeric `step` -> interpreted as hours; /// - string `step` -> parsed using the supported duration language; /// - resolved duration must be non-negative; /// - positive sub-hourly or non-hour-aligned durations are rejected. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `step`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the normalized step when /// the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or locally invalid step input, with the /// original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Step_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "step")) { return std::nullopt; } long seconds = 0; if (auto value = get_opt(mars, "step")) { seconds = detail::parseDurationStringSeconds(*value, "step"); } else if (auto value = get_opt(mars, "step")) { seconds = detail::checkedHoursToSeconds(*value, "step"); } else { throw Mars2GribDeductionException("Unsupported type for `step`", Here()); } if (seconds < 0) { throw Mars2GribDeductionException("`step` must be non-negative", Here()); } if (seconds > 0 && seconds % 3600L != 0) { throw Mars2GribDeductionException( "Positive sub-hourly or non-hour-aligned `step` is recognized but unsupported", Here()); } const TimeDuration result = detail::canonicalElapsedDuration(seconds, "step"); MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`step` resolved from input dictionaries: value='"} + std::to_string(result.length) + "' unit='" + tables::enum2name_TimeUnit_or_throw(result.unit) + "'"; }()); return result; } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Failed to resolve `step` from input dictionaries", Here())); } mars2gribUnreachable(); } /// /// @brief Resolve `step` or throw if absent. /// /// Thin wrapper around `resolve_Step_opt` that converts `std::nullopt` into a /// hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type. /// @tparam OptDict_t Options dictionary type. /// /// @param[in] mars MARS dictionary providing `step`. /// @param[in] par Parameter dictionary (forwarded). /// @param[in] opt Options dictionary (forwarded). /// /// @return The normalized step duration. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// if the source is absent, malformed, unsupported, or locally invalid; /// failures are wrapped via `std::throw_with_nested`. /// template TimeDuration resolve_Step_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { const auto result = resolve_Step_opt(mars, par, opt); if (result.has_value()) { return *result; } throw Mars2GribDeductionException("`step` is not defined in the Mars dictionary", Here()); } catch (...) { std::throw_with_nested(Mars2GribDeductionException("Unable to get `step` from Mars dictionary", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/modelErrorType.h0000664000175000017500000001126315246725757026135 0ustar alastairalastair/* * (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 modelErrorType.h /// @brief Deduction of the model-error type identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **model-error type identifier** (`modelErrorType`) from /// the parameter dictionary. /// /// The value is not derivable from MARS alone. It must be supplied via /// the parameter dictionary by the upstream tool, typically read from /// the input GRIB1 handle being re-encoded. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - enforcing deterministic resolution rules /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply heuristic or data-driven inference /// - do NOT validate against GRIB code tables unless explicitly required /// /// 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 resolved directly from input dictionaries /// /// @section References /// Concept: /// - @ref modelErrorEncoding.h /// /// Related deductions: /// - @ref componentIndex.h /// - @ref numberOfComponents.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the model-error type identifier. /// /// @section Deduction contract /// - Reads: `par["modelErrorType"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction retrieves the model-error type identifier from the /// parameter dictionary. /// /// The value is treated as mandatory: it cannot be derived from MARS /// metadata alone and must be supplied by the upstream tool that /// populates the parameter dictionary (typically read from the input /// GRIB1 handle being re-encoded). /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must provide the key /// `modelErrorType`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which the model-error type is retrieved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The model-error type identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `modelErrorType` is missing from the parameter dictionary, /// cannot be converted to `long`, or if any unexpected error occurs /// during deduction. /// /// @note /// This deduction does not infer or default the value. Absence of the /// key in the parameter dictionary is considered a contract violation /// by the upstream tool. /// template long resolve_ModelErrorType_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 parameter-dictionary modelErrorType long modelErrorType = get_or_throw(par, "modelErrorType"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`modelErrorType` resolved from input dictionaries: value="; logMsg += std::to_string(modelErrorType); return logMsg; }()); // Success exit point return modelErrorType; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `modelErrorType` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/month.h0000664000175000017500000001216115246725757024304 0ustar alastairalastair/* * (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 month.h /// @brief Public deduction header for the optional raw MARS `month` key. /// /// Exposes `resolve_Month_opt`, the canonical entry point that resolves the raw /// optional MARS `month` source into a validated canonical integer month. /// /// This deduction owns: /// - direct `month` dictionary access; /// - lexical parsing of integer and textual month-enum representations; /// - local validation that the resolved month value is in the enum range /// `[1,12]`. /// /// This deduction does NOT: /// - validate the relationship between `year` and `month`; /// - construct a date or datetime; /// - apply any ProductTimeSpec-specific semantics. /// /// @ingroup mars2grib_backend_deductions /// #pragma once #include #include #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/backend/deductions/detail/parseHelpers.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { namespace detail { /// /// @brief Validate one canonical integer month against the MARS enum range. /// /// The MARS `month` keyword is an enum whose canonical numeric values are the /// calendar months `1..12`. /// /// @param[in] value Canonical integer month value. /// @param[in] key Human-readable source-key name used in diagnostics. /// @return `value` unchanged when it lies in `[1,12]`. /// @throws Mars2GribDeductionException if `value` lies outside `[1,12]`. /// inline long checkedMonthEnumValue(long value, const std::string& key) { using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; if (value < 1 || value > 12) { throw Mars2GribDeductionException("`" + key + "` must be in [1,12]", Here()); } return value; } } // namespace detail /// /// @brief Resolve the optional raw MARS `month` source. /// /// @section Deduction contract /// - Reads (MARS): `month` /// - Reads (par): none (signature-only, reserved) /// - Reads (opt): none (signature-only, reserved) /// - Writes: none /// - Side effects: one `MARS2GRIB_LOG_RESOLVE` line on success /// - Failure mode: throws `Mars2GribDeductionException` (nested-with) /// /// Resolution rules: /// - `month` absent -> `std::nullopt`; /// - numeric `month` -> validated as an enum value in `[1,12]`; /// - string `month` -> parsed as one supported month alias or decimal integer, /// then validated as an enum value in `[1,12]`; /// - malformed, unsupported, or out-of-range values -> hard error. /// /// @tparam MarsDict_t MARS dictionary type. /// @tparam ParDict_t Parameter dictionary type (currently unused). /// @tparam OptDict_t Options dictionary type (currently unused). /// /// @param[in] mars MARS dictionary providing `month`. /// @param[in] par Parameter dictionary (signature-only). /// @param[in] opt Options dictionary (signature-only). /// /// @return `std::optional` containing the canonical integer month when /// the key is present, `std::nullopt` otherwise. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// on malformed, unsupported, or out-of-range raw `month` input, with /// the original cause attached via `std::throw_with_nested`. /// template std::optional resolve_Month_opt(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_opt; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; (void)par; (void)opt; try { if (!has(mars, "month")) { return std::nullopt; } long result = 0; if (auto value = get_opt(mars, "month")) { result = detail::checkedMonthEnumValue(*value, "month"); } else if (auto value = get_opt(mars, "month")) { result = detail::checkedMonthEnumValue(detail::parseMonthEnum(*value, "month"), "month"); } else { throw Mars2GribDeductionException("Unsupported type for `month`", Here()); } MARS2GRIB_LOG_RESOLVE([&]() { return std::string{"`month` resolved from input dictionaries: value='"} + std::to_string(result) + "'"; }()); return result; } catch (...) { std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `month` from input dictionaries", Here())); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/backend/deductions/subSetTrunc.h0000664000175000017500000001477715246725757025457 0ustar alastairalastair/* * (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 subSetTrunc.h /// @brief Deduction of the spectral subset truncation parameter. /// /// This header defines the deduction responsible for resolving the /// spectral subset truncation parameter used in spectral packing /// configurations. /// /// The value is obtained from the parameter dictionary when provided. /// If absent, a deterministic default is applied. /// /// Deductions: /// - extract values from input dictionaries /// - apply deterministic resolution logic /// - emit structured diagnostic logging /// /// Error handling follows a strict fail-fast strategy with nested /// exception propagation to preserve full diagnostic context. /// /// Logging policy: /// - OVERRIDE: value overridden from input dictionaries /// - DEFAULT: value defaulted due to missing input /// /// @section References /// Concept: /// - @ref packingEncoding.h /// /// Related deductions: /// - @ref bitsPerValue.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include #include #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 GRIB spectral subset truncation parameter. /// /// This deduction resolves the spectral subset truncation parameter /// used to define a reduced set of spectral coefficients. /// /// Resolution rules: /// - If `par::subSetTruncation` is present and valid, its value is used directly. /// - If `par::subSetTruncation` is absent, the value defaults explicitly to the /// smaller of the MARS truncation (`mars::truncation`) and a fixed maximum of 20. /// /// @tparam MarsDict_t Type of the MARS dictionary /// @tparam ParDict_t Type of the parameter dictionary /// @tparam OptDict_t Type of the options dictionary (unused) /// /// @param[in] mars MARS dictionary providing `truncation` for defaulting and validation /// @param[in] par Parameter dictionary; may contain `subSetTruncation` /// @param[in] opt Options dictionary (unused) /// /// @return The resolved spectral subset truncation value /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// - If an unexpected error occurs during dictionary access /// - If `par::subSetTruncation` is provided but exceeds the MARS truncation or is negative /// - If MARS truncation is invalid when needed for defaulting /// /// @note /// This deduction is fully deterministic and does not depend on /// any pre-existing GRIB header state. /// template long resolve_SubSetTruncation_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::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; static const std::regex tGridRegex("^T(0|[1-9][0-9]*)$"); try { // subSetTruncation must not be larger than any pentagonalResolutionParameter // NOTE: Mars keyword truncation is equivalent to pentagonalResolutionParameter{J,K,M} // At ECMWF we cannot produce spherical harmonics with different values for J/K/M const long truncation = [&]() { if (get_or_throw(opt, "skipSection3")) { const std::string grid = get_or_throw(mars, "grid"); std::smatch tGridMatch; if (!std::regex_match(grid, tGridMatch, tGridRegex) || tGridMatch.size() != 2) { throw Mars2GribDeductionException("Cannot extract truncation from MARS grid '" + grid + "'", Here()); } return std::stol(tGridMatch[1].str()); } return get_or_throw(mars, "truncation"); }(); if (truncation < 0) { throw Mars2GribDeductionException( "Invalid MARS truncation: value='" + std::to_string(truncation) + "' is negative", Here()); } if (has(par, "subSetTruncation")) { // Retrieve subSetTruncation from parameter dictionary long subSetTrunc = get_or_throw(par, "subSetTruncation"); // Validate subSetTruncation if (subSetTrunc < 0) { throw Mars2GribDeductionException( "Invalid `subSetTruncation`: value='" + std::to_string(subSetTrunc) + "' is negative", Here()); } if (subSetTrunc > truncation) { throw Mars2GribDeductionException("Invalid `subSetTruncation`: value='" + std::to_string(subSetTrunc) + "' exceeds MARS truncation='" + std::to_string(truncation) + "'", Here()); } // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { return "`subSetTruncation` overridden from parameter dictionary: value='" + std::to_string(subSetTrunc) + "'"; }()); // Success exit point return subSetTrunc; } else { // Note: This logic for default subSetTruncation reflects the behaviour of IFS const long defaultSubSetTruncation = truncation >= 213 ? 20L : std::min(10L, truncation); // Emit DEFAULT log entry for defaulting MARS2GRIB_LOG_DEFAULT([&]() { return "`subSetTruncation` defaulted: value='" + std::to_string(defaultSubSetTruncation) + "'"; }()); // Success exit point return defaultSubSetTruncation; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `subSetTruncation` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.20.2/src/metkit/mars2grib/copilot-instructions.md0000664000175000017500000000623315246725757024016 0ustar alastairalastair# Copilot Instructions for mars2grib These instructions apply to AI-assisted changes under `src/metkit/mars2grib`. ## Concept Or Variant Decision Before implementing any modification in the mars2grib concept system, determine whether the requested behavior is a new concept or a new variant of an existing concept. - Use a new concept when the feature is an independent semantic axis that must be composable with other concepts. - Use a new variant when the feature is an alternative realization inside an existing semantic axis and does not need independent composability. - This distinction is usually not reliably deducible from code structure alone. - If the user has not explicitly said whether the change is a new concept or a variant, ask the user before implementing. ## Level Concept Guardrail The `level` concept is intentionally constrained. In the GRIB header, vertical level information is ultimately represented by these six low-level fixed-surface keys: - `typeOfFirstFixedSurface` - `scaleFactorOfFirstFixedSurface` - `scaledValueOfFirstFixedSurface` - `typeOfSecondFixedSurface` - `scaleFactorOfSecondFixedSurface` - `scaledValueOfSecondFixedSurface` Do not set these keys directly as a shortcut or workaround. mars2grib encodes only official level definitions by relying on `typeOfLevel` plus, when needed, `level`, `topLevel`, `bottomLevel`, and PV-array data. Each supported `typeOfLevel` maps to a prescribed fixed-surface configuration. Some virtual `typeOfLevel` values exist because they cannot be introduced in ecCodes for backward-compatibility reasons. If a requested change appears to require direct writes to fixed-surface keys, do not implement that approach. Instead, add or adjust the appropriate `LevelType` variant, matcher mapping, or deduction so the level remains encoded through the official level abstraction. ## Documentation synchronization rule When working on a pull request: 1. Determine the set of files modified by the PR. 2. From that set, consider only files under: - `src/metkit/mars2grib` - Changes under `tests/mars2grib` require documentation updates only if they affect documented public behaviour 3. For each of those files: - Verify that the related documentation is present in the concrete doc locations for mars2grib, including (where applicable): - `src/metkit/mars2grib/docs/**` - Any module-level `.md` files or Doxygen pages associated with the modified code - Verify that the documentation in these locations is up to date with the code changes - If documentation is missing or outdated in these locations, propose the required updates 4. Do not request documentation changes for files outside these paths. ## Definition of “documentation in sync” Documentation must: - Describe the current public behavior and interfaces - Reflect any new parameters, options, or outputs - Remove references to deleted functionality ## PR review behavior During PR reviews: - Explicitly list the impacted files in the two target directories - State whether documentation is: - ✅ in sync - ❌ missing - ❌ outdated - Suggest concrete doc patches when needed, referencing the specific lines or sections that require updates.metkit-1.20.2/src/metkit/mars2grib/CoreOperations.h0000664000175000017500000010004415246725757022361 0ustar alastairalastair/* * (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 CoreOperations.h /// @brief High-level service layer for GRIB encoding and resolution orchestration. /// /// This header defines the `CoreOperations` suite, providing the primary /// functional building blocks for the mars2grib library. /// /// These operations facilitate a staged translation pipeline: /// 1. **Sanitization**: Normalizing input dictionaries against the language definition. /// 2. **Header Resolution**: Determining the GRIB structural layout and encoding metadata. /// 3. **Value Injection**: Physical realization of the GRIB data section. /// 4. **Diagnostic Capture**: Generating regression data for structural validation. /// /// /// --- /// /// ## Transitional cache preparation /// /// This header also contains a temporary staged-encoding path based on /// `prepare()` and `finaliseEncoding()`. /// /// This code is a preparatory step toward a future implementation where /// cache lifecycle and reuse are fully internalized inside the core layer. /// /// At the current stage, the staged API exists to support benchmarking, /// comparison, and incremental integration of the future cache design. /// /// @ingroup mars2grib_core /// #pragma once // System includes #include #include #include /// Project includes /// @note: clang-format needs to be off here to preserve the logical grouping of /// includes and avoid unnecessary reordering that breaks the layering and dependencies. // clang-format off #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/backend/concepts/GeneralRegistry.h" #include "metkit/mars2grib/backend/encodeValues.h" #include "metkit/mars2grib/frontend/header/SpecializedEncoder.h" #include "metkit/mars2grib/frontend/make_HeaderLayout.h" #include "metkit/mars2grib/frontend/normalization/normalization.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" // clang-format on namespace metkit::mars2grib { /// /// @brief Internal engine providing atomic encoding and diagnostic services. /// struct CoreOperations { /// /// @brief Normalize input dictionaries against the library language definition. /// /// This operation performs key-value sanitization for both MARS and Parameter /// metadata. It utilizes a **reference-redirection strategy**: if no /// modification is required, the returned references point to the original /// inputs; otherwise, they point to the provided scratch buffers. /// /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Encoding options dictionary type /// /// @param[in] inputMars Original MARS request /// @param[in] inputMisc Original Parameter metadata /// @param[in] opt Encoding options /// @param[in] lang Language definition (eckit::Value) /// @param[out] scratchMars Scratch buffer for MARS sanitization /// @param[out] scratchMisc Scratch buffer for Parameter sanitization /// /// @return A tuple containing const references to the active (sanitized) data /// template static std::tuple normalize_if_enabled( const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& opt, const eckit::Value& lang, MarsDict_t& scratchMars, ParDict_t& scratchMisc) { try { const MarsDict_t& activeMars = frontend::normalization::normalize_MarsDict_if_enabled(inputMars, opt, lang, scratchMars); const ParDict_t& activePar = frontend::normalization::normalize_MiscDict_if_enabled(inputMisc, opt, lang, scratchMisc); return {activeMars, activePar}; } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"normalize_if_enabled\","; json += "\"inputMars\":"; json += dict_to_json(inputMars); json += ","; json += "\"inputMisc\":"; json += dict_to_json(inputMisc); json += ","; json += "\"options\":"; json += dict_to_json(opt); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during metadata normalization", contextJson, Here())); } } /// /// @brief Resolve and encode GRIB header metadata. /// /// Executes the structural resolution phase to determine the GRIB layout /// and triggers the specialized metadata encoder to populate the header /// sections of the output object. /// /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Encoding options dictionary type /// @tparam OutDict_t Output GRIB handle/dictionary type /// template static std::unique_ptr encodeHeader(const MarsDict_t& mars, const ParDict_t& misc, const OptDict_t& opt) { try { using metkit::mars2grib::frontend::make_HeaderLayout_or_throw; using metkit::mars2grib::frontend::header::SpecializedEncoder; auto layout = make_HeaderLayout_or_throw(mars, opt); return SpecializedEncoder{std::move(layout)}.encode(mars, misc, opt); } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"encodeHeader\","; json += "\"mars\":"; json += dict_to_json(mars); json += ","; json += "\"misc\":"; json += dict_to_json(misc); json += ","; json += "\"options\":"; json += dict_to_json(opt); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during header encoding", contextJson, Here())); } } template static std::unique_ptr encodeHeaderWithNormalization(const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& options, const eckit::Value& language) { MarsDict_t scratchMars; ParDict_t scratchMisc; try { auto [activeMars, activeMisc] = normalize_if_enabled(inputMars, inputMisc, options, language, scratchMars, scratchMisc); return encodeHeader(activeMars, activeMisc, options); } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"encodeHeaderWithNormalization\","; json += "\"inputMars\":"; json += dict_to_json(inputMars); json += ","; json += "\"inputMisc\":"; json += dict_to_json(inputMisc); json += ","; json += "\"options\":"; json += dict_to_json(options); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during normalized header encoding", contextJson, Here())); } } /// /// @brief Inject numeric field values into a GRIB handle. /// /// A procedural operation that handles bitmap generation and physical /// data compression. Utilizes spans for zero-copy data passing. /// /// @tparam Val_t Numeric precision (float or double) /// @tparam ParDict_t Parameter dictionary type /// @tparam OptDict_t Encoding options dictionary type /// @tparam OutDict_t Output GRIB handle/dictionary type /// template static std::unique_ptr encodeValues(backend::Span values, const MarsDict_t& mars, const ParDict_t& misc, const OptDict_t& opt, std::unique_ptr handle) { try { if (metkit::mars2grib::utils::dict_traits::get_or_throw(opt, "skipSection3")) { metkit::mars2grib::backend::encodeValuesGridSpec(values, mars, misc, opt, *handle); return handle; } else { metkit::mars2grib::backend::encodeValues(values, misc, opt, *handle); return handle; } } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"encodeValues\","; json += "\"mars\":"; json += dict_to_json(mars); json += ","; json += "\"misc\":"; json += dict_to_json(misc); json += ","; json += "\"options\":"; json += dict_to_json(opt); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during value encoding", contextJson, Here())); } } /// /// @brief Encode a value field into a GRIB message. /// /// This function performs the complete encoding pipeline: /// - optional metadata normalization, /// - GRIB header construction, /// - value injection. /// /// The function is exception-safe and returns an output dictionary. This can be a fully initialized `CodesHandle` /// owning the encoded GRIB message. /// /// ----------------------------------------------------------------------------- /// Normalization and lifetime semantics (CRITICAL) /// ----------------------------------------------------------------------------- /// /// Metadata normalization is **conditionally enabled** based on runtime /// options. /// /// The normalization step does **not** return new objects. Instead, it /// returns **references** to the *active* metadata dictionaries: /// /// - If normalization is **disabled**: /// - references alias the input objects (`inputMars`, `inputMisc`) /// /// - If normalization is **enabled**: /// - references alias local scratch objects (`scratchMars`, `scratchMisc`) /// - the scratch objects contain normalized copies of the inputs /// /// The returned references must be treated as **borrowed**: /// - they must not be stored, /// - they must not escape this function, /// - their lifetime is strictly limited to this scope. /// /// This contract allows the pipeline to avoid unnecessary allocations when /// normalization is disabled, while preserving correctness when it is enabled. /// /// ----------------------------------------------------------------------------- /// @tparam Val_t /// Numeric type of the values to be encoded. /// /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameterization dictionary type /// @tparam OptDict_t Options dictionary type /// @tparam OutDict_t Output dictionary type /// /// @param values /// Contiguous span of values to encode. /// /// @param inputMars /// Input MARS description of the data (read-only). /// /// @param inputMisc /// Input miscellaneous description of the data (read-only). /// /// @param options /// Encoding options controlling behavior such as validation, /// logging, or feature toggles. /// /// @param language /// MARS language definition /// /// @return /// A `std::unique_ptr` with GRIB keys set /// /// @throws mars2grib::Exception /// If normalization, header encoding, or value encoding fails. /// template static std::unique_ptr encode(const metkit::codes::Span& values, const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& options, const eckit::Value& language) { // 1. Prepare Scratches for Normalization MarsDict_t scratchMars; ParDict_t scratchMisc; try { // 2. Normalize Metadata (conditionally) // ----------------------------------------------------------------- // IMPORTANT: Normalization returns *references*, not values. // // Depending on runtime options: // - normalization DISABLED -> activeMars / activeMisc alias inputs // - normalization ENABLED -> activeMars / activeMisc alias scratch // // The returned references are BORROWED and MUST NOT escape this scope. // Their lifetime is bounded by `scratchMars` / `scratchMisc`. // ----------------------------------------------------------------- auto [activeMars, activeMisc] = normalize_if_enabled(inputMars, inputMisc, options, language, scratchMars, scratchMisc); // 3. Encode Header (SpecializedEncoder creates the CodesHandle here) auto gribHeader = encodeHeader(activeMars, activeMisc, options); // 4. Inject Values return encodeValues(values, activeMars, activeMisc, options, std::move(gribHeader)); } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"encode\","; json += "\"inputMars\":"; json += dict_to_json(inputMars); json += ","; json += "\"inputMisc\":"; json += dict_to_json(inputMisc); json += ","; json += "\"options\":"; json += dict_to_json(options); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during encoding", contextJson, Here())); } } // ------------------------------------------------------------------------- // Temporary staged-cache preparation path // ------------------------------------------------------------------------- // // The following types and functions implement a transitional staged // preparation/finalisation workflow. // // This code exists as a preparatory step toward a future version where // cache construction, reuse, and lifecycle are fully internalized in the // core encoding layer. // /// /// @brief Immutable staged-encoding cache entry. /// /// This object stores the reusable state required to split the encoding /// pipeline into two phases: /// /// 1. **Preparation** /// - metadata normalization /// - header layout resolution /// - encoder specialization /// - creation of a reusable prepared sample /// /// 2. **Finalisation** /// - reuse of the prepared structural state /// - injection of dynamic metadata if needed /// - value encoding into a fresh output object /// /// The cache entry is intentionally immutable: /// - copy is disabled /// - move is disabled /// - the prepared sample is owned through an immutable smart pointer /// /// This guarantees that once constructed, the cache entry represents a /// stable reusable encoding context. /// /// @note /// This staged cache path is a preparatory step toward a future /// implementation where cache management is fully internalized in the /// core encoding layer. /// /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter/misc dictionary type /// @tparam OptDict_t Encoding options dictionary type /// @tparam OutDict_t Output GRIB handle/dictionary type /// template struct CacheEntry { using Encoder = metkit::mars2grib::frontend::header::SpecializedEncoder; using Layout = metkit::mars2grib::frontend::GribHeaderLayoutData; /// /// @brief Construct a cache entry from resolved layout and active metadata. /// /// This constructor: /// - internalizes the resolved header layout into a specialized encoder /// - prepares an immutable reusable sample from the active metadata /// /// The input metadata is expected to already represent the active /// dictionaries selected after any optional normalization step. /// /// @param[in] layout /// Resolved GRIB header layout to internalize. /// /// @param[in] inputMars /// Active MARS metadata dictionary. /// /// @param[in] inputMisc /// Active parameter/misc metadata dictionary. /// /// @param[in] options /// Encoding options controlling specialization behavior. /// /// @note /// This constructor is part of the temporary staged-cache path and /// prepares the structure required for a future internal cache design. /// CacheEntry(Layout&& layout, const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& options) : encoder_{std::move(layout)}, preparedSample_{encoder_.prepare(inputMars, inputMisc, options)} {}; CacheEntry(const CacheEntry&) = delete; CacheEntry& operator=(const CacheEntry&) = delete; CacheEntry(CacheEntry&&) = delete; CacheEntry& operator=(CacheEntry&&) = delete; ~CacheEntry() = default; /// /// @brief Fully specialized immutable encoder. /// /// This encoder owns the resolved layout and the precomputed execution /// plan used during staged finalisation. /// const Encoder encoder_; /// /// @brief Immutable prepared sample used as finalisation template. /// /// This object represents the reusable structural sample produced /// during the preparation phase. It is treated as read-only input /// during `finaliseEncoding()`, which derives a fresh output handle /// from it. /// const std::unique_ptr preparedSample_; }; /// /// @brief Prepare a reusable staged-encoding cache. /// /// This function performs the preparation phase of the staged encoding /// pipeline: /// - optional metadata normalization /// - header layout resolution /// - construction of a specialized encoder /// - creation of an immutable reusable sample /// /// The returned cache entry can later be passed to /// `finaliseEncoding()` to complete the encoding with concrete field /// values. /// /// ----------------------------------------------------------------------------- /// Normalization and lifetime semantics (CRITICAL) /// ----------------------------------------------------------------------------- /// /// As in `encode()`, normalization returns borrowed references to the /// active metadata dictionaries: /// - either the original inputs /// - or local scratch objects containing normalized copies /// /// These references must not escape this function except through /// immediate materialization into owned cache state. /// /// The resulting `CacheEntry` stores only owned immutable state and /// does not retain references to the scratch buffers. /// /// ----------------------------------------------------------------------------- /// /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter/misc dictionary type /// @tparam OptDict_t Encoding options dictionary type /// @tparam OutDict_t Output GRIB handle/dictionary type /// /// @param[in] inputMars /// Input MARS description of the data. /// /// @param[in] inputMisc /// Input miscellaneous description of the data. /// /// @param[in] options /// Encoding options controlling specialization and normalization. /// /// @param[in] language /// MARS language definition. /// /// @return /// A unique pointer owning an immutable staged cache entry. /// /// @throws mars2grib::Exception /// If normalization, layout resolution, or sample preparation fails. /// /// @note /// This staged cache path is a preparatory step toward a future /// implementation where cache lifecycle and reuse are fully /// internalized inside the core encoding layer. /// template static std::unique_ptr> prepare( const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& options, const eckit::Value& language) { // 1. Prepare Scratches for Normalization MarsDict_t scratchMars; ParDict_t scratchMisc; try { using metkit::mars2grib::frontend::make_HeaderLayout_or_throw; auto [activeMars, activeMisc] = normalize_if_enabled(inputMars, inputMisc, options, language, scratchMars, scratchMisc); auto layout = make_HeaderLayout_or_throw(activeMars, options); return std::make_unique>( std::move(layout), activeMars, activeMisc, options); } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"prepare\","; json += "\"inputMars\":"; json += dict_to_json(inputMars); json += ","; json += "\"inputMisc\":"; json += dict_to_json(inputMisc); json += ","; json += "\"options\":"; json += dict_to_json(options); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during cache preparation", contextJson, Here())); } } /// /// @brief Complete an encoding from a previously prepared cache entry. /// /// This function performs the finalisation phase of the staged encoding /// pipeline: /// - optional metadata normalization /// - reuse of the immutable specialized encoder /// - reuse of the immutable prepared sample /// - creation of a fresh header object /// - injection of the field values into the resulting output handle /// /// The provided cache entry is treated as read-only and is not consumed. /// It may therefore be reused across multiple finalisation calls. /// /// ----------------------------------------------------------------------------- /// Normalization and lifetime semantics (CRITICAL) /// ----------------------------------------------------------------------------- /// /// As in `encode()` and `prepare()`, normalization yields borrowed /// references to the active metadata dictionaries. These references are /// used only within this function and are not stored. /// /// The cache entry itself is fully owned and immutable, and no references /// to local scratch objects escape the function. /// /// ----------------------------------------------------------------------------- /// /// @tparam Val_t Numeric type of the values to be encoded /// @tparam MarsDict_t MARS dictionary type /// @tparam ParDict_t Parameter/misc dictionary type /// @tparam OptDict_t Encoding options dictionary type /// @tparam OutDict_t Output GRIB handle/dictionary type /// /// @param[in] cacheEntry /// Immutable staged cache entry previously produced by `prepare()`. /// /// @param[in] values /// Contiguous span of values to encode. /// /// @param[in] inputMars /// Input MARS description of the data. /// /// @param[in] inputMisc /// Input miscellaneous description of the data. /// /// @param[in] options /// Encoding options controlling behavior such as validation, /// logging, or feature toggles. /// /// @param[in] language /// MARS language definition. /// /// @return /// A `std::unique_ptr` owning the fully encoded output object. /// /// @throws mars2grib::Exception /// If normalization, staged header finalisation, or value encoding fails. /// /// @note /// This staged cache path is a preparatory step toward a future /// implementation where cache lifecycle and reuse are fully /// internalized inside the core encoding layer. /// template static std::unique_ptr finaliseEncoding( const CacheEntry& cacheEntry, const metkit::codes::Span& values, const MarsDict_t& inputMars, const ParDict_t& inputMisc, const OptDict_t& options, const eckit::Value& language) { // 1. Prepare Scratches for Normalization MarsDict_t scratchMars; ParDict_t scratchMisc; try { auto [activeMars, activeMisc] = normalize_if_enabled(inputMars, inputMisc, options, language, scratchMars, scratchMisc); auto gribHeader = cacheEntry.encoder_.finaliseEncoding(*(cacheEntry.preparedSample_), activeMars, activeMisc, options); return encodeValues(values, activeMars, activeMisc, options, std::move(gribHeader)); } catch (...) { const auto contextJson = [&]() { try { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"operation\":\"finaliseEncoding\","; json += "\"inputMars\":"; json += dict_to_json(inputMars); json += ","; json += "\"inputMisc\":"; json += dict_to_json(inputMisc); json += ","; json += "\"options\":"; json += dict_to_json(options); json += "}"; return json; } catch (...) { return std::string{"Failed to generate the context"}; } }(); std::throw_with_nested(mars2grib::utils::exceptions::Mars2GribCoreOperationsException( "Error during encoding finalisation", contextJson, Here())); } } /// /// @brief Capture a structural test point for regression analysis. /// /// Serializes the current resolution state (GRIB Blueprint) /// into a JSON format suitable for external validation tools. /// /// @tparam MarsDict_t MARS dictionary type /// @tparam OptDict_t Encoding options dictionary type /// // template // static std::string dumpHeaderTest(const MarsDict_t& mars, const OptDict_t& opt) { // try { // using metkit::mars2grib::frontend::make_HeaderLayout_or_throw; // using metkit::mars2grib::frontend::debug::debug_convert_GribHeaderLayoutData_to_json; // // auto layout = make_HeaderLayout_or_throw(mars, opt); // // return debug_convert_GribHeaderLayoutData_to_json(layout); // } // catch (...) { // const auto contextJson = [&]() { // try { // using metkit::mars2grib::utils::dict_traits::dict_to_json; // // std::string json; // json += "{"; // json += "\"operation\":\"dumpHeaderTest\","; // json += "\"mars\":"; // json += dict_to_json(mars); // json += ","; // json += "\"options\":"; // json += dict_to_json(opt); // json += "}"; // return json; // } // catch (...) { // return std::string{"Failed to generate the context"}; // } // }(); // // std::throw_with_nested( // mars2grib::utils::exceptions::Mars2GribCoreOperationsException("Error during header test dump", // contextJson, Here())); // } // } }; } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/mars2grib/testing-utils/0000775000175000017500000000000015246725757022070 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/testing-utils/RecordingDictionary.h0000664000175000017500000005241515246725757026212 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace metkit::mars2grib::testing_utils { namespace detail { inline std::string escape_json(std::string_view input) { std::string out; out.reserve(input.size() + 8); for (char c : input) { switch (c) { case '\\': out += "\\\\"; break; case '"': out += "\\\""; break; case '\b': out += "\\b"; break; case '\f': out += "\\f"; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: if (static_cast(c) < 0x20) { std::ostringstream oss; oss << "\\u"; oss.width(4); oss.fill('0'); oss << std::hex << static_cast(static_cast(c)); out += oss.str(); } else { out += c; } break; } } return out; } inline std::string quote_json(std::string_view input) { std::string out; out.reserve(input.size() + 2); out += '"'; out += escape_json(input); out += '"'; return out; } template inline std::string numeric_to_json(const T& value) { std::ostringstream oss; oss.precision(17); oss << value; return oss.str(); } template inline std::string vector_to_json(const std::vector& values); template struct RecordedTypeTraits; template <> struct RecordedTypeTraits { static constexpr std::string_view category = "bool"; static std::string datatype_json(bool) { return R"({"type":"boolean","rank":0,"size":1})"; } static std::string value_json(bool value) { return value ? "true" : "false"; } }; template <> struct RecordedTypeTraits { static constexpr std::string_view category = "integer"; static std::string datatype_json(long long) { return R"({"type":"integer","rank":0,"size":1})"; } static std::string value_json(long long value) { return numeric_to_json(value); } }; template <> struct RecordedTypeTraits { static constexpr std::string_view category = "double"; static std::string datatype_json(double) { return R"({"type":"double","rank":0,"size":1})"; } static std::string value_json(double value) { return numeric_to_json(value); } }; template <> struct RecordedTypeTraits { static constexpr std::string_view category = "string"; static std::string datatype_json(const std::string&) { return R"({"type":"string","rank":0,"size":1})"; } static std::string value_json(const std::string& value) { return quote_json(value); } }; template <> struct RecordedTypeTraits> { static constexpr std::string_view category = "vector"; static std::string datatype_json(const std::vector& value) { return std::string{"{\"type\":\"integer\",\"rank\":1,\"size\":"} + numeric_to_json(value.size()) + '}'; } static std::string value_json(const std::vector& value) { return vector_to_json(value); } }; template <> struct RecordedTypeTraits> { static constexpr std::string_view category = "vector"; static std::string datatype_json(const std::vector& value) { return std::string{"{\"type\":\"double\",\"rank\":1,\"size\":"} + numeric_to_json(value.size()) + '}'; } static std::string value_json(const std::vector& value) { return vector_to_json(value); } }; template <> struct RecordedTypeTraits> { static constexpr std::string_view category = "vector"; static std::string datatype_json(const std::vector& value) { return std::string{"{\"type\":\"string\",\"rank\":1,\"size\":"} + numeric_to_json(value.size()) + '}'; } static std::string value_json(const std::vector& value) { return vector_to_json(value); } }; template <> struct RecordedTypeTraits> { static constexpr std::string_view category = "vector"; static std::string datatype_json(const std::vector& value) { return std::string{"{\"type\":\"byte\",\"rank\":1,\"size\":"} + numeric_to_json(value.size()) + '}'; } static std::string value_json(const std::vector& value) { std::vector normalized; normalized.reserve(value.size()); for (uint8_t item : value) { normalized.push_back(static_cast(item)); } return vector_to_json(normalized); } }; template inline std::string element_to_json(const T& value) { if constexpr (std::is_same_v) { return quote_json(value); } return numeric_to_json(value); } template <> inline std::string element_to_json(const bool& value) { return value ? "true" : "false"; } template inline std::string vector_to_json(const std::vector& values) { std::string out; out += '['; for (std::size_t i = 0; i < values.size(); ++i) { if (i != 0) { out += ','; } out += element_to_json(values[i]); } out += ']'; return out; } } // namespace detail class RecordingDictionary { public: struct ValueCompareResult { bool equal{true}; std::string path; std::string reason; std::string lhs_json; std::string rhs_json; }; struct CompareResult { bool equal{true}; std::size_t first_mismatch_index{std::numeric_limits::max()}; std::string path; std::string reason; std::string lhs_json; std::string rhs_json; explicit operator bool() const { return equal; } }; class RecordedValue { public: virtual ~RecordedValue() = default; virtual std::unique_ptr clone() const = 0; virtual std::string category() const = 0; virtual std::string datatype_json() const = 0; virtual std::string value_json() const = 0; virtual ValueCompareResult compare(const RecordedValue& other) const = 0; }; template class TypedRecordedValue final : public RecordedValue { public: explicit TypedRecordedValue(T value) : value_{std::move(value)} {} std::unique_ptr clone() const override { return std::make_unique>(value_); } std::string category() const override { return std::string{detail::RecordedTypeTraits::category}; } std::string datatype_json() const override { return detail::RecordedTypeTraits::datatype_json(value_); } std::string value_json() const override { return detail::RecordedTypeTraits::value_json(value_); } ValueCompareResult compare(const RecordedValue& other) const override { const auto* typedOther = dynamic_cast*>(&other); if (typedOther == nullptr) { return {false, "datatype", "Recorded value categories differ", datatype_json(), other.datatype_json()}; } if (!(value_ == typedOther->value_)) { return {false, "value", "Recorded values differ", value_json(), typedOther->value_json()}; } return {}; } const T& value() const { return value_; } private: T value_; }; struct SampleMetadata { std::string sample; bool operator==(const SampleMetadata& other) const { return sample == other.sample; } }; struct CloneMetadata { std::size_t source_operation_count{0}; bool operator==(const CloneMetadata& other) const { return source_operation_count == other.source_operation_count; } }; struct MissingValueTag { bool operator==(const MissingValueTag&) const { return true; } }; struct ValuesSummary { std::size_t size{0}; double average{0.0}; bool operator==(const ValuesSummary& other) const { return size == other.size && average == other.average; } }; public: enum class OperationKind { MakeFromSample, Clone, Set, SetMissing, }; struct Operation { OperationKind kind{OperationKind::Set}; std::string key; std::unique_ptr value; Operation() = default; Operation(OperationKind opKind, std::string opKey, std::unique_ptr opValue) : kind{opKind}, key{std::move(opKey)}, value{std::move(opValue)} {} Operation(const Operation& other) : kind{other.kind}, key{other.key} { if (other.value) { value = other.value->clone(); } } Operation& operator=(const Operation& other) { if (this == &other) { return *this; } kind = other.kind; key = other.key; value.reset(); if (other.value) { value = other.value->clone(); } return *this; } Operation(Operation&&) noexcept = default; Operation& operator=(Operation&&) noexcept = default; }; RecordingDictionary() = default; RecordingDictionary(const RecordingDictionary&) = default; RecordingDictionary& operator=(const RecordingDictionary&) = default; RecordingDictionary(RecordingDictionary&&) noexcept = default; RecordingDictionary& operator=(RecordingDictionary&&) noexcept = default; ~RecordingDictionary() = default; void record_make_from_sample(std::string_view sample) { operations_.emplace_back( OperationKind::MakeFromSample, std::string{}, std::make_unique>(SampleMetadata{std::string(sample)})); } void record_clone(std::size_t sourceOperationCount) { operations_.emplace_back( OperationKind::Clone, std::string{}, std::make_unique>(CloneMetadata{sourceOperationCount})); } void record_set_missing(std::string_view key) { operations_.emplace_back(OperationKind::SetMissing, std::string(key), std::make_unique>(MissingValueTag{})); } void record_set(std::string_view key, bool value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(value)); } void record_set(std::string_view key, int value) { record_set(key, static_cast(value)); } void record_set(std::string_view key, long value) { record_set(key, static_cast(value)); } void record_set(std::string_view key, long long value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(value)); } void record_set(std::string_view key, double value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(value)); } void record_set(std::string_view key, const std::string& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(value)); } void record_set(std::string_view key, std::string&& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(std::move(value))); } void record_set(std::string_view key, const char* value) { record_set(key, std::string(value)); } void record_set(std::string_view key, const std::vector& value) { std::vector normalized; normalized.reserve(value.size()); for (long item : value) { normalized.push_back(static_cast(item)); } operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(std::move(normalized))); } void record_set(std::string_view key, std::vector&& value) { record_set(key, static_cast&>(value)); } void record_set(std::string_view key, const std::vector& value) { if (key == "values") { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(summarize_values(value))); return; } operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(value)); } void record_set(std::string_view key, std::vector&& value) { if (key == "values") { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>(summarize_values(value))); return; } operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(std::move(value))); } void record_set(std::string_view key, const std::vector& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(value)); } void record_set(std::string_view key, std::vector&& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(std::move(value))); } void record_set(std::string_view key, const std::vector& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(value)); } void record_set(std::string_view key, std::vector&& value) { operations_.emplace_back(OperationKind::Set, std::string(key), std::make_unique>>(std::move(value))); } std::size_t operation_count() const { return operations_.size(); } const std::vector& operations() const { return operations_; } std::string operation_to_json(const Operation& operation) const { switch (operation.kind) { case OperationKind::MakeFromSample: { return std::string{"{\"make_from_sample\":{\"sample\":"} + operation.value->value_json() + "}}"; } case OperationKind::Clone: { return std::string{"{\"clone\":{\"source_operation_count\":"} + operation.value->value_json() + "}}"; } case OperationKind::Set: { return std::string{"{\"set\":{\"key\":"} + detail::quote_json(operation.key) + ",\"datatype\":" + operation.value->datatype_json() + ",\"value\":" + operation.value->value_json() + "}}"; } case OperationKind::SetMissing: { return std::string{"{\"set_missing\":{\"key\":"} + detail::quote_json(operation.key) + "}}"; } } return "{}"; } std::string to_json() const { std::string out; out += "{\"operations\":["; for (std::size_t i = 0; i < operations_.size(); ++i) { if (i != 0) { out += ','; } out += operation_to_json(operations_[i]); } out += "]}"; return out; } CompareResult compare(const RecordingDictionary& other) const { if (operations_.size() != other.operations_.size()) { return {false, std::min(operations_.size(), other.operations_.size()), "operations.size", "Recorded operation counts differ", detail::numeric_to_json(operations_.size()), detail::numeric_to_json(other.operations_.size())}; } for (std::size_t i = 0; i < operations_.size(); ++i) { const auto& lhs = operations_[i]; const auto& rhs = other.operations_[i]; if (lhs.kind != rhs.kind) { return {false, i, "operations[" + detail::numeric_to_json(i) + "].kind", "Recorded operation kinds differ", operation_to_json(lhs), operation_to_json(rhs)}; } if (lhs.key != rhs.key) { return {false, i, "operations[" + detail::numeric_to_json(i) + "].key", "Recorded operation keys differ", operation_to_json(lhs), operation_to_json(rhs)}; } if (static_cast(lhs.value) != static_cast(rhs.value)) { return {false, i, "operations[" + detail::numeric_to_json(i) + "].value", "Recorded operation value presence differs", operation_to_json(lhs), operation_to_json(rhs)}; } if (lhs.value) { ValueCompareResult valueCmp = lhs.value->compare(*rhs.value); if (!valueCmp.equal) { return {false, i, "operations[" + detail::numeric_to_json(i) + "]." + valueCmp.path, valueCmp.reason, operation_to_json(lhs), operation_to_json(rhs)}; } } } return {}; } bool operator==(const RecordingDictionary& other) const { return compare(other).equal; } bool operator!=(const RecordingDictionary& other) const { return !(*this == other); } private: static ValuesSummary summarize_values(const std::vector& value) { double sum = 0.0; for (double item : value) { if (std::isnan(item)) { throw std::invalid_argument("RecordingDictionary does not accept NaN in `values`"); } sum += item; } const double average = value.empty() ? 0.0 : (sum / static_cast(value.size())); return ValuesSummary{value.size(), average}; } std::vector operations_; }; template <> struct detail::RecordedTypeTraits { static constexpr std::string_view category = "sample-metadata"; static std::string datatype_json(const RecordingDictionary::SampleMetadata&) { return R"({"type":"sample","rank":0,"size":1})"; } static std::string value_json(const RecordingDictionary::SampleMetadata& value) { return detail::quote_json(value.sample); } }; template <> struct detail::RecordedTypeTraits { static constexpr std::string_view category = "clone-metadata"; static std::string datatype_json(const RecordingDictionary::CloneMetadata&) { return R"({"type":"clone","rank":0,"size":1})"; } static std::string value_json(const RecordingDictionary::CloneMetadata& value) { return detail::numeric_to_json(value.source_operation_count); } }; template <> struct detail::RecordedTypeTraits { static constexpr std::string_view category = "missing"; static std::string datatype_json(const RecordingDictionary::MissingValueTag&) { return R"({"type":"missing","rank":0,"size":0})"; } static std::string value_json(const RecordingDictionary::MissingValueTag&) { return "null"; } }; template <> struct detail::RecordedTypeTraits { static constexpr std::string_view category = "values-summary"; static std::string datatype_json(const RecordingDictionary::ValuesSummary& value) { return std::string{"{\"type\":\"double\",\"rank\":1,\"size\":"} + detail::numeric_to_json(value.size) + '}'; } static std::string value_json(const RecordingDictionary::ValuesSummary& value) { return std::string{"{\"average\":"} + detail::numeric_to_json(value.average) + '}'; } }; } // namespace metkit::mars2grib::testing_utils metkit-1.20.2/src/metkit/mars2grib/testing-utils/dictionary_traits/0000775000175000017500000000000015246725757025623 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/testing-utils/dictionary_traits/dictaccess_recording_dictionary.h0000664000175000017500000001426215246725757034367 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include "metkit/codes/api/CodesTypes.h" #include "metkit/mars2grib/testing-utils/RecordingDictionary.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/type_traits_name.h" namespace metkit::mars2grib::utils { template <> constexpr std::string_view type_name() { return "metkit::mars2grib::testing_utils::RecordingDictionary"; } } // namespace metkit::mars2grib::utils namespace metkit::mars2grib::utils::dict_traits { template <> struct DictToJsonTraits { static std::string to_json(const metkit::mars2grib::testing_utils::RecordingDictionary& dict) { return dict.to_json(); } static void dump_or_ignore(const metkit::mars2grib::testing_utils::RecordingDictionary& dict, const std::string& fname) { try { std::ofstream out(fname, std::ios::out | std::ios::trunc); if (!out) { return; } out << dict.to_json(); out.flush(); } catch (...) { return; } } }; template <> struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw( std::string_view name) { auto dict = std::make_unique(); dict->record_make_from_sample(name); return dict; } static std::unique_ptr clone_or_throw( const metkit::mars2grib::testing_utils::RecordingDictionary& dict) { auto clone = std::make_unique(dict); clone->record_clone(dict.operation_count()); return clone; } }; template <> struct DictMissing { static bool isMissing(const metkit::mars2grib::testing_utils::RecordingDictionary&, std::string_view) noexcept(false) { throw std::logic_error("RecordingDictionary does not support read-side missing checks"); } static void setMissing(metkit::mars2grib::testing_utils::RecordingDictionary& dict, std::string_view key) noexcept(false) { dict.record_set_missing(key); } }; #define M2G_DEFINE_RECORDING_DICT_SET_TRAITS(CTYPE) \ template <> \ struct DictSetOrThrow { \ static void set_or_throw(metkit::mars2grib::testing_utils::RecordingDictionary& dict, std::string_view key, \ const CTYPE& value) noexcept(false) { \ dict.record_set(key, value); \ } \ }; \ \ template <> \ struct DictSetOrIgnore { \ static void set_or_ignore(metkit::mars2grib::testing_utils::RecordingDictionary& dict, std::string_view key, \ const CTYPE& value) noexcept(false) { \ dict.record_set(key, value); \ } \ }; M2G_DEFINE_RECORDING_DICT_SET_TRAITS(bool) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(int) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(long) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(double) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(std::string) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(std::vector) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(std::vector) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(std::vector) M2G_DEFINE_RECORDING_DICT_SET_TRAITS(std::vector) template <> struct DictSetOrThrow> { static void set_or_throw(metkit::mars2grib::testing_utils::RecordingDictionary& dict, std::string_view key, const metkit::codes::Span& value) noexcept(false) { auto span = value; std::vector owned; owned.reserve(span.size()); const double* raw = span.data(); for (std::size_t i = 0; i < span.size(); ++i) { owned.push_back(raw[i]); } dict.record_set(key, std::move(owned)); } }; template <> struct DictSetOrIgnore> { static void set_or_ignore(metkit::mars2grib::testing_utils::RecordingDictionary& dict, std::string_view key, const metkit::codes::Span& value) noexcept(false) { auto span = value; std::vector owned; owned.reserve(span.size()); const double* raw = span.data(); for (std::size_t i = 0; i < span.size(); ++i) { owned.push_back(raw[i]); } dict.record_set(key, std::move(owned)); } }; #undef M2G_DEFINE_RECORDING_DICT_SET_TRAITS } // namespace metkit::mars2grib::utils::dict_traits metkit-1.20.2/src/metkit/mars2grib/utils/0000775000175000017500000000000015246725757020415 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/utils/configConverter.h0000664000175000017500000002456615246725757023740 0ustar alastairalastair#include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/backend/concepts/concept_registry.h" #include "metkit/mars2grib/backend/sections/section_registry.h" #include "metkit/mars2grib/backend/sections/sections_recipes.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::utils::cfg { // Namespaces shortcuts using metkit::mars2grib::backend::cnpts::NUM_SECTIONS; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; // Namespaces shortcuts inline constexpr std::array sectionNames = { {"indicator-section", "identification-section", "local-use-section", "grid-definition-section", "product-definition-section", "data-representation-section"}}; inline std::string strip_descriptor(std::string_view name) { constexpr std::string_view suffix = "-configurator"; if (name.size() >= suffix.size() && name.substr(name.size() - suffix.size()) == suffix) { return std::string{name.substr(0, name.size() - suffix.size())}; } return std::string{name}; } struct Section { uint16_t templateNumber_; std::map concepts_; }; struct EncoderCfg { std::array sec_; }; Section lookupCfgSection(const eckit::LocalConfiguration& cfg, long SecId) { // Get section name from Id std::string sectionName = std::string(sectionNames[SecId]); // Verify the section is present if (!cfg.has(sectionName)) { throw Mars2GribGenericException(sectionName + " is missing", Here()); } // Get section configuration eckit::LocalConfiguration sectionCfg = cfg.getSubConfiguration(sectionName); // Initialize the Section Section sec; // Verify template number is present if (!sectionCfg.has("template-number")) { throw Mars2GribGenericException(sectionName + " has no template number", Here()); } // Get template number sec.templateNumber_ = static_cast(sectionCfg.getLong("template-number")); // Populate concepts for (auto& name : sectionCfg.keys()) { // Skip template number if (name == "template-number") { continue; // Skip template number }; // Get the key in the concept map std::string key = strip_descriptor(name); // Get the type eckit::LocalConfiguration conceptCfg = sectionCfg.getSubConfiguration(name); if (!conceptCfg.has("type")) { throw Mars2GribGenericException("No type found for concept " + name, Here()); } std::string type = conceptCfg.getString("type"); // ========================================================================================= // Modify type if (key == "model") { sec.concepts_.insert(std::make_pair("generatingProcess", std::string(type))); } else if (key == "data-type") { sec.concepts_.insert(std::make_pair("dataType", std::string(type))); } else if (key == "reference-time") { if (cfg.has("product-definition-section")) { auto tmp = cfg.getSubConfiguration("product-definition-section"); if (tmp.has("template-number")) { long tmplNum = tmp.getLong("template-number"); if (tmplNum == 60 || tmplNum == 61) { type = "reforecast"; } else { type = "standard"; } } else { throw Mars2GribGenericException("No product definition template number in configuration", Here()); } } else { throw Mars2GribGenericException("No product definition template number in configuration", Here()); } sec.concepts_.insert(std::make_pair("referenceTime", std::string(type))); } else if (key == "direction-frequency") { sec.concepts_.insert(std::make_pair("wave", "spectra")); } else if (key == "period") { sec.concepts_.insert(std::make_pair("wave", "period")); } else if (key == "ensemble") { sec.concepts_.insert(std::make_pair("ensemble", "individual")); } else if (key == "point-in-time") { sec.concepts_.insert(std::make_pair("pointInTime", std::string(type))); } else if (key == "chemistry") { sec.concepts_.insert(std::make_pair("composition", std::string(type))); } else if (key == "param") { sec.concepts_.insert(std::make_pair("param", "default")); } else if (key == "time-statistics") { std::string typeOfStatisticalProcess = conceptCfg.getString("type-of-statistical-processing"); if (typeOfStatisticalProcess == "average") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "average")); } else if (typeOfStatisticalProcess == "accumul") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "accumulation")); } else if (typeOfStatisticalProcess == "max") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "maximum")); } else if (typeOfStatisticalProcess == "min") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "minimum")); } else if (typeOfStatisticalProcess == "stddev") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "standardDeviation")); } else if (typeOfStatisticalProcess == "mode") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "mode")); } else if (typeOfStatisticalProcess == "severity") { // Insert into the map sec.concepts_.insert(std::make_pair("statistics", "severity")); } else { throw Mars2GribGenericException( "Unsupported type-of-statistical-processing " + typeOfStatisticalProcess + " for concept " + name, Here()); } } else { sec.concepts_.insert(std::make_pair(key, type)); } } // for concept name return sec; }; // lookupCfgSection Section lookupExpectedSection(const eckit::LocalConfiguration& cfg, long SecId) { using metkit::mars2grib::backend::sections::ConceptList; using metkit::mars2grib::backend::sections::resolveSectionTemplateConcepts; // Get section name from Id std::string sectionName = std::string(sectionNames[SecId]); // Verify the section is present if (!cfg.has(sectionName)) { throw Mars2GribGenericException(sectionName + " is missing", Here()); } // Get section configuration eckit::LocalConfiguration sectionCfg = cfg.getSubConfiguration(sectionName); // Initialize the Section Section sec; // Verify template number is present if (!sectionCfg.has("template-number")) { throw Mars2GribGenericException(sectionName + " has no template number", Here()); } // Get template number sec.templateNumber_ = static_cast(sectionCfg.getLong("template-number")); // Populate concepts const std::optional concepts = resolveSectionTemplateConcepts(SecId, sec.templateNumber_); // Insert into the map if (concepts) { for (const auto& concept : *concepts) { std::string key = std::string(concept.name); std::string type = concept.type ? std::string(*concept.type) : "default"; sec.concepts_.insert(std::make_pair(key, type)); } } else { throw Mars2GribGenericException( "No concepts found for " + sectionName + " template number " + std::to_string(sec.templateNumber_), Here()); } return sec; }; EncoderCfg parseEncoderCfg(const eckit::LocalConfiguration& cfg) { // Initialize EncoderCfg EncoderCfg encoderCfg; EncoderCfg expectedCfg; EncoderCfg combinedCfg; // Populate all sections for (size_t i = 0; i < NUM_SECTIONS; ++i) { encoderCfg.sec_[i] = lookupCfgSection(cfg, i); expectedCfg.sec_[i] = lookupExpectedSection(cfg, i); } // Combine configurations for (size_t i = 0; i < NUM_SECTIONS; ++i) { // Combine template number if (encoderCfg.sec_[i].templateNumber_ != expectedCfg.sec_[i].templateNumber_) { throw Mars2GribGenericException("Template number mismatch for section ", Here()); } combinedCfg.sec_[i].templateNumber_ = encoderCfg.sec_[i].templateNumber_; // Combine concepts for (const auto& [key, type] : expectedCfg.sec_[i].concepts_) { auto it = encoderCfg.sec_[i].concepts_.find(key); if (it != encoderCfg.sec_[i].concepts_.end()) { // Verify type matches if (type != "default" && it->second != type) { throw Mars2GribGenericException("Concept type mismatch for concept ", Here()); } combinedCfg.sec_[i].concepts_.insert(std::make_pair(key, type)); } else { combinedCfg.sec_[i].concepts_.insert(std::make_pair(key, type)); } } auto it = combinedCfg.sec_[4].concepts_.find("referenceTime"); if (it != combinedCfg.sec_[4].concepts_.end()) { combinedCfg.sec_[1].concepts_["referenceTime"] = combinedCfg.sec_[4].concepts_["referenceTime"]; } } // Validate concepts against expected return combinedCfg; }; void print_encoder_cfg(const EncoderCfg& cfg) { for (uint32_t i = 0; i < NUM_SECTIONS; ++i) { std::cout << "Section " << i << " (template " << cfg.sec_[i].templateNumber_ << "):" << std::endl; for (const auto& [key, type] : cfg.sec_[i].concepts_) { std::cout << " Concept: " << key << ", Type: " << type << std::endl; } } }; } // namespace metkit::mars2grib::utils::cfg metkit-1.20.2/src/metkit/mars2grib/utils/mars2gribExceptions.h0000664000175000017500000005630515246725757024531 0ustar alastairalastair/* * (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 mars2gribExceptions.h /// @brief Unified exception hierarchy for the mars2grib framework. /// /// This header defines the complete exception model used across /// mars2grib, covering: /// /// - Generic infrastructure errors /// - Layer-specific failures (matcher, rules, validation, tables, deduction) /// - Concept execution failures (with contextual metadata) /// - Encoder failures (with serialized dictionary state) /// /// The hierarchy is designed with the following goals: /// /// - Strong contextual diagnostics /// - Support for nested exception propagation /// - Structured debug frame printing /// - Clear separation between backend and frontend layers /// /// All exceptions ultimately derive from `eckit::Exception`, /// ensuring compatibility with the broader ECMWF ecosystem. /// /// Nested exception support allows propagation chains to be /// printed in a structured stack-like format. /// /// @ingroup mars2grib_utils #pragma once // System includes #include #include #include #include #include // Project includes #include "eckit/exception/Exceptions.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::utils::exceptions { /// @brief Base exception for mars2grib. /// /// This is the root exception type for most mars2grib failures. /// It: /// /// - Inherits from `eckit::Exception` /// - Supports nested exceptions via `std::nested_exception` /// - Provides structured frame printing /// /// Derived exceptions typically extend this class with /// additional contextual metadata. /// /// The `printFrame()` method is designed to be used by /// extended stack printers. class Mars2GribGenericException : public eckit::Exception, public std::nested_exception { public: Mars2GribGenericException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} virtual ~Mars2GribGenericException() = default; virtual void printFrame(std::ostream& os, const std::string& pad) const { const auto& loc = location(); os << pad << "+ file: " << loc.file() << "\n" << pad << "+ function: " << loc.func() << "\n" << pad << "+ line: " << loc.line() << "\n" << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" << pad << "+ message: " << what() << "\n"; } virtual void printFrame(const std::string& pad) const { const auto& loc = location(); LOG_DEBUG_LIB(LibMetkit) << pad << "+ file: " << loc.file() << "\n" << pad << "+ function: " << loc.func() << "\n" << pad << "+ line: " << loc.line() << "\n" << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" << pad << "+ message: " << what() << "\n"; } }; /// @brief Exception raised during GRIB section layout resolution. /// /// Used when the structural resolution layer fails while building /// the layout for a specific GRIB section. /// /// This exception extends the generic exception with section-specific /// diagnostic context, including: /// /// - The GRIB section identifier being resolved /// - A serialized snapshot of the active concepts state /// /// The additional metadata is emitted via `printFrame()` to support /// structured debugging of section selection failures. class Mars2GribSectionResolutionException : public Mars2GribGenericException { public: Mars2GribSectionResolutionException(std::string reason, std::size_t sectionId, const std::string& activeConceptsJson, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), sectionId_(sectionId), activeConceptsJson_(activeConceptsJson) {} virtual ~Mars2GribSectionResolutionException() = default; void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); os << pad << "+ section: " << sectionId_ << "\n" << pad << "+ activeConcepts: " << activeConceptsJson_ << "\n"; } virtual void printFrame(const std::string& pad) const { Mars2GribGenericException::printFrame(pad); LOG_DEBUG_LIB(LibMetkit) << pad << "+ section: " << sectionId_ << "\n" << pad << "+ activeConcepts: " << activeConceptsJson_ << "\n"; } private: std::size_t sectionId_; std::string activeConceptsJson_; }; /// @brief Exception raised during top-level header layout construction. /// /// Used when the frontend resolution pipeline fails while assembling /// the complete GRIB header layout from the input MARS request. /// /// This exception extends the generic exception with a serialized /// representation of the originating MARS request for diagnostics. /// /// The captured request payload is emitted via `printFrame()` to aid /// debugging of end-to-end layout resolution failures. class Mars2GribHeaderLayoutException : public Mars2GribGenericException { public: Mars2GribHeaderLayoutException(std::string reason, const std::string& marsJson, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), marsJson_(marsJson) {} virtual ~Mars2GribHeaderLayoutException() = default; void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); os << pad << "+ marsJson: " << marsJson_ << "\n"; } virtual void printFrame(const std::string& pad) const { Mars2GribGenericException::printFrame(pad); LOG_DEBUG_LIB(LibMetkit) << pad << "+ marsJson: " << marsJson_ << "\n"; } private: std::string marsJson_; }; /// @brief Exception raised during matcher evaluation. /// /// This exception is used when resolving whether a concept /// should be activated based on MARS input. /// /// It may optionally carry: /// /// - `param` : parameter identifier /// - `levtype` : level type /// /// All metadata fields are optional and printed only if defined. /// /// This exception extends the generic exception with matcher-specific /// diagnostic context. class Mars2GribMatcherException : public Mars2GribGenericException { public: Mars2GribMatcherException(long param, const std::string& levtype, std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), param_{std::to_string(param)}, levtype_{levtype} {} Mars2GribMatcherException(const std::string& levtype, std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), param_{std::nullopt}, levtype_{levtype} {} Mars2GribMatcherException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), param_{std::nullopt}, levtype_{std::nullopt} {} Mars2GribMatcherException(long param, std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), param_{std::to_string(param)}, levtype_{std::nullopt} {} const std::string levtype() const { return levtype_.has_value() ? levtype_.value() : "undefined"; } const std::string param() const { return param_.has_value() ? param_.value() : "undefined"; } void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); if (param_) { os << pad << "+ param: " << param() << '\n'; } if (levtype_) { os << pad << "+ levtype: " << levtype() << '\n'; } } void printFrame(const std::string& pad) const override { Mars2GribGenericException::printFrame(pad); if (param_) { LOG_DEBUG_LIB(LibMetkit) << pad << "+ param: " << param() << std::endl; } if (levtype_) { LOG_DEBUG_LIB(LibMetkit) << pad << "+ levtype: " << levtype() << std::endl; } }; private: std::optional param_; std::optional levtype_; }; /// @brief Exception raised in the rules layer. /// /// Used when evaluating rule-based logic fails. /// /// This class derives directly from `eckit::Exception` and /// supports nested exceptions. class Mars2GribRulesException : public eckit::Exception, public std::nested_exception { public: Mars2GribRulesException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} }; /// @brief Exception raised in the dictionary access layer. /// /// Used when dictionary validation or access fails. /// Inherits structured printing from the generic exception. class Mars2GribDictException : public Mars2GribGenericException { public: Mars2GribDictException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc) {} }; /// @brief Exception raised in the validation layer. /// /// Intended for semantic validation errors. /// Currently does not add additional metadata beyond the base class. class Mars2GribValidationException : public Mars2GribGenericException { public: Mars2GribValidationException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc) {} }; /// @brief Exception raised in table resolution logic. /// /// Used when GRIB table lookup or interpretation fails. class Mars2GribTableException : public Mars2GribGenericException { public: Mars2GribTableException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc) {} }; /// @brief Exception raised in the deduction layer. /// /// Used when derived values cannot be computed or inferred /// from the provided dictionaries. class Mars2GribDeductionException : public Mars2GribGenericException { public: Mars2GribDeductionException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc) {} }; /// @brief Exception raised by CoreOperations orchestration steps. /// /// This exception marks failures caught and rethrown by the core orchestration /// layer, without adding extra diagnostic payload yet. class Mars2GribCoreOperationsException : public Mars2GribGenericException { public: Mars2GribCoreOperationsException(std::string reason, std::string contextJson, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), contextJson_(std::move(contextJson)) {} void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); os << pad << "+ coreContext: " << contextJson_ << "\n"; } void printFrame(const std::string& pad) const override { Mars2GribGenericException::printFrame(pad); LOG_DEBUG_LIB(LibMetkit) << pad << "+ coreContext: " << contextJson_ << "\n"; } private: std::string contextJson_; }; /// @brief Exception raised in the backend model layer. /// /// Used when model-local orchestration fails while assembling normalized model /// input or constructing a backend model object from already-resolved /// deduction outputs. class Mars2GribModelException : public Mars2GribGenericException { public: Mars2GribModelException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc) {} Mars2GribModelException(std::string reason, std::string normalizedInputJson, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), normalizedInputJson_(std::move(normalizedInputJson)) {} void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); if (normalizedInputJson_) { os << pad << "+ normalizedInputJson: " << *normalizedInputJson_ << "\n"; } } void printFrame(const std::string& pad) const override { Mars2GribGenericException::printFrame(pad); if (normalizedInputJson_) { LOG_DEBUG_LIB(LibMetkit) << pad << "+ normalizedInputJson: " << *normalizedInputJson_ << "\n"; } } private: std::optional normalizedInputJson_; }; /// @brief Exception raised during concept execution. /// /// This is the most context-rich exception in the hierarchy. /// It carries: /// /// - Concept name /// - Concept variant /// - Encoding stage /// - GRIB section /// /// This allows precise identification of: /// /// - Which concept failed /// - Under which stage /// - In which section /// /// The metadata is optional and printed only if present. /// /// This exception is typically constructed via helper macros: /// /// - `MARS2GRIB_CONCEPT_THROW` /// - `MARS2GRIB_CONCEPT_RETHROW` /// /// Those macros automatically inject compile-time metadata. class Mars2GribConceptException : public Mars2GribGenericException { public: Mars2GribConceptException(std::string name, std::string variant, std::string stage, std::string section, std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), conceptName_(std::move(name)), conceptVariant_(std::move(variant)), stage_(std::move(stage)), section_(std::move(section)) {} const std::optional& conceptName() const { return conceptName_; } const std::optional& conceptVariant() const { return conceptVariant_; } const std::optional& stage() const { return stage_; } const std::optional& section() const { return section_; } void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); auto print_opt = [&](const char* k, const std::optional& v) { if (v) { os << pad << "+ " << k << ": " << *v << "\n"; } }; print_opt("concept", conceptName_); print_opt("variant", conceptVariant_); print_opt("stage", stage_); print_opt("section", section_); } void printFrame(const std::string& pad) const override { Mars2GribGenericException::printFrame(pad); auto print_opt = [&](const char* k, const std::optional& v) { if (v) { LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << k << ": " << *v << "\n"; } }; print_opt("concept", conceptName_); print_opt("variant", conceptVariant_); print_opt("stage", stage_); print_opt("section", section_); } private: std::optional conceptName_; std::optional conceptVariant_; std::optional stage_; std::optional section_; }; /// @brief Exception raised in the encoder layer. /// /// This exception captures serialized diagnostic state, /// including JSON representations of: /// /// - MARS dictionary /// - Parameter dictionary /// - Options dictionary /// - Encoder configuration /// /// It is intended for high-level failure reporting where /// complete encoding context must be preserved. /// /// The diagnostic information is printed in structured form /// via `printFrame()`. class Mars2GribEncoderException : public Mars2GribGenericException { public: Mars2GribEncoderException(std::string reason, std::string marsDict_json, std::string parDict_json, std::string optDict_json, std::string encoderCfg_json, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2GribGenericException(reason, loc), marsDict_json_(std::move(marsDict_json)), parDict_json_(std::move(parDict_json)), optDict_json_(std::move(optDict_json)), encoderCfg_json_(std::move(encoderCfg_json)) {} const std::string& marsDict_json() const { return marsDict_json_; } const std::string& geoDict_json() const { return geoDict_json_; } const std::string& parDict_json() const { return parDict_json_; } const std::string& optDict_json() const { return optDict_json_; } const std::string& encoderCfg_json() const { return encoderCfg_json_; } void printFrame(std::ostream& os, const std::string& pad) const override { Mars2GribGenericException::printFrame(os, pad); os << pad << "+ marsDict: " << marsDict_json_ << "\n" << pad << "+ parDict: " << parDict_json_ << "\n" << pad << "+ optDict: " << optDict_json_ << "\n" << pad << "+ encoderCfg: " << encoderCfg_json_ << "\n"; } void printFrame(const std::string& pad) const override { Mars2GribGenericException::printFrame(pad); LOG_DEBUG_LIB(LibMetkit) << pad << "+ marsDict: " << marsDict_json_ << "\n" << pad << "+ parDict: " << parDict_json_ << "\n" << pad << "+ optDict: " << optDict_json_ << "\n" << pad << "+ encoderCfg: " << encoderCfg_json_ << "\n"; } private: const std::string marsDict_json_; const std::string geoDict_json_; const std::string parDict_json_; const std::string optDict_json_; const std::string encoderCfg_json_; }; /// @brief Recursively print nested exception stack. /// /// Prints: /// /// - Exception type /// - Exception message /// - Nested exceptions (if any) /// /// The structure is indented according to nesting level. /// /// This function does not use the structured frame printer. /// For detailed frames, use `printExtendedStack`. /// /// @param e Root exception /// @param os Output stream /// @param level Initial indentation level inline void printExceptionStack(const std::exception& e, std::ostream& os, std::size_t level = 0) { const std::string indent(level * 2, ' '); // stampa tipo + messaggio os << indent << "- [" << typeid(e).name() << "] " << e.what() << '\n'; const auto* nested = dynamic_cast(&e); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } // verifica eccezione annidata try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { printExceptionStack(nested, os, level + 1); } catch (...) { os << indent << " - [unknown non-std exception]\n"; } }; inline constexpr int tabSize = 4; inline constexpr int lineSize = 120; inline std::string indent(std::size_t level) { return std::string(level * tabSize, ' '); } /// @brief Print structured exception stack with detailed frames. /// /// For each nested exception frame: /// /// - Prints file, function, line, and message (if available) /// - Prints additional metadata for specialized exceptions /// /// This function detects `Mars2GribGenericException` /// via `dynamic_cast` and calls `printFrame()` /// to extract structured information. /// /// Nested exceptions are recursively printed. /// /// @param e Root exception /// @param level Indentation level /// @param frame Frame counter inline void printExtendedStack(const std::exception& e, std::size_t level = 0, std::size_t frame = 1) { const std::string pad = indent(level); LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << std::string(lineSize, '=') << std::endl << pad << "+ frame " << frame << std::endl << pad << "+ " << std::string(lineSize, '-') << std::endl; if (const auto* me = dynamic_cast(&e)) { me->printFrame(pad); } else { LOG_DEBUG_LIB(LibMetkit) << pad << "+ message: " << e.what() << std::endl; } LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << std::string(lineSize, '+') << std::endl; const auto* nested = dynamic_cast(&e); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { printExtendedStack(nested, level + 1, frame + 1); } } /// @brief Join a vector of long values into a formatted string. /// /// Output format: /// `{v1, v2, v3}` /// /// Intended for diagnostic message construction. inline std::string joinNumbers(const std::vector& vec) { std::string s{"{"}; for (size_t i = 0; i < vec.size(); ++i) { s += std::to_string(vec[i]); if (i + 1 < vec.size()) { s += ", "; } } s += "}"; return s; } /// @brief Join a vector of double values into a formatted string. /// /// Output format: /// `{v1, v2, v3}` /// /// Intended for diagnostic message construction. inline std::string joinNumbersDouble(const std::vector& vec) { std::string s{"{"}; for (size_t i = 0; i < vec.size(); ++i) { s += std::to_string(vec[i]); if (i + 1 < vec.size()) { s += ", "; } } s += "}"; return s; } /// @brief Rethrow a concept exception with full compile-time metadata. /// /// This macro captures: /// /// - Concept name /// - Variant name /// - Encoding stage /// - Section /// /// and rethrows a `Mars2GribConceptException` while preserving /// the nested exception chain. #define MARS2GRIB_CONCEPT_RETHROW(CONCEPTNAME, MESSAGE) \ std::throw_with_nested(Mars2GribConceptException(std::string(CONCEPTNAME##Name), \ std::string(CONCEPTNAME##TypeName()), \ std::to_string(Stage), std::to_string(Section), MESSAGE, Here())) /// @brief Throw a concept exception with full compile-time metadata. /// /// Same as `MARS2GRIB_CONCEPT_RETHROW`, but without nesting. /// Intended for initial throw sites. #define MARS2GRIB_CONCEPT_THROW(CONCEPTNAME, MESSAGE) \ do { \ throw Mars2GribConceptException(std::string(CONCEPTNAME##Name), std::string(CONCEPTNAME##TypeName()), \ std::to_string(Stage), std::to_string(Section), MESSAGE, Here()); \ } while (0) } // namespace metkit::mars2grib::utils::exceptions metkit-1.20.2/src/metkit/mars2grib/utils/paramMatcher.h0000664000175000017500000000172615246725757023200 0ustar alastairalastair/* * (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. */ #pragma once namespace metkit::mars2grib::util::param_matcher { struct Range { int first; int last; bool contains(int x) const { return x >= first && x <= last; } }; inline Range range(int first, int last) { return {first, last}; } inline bool matchSingle(int x, const Range& arg) { return arg.contains(x); } inline bool matchSingle(int x, int y) { return x == y; } template bool matchAny(int value, T... arg) { return (matchSingle(value, arg) || ...); } } // namespace metkit::mars2grib::util::param_matcher metkit-1.20.2/src/metkit/mars2grib/utils/logUtils.h0000664000175000017500000000470315246725757022374 0ustar alastairalastair#pragma once #include "eckit/log/Log.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #define MARS2GRIB_LOG_CHECK(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-CHECK: " << msg << std::endl; \ } while (0) #define MARS2GRIB_LOG_MATCH(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-MATCH: " << msg << std::endl; \ } while (0) #define MARS2GRIB_LOG_RESOLVE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-RESOLVE: " << msg << std::endl; \ } while (0) #define MARS2GRIB_LOG_OVERRIDE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-OVERRIDE: " << msg << std::endl; \ } while (0) #define MARS2GRIB_LOG_DEFAULT(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-DEFAULT: " << msg << std::endl; \ } while (0) #define MARS2GRIB_LOG_CONCEPT(CONCEPTNAME) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2GRIB-CONCEPT:" \ << "[Concept " << std::string(CONCEPTNAME##Name) << "] " \ << "Op called: " \ << "Stage=" << Stage << ", " \ << "Section=" << Section << ", " \ << "Variant=" << std::string(CONCEPTNAME##TypeName()) << std::endl; \ } while (0) metkit-1.20.2/src/metkit/mars2grib/utils/generalUtils.h0000664000175000017500000000010415246725757023217 0ustar alastairalastair#pragma once #define mars2gribUnreachable() __builtin_unreachable()metkit-1.20.2/src/metkit/mars2grib/utils/TemporalArithmetic.h0000664000175000017500000005425015246725757024371 0ustar alastairalastair/* * (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 TemporalArithmetic.h /// @brief Adapt eckit date/time arithmetic to mars2grib temporal semantics. /// /// These low-level helpers do not receive backend-model input snapshots; /// therefore their exception frames use the location-only constructor. /// Higher-level callers may subsequently add richer context. /// /// Calendar-month arithmetic is never approximated with a fixed number of /// seconds. Calendar operations are resolved explicitly from `eckit::Date` /// components to preserve month semantics. /// /// @ingroup mars2grib_utils /// #pragma once #include #include #include #include #include #include #include "eckit/types/Date.h" #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/backend/deductions/common.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::utils::time_arithmetic { namespace detail { /// /// @brief Convert an `eckit::Second` to whole seconds, rejecting sub-second input. /// /// `eckit::Second` is a `double`; temporal arithmetic here operates on whole /// seconds only. Any value that is not (within tolerance) integral, or that /// cannot be represented as a `long`, is rejected. /// inline long toWholeSeconds(eckit::Second value) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const double rounded = std::nearbyint(value); if (std::abs(value - rounded) > 1.0e-6) { throw Mars2GribGenericException("Sub-second precision is not supported in temporal arithmetic", Here()); } if (rounded > static_cast(std::numeric_limits::max()) || rounded < static_cast(std::numeric_limits::min())) { throw Mars2GribGenericException("Whole-second value is out of range for a long", Here()); } return static_cast(rounded); } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to convert an eckit::Second to whole seconds", Here())); } } /// /// @brief Multiply a duration length by a per-unit second count with overflow checks. /// /// The multiplication is performed in `long long` and is rejected if it would /// overflow. /// inline long long checkedSecondsFromUnits(long length, long long secondsPerUnit) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const long long lengthWide = static_cast(length); if (lengthWide > 0 && lengthWide > std::numeric_limits::max() / secondsPerUnit) { throw Mars2GribGenericException("TimeDuration length overflows when converted to seconds", Here()); } if (lengthWide < 0 && lengthWide < std::numeric_limits::min() / secondsPerUnit) { throw Mars2GribGenericException("TimeDuration length underflows when converted to seconds", Here()); } return lengthWide * secondsPerUnit; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to convert a TimeDuration length to seconds", Here())); } } inline bool isLeapYear(long year) { return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); } inline long daysInMonth(long year, long month) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { switch (month) { case 1: return 31; case 2: return isLeapYear(year) ? 29 : 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; default: throw Mars2GribGenericException("Invalid calendar month in daysInMonth", Here()); } } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to resolve the number of days in a calendar month", Here())); } } inline eckit::DateTime shiftDateTimeBySeconds(const eckit::DateTime& dateTime, long long deltaSeconds) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { constexpr long long secondsPerDay = 86400LL; const long long currentTimeInSeconds = static_cast(toWholeSeconds(dateTime.time())); if (deltaSeconds > 0 && currentTimeInSeconds > std::numeric_limits::max() - deltaSeconds) { throw Mars2GribGenericException("DateTime second shift overflows", Here()); } if (deltaSeconds < 0 && currentTimeInSeconds < std::numeric_limits::min() - deltaSeconds) { throw Mars2GribGenericException("DateTime second shift underflows", Here()); } const long long totalSeconds = currentTimeInSeconds + deltaSeconds; long long dayOffset = totalSeconds / secondsPerDay; long long normalizedSeconds = totalSeconds % secondsPerDay; if (normalizedSeconds < 0) { normalizedSeconds += secondsPerDay; --dayOffset; } if (dayOffset > static_cast(std::numeric_limits::max()) || dayOffset < static_cast(std::numeric_limits::min())) { throw Mars2GribGenericException("DateTime day offset is out of range for a long", Here()); } eckit::Date shiftedDate = dateTime.date(); shiftedDate += static_cast(dayOffset); return eckit::DateTime{shiftedDate, eckit::Time{static_cast(normalizedSeconds)}}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to shift DateTime by elapsed seconds", Here())); } } inline eckit::DateTime shiftCalendarMonths(const eckit::DateTime& dateTime, long deltaMonths) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { constexpr long monthShiftBound = 12000000L; if (std::abs(deltaMonths) > monthShiftBound) { throw Mars2GribGenericException("Calendar-month shift magnitude is out of the supported range", Here()); } const eckit::Date& date = dateTime.date(); const auto absoluteMonthIndex = static_cast(date.year() - 1) * 12LL + static_cast(date.month() - 1) + static_cast(deltaMonths); auto yearIndex = absoluteMonthIndex / 12LL; auto monthIndex = absoluteMonthIndex % 12LL; if (monthIndex < 0) { monthIndex += 12LL; --yearIndex; } const long targetYear = static_cast(yearIndex + 1); const long targetMonth = static_cast(monthIndex + 1); if (targetYear < 100) { throw Mars2GribGenericException( "Calendar-month shift produced a year outside the eckit-safe range (>= 100)", Here()); } const long targetDay = std::min(date.day(), daysInMonth(targetYear, targetMonth)); return eckit::DateTime{eckit::Date{targetYear, targetMonth, targetDay}, dateTime.time()}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to shift DateTime by calendar months", Here())); } } } // namespace detail /// /// @brief Return the canonical default MARS time. /// /// @return `00:00:00` as an `eckit::Time`. /// @throws Mars2GribGenericException If construction fails unexpectedly. /// inline eckit::Time defaultMarsTime() { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return eckit::Time{0}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to construct the default MARS time", Here())); } } /// /// @brief Construct a DateTime from an explicit date and time. /// /// @param[in] date Calendar date. /// @param[in] time Time of day. /// @return Combined eckit DateTime. /// @throws Mars2GribGenericException If eckit construction fails. /// inline eckit::DateTime makeDateTime(const eckit::Date& date, const eckit::Time& time) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return eckit::DateTime{date, time}; } catch (...) { std::throw_with_nested( Mars2GribGenericException("Failed to construct DateTime from explicit date and time", Here())); } } /// /// @brief Construct a DateTime from a date and optional time. /// /// Missing time is replaced by the canonical default `00:00:00`. /// /// @param[in] date Calendar date. /// @param[in] time Optional time of day. /// @return Combined eckit DateTime. /// @throws Mars2GribGenericException If defaulting or construction fails. /// inline eckit::DateTime makeDateTime(const eckit::Date& date, const std::optional& time) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const eckit::Time resolvedTime = time.has_value() ? *time : defaultMarsTime(); return makeDateTime(date, resolvedTime); } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to construct DateTime from optional time", Here())); } } /// /// @brief Construct the canonical zero-duration value. /// /// @return ProductTimeDuration represented as zero seconds. /// @throws Mars2GribGenericException If construction fails unexpectedly. /// inline metkit::mars2grib::backend::deductions::TimeDuration zeroDuration() { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return TimeDuration{0, TimeUnit::Second}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to construct a zero ProductTimeDuration", Here())); } } /// /// @brief Return the canonical one-calendar-month duration. /// /// @return Duration `{1, Month}`. /// @throws Mars2GribGenericException If construction of the duration unexpectedly fails. /// inline metkit::mars2grib::backend::deductions::TimeDuration oneMonth() { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return {1, TimeUnit::Month}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to execute `oneMonth`", Here())); } } /// /// @brief Return the intrinsic twenty-four-hour synoptic increment. /// /// @return Duration `{24, Hour}`. /// @throws Mars2GribGenericException If construction of the duration unexpectedly fails. /// inline metkit::mars2grib::backend::deductions::TimeDuration twentyFourHours() { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return {24, TimeUnit::Hour}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to execute `twentyFourHours`", Here())); } } /// /// @brief Return true only when two TimeDuration values are exactly identical. /// /// Equality here is structural, not semantic: /// - units must be the same; /// - lengths must be the same. /// /// This intentionally does not normalize equivalent fixed-unit durations. /// For example, `{24, Hour}` and `{1, Day}` are considered different. /// /// @param[in] lhs Left-hand duration. /// @param[in] rhs Right-hand duration. /// @return `true` when both `length` and `unit` are identical; otherwise `false`. /// @throws Mars2GribGenericException If comparison fails unexpectedly. /// inline bool compareTimeDuration(const metkit::mars2grib::backend::deductions::TimeDuration& lhs, const metkit::mars2grib::backend::deductions::TimeDuration& rhs) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return lhs.length == rhs.length && lhs.unit == rhs.unit; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to compare two TimeDuration values", Here())); } } inline long convertToSeconds(const metkit::mars2grib::backend::deductions::TimeDuration& duration) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { long long seconds = 0; switch (duration.unit) { case TimeUnit::Second: return duration.length; case TimeUnit::Hour: seconds = detail::checkedSecondsFromUnits(duration.length, 3600LL); break; case TimeUnit::Day: seconds = detail::checkedSecondsFromUnits(duration.length, 86400LL); break; case TimeUnit::Month: throw Mars2GribGenericException("Cannot convert a calendar-month duration to seconds", Here()); default: throw Mars2GribGenericException("Unsupported TimeDuration unit in convertToSeconds", Here()); } if (seconds > static_cast(std::numeric_limits::max()) || seconds < static_cast(std::numeric_limits::min())) { throw Mars2GribGenericException("Converted duration in seconds is out of range for a long", Here()); } return static_cast(seconds); } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to convert ProductTimeDuration to seconds", Here())); } } /// /// @brief Add elapsed seconds to a DateTime. /// /// @param[in] dateTime Base DateTime. /// @param[in] seconds Elapsed seconds to add. /// @return Shifted DateTime. /// @throws Mars2GribGenericException If eckit arithmetic fails. /// inline eckit::DateTime addSeconds(const eckit::DateTime& dateTime, long seconds) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { return detail::shiftDateTimeBySeconds(dateTime, seconds); } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to add seconds to DateTime", Here())); } } /// /// @brief Subtract elapsed seconds from a DateTime. /// /// @param[in] dateTime Base DateTime. /// @param[in] seconds Elapsed seconds to subtract. /// @return Shifted DateTime. /// @throws Mars2GribGenericException If eckit arithmetic fails. /// inline eckit::DateTime subtractSeconds(const eckit::DateTime& dateTime, long seconds) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { if (seconds == std::numeric_limits::min()) { throw Mars2GribGenericException("Cannot subtract the minimum representable second count", Here()); } return detail::shiftDateTimeBySeconds(dateTime, -seconds); } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to subtract seconds from DateTime", Here())); } } /// /// @brief Add a TimeDuration to a DateTime. /// /// Fixed units are converted to seconds. Calendar-month addition preserves the /// time of day and clamps impossible month-end dates to the last valid day. /// /// @param[in] dateTime Base DateTime. /// @param[in] duration Duration to add. /// @return Shifted DateTime. /// @throws Mars2GribGenericException For unsupported units or arithmetic failures. /// inline eckit::DateTime addDuration(const eckit::DateTime& dateTime, const metkit::mars2grib::backend::deductions::TimeDuration& duration) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { switch (duration.unit) { case TimeUnit::Second: return addSeconds(dateTime, duration.length); case TimeUnit::Hour: return detail::shiftDateTimeBySeconds(dateTime, detail::checkedSecondsFromUnits(duration.length, 3600LL)); case TimeUnit::Day: return detail::shiftDateTimeBySeconds(dateTime, detail::checkedSecondsFromUnits(duration.length, 86400LL)); case TimeUnit::Month: if (duration.length == std::numeric_limits::min()) { throw Mars2GribGenericException("Cannot add the minimum representable month count", Here()); } return detail::shiftCalendarMonths(dateTime, duration.length); default: throw Mars2GribGenericException("Unsupported TimeDuration unit in addDuration", Here()); } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to add TimeDuration to DateTime", Here())); } } /// /// @brief Subtract a TimeDuration from a DateTime. /// /// Fixed units are converted to seconds. Calendar-month subtraction preserves /// the time of day and clamps impossible month-end dates to the last valid day. /// /// @param[in] dateTime Base DateTime. /// @param[in] duration Duration to subtract. /// @return Shifted DateTime. /// @throws Mars2GribGenericException For unsupported units or arithmetic failures. /// inline eckit::DateTime subtractDuration(const eckit::DateTime& dateTime, const metkit::mars2grib::backend::deductions::TimeDuration& duration) { using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { switch (duration.unit) { case TimeUnit::Second: return subtractSeconds(dateTime, duration.length); case TimeUnit::Hour: { const long long seconds = detail::checkedSecondsFromUnits(duration.length, 3600LL); if (seconds == std::numeric_limits::min()) { throw Mars2GribGenericException("Cannot subtract the minimum representable second count", Here()); } return detail::shiftDateTimeBySeconds(dateTime, -seconds); } case TimeUnit::Day: { const long long seconds = detail::checkedSecondsFromUnits(duration.length, 86400LL); if (seconds == std::numeric_limits::min()) { throw Mars2GribGenericException("Cannot subtract the minimum representable second count", Here()); } return detail::shiftDateTimeBySeconds(dateTime, -seconds); } case TimeUnit::Month: if (duration.length == std::numeric_limits::min()) { throw Mars2GribGenericException("Cannot subtract the minimum representable month count", Here()); } return detail::shiftCalendarMonths(dateTime, -duration.length); default: throw Mars2GribGenericException("Unsupported TimeDuration unit in subtractDuration", Here()); } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to subtract TimeDuration from DateTime", Here())); } } /// /// @brief Return the first instant of the calendar month following a DateTime. /// /// This operation is required by synoptic analysis domains and is expressed in /// calendar units rather than approximated in seconds. /// /// @param[in] dateTime DateTime whose following month boundary is required. /// @return First day of the following month at `00:00:00`. /// @throws Mars2GribGenericException If calendar-month resolution fails. /// inline eckit::DateTime beginningOfNextCalendarMonth(const eckit::DateTime& dateTime) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const eckit::Date& date = dateTime.date(); const long year = date.year(); const long month = date.month(); const bool isDecember = month == 12; const long nextYear = isDecember ? year + 1 : year; const long nextMonth = isDecember ? 1 : month + 1; return makeDateTime(eckit::Date{nextYear, nextMonth, 1}, defaultMarsTime()); } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to resolve the next calendar-month boundary", Here())); } } /// /// @brief Compute the non-negative elapsed duration between two DateTimes. /// /// @param[in] begin Inclusive beginning of the interval. /// @param[in] end Exclusive end of the interval. /// @return Elapsed interval represented in seconds. /// @throws Mars2GribGenericException If `end` precedes `begin` or arithmetic fails. /// inline metkit::mars2grib::backend::deductions::TimeDuration durationBetween(const eckit::DateTime& begin, const eckit::DateTime& end) { using metkit::mars2grib::backend::deductions::TimeDuration; using metkit::mars2grib::backend::tables::TimeUnit; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; try { const eckit::Second elapsedSeconds = end - begin; const bool durationIsNonNegative = end >= begin; if (!durationIsNonNegative) { throw Mars2GribGenericException("ProductTimeSpec domain duration cannot be negative", Here()); } const long seconds = detail::toWholeSeconds(elapsedSeconds); return TimeDuration{seconds, TimeUnit::Second}; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Failed to compute elapsed ProductTimeSpec duration", Here())); } } } // namespace metkit::mars2grib::utils::time_arithmetic metkit-1.20.2/src/metkit/mars2grib/utils/type_traits_name.h0000664000175000017500000000752315246725757024144 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include "metkit/codes/api/CodesTypes.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::utils { // ============================================================ // Primary template (deferred to fallback demangling) // ============================================================ template constexpr std::string_view type_name(); // ============================================================ // Base type specializations // ============================================================ template <> constexpr std::string_view type_name() { return "string"; } template <> constexpr std::string_view type_name() { return "bool"; } template <> constexpr std::string_view type_name() { return "char"; } template <> constexpr std::string_view type_name() { return "signed char"; } template <> constexpr std::string_view type_name() { return "unsigned char"; } template <> constexpr std::string_view type_name() { return "int"; } template <> constexpr std::string_view type_name() { return "long"; } template <> constexpr std::string_view type_name() { return "long long"; } template <> constexpr std::string_view type_name() { return "unsigned int"; } template <> constexpr std::string_view type_name() { return "unsigned long"; } template <> constexpr std::string_view type_name() { return "unsigned long long"; } template <> constexpr std::string_view type_name() { return "float"; } template <> constexpr std::string_view type_name() { return "double"; } template <> constexpr std::string_view type_name() { return "long double"; } // ============================================================ // std::vector specializations // ============================================================ template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } } // namespace metkit::mars2grib::utils metkit-1.20.2/src/metkit/mars2grib/utils/dictionary_traits/0000775000175000017500000000000015246725757024150 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h0000664000175000017500000005074415246725757032546 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/type_traits_name.h" // ============================================================================ // Helper macros to reduce boilerplate (similar to LocalConfiguration version) // ============================================================================ // ============================================================== // GET_OR_THROW // ============================================================== #define M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOrThrow { \ \ static CTYPE get_or_throw(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ /* Check key exists */ \ if (!cfg.has(k)) { \ throw exceptions::Mars2GribDictException("Missing key `"s + k + "` in dictionary type `"s + \ std::string(type_name()) + \ "`", \ Here()); \ } \ \ /* Check type */ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ throw exceptions::Mars2GribDictException( \ "Key `"s + k + "` is not of expected type `"s + std::string(type_name()) + \ "` for dictionary type `"s + std::string(type_name()) + "`", \ Here()); \ } \ } \ catch (const exceptions::Mars2GribGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2GribDictException( \ "Internal error while reading key `"s + k + "` as `" + std::string(type_name()) + \ "` from dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // GET_OPT // ============================================================== #define M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOpt { \ static std::optional get_opt(const eckit::LocalConfiguration& cfg, \ std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ if (!cfg.has(k)) { \ return std::nullopt; \ } \ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ return std::nullopt; \ } \ } \ catch (...) { \ return std::nullopt; \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // SET_OR_THROW // ============================================================== #define M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(CTYPE, SETFUNC) \ template <> \ struct DictSetOrIgnore { \ static void set_or_ignore(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (...) { \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // SET_OR_IGNORE // ============================================================== #define M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(CTYPE, SETFUNC) \ template <> \ struct DictSetOrThrow { \ \ static void set_or_throw(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (const exceptions::Mars2GribGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2GribDictException( \ "Unable to set key `"s + k + "` with type `"s + std::string(type_name()) + \ "` in dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2gribUnreachable(); \ } \ }; namespace metkit::mars2grib::utils { // ----------------------------------------------------------------------------- // type_name specialisation // ----------------------------------------------------------------------------- template <> constexpr std::string_view type_name() { return "eckit::LocalConfiguration"; } template <> constexpr std::string_view type_name>() { return "vector"; } } // namespace metkit::mars2grib::utils namespace metkit::mars2grib::utils::dict_traits { using std::operator""s; namespace hacks { inline bool isIntegral(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegral(std::string{key}); } inline bool isFloatingPoint(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPoint(std::string{key}) || conf.isIntegral(std::string{key}); } inline bool isBoolean(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isBoolean(std::string{key}); } inline bool isString(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isString(std::string{key}); } inline bool isSubConfiguration(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfiguration(std::string{key}); } inline bool isIntegralList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegralList(std::string{key}); } inline bool isFloatingPointList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPointList(std::string{key}) || conf.isIntegralList(std::string{key}); } inline bool isStringList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isStringList(std::string{key}); } inline bool isSubConfigurationList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfigurationList(std::string{key}); } } // namespace hacks // ----------------------------------------------------------------------------- // to_json // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const eckit::LocalConfiguration& cfg) noexcept(true) { try { std::ostringstream os; os << cfg; return os.str(); } catch (...) { return "[to_json failed for eckit::LocalConfiguration]"; } } }; // ----------------------------------------------------------------------------- // DictCreateFromSample / Clone / NeedsChecks // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view name) { auto cfg = std::make_unique(); cfg->set("SampleName", std::string(name)); return cfg; } static std::unique_ptr clone_or_throw(const eckit::LocalConfiguration& cfg) { return std::make_unique(cfg); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { const std::string k{key}; try { return cfg.has(k); } catch (const exceptions::Mars2GribGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Mars2GribDictException( "Internal error while checking presence of key `"s + k + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2gribUnreachable(); } }; // ============================================================ // eckit::LocalConfiguration specializations via macros // ============================================================ //------------------------------------------------------------------------------ // Scalar types //------------------------------------------------------------------------------ // bool M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(bool, isBoolean, getBool) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(bool, isBoolean, getBool) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(bool, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(bool, set) // int M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(int, isIntegral, getInt) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(int, isIntegral, getInt) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(int, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(int, set) // long M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(long, isIntegral, getLong) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(long, isIntegral, getLong) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(long, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(long, set) // float M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(float, isFloatingPoint, getFloat) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(float, isFloatingPoint, getFloat) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(float, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(float, set) // double M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(double, isFloatingPoint, getDouble) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(double, isFloatingPoint, getDouble) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(double, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(double, set) // std::string M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::string, isString, getString) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::string, isString, getString) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::string, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::string, set) // eckit::LocalConfiguration (sub-configuration) M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(eckit::LocalConfiguration, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(eckit::LocalConfiguration, set) //------------------------------------------------------------------------------ // Vector types //------------------------------------------------------------------------------ // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getIntVector) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getIntVector) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getLongVector) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getLongVector) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getFloatVector) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getFloatVector) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getDoubleVector) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getDoubleVector) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isStringList, getStringVector) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isStringList, getStringVector) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isSubConfigurationList, getSubConfigurations) M2G_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isSubConfigurationList, getSubConfigurations) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) } // namespace metkit::mars2grib::utils::dict_traits metkit-1.20.2/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h0000664000175000017500000002627315246725757030213 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file dictaccess_options.h /// @brief Read-only dictionary-access traits for Mars2Grib Options. /// /// This adapter exposes the strongly typed `Options` aggregate through the /// generic Mars2Grib dictionary API. /// /// Supported operations for the bool-backed and string-backed option keys: /// /// - `has(options, key)` /// - `has(options, key)` /// - `get_opt(options, key)` /// - `get_or_throw(options, key)` /// - `dict_to_json(options)` /// /// Deliberately unsupported operations: /// /// - mutation through dictionary traits; /// - cloning through dictionary traits; /// - construction from a sample; /// - typed access beyond the explicitly supported bool/string keys. /// /// The Options object is already a complete policy snapshot. The adapter is /// therefore read-only. /// #pragma once #include #include #include #include "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/backend/tables/typeOfTimeIntervals.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/type_traits_name.h" namespace metkit::mars2grib::utils { /// /// @brief Human-readable type name used by generic dictionary diagnostics. /// template <> constexpr std::string_view type_name() { return "TypeOfTimeIntervals"; } /// /// @brief Human-readable type name for the Options dictionary. /// template <> constexpr std::string_view type_name() { return "metkit::mars2grib::Options"; } } // namespace metkit::mars2grib::utils namespace metkit::mars2grib::utils::dict_traits { namespace options_detail { inline void appendJsonBool(std::string& json, std::string_view key, bool value, bool trailingComma) { json += "\""; json += key; json += "\":"; json += value ? "true" : "false"; if (trailingComma) { json += ','; } } inline void appendJsonString(std::string& json, std::string_view key, std::string_view value, bool trailingComma) { json += '"'; json += key; json += "\":\""; for (const char c : value) { if (c == '\\' || c == '"') { json += '\\'; } json += c; } json += '"'; if (trailingComma) { json += ','; } } inline bool isBoolKey(std::string_view key) noexcept { return key == "applyChecks" || key == "enableOverride" || key == "enableBitsPerValueCompression" || key == "normalizeMars" || key == "normalizeMisc" || key == "fixMarsGrid" || key == "skipSection3" || key == "saveErrorStack" || key == "printErrorStackToStdErr" || key == "allowDefaultTimeIncrement" || key == "allowZeroLengthFsWindow" || key == "allowExtendedSetOfOperationsForZeroLengthFsWindow" || key == "allowNonEnumeratedPositiveIntegerTimespanHours" || key == "allowRedundantTimeIncrement" || key == "allowMissingTimespanForInstantProduct" || key == "allowMissingTimespanForStatisticalProduct"; } inline bool isStringKey(std::string_view key) noexcept { return key == "errorStackPath"; } inline bool isKnownKey(std::string_view key) noexcept { return isBoolKey(key) || isStringKey(key); } [[noreturn]] inline void throwMissingOptionalValue(std::string_view key, std::string_view requestedType) { throw exceptions::Mars2GribDictException( "Key `" + std::string(key) + "` has no materialised value while reading `" + std::string(requestedType) + "` from dictionary type `metkit::mars2grib::Options`", Here()); } inline bool getBoolOrThrow(const Options& opts, std::string_view key) { if (key == "applyChecks") { return opts.applyChecks; } if (key == "enableOverride") { return opts.enableOverride; } if (key == "enableBitsPerValueCompression") { return opts.enableBitsPerValueCompression; } if (key == "normalizeMars") { return opts.normalizeMars; } if (key == "normalizeMisc") { return opts.normalizeMisc; } if (key == "fixMarsGrid") { return opts.fixMarsGrid; } if (key == "skipSection3") { return opts.skipSection3; } if (key == "saveErrorStack") { return opts.saveErrorStack; } if (key == "printErrorStackToStdErr") { return opts.printErrorStackToStdErr; } if (key == "allowDefaultTimeIncrement") { return opts.allowDefaultTimeIncrement; } if (key == "allowZeroLengthFsWindow") { return opts.allowZeroLengthFsWindow; } if (key == "allowExtendedSetOfOperationsForZeroLengthFsWindow") { return opts.allowExtendedSetOfOperationsForZeroLengthFsWindow; } if (key == "allowNonEnumeratedPositiveIntegerTimespanHours") { return opts.allowNonEnumeratedPositiveIntegerTimespanHours; } if (key == "allowRedundantTimeIncrement") { return opts.allowRedundantTimeIncrement; } if (key == "allowMissingTimespanForInstantProduct") { return opts.allowMissingTimespanForInstantProduct; } if (key == "allowMissingTimespanForStatisticalProduct") { return opts.allowMissingTimespanForStatisticalProduct; } throw exceptions::Mars2GribDictException("Key `" + std::string(key) + "` cannot be read as `" + "bool" + "` from dictionary type `metkit::mars2grib::Options`", Here()); } inline std::string getStringOrThrow(const Options& opts, std::string_view key) { if (key == "errorStackPath") { return opts.errorStackPath; } throw exceptions::Mars2GribDictException("Key `" + std::string(key) + "` cannot be read as `" + "std::string" + "` from dictionary type `metkit::mars2grib::Options`", Here()); } } // namespace options_detail // ----------------------------------------------------------------------------- // to_json // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const Options& opts) noexcept(true) { try { std::string json; json.reserve(512); json += '{'; options_detail::appendJsonBool(json, "applyChecks", opts.applyChecks, true); options_detail::appendJsonBool(json, "enableOverride", opts.enableOverride, true); options_detail::appendJsonBool(json, "enableBitsPerValueCompression", opts.enableBitsPerValueCompression, true); options_detail::appendJsonBool(json, "normalizeMars", opts.normalizeMars, true); options_detail::appendJsonBool(json, "normalizeMisc", opts.normalizeMisc, true); options_detail::appendJsonBool(json, "fixMarsGrid", opts.fixMarsGrid, true); options_detail::appendJsonBool(json, "skipSection3", opts.skipSection3, true); options_detail::appendJsonBool(json, "saveErrorStack", opts.saveErrorStack, true); options_detail::appendJsonString(json, "errorStackPath", opts.errorStackPath, true); options_detail::appendJsonBool(json, "printErrorStackToStdErr", opts.printErrorStackToStdErr, true); options_detail::appendJsonBool(json, "allowDefaultTimeIncrement", opts.allowDefaultTimeIncrement, true); options_detail::appendJsonBool(json, "allowZeroLengthFsWindow", opts.allowZeroLengthFsWindow, true); options_detail::appendJsonBool(json, "allowExtendedSetOfOperationsForZeroLengthFsWindow", opts.allowExtendedSetOfOperationsForZeroLengthFsWindow, true); options_detail::appendJsonBool(json, "allowNonEnumeratedPositiveIntegerTimespanHours", opts.allowNonEnumeratedPositiveIntegerTimespanHours, true); options_detail::appendJsonBool(json, "allowRedundantTimeIncrement", opts.allowRedundantTimeIncrement, true); options_detail::appendJsonBool(json, "allowMissingTimespanForInstantProduct", opts.allowMissingTimespanForInstantProduct, true); options_detail::appendJsonBool(json, "allowMissingTimespanForStatisticalProduct", opts.allowMissingTimespanForStatisticalProduct, false); json += '}'; return json; } catch (...) { return "[to_json failed for metkit::mars2grib::Options]"; } } }; // ----------------------------------------------------------------------------- // has(options, key) // ----------------------------------------------------------------------------- /// /// @brief Report whether an option has a readable materialised value. /// /// Every supported Options key is a non-optional bool or string member and is /// therefore always present. Unknown keys are absent. /// /// This definition gives the untyped `has(options, key)` operation normal /// dictionary semantics rather than merely reporting whether the C++ structure /// declares a member with that name. /// template <> struct DictHas { static bool has(const Options& opts, std::string_view key) noexcept(false) { if (options_detail::isKnownKey(key)) { return true; } return false; } }; // ----------------------------------------------------------------------------- // bool access // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static bool get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isBoolKey(key)) { return std::nullopt; } return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOrThrow { static std::string get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getStringOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isStringKey(key)) { return std::nullopt; } return options_detail::getStringOrThrow(opts, key); } }; /* * No explicit function-template specialisation is required for `has()`. * * The generic dictionary API implements: * * has(options, key) * * by invoking: * * DictGetOpt::get_opt(options, key).has_value() * * Therefore the DictGetOpt specialisation above provides: * * - has(options, key) * - has(options, key) */ } // namespace metkit::mars2grib::utils::dict_traits metkit-1.20.2/src/metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h0000664000175000017500000001675715246725757031415 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "metkit/mars2grib/utils/generalUtils.h" // Exceptions #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/type_traits_name.h" namespace metkit::mars2grib::utils::dict_traits { using std::operator""s; template struct dependent_false : std::false_type {}; template struct DictToJsonTraits { static std::string to_json(const Dict&) { return std::string{"[to_json not supported for this dictionary type]"}; } static void dump_or_ignore(const Dict&, const std::string&) { LOG_DEBUG_LIB(LibMetkit) << to_json(std::declval()); } }; template struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view) { static_assert(dependent_false::value, "DictTraits::make_from_sample_or_throw not specialized"); } static std::unique_ptr clone_or_throw(const Dict&) { static_assert(dependent_false::value, "DictTraits::clone_or_throw not specialized"); } }; template struct DictHas { static bool has(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictHas not specialized for this Dict"); mars2gribUnreachable(); } }; template struct DictMissing { static bool isMissing(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); mars2gribUnreachable(); } static void setMissing(Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); mars2gribUnreachable(); } }; template struct DictGetOpt { static std::optional get_opt(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOpt not specialized for this Dict and type"); mars2gribUnreachable(); } }; template struct DictGetOrThrow { static T get_or_throw(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOrThrow not specialized for this Dict and type"); mars2gribUnreachable(); } }; template struct DictSetOrIgnore { static void set_or_ignore(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrIgnore not specialized for this Dict and type"); mars2gribUnreachable(); } }; template struct DictSetOrThrow { static void set_or_throw(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrThrow not specialized for this Dict and type"); mars2gribUnreachable(); } }; // ============================================================ // dict_to_json // ============================================================ template std::string dict_to_json(const Dict& d) { return DictToJsonTraits::to_json(d); } // ============================================================ // clone / make_from_sample / needs_checks // ============================================================ template inline constexpr bool dict_supports_checks_v = DictTraits::support_checks; template std::unique_ptr make_from_sample_or_throw(std::string_view name) { return DictTraits::make_from_sample_or_throw(name); } template std::unique_ptr clone_or_throw(const Dict& d) { return DictTraits::clone_or_throw(d); } template void dump_or_ignore(const Dict& d, const std::string& f) { DictToJsonTraits::dump_or_ignore(d, f); } // ============================================================ // has / isMissing / setMissing // ============================================================ // has(dict,key) template inline bool has(const Dict& dict, std::string_view key) { return DictHas::has(dict, key); } // has(dict,key) template inline bool has(const Dict& dict, std::string_view key) { return DictGetOpt::get_opt(dict, key).has_value(); } // isMissing(dict,key) template inline bool isMissing(const Dict& dict, std::string_view key) { return DictMissing::isMissing(dict, key); } // setMissing(dict,key) template inline void setMissing_or_throw(Dict& dict, std::string_view key) { DictMissing::setMissing(dict, key); return; } // check(dict,key,cond) -> bool template inline bool check(const Dict& dict, std::string_view key, Cond&& condition) { if (auto v = DictGetOpt::get_opt(dict, key); v.has_value()) { return std::forward(condition)(*v); } return false; } // ============================================================ // GET UTILITIES // ============================================================ // get_or_throw(dict,key) -> T template inline T get_or_throw(const Dict& dict, std::string_view key) { try { return DictGetOrThrow::get_or_throw(dict, key); } catch (...) { std::throw_with_nested( exceptions::Mars2GribDictException("Forwarding errors while getting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` from dictionary`"s, Here())); mars2gribUnreachable(); } mars2gribUnreachable(); } // get(dict,key) -> std::optional template inline std::optional get_opt(const Dict& dict, std::string_view key) { try { return DictGetOpt::get_opt(dict, key); } catch (...) { return std::nullopt; } mars2gribUnreachable(); } // ============================================================ // SET UTILITIES // ============================================================ // set(dict,key,value) template inline void set_or_throw(Dict& dict, std::string_view key, const T& value) { try { DictSetOrThrow::set_or_throw(dict, key, value); return; } catch (...) { std::throw_with_nested(exceptions::Mars2GribDictException("Forwarding errors while setting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` to dictionary`"s, Here())); mars2gribUnreachable(); } mars2gribUnreachable(); } template inline void set_or_ignore(Dict& dict, std::string_view key, const T& value) { try { DictSetOrIgnore::set_or_ignore(dict, key, value); return; } catch (...) { // ignore exceptions mars2gribUnreachable(); } mars2gribUnreachable(); } } // namespace metkit::mars2grib::utils::dict_traits metkit-1.20.2/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_codes_handle.h0000664000175000017500000004674315246725757031134 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include "eckit/io/Buffer.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/CodesTypes.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" #include "metkit/mars2grib/utils/type_traits_name.h" // ============================================================================ // Helper macros to reduce boilerplate (similar to LocalConfiguration version) // ============================================================================ // ============================================================== // GET_OR_THROW // ============================================================== #define M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(CTYPE, CHECK_NATIVE, GETFUNC) \ template <> \ struct DictGetOrThrow { \ \ static CTYPE get_or_throw(const metkit::codes::CodesHandle& h, std::string_view key) noexcept(false) { \ const std::string k{key}; \ try { \ /* key exists? */ \ if (!h.isDefined(k)) { \ throw exceptions::Mars2GribDictException( \ "Missing key `"s + k + "` in dictionary `"s + \ std::string(type_name()) + "`", \ Here()); \ } \ \ /* check type */ \ auto t = h.type(k); \ if (!(CHECK_NATIVE)) { \ throw exceptions::Mars2GribDictException( \ "Key `"s + k + "` is not of expected type `"s + std::string(type_name()) + \ "` in dictionary `"s + std::string(type_name()) + "`", \ Here()); \ } \ \ return h.GETFUNC(k); \ } \ catch (const exceptions::Mars2GribGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2GribDictException( \ "Internal error while reading key `"s + k + "` as `" + std::string(type_name()) + \ "` from dictionary `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // GET_OPT // ============================================================== #define M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(CTYPE, CHECK_NATIVE, GETFUNC) \ template <> \ struct DictGetOpt { \ \ static std::optional get_opt(const metkit::codes::CodesHandle& h, \ std::string_view key) noexcept(false) { \ const std::string k{key}; \ try { \ if (!h.isDefined(k)) \ return std::nullopt; \ \ auto t = h.type(k); \ if (!(CHECK_NATIVE)) \ return std::nullopt; \ \ return h.GETFUNC(k); \ } \ catch (...) { \ return std::nullopt; \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // SET_OR_THROW // ============================================================== #define M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(CTYPE, SETFUNC) \ template <> \ struct DictSetOrThrow { \ \ static void set_or_throw(metkit::codes::CodesHandle& h, std::string_view key, \ const CTYPE& v) noexcept(false) { \ const std::string k{key}; \ try { \ h.SETFUNC(k, v); \ return; \ } \ catch (const exceptions::Mars2GribGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2GribDictException( \ "Unable to set key `"s + k + "` with type `"s + std::string(type_name()) + \ "` in dictionary `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2gribUnreachable(); \ } \ }; // ============================================================== // SET_OR_IGNORE // ============================================================== #define M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(CTYPE, SETFUNC) \ template <> \ struct DictSetOrIgnore { \ \ static void set_or_ignore(metkit::codes::CodesHandle& h, std::string_view key, \ const CTYPE& v) noexcept(false) { \ const std::string k{key}; \ try { \ h.SETFUNC(k, v); \ return; \ } \ catch (...) { \ return; \ } \ mars2gribUnreachable(); \ } \ }; namespace metkit::mars2grib::utils { // ----------------------------------------------------------------------------- // type_name specialisation // ----------------------------------------------------------------------------- template <> constexpr std::string_view type_name() { return "metkit::codes::CodesHandle"; } } // namespace metkit::mars2grib::utils namespace metkit::mars2grib::utils::dict_traits { using std::operator""s; // ----------------------------------------------------------------------------- // DictToJsonTraits // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const metkit::codes::CodesHandle& sample) { return std::string{"[to_json not supported for codeHandle this dictionary type]"}; } static void dump_or_ignore(const metkit::codes::CodesHandle& sample, const std::string& fname) { try { eckit::Buffer buf{sample.messageSize()}; sample.copyInto(reinterpret_cast(buf.data()), buf.size()); std::ofstream out(fname, std::ios::binary | std::ios::out); if (!out) { return; // fail silently } out.write(static_cast(buf.data()), buf.size()); out.flush(); // ofstream destructor closes the file } catch (...) { LOG_DEBUG_LIB(LibMetkit) << "dump_or_ignore: unable to dump CodesHandle to file " << fname << std::endl; // nothrow guarantee: swallow everything } } }; // ----------------------------------------------------------------------------- // DictCreateFromSample / Clone / NeedsChecks // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = true; static std::unique_ptr make_from_sample_or_throw(std::string_view name) { auto h = metkit::codes::codesHandleFromSample(std::string(name)); if (!h) { throw eckit::SeriousBug("codesHandleFromSample failed", Here()); } return h; } static std::unique_ptr clone_or_throw(const metkit::codes::CodesHandle& h) { return h.clone(); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const metkit::codes::CodesHandle& h, std::string_view key) noexcept(false) { try { return h.has(std::string(key)); } catch (const exceptions::Mars2GribGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Mars2GribDictException( "Internal error while checking presence of key `"s + std::string(key) + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2gribUnreachable(); } }; // ----------------------------------------------------------------------------- // DictMissing // ----------------------------------------------------------------------------- template <> struct DictMissing { static bool isMissing(const metkit::codes::CodesHandle& h, std::string_view key) noexcept(false) { try { return h.isMissing(std::string(key)); } catch (...) { std::throw_with_nested(exceptions::Mars2GribDictException( "Internal error while checking missing state of key `"s + std::string(key) + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2gribUnreachable(); } static void setMissing(metkit::codes::CodesHandle& h, std::string_view key) noexcept(false) { try { h.setMissing(std::string(key)); return; } catch (...) { std::throw_with_nested(exceptions::Mars2GribDictException( "Unable to set key `"s + std::string(key) + "` to missing in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2gribUnreachable(); } }; // ============================================================================ // SCALAR TYPES // ============================================================================ // bool (ECCODES does not have a native boolean; treat as long {0,1}) M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(bool, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(bool, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(bool, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(bool, set) // int M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(int, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(int, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(int, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(int, set) // long M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(long, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(long, (t == metkit::codes::NativeType::Long && h.size(k) == 1), getLong) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(long, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(long, set) // double M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(double, (t == metkit::codes::NativeType::Double && h.size(k) == 1), getDouble) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(double, (t == metkit::codes::NativeType::Double && h.size(k) == 1), getDouble) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(double, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(double, set) // string M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(std::string, (t == metkit::codes::NativeType::String), getString) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(std::string, (t == metkit::codes::NativeType::String), getString) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(std::string, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(std::string, set) // ============================================================================ // VECTOR TYPES // ============================================================================ // std::vector M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(std::vector, (t == metkit::codes::NativeType::Long), getLongArray) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(std::vector, (t == metkit::codes::NativeType::Long), getLongArray) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(std::vector, (t == metkit::codes::NativeType::Double), getDoubleArray) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(std::vector, (t == metkit::codes::NativeType::Double), getDoubleArray) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(metkit::codes::Span, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(metkit::codes::Span, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(std::vector, (t == metkit::codes::NativeType::String), getStringArray) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(std::vector, (t == metkit::codes::NativeType::String), getStringArray) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2G_DEFINE_CODESHANDLE_DICT_GET_OR_THROW(std::vector, (t == metkit::codes::NativeType::Bytes), getBytes) M2G_DEFINE_CODESHANDLE_DICT_GET_OPT(std::vector, (t == metkit::codes::NativeType::Bytes), getBytes) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_THROW(std::vector, set) M2G_DEFINE_CODESHANDLE_DICT_SET_OR_IGNORE(std::vector, set) } // namespace metkit::mars2grib::utils::dict_traits metkit-1.20.2/src/metkit/mars2grib/docs/0000775000175000017500000000000015246725757020205 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/docs/diagrams/0000775000175000017500000000000015246725757021774 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/docs/diagrams/concept.svg0000664000175000017500000002702315246725757024154 0ustar alastairalastair concept_model cluster_stages Abstract priority slots cluster_sections GRIB header (abstract sections) concept Concept Canonical, orthogonal representation of one semantic aspect expansion Concept materialization Sparse expansion across: - multiple sections - multiple priority slots (not compact in space nor priority) concept->expansion logical definition note A Concept is logically compact but expands into a sparse, distributed set of key/value assignments in the GRIB header concept->note allocate allocate expansion->allocate preset preset expansion->preset override override expansion->override runtime runtime expansion->runtime secA Section A allocate->secA key/value secC Section C allocate->secC key/value secB Section B preset->secB key/value override->secA key/value secD Section D override->secD key/value runtime->secC key/value metkit-1.20.2/src/metkit/mars2grib/docs/diagrams/architecture.svg0000664000175000017500000002772715246725757025216 0ustar alastairalastair encoder_overall_structure cluster_inputs Inputs cluster_frontend frontend/ (pure functions) cluster_backend backend/ (header construction) cluster_api api/ (orchestration) mars MARS dictionary frontend Frontend (derive EncoderCfg from MARS) mars->frontend input raii RAII Encoder (uses cfg + dictionaries) (build consistent header) mars->raii use par PAR / MISC / EXTRA dictionary par->raii use opt OPT dictionaries opt->raii use vals Values (data payload) api API (call frontend + backend, then encode values, return GRIB message) vals->api input encfg EncoderCfg (sections + ordered concepts) (used once; stored const for debug) frontend->encfg generate encfg->raii instantiate encfg->raii (cacheable design) gribHeader Grib Header (valid GRIB message) (values = 0) raii->gribHeader produce gribHeader->api encode values on grib header outmsg GRIB message (valid header + values) api->outmsg return metkit-1.20.2/src/metkit/mars2grib/docs/diagrams/concept.dot0000664000175000017500000000540215246725757024140 0ustar alastairalastairdigraph concept_model { rankdir=TB; fontsize=10; nodesep=0.6; ranksep=1.0; // ========================================================= // Concept (canonical representation) // ========================================================= concept [ shape=box, style=rounded, label="Concept\n\nCanonical, orthogonal\nrepresentation of\none semantic aspect" ]; // ========================================================= // Abstract expansion: stages x sections // ========================================================= expansion [ shape=box, style=rounded, label="Concept materialization\n\nSparse expansion across:\n- multiple sections\n- multiple priority slots\n\n(not compact in space nor priority)" ]; // ========================================================= // Abstract priority slots // ========================================================= subgraph cluster_stages { label="Abstract priority slots"; style=rounded; allocate [label="allocate"]; preset [label="preset"]; override [label="override"]; runtime [label="runtime"]; { rank=same; allocate; preset; override; runtime; } } // ========================================================= // Abstract GRIB header sections // ========================================================= subgraph cluster_sections { label="GRIB header (abstract sections)"; style=rounded; secA [label="Section A"]; secB [label="Section B"]; secC [label="Section C"]; secD [label="Section D"]; { rank=same; secA; secB; secC; secD; } } // ========================================================= // Relationships // ========================================================= concept -> expansion [label="logical definition"]; expansion -> allocate [style=dashed]; expansion -> preset [style=dashed]; expansion -> override [style=dashed]; expansion -> runtime [style=dashed]; // Sparse, non-uniform contributions allocate -> secA [label="key/value"]; allocate -> secC [label="key/value"]; preset -> secB [label="key/value"]; override -> secA [label="key/value"]; override -> secD [label="key/value"]; runtime -> secC [label="key/value"]; // ========================================================= // Conceptual note (design intent) // ========================================================= note [ shape=note, label="A Concept is logically compact\nbut expands into a sparse,\ndistributed set of key/value\nassignments in the GRIB header" ]; concept -> note [style=dotted, arrowhead=none]; } metkit-1.20.2/src/metkit/mars2grib/docs/diagrams/architecture.dot0000664000175000017500000000632715246725757025176 0ustar alastairalastairdigraph encoder_overall_structure { rankdir=TB; compound=true; fontsize=10; nodesep=0.6; ranksep=1.2; // ========================================================= // Inputs (two rows to reduce horizontal span) // ========================================================= subgraph cluster_inputs { label="Inputs"; style=rounded; // first row mars [label="MARS dictionary"]; par [label="PAR / MISC / EXTRA dictionary"]; // second row opt [label="OPT dictionaries"]; vals [label="Values (data payload)"]; { rank=same; mars; par; } { rank=same; opt; vals; } } // ========================================================= // Frontend (pure functions) // ========================================================= subgraph cluster_frontend { label="frontend/ (pure functions)"; style=rounded; frontend [shape=box, label="Frontend\n(derive EncoderCfg from MARS)"]; encfg [shape=box, label="EncoderCfg\n(sections + ordered concepts)\n(used once; stored const for debug)"]; } // ========================================================= // Backend (RAII encoder) // ========================================================= subgraph cluster_backend { label="backend/ (header construction)"; style=rounded; raii [shape=box, label="RAII Encoder\n(uses cfg + dictionaries)\n(build consistent header)"]; gribHeader [shape=box, label="Grib Header\n(valid GRIB message)\n(values = 0)"]; } // ========================================================= // API (orchestration) // ========================================================= subgraph cluster_api { label="api/ (orchestration)"; style=rounded; api [shape=box, label="API\n(call frontend + backend,\nthen encode values,\nreturn GRIB message)"]; outmsg [shape=box, label="GRIB message\n(valid header + values)"]; } // --------------------------------------------------------- // Main vertical flow // --------------------------------------------------------- mars -> frontend [label="input"]; frontend -> encfg [label="generate"]; encfg -> raii [label="instantiate"]; raii -> gribHeader [label="produce"]; gribHeader -> api [label="encode values on grib header"]; api -> outmsg [label="return"]; // --------------------------------------------------------- // Backend dictionary usage (orthogonal inputs) // --------------------------------------------------------- mars -> raii [label="use", style=dashed]; par -> raii [label="use", style=dashed]; opt -> raii [label="use", style=dashed]; // Values applied only at API level vals -> api [label="input"]; // --------------------------------------------------------- // Design intent (future caching) // --------------------------------------------------------- encfg -> raii [label="(cacheable design)", style=dotted, arrowhead=none]; } metkit-1.20.2/src/metkit/mars2grib/docs/doxygen/0000775000175000017500000000000015246725757021662 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/docs/doxygen/mars2grib_architecture.md0000664000175000017500000001174115246725757026642 0ustar alastairalastair@page architecture_page Encoder Architecture @section arch_scope Scope and Inputs The Encoder is designed to operate on a minimal and well-defined set of inputs. The user is expected to provide: - a **MARS dictionary** - a **miscellaneous dictionary** (hereafter referred to as *misc dictionary*) No additional dictionaries are required or assumed. All information required to determine the encoded product structure is derived from the inputs listed above and from internal compile-time rules. @section arch_mars_dictionary The MARS Dictionary The MARS dictionary is the primary semantic input of the Encoder. In addition to classical MARS metadata, the dictionary is **extended** and may contain post-processing related keywords, including (but not limited to): - grid - truncation - packing These extended keywords are treated as first-class inputs and participate directly in the encoding logic. @section arch_split Frontend and Backend Separation The Encoder is logically split into two distinct components: @subsection arch_frontend Frontend The frontend: - depends **only** on the MARS dictionary - performs no encoding - produces an intermediate object called **EncoderConfiguration** The frontend is responsible for interpreting the semantic intent expressed by the MARS dictionary and mapping it to an internal structural description. @subsection arch_backend Backend The backend: - depends on the MARS dictionary - depends on the misc dictionary - depends on the EncoderConfiguration produced by the frontend The backend performs the actual encoding by executing the encoding logic described by the configuration. @image html architecture.svg "General overview of the encoder architecture" @section arch_encoder_configuration EncoderConfiguration The EncoderConfiguration is a complete, self-contained description of the **structure** of the encoded header. Here, *structure* refers to: - memory layout - presence of keywords - association between sections and concepts The EncoderConfiguration is specified as follows. @subsection arch_sections Sections and Template Numbers For each encoding section, a template number is provided. - For sections 2, 3, 4, and 5, the template number is semantically relevant. - For all other sections, the template number is present but effectively a dummy value, as only a single valid template exists. @subsection arch_concept_variants Concept Variants per Section For each section, the EncoderConfiguration contains an ordered list of `Concept::Variant` entries. Each entry identifies: - the concept - the concrete variant of that concept to be used in that section @section arch_configuration_construction Construction of EncoderConfiguration The EncoderConfiguration is constructed by combining: - information extracted from the MARS dictionary - compile-time information provided by recipes The construction process follows a strict and deterministic workflow. @subsection arch_step1 Concept Activation From the MARS dictionary: - the set of **active concepts** is determined - for each active concept, a **version** is deduced Inactive concepts are excluded at this stage. @subsection arch_step2 Template Determination The set of active `(Concept, Version)` pairs is used to determine: - the template number for each relevant section This step must result in a unique and consistent set of template numbers. @subsection arch_step3 Recipe Completion Once template numbers are known, recipes are used to complete the EncoderConfiguration. For each `(section, template number)` pair, a recipe provides a list of `(Concept, Variant)` pairs. The following rules apply: - If a variant is **not explicitly specified** in the recipe, it is assumed to be `Default`. - If a variant is explicitly specified in the recipe, it is **mandatory**. A mismatch with the variant deduced from the MARS dictionary results in an error. - If the recipe specifies `Default`, the variant deduced from the MARS dictionary may override it. @section arch_dispatch Dispatch Table Construction Once the EncoderConfiguration is finalized, a three-dimensional table of function pointers is constructed. The table is indexed as: - **Stage** - **Section** - **Concept** The table is inferred **exclusively** from the EncoderConfiguration. No additional runtime decisions are required. During construction, **all null entries are explicitly eliminated**. Only valid `(Stage, Section, Concept)` combinations are inserted. As a result, the dispatch table is **dense** and contains only callable function pointers. @section arch_execution Encoding Execution Encoding is performed by iterating over the dispatch table. - for each stage: - for each section: - all function pointers associated with that stage and section are invoked in deterministic order Because the dispatch table contains no null entries: - execution is purely linear over the precomputed table All semantic decisions are completed prior to execution, during the construction of the EncoderConfiguration and the dispatch table. metkit-1.20.2/src/metkit/mars2grib/docs/doxygen/mars2grib_overview.md0000664000175000017500000000317315246725757026026 0ustar alastairalastair@mainpage Encoder Architecture Overview @tableofcontents @section intro_overview Overview This documentation describes the architecture of the Encoder: a modular, concept-driven system designed to construct encoded products (e.g. GRIB) from high-level semantic information. The Encoder is not a monolithic procedure. Instead, it is built as a composition of independent, orthogonal units—called *concepts*—that collectively describe *what* must be encoded, while the Encoder framework determines *how* this information is mapped to a concrete binary representation. The primary design goals are: - Separation of concerns between *semantics* and *encoding mechanics* - Compile-time validation of encoder configurations - Extensibility without modification of existing code paths - Deterministic and reproducible encoding behaviour @section intro_audience Intended Audience This documentation targets: - Developers extending or maintaining the Encoder - Reviewers validating correctness and design choices - Advanced users integrating the Encoder into production workflows Familiarity with modern C++ (templates, constexpr, type traits) and encoded data formats (e.g. GRIB) is assumed. @section intro_structure Documentation Structure The documentation is organised as follows: - @ref architecture_page : High-level architecture and data flow - @ref concepts_page : Definition and role of concepts - @ref stages_page : Encoding stages and execution model - @ref recipes_page : Template selection and validation Each section builds progressively from abstract principles to concrete implementation details. metkit-1.20.2/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in0000664000175000017500000044441115246725757025356 0ustar alastairalastair# Doxyfile 1.13.2 # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # Note: # # Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] # Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "mars2grib" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = 0.0.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # With the PROJECT_ICON tag one can specify an icon that is included in the tabs # when the HTML document is shown. Doxygen will copy the logo to the output # directory. PROJECT_ICON = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this # option can be useful when feeding Doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise cause # performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = YES # Controls the number of sub-directories that will be created when # CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed # number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. CREATE_SUBDIRS_LEVEL = 8 # If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English # (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, # Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with # English messages), Korean, Korean-en (Korean with English messages), Latvian, # Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, # Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, # Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @MARS2GRIB_SOURCE_DIRECTORY@ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but # less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot, question mark or exclamation mark) of a # Javadoc-style comment as the brief description. If set to NO, the Javadoc- # style will behave just like regular Qt-style comments (thus requiring an # explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first # line (until the first dot, question mark or exclamation mark) of a Qt-style # comment as the brief description. If set to NO, the Qt-style will behave just # like regular Qt-style comments (thus requiring an explicit \brief command for # a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # Doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". Note that you cannot put \n's in the value part of an alias # to insert newlines (in the resulting output). You can put ^^ in the value part # of an alias to insert a newline as if a physical newline was in the original # file. When you need a literal { or } or , in the value part of an alias you # have to escape them by means of a backslash (\), this can lead to conflicts # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by Doxygen, so you can # mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 6 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is # unique. # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a # sequence number starting at 0 and GITHUB use the lower case version of title # with any whitespace replaced by '-' and punctuation characters removed. # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. MARKDOWN_ID_STYLE = DOXYGEN # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. Words listed in the # AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES # This tag specifies a list of words that, when matching the start of a word in # the documentation, will suppress auto links generation, if it is enabled via # AUTOLINK_SUPPORT. This list does not affect affect links explicitly created # using \# or the \link or commands. # This tag requires that the tag AUTOLINK_SUPPORT is set to YES. AUTOLINK_IGNORE_WORDS = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also makes the inheritance and # collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software) sources only. Doxygen will parse # them like normal C++ but will assume all classes use public instead of private # inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small # Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 1 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to # NO can help when comparing the output of multiple runs. # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. TIMESTAMP = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = YES # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = YES # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = YES # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect # if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all # undocumented namespaces that are normally visible in the namespace hierarchy. # If set to NO, these namespaces will be included in the various overviews. This # option has no effect if EXTRACT_ALL is enabled. # The default value is: YES. HIDE_UNDOC_NAMESPACES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. # The default value is: SYSTEM. CASE_SENSE_NAMES = SYSTEM # If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_HEADERFILE tag is set to YES then the documentation for a class # will show which file needs to be included to use the class. # The default value is: YES. SHOW_HEADERFILE = YES # If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # # Note that if you run Doxygen from a directory containing a file called # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = # The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH # environment variable) so that external tools such as latex and gs can be # found. # Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the # path already specified by the PATH variable, and are added in the order # specified. # Note: This option is particularly useful for macOS version 14 (Sonoma) and # higher, when running Doxygen from Doxywizard, because in this case any user- # defined changes to the PATH are ignored. A typical example on macOS is to set # EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin # together with the standard path, the full search path used by doxygen when # launching external tools will then become # PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin EXTERNAL_TOOL_PATH = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete # function parameter documentation. If set to NO, Doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, Doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about # undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO # If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found # while parsing the user defined layout file, such as missing or wrong elements. # See also LAYOUT_FILE for details. If set to NO, problems with the layout file # will be suppressed. # The default value is: YES. WARN_LAYOUT_FILE = YES # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the Doxygen process Doxygen will return with a non-zero status. # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case # the behavior will remain as with the setting FAIL_ON_WARNINGS. # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place # (outside of Doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT # The default value is: at line $line of file $file. WARN_LINE_FORMAT = "at line $line of file $file" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the # warning and error messages are written to standard error. When as file - is # specified the warning and error messages are written to standard output # (stdout). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = \@MARS2GRIB_SOURCE_DIRECTORY@/utils/configConverter.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/dictionary_traits/dictaccess_codes_handle.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/dictionary_traits/dictionary_access_traits.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/dictionary_traits/dictaccess_eckit_configuration.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/type_traits_name.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/mars2gribExceptions.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/logUtils.h \ @MARS2GRIB_SOURCE_DIRECTORY@/utils/enableOptions.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/origin/originMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/origin/originConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/origin/originEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/origin/originEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/reference-time/referenceTimeEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/reference-time/referenceTimeConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/reference-time/referenceTimeMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/reference-time/referenceTimeEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/data-type/dataTypeEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/data-type/dataTypeMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/data-type/dataTypeConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/data-type/dataTypeEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/shape-of-the-earth/shapeOfTheEarthMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/shape-of-the-earth/shapeOfTheEarthEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/satellite/satelliteMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/satellite/satelliteEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/satellite/satelliteEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/satellite/satelliteConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/GeneralRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/EncodingCallbacksRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/longrange/longrangeEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/longrange/longrangeEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/longrange/longrangeConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/longrange/longrangeMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/derived/derivedEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/derived/derivedMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/derived/derivedConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/derived/derivedEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/nil/nilEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/nil/nilEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/nil/nilMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/nil/nilConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/packing/packingConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/packing/packingMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/packing/packingEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/packing/packingEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/mars/marsMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/mars/marsEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/mars/marsConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/mars/marsEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/composition/compositionMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/composition/compositionEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/composition/compositionEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/composition/compositionConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/level/levelConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/level/levelEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/level/levelEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/level/levelMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/tables/tablesConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/tables/tablesMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/tables/tablesEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/tables/tablesEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/generating-process/generatingProcessEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/generating-process/generatingProcessConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/generating-process/generatingProcessMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/generating-process/generatingProcessEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/MatchingCallbacksRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/destine/destineEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/destine/destineMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/destine/destineEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/destine/destineConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/statistics/statisticsEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/statistics/statisticsEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/statistics/statisticsMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/statistics/statisticsConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/wave/waveConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/wave/waveEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/wave/waveMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/wave/waveEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/AllConcepts.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/analysis/analysisMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/analysis/analysisEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/analysis/analysisConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/analysis/analysisEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/point-in-time/pointInTimeEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/point-in-time/pointInTimeMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/point-in-time/pointInTimeEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/point-in-time/pointInTimeConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/ensemble/ensembleMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/ensemble/ensembleEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/ensemble/ensembleEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/ensemble/ensembleConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/representation/representationEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/representation/representationConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/representation/representationMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/representation/representationEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/param/paramMatcher.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/param/paramEncoding.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/param/paramEnum.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/concepts/param/paramConceptDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/makePhaseCallbacksRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/RegisterEntryDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/utils.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/EntryVariantRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/common.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/makeVariantCallbacksRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/compile-time-registry-engine/makeEntryCallbacksRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/productionStatusOfProcessedData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/generation.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/typeOfProcessedData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/backgroundProcess.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/waveFrequencyGrid.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/scaleFactorOfCentralWaveNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/paramId.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/methodNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/typeOfGeneratingProcess.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/allowedReferenceValue.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/activity.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/periodItMin.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/realization.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/significanceOfReferenceTime.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/perturbationNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/localTablesVersion.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/model.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/waveFrequencyNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/shapeOfTheEarth.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/typeOfEnsembleForecast.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/pvArray.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/detail/pv_137_be.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/detail/ProductTime.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/detail/StatType.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/detail/StatisticalWindow.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/dataset.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/lengthOfTimeWindow.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/scaledValueOfCentralWaveNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/stream.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/expver.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/subCentre.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/tablesVersion.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/bitsPerValue.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/productTime.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/typeOfStatisticalProcessing.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/systemNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/resolution.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/experiment.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/class.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/subSetTrunc.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/timeIncrementInSeconds.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 \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/satelliteSeries.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/waveDirectionNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/centre.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/waveDirectionGrid.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/periodItMax.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/generatingProcessIdentifier.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/offsetToEndOf4DvarWindow.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/numberOfForecastsInEnsemble.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/numberOfFrequencies.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/type.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/satelliteNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/fourierCoefficientIndex.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/numberOfFourierCoefficients.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/matchDataRepresentationTemplateNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/matchProductDefinitionTemplateNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/checkLocalUseSection.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/matchGridDefinitionTemplateNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/matchDataset.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/checkDestinELocalSection.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/checkEnsembleProductDefinitionSection.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/matchLocalDefinitionNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/checkDerivedProductDefinitionSection.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/checks/checkStatisticsProductDefinitionSection.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/SectionLayoutData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/TemplateSignatureKey.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/dsl.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/Select.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/Recipe.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/ActiveConceptsData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/ResolvedTemplateData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/CompressionMask.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/resolver/SectionTemplateSelector.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer2.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer5.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializerCore.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer4.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer0.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer1.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionInitializer3.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/sections/initializers/sectionRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/productionStatusOfProcessedData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfProcessedData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/backgroundProcess.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfStatisticalProcessing.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfGeneratingProcess.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/significanceOfReferenceTime.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/timeUnits.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfEnsembleForecast.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfTimeIntervals.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/derivedForecast.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/shapeOfTheReferenceSystem.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/tables/typeOfInterval.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/encodeValues.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/GribHeaderLayoutData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/make_HeaderLayout.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/resolveActiveConcepts.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/resolveSectionsLayout.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section1Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section3Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section5Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section2Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section4Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/impl/section0Recipes.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/resolution/section-recipes/SectionTemplateSelectors.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/header/EncodingPlan.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/header/SpecializedEncoder.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/normalization.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/normalizeMarsDict.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/normalizeMiscDict.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/SanitizationRegistry.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/generation.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/param.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/ident.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/activity.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/hdate.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/stattype.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/anoffset.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/levtype.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/realization.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/model.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/timespan.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/packing.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/dataset.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/htime.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/stream.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/expver.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/time.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/resolution.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/All.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/experiment.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/class.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/channel.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/system.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/method.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/level.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/origin.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/direction.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/wavelength.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/number.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/date.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/frequency.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/domain.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/truncation.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/instrument.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/grid.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/levelist.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/type.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/chem.h \ @MARS2GRIB_SOURCE_DIRECTORY@/frontend/normalization/per_key/mars/step.h \ @MARS2GRIB_SOURCE_DIRECTORY@/api/Options.h \ @MARS2GRIB_SOURCE_DIRECTORY@/api/Mars2Grib.h \ @MARS2GRIB_SOURCE_DIRECTORY@/CoreOperations.h \ @MARS2GRIB_SOURCE_DIRECTORY@/api/Mars2Grib.cc \ @MARS2GRIB_SOURCE_DIRECTORY@/api/pymars2grib/mars2grib_core.cc # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files # that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default # INPUT_ENCODING if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). # See also: INPUT_ENCODING for further information on supported encodings. INPUT_FILE_ENCODING = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, # *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, # *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to # be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h *.cc # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = @MARS2GRIB_SOURCE_DIRECTORY@/mars2grib/docs/diagrams # The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = # If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- # directories of the project's root, is used as the documentation for that sub- # directory, except when the README.md starts with a \dir, \page or \mainpage # command. If set to NO, the README.md file needs to start with an explicit \dir # command in order to be used as directory documentation. # The default value is: YES. IMPLICIT_DIR_DOCS = YES # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can # be processed before the automatic comment starts. # Minimum value: 7, maximum value: 10000, default value: 72. FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # multi-line macros, enums or list initialized variables directly into the # documentation. # The default value is: NO. INLINE_SOURCES = YES # Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # that should be ignored while generating the index headers. The IGNORE_PREFIX # tag works for classes, function and member names. The entity will be placed in # the alphabetical list under the first letter of the entity name that remains # after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). # Note: Since the styling of scrollbars can currently not be overruled in # Webkit/Chromium, the styling will be left out of the default doxygen.css if # one or more extra stylesheets have been specified. So if scrollbar # customization is desired it has to be added explicitly. For an example see the # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. # Possible values are: LIGHT always generates light mode output, DARK always # generates dark mode output, AUTO_LIGHT automatically sets the mode according # to the user preference, uses light mode if no preference is set (the default), # AUTO_DARK automatically sets the mode according to the user preference, uses # dark mode if no preference is set and TOGGLE allows a user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be # dynamically folded and expanded in the generated HTML source code. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_CODE_FOLDING = YES # If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: # https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: # protocol. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COPY_CLIPBOARD = YES # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by # Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_PROJECT_COOKIE = # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 9999 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag determines the URL of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDURL = # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with # a.o. the download links, offline (the HTML help workshop was already many # years in maintenance mode). You can download the HTML help workshop from the # web archives at Installation executable (see: # http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo # ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # The SITEMAP_URL tag is used to specify the full URL of the place where the # generated documentation will be placed on the server by the user during the # deployment of the documentation. The generated sitemap is called sitemap.xml # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL # is specified no sitemap is generated. For information about the sitemap # protocol see https://www.sitemaps.org # This tag requires that the tag GENERATE_HTML is set to YES. SITEMAP_URL = # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an # example, the default style sheet generated by Doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. # Since the tree basically has the same information as the tab index, you could # consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the # project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # When the SHOW_ENUM_VALUES tag is set doxygen will show the specified # enumeration values besides the enumeration mnemonics. # The default value is: NO. SHOW_ENUM_VALUES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES # If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = YES # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. # Possible values are: MathJax_2 and MathJax_3. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 # (see: # http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported # for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This # is the name for Mathjax version 3, for MathJax version 2 this will be # translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be /ec/res4/hpcperm/mavm/ba/metkit-bundle/source/metkit/src/metkit/mars2grib/mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. The default value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # for MathJax version 2 (see # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): # MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled Doxygen will generate a search box for # the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /Node, # Edge and Graph Attributes specification You need to make sure dot is able # to find the font, which can be done by putting it in a standard location or by # setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. Default graphviz fontsize is 14. # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about # arrows shapes. # The default value is: labelfontname=Helvetica,labelfontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" # DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes # around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification # The default value is: shape=box,height=0.2,width=0.4. # This tag requires that the tag HAVE_DOT is set to YES. DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" # You can set the path where dot can find font specified with fontname in # DOT_COMMON_ATTR and others dot attributes. # This tag requires that the tag HAVE_DOT is set to YES. DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then Doxygen will # generate a graph for each documented class showing the direct and indirect # inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and # HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case # the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the # CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. # If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance # relations will be shown as texts / links. Explicit enabling an inheritance # graph or choosing a different representation for an inheritance graph of a # specific class, can be accomplished by means of the command \inheritancegraph. # Disabling an inheritance graph can be accomplished by means of the command # \hideinheritancegraph. # Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. # The default value is: YES. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH tag is set to YES then Doxygen will generate a # graph for each documented class showing the direct and indirect implementation # dependencies (inheritance, containment, and class references variables) of the # class with other documented classes. Explicit enabling a collaboration graph, # when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the # command \collaborationgraph. Disabling a collaboration graph can be # accomplished by means of the command \hidecollaborationgraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS tag is set to YES then Doxygen will generate a graph for # groups, showing the direct groups dependencies. Explicit enabling a group # dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means # of the command \groupgraph. Disabling a directory graph can be accomplished by # means of the command \hidegroupgraph. See also the chapter Grouping in the # manual. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES, Doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. UML_LOOK = YES # If the UML_LOOK tag is enabled, the fields and methods are shown inside the # class node. If there are many fields or methods and many nodes the graph may # become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the # number of items for each type to make the size more manageable. Set this to 0 # for no limit. Note that the threshold may be exceeded by 50% before the limit # is enforced. So when you set the threshold to 10, up to 15 fields may appear, # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. # This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 # If the DOT_UML_DETAILS tag is set to NO, Doxygen will show attributes and # methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS # tag is set to YES, Doxygen will add type and arguments for attributes and # methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, Doxygen # will not generate fields with class member information in the UML graphs. The # class diagrams will look similar to the default class diagrams but using UML # notation for the relationships. # Possible values are: NO, YES and NONE. # The default value is: NO. # This tag requires that the tag UML_LOOK is set to YES. DOT_UML_DETAILS = YES # The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters # to display on a single line. If the actual line length exceeds this threshold # significantly it will be wrapped across multiple lines. Some heuristics are # applied to avoid ugly line breaks. # Minimum value: 0, maximum value: 1000, default value: 17. # This tag requires that the tag HAVE_DOT is set to YES. DOT_WRAP_THRESHOLD = 17 # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. TEMPLATE_RELATIONS = YES # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to # YES then Doxygen will generate a graph for each documented file showing the # direct and indirect include dependencies of the file with other documented # files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO, # can be accomplished by means of the command \includegraph. Disabling an # include graph can be accomplished by means of the command \hideincludegraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDE_GRAPH = YES # If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are # set to YES then Doxygen will generate a graph for each documented file showing # the direct and indirect include dependencies of the file with other documented # files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set # to NO, can be accomplished by means of the command \includedbygraph. Disabling # an included by graph can be accomplished by means of the command # \hideincludedbygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH tag is set to YES then Doxygen will generate a call # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. Disabling a call graph can be # accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALL_GRAPH = YES # If the CALLER_GRAPH tag is set to YES then Doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. Disabling a caller graph can be # accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY tag is set to YES then Doxygen will graphical # hierarchy of all classes instead of a textual one. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH tag is set to YES then Doxygen will show the # dependencies a directory has on other directories in a graphical way. The # dependency relations are determined by the #include relations between the # files in the directories. Explicit enabling a directory graph, when # DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command # \directorygraph. Disabling a directory graph can be accomplished by means of # the command \hidedirectorygraph. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. DIRECTORY_GRAPH = YES # The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels # of child directories generated in directory dependency graphs by dot. # Minimum value: 1, maximum value: 25, default value: 1. # This tag requires that the tag DIRECTORY_GRAPH is set to YES. DIR_GRAPH_MAX_DEPTH = 25 # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: # https://www.graphviz.org/)). # # Note the formats svg:cairo and svg:cairo:cairo cannot be used in combination # with INTERACTIVE_SVG (the INTERACTIVE_SVG will be set to NO). # Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus, # png:gdiplus:gdiplus, svg:cairo, svg:cairo:cairo, svg:svg, svg:svg:core, # gif:cairo, gif:cairo:gd, gif:cairo:gdiplus, gif:gdiplus, gif:gdiplus:gdiplus, # gif:gd, gif:gd:gd, jpg:cairo, jpg:cairo:gd, jpg:cairo:gdiplus, jpg:gd, # jpg:gd:gd, jpg:gdiplus and jpg:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg or svg:svg or svg:svg:core, then this option # can be set to YES to enable generation of interactive SVG images that allow # zooming and panning. # # Note that this requires a modern browser other than Internet Explorer. Tested # and working are Firefox, Chrome, Safari, and Opera. # # Note This option will be automatically disabled when DOT_IMAGE_FORMAT is set # to svg:cairo or svg:cairo:cairo. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. INTERACTIVE_SVG = YES # The DOT_PATH tag can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. DOTFILE_DIRS = # You can include diagrams made with dia in Doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. DIA_PATH = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). DIAFILE_DIRS = # When using PlantUML, the PLANTUML_JAR_PATH tag should be used to specify the # path where java can find the plantuml.jar file or to the filename of jar file # to be used. If left blank, it is assumed PlantUML is not used or called during # a preprocessing step. Doxygen will generate a warning when it encounters a # \startuml command in this case and will not generate output for the diagram. PLANTUML_JAR_PATH = # When using PlantUML, the PLANTUML_CFG_FILE tag can be used to specify a # configuration file for PlantUML. PLANTUML_CFG_FILE = # When using PlantUML, the specified paths are searched for files specified by # the !include statement in a PlantUML block. PLANTUML_INCLUDE_PATH = # The PLANTUMLFILE_DIRS tag can be used to specify one or more directories that # contain PlantUml files that are included in the documentation (see the # \plantumlfile command). PLANTUMLFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes # larger than this value, Doxygen will truncate the graph, which is visualized # by representing a node as a red box. Note that if the number of direct # children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that # the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. # Minimum value: 0, maximum value: 10000, default value: 50. # This tag requires that the tag HAVE_DOT is set to YES. DOT_GRAPH_MAX_NODES = 1000 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs # generated by dot. A depth value of 3 means that only nodes reachable from the # root by following a path via at most 3 edges will be shown. Nodes that lay # further from the root node will be omitted. Note that setting this option to 1 # or 2 may greatly reduce the computation time needed for large code bases. Also # note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. # Minimum value: 0, maximum value: 1000, default value: 0. # This tag requires that the tag HAVE_DOT is set to YES. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support # this, this feature is disabled by default. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES Doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. # Note: This tag requires that UML_LOOK isn't set, i.e. the Doxygen internal # graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES, Doxygen will remove the intermediate # files that are used to generate the various graphs. # # Note: This setting is not only used for dot files but also for msc temporary # files. # The default value is: YES. DOT_CLEANUP = YES # You can define message sequence charts within Doxygen comments using the \msc # command. If the MSCGEN_TOOL tag is left empty (the default), then Doxygen will # use a built-in version of mscgen tool to produce the charts. Alternatively, # the MSCGEN_TOOL tag can also specify the name an external tool. For instance, # specifying prog as the value, Doxygen will call the tool as prog -T # -o . The external tool should support # output file formats "png", "eps", "svg", and "ismap". MSCGEN_TOOL = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). MSCFILE_DIRS = metkit-1.20.2/src/metkit/mars2grib/docs/doxygen/mars2grib_concepts.md0000664000175000017500000001255215246725757025777 0ustar alastairalastair@page concepts_page Concepts @section concepts_definition Definition of a Concept A **Concept** is the fundamental semantic unit of the Encoder. A concept represents an orthogonal piece of information required to describe an encoded product. Concepts are the only entities that **set values in the GRIB header**. @image html concept.svg "General idea of a concept" Concepts do not decide *what* values to set. They only decide *where* and *how* values are written. All value resolution is delegated to deductions. @section concepts_compile_time Compile-Time Registration Model Concepts are registered entirely at compile time. Each registered entity is a **pair** composed of: 1. a **key**, identifying the semantic capability 2. a **value**, representing the executable contributions @subsection concepts_registration_key Registration Key The registration key is: Concept::Variant Each `(Concept, Variant)` pair represents a distinct semantic realization and is treated as an independent entity by the Encoder. @subsection concepts_new_concept_or_variant New Concept or New Variant Before changing the concept system, decide whether the behavior belongs to a new concept or to a new variant of an existing concept. - A new concept is appropriate for an independent semantic axis that must be composable with other concepts. - A new variant is appropriate for an alternative realization inside an existing semantic axis when independent composability is not required. This is a domain decision and is not always apparent from the code structure. @section concepts_level_guardrail Level Concept Guardrail The `level` concept deliberately hides the raw fixed-surface representation used by GRIB. GRIB vertical levels are ultimately represented by: - `typeOfFirstFixedSurface` - `scaleFactorOfFirstFixedSurface` - `scaledValueOfFirstFixedSurface` - `typeOfSecondFixedSurface` - `scaleFactorOfSecondFixedSurface` - `scaledValueOfSecondFixedSurface` These keys must not be set directly by mars2grib concept changes. Although many combinations are technically possible, most are not meaningful ECMWF levels. Level encoding must go through the official abstraction: `typeOfLevel` plus, when needed, `level`, `topLevel`, `bottomLevel`, and PV-array data. Each supported `typeOfLevel` corresponds to a `LevelType` variant or to a small number of virtual type-of-level values maintained in mars2grib for backward-compatibility reasons. If new level behavior is required, update the `LevelType` variant, matcher mapping, or deduction instead of writing fixed-surface keys directly. @section concepts_registration_value Registration Value The value associated with each `Concept::Variant` key is a **dense, fixed-size table** indexed by: - **Stage** - **Section** Conceptually: Table[Stage][Section] -> function pointer | nullptr Each entry corresponds to a specialization of the concept for a given `(Stage, Section)` pair. - A non-null entry indicates that the concept provides logic for that `(Stage, Section)`. - A null entry indicates that no specialization exists for that pair. This table is constructed at compile time and is immutable. @section concepts_specialization Specializations A concept provides logic by defining one or more specializations of the form: Concept Only explicitly defined specializations are registered. No implicit instantiation or fallback mechanism exists. Missing `(Stage, Section)` combinations are represented by null pointers in the registration table. @section concepts_dispatch_role Role in Encoder Dispatch The compile-time registration tables define the **maximum set of capabilities** available to the Encoder. At runtime: - the EncoderConfiguration selects a set of `(Concept, Variant)` keys - for each selected key, only the non-null `(Stage, Section)` entries are extracted - all null entries are explicitly discarded From this process, a **dense runtime dispatch table** is built, containing only valid function pointers. Concepts are unaware of: - which other concepts are active - execution order - template numbers - runtime configuration @section concepts_deductions Deductions as the Only Resolution Mechanism Concepts do not access input dictionaries directly. All resolution of GRIB keywords and values is performed exclusively through **deductions**. A deduction: - reads information from the MARS and misc dictionaries - applies validation, normalization, and consistency rules - exposes resolved values through a stable interface Concepts consume only deduction results and never inspect raw inputs. This strict separation ensures that: - concepts remain dictionary-agnostic - resolution logic is centralized - concepts are reusable and independently testable @section concepts_orthogonality Semantic Orthogonality Concepts are **semantically orthogonal**. Each concept: - has a single, well-defined semantic responsibility - does not depend on other concepts - does not encode cross-concept assumptions Any interaction between concepts is expressed through: - deduction logic - recipe constraints Never through direct concept-to-concept coupling. @section concepts_non_goals Explicit Non-Goals Concepts intentionally do not: - select template numbers - perform value deduction - allocate memory - manage execution order - inspect runtime configuration Concepts are purely declarative, statically registered contributors to the encoding process. metkit-1.20.2/src/metkit/mars2grib/api/0000775000175000017500000000000015246725757020026 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/api/Mars2GribTestCaseGenerator.h0000664000175000017500000000330115246725757025267 0ustar alastairalastair/* * (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. */ #pragma once #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/value/Value.h" #include "metkit/mars2grib/api/Options.h" namespace metkit::mars2grib { class Mars2GribTestCaseGenerator { public: using OptionEntry = std::pair; using OptionList = std::initializer_list; Mars2GribTestCaseGenerator(); explicit Mars2GribTestCaseGenerator(const Options& opts); explicit Mars2GribTestCaseGenerator(const eckit::LocalConfiguration& opts); explicit Mars2GribTestCaseGenerator(OptionList opts); Mars2GribTestCaseGenerator(const Mars2GribTestCaseGenerator&) = delete; Mars2GribTestCaseGenerator& operator=(const Mars2GribTestCaseGenerator&) = delete; Mars2GribTestCaseGenerator(Mars2GribTestCaseGenerator&&) = delete; Mars2GribTestCaseGenerator& operator=(Mars2GribTestCaseGenerator&&) = delete; ~Mars2GribTestCaseGenerator() = default; std::string generate(const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); std::string generate(const eckit::LocalConfiguration& mars); private: const eckit::Value language_; const Options opts_; }; } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/mars2grib/api/pymars2grib/0000775000175000017500000000000015246725757022267 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2grib/api/pymars2grib/__init__.py0000664000175000017500000000004015246725757024372 0ustar alastairalastairfrom .mars2grib import Mars2Gribmetkit-1.20.2/src/metkit/mars2grib/api/pymars2grib/mars2grib.py0000664000175000017500000000151215246725757024530 0ustar alastairalastairfrom .mars2grib_core import init_bindings, Mars2GribCore class Mars2Grib(): """ Mars2Grib encoder object """ def __init__(self, options: dict = {}): init_bindings() self._mars2grib = Mars2GribCore(options) def encode(self, values: list[float], mars: dict, misc: dict = {}) -> bytes: """ Encode values, described by mars and (optionally) misc dictionaries, as a GRIB2 message :param values: Values to encode :type values: list[float] :param mars: Mars keys describing the values :type mars: dict :param misc: Miscellaneous (non-mars) keys describing the values, may be empty :type misc: dict :return: Encoded GRIB2 message as bytes object :rtype: bytes """ return self._mars2grib.encode(values, mars, misc) metkit-1.20.2/src/metkit/mars2grib/api/pymars2grib/mars2grib_core.cc0000664000175000017500000000775015246725757025507 0ustar alastairalastair/* * (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. */ #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/exception/Exceptions.h" #include "eckit/runtime/Main.h" #include "metkit/mars2grib/api/Mars2Grib.h" namespace py = pybind11; using metkit::mars2grib::Mars2Grib; static eckit::LocalConfiguration dictToLocalConfig(const py::dict& dict) { eckit::LocalConfiguration config; for (auto item : dict) { const auto key = item.first.cast(); const auto value = item.second; if (py::isinstance(value)) { config.set(key, value.cast()); } else if (py::isinstance(value)) { config.set(key, value.cast()); } else if (py::isinstance(value)) { config.set(key, value.cast()); } else if (py::isinstance(value)) { config.set(key, value.cast()); } else if (py::isinstance(value)) { config.set(key, dictToLocalConfig(value.cast())); } else if (py::isinstance(value)) { const py::list list = value.cast(); if (list.empty()) { config.set(key, std::vector{}); } else { bool hasFloat = false; for (const auto& item : list) { if (py::isinstance(item)) { hasFloat = true; } else if (!py::isinstance(item)) { throw eckit::Exception{"Unsupported type in list for key '" + key + "'", Here()}; } // else it's an int, just fall through... } if (hasFloat) { std::vector vector; vector.reserve(list.size()); for (const auto& item : list) { vector.push_back(item.cast()); } config.set(key, vector); } else { std::vector vector; vector.reserve(list.size()); for (const auto& item : list) { vector.push_back(item.cast()); } config.set(key, vector); } } } else { throw eckit::Exception{"Unsupported type for key '" + key + "'", Here()}; } } return config; } py::bytes encode(Mars2Grib& encoder, const std::vector& values, const py::dict& mars, const py::dict& misc) { const auto message = encoder.encode(values, dictToLocalConfig(mars), dictToLocalConfig(misc)); const auto size = message->messageSize(); std::vector buffer(message->messageSize()); message->copyInto(buffer.data(), buffer.size()); return py::bytes(reinterpret_cast(buffer.data()), buffer.size()); } PYBIND11_MODULE(mars2grib_core, m) { m.def("init_bindings", []() { const char* args[] = {"mars2grib", ""}; eckit::Main::initialise(1, const_cast(args)); }); auto mars2grib = py::class_(m, "Mars2GribCore") .def(py::init<>()) .def(py::init([](py::dict dict) { return std::make_unique(dictToLocalConfig(dict)); })) .def("encode", &encode, py::arg("values"), py::arg("mars"), py::arg("misc")); } metkit-1.20.2/src/metkit/mars2grib/api/pymars2grib/CMakeLists.txt0000664000175000017500000000203515246725757025027 0ustar alastairalastairpybind11_add_module(mars2grib_core MODULE mars2grib_core.cc) target_link_libraries(mars2grib_core PRIVATE pybind11::module pybind11::lto metkit eckit ) if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) # Strip unnecessary sections of the binary on Linux/macOS pybind11_strip(mars2grib_core) endif() set_target_properties(mars2grib_core PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/pymars2grib ) install( TARGETS mars2grib_core COMPONENT python DESTINATION "${CMAKE_INSTALL_LIBDIR}/pymars2grib" ) # Copy mars2grib.py into the install directory for now # TODO: Create a python wheel which can be installed with pip install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/mars2grib.py ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py COMPONENT python DESTINATION "${CMAKE_BINARY_DIR}/lib/pymars2grib" ) install( FILES ${CMAKE_CURRENT_SOURCE_DIR}/mars2grib.py ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py COMPONENT python DESTINATION "${CMAKE_INSTALL_LIBDIR}/pymars2grib" ) metkit-1.20.2/src/metkit/mars2grib/api/readOptionsFromInitializerList.h0000664000175000017500000001067215246725757026360 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromInitializerList.h /// @brief Parse Mars2Grib Options from a compact initializer list. /// /// This file is intended to be included into `Mars2Grib.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/value/Value.h" #include "metkit/mars2grib/api/Mars2Grib.h" #include "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::detail { namespace exceptions = metkit::mars2grib::utils::exceptions; [[noreturn]] inline void throwInvalidOptionType(std::string_view key, const eckit::Value& value, std::string_view expected) { throw exceptions::Mars2GribDictException("Option `" + std::string(key) + "` has value type `" + value.typeName() + "`; expected " + std::string(expected), Here()); } inline bool readBool(std::string_view key, const eckit::Value& value) { if (!value.isBool()) { throwInvalidOptionType(key, value, "bool"); } return value.as(); } inline std::string readString(std::string_view key, const eckit::Value& value) { if (!value.isString()) { throwInvalidOptionType(key, value, "string"); } return value.as(); } inline void applyOption(metkit::mars2grib::Options& opts, std::string_view key, const eckit::Value& value) { if (key == "applyChecks") { opts.applyChecks = readBool(key, value); return; } if (key == "enableOverride") { opts.enableOverride = readBool(key, value); return; } if (key == "enableBitsPerValueCompression") { opts.enableBitsPerValueCompression = readBool(key, value); return; } if (key == "normalizeMars") { opts.normalizeMars = readBool(key, value); return; } if (key == "normalizeMisc") { opts.normalizeMisc = readBool(key, value); return; } if (key == "fixMarsGrid") { opts.fixMarsGrid = readBool(key, value); return; } if (key == "skipSection3") { opts.skipSection3 = readBool(key, value); return; } if (key == "saveErrorStack") { opts.saveErrorStack = readBool(key, value); return; } if (key == "errorStackPath") { opts.errorStackPath = readString(key, value); return; } if (key == "printErrorStackToStdErr") { opts.printErrorStackToStdErr = readBool(key, value); return; } if (key == "allowDefaultTimeIncrement") { opts.allowDefaultTimeIncrement = readBool(key, value); return; } if (key == "allowZeroLengthFsWindow") { opts.allowZeroLengthFsWindow = readBool(key, value); return; } if (key == "allowExtendedSetOfOperationsForZeroLengthFsWindow") { opts.allowExtendedSetOfOperationsForZeroLengthFsWindow = readBool(key, value); return; } if (key == "allowNonEnumeratedPositiveIntegerTimespanHours") { opts.allowNonEnumeratedPositiveIntegerTimespanHours = readBool(key, value); return; } if (key == "allowRedundantTimeIncrement") { opts.allowRedundantTimeIncrement = readBool(key, value); return; } if (key == "allowMissingTimespanForInstantProduct") { opts.allowMissingTimespanForInstantProduct = readBool(key, value); return; } if (key == "allowMissingTimespanForStatisticalProduct") { opts.allowMissingTimespanForStatisticalProduct = readBool(key, value); return; } throw exceptions::Mars2GribDictException("Unknown Mars2Grib option `" + std::string(key) + "`", Here()); } inline metkit::mars2grib::Options readOptions(metkit::mars2grib::Mars2Grib::OptionList entries) { metkit::mars2grib::Options opts; std::unordered_set seen; seen.reserve(entries.size()); for (const auto& [key, value] : entries) { if (!seen.insert(key).second) { throw exceptions::Mars2GribDictException("Duplicate Mars2Grib option `" + key + "`", Here()); } applyOption(opts, key, value); } return opts; } } // namespace metkit::mars2grib::detail metkit-1.20.2/src/metkit/mars2grib/api/Mars2Grib.cc0000664000175000017500000005325615246725757022140 0ustar alastairalastair/* * (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 Mars2Grib.cc /// @brief Implementation of the Mars2Grib public encoding API. /// /// This file contains the implementation of the `Mars2Grib` class declared /// in `Mars2Grib.h`. /// /// It provides the concrete orchestration logic that: /// - builds the internal encoder configuration from the MARS dictionary /// - invokes the specialized backend encoder /// - injects field values into the resulting GRIB handle /// - exposes a temporary staged-encoding cache interface for benchmarking /// and transitional comparison purposes /// /// This file intentionally contains **no GRIB semantics** and **no deduction /// logic**. All domain-specific decisions are delegated to lower layers. /// /// --- /// /// ## Error propagation /// /// User-facing API calls are wrapped in a stable mars2grib error boundary. /// On mars2grib failures, the boundary rethrows an `eckit::UserError` that /// exposes the innermost exception text and optionally persists and/or prints /// the full structured nested stack according to the active `Options`. /// /// --- /// /// ## Transitional staged API /// /// This implementation also exposes a temporary staged-encoding interface /// based on `prepare()` and `finaliseEncoding()`. /// /// This interface is not intended for general use. It exists only to allow /// short-term benchmarking and comparison against a legacy cache-based /// implementation before the cache lifecycle is fully internalized in /// lower layers. /// /// --- /// /// ## Scope /// /// - This file is part of the **Mars2Grib public API implementation** /// - It is not intended for direct use by end users /// - Its behavior defines the observable semantics of `Mars2Grib::encode` /// and the temporary staged-encoding API /// /// @ingroup mars2grib_api /// #include "Mars2Grib.h" // other libraries #include "eckit/exception/Exceptions.h" // dictionary access traits #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_codes_handle.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" // encode header/values implementation #include "metkit/mars2grib/CoreOperations.h" #include "metkit/mars2grib/api/Mars2GribApiErrorHandling.h" #include "metkit/mars2grib/api/readOptionsFromInitializerList.h" #include "metkit/mars2grib/api/readOptionsFromLocalConfiguration.h" namespace metkit::mars2grib { namespace tables = metkit::mars2grib::backend::tables; namespace exceptions = metkit::mars2grib::utils::exceptions; /// /// @brief Concrete specialization of the generic core cache entry. /// /// This alias binds the generic `CoreOperations::CacheEntry` template to the /// concrete public API types: /// - `eckit::LocalConfiguration` for MARS metadata /// - `eckit::LocalConfiguration` for auxiliary metadata /// - `Options` for encoding options /// - `metkit::codes::CodesHandle` for the GRIB output object /// /// It is used internally as the implementation payload of the opaque /// `Mars2Grib::CacheEntry`. /// /// @note /// This staged cache path is temporary and not intended for public use. /// using CoreCacheEntry = CoreOperations::CacheEntry; /// /// @brief Opaque API wrapper around the concrete core cache entry. /// /// This type hides the concrete staged-encoding cache representation from /// API users while preserving unique ownership and immutability. /// /// Internally, it stores a fully prepared `CoreCacheEntry` specialized for /// the public `Mars2Grib` API types. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// purposes and may be changed or removed without notice. /// struct Mars2Grib::CacheEntry { /// /// @brief Construct the opaque wrapper from a concrete core cache. /// /// Ownership of the prepared core cache is transferred into the wrapper. /// /// @param[in] impl /// Unique pointer owning the prepared concrete core cache entry. /// explicit CacheEntry(std::unique_ptr&& impl) : impl_{std::move(impl)} {} CacheEntry(const CacheEntry&) = delete; CacheEntry& operator=(const CacheEntry&) = delete; CacheEntry(CacheEntry&&) = delete; CacheEntry& operator=(CacheEntry&&) = delete; ~CacheEntry() = default; /// /// @brief Owned immutable implementation cache. /// /// This pointer stores the concrete staged-encoding cache used by the /// generic core layer. /// const std::unique_ptr impl_; }; /// /// @brief Destroy an opaque API cache entry. /// /// This custom deleter is required because `Mars2Grib::CacheEntry` is /// incomplete in the public header and only fully defined in this /// implementation file. /// /// @param[in] p /// Pointer to the opaque cache entry to destroy. /// /// @note /// This staged cache API is temporary and not intended for public use. /// void Mars2Grib::CacheEntryDeleter::operator()(const CacheEntry* p) const { delete p; } /// /// @brief Prepare an opaque staged-encoding cache. /// /// This function delegates to the generic core layer to: /// - normalize input metadata if enabled, /// - resolve the structural header layout, /// - build the specialized encoder state, /// - prepare an immutable reusable sample for later finalization. /// /// The returned cache is opaque at the public API level and can be /// passed to `finaliseEncoding()` multiple times. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer owning an opaque immutable cache entry. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// purposes and may be changed or removed without notice. /// Mars2Grib::CacheEntryPtr Mars2Grib::prepare(const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling( "Mars2Grib::prepare", opts_, [&]() { auto coreCache = CoreOperations::prepare(mars, misc, opts_, language_); return CacheEntryPtr(new CacheEntry(std::move(coreCache))); }, Here()); } /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This function delegates to the generic core layer to complete the /// encoding pipeline using a cache previously produced by `prepare()`. /// /// The cache provides the pre-specialized, reusable encoding state, /// while this function injects the supplied field values and produces /// a final `CodesHandle`. /// /// The cache is not consumed and may be reused in subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Field values to encode as double. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// purposes and may be changed or removed without notice. /// std::unique_ptr Mars2Grib::finaliseEncoding(const CacheEntryPtr& cacheEntry, const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::finaliseEncoding", opts_, [&]() { return CoreOperations::finaliseEncoding( *(cacheEntry->impl_), Span{values}, mars, misc, opts_, language_); }, Here()); } /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This overload accepts field values as `float`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Pointer to the field values as double. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr Mars2Grib::finaliseEncoding(const CacheEntryPtr& cacheEntry, const double* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::finaliseEncoding", opts_, [&]() { return CoreOperations::finaliseEncoding( *(cacheEntry->impl_), Span{values, length}, mars, misc, opts_, language_); }, Here()); } /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This overload accepts field values as `float`. /// /// The cache provides the pre-specialized, reusable encoding state, /// while this function injects the supplied field values and produces /// a final `CodesHandle`. /// /// The cache is not consumed and may be reused in subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Field values to encode as float. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// purposes and may be changed or removed without notice. /// std::unique_ptr Mars2Grib::finaliseEncoding(const CacheEntryPtr& cacheEntry, const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::finaliseEncoding", opts_, [&]() { return CoreOperations::finaliseEncoding( *(cacheEntry->impl_), Span{values}, mars, misc, opts_, language_); }, Here()); } /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This overload accepts field values as `float`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Pointer to the field values as float. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr Mars2Grib::finaliseEncoding(const CacheEntryPtr& cacheEntry, const float* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::finaliseEncoding", opts_, [&]() { return CoreOperations::finaliseEncoding( *(cacheEntry->impl_), Span{values, length}, mars, misc, opts_, language_); }, Here()); } // ----------------------------------------------------------------------------- // Mars2Grib construction // ----------------------------------------------------------------------------- Mars2Grib::Mars2Grib() : opts_{} {} Mars2Grib::Mars2Grib(const Options& opts) : opts_{opts} {} Mars2Grib::Mars2Grib(const eckit::LocalConfiguration& opts) : opts_{detail::readOptions(opts)} {} Mars2Grib::Mars2Grib(OptionList opts) : opts_{detail::readOptions(opts)} {} // ----------------------------------------------------------------------------- // Encoding interfaces // ----------------------------------------------------------------------------- std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars) { const eckit::LocalConfiguration misc{}; return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars) { const eckit::LocalConfiguration misc{}; return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const double* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values, length}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const float* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values, length}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const double* values, size_t length, const eckit::LocalConfiguration& mars) { const eckit::LocalConfiguration misc{}; return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values, length}, mars, misc, opts_, language_); }, Here()); } std::unique_ptr Mars2Grib::encode(const float* values, size_t length, const eckit::LocalConfiguration& mars) { const eckit::LocalConfiguration misc{}; return exceptions::withMars2GribApiErrorHandling>( "Mars2Grib::encode", opts_, [&]() { return CoreOperations::encode(Span{values, length}, mars, misc, opts_, language_); }, Here()); } } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/mars2grib/api/Mars2GribApiErrorHandling.h0000664000175000017500000003026015246725757025101 0ustar alastairalastair/* * (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 Mars2GribApiErrorHandling.h /// @brief Common error handling for mars2grib public API calls. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::utils::exceptions { namespace detail { inline bool envVarEnablesStackMode(const char* name) { const char* value = std::getenv(name); if (value == nullptr) { return false; } std::string normalized; for (const unsigned char* p = reinterpret_cast(value); *p != '\0'; ++p) { normalized.push_back(static_cast(std::tolower(*p))); } return normalized == "1" || normalized == "true" || normalized == "on" || normalized == "yes"; } /// @brief Sanitize a string for use in a generated filename. inline std::string sanitizeForFileName(const std::string& value) { std::string out; out.reserve(value.size()); for (const unsigned char c : value) { if (std::isalnum(c) || c == '_' || c == '-' || c == '.') { out.push_back(static_cast(c)); } else { out.push_back('_'); } } return out; } /// @brief Convert the current thread identifier into a filename-safe string. inline std::string currentThreadIdAsString() { std::ostringstream os; os << std::this_thread::get_id(); return sanitizeForFileName(os.str()); } /// @brief Resolve the directory where stack files are stored. inline eckit::PathName errorStackDirectory(const metkit::mars2grib::Options& opts) { return eckit::PathName(opts.errorStackPath).fullName(); } /// @brief Build a unique error-stack file path for an API entry point. inline eckit::PathName makeErrorStackFilePath(const metkit::mars2grib::Options& opts, const std::string& apiName) { static std::atomic counter{0}; const eckit::PathName dir = errorStackDirectory(opts); try { dir.mkdir(); } catch (const std::exception& e) { throw eckit::UserError("Unable to create Mars2Grib error stack directory `" + dir.asString() + "`: " + e.what(), Here()); } std::ostringstream fileName; fileName << "mars2grib-error-stack-" << sanitizeForFileName(apiName) << "-pid" << static_cast(::getpid()) << "-tid" << currentThreadIdAsString() << "-" << counter.fetch_add(1, std::memory_order_relaxed) << ".log"; return dir / fileName.str(); } /// @brief Format an eckit code location as file:line. inline std::string formatCodeLocation(const eckit::CodeLocation& loc) { std::ostringstream os; os << loc.file() << ":" << loc.line(); return os.str(); } /// @brief Information extracted from the innermost exception. struct InnermostExceptionInfo { std::string message; eckit::CodeLocation location; }; /// @brief Extract the location from an eckit exception. /// /// If the exception is not an eckit exception, fall back to the API boundary /// location. inline eckit::CodeLocation exceptionLocationOrFallback(const std::exception& exception, const eckit::CodeLocation& fallback) { if (const auto* eckitException = dynamic_cast(&exception)) { return eckitException->location(); } return fallback; } /// @brief Recursively find the innermost std::nested_exception payload. /// /// This assumes the mars2grib exception stack is represented using /// std::nested_exception / std::throw_with_nested. If mars2grib uses a custom /// nesting mechanism instead, this is the only helper that needs to be adapted. inline InnermostExceptionInfo innermostExceptionInfo(const std::exception& exception, const eckit::CodeLocation& fallbackLocation) { const InnermostExceptionInfo current{ exception.what(), exceptionLocationOrFallback(exception, fallbackLocation), }; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return current; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return current; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { return innermostExceptionInfo(nested, current.location); } catch (...) { return InnermostExceptionInfo{ "", current.location, }; } return current; } /// @brief Serialize a mars2grib exception stack to disk. inline void writeStructuredExceptionFrame(const std::exception& exception, std::ostream& out, std::size_t level, std::size_t frame) { const std::string pad = indent(level); out << pad << "+ " << std::string(lineSize, '=') << '\n' << pad << "+ frame " << frame << '\n' << pad << "+ " << std::string(lineSize, '-') << '\n'; if (const auto* me = dynamic_cast(&exception)) { me->printFrame(out, pad); } else { out << pad << "+ message: " << exception.what() << '\n'; } out << pad << "+ " << std::string(lineSize, '+') << '\n'; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { writeStructuredExceptionFrame(nested, out, level + 1, frame + 1); } catch (...) { out << pad << "+ message: \n"; } } /// @brief Write the structured API-boundary stack header to a stream. inline void writeStructuredStackHeader(std::ostream& out, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation(), const std::string* errorStackFilePath = nullptr) { out << "Mars2Grib API failure\n"; out << "API entry point: " << apiName << "\n"; if (errorStackFilePath != nullptr) { out << "Error stack file: " << *errorStackFilePath << "\n"; } out << "\n"; out << "Nested exception stack:\n"; out << "-----------------------\n"; out << "+ " << std::string(lineSize, '=') << '\n' << "+ frame 0\n" << "+ " << std::string(lineSize, '-') << '\n'; out << "+ file: " << loc.file() << "\n" << "+ function: " << loc.func() << "\n" << "+ line: " << loc.line() << "\n" << "+ link: " << loc.file() << ":" << loc.line() << "\n" << "+ message: Error encountered at: " << apiName << "\n" << "+ " << std::string(lineSize, '+') << '\n'; } /// @brief Serialize a mars2grib exception stack to disk. inline void writeMars2GribExceptionStackToFile(const Mars2GribGenericException& exception, const std::string& apiName, const eckit::PathName& errorStackFilePath, const eckit::CodeLocation& loc = eckit::CodeLocation()) { std::ofstream out(errorStackFilePath.asString()); if (!out) { throw eckit::UserError("Mars2Grib conversion failed, but the error stack file could not be opened: `" + errorStackFilePath.asString() + "`", Here()); } const std::string path = errorStackFilePath.asString(); writeStructuredStackHeader(out, apiName, loc, &path); writeStructuredExceptionFrame(exception, out, 1, 1); } /// @brief Print a mars2grib exception stack to standard error. inline void writeMars2GribExceptionStackToStdErr(const Mars2GribGenericException& exception, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation()) { writeStructuredStackHeader(std::cerr, apiName, loc); writeStructuredExceptionFrame(exception, std::cerr, 1, 1); } /// @brief Build the short public-facing error message. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner, const eckit::PathName& errorStackFilePath) { std::ostringstream os; os << "Mars2Grib conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Full error stack written to `" << errorStackFilePath.asString() << "`"; return os.str(); } /// @brief Build the short public-facing error message when stack saving is disabled. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner) { std::ostringstream os; os << "Mars2Grib conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << ")."; return os.str(); } /// @brief Build the public-facing message when stack-file writing also failed. inline std::string buildPublicErrorMessageWithoutStackFile(const InnermostExceptionInfo& inner, const std::exception& stackWriteError) { std::ostringstream os; os << "Mars2Grib conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Additionally, the full error stack could not be written: " << stackWriteError.what(); return os.str(); } } // namespace detail /// @brief Execute a callable under the mars2grib API error boundary. /// /// @tparam Result /// Callable return type. /// /// @tparam Callable /// Callable type. /// /// @param[in] apiName /// API entry-point name used in diagnostics. /// /// @param[in] callable /// Operation to execute. /// /// @param[in] loc /// Source location attached to the API-boundary frame. template Result withMars2GribApiErrorHandling(const std::string& apiName, const metkit::mars2grib::Options& opts, Callable&& callable, const eckit::CodeLocation& loc = eckit::CodeLocation()) { try { return std::forward(callable)(); } catch (const Mars2GribGenericException& e) { const detail::InnermostExceptionInfo inner = detail::innermostExceptionInfo(e, loc); const bool printErrorStackToStdErr = opts.printErrorStackToStdErr || detail::envVarEnablesStackMode("MARS2GRIB_PRINT_STACK_TO_STDERR"); const bool saveErrorStack = opts.saveErrorStack || detail::envVarEnablesStackMode("MARS2GRIB_WRITE_STACK_TO_FILE"); if (printErrorStackToStdErr) { detail::writeMars2GribExceptionStackToStdErr(e, apiName, loc); } if (!saveErrorStack) { throw eckit::UserError(detail::buildPublicErrorMessage(inner), inner.location); } try { const eckit::PathName errorStackFilePath = detail::makeErrorStackFilePath(opts, apiName); detail::writeMars2GribExceptionStackToFile(e, apiName, errorStackFilePath, loc); throw eckit::UserError(detail::buildPublicErrorMessage(inner, errorStackFilePath), inner.location); } catch (const std::exception& stackWriteError) { throw eckit::UserError(detail::buildPublicErrorMessageWithoutStackFile(inner, stackWriteError), inner.location); } } } } // namespace metkit::mars2grib::utils::exceptions metkit-1.20.2/src/metkit/mars2grib/api/Options.h0000664000175000017500000002533315246725757021640 0ustar alastairalastair/* * (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 Options.h /// @brief Configuration options for the Mars2Grib encoding API. /// /// This header defines the public configuration structure used to control the /// behaviour of the Mars2Grib encoder. /// /// `Options` is a fully materialised, strongly typed policy object. A caller may /// construct it directly, or the API may populate it from another option source, /// such as an `eckit::LocalConfiguration` or an initializer list. /// /// Every member has an explicit default. Consequently, a default-constructed /// `Options` object is always valid as an option dictionary, even though a later /// deduction may reject a particular product when an opt-in policy required by /// that product has not been enabled. /// /// The options control: /// /// - validation and override behaviour; /// - metadata normalisation; /// - GRIB encoding strategy; /// - compatibility policies accepted by ProductTimeSpec; /// - explicit ProductTimeSpec compatibility policies. /// /// @ingroup mars2grib_api /// #pragma once #include namespace metkit::mars2grib { namespace defaults { inline constexpr bool applyChecks = true; inline constexpr bool enableOverride = false; inline constexpr bool enableBitsPerValueCompression = false; inline constexpr bool normalizeMars = false; inline constexpr bool normalizeMisc = false; inline constexpr bool fixMarsGrid = true; inline constexpr bool skipSection3 = false; inline constexpr bool allowDefaultTimeIncrement = false; inline constexpr bool allowZeroLengthFsWindow = true; inline constexpr bool allowExtendedSetOfOperationsForZeroLengthFsWindow = false; inline constexpr bool allowNonEnumeratedPositiveIntegerTimespanHours = true; inline constexpr bool allowRedundantTimeIncrement = true; inline constexpr bool allowMissingTimespanForInstantProduct = true; inline constexpr bool allowMissingTimespanForStatisticalProduct = false; inline constexpr bool saveErrorStack = false; inline constexpr const char* errorStackPath = "./"; inline constexpr bool printErrorStackToStdErr = false; } // namespace defaults /// /// @brief Encoding and ProductTimeSpec policy options for Mars2Grib. /// /// The structure is intentionally a plain aggregate: /// /// - direct programmatic construction remains simple; /// - every option can be copied into a stable policy snapshot; /// - dictionary traits can expose the structure through the same typed access /// API used for other Mars2Grib dictionaries; /// - adding an option does not require virtual dispatch or ownership machinery. /// /// Unless stated otherwise, boolean options are disabled by default. This keeps /// compatibility relaxations explicit and opt-in. /// struct Options { // ------------------------------------------------------------------------- // General encoder behaviour // ------------------------------------------------------------------------- /// /// @brief Enable or disable input validation checks. /// /// When enabled, the encoder performs consistency and validity checks at /// selected critical points during encoding. /// /// Disabling this option may improve performance, but malformed or /// inconsistent input may then fail later and with less useful diagnostics. /// /// @default false /// bool applyChecks = defaults::applyChecks; /// /// @brief Enable metadata override semantics. /// /// When enabled, values provided through the parameter dictionary may /// override values resolved from the MARS dictionary. /// /// When disabled, conflicting overrides result in an error. /// /// @default false /// bool enableOverride = defaults::enableOverride; /// /// @brief Enable bits-per-value compression. /// /// When enabled, the encoder may select a bits-per-value packing strategy /// to reduce message size. /// /// This option affects the encoding strategy only. It does not alter the /// semantic meaning of the field. /// /// @default false /// bool enableBitsPerValueCompression = defaults::enableBitsPerValueCompression; /// /// @brief Enable semantic normalisation of the MARS dictionary. /// /// When enabled, the MARS request is sanitised against the active language /// definition before deductions are performed. /// /// @default false /// bool normalizeMars = defaults::normalizeMars; /// /// @brief Enable semantic normalisation of auxiliary metadata. /// /// When enabled, the auxiliary or Misc dictionary is sanitised against the /// active language definition before deductions are performed. /// /// @default false /// bool normalizeMisc = defaults::normalizeMisc; /// /// @brief Automatically normalise legacy MARS `grid` syntax. /// /// When enabled, the encoder converts supported legacy MARS grid /// specifications into the form expected by the GRIB geometry encoding. /// /// @default false /// bool fixMarsGrid = defaults::fixMarsGrid; /// /// @brief Skip explicit encoding of GRIB Section 3. /// /// When enabled, the encoder does not encode the Grid Definition Section /// itself and leaves geometry handling to gridSpec/ecCodes. /// /// @default false /// bool skipSection3 = defaults::skipSection3; /// /// @brief Save the detailed API error stack to a file. /// /// When enabled, user-facing API failures persist the detailed structured /// exception stack to a file under `errorStackPath`, and the thrown public /// exception includes the full path to that file. /// /// @default true /// bool saveErrorStack = defaults::saveErrorStack; /// /// @brief Directory where API error-stack files are written. /// /// This path is used only when `saveErrorStack` is enabled. /// /// @default "./" /// std::string errorStackPath = defaults::errorStackPath; /// /// @brief Print the detailed API error stack to standard error. /// /// When enabled, user-facing API failures also emit the structured nested /// exception stack to `std::cerr`. /// /// @default false /// bool printErrorStackToStdErr = defaults::printErrorStackToStdErr; // ------------------------------------------------------------------------- // ProductTimeSpec policies // ------------------------------------------------------------------------- /// /// @brief Allow a missing source time increment to be accepted. /// /// This option only enables the compatibility policy. ProductTimeSpec still /// decides whether the current product shape is eligible for this /// interpretation. /// /// In particular, enabling this option does not make every missing increment /// valid. Non-`ml` from-start single-loop products remain ineligible and /// still require an explicit source increment. /// /// @default false /// bool allowDefaultTimeIncrement = defaults::allowDefaultTimeIncrement; /// /// @brief Allow a zero-length from-start statistical window. /// /// When disabled, a from-start product whose resolved step is zero is /// rejected. When enabled, ProductTimeSpec may construct the explicitly /// supported zero-length from-start statistical representation. /// /// @default false /// bool allowZeroLengthFsWindow = defaults::allowZeroLengthFsWindow; /// @brief Allow extended set of operations for zero-length from-start window. /// /// When disabled, only accumulation is allowed at step zero. When enabled, /// ProductTimeSpec may construct the explicitly supported zero-length from-start /// statistical representation for average, minimum, and maximum operations. /// /// @default false /// bool allowExtendedSetOfOperationsForZeroLengthFsWindow = defaults::allowExtendedSetOfOperationsForZeroLengthFsWindow; /// /// @brief Allow positive integer-hour `timespan` values not enumerated by /// the active MARS language. /// /// Integer-valued `timespan` is interpreted as hours. By default, the value /// must belong to the language-defined supported set. Enabling this option /// allows any strictly positive integer number of hours, subject to the /// remaining ProductTimeSpec checks. /// /// @default false /// bool allowNonEnumeratedPositiveIntegerTimespanHours = defaults::allowNonEnumeratedPositiveIntegerTimespanHours; /// /// @brief Allow explicit time increments that are semantically redundant. /// /// This compatibility policy is used for cases where the product semantics /// do not require an increment, but an input dictionary nevertheless /// provides one. ProductTimeSpec remains responsible for deciding which /// product shapes may ignore such a redundant value. /// /// @default false /// bool allowRedundantTimeIncrement = defaults::allowRedundantTimeIncrement; /// /// @brief Allow a missing `timespan` to represent an instant product. /// /// The normative instant representation uses `timespan="none"`. Enabling /// this option accepts the compatibility representation in which both /// `timespan` and `stattype` are absent. /// /// @default false /// bool allowMissingTimespanForInstantProduct = defaults::allowMissingTimespanForInstantProduct; /// /// @brief Allow a missing `timespan` to represent selected statistical products. /// /// The normative statistical representations keep an explicit `timespan` /// source. Enabling this option accepts the compatibility representation in /// which `timespan` is missing for eligible statistical shapes whose /// canonical window construction does not need the source `timespan` value. /// /// ProductTimeSpec remains responsible for deciding which statistical shape /// families may use this compatibility policy. /// /// @default false /// bool allowMissingTimespanForStatisticalProduct = defaults::allowMissingTimespanForStatisticalProduct; }; } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/mars2grib/api/readOptionsFromLocalConfiguration.h0000664000175000017500000000763515246725757027030 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromLocalConfiguration.h /// @brief Parse Mars2Grib Options from eckit::LocalConfiguration. /// /// This file is intended to be included into `Mars2Grib.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::detail { inline bool readBoolOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isBoolean(std::string{key})) { throw utils::exceptions::Mars2GribDictException( "Option `" + std::string(key) + "` must be a bool in eckit::LocalConfiguration", Here()); } return conf.getBool(std::string{key}); } inline std::string readStringOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isString(std::string{key})) { throw utils::exceptions::Mars2GribDictException( "Option `" + std::string(key) + "` must be a string in eckit::LocalConfiguration", Here()); } return conf.getString(std::string{key}); } inline Options readOptions(const eckit::LocalConfiguration& conf) { Options opts; if (conf.has("applyChecks")) { opts.applyChecks = readBoolOption(conf, "applyChecks"); } if (conf.has("enableOverride")) { opts.enableOverride = readBoolOption(conf, "enableOverride"); } if (conf.has("enableBitsPerValueCompression")) { opts.enableBitsPerValueCompression = readBoolOption(conf, "enableBitsPerValueCompression"); } if (conf.has("normalizeMars")) { opts.normalizeMars = readBoolOption(conf, "normalizeMars"); } if (conf.has("normalizeMisc")) { opts.normalizeMisc = readBoolOption(conf, "normalizeMisc"); } if (conf.has("fixMarsGrid")) { opts.fixMarsGrid = readBoolOption(conf, "fixMarsGrid"); } if (conf.has("skipSection3")) { opts.skipSection3 = readBoolOption(conf, "skipSection3"); } if (conf.has("saveErrorStack")) { opts.saveErrorStack = readBoolOption(conf, "saveErrorStack"); } if (conf.has("errorStackPath")) { opts.errorStackPath = readStringOption(conf, "errorStackPath"); } if (conf.has("printErrorStackToStdErr")) { opts.printErrorStackToStdErr = readBoolOption(conf, "printErrorStackToStdErr"); } if (conf.has("allowDefaultTimeIncrement")) { opts.allowDefaultTimeIncrement = readBoolOption(conf, "allowDefaultTimeIncrement"); } if (conf.has("allowZeroLengthFsWindow")) { opts.allowZeroLengthFsWindow = readBoolOption(conf, "allowZeroLengthFsWindow"); } if (conf.has("allowExtendedSetOfOperationsForZeroLengthFsWindow")) { opts.allowExtendedSetOfOperationsForZeroLengthFsWindow = readBoolOption(conf, "allowExtendedSetOfOperationsForZeroLengthFsWindow"); } if (conf.has("allowNonEnumeratedPositiveIntegerTimespanHours")) { opts.allowNonEnumeratedPositiveIntegerTimespanHours = readBoolOption(conf, "allowNonEnumeratedPositiveIntegerTimespanHours"); } if (conf.has("allowRedundantTimeIncrement")) { opts.allowRedundantTimeIncrement = readBoolOption(conf, "allowRedundantTimeIncrement"); } if (conf.has("allowMissingTimespanForInstantProduct")) { opts.allowMissingTimespanForInstantProduct = readBoolOption(conf, "allowMissingTimespanForInstantProduct"); } if (conf.has("allowMissingTimespanForStatisticalProduct")) { opts.allowMissingTimespanForStatisticalProduct = readBoolOption(conf, "allowMissingTimespanForStatisticalProduct"); } return opts; } } // namespace metkit::mars2grib::detail metkit-1.20.2/src/metkit/mars2grib/api/Mars2GribTestCaseGenerator.cc0000664000175000017500000002332615246725757025436 0ustar alastairalastair/* * (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. */ #include "Mars2GribTestCaseGenerator.h" #include #include #include "metkit/mars2grib/CoreOperations.h" #include "metkit/mars2grib/api/Mars2GribApiErrorHandling.h" #include "metkit/mars2grib/testing-utils/RecordingDictionary.h" #include "metkit/mars2grib/testing-utils/dictionary_traits/dictaccess_recording_dictionary.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h" #include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib { namespace exceptions = metkit::mars2grib::utils::exceptions; namespace { [[noreturn]] void throwInvalidOptionType(std::string_view key, const eckit::Value& value, std::string_view expected) { throw exceptions::Mars2GribDictException("Option `" + std::string(key) + "` has value type `" + value.typeName() + "`; expected " + std::string(expected), Here()); } bool readBool(const eckit::Value& value, std::string_view key) { if (!value.isBool()) { throwInvalidOptionType(key, value, "bool"); } return value.as(); } std::string readString(const eckit::Value& value, std::string_view key) { if (!value.isString()) { throwInvalidOptionType(key, value, "string"); } return value.as(); } void applyOption(Options& opts, std::string_view key, const eckit::Value& value) { if (key == "applyChecks") { opts.applyChecks = readBool(value, key); return; } if (key == "enableOverride") { opts.enableOverride = readBool(value, key); return; } if (key == "enableBitsPerValueCompression") { opts.enableBitsPerValueCompression = readBool(value, key); return; } if (key == "normalizeMars") { opts.normalizeMars = readBool(value, key); return; } if (key == "normalizeMisc") { opts.normalizeMisc = readBool(value, key); return; } if (key == "fixMarsGrid") { opts.fixMarsGrid = readBool(value, key); return; } if (key == "skipSection3") { opts.skipSection3 = readBool(value, key); return; } if (key == "saveErrorStack") { opts.saveErrorStack = readBool(value, key); return; } if (key == "errorStackPath") { opts.errorStackPath = readString(value, key); return; } if (key == "printErrorStackToStdErr") { opts.printErrorStackToStdErr = readBool(value, key); return; } if (key == "allowDefaultTimeIncrement") { opts.allowDefaultTimeIncrement = readBool(value, key); return; } if (key == "allowZeroLengthFsWindow") { opts.allowZeroLengthFsWindow = readBool(value, key); return; } if (key == "allowExtendedSetOfOperationsForZeroLengthFsWindow") { opts.allowExtendedSetOfOperationsForZeroLengthFsWindow = readBool(value, key); return; } if (key == "allowNonEnumeratedPositiveIntegerTimespanHours") { opts.allowNonEnumeratedPositiveIntegerTimespanHours = readBool(value, key); return; } if (key == "allowRedundantTimeIncrement") { opts.allowRedundantTimeIncrement = readBool(value, key); return; } if (key == "allowMissingTimespanForInstantProduct") { opts.allowMissingTimespanForInstantProduct = readBool(value, key); return; } if (key == "allowMissingTimespanForStatisticalProduct") { opts.allowMissingTimespanForStatisticalProduct = readBool(value, key); return; } throw exceptions::Mars2GribDictException("Unknown Mars2Grib option `" + std::string(key) + "`", Here()); } Options readOptions(const Mars2GribTestCaseGenerator::OptionList entries) { Options opts; std::unordered_set seen; seen.reserve(entries.size()); for (const auto& [key, value] : entries) { if (!seen.insert(key).second) { throw exceptions::Mars2GribDictException("Duplicate Mars2Grib option `" + key + "`", Here()); } applyOption(opts, key, value); } return opts; } bool readBoolOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isBoolean(std::string{key})) { throw exceptions::Mars2GribDictException( "Option `" + std::string(key) + "` must be a bool in eckit::LocalConfiguration", Here()); } return conf.getBool(std::string{key}); } std::string readStringOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isString(std::string{key})) { throw exceptions::Mars2GribDictException( "Option `" + std::string(key) + "` must be a string in eckit::LocalConfiguration", Here()); } return conf.getString(std::string{key}); } Options readOptions(const eckit::LocalConfiguration& conf) { Options opts; if (conf.has("applyChecks")) { opts.applyChecks = readBoolOption(conf, "applyChecks"); } if (conf.has("enableOverride")) { opts.enableOverride = readBoolOption(conf, "enableOverride"); } if (conf.has("enableBitsPerValueCompression")) { opts.enableBitsPerValueCompression = readBoolOption(conf, "enableBitsPerValueCompression"); } if (conf.has("normalizeMars")) { opts.normalizeMars = readBoolOption(conf, "normalizeMars"); } if (conf.has("normalizeMisc")) { opts.normalizeMisc = readBoolOption(conf, "normalizeMisc"); } if (conf.has("fixMarsGrid")) { opts.fixMarsGrid = readBoolOption(conf, "fixMarsGrid"); } if (conf.has("skipSection3")) { opts.skipSection3 = readBoolOption(conf, "skipSection3"); } if (conf.has("saveErrorStack")) { opts.saveErrorStack = readBoolOption(conf, "saveErrorStack"); } if (conf.has("errorStackPath")) { opts.errorStackPath = readStringOption(conf, "errorStackPath"); } if (conf.has("printErrorStackToStdErr")) { opts.printErrorStackToStdErr = readBoolOption(conf, "printErrorStackToStdErr"); } if (conf.has("allowDefaultTimeIncrement")) { opts.allowDefaultTimeIncrement = readBoolOption(conf, "allowDefaultTimeIncrement"); } if (conf.has("allowZeroLengthFsWindow")) { opts.allowZeroLengthFsWindow = readBoolOption(conf, "allowZeroLengthFsWindow"); } if (conf.has("allowExtendedSetOfOperationsForZeroLengthFsWindow")) { opts.allowExtendedSetOfOperationsForZeroLengthFsWindow = readBoolOption(conf, "allowExtendedSetOfOperationsForZeroLengthFsWindow"); } if (conf.has("allowNonEnumeratedPositiveIntegerTimespanHours")) { opts.allowNonEnumeratedPositiveIntegerTimespanHours = readBoolOption(conf, "allowNonEnumeratedPositiveIntegerTimespanHours"); } if (conf.has("allowRedundantTimeIncrement")) { opts.allowRedundantTimeIncrement = readBoolOption(conf, "allowRedundantTimeIncrement"); } if (conf.has("allowMissingTimespanForInstantProduct")) { opts.allowMissingTimespanForInstantProduct = readBoolOption(conf, "allowMissingTimespanForInstantProduct"); } if (conf.has("allowMissingTimespanForStatisticalProduct")) { opts.allowMissingTimespanForStatisticalProduct = readBoolOption(conf, "allowMissingTimespanForStatisticalProduct"); } return opts; } std::string makeTestCaseJson(const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc, const Options& opts, const testing_utils::RecordingDictionary& out) { using metkit::mars2grib::utils::dict_traits::dict_to_json; std::string json; json += "{"; json += "\"mars\":"; json += dict_to_json(mars); json += ",\"misc\":"; json += dict_to_json(misc); json += ",\"opt\":"; json += dict_to_json(opts); json += ",\"out\":"; json += out.to_json(); json += "}"; return json; } } // namespace Mars2GribTestCaseGenerator::Mars2GribTestCaseGenerator() : opts_{} {} Mars2GribTestCaseGenerator::Mars2GribTestCaseGenerator(const Options& opts) : opts_{opts} {} Mars2GribTestCaseGenerator::Mars2GribTestCaseGenerator(const eckit::LocalConfiguration& opts) : opts_{readOptions(opts)} {} Mars2GribTestCaseGenerator::Mars2GribTestCaseGenerator(OptionList opts) : opts_{readOptions(opts)} {} std::string Mars2GribTestCaseGenerator::generate(const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { return exceptions::withMars2GribApiErrorHandling( "Mars2GribTestCaseGenerator::generate", opts_, [&]() { auto out = CoreOperations::encodeHeaderWithNormalization( mars, misc, opts_, language_); return makeTestCaseJson(mars, misc, opts_, *out); }, Here()); } std::string Mars2GribTestCaseGenerator::generate(const eckit::LocalConfiguration& mars) { const eckit::LocalConfiguration misc{}; return generate(mars, misc); } } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/mars2grib/api/Mars2Grib.h0000664000175000017500000005163415246725757022000 0ustar alastairalastair/* * (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 Mars2Grib.h /// @brief High-level API for encoding MARS fields into GRIB messages. /// /// This header defines the **public Mars2Grib encoding API**, providing a /// user-facing interface to convert MARS-style metadata and field values /// into GRIB messages. /// /// The `Mars2Grib` class acts as a **stateless encoder facade**: /// - it validates and interprets user-provided metadata /// - it constructs a GRIB header according to MARS conventions /// - it encodes the provided field values /// - it returns a fully-formed GRIB handle /// /// This API is designed for: /// - application developers /// - workflow orchestration layers /// - bindings (Fortran, Python, etc.) /// /// It intentionally hides all internal concepts such as planners, /// deductions, sections, or encoding strategies. /// /// --- /// /// ## Conceptual overview /// /// Encoding is driven by three inputs: /// /// - **MARS dictionary** (`mars`) /// Describes the field semantics (e.g. parameter, level, step, date). /// /// - **Misc dictionary** (`misc`, optional) /// Provides auxiliary metadata not strictly part of the MARS request /// (e.g. grid geometry, packing hints, implementation options). /// /// - **Values** /// The numerical field values to be encoded. /// /// The result of an encoding operation is a /// `metkit::codes::CodesHandle`, which can be: /// - written to file /// - passed to ecCodes /// - transferred to downstream systems /// /// --- /// /// ## Error handling /// /// - All encoding failures are reported via C++ exceptions. /// - Errors are fail-fast and no partial GRIB messages are produced. /// - On failure, no `CodesHandle` is returned. /// /// --- /// /// ## Thread safety /// /// - A `Mars2Grib` instance is safe to use from a single thread. /// - Concurrent use from multiple threads requires separate instances. /// /// @ingroup mars2grib_api /// #pragma once // System includes #include #include #include #include #include // eckit #include "eckit/config/LocalConfiguration.h" #include "eckit/value/Value.h" // ecCodes API wrapper #include "metkit/codes/api/CodesAPI.h" // Codes wrapper types #include "metkit/codes/api/CodesTypes.h" // mars2grib public options #include "metkit/mars2grib/api/Options.h" namespace metkit::mars2grib { /// --- /// /// ## Transitional staged API /// /// A temporary staged-encoding interface is also exposed through /// `prepare()` and `finaliseEncoding()`. /// /// This interface exists only for short-term benchmarking and migration /// purposes and is not intended for external use. It may be changed or /// removed without notice. /// /// /// @brief High-level encoder for converting MARS fields to GRIB. /// /// The `Mars2Grib` class provides a **single-entry-point API** /// for encoding numerical field data together with MARS metadata /// into a GRIB message. /// /// A `Mars2Grib` object encapsulates a fixed set of encoding options /// and can be reused to encode multiple fields with the same /// configuration. /// /// ### Lifetime and ownership /// /// - `Mars2Grib` does not own any external resources. /// - Each call to `encode()` returns a new `CodesHandle` owned /// by the caller. /// /// ### Copy semantics /// /// Copy and move operations are explicitly disabled to avoid /// accidental sharing of internal state. /// class Mars2Grib { public: using OptionEntry = std::pair; using OptionList = std::initializer_list; template using Span = metkit::codes::Span; /// /// @brief Construct a Mars2Grib encoder with default options. /// /// Default options correspond to standard mars2grib behavior. /// Mars2Grib(); /// /// @brief Construct a Mars2Grib encoder with explicit options. /// /// @param[in] opts /// Encoding options controlling behavior such as validation, /// logging, or feature toggles. /// explicit Mars2Grib(const Options& opts); /// /// @brief Construct a Mars2Grib encoder from a configuration object. /// /// This constructor allows options to be provided via an /// `eckit::LocalConfiguration`, typically originating from YAML /// or JSON configuration files. /// /// @param[in] opts /// Configuration object describing encoder options. /// explicit Mars2Grib(const eckit::LocalConfiguration& opts); /// /// @brief Construct a Mars2Grib encoder from inline option entries. /// /// This constructor supports compact inline configuration such as: /// /// @code /// Mars2Grib{{"saveErrorStack", false}, {"errorStackPath", "./errors"}} /// @endcode /// /// @param[in] opts /// Initializer-list option entries. /// explicit Mars2Grib(OptionList opts); Mars2Grib(const Mars2Grib&) = delete; Mars2Grib(Mars2Grib&&) = delete; Mars2Grib operator=(const Mars2Grib&) = delete; Mars2Grib operator=(Mars2Grib&&) = delete; ~Mars2Grib() = default; // ------------------------------------------------------------------ // Encoding interface — std::vector based // ------------------------------------------------------------------ /// /// @brief Encode a field into a GRIB message. /// /// @param[in] values /// Field values to encode as double. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Encode a field into a GRIB message. /// /// @param[in] values /// Field values to encode as float. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Encode a field using only a MARS dictionary. /// /// This overload omits the `misc` dictionary. /// /// @param[in] values /// Field values to encode as double. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const std::vector& values, const eckit::LocalConfiguration& mars); /// /// @brief Encode a field using only a MARS dictionary. /// /// This overload omits the `misc` dictionary. /// /// @param[in] values /// Field values to encode as float. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const std::vector& values, const eckit::LocalConfiguration& mars); // ------------------------------------------------------------------ // Encoding interface — raw pointer based // ------------------------------------------------------------------ /// /// @brief Encode a field from a raw value buffer. /// /// @param[in] values /// Pointer to the field values as double. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const double* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Encode a field from a raw value buffer. /// /// @param[in] values /// Pointer to the field values as float. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const float* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Encode a field from a raw value buffer. /// /// @param[in] values /// Pointer to the field values as double. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const double* values, size_t length, const eckit::LocalConfiguration& mars); /// /// @brief Encode a field from a raw value buffer. /// /// @param[in] values /// Pointer to the field values as float. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// std::unique_ptr encode(const float* values, size_t length, const eckit::LocalConfiguration& mars); /// /// @brief Opaque cache object for staged GRIB encoding. /// /// This type represents a precomputed encoding cache produced by /// `prepare()` and later consumed by `finaliseEncoding()`. /// /// The cache internalizes the expensive metadata-specialization phase /// and stores immutable state that can be reused across multiple /// finalization calls. /// /// The concrete representation of this type is intentionally hidden /// from API users. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// struct CacheEntry; /// /// @brief Custom deleter for opaque cache entries. /// /// This deleter is paired with `CacheEntryPtr` to allow ownership of /// an incomplete, opaque `CacheEntry` type in the public interface. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// struct CacheEntryDeleter { /// /// @brief Destroy an opaque cache entry. /// /// @param[in] cache /// Pointer to the cache entry to destroy. /// void operator()(const CacheEntry*) const; }; /// /// @brief Owning smart pointer to an opaque staged-encoding cache. /// /// This alias represents unique ownership of a `CacheEntry` /// produced by `prepare()`. /// /// The pointed object is immutable and may be reused across /// multiple `finaliseEncoding()` calls. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// using CacheEntryPtr = std::unique_ptr; /// /// @brief Prepare a reusable staged-encoding cache. /// /// This function performs the metadata-specialization phase of the /// encoding pipeline and returns an opaque cache object that can be /// reused in subsequent calls to `finaliseEncoding()`. /// /// The cache is derived from: /// - the MARS dictionary, /// - the auxiliary metadata dictionary, /// - the current `Mars2Grib` options, /// - the active language definition. /// /// This operation is intended for workflows where the structural /// encoding setup is reused multiple times with different field values /// and/or repeated finalization requests. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer owning an opaque immutable cache entry. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// CacheEntryPtr prepare(const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This function completes the encoding pipeline using a cache /// previously produced by `prepare()`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Field values to encode as double. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr finaliseEncoding(const CacheEntryPtr& cacheEntry, const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This function completes the encoding pipeline using a cache /// previously produced by `prepare()`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Pointer to the field values as double. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr finaliseEncoding(const CacheEntryPtr& cacheEntry, const double* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This overload accepts field values as `float`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Field values to encode as float. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr finaliseEncoding(const CacheEntryPtr& cacheEntry, const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); /// /// @brief Finalize a GRIB encoding from a previously prepared cache. /// /// This overload accepts field values as `float`. /// /// The cache supplies the pre-specialized, reusable encoding state, /// while this function injects the provided field values and applies /// any remaining dynamic metadata handling required to produce a final /// GRIB message. /// /// The cache is not consumed by this operation and may be reused /// for subsequent calls. /// /// @param[in] cacheEntry /// Opaque staged-encoding cache previously produced by `prepare()`. /// /// @param[in] values /// Pointer to the field values as float. /// /// @param[in] length /// Number of values in the buffer. /// /// @param[in] mars /// MARS dictionary describing the field metadata. /// /// @param[in] misc /// Auxiliary metadata dictionary. /// /// @return /// A unique pointer to a GRIB handle containing the encoded message. /// /// @note /// This staged cache API is temporary and not intended for public use. /// It is exposed only for transitional benchmarking and comparison /// against a legacy implementation. It may be changed or removed /// without notice. /// std::unique_ptr finaliseEncoding(const CacheEntryPtr& cacheEntry, const float* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc); private: const eckit::Value language_; const Options opts_; }; } // namespace metkit::mars2grib metkit-1.20.2/src/metkit/metkit_version.h.in0000664000175000017500000000111015246725757021176 0ustar alastairalastair#ifndef metkit_version_h #define metkit_version_h #define metkit_VERSION_STR "@metkit_VERSION_STR@" #define metkit_VERSION "@metkit_VERSION@" #define metkit_VERSION_MAJOR @metkit_VERSION_MAJOR@ #define metkit_VERSION_MINOR @metkit_VERSION_MINOR@ #define metkit_VERSION_PATCH @metkit_VERSION_PATCH@ #define metkit_GIT_SHA1 "@metkit_GIT_SHA1@" #ifdef __cplusplus extern "C" { #endif const char * metkit_version(); unsigned int metkit_version_int(); const char * metkit_version_str(); const char * metkit_git_sha1(); #ifdef __cplusplus } #endif #endif // metkit_version_h metkit-1.20.2/src/metkit/pointdb/0000775000175000017500000000000015246725757017024 5ustar alastairalastairmetkit-1.20.2/src/metkit/pointdb/GribFieldInfo.cc0000664000175000017500000001201415246725757021774 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #include "metkit/pointdb/GribFieldInfo.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/pointdb/GribDataSource.h" #include using namespace eckit; extern "C" { unsigned long grib_decode_unsigned_long(const unsigned char* p, long* offset, int bits); double grib_power(long s, long n); } namespace metkit { namespace pointdb { static Mutex mutex; #define MISSING 9999 static int bits[65536] = { #include "metkit/pointdb/bits.h" }; static uint64_t masks[64] = { #include "metkit/pointdb/masks.h" }; static inline int count_bits(unsigned long long n) { return bits[n & 0xffffu] + bits[(n >> 16) & 0xffffu] + bits[(n >> 32) & 0xffffu] + bits[(n >> 48) & 0xffffu]; } GribFieldInfo::GribFieldInfo() : referenceValue_(0), binaryScaleFactor_(0), decimalScaleFactor_(0), bitsPerValue_(0), offsetBeforeData_(0), offsetBeforeBitmap_(0), numberOfValues_(0), numberOfDataPoints_(0), sphericalHarmonics_(0) {} void GribFieldInfo::update(const codes::CodesHandle& h) { binaryScaleFactor_ = h.getLong("binaryScaleFactor"); decimalScaleFactor_ = h.getLong("decimalScaleFactor"); bitsPerValue_ = h.getLong("bitsPerValue"); referenceValue_ = h.getDouble("referenceValue"); offsetBeforeData_ = h.getLong("offsetBeforeData"); numberOfDataPoints_ = h.getLong("numberOfDataPoints"); numberOfValues_ = h.getLong("numberOfValues"); sphericalHarmonics_ = h.getLong("sphericalHarmonics"); if (h.getLong("bitmapPresent")) offsetBeforeBitmap_ = h.getLong("offsetBeforeBitmap"); else offsetBeforeBitmap_ = 0; if (!sphericalHarmonics_) geographyHash_ = h.getString("md5GridSection"); } void GribFieldInfo::print(std::ostream& s) const { s << "GribFieldInfo["; s << "binaryScaleFactor=" << binaryScaleFactor_; s << ",decimalScaleFactor=" << decimalScaleFactor_; s << ",bitsPerValue=" << bitsPerValue_; s << ",referenceValue=" << referenceValue_; s << ",offsetBeforeData=" << offsetBeforeData_; s << ",numberOfDataPoints=" << numberOfDataPoints_; s << ",numberOfValues=" << numberOfValues_; s << ",offsetBeforeBitmap=" << offsetBeforeBitmap_; s << ",sphericalHarmonics=" << sphericalHarmonics_; s << ",geographyHash=" << geographyHash_; s << "]"; } double GribFieldInfo::interpolate(GribDataSource& f, double& lat, double& lon) const { NOTIMP; } double GribFieldInfo::value(const GribDataSource& f, size_t index) const { unsigned char buf[8]; if (bitsPerValue_ == 0) return referenceValue_; ASSERT(!sphericalHarmonics_); if (offsetBeforeBitmap_) { ASSERT(index < numberOfDataPoints_); Log::info() << "offsetBeforeBitmap_ " << offsetBeforeBitmap_ << ",index " << index << std::endl; Offset offset(offsetBeforeBitmap_); ASSERT(f.seek(offset) == offset); size_t count = 0; uint64_t n; ASSERT(sizeof(n) == 8); size_t skip = index / (sizeof(n) * 8); Log::info() << "Read " << skip << " words" << std::endl; for (size_t i = 0; i < skip; ++i) { ASSERT(f.read(&n, sizeof(n)) == sizeof(n)); count += count_bits(n); } Log::info() << "Count " << count << std::endl; size_t pos = index % (sizeof(n) * 8); Log::info() << "Read last bits, " << pos << std::endl; ASSERT(f.read(&n, sizeof(n)) == sizeof(n)); Log::info() << "Read last bits, " << pos << ", before mask " << std::bitset<64>(n) << std::endl; n &= masks[pos]; Log::info() << "Read last bits, " << pos << ", after mask " << std::bitset<64>(n) << ", bits=" << count_bits(n) << std::endl; count += count_bits(n); Log::info() << "Count " << count << std::endl; n = (n >> (64 - pos)) & 1; if (!n) { return MISSING; } ASSERT(count); index = count - 1; } Log::info() << "index " << index << ", numberOfValues " << numberOfValues_ << std::endl; ASSERT(index < numberOfValues_); { Offset offset = off_t(offsetBeforeData_) + off_t(index * bitsPerValue_ / 8); ASSERT(f.seek(offset) == offset); long len = (bitsPerValue_ + 7) / 8; ASSERT(f.read(buf, len) == len); } long bitp = (index * bitsPerValue_) % 8; unsigned long p = grib_decode_unsigned_long(buf, &bitp, bitsPerValue_); // TODO: store the precomputed values double s = grib_power(binaryScaleFactor_, 2); double d = grib_power(-decimalScaleFactor_, 10); double v = (double)(((p * s) + referenceValue_) * d); return v; } } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/pointdb/GribHandleDataSource.h0000664000175000017500000000311315246725757023145 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #ifndef metkit_GribHandleDataSource_H #define metkit_GribHandleDataSource_H #include "metkit/pointdb/GribDataSource.h" #include "metkit/pointdb/GribFieldInfo.h" namespace eckit { class DataHandle; } namespace metkit { namespace pointdb { class GribHandleDataSource : public GribDataSource { public: GribHandleDataSource(const eckit::PathName&, const eckit::Offset& = 0); GribHandleDataSource(eckit::DataHandle&, const eckit::Offset& = 0); GribHandleDataSource(eckit::DataHandle*, const eckit::Offset& = 0); ~GribHandleDataSource(); private: mutable eckit::DataHandle* handle_; bool ownsHandle_; mutable bool opened_; mutable GribFieldInfo info_; eckit::Offset offset_; virtual eckit::Offset seek(const eckit::Offset&) const override; virtual long read(void*, long) const override; virtual const GribFieldInfo& info() const override; virtual void print(std::ostream& s) const override; virtual const std::map& request() const override; virtual std::string groupKey() const override; virtual std::string sortKey() const override; void open() const; }; } // namespace pointdb } // namespace metkit #endif metkit-1.20.2/src/metkit/pointdb/DataSource.h0000664000175000017500000000366315246725757021237 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #ifndef metkit_DataSource_H #define metkit_DataSource_H #include #include #include "eckit/memory/NonCopyable.h" namespace eckit { class JSON; class Value; } // namespace eckit namespace metkit { class MarsRequest; namespace pointdb { class DataSource; struct PointResult { double value_; double lat_; double lon_; const DataSource* source_; // Warning, the source must have a longer life time as the result void print(std::ostream& s) const; friend std::ostream& operator<<(std::ostream& s, const PointResult& f) { f.print(s); return s; } }; class DataSourceHandler { public: virtual void handle(DataSource*) = 0; }; class DataSource : public eckit::NonCopyable { public: virtual ~DataSource(); virtual PointResult extract(double lat, double lon) const = 0; // Encode a MARS-like request representing the field virtual const std::map& request() const = 0; // A key to group source togther, e.g. sources poiting to the same file virtual std::string groupKey() const = 0; // A key to sort sources of the same group, e.g. offset in the file virtual std::string sortKey() const = 0; // Used to throw away requests in case of restarted transactions virtual size_t batch() const; private: virtual void print(std::ostream& s) const = 0; friend std::ostream& operator<<(std::ostream& s, const DataSource& f) { f.print(s); return s; } }; } // namespace pointdb } // namespace metkit #endif metkit-1.20.2/src/metkit/pointdb/masks.h0000664000175000017500000000325515246725757020320 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ 0x0, 0x8000000000000000, 0xc000000000000000, 0xe000000000000000, 0xf000000000000000, 0xf800000000000000, 0xfc00000000000000, 0xfe00000000000000, 0xff00000000000000, 0xff80000000000000, 0xffc0000000000000, 0xffe0000000000000, 0xfff0000000000000, 0xfff8000000000000, 0xfffc000000000000, 0xfffe000000000000, 0xffff000000000000, 0xffff800000000000, 0xffffc00000000000, 0xffffe00000000000, 0xfffff00000000000, 0xfffff80000000000, 0xfffffc0000000000, 0xfffffe0000000000, 0xffffff0000000000, 0xffffff8000000000, 0xffffffc000000000, 0xffffffe000000000, 0xfffffff000000000, 0xfffffff800000000, 0xfffffffc00000000, 0xfffffffe00000000, 0xffffffff00000000, 0xffffffff80000000, 0xffffffffc0000000, 0xffffffffe0000000, 0xfffffffff0000000, 0xfffffffff8000000, 0xfffffffffc000000, 0xfffffffffe000000, 0xffffffffff000000, 0xffffffffff800000, 0xffffffffffc00000, 0xffffffffffe00000, 0xfffffffffff00000, 0xfffffffffff80000, 0xfffffffffffc0000, 0xfffffffffffe0000, 0xffffffffffff0000, 0xffffffffffff8000, 0xffffffffffffc000, 0xffffffffffffe000, 0xfffffffffffff000, 0xfffffffffffff800, 0xfffffffffffffc00, 0xfffffffffffffe00, 0xffffffffffffff00, 0xffffffffffffff80, 0xffffffffffffffc0, 0xffffffffffffffe0, 0xfffffffffffffff0, 0xfffffffffffffff8, 0xfffffffffffffffc, 0xfffffffffffffffe, metkit-1.20.2/src/metkit/pointdb/FieldIndexer.cc0000664000175000017500000000125015246725757021673 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/pointdb/FieldIndexer.h" namespace metkit { namespace pointdb { FieldIndexer::~FieldIndexer() {} //---------------------------------------------------------------------------------------------------------------------- } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/pointdb/bits.h0000664000175000017500000065407115246725757020153 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 5, 6, 6, 7, 6, 7, 7, 8, 6, 7, 7, 8, 7, 8, 8, 9, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 6, 7, 7, 8, 7, 8, 8, 9, 7, 8, 8, 9, 8, 9, 9, 10, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 7, 8, 8, 9, 8, 9, 9, 10, 8, 9, 9, 10, 9, 10, 10, 11, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 8, 9, 9, 10, 9, 10, 10, 11, 9, 10, 10, 11, 10, 11, 11, 12, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 9, 10, 10, 11, 10, 11, 11, 12, 10, 11, 11, 12, 11, 12, 12, 13, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 10, 11, 11, 12, 11, 12, 12, 13, 11, 12, 12, 13, 12, 13, 13, 14, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 11, 12, 12, 13, 12, 13, 13, 14, 12, 13, 13, 14, 13, 14, 14, 15, 12, 13, 13, 14, 13, 14, 14, 15, 13, 14, 14, 15, 14, 15, 15, 16, metkit-1.20.2/src/metkit/pointdb/GribFieldInfo.h0000664000175000017500000000320015246725757021633 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #ifndef FieldInfoData_H #define FieldInfoData_H #include "metkit/codes/api/CodesAPI.h" #include "eckit/io/Length.h" #include "eckit/io/Offset.h" #include "eckit/types/FixedString.h" namespace eckit { class PathName; } namespace metkit::pointdb { class GribDataSource; class GribFieldInfo { public: GribFieldInfo(); std::string geographyHash() const { return geographyHash_; } bool ready() const { return numberOfValues_ > 0; } void update(const codes::CodesHandle& h); double value(const GribDataSource&, size_t index) const; bool useInterpolation() const { return sphericalHarmonics_ != 0; } double interpolate(GribDataSource&, double& lat, double& lon) const; private: double referenceValue_; long binaryScaleFactor_; long decimalScaleFactor_; unsigned long bitsPerValue_; unsigned long offsetBeforeData_; unsigned long offsetBeforeBitmap_; unsigned long numberOfValues_; unsigned long numberOfDataPoints_; long sphericalHarmonics_; eckit::FixedString<32> geographyHash_; void print(std::ostream&) const; friend std::ostream& operator<<(std::ostream& s, const GribFieldInfo& f) { f.print(s); return s; } }; } // namespace metkit::pointdb #endif metkit-1.20.2/src/metkit/pointdb/GribDataSource.h0000664000175000017500000000216715246725757022041 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #ifndef metkit_GribDataSource_H #define metkit_GribDataSource_H #include "metkit/pointdb/DataSource.h" #include "metkit/pointdb/PointIndex.h" namespace eckit { class Offset; } namespace metkit { namespace pointdb { class GribFieldInfo; class GribDataSource : public DataSource { public: virtual PointResult extract(double lat, double lon) const; private: virtual double value(size_t index) const; virtual std::string geographyHash() const; virtual eckit::Offset seek(const eckit::Offset&) const = 0; virtual long read(void*, long) const = 0; virtual const GribFieldInfo& info() const = 0; friend class GribFieldInfo; }; } // namespace pointdb } // namespace metkit #endif metkit-1.20.2/src/metkit/pointdb/FieldIndexer.h0000664000175000017500000000426615246725757021547 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 FieldIndex.h // Baudouin Raoult - ECMWF Oct 19 #ifndef metkit_FieldIndex_H #define metkit_FieldIndex_H #include #include namespace eckit { class Value; class JSON; } // namespace eckit namespace metkit { class MarsRequest; namespace pointdb { class DataSource; class DataSourceHandler; //---------------------------------------------------------------------------------------------------------------------- struct FieldIndexerStatus { size_t count_; size_t batch_; FieldIndexerStatus(size_t count, size_t batch) : count_(count), batch_(batch) {} }; class FieldIndexer { public: // -- Exceptions // None // -- Contructors // -- Destructor virtual ~FieldIndexer(); // -- Convertors // None // -- Operators // None // -- Methods virtual FieldIndexerStatus lookup(const eckit::Value&, DataSourceHandler&) const = 0; // -- Overridden methods // None // -- Class members // None // -- Class methods // None // Uncomment for persistent, remove otherwise protected: // -- Members // None // -- Methods virtual void print(std::ostream& s) const = 0; // -- Overridden methods // None // -- Class members // None // -- Class methods // None private: // No copy allowed // -- Members // -- Methods // None // -- Overridden methods // None // -- Class members // None // -- Class methods // None // -- Friends friend std::ostream& operator<<(std::ostream& s, const FieldIndexer& p) { p.print(s); return s; } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace pointdb } // namespace metkit #endif metkit-1.20.2/src/metkit/pointdb/GribHandleDataSource.cc0000664000175000017500000000716215246725757023313 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "eckit/io/Buffer.h" #include "eckit/io/DataHandle.h" #include "eckit/io/PooledHandle.h" #include "eckit/io/StdFile.h" #include "eckit/utils/MD5.h" #include "metkit/pointdb/GribFieldInfo.h" #include "metkit/pointdb/GribHandleDataSource.h" #include "metkit/pointdb/PointIndex.h" namespace metkit { namespace pointdb { GribHandleDataSource::GribHandleDataSource(const eckit::PathName& path, const eckit::Offset& offset) : handle_(path.fileHandle()), ownsHandle_(true), opened_(false), offset_(offset) {} GribHandleDataSource::GribHandleDataSource(eckit::DataHandle& handle, const eckit::Offset& offset) : handle_(&handle), ownsHandle_(false), opened_(false), offset_(offset) {} GribHandleDataSource::GribHandleDataSource(eckit::DataHandle* handle, const eckit::Offset& offset) : handle_(handle), ownsHandle_(true), opened_(false), offset_(offset) { ASSERT(handle_); } GribHandleDataSource::~GribHandleDataSource() { if (opened_) { handle_->close(); } if (ownsHandle_) { delete handle_; } } void GribHandleDataSource::open() const { if (!opened_) { handle_->openForRead(); opened_ = true; } } eckit::Offset GribHandleDataSource::seek(const eckit::Offset& offset) const { open(); eckit::Offset pos = handle_->seek(offset_ + offset); return (long long)pos - (long long)offset_; } long GribHandleDataSource::read(void* buffer, long len) const { open(); return handle_->read(buffer, len); } const GribFieldInfo& GribHandleDataSource::info() const { if (!info_.ready()) { eckit::MD5 md5; md5 << *handle_; md5 << static_cast(offset_); eckit::PathName cache = PointIndex::cachePath("grib-info", md5); if (cache.exists()) { eckit::StdFile f(cache); ASSERT(::fread(&info_, sizeof(info_), 1, f) == 1); f.close(); } else { open(); handle_->seek(offset_); auto codesHandle = codes::codesHandleFromStream( [&](uint8_t* buffer, int64_t len) -> int64_t { return handle_->read(buffer, len); }); ASSERT(codesHandle); info_.update(*codesHandle.get()); cache.dirName().mkdir(); eckit::StdFile f(cache, "w"); ASSERT(::fwrite(&info_, sizeof(info_), 1, f) == 1); f.close(); PointIndex::cache(*codesHandle.get()); } } return info_; } void GribHandleDataSource::print(std::ostream& s) const { s << "GribHandleDataSource[" << *handle_ << "]" << std::endl; } const std::map& GribHandleDataSource::request() const { NOTIMP; // Implement a grib2request like function } std::string GribHandleDataSource::groupKey() const { eckit::MD5 md5; md5 << *handle_; return md5; } std::string GribHandleDataSource::sortKey() const { std::ostringstream oss; oss << std::setfill('0') << std::setw(20) << offset_; return oss.str(); } //---------------------------------------------------------------------------------------------------------------------- } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/pointdb/GribDataSource.cc0000664000175000017500000000245015246725757022172 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #include "metkit/pointdb/GribDataSource.h" #include "metkit/pointdb/GribFieldInfo.h" namespace metkit { namespace pointdb { PointResult GribDataSource::extract(double lat, double lon) const { PointResult result; // ASSERT(!source.needInterpolation()); PointIndex& pi = PointIndex::lookUp(geographyHash()); PointIndex::NodeInfo n = pi.nearestNeighbour(lat, lon); result.lat_ = n.point().lat(); result.lon_ = n.point().lon(); result.value_ = value(n.point().payload_); result.source_ = this; return result; } double GribDataSource::value(size_t index) const { return info().value(*this, index); } std::string GribDataSource::geographyHash() const { return info().geographyHash(); } //---------------------------------------------------------------------------------------------------------------------- } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/pointdb/PointIndex.h0000664000175000017500000000555215246725757021265 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #ifndef PointIndex_H #define PointIndex_H #include #include "eckit/container/KDTree.h" #include "eckit/geometry/Point3.h" #include "metkit/codes/api/CodesAPI.h" namespace metkit { namespace pointdb { struct LLPoint2 : public eckit::geometry::Point3 { double lat_; double lon_; size_t payload_; double lat() const { return lat_; } double lon() const { return lon_; } size_t payload() const { return payload_; } const LLPoint2& point() const { return *this; } LLPoint2() : eckit::geometry::Point3() {} LLPoint2(double lat, double lon, size_t index) : eckit::geometry::Point3(), lat_(lat), lon_(lon) { // See http://en.wikipedia.org/wiki/Geodetic_system#From_geodetic_to_ECEF payload_ = index; double& X = x_[0]; double& Y = x_[1]; double& Z = x_[2]; double h = 0; // Altitude const double earthRadius = 6378137.0; double a = earthRadius; // 6378137.0 ; // WGS84 semi-major axis double e2 = 0; // 6.69437999014E-3; // WGS84 first numerical eccentricity sqared double phi = lat / 180.0 * M_PI; double lambda = lon / 180.0 * M_PI; double cos_phi = cos(phi); double sin_phi = sin(phi); double cos_lambda = cos(lambda); double sin_lambda = sin(lambda); double N_phi = a / sqrt(1 - e2 * sin_phi * sin_phi); X = (N_phi + h) * cos_phi * cos_lambda; Y = (N_phi + h) * cos_phi * sin_lambda; Z = (N_phi * (1 - e2) + h) * sin_phi; } friend std::ostream& operator<<(std::ostream& s, const LLPoint2& p) { s << '(' << p.lat_ << "," << p.lon_ << ' ' << p.payload_ << ')'; return s; } }; struct PointIndexTraits { using Point = LLPoint2; using Payload = size_t; }; class PointIndex { public: typedef eckit::KDTreeMapped Tree; typedef Tree::Point Point; typedef Tree::NodeInfo NodeInfo; NodeInfo nearestNeighbour(double lat, double lon); static PointIndex& lookUp(const std::string& md5); static std::string cache(const metkit::codes::CodesHandle& h); static eckit::PathName cachePath(const std::string& dir, const std::string& name); private: PointIndex(const eckit::PathName&, Tree* tree = 0); ~PointIndex(); eckit::PathName path_; std::unique_ptr tree_; std::map last_; eckit::Mutex mutex_; }; } // namespace pointdb } // namespace metkit #endif metkit-1.20.2/src/metkit/pointdb/DataSource.cc0000664000175000017500000000161515246725757021370 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ #include "metkit/pointdb/DataSource.h" #include "metkit/pointdb/PointIndex.h" namespace metkit { namespace pointdb { DataSource::~DataSource() {} size_t DataSource::batch() const { return 0; } void PointResult::print(std::ostream& s) const { s << "PointResult[lat=" << lat_ << ",lon=" << lon_ << ",value=" << value_ << "]"; } //---------------------------------------------------------------------------------------------------------------------- } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/pointdb/PointIndex.cc0000664000175000017500000001054215246725757021416 0ustar alastairalastair/* * (C) Copyright 1996-2013 ECMWF. * * 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. */ // #include "eckit/exception/Exceptions.h" // #include "eckit/thread/Mutex.h" // #include "eckit/os/Stat.h" // #include "eckit/log/Timer.h" #include "metkit/pointdb/PointIndex.h" #include "eckit/io/FileHandle.h" #include "eckit/thread/AutoLock.h" // #include "eckit/io/StdFile.h" #include "eckit/config/Resource.h" #include "metkit/codes/api/CodesAPI.h" // #include "eccodes.h" using namespace eckit; namespace metkit { namespace pointdb { static Mutex mutex; static std::set done_; static std::map cache_; eckit::PathName PointIndex::cachePath(const std::string& dir, const std::string& name) { static eckit::PathName pointdbCachePath = eckit::Resource("pointdbCachePath", "~/pointdb"); return pointdbCachePath / dir / name; } std::string PointIndex::cache(const metkit::codes::CodesHandle& h) { std::string md5 = h.getString("md5GridSection"); AutoLock lock(mutex); if (done_.find(md5) != done_.end()) { return md5; } eckit::PathName path = cachePath("grids", md5 + ".kdtree"); if (path.exists()) { done_.insert(md5); return md5; } path.dirName().mkdir(); size_t v = h.size("values"); std::vector p; p.reserve(v); size_t j = 0; for (auto data : h.values()) { while (data.longitude < 0) data.longitude += 360; while (data.longitude >= 360) data.longitude -= 360; p.push_back(Point(data.latitude, data.longitude, j)); j++; } PathName tmp = cachePath("grids", md5 + ".tmp"); tmp.unlink(); Tree* tree = new Tree(tmp, p.size(), 0); tree->build(p.begin(), p.end()); // PathName dump(std::string("/tmp/cache/pointdb/") + md5 + ".dump"); // StdFile f(dump, "w"); // codes_dump_content(h, f, "debug", 0, 0); // f.close(); // Write handle to file PathName grib = cachePath("grids", md5 + ".grib"); eckit::FileHandle fh(grib.localPath()); auto data = h.messageData(); fh.openForWrite(data.size()); fh.write(data.data(), data.size()); fh.close(); PathName::rename(tmp, path); cache_[md5] = new PointIndex(path, tree); done_.insert(md5); return md5; } PointIndex& PointIndex::lookUp(const std::string& md5) { AutoLock lock(mutex); std::map::iterator k = cache_.find(md5); if (k == cache_.end()) { eckit::PathName path = cachePath("grids", md5 + ".kdtree"); if (!path.exists()) { Log::warning() << path << " does not exists" << std::endl; PathName grib = cachePath("grids", md5 + ".grib"); if (grib.exists()) { Log::warning() << "Rebuilding index from " << grib << std::endl; auto codesHandle = codes::codesHandleFromFile(grib.localPath(), codes::Product::GRIB); ASSERT(cache(*codesHandle.get()) == md5); } } Log::warning() << "Loading " << path << std::endl; PointIndex* p = new PointIndex(path); cache_[md5] = p; return *p; } return *(*k).second; } PointIndex::PointIndex(const PathName& path, PointIndex::Tree* tree) : path_(path), tree_(tree) { if (!tree) { Log::info() << "Load tree " << path << std::endl; ASSERT(path.exists()); tree_.reset(new Tree(path, 0, 0)); } } PointIndex::~PointIndex() { // TODO } PointIndex::NodeInfo PointIndex::nearestNeighbour(double lat, double lon) { Point p(lat, lon, 0); std::map::iterator k; { AutoLock lock(mutex_); k = last_.find(p); if (k != last_.end()) return (*k).second; } NodeInfo n; { Timer timer("Find nearest"); n = tree_->nearestNeighbour(p); } { AutoLock lock(mutex_); if (last_.size() >= 4096) last_.clear(); last_[p] = n; } return n; } } // namespace pointdb } // namespace metkit metkit-1.20.2/src/metkit/metkit_config.h.in0000664000175000017500000000137315246725757020771 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #ifndef metkit_config_h #define metkit_config_h #include "metkit_ecbuild_config.h" /* generated by ecbuild */ /* variables */ #cmakedefine metkit_HAVE_GRIB #cmakedefine metkit_HAVE_NETCDF #cmakedefine metkit_HAVE_BUFR #cmakedefine metkit_HAVE_ODB #cmakedefine metkit_HAVE_FAIL_ON_CCSDS #cmakedefine metkit_HAVE_MARS2MARS /* packages */ #endif /* metkit_config_h */ metkit-1.20.2/src/metkit/grib2mars/0000775000175000017500000000000015246725757017255 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/utils/0000775000175000017500000000000015246725757020415 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/utils/paramMatcher.h0000664000175000017500000000257615246725757023204 0ustar alastairalastair/* * (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 paramMatcher.h /// @brief Helpers for compact parameter matching in rule tables. #pragma once namespace metkit::grib2mars::util::param_matcher { /// @brief Inclusive integer range used by parameter predicates. struct Range { int first; int last; /// @brief Check whether a value lies inside the range. bool contains(int x) const { return x >= first && x <= last; } }; /// @brief Construct an inclusive integer range. inline Range range(int first, int last) { return {first, last}; } /// @brief Match a value against a range argument. inline bool matchSingle(int x, const Range& arg) { return arg.contains(x); } /// @brief Match a value against a scalar argument. inline bool matchSingle(int x, int y) { return x == y; } /// @brief Match a value against any supplied argument. template bool matchAny(int value, T... arg) { return (matchSingle(value, arg) || ...); } } // namespace metkit::grib2mars::util::param_matcher metkit-1.20.2/src/metkit/grib2mars/utils/logUtils.h0000664000175000017500000000534015246725757022372 0ustar alastairalastair/// @file logUtils.h /// @brief Logging macros used by grib2mars. #pragma once #include "eckit/log/Log.h" #include "metkit/config/LibMetkit.h" #include "metkit/grib2mars/utils/generalUtils.h" /// @brief Log a validation or guard check. #define GRIB2MARS_LOG_CHECK(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-CHECK: " << msg << std::endl; \ } while (0) /// @brief Log a successful match. #define GRIB2MARS_LOG_MATCH(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-MATCH: " << msg << std::endl; \ } while (0) /// @brief Log a resolved value. #define GRIB2MARS_LOG_RESOLVE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-RESOLVE: " << msg << std::endl; \ } while (0) /// @brief Log an overridden value. #define GRIB2MARS_LOG_OVERRIDE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-OVERRIDE: " << msg << std::endl; \ } while (0) /// @brief Log a defaulted value. #define GRIB2MARS_LOG_DEFAULT(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-DEFAULT: " << msg << std::endl; \ } while (0) /// @brief Log a concept invocation. #define GRIB2MARS_LOG_CONCEPT(CONCEPTNAME) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " GRIB2MARS-CONCEPT:" \ << "[Concept " << std::string(CONCEPTNAME##Name) << "] " \ << "Op called: " \ << "Stage=" << Stage << ", " \ << "Section=" << Section << ", " \ << "Variant=" << std::string(CONCEPTNAME##TypeName()) << std::endl; \ } while (0) metkit-1.20.2/src/metkit/grib2mars/utils/generalUtils.h0000664000175000017500000000027715246725757023232 0ustar alastairalastair/// @file generalUtils.h /// @brief Small utility macros shared by grib2mars. #pragma once /// @brief Mark a code path as unreachable. #define grib2marsUnreachable() __builtin_unreachable() metkit-1.20.2/src/metkit/grib2mars/utils/grib2marsExceptions.h0000664000175000017500000001303515246725757024522 0ustar alastairalastair/* * (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 grib2marsExceptions.h /// @brief Unified exception hierarchy for the grib2mars framework. /// /// This header defines the complete exception model used across /// grib2mars, covering: /// /// - Generic infrastructure errors /// - Layer-specific failures (matcher, rules, validation, tables, deduction) /// - Concept execution failures (with contextual metadata) /// - Encoder failures (with serialized dictionary state) /// /// The hierarchy is designed with the following goals: /// /// - Strong contextual diagnostics /// - Support for nested exception propagation /// - Structured debug frame printing /// - Clear separation between backend and frontend layers /// /// All exceptions ultimately derive from `eckit::Exception`, /// ensuring compatibility with the broader ECMWF ecosystem. /// /// Nested exception support allows propagation chains to be /// printed in a structured stack-like format. /// /// @ingroup grib2mars_utils #pragma once // System includes #include #include #include #include // Project includes #include "eckit/exception/Exceptions.h" #include "metkit/config/LibMetkit.h" #include "metkit/grib2mars/utils/generalUtils.h" namespace metkit::grib2mars::utils::exceptions { /// @brief Base exception for grib2mars. /// /// This is the root exception type for most grib2mars failures. /// It: /// /// - Inherits from `eckit::Exception` /// - Supports nested exceptions via `std::nested_exception` /// - Provides structured frame printing /// /// Derived exceptions typically extend this class with /// additional contextual metadata. /// /// The `printFrame()` method is designed to be used by /// extended stack printers. class Grib2MarsGenericException : public eckit::Exception, public std::nested_exception { public: Grib2MarsGenericException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} virtual ~Grib2MarsGenericException() = default; virtual void printFrame(const std::string& pad, std::ostream& os) const { const auto& loc = location(); os << pad << "+ file: " << loc.file() << "\n" << pad << "+ function: " << loc.func() << "\n" << pad << "+ line: " << loc.line() << "\n" << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" << pad << "+ message: " << what() << "\n"; } }; /// @brief Exception raised in the rules layer. /// /// Used when evaluating rule-based logic fails. /// /// This class derives directly from `eckit::Exception` and /// supports nested exceptions. class Grib2MarsRulesException : public eckit::Exception, public std::nested_exception { public: Grib2MarsRulesException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} }; /// @brief Exception raised in the dictionary access layer. /// /// Used when dictionary validation or access fails. /// Inherits structured printing from the generic exception. class Grib2MarsDictException : public Grib2MarsGenericException { public: Grib2MarsDictException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Grib2MarsGenericException(reason, loc) {} }; inline constexpr int tabSize = 4; inline constexpr int lineSize = 120; inline std::string indent(std::size_t level) { return std::string(level * tabSize, ' '); } /// @brief Print structured exception stack with detailed frames. /// /// For each nested exception frame: /// /// - Prints file, function, line, and message (if available) /// - Prints additional metadata for specialized exceptions /// /// This function detects `Grib2MarsGenericException` /// via `dynamic_cast` and calls `printFrame()` /// to extract structured information. /// /// Nested exceptions are recursively printed. /// /// @param e Root exception /// @param level Indentation level /// @param frame Frame counter inline void printExtendedStack(const std::exception& e, std::ostream& os, std::size_t level = 0, std::size_t frame = 1) { const std::string pad = indent(level); os << pad << "+ " << std::string(lineSize, '=') << std::endl << pad << "+ frame " << frame << std::endl << pad << "+ " << std::string(lineSize, '-') << std::endl; if (const auto* me = dynamic_cast(&e)) { me->printFrame(pad, os); } else { os << pad << "+ message: " << e.what() << std::endl; } os << pad << "+ " << std::string(lineSize, '+') << std::endl; const auto* nested = dynamic_cast(&e); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nestedException) { printExtendedStack(nestedException, os, level + 1, frame + 1); } catch (...) { os << std::string(level + 1, ' ') << "[nested non-std exception]" << std::endl; } } } // namespace metkit::grib2mars::utils::exceptions metkit-1.20.2/src/metkit/grib2mars/utils/type_traits_name.h0000664000175000017500000001057515246725757024145 0ustar alastairalastair/* * (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 type_traits_name.h /// @brief Human-readable type names used in diagnostics. #pragma once #include #include #include #include #include #include #include "metkit/codes/api/CodesTypes.h" #include "metkit/grib2mars/utils/generalUtils.h" namespace metkit::grib2mars::utils { /// @brief Return a readable name for `T`. // ============================================================ // Primary template (deferred to fallback demangling) // ============================================================ template constexpr std::string_view type_name(); // ============================================================ // Base type specializations // ============================================================ template <> constexpr std::string_view type_name() { return "string"; } template <> constexpr std::string_view type_name() { return "bool"; } template <> constexpr std::string_view type_name() { return "char"; } template <> constexpr std::string_view type_name() { return "signed char"; } template <> constexpr std::string_view type_name() { return "unsigned char"; } template <> constexpr std::string_view type_name() { return "int"; } template <> constexpr std::string_view type_name() { return "long"; } template <> constexpr std::string_view type_name() { return "long long"; } template <> constexpr std::string_view type_name() { return "unsigned int"; } template <> constexpr std::string_view type_name() { return "unsigned long"; } template <> constexpr std::string_view type_name() { return "unsigned long long"; } template <> constexpr std::string_view type_name() { return "float"; } template <> constexpr std::string_view type_name() { return "double"; } template <> constexpr std::string_view type_name() { return "long double"; } // ============================================================ // std::vector specializations // ============================================================ template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } } // namespace metkit::grib2mars::utils metkit-1.20.2/src/metkit/grib2mars/utils/dictionary_traits/0000775000175000017500000000000015246725757024150 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/utils/dictionary_traits/dictaccess_eckit_configuration.h0000664000175000017500000005074415246725757032546 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/grib2mars/utils/generalUtils.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" #include "metkit/grib2mars/utils/type_traits_name.h" // ============================================================================ // Helper macros to reduce boilerplate (similar to LocalConfiguration version) // ============================================================================ // ============================================================== // GET_OR_THROW // ============================================================== #define G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOrThrow { \ \ static CTYPE get_or_throw(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ /* Check key exists */ \ if (!cfg.has(k)) { \ throw exceptions::Grib2MarsDictException("Missing key `"s + k + "` in dictionary type `"s + \ std::string(type_name()) + \ "`", \ Here()); \ } \ \ /* Check type */ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ throw exceptions::Grib2MarsDictException( \ "Key `"s + k + "` is not of expected type `"s + std::string(type_name()) + \ "` for dictionary type `"s + std::string(type_name()) + "`", \ Here()); \ } \ } \ catch (const exceptions::Grib2MarsGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Grib2MarsDictException( \ "Internal error while reading key `"s + k + "` as `" + std::string(type_name()) + \ "` from dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ grib2marsUnreachable(); \ } \ }; // ============================================================== // GET_OPT // ============================================================== #define G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOpt { \ static std::optional get_opt(const eckit::LocalConfiguration& cfg, \ std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ if (!cfg.has(k)) { \ return std::nullopt; \ } \ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ return std::nullopt; \ } \ } \ catch (...) { \ return std::nullopt; \ } \ grib2marsUnreachable(); \ } \ }; // ============================================================== // SET_OR_THROW // ============================================================== #define G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(CTYPE, SETFUNC) \ template <> \ struct DictSetOrIgnore { \ static void set_or_ignore(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (...) { \ } \ grib2marsUnreachable(); \ } \ }; // ============================================================== // SET_OR_IGNORE // ============================================================== #define G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(CTYPE, SETFUNC) \ template <> \ struct DictSetOrThrow { \ \ static void set_or_throw(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (const exceptions::Grib2MarsGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Grib2MarsDictException( \ "Unable to set key `"s + k + "` with type `"s + std::string(type_name()) + \ "` in dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ grib2marsUnreachable(); \ } \ }; namespace metkit::grib2mars::utils { // ----------------------------------------------------------------------------- // type_name specialisation // ----------------------------------------------------------------------------- template <> constexpr std::string_view type_name() { return "eckit::LocalConfiguration"; } template <> constexpr std::string_view type_name>() { return "vector"; } } // namespace metkit::grib2mars::utils namespace metkit::grib2mars::utils::dict_traits { using std::operator""s; namespace hacks { inline bool isIntegral(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegral(std::string{key}); } inline bool isFloatingPoint(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPoint(std::string{key}) || conf.isIntegral(std::string{key}); } inline bool isBoolean(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isBoolean(std::string{key}); } inline bool isString(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isString(std::string{key}); } inline bool isSubConfiguration(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfiguration(std::string{key}); } inline bool isIntegralList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegralList(std::string{key}); } inline bool isFloatingPointList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPointList(std::string{key}) || conf.isIntegralList(std::string{key}); } inline bool isStringList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isStringList(std::string{key}); } inline bool isSubConfigurationList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfigurationList(std::string{key}); } } // namespace hacks // ----------------------------------------------------------------------------- // to_json // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const eckit::LocalConfiguration& cfg) noexcept(true) { try { std::ostringstream os; os << cfg; return os.str(); } catch (...) { return "[to_json failed for eckit::LocalConfiguration]"; } } }; // ----------------------------------------------------------------------------- // DictCreateFromSample / Clone / NeedsChecks // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view name) { auto cfg = std::make_unique(); cfg->set("SampleName", std::string(name)); return cfg; } static std::unique_ptr clone_or_throw(const eckit::LocalConfiguration& cfg) { return std::make_unique(cfg); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { const std::string k{key}; try { return cfg.has(k); } catch (const exceptions::Grib2MarsGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Grib2MarsDictException( "Internal error while checking presence of key `"s + k + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } grib2marsUnreachable(); } }; // ============================================================ // eckit::LocalConfiguration specializations via macros // ============================================================ //------------------------------------------------------------------------------ // Scalar types //------------------------------------------------------------------------------ // bool G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(bool, isBoolean, getBool) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(bool, isBoolean, getBool) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(bool, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(bool, set) // int G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(int, isIntegral, getInt) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(int, isIntegral, getInt) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(int, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(int, set) // long G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(long, isIntegral, getLong) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(long, isIntegral, getLong) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(long, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(long, set) // float G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(float, isFloatingPoint, getFloat) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(float, isFloatingPoint, getFloat) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(float, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(float, set) // double G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(double, isFloatingPoint, getDouble) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(double, isFloatingPoint, getDouble) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(double, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(double, set) // std::string G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::string, isString, getString) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::string, isString, getString) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::string, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::string, set) // eckit::LocalConfiguration (sub-configuration) G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(eckit::LocalConfiguration, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(eckit::LocalConfiguration, set) //------------------------------------------------------------------------------ // Vector types //------------------------------------------------------------------------------ // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getIntVector) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getIntVector) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getLongVector) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getLongVector) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getFloatVector) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getFloatVector) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getDoubleVector) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getDoubleVector) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isStringList, getStringVector) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isStringList, getStringVector) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector G2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isSubConfigurationList, getSubConfigurations) G2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isSubConfigurationList, getSubConfigurations) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) G2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) } // namespace metkit::grib2mars::utils::dict_traits metkit-1.20.2/src/metkit/grib2mars/utils/dictionary_traits/dictaccess_options.h0000664000175000017500000001626715246725757030215 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file dictaccess_options.h /// @brief Read-only dictionary-access traits for Grib2Mars Options. /// /// This adapter exposes the strongly typed `Options` aggregate through the /// generic Grib2Mars dictionary API. /// /// Supported operations for the bool-backed and string-backed option keys: /// /// - `has(options, key)` /// - `has(options, key)` /// - `get_opt(options, key)` /// - `get_or_throw(options, key)` /// - `dict_to_json(options)` /// /// Deliberately unsupported operations: /// /// - mutation through dictionary traits; /// - cloning through dictionary traits; /// - construction from a sample; /// - typed access beyond the explicitly supported bool/string keys. /// /// The Options object is already a complete policy snapshot. The adapter is /// therefore read-only. /// #pragma once #include #include #include #include "metkit/grib2mars/api/Options.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" #include "metkit/grib2mars/utils/type_traits_name.h" namespace metkit::grib2mars::utils { /// /// @brief Human-readable type name for the Options dictionary. /// template <> constexpr std::string_view type_name() { return "metkit::grib2mars::Options"; } } // namespace metkit::grib2mars::utils namespace metkit::grib2mars::utils::dict_traits { namespace options_detail { inline void appendJsonBool(std::string& json, std::string_view key, bool value, bool trailingComma) { json += "\""; json += key; json += "\":"; json += value ? "true" : "false"; if (trailingComma) { json += ','; } } inline void appendJsonString(std::string& json, std::string_view key, std::string_view value, bool trailingComma) { json += '"'; json += key; json += "\":\""; for (const char c : value) { if (c == '\\' || c == '"') { json += '\\'; } json += c; } json += '"'; if (trailingComma) { json += ','; } } inline bool isBoolKey(std::string_view key) noexcept { return key == "saveErrorStack" || key == "printErrorStackToStdErr" || key == "skipSection3" || key == "tryFixBadInput_ZeroAccumulation" || key == "tryFixBadInput_RemoveStepRangeForStrikeProbability" || key == "tryFixBadInput_StandardisedAnomalyAsInstant"; } inline bool isStringKey(std::string_view key) noexcept { return key == "errorStackPath"; } inline bool isKnownKey(std::string_view key) noexcept { return isBoolKey(key) || isStringKey(key); } inline bool getBoolOrThrow(const Options& opts, std::string_view key) { if (key == "saveErrorStack") { return opts.saveErrorStack; } if (key == "printErrorStackToStdErr") { return opts.printErrorStackToStdErr; } if (key == "skipSection3") { return opts.skipSection3; } if (key == "tryFixBadInput_ZeroAccumulation") { return opts.tryFixBadInput_ZeroAccumulation; } if (key == "tryFixBadInput_RemoveStepRangeForStrikeProbability") { return opts.tryFixBadInput_RemoveStepRangeForStrikeProbability; } if (key == "tryFixBadInput_StandardisedAnomalyAsInstant") { return opts.tryFixBadInput_StandardisedAnomalyAsInstant; } throw exceptions::Grib2MarsDictException("Key `" + std::string(key) + "` cannot be read as `" + "bool" + "` from dictionary type `metkit::grib2mars::Options`", Here()); } inline std::string getStringOrThrow(const Options& opts, std::string_view key) { if (key == "errorStackPath") { return opts.errorStackPath; } throw exceptions::Grib2MarsDictException("Key `" + std::string(key) + "` cannot be read as `" + "std::string" + "` from dictionary type `metkit::grib2mars::Options`", Here()); } } // namespace options_detail template <> struct DictToJsonTraits { static std::string to_json(const Options& opts) noexcept(true) { try { std::string json; json.reserve(128); json += '{'; options_detail::appendJsonBool(json, "saveErrorStack", opts.saveErrorStack, true); options_detail::appendJsonBool(json, "skipSection3", opts.skipSection3, true); options_detail::appendJsonBool(json, "tryFixBadInput_ZeroAccumulation", opts.tryFixBadInput_ZeroAccumulation, true); options_detail::appendJsonBool(json, "tryFixBadInput_RemoveStepRangeForStrikeProbability", opts.tryFixBadInput_RemoveStepRangeForStrikeProbability, true); options_detail::appendJsonBool(json, "tryFixBadInput_StandardisedAnomalyAsInstant", opts.tryFixBadInput_StandardisedAnomalyAsInstant, true); options_detail::appendJsonString(json, "errorStackPath", opts.errorStackPath, true); options_detail::appendJsonBool(json, "printErrorStackToStdErr", opts.printErrorStackToStdErr, false); json += '}'; return json; } catch (...) { return "[to_json failed for metkit::grib2mars::Options]"; } } }; template <> struct DictHas { static bool has(const Options&, std::string_view key) noexcept(false) { if (options_detail::isKnownKey(key)) { return true; } return false; } }; template <> struct DictGetOrThrow { static bool get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isBoolKey(key)) { return std::nullopt; } return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOrThrow { static std::string get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getStringOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isStringKey(key)) { return std::nullopt; } return options_detail::getStringOrThrow(opts, key); } }; /* * No explicit function-template specialisation is required for `has()`. * * The generic dictionary API implements: * * has(options, key) * * by invoking: * * DictGetOpt::get_opt(options, key).has_value() * * Therefore the DictGetOpt specialisation above provides: * * - has(options, key) * - has(options, key) */ } // namespace metkit::grib2mars::utils::dict_traits metkit-1.20.2/src/metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h0000664000175000017500000002231615246725757031401 0ustar alastairalastair/* * (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 dictionary_access_traits.h /// @brief Generic dictionary access traits for grib2mars. #pragma once #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "metkit/grib2mars/utils/generalUtils.h" // Exceptions #include "metkit/config/LibMetkit.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" #include "metkit/grib2mars/utils/type_traits_name.h" namespace metkit::grib2mars::utils::dict_traits { using std::operator""s; template struct dependent_false : std::false_type {}; /// @brief Trait hook used to render a dictionary as JSON. template struct DictToJsonTraits { static std::string to_json(const Dict&) { return std::string{"[to_json not supported for this dictionary type]"}; } static void dump_or_ignore(const Dict&, const std::string&) { LOG_DEBUG_LIB(LibMetkit) << to_json(std::declval()); } }; /// @brief Trait hook for cloning and sample creation. template struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view) { static_assert(dependent_false::value, "DictTraits::make_from_sample_or_throw not specialized"); } static std::unique_ptr clone_or_throw(const Dict&) { static_assert(dependent_false::value, "DictTraits::clone_or_throw not specialized"); } }; /// @brief Trait hook for key presence checks. template struct DictHas { static bool has(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictHas not specialized for this Dict"); grib2marsUnreachable(); } }; /// @brief Trait hook for missing-value checks. template struct DictMissing { static bool isMissing(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); grib2marsUnreachable(); } static void setMissing(Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); grib2marsUnreachable(); } }; /// @brief Trait hook for optional value retrieval. template struct DictGetOpt { static std::optional get_opt(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOpt not specialized for this Dict and type"); grib2marsUnreachable(); } }; /// @brief Trait hook for mandatory value retrieval. template struct DictGetOrThrow { static T get_or_throw(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOrThrow not specialized for this Dict and type"); grib2marsUnreachable(); } }; /// @brief Trait hook for assignment that may be ignored. template struct DictSetOrIgnore { static void set_or_ignore(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrIgnore not specialized for this Dict and type"); grib2marsUnreachable(); } }; /// @brief Trait hook for assignment that must succeed. template struct DictSetOrThrow { static void set_or_throw(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrThrow not specialized for this Dict and type"); grib2marsUnreachable(); } }; // ============================================================ // dict_to_json // ============================================================ /// @brief Convert a dictionary to JSON using the active traits. template std::string dict_to_json(const Dict& d) { return DictToJsonTraits::to_json(d); } // ============================================================ // clone / make_from_sample / needs_checks // ============================================================ /// @brief Report whether a dictionary supports consistency checks. template inline constexpr bool dict_supports_checks_v = DictTraits::support_checks; /// @brief Create a dictionary from a sample name. template std::unique_ptr make_from_sample_or_throw(std::string_view name) { return DictTraits::make_from_sample_or_throw(name); } /// @brief Clone a supported dictionary. template std::unique_ptr clone_or_throw(const Dict& d) { return DictTraits::clone_or_throw(d); } /// @brief Render a dictionary or ignore unsupported types. template void dump_or_ignore(const Dict& d, const std::string& f) { DictToJsonTraits::dump_or_ignore(d, f); } // ============================================================ // has / isMissing / setMissing // ============================================================ // has(dict,key) /// @brief Check whether a key exists in a dictionary. template inline bool has(const Dict& dict, std::string_view key) { return DictHas::has(dict, key); } // has(dict,key) /// @brief Check whether a typed value exists in a dictionary. template inline bool has(const Dict& dict, std::string_view key) { return DictGetOpt::get_opt(dict, key).has_value(); } // isMissing(dict,key) /// @brief Check whether a key is semantically missing. template inline bool isMissing(const Dict& dict, std::string_view key) { return DictMissing::isMissing(dict, key); } // setMissing(dict,key) /// @brief Mark a key as missing. template inline void setMissing_or_throw(Dict& dict, std::string_view key) { DictMissing::setMissing(dict, key); return; } // check(dict,key,cond) -> bool /// @brief Evaluate a predicate on a typed optional value. template inline bool check(const Dict& dict, std::string_view key, Cond&& condition) { if (auto v = DictGetOpt::get_opt(dict, key); v.has_value()) { return std::forward(condition)(*v); } return false; } // ============================================================ // GET UTILITIES // ============================================================ // get_or_throw(dict,key) -> T /// @brief Retrieve a mandatory typed value from a dictionary. template inline T get_or_throw(const Dict& dict, std::string_view key) { try { return DictGetOrThrow::get_or_throw(dict, key); } catch (...) { std::throw_with_nested( exceptions::Grib2MarsDictException("Forwarding errors while getting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` from dictionary`"s, Here())); grib2marsUnreachable(); } grib2marsUnreachable(); } // get(dict,key) -> std::optional /// @brief Retrieve an optional typed value from a dictionary. template inline std::optional get_opt(const Dict& dict, std::string_view key) { try { return DictGetOpt::get_opt(dict, key); } catch (...) { return std::nullopt; } grib2marsUnreachable(); } // ============================================================ // SET UTILITIES // ============================================================ // set(dict,key,value) /// @brief Set a typed value in a dictionary and throw on failure. template inline void set_or_throw(Dict& dict, std::string_view key, const T& value) { try { DictSetOrThrow::set_or_throw(dict, key, value); return; } catch (...) { std::throw_with_nested(exceptions::Grib2MarsDictException("Forwarding errors while setting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` to dictionary`"s, Here())); grib2marsUnreachable(); } grib2marsUnreachable(); } /// @brief Set a typed value in a dictionary and ignore failures. template inline void set_or_ignore(Dict& dict, std::string_view key, const T& value) { try { DictSetOrIgnore::set_or_ignore(dict, key, value); return; } catch (...) { // ignore exceptions grib2marsUnreachable(); } grib2marsUnreachable(); } } // namespace metkit::grib2mars::utils::dict_traits metkit-1.20.2/src/metkit/grib2mars/utils/dictionary_traits/dictaccess_mars_request.h0000664000175000017500000005654315246725757031235 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include #include "eckit/log/JSON.h" #include "metkit/mars/MarsRequest.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/generalUtils.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" #include "metkit/grib2mars/utils/type_traits_name.h" namespace metkit::grib2mars::utils { template <> constexpr std::string_view type_name() { return "metkit::mars::MarsRequest"; } } // namespace metkit::grib2mars::utils namespace metkit::grib2mars::utils::dict_traits { using std::operator""s; namespace detail { inline const std::string& scalarStringOrThrow(const metkit::mars::MarsRequest& mars, std::string_view key) { const std::string k{key}; if (!mars.has(k)) { throw exceptions::Grib2MarsDictException("Missing key `"s + k + "` in metkit::mars::MarsRequest", Here()); } const auto& values = mars.values(k); if (values.size() != 1) { throw exceptions::Grib2MarsDictException( "Invalid non-scalar key `"s + k + "` in metkit::mars::MarsRequest: found `"s + std::to_string(values.size()) + "` values. grib2mars expects a single MARS point, not a hypercube.", Here()); } return values.front(); } inline std::optional scalarStringOpt(const metkit::mars::MarsRequest& mars, std::string_view key) { const std::string k{key}; if (!mars.has(k)) { return std::nullopt; } const auto& values = mars.values(k); if (values.empty()) { return std::nullopt; } if (values.size() != 1) { throw exceptions::Grib2MarsDictException( "Invalid non-scalar key `"s + k + "` in metkit::mars::MarsRequest: found `"s + std::to_string(values.size()) + "` values. grib2mars expects a single MARS point, not a hypercube.", Here()); } return values.front(); } inline long parsePlainLong(std::string_view value, std::string_view key) { const std::string s{value}; std::size_t pos = 0; long result = 0; try { result = std::stol(s, &pos); } catch (...) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to long", Here()); } if (pos != s.size()) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to long without loss", Here()); } return result; } inline double parsePlainDouble(std::string_view value, std::string_view key) { const std::string s{value}; std::size_t pos = 0; double result = 0.0; try { result = std::stod(s, &pos); } catch (...) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to double", Here()); } if (pos != s.size()) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to double without loss", Here()); } return result; } inline long parseHoursAsLong(std::string_view value, std::string_view key) { const std::string s{value}; if (s.empty()) { throw exceptions::Grib2MarsDictException("Cannot convert empty key `"s + std::string{key} + "` value to hours", Here()); } if (s.back() == 'h') { return parsePlainLong(std::string_view{s.data(), s.size() - 1}, key); } if (s.back() == 'm' || s.back() == 's') { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to hours: sub-hourly extents are not supported by this grib2mars path", Here()); } // Existing workflows treat bare numeric step/timespan as hours. return parsePlainLong(s, key); } inline std::string longToHours(long value) { return std::to_string(value) + "h"; } inline std::string doubleToString(double value) { std::ostringstream os; os.precision(std::numeric_limits::max_digits10); os << value; return os.str(); } // ----------------------------------------------------------------------------- // long converters // ----------------------------------------------------------------------------- using ToLong = long (*)(std::string_view value, std::string_view key); using FromLong = std::string (*)(long value); inline long paramToLong(std::string_view value, std::string_view key) { // Conservative first implementation: // param=130 -> OK // param=228.128 -> throw // // If later you need "228.128" -> some canonical long encoding, // add that rule here, not in generic parsing. // std::cout << "Parsing param value `" << value << "` as long for key `" << key << "`" << std::endl; return parsePlainLong(value, key); } inline long plainLong(std::string_view value, std::string_view key) { return parsePlainLong(value, key); } inline long hoursLong(std::string_view value, std::string_view key) { return parseHoursAsLong(value, key); } inline std::string plainLongString(long value) { return std::to_string(value); } inline bool allDigits(std::string_view s) { return std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); }); } inline bool isLeapYear(long year) { return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); } inline long daysInMonth(long year, long month) { switch (month) { case 1: return 31; case 2: return isLeapYear(year) ? 29 : 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; default: return 0; } } inline void checkYYYYMMDD(long yyyymmdd, std::string_view key) { const long year = yyyymmdd / 10000; const long month = (yyyymmdd / 100) % 100; const long day = yyyymmdd % 100; if (year <= 0 || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) { throw exceptions::Grib2MarsDictException("Invalid MARS date `"s + std::to_string(yyyymmdd) + "` for key `"s + std::string{key} + "`. Expected a valid yyyymmdd date.", Here()); } } inline long marsDateToLong(std::string_view value, std::string_view key) { const std::string s{value}; std::string compact; if (s.size() == 8 && allDigits(s)) { compact = s; } else if (s.size() == 10 && s[4] == '-' && s[7] == '-' && allDigits(std::string_view{s.data(), 4}) && allDigits(std::string_view{s.data() + 5, 2}) && allDigits(std::string_view{s.data() + 8, 2})) { compact.reserve(8); compact.append(s, 0, 4); compact.append(s, 5, 2); compact.append(s, 8, 2); } else { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS date integer yyyymmdd. Supported forms are yyyymmdd and yyyy-mm-dd.", Here()); } const long result = parsePlainLong(compact, key); checkYYYYMMDD(result, key); return result; } inline std::string longToMarsDate(long value) { const std::string s = std::to_string(value); if (s.size() != 8 || !allDigits(s)) { throw exceptions::Grib2MarsDictException("Cannot convert integer `"s + s + "` to MARS date. Expected yyyymmdd.", Here()); } checkYYYYMMDD(value, "date"); return s; } inline long parseTimePart(std::string_view part, std::string_view full, std::string_view key) { if (part.empty() || !allDigits(part)) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + std::string{full} + "` to MARS time.", Here()); } return parsePlainLong(part, key); } inline long makeHHMMSS(long hh, long mm, long ss, std::string_view value, std::string_view key) { if (hh < 0 || hh > 23 || mm < 0 || mm > 59 || ss < 0 || ss > 59) { throw exceptions::Grib2MarsDictException("Invalid MARS time `"s + std::string{value} + "` for key `"s + std::string{key} + "`. Expected hh=[0,23], mm=[0,59], ss=[0,59].", Here()); } if (ss != 0) { throw exceptions::Grib2MarsDictException("Invalid MARS time `"s + std::string{value} + "` for key `"s + std::string{key} + "`. Seconds are not supported by metkit MARS time normalisation.", Here()); } return hh * 10000 + mm * 100 + ss; } inline long parseSeparatedMarsTime(std::string_view value, std::string_view key, char sep) { const auto p0 = value.find(sep); if (p0 == std::string_view::npos) { throw exceptions::Grib2MarsDictException( "Internal error while parsing separated MARS time `"s + std::string{value} + "`", Here()); } const auto p1 = value.find(sep, p0 + 1); const auto hhPart = value.substr(0, p0); if (p1 == std::string_view::npos) { const auto mmPart = value.substr(p0 + 1); const long hh = parseTimePart(hhPart, value, key); const long mm = parseTimePart(mmPart, value, key); return makeHHMMSS(hh, mm, 0, value, key); } if (value.find(sep, p1 + 1) != std::string_view::npos) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + std::string{value} + "` to MARS time.", Here()); } const auto mmPart = value.substr(p0 + 1, p1 - p0 - 1); const auto ssPart = value.substr(p1 + 1); const long hh = parseTimePart(hhPart, value, key); const long mm = parseTimePart(mmPart, value, key); const long ss = parseTimePart(ssPart, value, key); return makeHHMMSS(hh, mm, ss, value, key); } inline long marsTimeToLong(std::string_view value, std::string_view key) { const std::string s{value}; if (s.empty()) { throw exceptions::Grib2MarsDictException( "Cannot convert empty key `"s + std::string{key} + "` value to MARS time.", Here()); } if (s.find(':') != std::string::npos) { return parseSeparatedMarsTime(s, key, ':'); } if (s.find('-') != std::string::npos) { return parseSeparatedMarsTime(s, key, '-'); } if (!allDigits(s)) { throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS time integer hhmmss.", Here()); } if (s.size() <= 2) { // H or HH -> HH:00:00 const long hh = parsePlainLong(s, key); return makeHHMMSS(hh, 0, 0, value, key); } if (s.size() <= 4) { // HMM or HHMM -> HH:MM:00 std::string hhmm = s; hhmm.insert(hhmm.begin(), 4 - hhmm.size(), '0'); const long hh = parsePlainLong(std::string_view{hhmm.data(), 2}, key); const long mm = parsePlainLong(std::string_view{hhmm.data() + 2, 2}, key); return makeHHMMSS(hh, mm, 0, value, key); } if (s.size() <= 6) { // HMMSS or HHMMSS -> HH:MM:SS std::string hhmmss = s; hhmmss.insert(hhmmss.begin(), 6 - hhmmss.size(), '0'); const long hh = parsePlainLong(std::string_view{hhmmss.data(), 2}, key); const long mm = parsePlainLong(std::string_view{hhmmss.data() + 2, 2}, key); const long ss = parsePlainLong(std::string_view{hhmmss.data() + 4, 2}, key); return makeHHMMSS(hh, mm, ss, value, key); } throw exceptions::Grib2MarsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS time integer hhmmss.", Here()); } inline std::string longToMarsTime(long value) { if (value < 0 || value > 235959) { throw exceptions::Grib2MarsDictException( "Cannot convert integer `"s + std::to_string(value) + "` to MARS time. Expected hhmmss.", Here()); } std::ostringstream os; os << std::setw(6) << std::setfill('0') << value; const std::string hhmmss = os.str(); const long hh = parsePlainLong(std::string_view{hhmmss.data(), 2}, "time"); const long mm = parsePlainLong(std::string_view{hhmmss.data() + 2, 2}, "time"); const long ss = parsePlainLong(std::string_view{hhmmss.data() + 4, 2}, "time"); makeHHMMSS(hh, mm, ss, hhmmss, "time"); // metkit canonical MARS time is HHMM, not HHMMSS. return hhmmss.substr(0, 4); } inline const std::unordered_map& toLongConverters() { static const std::unordered_map converters = { {"param", paramToLong}, {"levelist", plainLong}, {"chem", plainLong}, {"step", hoursLong}, {"timespan", hoursLong}, {"date", marsDateToLong}, {"hdate", marsDateToLong}, {"time", marsTimeToLong}, {"htime", marsTimeToLong}, }; return converters; } inline const std::unordered_map& fromLongConverters() { static const std::unordered_map converters = { {"param", plainLongString}, {"levelist", plainLongString}, {"chem", plainLongString}, {"step", longToHours}, {"timespan", longToHours}, {"date", longToMarsDate}, {"hdate", longToMarsDate}, {"time", longToMarsTime}, {"htime", longToMarsTime}, }; return converters; } inline long convertToLongOrThrow(std::string_view key, std::string_view value) { const std::string k{key}; const auto& converters = toLongConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Grib2MarsDictException( "Key `"s + k + "` does not support conversion to long for metkit::mars::MarsRequest", Here()); } return it->second(value, key); } inline std::string convertFromLongOrThrow(std::string_view key, long value) { const std::string k{key}; const auto& converters = fromLongConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Grib2MarsDictException( "Key `"s + k + "` does not support assignment from long for metkit::mars::MarsRequest", Here()); } return it->second(value); } // ----------------------------------------------------------------------------- // double converters // ----------------------------------------------------------------------------- using ToDouble = double (*)(std::string_view value, std::string_view key); using FromDouble = std::string (*)(double value); inline const std::unordered_map& toDoubleConverters() { static const std::unordered_map converters = { {"wavelength", parsePlainDouble}, }; return converters; } inline const std::unordered_map& fromDoubleConverters() { static const std::unordered_map converters = { {"wavelength", doubleToString}, }; return converters; } inline double convertToDoubleOrThrow(std::string_view key, std::string_view value) { const std::string k{key}; const auto& converters = toDoubleConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Grib2MarsDictException( "Key `"s + k + "` does not support conversion to double for metkit::mars::MarsRequest", Here()); } return it->second(value, key); } inline std::string convertFromDoubleOrThrow(std::string_view key, double value) { const std::string k{key}; const auto& converters = fromDoubleConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Grib2MarsDictException( "Key `"s + k + "` does not support assignment from double for metkit::mars::MarsRequest", Here()); } return it->second(value); } } // namespace detail // ----------------------------------------------------------------------------- // DictToJsonTraits // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const metkit::mars::MarsRequest& mars) noexcept(true) { try { std::ostringstream os; eckit::JSON json(os); mars.json(json); return os.str(); } catch (...) { return "[to_json failed for metkit::mars::MarsRequest]"; } } }; // ----------------------------------------------------------------------------- // DictTraits // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = false; // static std::unique_ptr // make_from_sample_or_throw(std::string_view name) { // return std::make_unique(std::string{name}); // } static std::unique_ptr clone_or_throw(const metkit::mars::MarsRequest& mars) { return std::make_unique(mars); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return mars.has(std::string{key}); } }; // ----------------------------------------------------------------------------- // DictMissing // ----------------------------------------------------------------------------- template <> struct DictMissing { static bool isMissing(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const std::string k{key}; if (!mars.has(k)) { return true; } const auto& values = mars.values(k, true); return values.empty(); } static void setMissing(metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { mars.unsetValues(std::string{key}); } }; // ----------------------------------------------------------------------------- // std::string access: all keys, raw scalar string // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static std::string get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return detail::scalarStringOrThrow(mars, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return detail::scalarStringOpt(mars, key); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const std::string& value) noexcept(false) { mars.setValue(std::string{key}, value); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const std::string& value) noexcept(false) { try { mars.setValue(std::string{key}, value); } catch (...) { } } }; // ----------------------------------------------------------------------------- // long access: explicit supported keys only // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static long get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto& raw = detail::scalarStringOrThrow(mars, key); return detail::convertToLongOrThrow(key, raw); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto raw = detail::scalarStringOpt(mars, key); if (!raw.has_value()) { return std::nullopt; } return detail::convertToLongOrThrow(key, *raw); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const long& value) noexcept(false) { mars.setValue(std::string{key}, detail::convertFromLongOrThrow(key, value)); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const long& value) noexcept(false) { try { mars.setValue(std::string{key}, detail::convertFromLongOrThrow(key, value)); } catch (...) { } } }; // ----------------------------------------------------------------------------- // double access: wavelength only // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static double get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto& raw = detail::scalarStringOrThrow(mars, key); return detail::convertToDoubleOrThrow(key, raw); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto raw = detail::scalarStringOpt(mars, key); if (!raw.has_value()) { return std::nullopt; } return detail::convertToDoubleOrThrow(key, *raw); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const double& value) noexcept(false) { mars.setValue(std::string{key}, detail::convertFromDoubleOrThrow(key, value)); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const double& value) noexcept(false) { try { mars.setValue(std::string{key}, detail::convertFromDoubleOrThrow(key, value)); } catch (...) { } } }; } // namespace metkit::grib2mars::utils::dict_traitsmetkit-1.20.2/src/metkit/grib2mars/mappings/0000775000175000017500000000000015246725757021073 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/mappings/MarsTopology.h0000664000175000017500000002154315246725757023710 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/KeyIterator.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { class MarsTopology { public: using container_type = std::vector; using const_iterator = container_type::const_iterator; MarsTopology() = delete; explicit MarsTopology(const metkit::codes::CodesHandle& grib) : MarsTopology(make_MarsKeywords(grib)) {} MarsTopology(const MarsTopology&) = default; MarsTopology& operator=(const MarsTopology&) = default; MarsTopology(MarsTopology&&) = default; MarsTopology& operator=(MarsTopology&&) = default; ~MarsTopology() = default; const_iterator begin() const { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { return keywords_.begin(); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to access begin iterator of grib2mars MarsTopology", Here())); } } const_iterator end() const { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { return keywords_.end(); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to access end iterator of grib2mars MarsTopology", Here())); } } bool contains(const std::string& keyword) const { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const std::unordered_set::const_iterator it = index_.find(keyword); return it != index_.end(); } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to check MARS keyword `" + keyword + "` in grib2mars MarsTopology", Here())); } } std::size_t size() const { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { return keywords_.size(); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to get size of grib2mars MarsTopology", Here())); } } bool empty() const { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { return keywords_.empty(); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to check whether grib2mars MarsTopology is empty", Here())); } } private: explicit MarsTopology(std::vector keywords) : keywords_(std::move(keywords)), index_(make_index(keywords_)) {} static bool contains_keyword(const std::vector& keywords, const std::string& keyword) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const std::vector::const_iterator it = std::find(keywords.begin(), keywords.end(), keyword); return it != keywords.end(); } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to check MARS keyword `" + keyword + "` while building grib2mars MarsTopology", Here())); } } static void append_unique(std::vector& keywords, const std::string& keyword) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const bool alreadyPresent = contains_keyword(keywords, keyword); if (!alreadyPresent) { keywords.push_back(keyword); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to append MARS keyword `" + keyword + "` while building grib2mars MarsTopology", Here())); } } static void remove_keyword(std::vector& keywords, const std::string& keyword) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { keywords.erase(std::remove(keywords.begin(), keywords.end(), keyword), keywords.end()); } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to remove MARS keyword `" + keyword + "` while building grib2mars MarsTopology", Here())); } } static void append_ecCodes_MarsNamespace_keywords(const metkit::codes::CodesHandle& grib, std::vector& keywords) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { for (const metkit::codes::KeyIterator& key : grib.keys(metkit::codes::namespaces::mars)) { const std::string keyword = key.name(); append_unique(keywords, keyword); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to extract ecCodes MARS namespace while building grib2mars MarsTopology", Here())); } } static void append_packing_keyword(const metkit::codes::CodesHandle& grib, std::vector& keywords) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const bool hasPackingType = grib.has("packingType"); if (hasPackingType) { append_unique(keywords, "packing"); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to add MARS keyword `packing` while building grib2mars MarsTopology", Here())); } } static void append_geometry_keyword(const metkit::codes::CodesHandle& grib, std::vector& keywords) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const bool hasGridType = grib.has("gridType"); if (!hasGridType) { return; } const std::string gridType = grib.getString("gridType"); if (gridType == "sh") { remove_keyword(keywords, "grid"); append_unique(keywords, "truncation"); return; } remove_keyword(keywords, "truncation"); append_unique(keywords, "grid"); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to select MARS geometry keyword `grid`/`truncation` " "while building grib2mars MarsTopology", Here())); } } static void append_required_keywords(std::vector& keywords) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { append_unique(keywords, "class"); append_unique(keywords, "stream"); append_unique(keywords, "type"); append_unique(keywords, "origin"); } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to append required MARS keywords while building grib2mars MarsTopology", Here())); } } static std::vector make_MarsKeywords(const metkit::codes::CodesHandle& grib) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { std::vector keywords; append_ecCodes_MarsNamespace_keywords(grib, keywords); // MARS keywords required by mars2grib but not always injected by ecCodes. append_packing_keyword(grib, keywords); append_geometry_keyword(grib, keywords); append_required_keywords(keywords); return keywords; } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to build MARS keyword list for grib2mars MarsTopology", Here())); } } static std::unordered_set make_index(const std::vector& keywords) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { std::unordered_set index; for (const std::string& keyword : keywords) { index.insert(keyword); } return index; } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to build keyword index for grib2mars MarsTopology", Here())); } } private: const std::vector keywords_; const std::unordered_set index_; }; } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/Grib2MarsReturnValue.h0000664000175000017500000000174415246725757025237 0ustar alastairalastair/* * (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 Grib2MarsReturnValue.h /// @brief Return type used by grib2mars conversion APIs. #pragma once #include "eckit/config/LocalConfiguration.h" namespace metkit::grib2mars { /// @brief Structured result returned by grib2mars conversions. /// /// @tparam MarsDict /// Dictionary type used for the converted request. template struct Grib2MarsResult { /// @brief Converted MARS request. MarsDict mars; /// @brief Auxiliary metadata produced during conversion. eckit::LocalConfiguration misc; }; } // namespace metkit::grib2mars metkit-1.20.2/src/metkit/grib2mars/mappings/mappings.h0000664000175000017500000000324315246725757023064 0ustar alastairalastair#pragma once #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/mappings/Grib2MarsReturnValue.h" #include "metkit/grib2mars/mappings/MarsTopology.h" #include "metkit/grib2mars/mappings/rules/extract.h" #include "metkit/grib2mars/mappings/rules/misc.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules { template Grib2MarsResult convertAll(const metkit::codes::CodesHandle& grib, const OptDict_t& opts) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { std::unique_ptr mars = std::make_unique(); std::unique_ptr misc = std::make_unique(); // // Topology is the subset of MARS keywords that are relevant to describe the input grib message. const metkit::grib2mars::rules::impl::MarsTopology topology(grib); // // This is used to inject the values for (const std::string& keyword : topology) { impl::extract(keyword, grib, *mars, *misc, opts); } // Extract the remaining miscelanious values which are not attached to a MARS mapping impl::extractMisc(grib, *mars, *misc, opts); return Grib2MarsResult{std::move(*mars), std::move(*misc)}; } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to convertAll grib message", Here())); } } } // namespace metkit::grib2mars::rulesmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/0000775000175000017500000000000015246725757022225 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/model.h0000664000175000017500000000223615246725757023501 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractModel(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/hdate.h0000664000175000017500000000221615246725757023464 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractHdate(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/leg_number.h0000664000175000017500000000107515246725757024520 0ustar alastairalastair#pragma once #include #include #include "metkit/codes/api/CodesAPI.h" namespace metkit::grib2mars::rules::impl { template void extractLegNumber(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { (void)grib; (void)mars; (void)misc; // std::cerr << "WARNING: ignoring unsupported grib2mars MARS keyword `" << keyword << "`" << std::endl; } } // namespace metkit::grib2mars::rules::impl metkit-1.20.2/src/metkit/grib2mars/mappings/rules/stattype.h0000664000175000017500000000224415246725757024255 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractStatType(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/expver.h0000664000175000017500000000216015246725757023706 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractExpver(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException("GRIB message is missing required key `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/origin.h0000664000175000017500000000264215246725757023671 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractOrigin(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (grib.has("origin")) { const std::string value = grib.getString("origin"); set_or_throw(mars, keyword, value); return; } if (grib.has("centre")) { const std::string value = grib.getString("centre"); set_or_throw(mars, keyword, value); return; } throw Grib2MarsGenericException( "Missing GRIB keys `origin` and `centre` required to extract " "MARS keyword `" + keyword + "`", Here()); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/grid.h0000664000175000017500000000561115246725757023326 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractGrid(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has("gridType")) { throw Grib2MarsGenericException( "Missing GRIB key `gridType` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string gridType = grib.getString("gridType"); if (gridType == "sh") { throw Grib2MarsGenericException( "Cannot extract MARS keyword `grid` from spherical harmonics " "GRIB message; `truncation` is required instead", Here()); } if (gridType == "regular_ll") { if (!grib.has("iDirectionIncrementInDegrees")) { throw Grib2MarsGenericException( "Missing GRIB key `iDirectionIncrementInDegrees` required " "to extract regular_ll MARS grid", Here()); } if (!grib.has("jDirectionIncrementInDegrees")) { throw Grib2MarsGenericException( "Missing GRIB key `jDirectionIncrementInDegrees` required " "to extract regular_ll MARS grid", Here()); } const double dx = grib.getDouble("iDirectionIncrementInDegrees"); const double dy = grib.getDouble("jDirectionIncrementInDegrees"); const std::string grid = std::to_string(dx) + "/" + std::to_string(dy); set_or_throw(mars, keyword, grid); return; } if (!grib.has("gridName")) { throw Grib2MarsGenericException("Missing GRIB key `gridName` required to extract MARS keyword `" + keyword + "` for gridType `" + gridType + "`", Here()); } const std::string gridName = grib.getString("gridName"); set_or_throw(mars, keyword, gridName); if (grib.has("shapeOfTheEarth")) { const long shapeOfTheEarth = grib.getLong("shapeOfTheEarth"); misc.set("shapeOfTheEarth", shapeOfTheEarth); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/system.h0000664000175000017500000000222015246725757023716 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractSystem(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/anoffset.h0000664000175000017500000000324515246725757024207 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractAnoffset(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } if (!grib.has("lengthOf4DvarWindow")) { throw Grib2MarsGenericException( "Missing GRIB key `lengthOf4DvarWindow` required while " "extracting MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); const long lengthOf4DvarWindow = grib.getLong("lengthOf4DvarWindow"); // Historical mars2grib misc name. // Source GRIB key: lengthOf4DvarWindow. // Unit assumption: hours. misc.set("lengthOfTimeWindow", lengthOf4DvarWindow); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/activity.h0000664000175000017500000000224415246725757024234 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractActivity(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/misc.h0000664000175000017500000000457115246725757023340 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractMisc(const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::get_or_throw; using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (grib.has("generatingProcessIdentifier")) { const auto generatingProcessIdentifier = grib.getLong("generatingProcessIdentifier"); misc.set("generatingProcessIdentifier", generatingProcessIdentifier); } if (grib.getString("type") != "ai" && grib.has("typeOfProcessedData")) { const auto typeOfProcessedData = grib.getString("typeOfProcessedData"); if (typeOfProcessedData != "missing") { misc.set("typeOfProcessedData", typeOfProcessedData); } } const auto type = get_or_throw(mars, "type"); if (type == "eme" || type == "me") { if (grib.has("numberOfComponents")) { const auto numberOfComponents = grib.getLong("numberOfComponents"); misc.set("numberOfComponents", numberOfComponents); } if (grib.has("modelErrorType")) { const auto modelErrorType = grib.getLong("modelErrorType"); misc.set("modelErrorType", modelErrorType); } } if (type == "es" || type == "em" || type == "ses") { if (!grib.has("numberOfForecastsInEnsemble")) { throw Grib2MarsGenericException( "Missing GRIB key `numberOfForecastsInEnsemble` required for derived ensemble forecast", Here()); } const long numberOfForecastsInEnsemble = grib.getLong("numberOfForecastsInEnsemble"); misc.set("numberOfForecastsInEnsemble", numberOfForecastsInEnsemble); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MISC keywords", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/iteration.h0000664000175000017500000000260515246725757024377 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractIteration(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); if (grib.getString("type") == "4i" && grib.has("totalNumberOfIterations")) { const long totalNumberOfIterations = grib.getLong("totalNumberOfIterations"); misc.set("totalNumberOfIterations", totalNumberOfIterations); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::impl metkit-1.20.2/src/metkit/grib2mars/mappings/rules/channel.h0000664000175000017500000000222215246725757024004 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractChannel(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/ident.h0000664000175000017500000000221615246725757023502 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractIdent(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/timespan.h0000664000175000017500000001071715246725757024224 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { namespace detail { template void tryFixBadInput_ZeroAccumulation(const metkit::codes::CodesHandle& grib, MarsDict& mars, const std::string& stepType, long startStep, long endStep, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::get_or_throw; using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { long marsParamId = get_or_throw(mars, "paramId"); // This fix an inconsistency in the current grib fields in production if (marsParamId == 162110 && stepType == "accum") { set_or_throw(mars, "timespan", "fs"); } else { throw Grib2MarsGenericException( "Cannot fix bad input for zero accumulation for MARS keyword `timespan` with param=" + std::to_string(marsParamId) + ", stepType=`" + stepType + "`, startStep=" + std::to_string(startStep) + ", endStep=" + std::to_string(endStep), Here()); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to fix bad input for zero accumulation while extracting MARS keyword `timespan`", Here())); } } } // namespace detail template void extractTimespan(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::get_or_throw; using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has("stepType")) { throw Grib2MarsGenericException( "Missing GRIB key `stepType` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string stepType = grib.getString("stepType"); if (stepType == "instant") { set_or_throw(mars, keyword, "none"); return; } if (!grib.has("startStep")) { throw Grib2MarsGenericException("Missing GRIB key `startStep` required to extract MARS keyword `" + keyword + "` for statistical stepType `" + stepType + "`", Here()); } if (!grib.has("endStep")) { throw Grib2MarsGenericException("Missing GRIB key `endStep` required to extract MARS keyword `" + keyword + "` for statistical stepType `" + stepType + "`", Here()); } const long startStep = grib.getLong("startStep"); const long endStep = grib.getLong("endStep"); const long timespan = endStep - startStep; if (timespan < 0) { throw Grib2MarsGenericException( "Invalid statistical window while extracting MARS keyword `" + keyword + "`: stepType=`" + stepType + "`, startStep=" + std::to_string(startStep) + ", endStep=" + std::to_string(endStep) + ", timespan=" + std::to_string(timespan), Here()); } if (timespan == 0 && get_or_throw(opts, "tryFixBadInput_ZeroAccumulation")) { detail::tryFixBadInput_ZeroAccumulation(grib, mars, stepType, startStep, endStep, opts); return; } else { throw Grib2MarsGenericException( "Invalid statistical window while extracting MARS keyword `" + keyword + "`: stepType=`" + stepType + "`, startStep=" + std::to_string(startStep) + ", endStep=" + std::to_string(endStep) + ", timespan=" + std::to_string(timespan), Here()); } set_or_throw(mars, keyword, timespan); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/wavelength.h0000664000175000017500000000225415246725757024545 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractWavelength(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to extract scalar MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/chem.h0000664000175000017500000000221415246725757023311 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractChem(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/domain.h0000664000175000017500000000224015246725757023643 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractDomain(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/packing.h0000664000175000017500000000572515246725757024023 0ustar alastairalastair#pragma once #include #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { namespace detail { inline std::string convertPackingType(const std::string& packingType) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { static const std::unordered_map packingMap = { {"grid_simple", "simple"}, {"grid_complex", "complex"}, {"spectral_complex", "complex"}, {"grid_ccsds", "ccsds"}, {"grid_ieee", "ccsds"}, {"grid_second_order", "ccsds"}, }; const std::unordered_map::const_iterator it = packingMap.find(packingType); if (it == packingMap.end()) { throw Grib2MarsGenericException("Unhandled packingType `" + packingType + "`", Here()); } const std::string value = it->second; return value; } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to convert GRIB packingType `" + packingType + "`", Here())); } } } // namespace detail template void extractPacking(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { if (!grib.has("packingType")) { throw Grib2MarsGenericException( "Missing GRIB key `packingType` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string packingType = grib.getString("packingType"); const std::string packing = detail::convertPackingType(packingType); set_or_throw(mars, keyword, packing); if (grib.has("setPackingType")) { const std::string setPackingType = grib.getString("setPackingType"); const std::string overridePacking = detail::convertPackingType(setPackingType); set_or_throw(mars, keyword, overridePacking); } if (grib.has("bitmapPresent")) { const bool bitmapPresent = grib.getLong("bitmapPresent") == 1; misc.set("bitmapPresent", bitmapPresent); } const double missingValue = 9999.0; misc.set("missingValue", missingValue); long bitsPerValue = 24; if (grib.has("bitsPerValue")) { bitsPerValue = grib.getLong("bitsPerValue"); } misc.set("bitsPerValue", bitsPerValue); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/experiment.h0000664000175000017500000000225015246725757024555 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractExperiment(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/instrument.h0000664000175000017500000000350715246725757024613 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractInstrument(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); if (grib.has("satelliteSeries")) { const long satelliteSeries = grib.getLong("satelliteSeries"); misc.set("satelliteSeries", satelliteSeries); } if (grib.has("scaleFactorOfCentralWaveNumber")) { const long scaleFactorOfCentralWaveNumber = grib.getLong("scaleFactorOfCentralWaveNumber"); misc.set("scaleFactorOfCentralWaveNumber", scaleFactorOfCentralWaveNumber); } if (grib.has("scaledValueOfCentralWaveNumber")) { const long scaledValueOfCentralWaveNumber = grib.getLong("scaledValueOfCentralWaveNumber"); misc.set("scaledValueOfCentralWaveNumber", scaledValueOfCentralWaveNumber); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/levtype.h0000664000175000017500000000244215246725757024070 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractLevtype(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); if (grib.has("pv")) { const auto pv = grib.getDoubleArray("pv"); misc.set("pv", pv); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/method.h0000664000175000017500000000222015246725757023652 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractMethod(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/date.h0000664000175000017500000000221415246725757023312 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractDate(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/stream.h0000664000175000017500000000216015246725757023670 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractStream(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException("GRIB message is missing required key `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/truncation.h0000664000175000017500000000546315246725757024574 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractTruncation(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has("gridType")) { throw Grib2MarsGenericException( "Missing GRIB key `gridType` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string gridType = grib.getString("gridType"); if (gridType != "sh") { throw Grib2MarsGenericException( "Cannot extract MARS keyword `truncation` from non-spherical " "harmonics gridType `" + gridType + "`", Here()); } if (!grib.has("J")) { throw Grib2MarsGenericException("Missing GRIB key `J` required to extract MARS keyword `" + keyword + "`", Here()); } const long j = grib.getLong("J"); const long k = grib.getLong("K"); const long m = grib.getLong("M"); if (j != k || j != m) { throw Grib2MarsGenericException("Grib keys `J/K/M` must be equal! J=" + std::to_string(j) + ", K=" + std::to_string(k) + ", M=" + std::to_string(m), Here()); } // Set MARS truncation based on J, which is equal to K and M set_or_throw(mars, keyword, j); if (grib.has("JS")) { const long js = grib.getLong("JS"); const long ks = grib.getLong("KS"); const long ms = grib.getLong("MS"); if (js != ks || js != ms) { throw Grib2MarsGenericException("Grib keys `JS/KS/MS` must be equal! JS=" + std::to_string(js) + ", KS=" + std::to_string(ks) + ", MS=" + std::to_string(ms), Here()); } if (js > 0 && js <= j) { // Set subSetTruncation based on JS, which is equal to KS and MS set_or_throw(misc, "subSetTruncation", js); } } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/dataset.h0000664000175000017500000000224215246725757024023 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractDataset(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/param.h0000664000175000017500000000221315246725757023474 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractParam(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)misc; (void)opts; if (!grib.has("paramId")) { throw Grib2MarsGenericException( "GRIB message is missing required key `paramId` for MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong("paramId"); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/number.h0000664000175000017500000000436015246725757023671 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractNumber(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); if (grib.has("numberOfForecastsInEnsemble")) { const long numberOfForecastsInEnsemble = grib.getLong("numberOfForecastsInEnsemble"); if (numberOfForecastsInEnsemble != 0) { misc.set("numberOfForecastsInEnsemble", numberOfForecastsInEnsemble); if (grib.getString("class") != "ai") { if (grib.has("typeOfEnsembleForecast")) { const long typeOfEnsembleForecast = grib.getLong("typeOfEnsembleForecast"); misc.set("typeOfEnsembleForecast", typeOfEnsembleForecast); } else if (grib.has("eps")) { const long typeOfEnsembleForecast = grib.getLong("eps"); misc.set("typeOfEnsembleForecast", typeOfEnsembleForecast); } } } } else if (grib.getString("type") != "me") { throw Grib2MarsGenericException( "Missing GRIB key `numberOfForecastsInEnsemble` required to extract MARS keyword `" + keyword + "`", Here()); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/frequency.h0000664000175000017500000000506115246725757024401 0ustar alastairalastair#pragma once #include #include #include #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractFrequency(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long frequency = grib.getLong(keyword); set_or_throw(mars, keyword, frequency); if (grib.has("scaledValuesOfWaveFrequencies")) { std::vector waveFrequencies = grib.getDoubleArray("scaledValuesOfWaveFrequencies"); double frequencyScalingFactor = 0.0; if (grib.has("scaleFactorOfWaveFrequencies")) { const double scaleFactorOfWaveFrequencies = grib.getDouble("scaleFactorOfWaveFrequencies"); frequencyScalingFactor = std::pow(10.0, scaleFactorOfWaveFrequencies); } else { if (!grib.has("frequencyScalingFactor")) { throw Grib2MarsGenericException( "Missing both `scaleFactorOfWaveFrequencies` and " "`frequencyScalingFactor` while extracting MARS keyword `" + keyword + "`", Here()); } frequencyScalingFactor = grib.getDouble("frequencyScalingFactor"); } std::for_each(waveFrequencies.begin(), waveFrequencies.end(), [frequencyScalingFactor](double& value) { value /= frequencyScalingFactor; }); const double scaleFactorOfWaveFrequencies = std::log10(frequencyScalingFactor); misc.set("scaleFactorOfWaveFrequencies", scaleFactorOfWaveFrequencies); misc.set("waveFrequencies", waveFrequencies); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/levelist.h0000664000175000017500000000415415246725757024231 0ustar alastairalastair#pragma once #include #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractLevelist(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has("levtype")) { throw Grib2MarsGenericException( "Missing GRIB key `levtype` required to extract MARS keyword `" + keyword + "`", Here()); } if (!grib.has("level")) { throw Grib2MarsGenericException( "Missing GRIB key `level` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string levtype = grib.getString("levtype"); std::optional levelist; if (levtype == "pl") { if (!grib.has("pressureUnits")) { throw Grib2MarsGenericException( "Missing GRIB key `pressureUnits` required to extract " "pressure-level MARS keyword `" + keyword + "`", Here()); } const std::string pressureUnits = grib.getString("pressureUnits"); if (pressureUnits == "hPa") { const long level = grib.getLong("level"); levelist = level * 100; } } if (!levelist) { const long level = grib.getLong("level"); levelist = level; } set_or_throw(mars, keyword, *levelist); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/time.h0000664000175000017500000000313215246725757023333 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractTime(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has("hour")) { throw Grib2MarsGenericException( "Missing GRIB key `hour` required to extract MARS keyword `" + keyword + "`", Here()); } if (!grib.has("minute")) { throw Grib2MarsGenericException( "Missing GRIB key `minute` required to extract MARS keyword `" + keyword + "`", Here()); } if (grib.has("second") && grib.getLong("second") != 0) { throw Grib2MarsGenericException("Non-zero GRIB key `second` is not supported!", Here()); } const long hour = grib.getLong("hour"); const long minute = grib.getLong("minute"); const long value = hour * 100 + minute; set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/resolution.h0000664000175000017500000000225015246725757024600 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractResolution(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/type.h0000664000175000017500000000215415246725757023361 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractType(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { throw Grib2MarsGenericException("GRIB message is missing required key `" + keyword + "`", Here()); } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/class.h0000664000175000017500000000202315246725757023500 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractClass(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; (void)misc; if (!grib.has(keyword)) { return; } const std::string value = grib.getString(keyword); set_or_throw(mars, keyword, value); } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/direction.h0000664000175000017500000000524115246725757024360 0ustar alastairalastair#pragma once #include #include #include #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractDirection(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long direction = grib.getLong(keyword); set_or_throw(mars, keyword, direction); if (grib.has("scaledValuesOfWaveDirections")) { constexpr double rad2deg = 57.29577951308232087679815481410517033240547246656432154916; std::vector waveDirections = grib.getDoubleArray("scaledValuesOfWaveDirections"); double directionScalingFactor = 0.0; if (grib.has("scaleFactorOfWaveDirections")) { const double scaleFactorOfWaveDirections = grib.getDouble("scaleFactorOfWaveDirections"); directionScalingFactor = std::pow(10.0, scaleFactorOfWaveDirections); } else { if (!grib.has("directionScalingFactor")) { throw Grib2MarsGenericException( "Missing both `scaleFactorOfWaveDirections` and " "`directionScalingFactor` while extracting MARS keyword `" + keyword + "`", Here()); } directionScalingFactor = grib.getDouble("directionScalingFactor"); } std::for_each( waveDirections.begin(), waveDirections.end(), [directionScalingFactor, rad2deg](double& value) { value /= directionScalingFactor * rad2deg; }); const double scaleFactorOfWaveDirections = std::log10(directionScalingFactor); misc.set("scaleFactorOfWaveDirections", scaleFactorOfWaveDirections); misc.set("waveDirections", waveDirections); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/extract.h0000664000175000017500000001561615246725757024061 0ustar alastairalastair#pragma once #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" // One extractor header per MARS keyword #include "metkit/grib2mars/mappings/rules/activity.h" #include "metkit/grib2mars/mappings/rules/anoffset.h" #include "metkit/grib2mars/mappings/rules/channel.h" #include "metkit/grib2mars/mappings/rules/class.h" #include "metkit/grib2mars/mappings/rules/coeffindex.h" #include "metkit/grib2mars/mappings/rules/dataset.h" #include "metkit/grib2mars/mappings/rules/date.h" #include "metkit/grib2mars/mappings/rules/direction.h" #include "metkit/grib2mars/mappings/rules/domain.h" #include "metkit/grib2mars/mappings/rules/experiment.h" #include "metkit/grib2mars/mappings/rules/expver.h" #include "metkit/grib2mars/mappings/rules/frequency.h" #include "metkit/grib2mars/mappings/rules/grid.h" #include "metkit/grib2mars/mappings/rules/hdate.h" #include "metkit/grib2mars/mappings/rules/ident.h" #include "metkit/grib2mars/mappings/rules/instrument.h" #include "metkit/grib2mars/mappings/rules/iteration.h" #include "metkit/grib2mars/mappings/rules/leg_number.h" #include "metkit/grib2mars/mappings/rules/levelist.h" #include "metkit/grib2mars/mappings/rules/levtype.h" #include "metkit/grib2mars/mappings/rules/method.h" #include "metkit/grib2mars/mappings/rules/model.h" #include "metkit/grib2mars/mappings/rules/number.h" #include "metkit/grib2mars/mappings/rules/origin.h" #include "metkit/grib2mars/mappings/rules/packing.h" #include "metkit/grib2mars/mappings/rules/param.h" #include "metkit/grib2mars/mappings/rules/resolution.h" #include "metkit/grib2mars/mappings/rules/stattype.h" #include "metkit/grib2mars/mappings/rules/step.h" #include "metkit/grib2mars/mappings/rules/stream.h" #include "metkit/grib2mars/mappings/rules/system.h" #include "metkit/grib2mars/mappings/rules/time.h" #include "metkit/grib2mars/mappings/rules/timespan.h" #include "metkit/grib2mars/mappings/rules/truncation.h" #include "metkit/grib2mars/mappings/rules/type.h" #include "metkit/grib2mars/mappings/rules/wavelength.h" namespace metkit::grib2mars::rules::impl { namespace detail { template using MarsExtractor = void (*)(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts); template const std::unordered_map>& extractorRegistry() { static const std::unordered_map> registry = { {"class", extractClass}, {"stream", extractStream}, {"type", extractType}, {"expver", extractExpver}, {"param", extractParam}, {"levtype", extractLevtype}, {"levelist", extractLevelist}, {"frequency", extractFrequency}, {"direction", extractDirection}, {"ident", extractIdent}, {"channel", extractChannel}, {"instrument", extractInstrument}, {"_leg_number", extractLegNumber}, {"anoffset", extractAnoffset}, {"number", extractNumber}, {"grid", extractGrid}, {"truncation", extractTruncation}, {"packing", extractPacking}, {"date", extractDate}, {"hdate", extractHdate}, {"time", extractTime}, {"step", extractStep}, {"system", extractSystem}, {"method", extractMethod}, {"origin", extractOrigin}, {"timespan", extractTimespan}, {"stattype", extractStatType}, {"domain", extractDomain}, {"activity", extractActivity}, {"dataset", extractDataset}, {"experiment", extractExperiment}, {"resolution", extractResolution}, {"model", extractModel}, {"wavelength", extractWavelength}, {"iteration", extractIteration}, {"coeffindex", extractCoeffindex}, }; return registry; } template MarsExtractor resolveExtractor(const std::string& keyword) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const std::unordered_map>& registry = extractorRegistry(); const typename std::unordered_map>::const_iterator it = registry.find(keyword); if (it == registry.end()) { throw Grib2MarsGenericException("No grib2mars extractor registered for MARS keyword `" + keyword + "`", Here()); } const MarsExtractor extractor = it->second; return extractor; } catch (...) { std::throw_with_nested(Grib2MarsGenericException( "Failed to resolve grib2mars extractor for MARS keyword `" + keyword + "`", Here())); } } } // namespace detail template void extract(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { const detail::MarsExtractor extractor = detail::resolveExtractor(keyword); extractor(keyword, grib, mars, misc, opts); } catch (...) { std::throw_with_nested( Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "` from GRIB message", Here())); } } } // namespace metkit::grib2mars::rules::impl metkit-1.20.2/src/metkit/grib2mars/mappings/rules/step.h0000664000175000017500000000607715246725757023363 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { bool isStrikeProbability(long paramId) { return (paramId == 131074 || paramId == 131075 || paramId == 131076 || paramId == 131077 || paramId == 131089 || paramId == 131090 || paramId == 131091); } template void extractStep(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::get_or_throw; using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; const bool isInstant = grib.getString("stepType") == "instant"; const bool hasTimespan = grib.has("timespan"); const bool isStandardisedAnomaly = grib.getString("stepType") == "stdanom" && get_or_throw(opts, "tryFixBadInput_StandardisedAnomalyAsInstant"); if (isInstant || hasTimespan || isStandardisedAnomaly) { if (!grib.has("endStep")) { throw Grib2MarsGenericException( "Missing GRIB key `endStep` required to extract MARS keyword `" + keyword + "`", Here()); } const long endStep = grib.getLong("endStep"); set_or_throw(mars, keyword, endStep); } else { if (!grib.has("startStep")) { throw Grib2MarsGenericException( "Missing GRIB key `startStep` required to extract MARS keyword `" + keyword + "`", Here()); } if (!grib.has("endStep")) { throw Grib2MarsGenericException( "Missing GRIB key `endStep` required to extract MARS keyword `" + keyword + "`", Here()); } const long startStep = grib.getLong("startStep"); const long endStep = grib.getLong("endStep"); const std::string stepRange = std::to_string(startStep) + "-" + std::to_string(endStep); if (isStrikeProbability(grib.getLong("paramId")) && get_or_throw(opts, "tryFixBadInput_RemoveStepRangeForStrikeProbability")) { set_or_throw(mars, keyword, startStep); return; } set_or_throw(mars, keyword, stepRange); } if (grib.has("timeIncrement")) { const long timeIncrement = grib.getLong("timeIncrement"); if (timeIncrement > 0) { misc.set("timeIncrementInSeconds", timeIncrement); } } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::implmetkit-1.20.2/src/metkit/grib2mars/mappings/rules/coeffindex.h0000664000175000017500000000257115246725757024515 0ustar alastairalastair#pragma once #include #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::rules::impl { template void extractCoeffindex(const std::string& keyword, const metkit::codes::CodesHandle& grib, MarsDict& mars, MiscDict& misc, const OptDict_t& opts) { using metkit::grib2mars::utils::dict_traits::set_or_throw; using metkit::grib2mars::utils::exceptions::Grib2MarsGenericException; try { (void)opts; if (!grib.has(keyword)) { throw Grib2MarsGenericException( "Missing GRIB key `" + keyword + "` required to extract MARS keyword `" + keyword + "`", Here()); } const long value = grib.getLong(keyword); set_or_throw(mars, keyword, value); if (grib.has("numberOfFourierCoefficients")) { const long numberOfFourierCoefficients = grib.getLong("numberOfFourierCoefficients"); misc.set("numberOfFourierCoefficients", numberOfFourierCoefficients); } } catch (...) { std::throw_with_nested(Grib2MarsGenericException("Failed to extract MARS keyword `" + keyword + "`", Here())); } } } // namespace metkit::grib2mars::rules::impl metkit-1.20.2/src/metkit/grib2mars/api/0000775000175000017500000000000015246725757020026 5ustar alastairalastairmetkit-1.20.2/src/metkit/grib2mars/api/Grib2Mars.cc0000664000175000017500000000576615246725757022143 0ustar alastairalastair/* * (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 Grib2Mars.cc /// @brief Implementation of the public Grib2Mars conversion API. /// /// The implementation forwards each supported conversion through the rule /// layer and wraps the result in the common API error-handling policy. /// #include "Grib2Mars.h" #include "eckit/config/LocalConfiguration.h" #include "eckit/exception/Exceptions.h" #include "metkit/grib2mars/mappings/mappings.h" #include "metkit/mars/MarsRequest.h" // dictionary access traits #include "metkit/grib2mars/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/grib2mars/utils/dictionary_traits/dictaccess_mars_request.h" #include "metkit/grib2mars/utils/dictionary_traits/dictaccess_options.h" #include "metkit/grib2mars/utils/dictionary_traits/dictionary_access_traits.h" // Error handling utilities #include "Grib2MarsApiErrorHandling.h" #include "metkit/grib2mars/api/readOptionsFromInitializerList.h" #include "metkit/grib2mars/api/readOptionsFromLocalConfiguration.h" namespace metkit::grib2mars { // ----------------------------------------------------------------------------- // Grib2Mars construction // ----------------------------------------------------------------------------- /// @brief Default construct a Grib2Mars converter. Grib2Mars::Grib2Mars() : opts_{} {} Grib2Mars::Grib2Mars(const Options& opts) : opts_{opts} {} Grib2Mars::Grib2Mars(const eckit::LocalConfiguration& opts) : opts_{detail::readOptions(opts)} {} Grib2Mars::Grib2Mars(OptionList opts) : opts_{detail::readOptions(opts)} {} /// @brief Convert an `eckit::LocalConfiguration` request. template <> Grib2MarsResult Grib2Mars::convert( const metkit::codes::CodesHandle& grib) { using metkit::grib2mars::utils::exceptions::withGrib2MarsApiErrorHandling; return withGrib2MarsApiErrorHandling>( "Grib2Mars::convert", opts_, [&]() { return rules::convertAll(grib, opts_); }, Here()); } /// @brief Convert a `metkit::mars::MarsRequest` request. template <> Grib2MarsResult Grib2Mars::convert( const metkit::codes::CodesHandle& grib) { using metkit::grib2mars::utils::exceptions::withGrib2MarsApiErrorHandling; return withGrib2MarsApiErrorHandling>( "Grib2Mars::convert", opts_, [&]() { return rules::convertAll(grib, opts_); }, Here()); } } // namespace metkit::grib2mars metkit-1.20.2/src/metkit/grib2mars/api/Grib2MarsApiErrorHandling.h0000664000175000017500000002771215246725757025111 0ustar alastairalastair/* * (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 Grib2MarsApiErrorHandling.h /// @brief Common error handling for grib2mars public API calls. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "metkit/grib2mars/api/Options.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::utils::exceptions { namespace detail { inline bool envVarEnablesStackMode(const char* name) { const char* value = std::getenv(name); if (value == nullptr) { return false; } std::string normalized; for (const unsigned char* p = reinterpret_cast(value); *p != '\0'; ++p) { normalized.push_back(static_cast(std::tolower(*p))); } return normalized == "1" || normalized == "true" || normalized == "on" || normalized == "yes"; } /// @brief Sanitize a string for use in a generated filename. inline std::string sanitizeForFileName(const std::string& value) { std::string out; out.reserve(value.size()); for (const unsigned char c : value) { if (std::isalnum(c) || c == '_' || c == '-' || c == '.') { out.push_back(static_cast(c)); } else { out.push_back('_'); } } return out; } /// @brief Convert the current thread identifier into a filename-safe string. inline std::string currentThreadIdAsString() { std::ostringstream os; os << std::this_thread::get_id(); return sanitizeForFileName(os.str()); } /// @brief Resolve the directory where stack files are stored. inline eckit::PathName errorStackDirectory(const metkit::grib2mars::Options& opts) { return eckit::PathName(opts.errorStackPath).fullName(); } /// @brief Build a unique error-stack file path for an API entry point. inline eckit::PathName makeErrorStackFilePath(const metkit::grib2mars::Options& opts, const std::string& apiName) { static std::atomic counter{0}; const eckit::PathName dir = errorStackDirectory(opts); try { dir.mkdir(); } catch (const std::exception& e) { throw eckit::UserError("Unable to create Grib2Mars error stack directory `" + dir.asString() + "`: " + e.what(), Here()); } std::ostringstream fileName; fileName << "grib2mars-error-stack-" << sanitizeForFileName(apiName) << "-pid" << static_cast(::getpid()) << "-tid" << currentThreadIdAsString() << "-" << counter.fetch_add(1, std::memory_order_relaxed) << ".log"; return dir / fileName.str(); } /// @brief Format an eckit code location as file:line. inline std::string formatCodeLocation(const eckit::CodeLocation& loc) { std::ostringstream os; os << loc.file() << ":" << loc.line(); return os.str(); } /// @brief Information extracted from the innermost exception. struct InnermostExceptionInfo { std::string message; eckit::CodeLocation location; }; /// @brief Extract the location from an eckit exception. /// /// If the exception is not an eckit exception, fall back to the API boundary /// location. inline eckit::CodeLocation exceptionLocationOrFallback(const std::exception& exception, const eckit::CodeLocation& fallback) { if (const auto* eckitException = dynamic_cast(&exception)) { return eckitException->location(); } return fallback; } /// @brief Recursively find the innermost std::nested_exception payload. inline InnermostExceptionInfo innermostExceptionInfo(const std::exception& exception, const eckit::CodeLocation& fallbackLocation) { const InnermostExceptionInfo current{ exception.what(), exceptionLocationOrFallback(exception, fallbackLocation), }; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return current; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return current; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { return innermostExceptionInfo(nested, current.location); } catch (...) { return InnermostExceptionInfo{ "", current.location, }; } return current; } /// @brief Serialize a grib2mars exception stack to a stream. inline void writeStructuredExceptionFrame(const std::exception& exception, std::ostream& out, std::size_t level, std::size_t frame) { const std::string pad = indent(level); out << pad << "+ " << std::string(lineSize, '=') << '\n' << pad << "+ frame " << frame << '\n' << pad << "+ " << std::string(lineSize, '-') << '\n'; if (const auto* me = dynamic_cast(&exception)) { me->printFrame(pad, out); } else { out << pad << "+ message: " << exception.what() << '\n'; } out << pad << "+ " << std::string(lineSize, '+') << '\n'; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { writeStructuredExceptionFrame(nested, out, level + 1, frame + 1); } catch (...) { out << pad << "+ message: \n"; } } /// @brief Write the structured API-boundary stack header to a stream. inline void writeStructuredStackHeader(std::ostream& out, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation(), const std::string* errorStackFilePath = nullptr) { out << "Grib2Mars API failure\n"; out << "API entry point: " << apiName << "\n"; if (errorStackFilePath != nullptr) { out << "Error stack file: " << *errorStackFilePath << "\n"; } out << "\n"; out << "Nested exception stack:\n"; out << "-----------------------\n"; out << "+ " << std::string(lineSize, '=') << '\n' << "+ frame 0\n" << "+ " << std::string(lineSize, '-') << '\n'; out << "+ file: " << loc.file() << "\n" << "+ function: " << loc.func() << "\n" << "+ line: " << loc.line() << "\n" << "+ link: " << loc.file() << ":" << loc.line() << "\n" << "+ message: Error encountered at: " << apiName << "\n" << "+ " << std::string(lineSize, '+') << '\n'; } /// @brief Serialize a grib2mars exception stack to disk. inline void writeGrib2MarsExceptionStackToFile(const Grib2MarsGenericException& exception, const std::string& apiName, const eckit::PathName& errorStackFilePath, const eckit::CodeLocation& loc = eckit::CodeLocation()) { std::ofstream out(errorStackFilePath.asString()); if (!out) { throw eckit::UserError("Grib2Mars conversion failed, but the error stack file could not be opened: `" + errorStackFilePath.asString() + "`", Here()); } const std::string path = errorStackFilePath.asString(); writeStructuredStackHeader(out, apiName, loc, &path); writeStructuredExceptionFrame(exception, out, 1, 1); } /// @brief Print a grib2mars exception stack to standard error. inline void writeGrib2MarsExceptionStackToStdErr(const Grib2MarsGenericException& exception, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation()) { writeStructuredStackHeader(std::cerr, apiName, loc); writeStructuredExceptionFrame(exception, std::cerr, 1, 1); } /// @brief Build the short public-facing error message. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner, const eckit::PathName& errorStackFilePath) { std::ostringstream os; os << "Grib2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Full error stack written to `" << errorStackFilePath.asString() << "`"; return os.str(); } /// @brief Build the short public-facing error message when stack saving is disabled. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner) { std::ostringstream os; os << "Grib2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << ")."; return os.str(); } /// @brief Build the public-facing message when stack-file writing also failed. inline std::string buildPublicErrorMessageWithoutStackFile(const InnermostExceptionInfo& inner, const std::exception& stackWriteError) { std::ostringstream os; os << "Grib2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Additionally, the full error stack could not be written: " << stackWriteError.what(); return os.str(); } } // namespace detail /// @brief Execute a callable under the grib2mars API error boundary. /// /// @tparam Result /// Callable return type. /// /// @tparam Callable /// Callable type. /// /// @param[in] apiName /// API entry-point name used in diagnostics. /// /// @param[in] callable /// Operation to execute. /// /// @param[in] loc /// Source location attached to user-facing failures. template Result withGrib2MarsApiErrorHandling(const std::string& apiName, const metkit::grib2mars::Options& opts, Callable&& callable, const eckit::CodeLocation& loc = eckit::CodeLocation()) { try { return std::forward(callable)(); } catch (const Grib2MarsGenericException& e) { const detail::InnermostExceptionInfo inner = detail::innermostExceptionInfo(e, loc); const bool printErrorStackToStdErr = opts.printErrorStackToStdErr || detail::envVarEnablesStackMode("GRIB2MARS_PRINT_STACK_TO_STDERR"); const bool saveErrorStack = opts.saveErrorStack || detail::envVarEnablesStackMode("GRIB2MARS_WRITE_STACK_TO_FILE"); if (printErrorStackToStdErr) { detail::writeGrib2MarsExceptionStackToStdErr(e, apiName, loc); } if (!saveErrorStack) { throw eckit::UserError(detail::buildPublicErrorMessage(inner), inner.location); } try { const eckit::PathName errorStackFilePath = detail::makeErrorStackFilePath(opts, apiName); detail::writeGrib2MarsExceptionStackToFile(e, apiName, errorStackFilePath, loc); throw eckit::UserError(detail::buildPublicErrorMessage(inner, errorStackFilePath), inner.location); } catch (const std::exception& stackWriteError) { throw eckit::UserError(detail::buildPublicErrorMessageWithoutStackFile(inner, stackWriteError), inner.location); } } } } // namespace metkit::grib2mars::utils::exceptions metkit-1.20.2/src/metkit/grib2mars/api/Grib2Mars.h0000664000175000017500000001072515246725757021774 0ustar alastairalastair/* * (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 Grib2Mars.h /// @brief Public conversion API for transforming MARS requests. /// /// This header exposes the primary grib2mars entry point. Callers provide a /// supported dictionary type and receive a structured result containing the /// converted MARS request plus auxiliary conversion metadata. /// /// The API is intentionally narrow and explicit: /// - only supported dictionary types may be converted /// - conversion is delegated to the rule engine /// - all failures are routed through the shared API error boundary /// /// @section References /// Implementation: /// - @ref Grib2Mars.cc /// /// Result type: /// - @ref Grib2MarsReturnValue.h /// /// @ingroup grib2mars_api /// #pragma once #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsRequest.h" // Codes wrapper types #include "metkit/codes/api/CodesAPI.h" #include "metkit/grib2mars/api/Options.h" #include "metkit/grib2mars/mappings/Grib2MarsReturnValue.h" namespace metkit::grib2mars { /// /// @brief Main API for converting MARS requests. /// /// The converter stores a fixed runtime options snapshot. It is a thin façade /// around the conversion rules and the shared API error handling layer. /// class Grib2Mars { public: using OptionEntry = std::pair; using OptionList = std::initializer_list; /// /// @brief Construct a Grib2Mars converter with default options. /// /// Default options correspond to standard grib2mars behavior. /// Grib2Mars(); /// /// @brief Construct a Grib2Mars converter with explicit options. /// /// @param[in] opts /// API options controlling error-stack persistence and diagnostics. /// explicit Grib2Mars(const Options& opts); /// /// @brief Construct a Grib2Mars converter from a configuration object. /// /// This constructor allows options to be provided via an /// `eckit::LocalConfiguration`, typically originating from YAML /// or JSON configuration files. /// /// @param[in] opts /// Configuration object describing converter options. /// explicit Grib2Mars(const eckit::LocalConfiguration& opts); /// /// @brief Construct a Grib2Mars converter from inline option entries. /// /// This constructor supports compact inline configuration such as: /// /// @code /// Grib2Mars{{"printErrorStackToStdErr", true}, {"saveErrorStack", true}} /// @endcode /// /// @param[in] opts /// Initializer-list option entries. /// explicit Grib2Mars(OptionList opts); Grib2Mars(const Grib2Mars&) = delete; Grib2Mars(Grib2Mars&&) = delete; Grib2Mars operator=(const Grib2Mars&) = delete; Grib2Mars operator=(Grib2Mars&&) = delete; ~Grib2Mars() = default; /// @brief Convert a supported dictionary type. /// /// The primary template is deleted intentionally. Only explicit /// specializations are available through the public API. /// /// @tparam Dict_t /// Input dictionary type. /// /// @param[in] mars /// Input MARS dictionary. /// /// @return /// A converted result for the supported dictionary type. template Grib2MarsResult convert(const metkit::codes::CodesHandle& grib) = delete; private: const Options opts_; }; // ----------------------------------------------------------------------------- // Supported API specializations // ----------------------------------------------------------------------------- /// @brief Convert an `eckit::LocalConfiguration` request. template <> Grib2MarsResult Grib2Mars::convert( const metkit::codes::CodesHandle& grib); /// @brief Convert a `metkit::mars::MarsRequest` request. template <> Grib2MarsResult Grib2Mars::convert( const metkit::codes::CodesHandle& grib); } // namespace metkit::grib2mars metkit-1.20.2/src/metkit/grib2mars/api/readOptionsFromInitializerList.h0000664000175000017500000000633015246725757026354 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromInitializerList.h /// @brief Parse Grib2Mars Options from a compact initializer list. /// /// This file is intended to be included into `Grib2Mars.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/value/Value.h" #include "metkit/grib2mars/api/Grib2Mars.h" #include "metkit/grib2mars/api/Options.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::detail { namespace exceptions = metkit::grib2mars::utils::exceptions; [[noreturn]] inline void throwInvalidOptionType(std::string_view key, const eckit::Value& value, std::string_view expected) { throw exceptions::Grib2MarsDictException("Option `" + std::string(key) + "` has value type `" + value.typeName() + "`; expected " + std::string(expected), Here()); } inline bool readBool(std::string_view key, const eckit::Value& value) { if (!value.isBool()) { throwInvalidOptionType(key, value, "bool"); } return value.as(); } inline std::string readString(std::string_view key, const eckit::Value& value) { if (!value.isString()) { throwInvalidOptionType(key, value, "string"); } return value.as(); } inline void applyOption(metkit::grib2mars::Options& opts, std::string_view key, const eckit::Value& value) { if (key == "saveErrorStack") { opts.saveErrorStack = readBool(key, value); return; } if (key == "errorStackPath") { opts.errorStackPath = readString(key, value); return; } if (key == "printErrorStackToStdErr") { opts.printErrorStackToStdErr = readBool(key, value); return; } if (key == "skipSection3") { opts.skipSection3 = readBool(key, value); return; } if (key == "tryFixBadInput_ZeroAccumulation") { opts.tryFixBadInput_ZeroAccumulation = readBool(key, value); return; } if (key == "tryFixBadInput_RemoveStepRangeForStrikeProbability") { opts.tryFixBadInput_RemoveStepRangeForStrikeProbability = readBool(key, value); return; } if (key == "tryFixBadInput_StandardisedAnomalyAsInstant") { opts.tryFixBadInput_StandardisedAnomalyAsInstant = readBool(key, value); return; } throw exceptions::Grib2MarsDictException("Unknown Grib2Mars option `" + std::string(key) + "`", Here()); } inline metkit::grib2mars::Options readOptions(metkit::grib2mars::Grib2Mars::OptionList entries) { metkit::grib2mars::Options opts; std::unordered_set seen; seen.reserve(entries.size()); for (const auto& [key, value] : entries) { if (!seen.insert(key).second) { throw exceptions::Grib2MarsDictException("Duplicate Grib2Mars option `" + key + "`", Here()); } applyOption(opts, key, value); } return opts; } } // namespace metkit::grib2mars::detail metkit-1.20.2/src/metkit/grib2mars/api/Options.h0000664000175000017500000001131515246725757021633 0ustar alastairalastair/* * (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 Options.h /// @brief Configuration options for the Grib2Mars conversion API. /// /// This header defines the public configuration structure used to control the /// behaviour of the Grib2Mars API error boundary. /// /// `Options` is a fully materialised, strongly typed policy object. A caller may /// construct it directly, or the API may populate it from another option source, /// such as an `eckit::LocalConfiguration` or an initializer list. /// /// Every member has an explicit default. Consequently, a default-constructed /// `Options` object is always valid as an option dictionary. /// /// The options control: /// /// - whether a detailed structured error stack is saved to a file; /// - where stack files are written when stack saving is enabled; /// - whether the detailed structured error stack is printed to standard error. /// /// @ingroup grib2mars_api /// #pragma once #include namespace metkit::grib2mars { namespace defaults { inline constexpr bool saveErrorStack = false; inline constexpr const char* errorStackPath = "./"; inline constexpr bool printErrorStackToStdErr = false; inline constexpr bool skipSection3 = false; inline constexpr bool tryFixBadInput_ZeroAccumulation = false; inline constexpr bool tryFixBadInput_RemoveStepRangeForStrikeProbability = false; inline constexpr bool tryFixBadInput_StandardisedAnomalyAsInstant = false; } // namespace defaults /// /// @brief API error-handling options for Grib2Mars. /// /// The structure is intentionally a plain aggregate: /// /// - direct programmatic construction remains simple; /// - every option can be copied into a stable policy snapshot; /// - dictionary traits can expose the structure through the same typed access /// API used for other Grib2Mars dictionaries; /// - adding an option does not require virtual dispatch or ownership machinery. /// struct Options { /// /// @brief Save the detailed API error stack to a file. /// /// When enabled, user-facing API failures persist the detailed structured /// exception stack to a file under `errorStackPath`, and the thrown public /// exception includes the full path to that file. /// /// @default false /// bool saveErrorStack = defaults::saveErrorStack; /// /// @brief Directory where API error-stack files are written. /// /// This path is used only when `saveErrorStack` is enabled. /// /// @default "./" /// std::string errorStackPath = defaults::errorStackPath; /// /// @brief Print the detailed API error stack to standard error. /// /// When enabled, user-facing API failures also emit the structured nested /// exception stack to `std::cerr`. /// /// @default false /// bool printErrorStackToStdErr = defaults::printErrorStackToStdErr; /// /// @brief Skip explicit encoding of GRIB Section 3. /// /// When enabled, the encoder does not encode the Grid Definition Section /// itself and leaves geometry handling to gridSpec/ecCodes. /// /// @default false /// bool skipSection3 = defaults::skipSection3; /// /// @brief Attempt to fix bad input when the accumulation period is zero. /// /// When enabled, the encoder attempts to fix bad input when the accumulation /// period is zero. /// /// @default false /// bool tryFixBadInput_ZeroAccumulation = defaults::tryFixBadInput_ZeroAccumulation; /// /// @brief Attempt to fix bad input for strike probability parameters. /// /// When enabled, the encoder attempts to fix bad input for strike probability parameters. /// /// @default false /// bool tryFixBadInput_RemoveStepRangeForStrikeProbability = defaults::tryFixBadInput_RemoveStepRangeForStrikeProbability; /// /// /// @brief Attempt to fix bad input for standard anomaly parameters encoded as instant. /// /// When enabled, the encoder attempts to fix bad input for standard anomaly parameters encoded as instant. /// /// @default false /// bool tryFixBadInput_StandardisedAnomalyAsInstant = defaults::tryFixBadInput_StandardisedAnomalyAsInstant; }; } // namespace metkit::grib2mars metkit-1.20.2/src/metkit/grib2mars/api/readOptionsFromLocalConfiguration.h0000664000175000017500000000506215246725757027020 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromLocalConfiguration.h /// @brief Parse Grib2Mars Options from eckit::LocalConfiguration. /// /// This file is intended to be included into `Grib2Mars.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/grib2mars/api/Options.h" #include "metkit/grib2mars/utils/grib2marsExceptions.h" namespace metkit::grib2mars::detail { inline bool readBoolOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isBoolean(std::string{key})) { throw utils::exceptions::Grib2MarsDictException( "Option `" + std::string(key) + "` must be a bool in eckit::LocalConfiguration", Here()); } return conf.getBool(std::string{key}); } inline std::string readStringOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isString(std::string{key})) { throw utils::exceptions::Grib2MarsDictException( "Option `" + std::string(key) + "` must be a string in eckit::LocalConfiguration", Here()); } return conf.getString(std::string{key}); } inline Options readOptions(const eckit::LocalConfiguration& conf) { Options opts; if (conf.has("saveErrorStack")) { opts.saveErrorStack = readBoolOption(conf, "saveErrorStack"); } if (conf.has("errorStackPath")) { opts.errorStackPath = readStringOption(conf, "errorStackPath"); } if (conf.has("printErrorStackToStdErr")) { opts.printErrorStackToStdErr = readBoolOption(conf, "printErrorStackToStdErr"); } if (conf.has("skipSection3")) { opts.skipSection3 = readBoolOption(conf, "skipSection3"); } if (conf.has("tryFixBadInput_ZeroAccumulation")) { opts.tryFixBadInput_ZeroAccumulation = readBoolOption(conf, "tryFixBadInput_ZeroAccumulation"); } if (conf.has("tryFixBadInput_RemoveStepRangeForStrikeProbability")) { opts.tryFixBadInput_RemoveStepRangeForStrikeProbability = readBoolOption(conf, "tryFixBadInput_RemoveStepRangeForStrikeProbability"); } if (conf.has("tryFixBadInput_StandardisedAnomalyAsInstant")) { opts.tryFixBadInput_StandardisedAnomalyAsInstant = readBoolOption(conf, "tryFixBadInput_StandardisedAnomalyAsInstant"); } return opts; } } // namespace metkit::grib2mars::detail metkit-1.20.2/src/metkit/tool/0000775000175000017500000000000015246725757016342 5ustar alastairalastairmetkit-1.20.2/src/metkit/tool/MetkitTool.cc0000664000175000017500000000564515246725757020756 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "eckit/system/Library.h" #include "metkit/config/LibMetkit.h" #include "metkit/tool/MetkitTool.h" #include "eckit/system/LibraryManager.h" using namespace eckit; using namespace eckit::option; namespace metkit { //---------------------------------------------------------------------------------------------------------------------- static MetkitTool* instance_ = nullptr; MetkitTool::MetkitTool(int argc, char** argv) : eckit::Tool(argc, argv, "METKIT_HOME") { ASSERT(instance_ == nullptr); instance_ = this; options_.push_back(new SimpleOption("version", "Prints the version and exits")); options_.push_back(new SimpleOption("porcelain", "Stable output that can be used as input to other tools")); } static void usage(const std::string& tool) { ASSERT(instance_); instance_->usage(tool); } void MetkitTool::run() { LOG_DEBUG_LIB(LibMetkit) << "MetkitTool::run()" << std::endl; CmdArgs args(&metkit::usage, options_, numberOfPositionalArguments(), minimumPositionalArguments()); if (args.has("version")) { auto& log = eckit::Log::info(); using eckit::system::LibraryManager; for (const auto& lib_name : LibraryManager::list()) { auto& lib = LibraryManager::lookup(lib_name); log << lib.name() << " " << lib.version() << " git-sha1:" << lib.gitsha1(8) << " home:" << lib.libraryHome() << std::endl; } return; //< stops processing } init(args); execute(args); finish(args); } void MetkitTool::usage(const std::string& tool) const { Log::info() << "Usage: " << tool << "[-h] [...options...]" << std::endl; } void MetkitTool::init(const eckit::option::CmdArgs& args) { LOG_DEBUG_LIB(LibMetkit) << "MetkitTool::init()" << std::endl; args.get("porcelain", porcelain_); } void MetkitTool::finish(const eckit::option::CmdArgs& args) { LOG_DEBUG_LIB(LibMetkit) << "MetkitTool::finish()" << std::endl; } //---------------------------------------------------------------------------------------------------------------------- MetkitToolException::MetkitToolException(const std::string& w) : Exception(w) {} MetkitToolException::MetkitToolException(const std::string& w, const eckit::CodeLocation& l) : Exception(w, l) {} //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/tool/MetkitTool.h0000664000175000017500000000375015246725757020613 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 MetkitTool.h /// @author Tiago Quintino /// @date Mar 2020 #ifndef metkit_MetkitTool_H #define metkit_MetkitTool_H #include #include "eckit/exception/Exceptions.h" #include "eckit/runtime/Tool.h" namespace eckit { namespace option { class Option; class CmdArgs; } // namespace option } // namespace eckit namespace metkit { //---------------------------------------------------------------------------------------------------------------------- class MetkitTool : public eckit::Tool { public: // methods MetkitTool(int argc, char** argv); virtual void usage(const std::string& tool) const; protected: // methods using options_t = std::vector; protected: // members options_t options_; bool porcelain_ = false; private: // methods virtual void init(const eckit::option::CmdArgs& args); virtual void execute(const eckit::option::CmdArgs& args) = 0; virtual void finish(const eckit::option::CmdArgs& args); virtual int numberOfPositionalArguments() const { return -1; } virtual int minimumPositionalArguments() const { return -1; } virtual void run(); }; //---------------------------------------------------------------------------------------------------------------------- class MetkitToolException : public eckit::Exception { public: MetkitToolException(const std::string&); MetkitToolException(const std::string&, const eckit::CodeLocation&); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit #endif metkit-1.20.2/src/metkit/config/0000775000175000017500000000000015246725757016632 5ustar alastairalastairmetkit-1.20.2/src/metkit/config/LibMetkit.h0000664000175000017500000000314215246725757020667 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @author Tiago Quintino /// @date August 2016 #ifndef mir_LibMetkit_H #define mir_LibMetkit_H #include "eckit/filesystem/PathName.h" #include "eckit/system/Library.h" //---------------------------------------------------------------------------------------------------------------------- namespace metkit { class LibMetkit : public eckit::system::Library { public: LibMetkit(); static eckit::PathName configFile(const std::string& filename); static eckit::PathName languageYamlFile(); static std::vector modifiersYamlFiles(); static eckit::PathName paramYamlFile(); static eckit::PathName paramStaticYamlFile(); static eckit::PathName paramIDYamlFile(); static eckit::PathName paramMatchingYamlFile(); static eckit::PathName shortnameContextYamlFile(); static eckit::PathName bufrSubtypesYamlFile(); static const LibMetkit& instance(); protected: const void* addr() const; virtual std::string version() const; virtual std::string gitsha1(unsigned int count) const; }; } // namespace metkit //---------------------------------------------------------------------------------------------------------------------- #endif metkit-1.20.2/src/metkit/config/LibMetkit.cc0000664000175000017500000000473615246725757021037 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @author Tiago Quintino /// @date August 2016 #include "metkit/config/LibMetkit.h" #include #include "eckit/config/Resource.h" #include "metkit/metkit_version.h" namespace metkit { //---------------------------------------------------------------------------------------------------------------------- REGISTER_LIBRARY(LibMetkit); LibMetkit::LibMetkit() : Library("metkit") {} const LibMetkit& LibMetkit::instance() { static LibMetkit libmetkit; return libmetkit; } const void* LibMetkit::addr() const { return this; } std::string LibMetkit::version() const { return metkit_version_str(); } std::string LibMetkit::gitsha1(unsigned int count) const { std::string sha1(metkit_git_sha1()); if (sha1.empty()) { return "not available"; } return sha1.substr(0, std::min(count, 40u)); } eckit::PathName LibMetkit::configFile(const std::string& filename) { return eckit::PathName{"~metkit/share/metkit"} / filename; } eckit::PathName LibMetkit::languageYamlFile() { return LibMetkit::configFile("language.yaml"); } std::vector LibMetkit::modifiersYamlFiles() { return std::vector{LibMetkit::configFile("modifiers.yaml"), LibMetkit::configFile("reportype.yaml")}; } eckit::PathName LibMetkit::paramYamlFile() { return LibMetkit::configFile("params.yaml"); } eckit::PathName LibMetkit::paramStaticYamlFile() { return LibMetkit::configFile("params-static.yaml"); } eckit::PathName LibMetkit::shortnameContextYamlFile() { return LibMetkit::configFile("shortname-context.yaml"); } eckit::PathName LibMetkit::paramIDYamlFile() { return LibMetkit::configFile("paramids.yaml"); } eckit::PathName LibMetkit::paramMatchingYamlFile() { return LibMetkit::configFile("param-matching.yaml"); } eckit::PathName LibMetkit::bufrSubtypesYamlFile() { return LibMetkit::configFile("bufr-subtypes.yaml"); } } // namespace metkit //---------------------------------------------------------------------------------------------------------------------- metkit-1.20.2/src/metkit/mars/0000775000175000017500000000000015246725757016327 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars/TypeAny.cc0000664000175000017500000000207215246725757020230 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeAny.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeAny::TypeAny(const std::string& name, const eckit::Value& settings) : Type(name, settings) {} void TypeAny::print(std::ostream& out) const { out << "TypeAny[name=" << name_ << "]"; } bool TypeAny::expand(std::string&, const MarsRequest&) const { return true; } static TypeBuilder type("any"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Matcher.h0000664000175000017500000000615515246725757020072 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ /// @author Chris Bradley /// @date Sep 2025 #pragma once #include #include #include #include #include #include "eckit/utils/Regex.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { class RequestLike; /// Parse a match expression into a map of key:regex pairs /// @param expr is a series of comma separated key:regex pairs e.g. /// "expver=(0001|o[0-9a-z]{3}),dataset=^climate-dt$" std::map parseMatchString(const std::string& expr); // ------------------------------------------------------------------------------------------------------------- /// For matching requests against a set of key:regex conditions. For constructing select/exclude filters. class Matcher { public: /// Policy for handling requests with multiple values for a given key. enum class Policy { All, ///< Require all values to match Any ///< Require at least one value to match }; /// Policy for handling keys in the matcher that are absent in the request. enum class MatchMissingPolicy { MatchOnMissing, DontMatchOnMissing }; static constexpr auto MatchOnMissing = MatchMissingPolicy::MatchOnMissing; static constexpr auto DontMatchOnMissing = MatchMissingPolicy::DontMatchOnMissing; public: /// Construct a matcher from a map of key:regex pairs Matcher(std::map regexMap, Policy policy); Matcher(const std::string& expr, Policy policy); bool match(const RequestLike& request, MatchMissingPolicy matchOnMissing = MatchOnMissing) const; bool match(const MarsRequest& request, MatchMissingPolicy matchOnMissing = MatchOnMissing) const; void print(std::ostream& s) const; friend std::ostream& operator<<(std::ostream& s, const Matcher& m) { m.print(s); return s; } private: std::map regexMap_; Policy policy_; }; // ---------------------------------------------------------------------------------------------------------------------- /// Interface to access keyword / value(s) pairs across similar types. /// Designed to allow uniform access to keywords / values for MarsRequest / fdb5::Key class RequestLike { public: using values_t = std::variant, ///< single value std::reference_wrapper> ///< multiple values >; virtual ~RequestLike() = default; /// Get a reference_wrapper to the value(s) for the given keyword, or nullopt if not found virtual std::optional get(const std::string& keyword) const = 0; }; } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/StepRange.cc0000664000175000017500000001050615246725757020530 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/StepRange.h" #include "metkit/mars/TypeRange.h" #include "metkit/mars/TypeTime.h" #include "eckit/exception/Exceptions.h" #include "eckit/persist/DumpLoad.h" #include "eckit/utils/Tokenizer.h" using namespace eckit; //---------------------------------------------------------------------------------------------------------------------- namespace { // enum TimeUnit { // Second = 0, // Minute = 1, // Hour = 2, // Day = 3 // }; // TimeUnit maxUnit(const eckit::Time& t) { // if (t.seconds() == 0) { // if (t.minutes() == 0) { // if (t.hours() == 0) { // return TimeUnit::Day; // } // return TimeUnit::Hour; // } // return TimeUnit::Minute; // } // return TimeUnit::Second; // } std::string canonical(const eckit::Time& time, bool useDays, bool alwaysUseHours) { long h = time.hours(); long m = time.minutes(); long s = time.seconds(); long d = 0; if (useDays && h > 23) { d = h / 24; h = h % 24; } std::string out = ""; if (d != 0) { out += std::to_string(d) + "d"; } if (h != 0 || (m == 0 && s == 0 && d == 0)) { out += std::to_string(h); if (alwaysUseHours || m != 0 || s != 0 || d != 0) { out += "h"; } } if (m != 0) { out += std::to_string(m) + "m"; } if (s != 0) { out += std::to_string(s) + "s"; } return out; } // std::string canonical(const eckit::Time& time, TimeUnit unit) { // switch (unit) { // case TimeUnit::Second: // return std::to_string(time.seconds()+60*time.minutes()+3600*time.hours()) + "s"; // case TimeUnit::Minute: // return std::to_string(time.minutes()+60*time.hours()) + "m"; // case TimeUnit::Day: // case TimeUnit::Hour: // default: // return std::to_string(time.hours()); // } // } } // namespace namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- StepRange::operator std::string() const { return toString(); } std::string StepRange::toString(bool useDays, bool alwaysUseHours) const { std::ostringstream os; print(os, useDays, alwaysUseHours); return os.str(); } void StepRange::print(std::ostream& s, bool useDays, bool alwaysUseHours) const { if (from_ == to_) { s << canonical(eckit::Time(std::lround(from_ * 3600.), true), useDays, alwaysUseHours); } else { eckit::Time f{std::lround(from_ * 3600.), true}; eckit::Time t{std::lround(to_ * 3600.), true}; s << canonical(f, useDays, alwaysUseHours) << '-' << canonical(t, useDays, alwaysUseHours); } } StepRange::StepRange(const std::string& s) : from_(0.), to_(0.) { Tokenizer parse("-"); std::vector result; // negative steps are not supported if (s[0] != '-') parse(s, result); switch (result.size()) { case 1: to_ = from_ = eckit::Time(result[0], true) / 3600.; break; case 2: from_ = eckit::Time(result[0], true) / 3600.; to_ = eckit::Time(result[1], true) / 3600.; if (from_ > to_) { std::ostringstream oss; oss << "initial value " << from_ << " cannot be greater that final value " << to_; throw eckit::BadValue(oss.str(), Here()); } break; default: std::ostringstream msg; msg << "Bad StepRange [" << s << "]"; throw eckit::BadValue(msg.str(), Here()); } } void StepRange::dump(DumpLoad& a) const { a.dump(from_); a.dump(to_); } void StepRange::load(DumpLoad& a) { a.load(from_); a.load(to_); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsRequestHandle.h0000664000175000017500000000420115246725757022064 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 MarsRequestHandle.h // Baudouin Raoult - (c) ECMWF Feb 12 #ifndef MarsRequestHandle_H #define MarsRequestHandle_H #include #include #include "eckit/exception/Exceptions.h" #include "eckit/io/DataHandle.h" #include "eckit/types/Types.h" #include "metkit/mars/BaseProtocol.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { class MarsRequestHandle : public eckit::DataHandle { public: class RetryTransfer : public eckit::Exception { bool retryOnClient() const override { return true; } public: RetryTransfer(const std::string& what) : eckit::Exception(what) {} }; public: MarsRequestHandle(eckit::Stream&); MarsRequestHandle(const metkit::mars::MarsRequest& request, const eckit::Configuration& database); MarsRequestHandle(const metkit::mars::MarsRequest& request, metkit::mars::BaseProtocol* protocol); ~MarsRequestHandle(); // -- Overridden methods (from Streamable) std::string className() const override { return "MarsRequestHandle"; } const eckit::ReanimatorBase& reanimator() const override; static const eckit::ClassSpec& classSpec(); const eckit::StringDict& stats() const; private: // members metkit::mars::MarsRequest request_; std::unique_ptr protocol_; bool opened_; private: // members void print(std::ostream&) const override; void encode(eckit::Stream&) const override; eckit::Length openForRead() override; void openForWrite(const eckit::Length&) override; void openForAppend(const eckit::Length&) override; long read(void*, long) override; long write(const void*, long) override; void close() override; bool canSeek() const override; }; } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/StepRangeNormalise.h0000664000175000017500000000561715246725757022253 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Simon Smart /// @date May 2019 #ifndef metkit_StepRangeNormalise_H #define metkit_StepRangeNormalise_H #include #include "eckit/exception/Exceptions.h" #include "metkit/mars/StepRange.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- // It would be nice to put this in StepRange (as is done with ParamID), but this seems // to break the generation of the StepRange.b file. // // -> Split it out class StepRangeNormalise { public: template static void normalise(std::vector& v, const AXIS_T& axis); }; //---------------------------------------------------------------------------------------------------------------------- template void StepRangeNormalise::normalise(std::vector& values, const AXIS_T& axis) { std::vector outputValues; for (eckit::Ordinal i = 0; i < values.size(); ++i) { // If the supplied range is found in the axis, then use that auto j = std::find(axis.begin(), axis.end(), values[i]); if (j != axis.end()) { outputValues.push_back(values[i]); // If specified, and matched, a RANGE, then use that if (values[i].from() != values[i].to()) { eckit::Log::info() << "Matched range: " << values[i] << std::endl; continue; } } bool matched = false; double singleValue = values[i].from(); if (values[i].from() != values[i].to()) { j = std::find(axis.begin(), axis.end(), StepRange(singleValue, singleValue)); if (j != axis.end()) { outputValues.push_back(*j); matched = true; } } // If singleValue == 0, this test is the same as the previous one... if (singleValue != 0) { j = std::find(axis.begin(), axis.end(), StepRange(0, singleValue)); if (j != axis.end()) { if (matched) { eckit::Log::userWarning() << "Step " << values[i] << " matches " << values[i] << " and " << (*j) << std::endl; } outputValues.push_back(*j); } } } std::swap(values, outputValues); } //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/ParamID.cc0000664000175000017500000000636115246725757020121 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/ParamID.h" #include #include #include "eckit/config/Resource.h" #include "eckit/parser/YAMLParser.h" #include "eckit/system/Library.h" #include "eckit/utils/Tokenizer.h" #include "metkit/config/LibMetkit.h" using namespace eckit; namespace metkit { //---------------------------------------------------------------------------------------------------------------------- static std::vector windFamilies_; static std::vector dropTables_; static NormalisationMode normalisationMode_; static std::set mlParamsSingleLevel_; //---------------------------------------------------------------------------------------------------------------------- static pthread_once_t once = PTHREAD_ONCE_INIT; static void readTable() { eckit::PathName paramMatchingPath = eckit::Resource("paramMatchingPath;$PARAM_MATCHING_PATH", LibMetkit::paramMatchingYamlFile()); const eckit::Value paramMatching = eckit::YAMLParser::decodeFile(paramMatchingPath); const eckit::Value wind = paramMatching["wind"]; ASSERT(wind.isList()); for (size_t i = 0; i < wind.size(); ++i) { const eckit::Value s = wind[i]; ASSERT(s.isList()); ASSERT(s.size() == 4); windFamilies_.push_back(ParamID::WindFamily(s[0], s[1], s[2], s[3])); } const eckit::Value dropTables = paramMatching["drop-tables"]; ASSERT(dropTables.isList()); for (size_t i = 0; i < dropTables.size(); ++i) { dropTables_.push_back(dropTables[i]); } normalisationMode_ = NormalisationMode::Loose; if (paramMatching.contains("full-table-dropping")) { const eckit::Value fullTableDropping = paramMatching["full-table-dropping"]; ASSERT(fullTableDropping.isBool()); bool fullTableDroppingValue = fullTableDropping; if (fullTableDroppingValue) { normalisationMode_ = NormalisationMode::FullTableDropping; } } const eckit::Value mlParamsSingleLevel = paramMatching["ml-params-single-level"]; ASSERT(mlParamsSingleLevel.isList()); for (size_t i = 0; i < mlParamsSingleLevel.size(); ++i) { mlParamsSingleLevel_.insert(mlParamsSingleLevel[i]); } } const std::vector& ParamID::getWindFamilies() { pthread_once(&once, readTable); return windFamilies_; } const std::vector& ParamID::getDropTables() { pthread_once(&once, readTable); return dropTables_; } const std::set& ParamID::getMlParamsSingleLevel() { pthread_once(&once, readTable); return mlParamsSingleLevel_; } NormalisationMode ParamID::normalisationMode() { pthread_once(&once, readTable); return normalisationMode_; } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/mars/Parameter.cc0000664000175000017500000000641315246725757020562 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include "metkit/mars/Parameter.h" #include "metkit/mars/Type.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- class UndefinedType : public Type { void print(std::ostream& out) const override { out << ""; } bool expand(std::string&, const MarsRequest&) const override { NOTIMP; } public: UndefinedType() : Type("", eckit::Value()) { attach(); } }; static UndefinedType undefined; //---------------------------------------------------------------------------------------------------------------------- Parameter::Parameter() : type_(&undefined) { type_->attach(); } Parameter::~Parameter() { type_->detach(); } Parameter::Parameter(const std::vector& values, Type* type) : type_(type), values_(values) { if (!type) { type_ = &undefined; } type_->attach(); } Parameter::Parameter(const Parameter& other) : type_(other.type_), values_(other.values_) { type_->attach(); } Parameter& Parameter::operator=(const Parameter& other) { Type* old = type_; type_ = other.type_; type_->attach(); old->detach(); values_ = other.values_; return *this; } void Parameter::values(const std::vector& values) { values_ = values; } bool Parameter::filter(const std::vector& filter) { return type_->filter(filter, values_); } bool Parameter::filter(const std::string& keyword, const std::vector& filter) { return type_->filter(keyword, filter, values_); } bool Parameter::matches(const std::vector& match) const { return type_->matches(match, values_); } void Parameter::merge(const Parameter& p) { ASSERT(name() == p.name()); /// @note this isn't optimal O(N^2) but it respects the order std::vector diff; for (auto& o : p.values_) { bool found = false; for (auto& v : values_) { if (v == o) { found = true; break; } } if (!found) diff.push_back(o); } values_.insert(values_.end(), std::make_move_iterator(diff.begin()), std::make_move_iterator(diff.end())); } const std::string& Parameter::name() const { return type_->name(); } size_t Parameter::count() const { return type_->count(values_); } void Parameter::print(std::ostream& s) const { s << "Parameter[type=" << *type_ << ",values=" << values_ << "]"; } bool Parameter::operator<(const Parameter& other) const { if (name() != other.name()) { return name() < other.name(); } return values_ < other.values_; } //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/MarsExpansion.cc0000664000175000017500000000421515246725757021427 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsLanguage.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- MarsExpansion::MarsExpansion(bool inherit, bool strict) : inherit_(inherit), strict_(strict) {} MarsExpansion::~MarsExpansion() { for (auto& language : languages_) { delete language.second; } } void MarsExpansion::reset() { for (auto& language : languages_) { language.second->reset(); } } MarsLanguage& MarsExpansion::language(const std::string& verb) { auto v = MarsLanguage::expandVerb(verb); if (auto j = languages_.find(v); j != languages_.end()) { return *(*j).second; } auto j = languages_.emplace(v, new MarsLanguage(v)).first; return *(*j).second; } std::vector MarsExpansion::expand(const std::vector& requests) { std::vector result; result.reserve(requests.size()); // Implement inheritence for (const auto& request : requests) { auto& lang = language(request.verb()); result.emplace_back(lang.expand(request, inherit_, strict_)); } return result; } MarsRequest MarsExpansion::expand(const MarsRequest& request) { return language(request.verb()).expand(request, inherit_, strict_); } void MarsExpansion::expand(const MarsRequest& request, ExpandCallback& callback) { callback(expand(request)); } void MarsExpansion::flatten(const MarsRequest& request, FlattenCallback& callback) { language(request.verb()).flatten(request, callback); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeFloat.h0000664000175000017500000000225615246725757020414 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeFloat.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Emanuele Danovaro /// @date April 2016 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeFloat : public Type { public: // methods TypeFloat(const std::string& name, const eckit::Value& settings); ~TypeFloat() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeExpver.h0000664000175000017500000000222415246725757020613 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeExpver.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeExpver : public Type { public: // methods TypeExpver(const std::string& name, const eckit::Value& settings); ~TypeExpver() noexcept override = default; bool expand(std::string& value, const MarsRequest& request) const override; private: // methods void print(std::ostream& out) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsParser.h0000664000175000017500000000327215246725757020563 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2012 #pragma once #include "eckit/parser/StreamParser.h" #include "eckit/types/Types.h" #include "metkit/mars/MarsParsedRequest.h" namespace metkit::mars { class MarsRequest; //---------------------------------------------------------------------------------------------------------------------- class MarsParserCallback { public: virtual void operator()(const MarsRequest&) = 0; protected: virtual ~MarsParserCallback() = default; }; //---------------------------------------------------------------------------------------------------------------------- class MarsParser : public eckit::StreamParser { public: // methods MarsParser(std::istream& in); std::vector parse(); void parse(MarsParserCallback& cb); static void quoted(std::ostream& out, const std::string& value); private: // methods MarsParsedRequest parseRequest(); std::string parseVerb(); std::string parseKeyword(); std::vector parseValues(); std::string parseValue(); std::string parseIndent(); std::string parseIndents(); std::string parseString(char c); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsParsedRequest.h0000664000175000017500000000227715246725757022122 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #pragma once #include "metkit/mars/MarsRequest.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class MarsParsedRequest : public MarsRequest { public: MarsParsedRequest(const std::string& verb, size_t line); virtual ~MarsParsedRequest() = default; virtual void info(std::ostream&) const; friend std::ostream& operator<<(std::ostream& s, const MarsParsedRequest& r) { r.info(s); return s; } private: std::size_t line_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeEnum.cc0000664000175000017500000001101215246725757020377 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeEnum.h" #include "eckit/utils/StringTools.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- void TypeEnum::addValue(const std::string& vv, uint16_t idx, bool allowDuplicates) const { std::string value = eckit::StringTools::lower(vv); if (!allowDuplicates && values_.find(value) != values_.end()) { std::ostringstream oss; oss << "Redefined enum value '" << value << "'"; throw eckit::SeriousBug(oss.str()); } values_[value] = idx; } uint16_t TypeEnum::parseValueNames(const eckit::Value& names, bool allowDuplicates) const { std::string firstName = names.isList() ? names[0] : names; uint16_t idx = groups_.size(); addValue(firstName, idx, allowDuplicates); if (names.isList()) { for (size_t i = 1; i < names.size(); ++i) { addValue(names[i], idx, allowDuplicates); } } if (uppercase_) { firstName = eckit::StringTools::upper(firstName); } groups_.emplace_back(firstName, std::vector{}); return idx; } std::vector TypeEnum::parseEnumValue(const eckit::Value& val, bool allowDuplicates) const { if (val.isMap()) { hasGroups_ = true; ASSERT(val.contains("name")); uint16_t idx = parseValueNames(val["name"], allowDuplicates); ASSERT(val.contains("group")); eckit::Value group = val["group"]; ASSERT(group.isList()); std::set outSet; for (size_t i = 0; i < group.size(); ++i) { std::vector groupVals = parseEnumValue(group[i], true); for (const auto& v : groupVals) { if (outSet.find(v) == outSet.end()) { outSet.insert(v); groups_.at(idx).second.push_back(v); } } } return groups_.at(idx).second; } uint16_t idx = parseValueNames(val, allowDuplicates); std::string nn = groups_.at(idx).first; groups_.at(idx).second.push_back(nn); return {nn}; } void TypeEnum::readValuesFile() const { if (!valuesFile_.empty()) { auto values = MarsLanguage::jsonFile(valuesFile_); ASSERT(values.isList()); for (size_t i = 0; i < values.size(); ++i) { parseEnumValue(values[i]); } } } TypeEnum::TypeEnum(const std::string& name, const eckit::Value& settings) : Type(name, settings) { LOG_DEBUG_LIB(LibMetkit) << "TypeEnum name=" << name << " settings=" << settings << std::endl; eckit::Value values = settings["values"]; if (settings.contains("uppercase")) { uppercase_ = settings["uppercase"]; } if (!values.isList()) { valuesFile_ = static_cast(values); } else { for (size_t i = 0; i < values.size(); ++i) { parseEnumValue(values[i]); } } } void TypeEnum::print(std::ostream& out) const { out << "TypeEnum[name=" << name_ << "]"; } bool TypeEnum::expand(std::string& value, const MarsRequest& request) const { auto it = find(value); if (it == values_.end()) { return false; } ASSERT(groups_.size() > it->second); value = groups_.at(it->second).first; return true; } std::map::const_iterator TypeEnum::find(const std::string& value) const { std::call_once(readValues_, &TypeEnum::readValuesFile, this); return values_.find(eckit::StringTools::lower(value)); } std::optional>> TypeEnum::group(const std::string& value) const { ASSERT(hasGroups_); auto it = find(value); if (it != values_.end()) { return groups_.at(it->second).second; } return std::nullopt; } void TypeEnum::reset() { Type::reset(); } static TypeBuilder type("enum"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Quantile.cc0000664000175000017500000000607115246725757020424 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "eckit/exception/Exceptions.h" #include "eckit/utils/Tokenizer.h" #include "metkit/mars/Quantile.h" using namespace eckit; namespace metkit { //---------------------------------------------------------------------------------------------------------------------- Quantile::Quantile(const std::string& value) { Tokenizer parse(":"); std::vector result; parse(value, result); if (result.size() != 2) { std::ostringstream oss; oss << "Quantile " << value << " must be in the form :"; throw eckit::BadValue(oss.str()); } try { num_ = std::stol(result[0]); den_ = std::stol(result[1]); } catch (const std::invalid_argument& e) { std::ostringstream oss; oss << "Quantile " << value << " must be in the form :"; throw eckit::BadValue(oss.str()); } check(); } void Quantile::check() const { if (num_ < 0) { std::ostringstream oss; oss << "Quantile numerator " << num_ << " must be non negative"; throw eckit::BadValue(oss.str()); } if (den_ < 0) { std::ostringstream oss; oss << "Quantile denominator " << den_ << " must be non negative"; throw eckit::BadValue(oss.str()); } if (den_ < num_) { std::ostringstream oss; oss << "Quantile numerator " << num_ << " must be less or equal the value of denominator " << den_; throw eckit::BadValue(oss.str()); } } Quantile::Quantile(long num, long den) : num_(num), den_(den) { check(); } Quantile::operator std::string() { std::ostringstream oss; oss << num_ << ':' << den_; return oss.str(); } void Quantile::print(std::ostream& s) const { s << num_ << ':' << den_; } Quantile& Quantile::operator+=(const long& rhs) { num_ += rhs; check(); return *this; } Quantile& Quantile::operator-=(const long& rhs) { num_ -= rhs; check(); return *this; } bool operator==(const Quantile& lhs, const Quantile& rhs) { if (lhs.den() != rhs.den()) { std::ostringstream oss; oss << "Quantile values must belong to the same quantile group"; throw eckit::BadValue(oss.str()); } return (lhs.num() == rhs.num()); } bool operator<(const Quantile& lhs, const Quantile& rhs) { if (lhs.den() != rhs.den()) { std::ostringstream oss; oss << "Quantile values must belong to the same quantile group"; throw eckit::BadValue(oss.str()); } return (lhs.num() < rhs.num()); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/mars/BaseProtocol.cc0000664000175000017500000000634715246725757021244 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 BaseProtocol.cc // Baudouin Raoult - (c) ECMWF Feb 12 #include "metkit/mars/BaseProtocol.h" #include #include #include "eckit/config/Configuration.h" #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "eckit/thread/AutoLock.h" #include "eckit/thread/Mutex.h" #include "eckit/types/Types.h" namespace metkit::mars { static eckit::Mutex* local_mutex = 0; static std::map* m = 0; static pthread_once_t once = PTHREAD_ONCE_INIT; static void init() { local_mutex = new eckit::Mutex(); m = new std::map(); } BaseProtocol::BaseProtocol() = default; BaseProtocol::BaseProtocol(eckit::Stream& s) : eckit::Streamable(s) {} BaseProtocol::BaseProtocol(const eckit::Configuration&) {} const eckit::StringDict& BaseProtocol::stats() const { static const eckit::StringDict empty{}; return empty; } const eckit::ClassSpec& BaseProtocol::classSpec() { static eckit::ClassSpec spec = {&Streamable::classSpec(), "BaseProtocol"}; return spec; } void BaseProtocol::encode(eckit::Stream& s) const { eckit::Streamable::encode(s); } //======================================================================== ProtocolFactory::ProtocolFactory(const std::string& name) : name_(name) { pthread_once(&once, init); eckit::AutoLock lock(local_mutex); if (m->find(name) != m->end()) { throw eckit::SeriousBug("ProtocolFactory: duplication action: " + name); } ASSERT(m->find(name) == m->end()); (*m)[name] = this; } ProtocolFactory::~ProtocolFactory() { eckit::AutoLock lock(local_mutex); m->erase(name_); } BaseProtocol* ProtocolFactory::build(const eckit::Configuration& params) { pthread_once(&once, init); eckit::AutoLock lock(local_mutex); const std::string& name = params.getString("class"); std::map::const_iterator j = m->find(name); if (j == m->end()) { if (j == m->end()) { eckit::Log::error() << "No ProtocolFactory for [" << name << "]" << std::endl; eckit::Log::error() << "ProtocolFactories are:" << std::endl; for (j = m->begin(); j != m->end(); ++j) eckit::Log::error() << " " << (*j).first << std::endl; throw eckit::SeriousBug(std::string("No ProtocolFactory called ") + name); } } return (*j).second->make(params); } void ProtocolFactory::list(std::ostream& out) { pthread_once(&once, init); eckit::AutoLock lock(local_mutex); const char* sep = ""; for (std::map::const_iterator j = m->begin(); j != m->end(); ++j) { out << sep << (*j).first; sep = ", "; } } } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeAny.h0000664000175000017500000000223015246725757020066 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeAny.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeAny : public Type { public: // methods TypeAny(const std::string& name, const eckit::Value& settings = eckit::Value()); ~TypeAny() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeToByListQuantile.h0000664000175000017500000000235215246725757022560 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeToByListQuantile.h /// @author Emanuele Danovaro /// @date February 2022 #pragma once #include "metkit/mars/Quantile.h" #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeToByListQuantile : public Type { public: // methods TypeToByListQuantile(const std::string& name, const eckit::Value& settings); ~TypeToByListQuantile() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; std::set denominators_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Type.cc0000664000175000017500000002751215246725757017566 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/Type.h" #include #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/value/Value.h" #include "metkit/hypercube/HyperCube.h" #include "metkit/mars/ContextRule.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypeToByList.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- void Context::add(std::unique_ptr rule) { rules_.push_back(std::move(rule)); } bool Context::matches(MarsRequest req) const { for (const auto& r : rules_) { if (!r->matches(req)) { return false; } } return true; } std::ostream& operator<<(std::ostream& s, const Context& c) { c.print(s); return s; } void Context::print(std::ostream& out) const { std::string sep; out << "Context["; for (const auto& r : rules_) { out << sep << *r; sep = ","; } out << "]"; } //---------------------------------------------------------------------------------------------------------------------- // HELPERS std::unique_ptr parseRule(std::string key, eckit::Value r) { std::set vals; if (r.isList()) { if (r.size() == 0) { throw eckit::UserError("Empty list for context rule '" + key + "'"); } bool exclude = (r[0] == "!"); for (size_t k = exclude ? 1 : 0; k < r.size(); k++) { vals.insert(r[k]); } if (exclude) return std::make_unique(key, vals); return std::make_unique(key, vals); } else { ASSERT(r.isString()); std::string v = r; if (v == "undefined") { return std::make_unique(key); } else if (v == "defined") { return std::make_unique(key); } } return nullptr; } std::unique_ptr Context::parseContext(eckit::Value c) { std::unique_ptr context = std::make_unique(); eckit::Value keys = c.keys(); for (size_t j = 0; j < keys.size(); j++) { std::string key = keys[j]; context->add(parseRule(key, c[key])); } return context; } size_t Context::maxAxisIndex() const { size_t maxIndex = 0; for (const auto& r : rules_) { size_t idx = 0; if (!r->key().empty() && r->key()[0] != '_') { idx = metkit::hypercube::AxisOrder::instance().index(r->key()); if (idx > maxIndex) { maxIndex = idx; } } } return maxIndex; } //---------------------------------------------------------------------------------------------------------------------- Type::Type(const std::string& name, const eckit::Value& settings) : name_(name), flatten_(true), multiple_(false), duplicates_(true) { if (settings.contains("multiple")) { multiple_ = settings["multiple"]; } if (settings.contains("flatten")) { flatten_ = settings["flatten"]; } if (settings.contains("duplicates")) { duplicates_ = settings["duplicates"]; } if (settings.contains("category")) { category_ = std::string(settings["category"]); } if (settings.contains("defaults")) { eckit::Value defaults = settings["defaults"]; if (!defaults.isNil() && defaults.isList()) { for (size_t i = 0; i < defaults.size(); i++) { std::vector vals; eckit::Value d = defaults[i]; ASSERT(d.contains("vals")); eckit::Value vv = d["vals"]; if (vv.isList()) { for (size_t k = 0; k < vv.size(); k++) { vals.push_back(vv[k]); } } else { vals.push_back(vv); } if (d.contains("context")) { defaults_.emplace(Context::parseContext(d["context"]), vals); } else { defaults_.emplace(std::make_unique(), vals); } } } } } void Type::defaults(std::shared_ptr context, const std::vector& values) { defaults_.emplace(std::move(context), values); } void Type::set(std::shared_ptr context, const std::vector& values) { sets_.emplace(std::move(context), values); } void Type::unset(std::shared_ptr context) { unsets_.insert(std::move(context)); } void Type::patchRequest(MarsRequest& request, const std::vector& values) { // Special case: inheritance from another key. // If the value is of the form _key, then copy values from that key if (values.size() == 1 && values[0][0] == '_') { std::string key = values[0].substr(1); if (request.has(key)) { request.setValuesTyped(this, request.values(key)); } } else { request.setValuesTyped(this, values); } } bool Type::flatten() const { return flatten_; } bool Type::multiple() const { return multiple_; } size_t Type::count(const std::vector& values) const { return flatten_ ? values.size() : 1; } class NotInSet { std::set set_; public: NotInSet(const std::vector& f) : set_(f.begin(), f.end()) {} bool operator()(const std::string& s) const { return set_.find(s) == set_.end(); } }; bool Type::filter(const std::vector& filter, std::vector& values) const { NotInSet not_in_set(filter); values.erase(std::remove_if(values.begin(), values.end(), not_in_set), values.end()); return !values.empty(); } bool Type::filter(const std::string& keyword, const std::vector& f, std::vector& values) const { if (keyword == name_) { return filter(f, values); } auto it = filters_.find(keyword); if (it == filters_.end()) { return false; } return it->second(f, values); } class InSet { std::set set_; public: InSet(const std::vector& f) : set_(f.begin(), f.end()) {} bool operator()(const std::string& s) const { return set_.find(s) != set_.end(); } }; bool Type::matches(const std::vector& match, const std::vector& values) const { InSet in_set(match); return std::find_if(values.begin(), values.end(), in_set) != values.end(); } std::ostream& operator<<(std::ostream& s, const Type& x) { x.print(s); return s; } std::string Type::tidy(const std::string& value, const MarsRequest& request) const { std::string result = value; expand(result, request); return result; } bool Type::expand(std::string& value, const MarsRequest&) const { std::ostringstream oss; oss << *this << ": expand not implemented (" << value << ")"; throw eckit::SeriousBug(oss.str()); } void Type::expand(std::vector& values, const MarsRequest& request) const { if (toByList_ && values.size() > 1) { toByList_->expandRanges(values, request); } std::vector newvals; std::set seen; for (std::string& val : values) { std::string value = val; if (!expand(value, request)) { std::ostringstream oss; oss << *this << ": cannot expand '" << val << "'"; throw eckit::UserError(oss.str()); } if (hasGroups()) { auto gg = group(value); if (gg) { for (const auto& v : gg->get()) { if (seen.find(v) == seen.end()) { seen.insert(v); newvals.push_back(v); } } } } else { if (!duplicates_ && seen.find(value) != seen.end()) { std::ostringstream oss; oss << *this << ": duplicated value '" << value << "'"; throw eckit::UserError(oss.str()); } newvals.push_back(value); } } std::swap(newvals, values); if (!multiple_ && values.size() > 1) { throw eckit::UserError("Only one value possible for '" + name_ + "'"); } } void Type::setDefaults(MarsRequest& request) { if (inheritance_) { request.setValuesTyped(this, inheritance_.value()); } else { bool unset = false; for (const auto& unsetContext : unsets_) { if (unsetContext->matches(request)) { unset = true; break; } } if (!unset) { for (const auto& [defaultContext, values] : defaults_) { if (defaultContext->matches(request)) { patchRequest(request, values); break; } } } } } void Type::setInheritance(const std::vector& inheritance) { inheritance_ = inheritance; } const std::vector& Type::flattenValues(const MarsRequest& request) { return request.values(name_); } void Type::clearDefaults() { defaults_.clear(); } void Type::reset() { inheritance_.reset(); } const std::string& Type::name() const { return name_; } const std::string& Type::category() const { return category_; } void Type::pass2(MarsRequest& request) {} void Type::finalise(MarsRequest& request, bool strict) { const std::vector& values = request.values(name_, true); if (values.size() == 1 && values[0] == "off") { request.unsetValues(name_); } else { if (values.size() > 0) { for (const auto& context : unsets_) { if (context->matches(request)) { if (strict && request.has(name_)) { std::ostringstream oss; oss << *this << ": Key [" << name_ << "] not acceptable with context: " << *context; throw eckit::UserError(oss.str()); } request.unsetValues(name_); } } } if (request.verb() != "list") { for (const auto& [context, values] : sets_) { if (context->matches(request)) { if (strict && !request.has(name_)) { std::ostringstream oss; oss << *this << ": missing Key [" << name_ << "] - required with context: " << *context; throw eckit::UserError(oss.str()); } patchRequest(request, values); } } } } } void Type::check(const std::vector& values) const { if (flatten_) { std::set s(values.begin(), values.end()); if (values.size() != s.size()) { std::cerr << "Duplicate values in " << name_ << " " << values; std::set seen; for (const std::string& val : values) { if (seen.find(val) != seen.end()) { std::cerr << ' ' << val; } seen.insert(val); } std::cerr << std::endl; } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/BaseProtocol.h0000664000175000017500000000421515246725757021076 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 BaseProtocol.h // Baudouin Raoult - (c) ECMWF Feb 12 #ifndef BaseProtocol_H #define BaseProtocol_H #include #include "eckit/io/Length.h" #include "eckit/serialisation/Streamable.h" #include "eckit/types/Types.h" namespace eckit { class Configuration; } namespace metkit::mars { class MarsRequest; class BaseProtocol : public eckit::Streamable { public: BaseProtocol(); BaseProtocol(eckit::Stream&); BaseProtocol(const eckit::Configuration&); virtual eckit::Length retrieve(const MarsRequest&) = 0; virtual void archive(const MarsRequest&, const eckit::Length&) = 0; virtual long read(void* buffer, long len) = 0; virtual long write(const void* buffer, long len) = 0; virtual void cleanup() = 0; virtual const eckit::StringDict& stats() const; // -- Overridden methods (from Streamable) static const eckit::ClassSpec& classSpec(); protected: virtual void print(std::ostream&) const = 0; void encode(eckit::Stream&) const override; private: friend std::ostream& operator<<(std::ostream& s, const BaseProtocol& p) { p.print(s); return s; } }; class ProtocolFactory { std::string name_; virtual BaseProtocol* make(const eckit::Configuration&) = 0; protected: ProtocolFactory(const std::string&); virtual ~ProtocolFactory(); public: static BaseProtocol* build(const eckit::Configuration&); static void list(std::ostream&); }; template class ProtocolBuilder : public ProtocolFactory { BaseProtocol* make(const eckit::Configuration& param) override { return new T(param); } public: ProtocolBuilder(const std::string& name) : ProtocolFactory(name) {} }; } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/TypeTime.h0000664000175000017500000000233115246725757020237 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeTime.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Emanuele Danovaro /// @date April 2016 #ifndef metkit_TypeTime_H #define metkit_TypeTime_H #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeTime : public Type { public: // methods TypeTime(const std::string& name, const eckit::Value& settings); ~TypeTime() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/TypeLowercase.h0000664000175000017500000000223215246725757021265 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeLowercase.h /// @author Emanuele Danovaro /// @date February 2024 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeLowercase : public Type { public: // methods TypeLowercase(const std::string& name, const eckit::Value& settings = eckit::Value()); ~TypeLowercase() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/DHSProtocol.h0000664000175000017500000000622215246725757020642 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 DHSProtocol.h // Baudouin Raoult - (c) ECMWF Feb 12 #ifndef DHSProtocol_H #define DHSProtocol_H #include #include #include #include #include "eckit/io/Length.h" #include "eckit/net/Endpoint.h" #include "eckit/net/TCPSocket.h" #include "eckit/serialisation/Streamable.h" #include "eckit/types/Types.h" #include "metkit/mars/BaseProtocol.h" #include "metkit/mars/ClientTask.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { // --------------------------------------------------------------------------------------------------------------------- class BaseCallbackConnection : public eckit::Streamable { public: BaseCallbackConnection() = default; static BaseCallbackConnection* build(const eckit::Configuration& config, const std::string& host = ""); virtual const eckit::net::Endpoint& endpoint() const = 0; virtual eckit::net::TCPSocket& connect() = 0; void encode(eckit::Stream&) const override = 0; static const eckit::ClassSpec& classSpec(); }; // --------------------------------------------------------------------------------------------------------------------- class DHSProtocol : public BaseProtocol { public: DHSProtocol(const eckit::Configuration&); DHSProtocol(const eckit::Configuration&, const std::map& env); DHSProtocol(const std::string& name, const std::string& host, int port, bool forwardMessages = false); DHSProtocol(eckit::Stream&); ~DHSProtocol() override; // -- Overridden methods (from Streamable) std::string className() const override { return "DHSProtocol"; } const eckit::ReanimatorBase& reanimator() const override; static const eckit::ClassSpec& classSpec(); const eckit::StringDict& stats() const override { return stats_; } private: // -- Members std::unique_ptr callback_; eckit::net::TCPSocket socket_; std::string name_; std::string host_; int port_; std::string msg_; std::unique_ptr task_; bool done_; bool error_; bool sending_; bool forward_; MarsRequest env_; eckit::StringDict stats_; // -- Methods bool wait(eckit::Length&); // -- Overridden methods // From BaseProtocol eckit::Length retrieve(const MarsRequest& request) override; void archive(const MarsRequest& request, const eckit::Length&) override; long read(void* buffer, long len) override; long write(const void* buffer, long len) override; void cleanup() override; void print(std::ostream&) const override; void encode(eckit::Stream&) const override; }; // --------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/Quantile.h0000664000175000017500000000344415246725757020267 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 Quantile.h /// @author Emanuele Danovaro /// @date February 2022 #pragma once #include namespace metkit { //---------------------------------------------------------------------------------------------------------------------- class Quantile { public: Quantile(const std::string& value); Quantile(long num, long den); long num() const { return num_; } long den() const { return den_; } operator std::string(); Quantile& operator+=(const long& rhs); Quantile& operator-=(const long& rhs); protected: void print(std::ostream& s) const; private: void check() const; friend std::ostream& operator<<(std::ostream& s, const Quantile& q) { q.print(s); return s; } private: long num_; long den_; }; bool operator==(const Quantile& lhs, const Quantile& rhs); inline bool operator!=(const Quantile& lhs, const Quantile& rhs) { return !(rhs == lhs); } bool operator<(const Quantile& lhs, const Quantile& rhs); inline bool operator>(const Quantile& lhs, const Quantile& rhs) { return rhs < lhs; } inline bool operator<=(const Quantile& lhs, const Quantile& rhs) { return !(lhs > rhs); } inline bool operator>=(const Quantile& lhs, const Quantile& rhs) { return !(lhs < rhs); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/mars/Param.h0000664000175000017500000000405215246725757017541 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 Param.h // Baudouin Raoult - ECMWF Dec 97 #ifndef metkit_Param_H #define metkit_Param_H #include #include "eckit/persist/Bless.h" namespace eckit { class DumpLoad; } namespace metkit { //---------------------------------------------------------------------------------------------------------------------- class Param { public: Param() : table_(-1), value_(-1) {} Param(const std::string&); Param(long table, long value) : table_(table), value_(value) {} #include "metkit/mars/Param.b" ~Param() {} operator std::string() const; bool operator==(const Param& other) const { return value_ == other.value_ && table_ == other.table_; } bool operator!=(const Param& other) const { return value_ != other.value_ || table_ != other.table_; } bool operator<(const Param& other) const { return (value_ == other.value_) ? (table_ < other.table_) : (value_ < other.value_); } bool operator>(const Param& other) const { return (value_ == other.value_) ? (table_ > other.table_) : (value_ > other.value_); } long table() const { return table_; } long value() const { return value_; } long grib1value() const { return value_ % 1000; } long paramId() const; void dump(eckit::DumpLoad&) const; void load(eckit::DumpLoad&); protected: void print(std::ostream& s) const; private: long table_; long value_; friend std::ostream& operator<<(std::ostream& s, const Param& p) { p.print(s); return s; } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/ContextRule.h0000664000175000017500000000115715246725757020760 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #pragma once #include "metkit/mars/MarsRequest.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeInteger.h0000664000175000017500000000255215246725757020743 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeInteger.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Emanuele Danovaro /// @date April 2016 #pragma once #include #include #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeInteger : public Type { public: // methods TypeInteger(const std::string& name, const eckit::Value& settings); protected: bool ok(const std::string& value, long& n) const; bool expand(std::string& value, const MarsRequest& request) const override; private: // methods void print(std::ostream& out) const override; private: // members struct Range { int lower_; int upper_; }; std::optional range_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeDate.cc0000664000175000017500000001330615246725757020360 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeDate.h" #include #include #include #include "eckit/types/Date.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Tokenizer.h" #include "eckit/utils/Translator.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace { //---------------------------------------------------------------------------------------------------------------------- static std::array months{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; std::string month(const std::string& value) { if (std::isdigit(value[0])) { eckit::Translator s2i; int m = s2i(value); if (m > 12) { std::ostringstream oss; oss << value << " is not a valid month"; throw eckit::BadValue(oss.str()); } return months[m - 1]; } ASSERT(value.size() >= 3); const auto* it = std::find(months.begin(), months.end(), eckit::StringTools::lower(value.substr(0, 3))); if (it == months.end()) { std::ostringstream oss; oss << value << " is not a valid month name"; throw eckit::BadValue(oss.str()); } return *it; } long day(std::string& value) { if (value.empty()) { return -1; } eckit::Translator s2l; if (value[0] == '0' || value[0] == '-') { long n = s2l(value); if (n <= 0) { eckit::Date now(n); return now.day(); } } else { eckit::Tokenizer t("-"); std::vector tokens = t.tokenize(value); if (tokens.size() == 2) { if (std::isdigit(tokens[0][0]) && tokens[0].size() > 2) { // year-dayOfYear (e.g. 2018-23 ==> 20180123) eckit::Date d(s2l(tokens[0]), s2l(tokens[1])); return d.day(); } else { // month-day (i.e. TypeClimateDaily) return s2l(tokens[1]); } } if (tokens.size() == 1 && (!std::isdigit(value[0]) || value.size() <= 2)) { // month (i.e. TypeClimateMonthly) return -1; } eckit::Date date(value); return date.day(); } return -1; } bool filterByDay(const std::vector& filter, std::vector& values) { std::set days; eckit::Translator s2l; for (auto f : filter) { days.emplace(s2l(f)); } for (auto v = values.begin(); v != values.end();) { long d = day(*v); if (d != -1) { auto it = days.find(d); if (it != days.end()) { v++; continue; } } v = values.erase(v); } return !values.empty(); } } // namespace namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeDate::TypeDate(const std::string& name, const eckit::Value& settings) : Type(name, settings) { toByList_ = std::make_unique>(*this, settings); multiple_ = true; filters_["day"] = &filterByDay; } void TypeDate::pass2(MarsRequest& request) { std::vector values = request.values(name_, true); if (values.size() == 1 && values[0] == "-1") { Type::expand(values, request); request.setValuesTyped(this, values); } } bool TypeDate::expand(std::string& value, const MarsRequest&) const { if (!value.empty()) { eckit::Translator s2l; eckit::Translator l2s; if (value[0] == '0' || value[0] == '-') { long n = s2l(value); if (n <= 0) { eckit::Date now(n); value = l2s(now.yyyymmdd()); } } else { eckit::Tokenizer t("-"); std::vector tokens = t.tokenize(value); if (tokens.size() == 2) { // month-day (i.e. TypeClimateDaily) if (std::isdigit(tokens[0][0]) && tokens[0].size() > 2) { // year-dayOfYear (e.g. 2018-23 ==> 20180123) eckit::Date d(s2l(tokens[0]), s2l(tokens[1])); value = l2s(d.yyyymmdd()); } else { // month-day (i.e. TypeClimateDaily) std::string m = month(tokens[0]); long d = s2l(tokens[1]); value = m + "-" + l2s(d); } } else { if (tokens.size() == 1 && (!std::isdigit(value[0]) || value.size() <= 2)) { // month (i.e. TypeClimateMonthly) value = month(tokens[0]); } else { eckit::Date date(value); value = l2s(date.yyyymmdd()); } } } } return true; } void TypeDate::print(std::ostream& out) const { out << "TypeDate[name=" << name_ << "]"; } static TypeBuilder type("date"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsLocation.h0000664000175000017500000000466215246725757021103 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Tiago Quintino /// @date Dec 2015 #ifndef metkit_MarsLocation_H #define metkit_MarsLocation_H #include "eckit/config/Configuration.h" #include "metkit/mars/MarsRequest.h" namespace eckit { class JSON; class Stream; } // namespace eckit namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- /// MarsLocation represents a MarsRequest associated with the hostname and port. /// From this location, the data can be directly retrieved without going through a queueing system. /// It assumes all the data identified by the request can be got from the same location. /// This is useful to identify and retrieve data directly from memory. /// /// If we consider that a MarsRequest is analogue to a URI, then a MarsLocation is an analogue to a URL class MarsLocation { public: // methods // - Constructors MarsLocation(const MarsRequest& r, const std::string& hostname, int port); MarsLocation(eckit::Stream&); MarsLocation(const eckit::Configuration&); // -- Destructor ~MarsLocation(); // -- Operators operator eckit::Value() const; void json(eckit::JSON&) const; const MarsRequest& request() const; std::string hostname() const; int port() const; private: // members MarsRequest request_; std::string hostname_; int port_; private: // methods void print(std::ostream&) const; void encode(eckit::Stream&) const; // -- Class members friend std::ostream& operator<<(std::ostream& s, const MarsLocation& r) { r.print(s); return s; } friend eckit::JSON& operator<<(eckit::JSON& s, const MarsLocation& r) { r.json(s); return s; } friend eckit::Stream& operator<<(eckit::Stream& s, const MarsLocation& r) { r.encode(s); return s; } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/TypeExpver.cc0000664000175000017500000000277715246725757020766 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" #include "metkit/mars/TypeExpver.h" #include "metkit/mars/TypesFactory.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- TypeExpver::TypeExpver(const std::string& name, const eckit::Value& settings) : Type(name, settings) {} bool TypeExpver::expand(std::string& value, const MarsRequest&) const { std::string v = eckit::StringTools::lower(eckit::StringTools::trim(value)); if (v == "all" || v == "any") { value = v; } else { std::ostringstream oss; oss << std::setfill('0') << std::setw(4) << eckit::StringTools::trim(value); value = oss.str(); } return true; } void TypeExpver::print(std::ostream& out) const { out << "TypeExpver[name=" << name_ << "]"; } static TypeBuilder type("expver"); //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/TypeToByList.h0000664000175000017500000001124315246725757021054 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeToByList.h /// @author Emanuele Danovaro /// @date March 2025 #pragma once #include "eckit/exception/Exceptions.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- template class TypeToByList : public ITypeToByList { private: // members const Type& type_; const std::string by_; public: // methods TypeToByList(const Type& type, const eckit::Value& settings) : type_(type), by_(settings.contains("by") ? settings["by"] : "1") {} virtual ~TypeToByList() = default; void expandRanges(std::vector& values, const MarsRequest& request) const override { if (values.size() == 1) { return; } static eckit::Translator s2el; static eckit::Translator s2by; static eckit::Translator el2s; std::vector newval; for (size_t i = 0; i < values.size(); ++i) { const std::string& s = values[i]; if (eckit::StringTools::lower(s) == "to" || eckit::StringTools::lower(s) == "t0") { // TimeUnit unit; if (newval.size() == 0) { std::ostringstream oss; oss << type_.name() << " list: 'to' must be preceeded by a starting value."; throw eckit::BadValue(oss.str()); } if (values.size() <= i + 1) { std::ostringstream oss; oss << type_.name() << " list: 'to' must be followed by an ending value."; throw eckit::BadValue(oss.str()); } EL from = s2el(type_.tidy(values[i - 1], request)); std::string to_s = type_.tidy(values[i + 1], request); EL to = s2el(to_s); BY by = s2by(by_); if (i + 2 < values.size() && eckit::StringTools::lower(values[i + 2]) == "by") { if (values.size() <= i + 3) { std::ostringstream oss; oss << type_.name() << " list: 'by' must be followed by a step size."; throw eckit::BadValue(oss.str()); } by = s2by(values[i + 3]); i += 2; } i++; if (by == BY{0}) { std::ostringstream oss; oss << type_.name() + ": 'by' value " << by << " cannot be zero"; throw eckit::BadValue(oss.str()); } if (from < to && by < BY{0}) { std::ostringstream oss; oss << type_.name() << ": impossible to define a sequence starting from " << from << " to " << to << " with step " << by; throw eckit::BadValue(oss.str()); } bool addBy = (from < to && by > BY{0}) || (from > to && by < BY{0}); if (from == to) { continue; } EL j = from; while (j != to) { std::string j_s; try { if (addBy) { j += by; } else { j -= by; } j_s = type_.tidy(el2s(j), request); } catch (...) { break; /// reached an invalid value } if (((from < to && j > to) || (from > to && j < to)) && j != to && j_s != to_s) { break; } newval.push_back(j_s); } } else { newval.push_back(type_.tidy(s, request)); } } std::swap(values, newval); } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsHandle.cc0000664000175000017500000001311215246725757020652 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "eckit/config/Resource.h" #include "eckit/log/Bytes.h" #include "eckit/log/Log.h" #include "eckit/serialisation/HandleStream.h" #include "metkit/mars/MarsHandle.h" const unsigned long startCRC = 0xffffffffL; eckit::Reanimator MarsHandle::reanimator_; class MarsHandleStream : public eckit::HandleStream { MarsHandle& handle_; public: MarsHandleStream(MarsHandle& handle) : eckit::HandleStream(handle), handle_(handle) { handle_.streamMode_ = true; } ~MarsHandleStream() { handle_.streamMode_ = false; } }; void MarsHandle::encode(eckit::Stream& s) const { TCPHandle::encode(s); s << clientID_; s << doCRC_; } MarsHandle::MarsHandle(eckit::Stream& s) : TCPHandle(s), length_(0), total_(0), receiving_(false), streamMode_(false), doCRC_(false), crc_(startCRC) { s >> clientID_; if (s.endObjectFound()) { eckit::Log::info() << "Got old metkit without CRC" << std::endl; return; } s >> doCRC_; if (doCRC_) eckit::Log::info() << "Got new metkit with CRC" << std::endl; } MarsHandle::MarsHandle(const std::string& host, int port, unsigned long long clientID) : TCPHandle(host, port), clientID_(clientID), length_(0), total_(0), receiving_(false), streamMode_(false), doCRC_(false), crc_(startCRC) {} MarsHandle::~MarsHandle() {} std::string MarsHandle::title() const { std::ostringstream os; os << "Client["; os << eckit::net::TCPSocket::hostName(host_); os << ":" << port_ << "]"; return os.str(); } std::string MarsHandle::metricsTag() const { std::ostringstream os; os << "client@"; os << eckit::net::TCPSocket::hostName(host_); return os.str(); } eckit::Length MarsHandle::openForRead() { static long size = eckit::Resource("archiveSocketBufferSize", 0); connection_.bufferSize(size); eckit::TCPHandle::openForRead(); MarsHandleStream s(*this); s << clientID_; // Send info s << 'r'; // Send the request s >> length_; // Get the length back eckit::Log::status() << "Receiving " << eckit::Bytes(length_) << std::endl; total_ = 0; receiving_ = true; crc_ = startCRC; return length_; } void MarsHandle::openForWrite(const eckit::Length& length) { eckit::TCPHandle::openForWrite(length); MarsHandleStream s(*this); length_ = length; s << clientID_; // Send info s << 'w'; s << length_; // Send the length eckit::Log::status() << "Sending " << eckit::Bytes(length_) << std::endl; total_ = 0; receiving_ = false; } void MarsHandle::openForAppend(const eckit::Length&) { NOTIMP; } long MarsHandle::read(void* buffer, long length) { if (streamMode_) return TCPHandle::read(buffer, length); long long left = length_ - total_; if (left < length) { length = left; if (length == 0) return 0; } long len = TCPHandle::read(buffer, length); if (doCRC_) updateCRC(buffer, len); total_ += len; return len; } long MarsHandle::write(const void* buffer, long length) { long len = TCPHandle::write(buffer, length); total_ += len; return len; } void MarsHandle::close() { bool gotCRC = false; unsigned long version = 0; unsigned long crc = 0; if (length_ > 0 && total_ != length_) { TCPHandle::close(); eckit::Log::error() << "Recieved/Sent " << total_ << " bytes instead of " << length_ << std::endl; if (eckit::Exception::throwing()) { eckit::Log::error() << "A expection is already active" << std::endl; return; } throw eckit::ShortFile("Bad total in MarsHandle"); } if (receiving_) { crc_ ^= 0xffffffff; length_ = 0; // Try to read CRC try { MarsHandleStream s(*this); s >> version; unsigned long long c; s >> c; crc = ((unsigned long)(c)); gotCRC = true; } catch (std::exception& e) { eckit::Log::warning() << "Cannot read crc: " << e.what() << std::endl; } } if (doCRC_ && gotCRC) { eckit::Log::info() << "Local CRC " << crc_ << ", remote CRC " << crc << std::endl; ASSERT(version == 1); if (crc != crc_) { { FILE* p = popen("mail mab mar", "w"); if (p) { fprintf(p, "CRC error\n"); pclose(p); } } { eckit::PathName lock("~/locks/pause_if_crc_error"); while (lock.exists()) { eckit::Log::status() << "**** CRC ERROR ****" << std::endl; ::sleep(120); } } eckit::TCPHandle::close(); throw eckit::SeriousBug("Invalide checksum"); } } TCPHandle::close(); } eckit::Length MarsHandle::estimate() { return length_; } void MarsHandle::updateCRC(void* buffer, long length) {} const eckit::ClassSpec& MarsHandle::classSpec() { static const eckit::ClassSpec _classSpec = {&TCPHandle::classSpec(), "MarsHandle"}; return _classSpec; } metkit-1.20.2/src/metkit/mars/TypeFloat.cc0000664000175000017500000000537515246725757020557 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/utils/Translator.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypeFloat.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeFloat::TypeFloat(const std::string& name, const eckit::Value& settings) : Type(name, settings) {} bool TypeFloat::expand(std::string& value, const MarsRequest&) const { bool dot = false; for (std::string::const_iterator j = value.begin(); j != value.end(); ++j) { switch (*j) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': break; case '.': dot = true; break; default: return false; } } // Strip leading zeros while (value.size() && value[0] == '0') { value = value.substr(1); } // Strip trailing zeros if (dot) { while (value.size() && value[value.size() - 1] == '0') { value = value.substr(0, value.size() - 1); } if (value.size() && value[value.size() - 1] == '.') { value = value.substr(0, value.size() - 1); } } if (value.empty()) { value = "0"; } return true; } void TypeFloat::print(std::ostream& out) const { out << "TypeFloat[name=" << name() << "]"; } static TypeBuilder type("float"); //---------------------------------------------------------------------------------------------------------------------- class TypeToByListFloat : public TypeFloat { public: TypeToByListFloat(const std::string& name, const eckit::Value& settings) : TypeFloat(name, settings) { toByList_ = std::make_unique>(*this, settings); multiple_ = true; } protected: void print(std::ostream& out) const override { out << "TypeToByListFloat[name=" << name() << "]"; } }; static TypeBuilder typeList("to-by-list-float"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeRange.h0000664000175000017500000000221415246725757020375 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeRange.h /// @author Emanuele Danovaro /// @date February 2022 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { class StepRange; //---------------------------------------------------------------------------------------------------------------------- class TypeRange : public Type { public: // methods TypeRange(const std::string& name, const eckit::Value& settings); ~TypeRange() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeParam.h0000664000175000017500000000242115246725757020401 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeParam.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeParam : public Type { public: // methods TypeParam(const std::string& name, const eckit::Value& settings); ~TypeParam() noexcept override = default; private: // methods eckit::ValueMap expandWith_; bool firstRule_; void print(std::ostream& out) const override; void reset() override; void pass2(MarsRequest& request) override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Type.h0000664000175000017500000002004615246725757017423 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 Type.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Emanuele Danovaro /// @date April 2016 #pragma once #include #include #include #include #include #include #include "eckit/memory/Counted.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- /// @brief abstract class - ContextRule subclasses are used to define a context. A MarsRequest matches a context, if it /// matches all its ContextRules class ContextRule { public: ContextRule(const std::string& k) : key_(k) {} virtual ~ContextRule() = default; const std::string& key() const { return key_; } virtual bool matches(MarsRequest req) const = 0; friend std::ostream& operator<<(std::ostream& s, const ContextRule& r) { r.print(s); return s; } protected: std::string key_; private: // methods virtual void print(std::ostream& out) const = 0; }; //---------------------------------------------------------------------------------------------------------------------- /// @brief A MarsRequest matches an Include ContextRule if at least one of the mars request values matches with the /// values associated with the Include rule class Include : public ContextRule { public: Include(const std::string& k, const std::set& vv) : ContextRule(k), vals_(vv) {} bool matches(MarsRequest req) const override { if (key_ == "_verb") { return (vals_.find(req.verb()) != vals_.end()); } if (!req.has(key_)) { return false; } for (const std::string& v : req.values(key_)) { if (vals_.find(v) != vals_.end()) { return true; } } return false; } private: // methods void print(std::ostream& out) const override { out << "Include[key=" << key_ << ",vals=[" << vals_ << "]]"; } private: std::set vals_; }; /// @brief A MarsRequest matches an Exclude ContextRule if none of the mars request values matches with the values /// associated with the Exclude rule class Exclude : public ContextRule { public: Exclude(const std::string& k, const std::set& vv) : ContextRule(k), vals_(vv) {} bool matches(MarsRequest req) const override { if (!req.has(key_)) { return false; } for (const std::string& v : req.values(key_)) { if (vals_.find(v) != vals_.end()) { return false; } } return true; } private: // methods void print(std::ostream& out) const override { out << "Exclude[key=" << key_ << ",vals=[" << vals_ << "]]"; } private: std::set vals_; }; /// @brief A MarsRequest matches an Undef ContextRule if the specified keyword is not defined in the mars request class Undef : public ContextRule { public: Undef(const std::string& k) : ContextRule(k) {} bool matches(MarsRequest req) const override { return !req.has(key_); } private: // methods void print(std::ostream& out) const override { out << "Undef[key=" << key_ << "]"; } }; /// @brief A MarsRequest matches an Undef ContextRule if the specified keyword is defined in the mars request class Def : public ContextRule { public: Def(const std::string& k) : ContextRule(k) {} bool matches(MarsRequest req) const override { return req.has(key_); } private: // methods void print(std::ostream& out) const override { out << "Def[key=" << key_ << "]"; } }; //---------------------------------------------------------------------------------------------------------------------- /// @brief a Context contains a list of ContextRule. A MarsRequest matches a context, if it matches all the ContextRules /// associated class Context { public: static std::unique_ptr parseContext(eckit::Value c); /// @note takes ownership of the rule void add(std::unique_ptr rule); size_t maxAxisIndex() const; bool matches(MarsRequest req) const; friend std::ostream& operator<<(std::ostream& s, const Context& x); private: // methods void print(std::ostream& out) const; private: std::vector> rules_; }; //---------------------------------------------------------------------------------------------------------------------- class ITypeToByList { public: virtual ~ITypeToByList() = default; virtual void expandRanges(std::vector& values, const MarsRequest& request) const = 0; }; //---------------------------------------------------------------------------------------------------------------------- class Type : public eckit::Counted { public: // methods Type(const std::string& name, const eckit::Value& settings); ~Type() noexcept override = default; virtual bool expand(std::string& value, const MarsRequest& request = {}) const; void expand(std::vector& values, const MarsRequest& request = {}) const; std::string tidy(const std::string& value, const MarsRequest& request = {}) const; virtual void setDefaults(MarsRequest& request); virtual void setInheritance(const std::vector& inheritance); virtual void check(const std::vector& values) const; virtual void clearDefaults(); virtual void reset(); virtual void pass2(MarsRequest& request); virtual void finalise(MarsRequest& request, bool strict); virtual const std::vector& flattenValues(const MarsRequest& request); virtual bool flatten() const; virtual bool multiple() const; virtual bool filter(const std::vector& filter, std::vector& values) const; virtual bool filter(const std::string& keyword, const std::vector& filter, std::vector& values) const; virtual bool matches(const std::vector& filter, const std::vector& values) const; const std::string& name() const; const std::string& category() const; friend std::ostream& operator<<(std::ostream& s, const Type& x); virtual size_t count(const std::vector& values) const; protected: // methods virtual bool hasGroups() const { return false; } virtual std::optional>> group(const std::string&) const { NOTIMP; } friend class MarsLanguage; void defaults(std::shared_ptr context, const std::vector& values); void set(std::shared_ptr context, const std::vector& values); void unset(std::shared_ptr context); protected: // members std::string name_; std::string category_; bool flatten_; bool multiple_; bool duplicates_; std::map, std::vector> defaults_; std::map, std::vector> sets_; std::set> unsets_; std::optional> inheritance_; std::unique_ptr toByList_; std::map&, std::vector&)>> filters_; private: // methods virtual void print(std::ostream& out) const = 0; void patchRequest(MarsRequest& request, const std::vector& values); }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Matcher.cc0000664000175000017500000001072415246725757020225 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ /// @author Chris Bradley #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/utils/Overloaded.h" #include "eckit/utils/Regex.h" #include "eckit/utils/Tokenizer.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Matcher.h" namespace metkit::mars { // ---------------------------------------------------------------------------------------------------------------------- namespace { // helpers class MarsRequestAccessor : public RequestLike { public: explicit MarsRequestAccessor(const metkit::mars::MarsRequest& request) : request_(request) {} std::optional get(const std::string& keyword) const override { return request_.get(keyword); } private: const metkit::mars::MarsRequest& request_; }; inline std::string trim(const std::string& s) { auto start = std::find_if_not(s.begin(), s.end(), [](unsigned char ch) { return std::isspace(ch); }); auto end = std::find_if_not(s.rbegin(), s.rend(), [](unsigned char ch) { return std::isspace(ch); }).base(); if (start >= end) { return ""; // string is all whitespace } return std::string(start, end); } } // namespace // ---------------------------------------------------------------------------------------------------------------------- std::map parseMatchString(const std::string& expr) { if (expr.empty()) { throw eckit::BadValue("Empty match expression", Here()); } std::vector key_vals; eckit::Tokenizer(',')(expr, key_vals); eckit::Tokenizer equals('='); std::map out; for (const std::string& item : key_vals) { std::vector kv; equals(item, kv); if (kv.size() != 2) { throw eckit::BadValue("Invalid condition " + item + " in expression: " + expr, Here()); } const std::string& key = trim(kv[0]); const std::string& val = trim(kv[1]); auto [it, inserted] = out.try_emplace(key, val); if (!inserted) { throw eckit::BadValue("Duplicate key " + key + " in expression: " + expr, Here()); } } return out; } // ---------------------------------------------------------------------------------------------------------------------- Matcher::Matcher(std::map regexMap, Policy policy) : regexMap_{std::move(regexMap)}, policy_{policy} {} Matcher::Matcher(const std::string& expr, Policy policy) : regexMap_{parseMatchString(expr)}, policy_{policy} {} bool Matcher::match(const MarsRequest& request, MatchMissingPolicy matchOnMissing) const { return match(MarsRequestAccessor(request), matchOnMissing); } bool Matcher::match(const RequestLike& request, MatchMissingPolicy matchOnMissing) const { return std::all_of(regexMap_.begin(), regexMap_.end(), [&](const auto& kv) { const auto& keyword = kv.first; const auto& regex = kv.second; auto opt_values = request.get(keyword); if (!opt_values) return matchOnMissing == MatchOnMissing; auto pred = [®ex](const std::string& s) { return regex.match(s); }; // clang-format off return std::visit(eckit::Overloaded { [&](std::reference_wrapper value) { return pred(value.get()); }, [&](std::reference_wrapper> values) { const auto& vec = values.get(); if (policy_ == Policy::Any) { return std::any_of(vec.begin(), vec.end(), pred); } ASSERT(policy_ == Policy::All); return std::all_of(vec.begin(), vec.end(), pred); } }, *opt_values); // clang-format on }); } void Matcher::print(std::ostream& s) const { s << "{"; const char* sep = ""; for (const auto& [key, regex] : regexMap_) { s << sep << key << "=" << static_cast(regex); sep = ","; } s << "}"; } } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeLowercase.cc0000664000175000017500000000237615246725757021434 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/utils/StringTools.h" #include "metkit/mars/TypeLowercase.h" #include "metkit/mars/TypesFactory.h" #include #include #include namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeLowercase::TypeLowercase(const std::string& name, const eckit::Value& settings) : Type(name, settings) {} void TypeLowercase::print(std::ostream& out) const { out << "TypeLowercase[name=" << name_ << "]"; } bool TypeLowercase::expand(std::string& value, const MarsRequest&) const { value = eckit::StringTools::lower(value); return true; } static TypeBuilder type("lowercase"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsLocation.cc0000664000175000017500000000470615246725757021240 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include "eckit/config/Configuration.h" #include "eckit/config/LocalConfiguration.h" #include "eckit/config/Resource.h" #include "eckit/log/JSON.h" #include "eckit/log/Log.h" #include "eckit/utils/MD5.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" #include "metkit/mars/MarsLocation.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- MarsLocation::MarsLocation(const MarsRequest& r, const std::string& hostname, int port) : request_(r), hostname_(hostname), port_(port) {} MarsLocation::MarsLocation(eckit::Stream& s) : request_(s) { s >> hostname_; s >> port_; NOTIMP; // FIXME: added this constructor just to get develop branches to compile } MarsLocation::MarsLocation(const eckit::Configuration& c) : request_(c.getString("request")), hostname_(c.getString("server")), port_(c.getInt("port")) {} MarsLocation::~MarsLocation() {} metkit::mars::MarsLocation::operator eckit::Value() const { eckit::Value dict = eckit::Value::makeMap(); dict["request"] = request_; dict["server"] = hostname_; dict["port"] = port_; return dict; } void MarsLocation::encode(eckit::Stream& s) const { s << request_; s << hostname_; s << port_; } const MarsRequest& MarsLocation::request() const { return request_; } std::string MarsLocation::hostname() const { return hostname_; } int MarsLocation::port() const { return port_; } void MarsLocation::print(std::ostream& s) const { s << request_ << ',' << std::endl; s << "hostname=" << hostname_ << ',' << std::endl; s << "port=" << port_ << std::endl; } void MarsLocation::json(eckit::JSON& s) const { s.startObject(); s << "request" << request_; s << "hostname" << hostname_; s << "port" << port_; s.endObject(); } //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/MarsExpandContext.h0000664000175000017500000000175015246725757022112 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #pragma once #include namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class MarsExpandContext { void info(std::ostream&) const {} public: friend std::ostream& operator<<(std::ostream& s, const MarsExpandContext& r) { return s; } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeMixed.h0000664000175000017500000000242615246725757020414 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeMixed.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #ifndef metkit_TypeMixed_H #define metkit_TypeMixed_H #include "metkit/mars/Type.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- class TypeMixed : public Type { public: // methods TypeMixed(const std::string& name, const eckit::Value& settings); ~TypeMixed() noexcept override; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; std::vector, Type*>> types_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/TypesFactory.cc0000664000175000017500000000551615246725757021301 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/exception/Exceptions.h" #include "eckit/thread/AutoLock.h" #include "eckit/value/Value.h" #include "metkit/mars/TypesFactory.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- TypesFactory::TypesFactory(const std::string& name) : name_(name) { TypesRegistry::instance().add(name, this); } TypesFactory::~TypesFactory() { TypesRegistry::instance().remove(name_); } Type* TypesRegistry::build(const std::string& keyword, const eckit::Value& settings) { std::string name; if (settings["type"].isList()) { name = "mixed"; } else { name = std::string(settings["type"]); } eckit::AutoLock lock(mutex_); std::map::const_iterator j = m_.find(name); if (j == m_.end()) { eckit::Log::error() << "No TypesFactory for [" << name << "]" << std::endl; eckit::Log::error() << "KeywordTypes are:" << std::endl; for (j = m_.begin(); j != m_.end(); ++j) eckit::Log::error() << " " << (*j).first << std::endl; throw eckit::SeriousBug(std::string("No TypesFactory called ") + name); } return (*j).second->make(keyword, settings); } void TypesRegistry::list(std::ostream& s) { eckit::AutoLock lock(mutex_); s << "["; bool first = true; std::map::const_iterator j = m_.begin(); while (j != m_.end()) { if (!first) s << ","; s << (j++)->first; first = false; } s << "]"; } Type* TypesFactory::build(const std::string& keyword, const eckit::Value& settings) { return TypesRegistry::instance().build(keyword, settings); } void TypesFactory::list(std::ostream& s) { TypesRegistry::instance().list(s); } TypesRegistry& TypesRegistry::instance() { static TypesRegistry instance; return instance; } void TypesRegistry::add(const std::string& name, TypesFactory* f) { eckit::AutoLock lock(mutex_); ASSERT(m_.find(name) == m_.end()); m_[name] = f; } void TypesRegistry::remove(const std::string& name) { eckit::AutoLock lock(mutex_); m_.erase(name); } //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/ParamID.h0000664000175000017500000002217715246725757017766 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Simon Smart /// @date Febuary 2019 #ifndef metkit_ParamID_H #define metkit_ParamID_H #include #include #include #include #include "eckit/config/Resource.h" #include "eckit/filesystem/PathName.h" #include "eckit/log/Log.h" #include "eckit/runtime/Metrics.h" #include "eckit/types/Types.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/Param.h" namespace metkit { //---------------------------------------------------------------------------------------------------------------------- enum class NormalisationMode { Strict, // Exact match + wind conversion Loose, // Strict + partial match (dropping tables defined in param-matching.yaml) FullTableDropping }; //---------------------------------------------------------------------------------------------------------------------- class ParamID { public: // types struct WindFamily { Param u_; Param v_; Param vo_; Param d_; WindFamily(const std::string& u, const std::string& v, const std::string& vo, const std::string& d) : u_(u), v_(v), vo_(vo), d_(d) {} }; public: // methods template static void normalise(const REQUEST_T& r, std::vector& req, const AXIS_T& axis, bool& windConversion, NormalisationMode mode = ParamID::normalisationMode()); template [[deprecated]] static void normalise(const REQUEST_T& r, std::vector& req, const AXIS_T& axis, bool& windConversion, bool fullTableDropping, bool forceGRIBParamID = false); static const std::vector& getWindFamilies(); static const std::vector& getDropTables(); static NormalisationMode normalisationMode(); [[deprecated]] static bool fullTableDropping() { return normalisationMode() == NormalisationMode::FullTableDropping; } static const std::set& getMlParamsSingleLevel(); }; //---------------------------------------------------------------------------------------------------------------------- inline long replaceTable(size_t table, long paramid) { return (table * 1000 + paramid % 1000); } template void ParamID::normalise(const REQUEST_T& request, std::vector& req, const AXIS_T& axis, bool& windConversion, bool fullTableDropping, bool forceGRIBParamID) { normalise(request, req, axis, windConversion, forceGRIBParamID ? NormalisationMode::Strict : (fullTableDropping ? NormalisationMode::FullTableDropping : NormalisationMode::Loose)); } template void ParamID::normalise(const REQUEST_T& request, std::vector& req, const AXIS_T& axis, bool& windConversion, NormalisationMode mode) { static const bool useGRIBParamID = eckit::Resource("useGRIBParamID", false); bool strict = useGRIBParamID || mode == NormalisationMode::Strict; const std::vector& windFamilies(getWindFamilies()); std::vector> tableDropped; std::set inAxis; std::map inAxisParamID; std::set wind; for (typename AXIS_T::const_iterator j = axis.begin(); j != axis.end(); ++j) { Param p = *j; inAxis.emplace(p); inAxisParamID[p.paramId()] = p; } std::vector newreq; newreq.reserve(req.size()); for (const auto& r : req) { if (inAxis.find(r) != inAxis.end()) { // Perfect match - look no further newreq.push_back(r); } else { // r is normalised to ParamID long paramid = r.paramId(); const auto& ap = inAxisParamID.find(paramid); // ParamID representation matching - look no further if (ap != inAxisParamID.end()) { newreq.push_back(ap->second); } else { // Special case for U/V - exact match bool ok = false; for (const auto& wf : windFamilies) { if ((paramid == wf.u_.paramId() || paramid == wf.v_.paramId() || (!strict && (paramid == wf.u_.grib1value() || paramid == wf.v_.grib1value()))) && inAxis.find(wf.vo_) != inAxis.end() && inAxis.find(wf.d_) != inAxis.end()) { if (paramid == wf.u_.paramId() || (!strict && paramid == wf.u_.grib1value())) newreq.push_back(wf.u_); else newreq.push_back(wf.v_); wind.emplace(wf.vo_); wind.emplace(wf.d_); windConversion = true; ok = true; break; } } // Partial match (only it table has not been specified by user) if (!ok && !strict && r.table() == 0 && paramid < 1000) { const std::vector& dropTables = ParamID::getDropTables(); for (auto t : dropTables) { auto ap = inAxisParamID.find(replaceTable(t, paramid)); if (ap != inAxisParamID.end()) { // ParamID representation matching - look no further newreq.push_back(ap->second); ok = true; break; } } if (!ok) { // Special case for U/V - partial match for (const auto& wf : windFamilies) { if (paramid == wf.u_.paramId() || paramid == wf.v_.paramId()) { for (auto t : dropTables) { auto vo = inAxisParamID.find(replaceTable(t, wf.vo_.paramId())); auto d = inAxisParamID.find(replaceTable(t, wf.d_.paramId())); if (vo != inAxisParamID.end() && d != inAxisParamID.end()) { bool grib1 = vo->second.table() > 0; if (paramid == wf.u_.paramId()) newreq.push_back(grib1 ? Param(t, paramid) : Param(0, replaceTable(t, paramid))); else newreq.push_back(grib1 ? Param(t, paramid) : Param(0, replaceTable(t, paramid))); wind.emplace(vo->second); wind.emplace(d->second); windConversion = true; ok = true; break; } } if (ok) break; } } } if (mode == NormalisationMode::FullTableDropping && !ok) { // Backward compatibility - Partial match (drop completely table information) for (const auto& ap : inAxisParamID) { if (ap.first % 1000 == paramid % 1000) { newreq.push_back(ap.second); ok = true; tableDropped.push_back(std::make_pair(r, ap.second)); break; } } } } } } } req = newreq; for (const auto& w : wind) { bool exist = false; for (eckit::Ordinal i = 0; i < req.size(); i++) if (req[i] == w) { exist = true; break; } if (!exist) { req.push_back(w); } } if (tableDropped.size() > 0) { eckit::MetricsPrefix prefix("paramid_normalisation"); { std::ostringstream oss; oss << request; eckit::Metrics::set("user_request", oss.str()); } { std::ostringstream oss; oss << tableDropped; eckit::Metrics::set("params", oss.str()); } } } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/TypeRegex.cc0000664000175000017500000000351715246725757020560 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeRegex.h" #include "eckit/parser/JSONParser.h" #include "eckit/utils/StringTools.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/TypesFactory.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- TypeRegex::TypeRegex(const std::string& name, const eckit::Value& settings) : Type(name, settings), uppercase_(false) { if (settings.contains("uppercase")) { uppercase_ = settings["uppercase"]; } eckit::Value r = settings["regex"]; if (r.isList()) { for (size_t i = 0; i < r.size(); ++i) { regex_.push_back(std::string(r[i])); } } else { regex_.push_back(std::string(r)); } } bool TypeRegex::expand(std::string& value, const MarsRequest&) const { for (std::vector::const_iterator j = regex_.begin(); j != regex_.end(); ++j) { if ((*j).match(value)) { if (uppercase_) { value = eckit::StringTools::upper(value); } return true; } } return false; } void TypeRegex::print(std::ostream& out) const { out << "TypeRegex[name=" << name_ << "]"; } static TypeBuilder type("regex"); //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/TypeEnum.h0000664000175000017500000000415515246725757020253 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeEnum.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #pragma once #include #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeEnum : public Type { public: // methods TypeEnum(const std::string& name, const eckit::Value& settings); ~TypeEnum() noexcept override = default; private: // methods bool hasGroups() const override { return hasGroups_; } std::optional>> group( const std::string& value) const override; void print(std::ostream& out) const override; void reset() override; bool expand(std::string& value, const MarsRequest& request) const override; std::map::const_iterator find(const std::string& value) const; std::vector parseEnumValue(const eckit::Value& val, bool allowDuplicates = false) const; void addValue(const std::string& value, uint16_t idx, bool allowDuplicates) const; uint16_t parseValueNames(const eckit::Value& names, bool allowDuplicates) const; void readValuesFile() const; private: // members std::string valuesFile_; bool uppercase_ = false; mutable bool hasGroups_ = false; mutable std::vector>> groups_; mutable std::map values_; // map of acceptable values (included aliases) mutable std::once_flag readValues_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsHandle.h0000664000175000017500000000375115246725757020524 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 MarsHandle.h // Baudouin Raoult - ECMWF Oct 96 #ifndef MarsHandle_H #define MarsHandle_H #include "eckit/io/Length.h" #include "eckit/io/TCPHandle.h" #include "eckit/serialisation/Stream.h" class MarsHandle : public eckit::TCPHandle { public: // -- Contructors MarsHandle(const std::string& host, int port, unsigned long long); MarsHandle(eckit::Stream&); // -- Destructor ~MarsHandle(); // -- Overridden methods // From eckit::DataHandle virtual eckit::Length openForRead() override; virtual void openForWrite(const eckit::Length&) override; virtual void openForAppend(const eckit::Length&) override; virtual void close() override; virtual long read(void*, long) override; virtual long write(const void*, long) override; virtual eckit::Length estimate() override; virtual std::string title() const override; virtual std::string metricsTag() const override; virtual bool moveable() const override { return true; } // From Streamable virtual void encode(eckit::Stream&) const override; virtual const eckit::ReanimatorBase& reanimator() const override { return reanimator_; } // -- Class methods static const eckit::ClassSpec& classSpec(); private: // -- Members unsigned long long clientID_; eckit::Length length_; eckit::Length total_; bool receiving_; bool streamMode_; bool doCRC_; unsigned long crc_; // -- Methods void updateCRC(void*, long); // -- Class members static eckit::Reanimator reanimator_; friend class MarsHandleStream; }; #endif metkit-1.20.2/src/metkit/mars/RequestEnvironment.cc0000664000175000017500000000377615246725757022530 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/RequestEnvironment.h" #include #include #include #include #include #include "eckit/runtime/Main.h" #include "eckit/system/SystemInfo.h" #include "eckit/utils/Tokenizer.h" namespace metkit::mars { RequestEnvironment::RequestEnvironment(const RequestEnvironment& other) : env_(other.env_) {} void RequestEnvironment::update(const std::map& env) { // Split string on '/' and ignore empty splits const auto split = [](const std::string& str) -> std::vector { std::vector result; eckit::Tokenizer parse("/"); parse(str, result); return result; }; for (const auto& [k, v] : env) { env_->values(k, split(v)); } } void RequestEnvironment::initialize(const std::map& env) { RequestEnvironment& re = inst(); std::lock_guard lock(re.init_); re.env_.emplace("environ"); re.env_->setValue("host", eckit::Main::hostname()); re.env_->setValue("user", eckit::system::SystemInfo::instance().userName()); re.env_->setValue("pid", getpid()); re.env_->setValue("client", "unknown"); re.update(env); } RequestEnvironment& RequestEnvironment::inst() { static RequestEnvironment e; return e; } const RequestEnvironment& RequestEnvironment::instance() { auto& re = RequestEnvironment::inst(); std::lock_guard lock(re.init_); if (!re.env_) { throw eckit::SeriousBug("RequestEnvironment not initialized"); } return re; } } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypesFactory.h0000664000175000017500000000431315246725757021135 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypesFactory.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #ifndef metkit_TypesFactory_H #define metkit_TypesFactory_H #include #include "eckit/memory/NonCopyable.h" #include "eckit/types/Types.h" namespace eckit { class Value; } namespace metkit { namespace mars { class Type; class TypesFactory; //---------------------------------------------------------------------------------------------------------------------- class TypesRegistry : private eckit::NonCopyable { eckit::Mutex mutex_; std::map m_; public: static TypesRegistry& instance(); void add(const std::string& name, TypesFactory* f); void remove(const std::string& name); Type* build(const std::string& keyword, const eckit::Value&); void list(std::ostream& s); }; /// A self-registering factory for producing TypesFactory instances class TypesFactory { public: virtual Type* make(const std::string& keyword, const eckit::Value& settings) const = 0; static Type* build(const std::string& keyword, const eckit::Value& settings); static void list(std::ostream& s); protected: TypesFactory(const std::string&); ~TypesFactory(); std::string name_; }; /// Templated specialisation of the self-registering factory, /// that does the self-registration, and the construction of each object. template class TypeBuilder : public TypesFactory { Type* make(const std::string& keyword, const eckit::Value& settings) const override { return new T(keyword, settings); } public: TypeBuilder(const std::string& name) : TypesFactory(name) {} }; //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/StepRange.h0000664000175000017500000000701215246725757020370 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 StepRange.h // Baudouin Raoult - ECMWF Dec 97 #ifndef metkit_StepRange_H #define metkit_StepRange_H #include #include "eckit/persist/Bless.h" #include "eckit/types/Time.h" #include "eckit/types/Types.h" namespace eckit { class DumpLoad; } namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class StepRange { public: // -- Exceptions // None // -- Contructors StepRange(const std::string&); explicit StepRange(eckit::Time from = eckit::Time(0), eckit::Time to = eckit::Time(0)) : from_(from / 3600.), to_(to / 3600.) { if (from != eckit::Time(0) && to == eckit::Time(0)) { to_ = from_; } } explicit StepRange(double from, double to = 0) : StepRange(eckit::Time(std::lround(from * 3600.), true), eckit::Time(std::lround(to * 3600.), true)) {} #include "metkit/mars/StepRange.b" // -- Destructor ~StepRange() {} // -- Convertors // None // -- Operators // None operator std::string() const; std::string toString(bool useDays = false, bool alwaysUseHours = false) const; StepRange& operator+=(const eckit::Time& step) { from_ += step / 3600.; to_ += step / 3600.; return *this; } StepRange& operator-=(const eckit::Time& step) { from_ -= step / 3600.; to_ -= step / 3600.; return *this; } bool operator==(const StepRange& other) const { return from_ == other.from_ && to_ == other.to_; } bool operator!=(const StepRange& other) const { return from_ != other.from_ || to_ != other.to_; } bool operator<(const StepRange& other) const { return (from_ != other.from_) ? (from_ < other.from_) : (to_ < other.to_); } bool operator<=(const StepRange& other) const { return (from_ != other.from_) ? (from_ <= other.from_) : (to_ <= other.to_); } bool operator>(const StepRange& other) const { return other < *this; } // -- Methods double from() const { return from_; } double to() const { return to_; } void dump(eckit::DumpLoad&) const; void load(eckit::DumpLoad&); // -- Overridden methods // None // -- Class members // None // -- Class methods // None // Uncomment for persistent, remove otherwise protected: // -- Members // None // -- Methods void print(std::ostream& s, bool useDays = false, bool alwaysUseHours = false) const; // -- Overridden methods // None // -- Class members // None // -- Class methods // None private: // No copy allowed // -- Members double from_; double to_; // -- Methods // None // -- Overridden methods // None // -- Class members // None // -- Class methods // None // -- Friends friend std::ostream& operator<<(std::ostream& s, const StepRange& p) { p.print(s); return s; } }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/TypeRegex.h0000664000175000017500000000246615246725757020424 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeRegex.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date April 2016 #ifndef metkit_TypeRegex_H #define metkit_TypeRegex_H #include "eckit/utils/Regex.h" #include "metkit/mars/Type.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- class TypeRegex : public Type { public: // methods TypeRegex(const std::string& name, const eckit::Value& settings); ~TypeRegex() noexcept override = default; private: // methods void print(std::ostream& out) const override; bool expand(std::string& value, const MarsRequest& request) const override; std::vector regex_; bool uppercase_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/TypeMixed.cc0000664000175000017500000000512215246725757020546 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeMixed.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypesFactory.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- TypeMixed::TypeMixed(const std::string& name, const eckit::Value& settings) : Type(name, settings) { eckit::Value types = settings["type"]; eckit::Value cfg; for (size_t i = 0; i < types.size(); ++i) { if (types[i].isString()) { cfg = settings; cfg["type"] = types[i]; Type* k = TypesFactory::build(name + "." + std::string(types[i]), cfg); k->attach(); types_.emplace_back(nullptr, k); } else { // it is a subtype, potentially with a Context cfg = types[i]; eckit::Value type = cfg["type"]; std::unique_ptr c; if (cfg.contains("context")) { c = Context::parseContext(cfg["context"]); } Type* k = TypesFactory::build(name + "." + std::to_string(i) + "." + std::string(type), cfg); k->attach(); types_.emplace_back(std::move(c), k); } } } TypeMixed::~TypeMixed() noexcept { for (auto it = types_.begin(); it != types_.end(); it++) { (*it).second->detach(); } } void TypeMixed::print(std::ostream& out) const { out << "TypeMixed[name=" << name_; for (auto it = types_.begin(); it != types_.end(); it++) { out << "," << *((*it).second); } out << "]"; } bool TypeMixed::expand(std::string& value, const MarsRequest& request) const { for (auto it = types_.begin(); it != types_.end(); it++) { if ((*it).first == nullptr || (*it).first->matches(request)) { std::string tmp = value; if ((*it).second->expand(tmp, request)) { value = tmp; return true; } } } return false; } static TypeBuilder type("mixed"); //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/ClientTask.h0000664000175000017500000001171715246725757020550 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 ClientTask.h // Baudouin Raoult - ECMWF Oct 96 #ifndef metkit_ClientTask_H #define metkit_ClientTask_H #include // #include "eckit/bases/Watcher.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/DataHandle.h" // #include "eckit/log/UserChannel.h" #include "eckit/transaction/TxnEvent.h" #include "metkit/mars/MarsRequest.h" namespace metkit { namespace mars { class ClientTask { public: // -- Contructors ClientTask(const MarsRequest&, const MarsRequest&, const std::string& name, int port, unsigned long long id = 0); // ClientTask(eckit::Stream&); // -- Destructor ~ClientTask(); // -- Convertors // None // -- Operators // None // -- Methods // void dump(std::ostream&) const; // void json(eckit::JSON&) const; // MarsInfo info() const; // const MarsID& id() const { return transactionID(); } // const MarsRequest& request() const { return request_; } // const MarsRequest& environ() const { return environ_; } // eckit::Length transferData(const eckit::PathName&); // eckit::PathName transferData(); // /// @returns eckit::DataHandle with the data that will be received, but does not give ownership of the // eckit::DataHandle eckit::DataHandle& getDataHandle(); // void sendData(eckit::DataHandle&); // void sendHandle(eckit::DataHandle&); // void acknowledge(eckit::Stream& s) { send(s,'a'); } // void patch(); // bool authenticated() const; // eckit::DataHandle& dataHandle() { return *handle_; } // // Report // void success(); // void failure(const std::string&); // void retry(const std::string&); // void infoMsg(const std::string&); // void warningMsg(const std::string&); // void errorMsg(const std::string&); // void notifyClient(const std::string&); // void notifyStart(); // void queueTime(); // void ping(); // void sendCost(); // void sendChecksum(const std::string&); // // Queuing // double startingPriority() const { return startingPriority_;} // void startingPriority(double p) { startingPriority_ = p;} // // Cost // Cost& cost() { return cost_; } // const Cost& cost() const { return cost_; } // void costChanged(); // // Called by PipeProcess // void send(eckit::Stream&); // void reply(eckit::Stream&); // bool error(std::exception&,int); // void done(); // Logging // Mars tree // void push(const std::string&,const std::string&); // void pop(); // void reset(); // const eckit::StringList& treeNames() const { return treeNames_; } // const eckit::StringList& treeValues() const { return treeValues_; } // // For the metkit void send(eckit::Stream&) const; char receive(eckit::Stream&) const; // -- Overridden methods // From Streamble // virtual void encode(eckit::Stream&) const override; // virtual const eckit::ReanimatorBase& reanimator() const { return reanimator_; } // From watcher // virtual void watch(); // -- Class methods // static const eckit::ClassSpec& classSpec() { return classSpec_;} // static void recover(TxnRecoverer&); // static void find(TxnFinder&); // static std::string commandName(); // None protected: // -- Members MarsRequest request_; MarsRequest environ_; private: // -- Members // unsigned long long txnID_; // unused unsigned long long metkitID_; int port_; std::string host_; std::unique_ptr handle_; std::string checksum_; // Not sent over streams // double startingPriority_; // Cost cost_; // time_t lastPing_; // time_t queueTime_; // eckit::StringList treeValues_; // eckit::StringList treeNames_; // bool authenticated_; // -- Methods // void print(std::ostream&) const; // void send(eckit::Stream&,char) const; // -- Overridden methods // None // -- Class members // static eckit::ClassSpec classSpec_; // static eckit::Reanimator reanimator_; // -- Class methods // None // friend std::ostream& operator<<(std::ostream& s, const ClientTask& r) // { r.print(s); return s; } // friend eckit::JSON& operator<<(eckit::JSON& s, const ClientTask& r) // { r.json(s); return s; } }; } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/DHSProtocol.cc0000664000175000017500000004367715246725757021017 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 DHSProtocol.cc // Baudouin Raoult - (c) ECMWF Feb 12 #include "metkit/mars/DHSProtocol.h" #include "eckit/config/Configuration.h" #include "eckit/config/Resource.h" #include "eckit/log/Log.h" #include "eckit/net/Endpoint.h" #include "eckit/net/IPAddress.h" #include "eckit/net/TCPClient.h" #include "eckit/net/TCPServer.h" #include "eckit/net/TCPSocket.h" #include "eckit/net/TCPStream.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Translator.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/ClientTask.h" #include "metkit/mars/RequestEnvironment.h" using namespace eckit; using eckit::net::Endpoint; namespace metkit::mars { static Reanimator dhsProtocolReanimator; // --------------------------------------------------------------------------------------------------------------------- namespace { constexpr const int DEFAULT_CALLBACK_PROXY_PORT = 9707; // Normally we would let the Endpoint constructor do this unpacking itself, but we want to set a default port Endpoint unpackHostPort(const std::string& hoststr) { auto bits = StringTools::split(":", hoststr); ASSERT(!bits.empty() && bits.size() < 3); int port = DEFAULT_CALLBACK_PROXY_PORT; if (bits.size() == 2) { port = Translator()(bits[1]); } return {bits[0], port}; } Endpoint selectProxyHost(const std::vector proxies) { return unpackHostPort(proxies[std::rand() % proxies.size()]); } Endpoint selectProxyHost(const Configuration& config) { if (config.has("proxyHost")) { return unpackHostPort(config.getString("proxyHost")); } if (config.has("proxyHosts")) { return selectProxyHost(config.getStringVector("proxyHosts")); } throw UserError("Neither proxyHosts nor proxyHost specified in configuration"); } } // namespace // Implement the default callback behaviour. Client opens a socket that can be connected to by // the server or data mover class SimpleCallback : public BaseCallbackConnection { public: SimpleCallback() : callback_(Resource("$MARS_DHS_LOCALPORT", 0)), callbackEndpoint_(computeEndpoint()) { LOG_DEBUG_LIB(LibMetkit) << "Simple callback. local port=" << callback_.localPort() << " host=" << callbackEndpoint_.host() << " port=" << callbackEndpoint_.port() << std::endl; } explicit SimpleCallback(const Configuration&) : SimpleCallback() {} explicit SimpleCallback(Stream&) : SimpleCallback() {} private: const Endpoint& endpoint() const override { return callbackEndpoint_; } net::TCPSocket& connect() override { return callback_.accept(); } net::EphemeralTCPServer callback_; Endpoint callbackEndpoint_; Endpoint computeEndpoint() const { static std::string callbackHost = Resource("$MARS_DHS_CALLBACK_HOST", ""); static int callbackPort = Resource("$MARS_DHS_CALLBACK_PORT", 0); if (callbackHost.empty()) { return Endpoint{net::IPAddress::hostAddress(callback_.localHost()).asString(), callback_.localPort()}; } if (callbackPort == 0) { return Endpoint{callbackHost, callback_.localPort()}; } return Endpoint{callbackHost, callbackPort}; } public: static const ClassSpec& classSpec() { static ClassSpec spec = {&BaseCallbackConnection::classSpec(), "SimpleCallback"}; return spec; } protected: const ReanimatorBase& reanimator() const override { return reanimator_; } void encode(Stream&) const override {} static Reanimator reanimator_; }; Reanimator SimpleCallback::reanimator_; // --------------------------------------------------------------------------------------------------------------------- // Implement a callback via the callback proxy. First we open a connection to the proxy. This then returns to us // the host/port that it has made available for the server/mover to connect to. It then proxies the connection // to our host/port class ProxyCallback : public BaseCallbackConnection { public: explicit ProxyCallback(const Endpoint& proxyhost) : control_(net::TCPClient().connect(proxyhost)), proxyHost_(proxyhost) { LOG_DEBUG_LIB(LibMetkit) << "Proxy callback. proxyhost=" << proxyhost.host() << " proxyport=" << proxyhost.port() << std::endl; std::string localAddr = net::IPAddress::hostAddress(callback_.localHost()).asString(); int localPort = callback_.localPort(); bool passive = false; control_ << localAddr; control_ << localPort; control_ << passive; remoteAddr_ = Endpoint(control_); } explicit ProxyCallback(const Configuration& config) : ProxyCallback(selectProxyHost(config)) {} explicit ProxyCallback(Stream& s) : ProxyCallback(Endpoint(s)) {} private: const Endpoint& endpoint() const override { return remoteAddr_; } net::TCPSocket& connect() override { // FIXME: Check that the callback connection is still alive... return callback_.accept(); } net::TCPStream control_; net::EphemeralTCPServer callback_; Endpoint proxyHost_; Endpoint remoteAddr_; public: static const ClassSpec& classSpec() { static ClassSpec spec = {&BaseCallbackConnection::classSpec(), "ProxyCallback"}; return spec; } protected: const ReanimatorBase& reanimator() const override { return reanimator_; } void encode(Stream& s) const override { s << proxyHost_; } static Reanimator reanimator_; }; Reanimator ProxyCallback::reanimator_; // --------------------------------------------------------------------------------------------------------------------- // Implement a callback via the callback proxy using passive mode. First we open a connection to the proxy. // This then returns to us the host/port that it has made available for the server/mover to connect to. // We then need to open connections to the proxy for the callbacks to connect to. class PassiveProxyCallback : public BaseCallbackConnection { public: explicit PassiveProxyCallback(const Endpoint& proxyhost, bool useProxyHostAsCallback = true) : control_(net::TCPClient().connect(proxyhost)), proxyHost_(proxyhost) { LOG_DEBUG_LIB(LibMetkit) << "Passive proxy callback. proxyhost=" << proxyhost << std::endl; std::string localAddr = ""; int localPort = -1; bool passive = true; control_ << localAddr; control_ << localPort; control_ << passive; remoteAddr_ = Endpoint(control_); LOG_DEBUG_LIB(LibMetkit) << "Remote address. host=" << remoteAddr_ << std::endl; passiveAddr_ = Endpoint(control_); if (useProxyHostAsCallback) { passiveAddr_ = Endpoint(proxyhost.host(), passiveAddr_.port()); } LOG_DEBUG_LIB(LibMetkit) << "Passive address. host=" << passiveAddr_ << std::endl; control_ >> passiveCheck_; LOG_DEBUG_LIB(LibMetkit) << "Passive address. host=" << passiveAddr_ << " check=" << passiveCheck_ << std::endl; } explicit PassiveProxyCallback(const Configuration& config) : PassiveProxyCallback(selectProxyHost(config), config.getBool("useProxyHostAsCallback", true)) {} explicit PassiveProxyCallback(Stream& s) : PassiveProxyCallback(Endpoint(s)) {} private: const Endpoint& endpoint() const override { return remoteAddr_; } net::TCPSocket& connect() override { // FIXME: Check that the callback connection is still alive... ASSERT(!socket_.isConnected()); socket_ = net::TCPClient().connect(passiveAddr_); net::InstantTCPStream s(socket_); s << passiveCheck_; return socket_; } net::TCPStream control_; net::TCPSocket socket_; Endpoint proxyHost_; Endpoint remoteAddr_; Endpoint passiveAddr_; unsigned long long passiveCheck_; public: static const ClassSpec& classSpec() { static ClassSpec spec = {&BaseCallbackConnection::classSpec(), "PassiveProxyCallback"}; return spec; } protected: const ReanimatorBase& reanimator() const override { return reanimator_; } void encode(Stream& s) const override { s << proxyHost_; } static Reanimator reanimator_; }; Reanimator PassiveProxyCallback::reanimator_; // --------------------------------------------------------------------------------------------------------------------- const ClassSpec& BaseCallbackConnection::classSpec() { static ClassSpec spec = {&Streamable::classSpec(), "BaseCallbackConnection"}; return spec; } BaseCallbackConnection* BaseCallbackConnection::build(const Configuration& config, const std::string& host) { if (config.has("proxyHost") || config.has("proxyHosts") || (config.getBool("passiveProxy", true) && config.getBool("useHostAsProxy", false))) { if (config.getBool("passiveProxy", true)) { if (config.getBool("useHostAsProxy", false)) { return new PassiveProxyCallback{unpackHostPort(host)}; } return new PassiveProxyCallback{config}; } return new ProxyCallback{config}; } static bool passiveProxy = Resource("$MARS_DHS_PASSIVE_PROXY", true); static std::vector proxyHosts = Resource>("$MARS_DHS_CALLBACK_PROXY_HOST", {}); if (!proxyHosts.empty()) { Endpoint proxyHost = selectProxyHost(proxyHosts); if (passiveProxy) { return new PassiveProxyCallback(proxyHost); } else { return new ProxyCallback(proxyHost); } } return new SimpleCallback{config}; } // --------------------------------------------------------------------------------------------------------------------- DHSProtocol::DHSProtocol(const std::string& name, const std::string& host, int port, bool forwardMessages) : callback_(new SimpleCallback{}), name_(name), host_(host), port_(port), done_(false), error_(false), sending_(false), forward_(forwardMessages) {} DHSProtocol::DHSProtocol(const Configuration& params) : BaseProtocol(params), name_(params.getString("name")), port_(params.getInt("port", 9000)), done_(false), error_(false), sending_(false), forward_(false) { if (params.has("hosts")) { std::vector hosts = params.getStringVector("hosts"); host_ = hosts.at(std::rand() % hosts.size()); } else { ASSERT(params.has("host")); host_ = params.getString("host"); } env_ = RequestEnvironment::instance().request(); callback_.reset(BaseCallbackConnection::build(params, host_)); } DHSProtocol::DHSProtocol(const Configuration& params, const std::map& env) : DHSProtocol(params) { RequestEnvironment requestEnv{RequestEnvironment::instance()}; requestEnv.update(env); env_ = requestEnv.request(); } DHSProtocol::DHSProtocol(Stream& s) : BaseProtocol(s), callback_(Reanimator::reanimate(s)) { s >> name_; s >> host_; s >> port_; s >> done_; s >> error_; s >> sending_; s >> forward_; env_ = MarsRequest(s); } /// @attention this dtor may throw in cleanup()! DHSProtocol::~DHSProtocol() { done_ = true; cleanup(); } const ReanimatorBase& DHSProtocol::reanimator() const { return dhsProtocolReanimator; } const ClassSpec& DHSProtocol::classSpec() { static ClassSpec spec = {&BaseProtocol::classSpec(), "DHSProtocol"}; return spec; } Length DHSProtocol::retrieve(const MarsRequest& request) { Endpoint callbackEndpoint = callback_->endpoint(); LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol: call back on " << callbackEndpoint << std::endl; task_.reset(new ClientTask(request, env_, callbackEndpoint.host(), callbackEndpoint.port())); net::TCPStream s(net::TCPClient().connect(host_, port_)); task_->send(s); ASSERT(task_->receive(s) == 'a'); // Acknoledgement Length result = 0; while (wait(result)) {} LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol::retrieve " << result << std::endl; return result; } void DHSProtocol::archive(const MarsRequest& request, const Length& size) { Endpoint callbackEndpoint = callback_->endpoint(); LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol::archive " << size << std::endl; LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol: call back on " << callbackEndpoint << std::endl; task_.reset(new ClientTask(request, env_, callbackEndpoint.host(), callbackEndpoint.port())); net::TCPStream s(net::TCPClient().connect(host_, port_)); task_->send(s); ASSERT(task_->receive(s) == 'a'); // Acknoledgement Length result = size; while (wait(result)) {} LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol: archive completed." << std::endl; } void DHSProtocol::cleanup() { if (socket_.isConnected()) { if (sending_) { unsigned long version = 1; unsigned long long crc = 0; try { net::InstantTCPStream s(socket_); s << version; s << crc; } catch (std::exception& e) { Log::error() << "** " << e.what() << " Caught in " << Here() << std::endl; Log::error() << "** Exception is ignored" << std::endl; } } socket_.close(); } sending_ = false; if (!done_) { Length result = 0; while (wait(result)) { ; } } if (error_) { error_ = false; throw UserError(std::string("Error from [") + name_ + "]: " + msg_); } } void DHSProtocol::print(std::ostream& s) const { s << "DHSProtocol[" << name_ << "]"; } void DHSProtocol::encode(Stream& s) const { BaseProtocol::encode(s); callback_->encode(s); s << name_; s << host_; s << port_; s << done_; s << error_; s << sending_; s << forward_; s << env_; } long DHSProtocol::read(void* buffer, long len) { return socket_.read(buffer, len); } long DHSProtocol::write(const void* buffer, long len) { return socket_.write(buffer, len); } bool DHSProtocol::wait(Length& size) { for (;;) { if (socket_.isConnected()) socket_.close(); socket_ = callback_->connect(); net::InstantTCPStream s(socket_); char code = task_->receive(s); LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol: code [" << code << "]" << std::endl; std::string msg; long long bytes; switch (code) { /* OK */ case 'o': done_ = true; return false; /* read source */ case 'r': bytes = size; LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol:r [" << bytes << "]" << std::endl; s << bytes; sending_ = true; return false; /* get */ case 'h': NOTIMP; case 'w': s >> bytes; LOG_DEBUG_LIB(LibMetkit) << "DHSProtocol:w " << bytes << std::endl; size = bytes; return false; case 'm': NOTIMP; case 'X': NOTIMP; case 'e': s >> msg_; Log::error() << msg_ << " [" << name_ << "]" << std::endl; error_ = true; done_ = true; return false; case 'y': /* retry */ NOTIMP; case 'I': /* info */ s >> msg; Log::info() << msg << " [" << name_ << "]" << std::endl; if (forward_) { Log::userInfo() << msg << " [" << name_ << "]" << std::endl; } break; case 'W': /* warning */ s >> msg; Log::warning() << msg << " [" << name_ << "]" << std::endl; if (forward_) { Log::userWarning() << msg << " [" << name_ << "]" << std::endl; } break; case 'D': /* debug */ s >> msg; LOG_DEBUG_LIB(LibMetkit) << msg << " [" << name_ << "]" << std::endl; if (forward_) { Log::userInfo() << msg << " [" << name_ << "]" << std::endl; } break; case 'E': /* error */ s >> msg; Log::error() << msg << " [" << name_ << "]" << std::endl; if (forward_) { Log::userError() << msg << " [" << name_ << "]" << std::endl; } break; case 'N': /* notification */ NOTIMP; case 'p': /* ping */ s << 'p'; break; case 's': /* statistics */ { int n; s >> n; std::string key, value; for (int i = 0; i < n; i++) { s >> key >> value; stats_[key] = value; Log::info() << "DHSProtocol:s " << key << "=" << value << std::endl; } } break; case 'S': /* notification start */ NOTIMP; case 't': /* new timeout */ NOTIMP; default: throw Exception(std::string("Unknown code [") + code + "]"); } } } static ProtocolBuilder builder("dhsbase"); } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/Parameter.h0000664000175000017500000000414215246725757020421 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #ifndef metkit_Parameter_H #define metkit_Parameter_H #include "eckit/types/Date.h" #include "eckit/types/Double.h" #include "eckit/types/Time.h" #include "eckit/utils/Translator.h" #include "eckit/value/Value.h" namespace eckit { class JSON; class MD5; } // namespace eckit namespace metkit { namespace mars { class Type; class MarsRequest; //---------------------------------------------------------------------------------------------------------------------- class Parameter { public: // methods Parameter(); ~Parameter(); Parameter(const std::vector& values, Type* = 0); Parameter(const Parameter&); Parameter& operator=(const Parameter&); bool operator<(const Parameter&) const; const std::vector& values() const { return values_; } void values(const std::vector& values); bool filter(const std::vector& filter); bool filter(const std::string& keyword, const std::vector& filter); bool matches(const std::vector& matches) const; void merge(const Parameter& p); Type& type() const { return *type_; } const std::string& name() const; size_t count() const; private: // methods void print(std::ostream&) const; friend std::ostream& operator<<(std::ostream& s, const Parameter& p) { p.print(s); return s; } private: // members Type* type_; std::vector values_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit #endif metkit-1.20.2/src/metkit/mars/MarsParser.cc0000664000175000017500000001111515246725757020714 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 MarsParser.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Jun 2012 #include "metkit/mars/MarsParser.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- std::string MarsParser::parseString(char quote) { consume(quote); std::string s; for (;;) { char c = next(true); if (c == '\\') { c = next(true); switch (c) { case '"': s += '"'; break; case '\'': s += '\''; break; case '\\': s += '\\'; break; case '/': s += '/'; break; case 'b': s += '\b'; break; case 'f': s += '\f'; break; case 'n': s += '\n'; break; case 'r': s += '\r'; break; case 't': s += '\t'; break; case 'u': throw StreamParser::Error(std::string("JSONTokenizer::parseString \\uXXXX format not supported")); default: throw StreamParser::Error(std::string("JSONTokenizer::parseString invalid \\ char '") + c + "'"); } } else { if (c == quote) { return s; // void(s); } s += c; } } } static bool inindent(char c) { return isalnum(c) || c == '_' || c == ':' || c == '-' || c == '.' || c == '@'; } void MarsParser::quoted(std::ostream& out, const std::string& value) { char quote = 0; for (std::string::const_iterator j = value.begin(); j != value.end(); ++j) { if (!inindent(*j)) { quote = '"'; break; } } if (quote) { out << quote << value << quote; } else { out << value; } } std::string MarsParser::parseIndents() { std::ostringstream oss; oss << parseIndent(); for (;;) { char c = peek(true); while (c == ' ') { next(true); c = peek(true); } if (!inindent(c)) { break; } oss << " " << parseIndent(); } return oss.str(); } std::string MarsParser::parseValue() { char c = peek(); if (c == '\"' || c == '\'') { return parseString(c); } return parseIndents(); } std::vector MarsParser::parseValues() { std::vector v(1, parseValue()); char c = peek(); while (c == '/') { consume('/'); v.push_back(parseValue()); c = peek(); } return v; } std::string MarsParser::parseIndent() { char c = peek(); std::string s; while (inindent(c)) { s += next(true); c = peek(true); } return s; } std::string MarsParser::parseVerb() { char c = peek(); if (!isalpha(c) && c != '_') { throw StreamParser::Error(std::string("MarsParser::parseVerb invalid char '") + c + "'", line_ + 1); } return parseIndent(); } MarsParsedRequest MarsParser::parseRequest() { MarsParsedRequest r(parseVerb(), line_ + 1); char c = peek(); while (c == ',') { consume(','); std::string key = parseIndents(); consume('='); r.values(key, parseValues()); c = peek(); } if (c == '.') { consume('.'); } return r; } MarsParser::MarsParser(std::istream& in) : StreamParser(in, true, "#") {} std::vector MarsParser::parse() { std::vector result; while (peek() != 0) { result.push_back(parseRequest()); } return result; } void MarsParser::parse(MarsParserCallback& cb) { while (peek() != 0) { auto r = parseRequest(); cb(r); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsRequest.cc0000664000175000017500000003702315246725757021116 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/log/JSON.h" #include "eckit/log/Log.h" #include "eckit/types/Types.h" #include "eckit/utils/MD5.h" #include "eckit/utils/StringTools.h" #include "eckit/message/Message.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsParser.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/ParamID.h" #include "metkit/mars/TypeAny.h" namespace metkit { namespace mars { //---------------------------------------------------------------------------------------------------------------------- MarsRequest::MarsRequest() {} MarsRequest::MarsRequest(const std::string& s) : verb_(s) { ASSERT(s.find(',') == std::string::npos); } MarsRequest::MarsRequest(const std::string& s, const std::map& values) : verb_(s) { for (auto j = values.begin(); j != values.end(); ++j) { const std::string& param = (*j).first; const std::string& value = (*j).second; params_.push_back(Parameter(std::vector(1, value), new TypeAny(param))); } } MarsRequest::MarsRequest(const std::string& s, const eckit::Value& values) : verb_(s) { eckit::ValueMap m = values; for (auto j = m.begin(); j != m.end(); ++j) { const std::string& param = (*j).first; const eckit::Value& value = (*j).second; if (value.isList()) { std::vector vals; eckit::fromValue(vals, value); params_.push_back(Parameter(vals, new TypeAny(param))); } else { params_.push_back(Parameter(std::vector(1, value), new TypeAny(param))); } } } MarsRequest::MarsRequest(const eckit::message::Message& message) : verb_("message") { eckit::message::StringSetter setter(*this); message.getMetadata(setter); } MarsRequest::MarsRequest(eckit::Stream& s, bool lowercase) { int size; s >> verb_; if (lowercase) verb_ = eckit::StringTools::lower(verb_); s >> size; for (int i = 0; i < size; i++) { std::string param; int count; s >> param; if (lowercase) param = eckit::StringTools::lower(param); s >> count; std::vector v; v.reserve(count); for (int k = 0; k < count; k++) { std::string value; s >> value; v.push_back(value); } params_.push_back(Parameter(v, new TypeAny(param))); } } void MarsRequest::encode(eckit::Stream& s) const { s << verb_; int size = params_.size(); s << size; for (std::list::const_iterator i = params_.begin(); i != params_.end(); ++i) { s << (*i).name(); const std::vector& v = (*i).values(); int size = v.size(); // For backward compatibility s << size; for (std::vector::const_iterator k = v.begin(); k != v.end(); ++k) { s << *k; } } } bool MarsRequest::empty() const { return params_.empty(); } void MarsRequest::print(std::ostream& s) const { dump(s, "", "", true); } void MarsRequest::dump(std::ostream& s, const char* cr, const char* tab, bool verb) const { std::list::const_iterator begin = params_.begin(); std::list::const_iterator end = params_.end(); if (verb) { s << verb_ << ','; } std::string separator = ""; if (begin != end) { s << separator << cr << tab; separator = ","; int a = 0; for (std::list::const_iterator i = begin; i != end; ++i) { if (a++) { s << ','; s << cr << tab; } int b = 0; s << (*i).name() // << "." << (*i).second.type() << "="; const std::vector& v = (*i).values(); for (std::vector::const_iterator k = v.begin(); k != v.end(); ++k) { if (b++) { s << '/'; } MarsParser::quoted(s, *k); } // s << " {" << (*i).type().category() << "}"; } } s << cr << cr; } void MarsRequest::json(eckit::JSON& s, bool array) const { s.startObject(); // s << "_verb" << verb_; std::list::const_iterator begin = params_.begin(); std::list::const_iterator end = params_.end(); for (std::list::const_iterator i = begin; i != end; ++i) { s << (*i).name(); const std::vector& v = (*i).values(); bool list = v.size() != 1 || (array && (*i).type().multiple()); if (list) { s.startList(); } for (std::vector::const_iterator k = v.begin(); k != v.end(); ++k) { s << (*k); } if (list) { s.endList(); } } s.endObject(); } void MarsRequest::md5(eckit::MD5& md5) const { std::ostringstream oss; oss << *this; md5.add(oss.str()); } void MarsRequest::unsetValues(const std::string& name) { std::list::iterator i = find(name); if (i != params_.end()) { params_.erase(i); } } void MarsRequest::setValuesTyped(Type* type, const std::vector& values) { std::list::iterator i = find(type->name()); if (i != params_.end()) { (*i) = Parameter(values, type); } else { params_.push_back(Parameter(values, type)); } } bool MarsRequest::filter(const MarsRequest& filter) { for (std::list::iterator i = params_.begin(); i != params_.end(); ++i) { if ((*i).name() == "date") { std::list::const_iterator j = filter.find("day"); if (j != filter.params_.end()) { if (!(*i).filter("day", (*j).values())) { return false; } } } std::list::const_iterator j = filter.find((*i).name()); if (j == filter.params_.end()) { continue; } if (!(*i).filter((*j).values())) { return false; } } return true; } bool MarsRequest::matches(const MarsRequest& matches) const { std::vector params = matches.params(); for (std::vector::const_iterator j = params.begin(); j != params.end(); ++j) { std::list::const_iterator k = find(*j); if (k == params_.end()) { return false; } if (!(*k).matches(matches.values(*j))) { return false; } } return true; } void MarsRequest::values(const std::string& name, const std::vector& v) { std::list::iterator i = find(name); if (i != params_.end()) { (*i).values(v); } else { params_.push_back(Parameter(v, new TypeAny(name))); } } size_t MarsRequest::countValues(const std::string& name) const { std::list::const_iterator i = find(name); if (i != params_.end()) { return (*i).values().size(); } return 0; } bool MarsRequest::has(const std::string& name) const { return find(name) != params_.end(); } bool MarsRequest::is(const std::string& name, const std::string& value) const { std::list::const_iterator i = find(name); if (i != params_.end()) { const std::vector& v = (*i).values(); return v.size() == 1 && v[0] == value; } return false; } const std::vector& MarsRequest::values(const std::string& name, bool emptyOk) const { std::list::const_iterator i = find(name); if (i == params_.end()) { if (emptyOk) { static std::vector empty; return empty; } std::ostringstream oss; oss << "No parameter called '" << name << "' in request " << *this; throw eckit::UserError(oss.str()); } return (*i).values(); } std::optional>> MarsRequest::get( const std::string& keyword) const { std::list::const_iterator i = find(keyword); if (i == params_.end()) { return std::nullopt; } return std::cref((*i).values()); } const std::string& MarsRequest::operator[](const std::string& name) const { std::list::const_iterator i = find(name); if (i == params_.end()) { std::ostringstream oss; oss << "Parameter '" << name << "' is undefined"; throw eckit::UserError(oss.str()); } const std::vector& c = (*i).values(); if (c.size() > 1) { std::ostringstream oss; oss << "Parameter '" << name << "' has more than one value"; throw eckit::UserError(oss.str()); } return c[0]; } void MarsRequest::getParams(std::vector& p) const { p.clear(); for (std::list::const_iterator i = params_.begin(); i != params_.end(); ++i) { p.push_back((*i).name()); } } size_t MarsRequest::count() const { size_t result = 1; const std::set& paramIdsSingleLevel = ParamID::getMlParamsSingleLevel(); bool ml = false; size_t levels = 1; size_t params = 0; size_t paramsSingleLevel = 0; bool hasLevelOne = false; for (const auto& p : params_) { if (p.values().size() == 1 && (p.values().at(0) == "all" || p.values().at(0) == "any")) { return 0; } if (p.name() == "levelist") { levels = p.values().size(); // For a parameter matching paramIdsSingleLevel, we can only include one value, and for level=1 hasLevelOne = std::find(p.values().begin(), p.values().end(), "1") != p.values().end(); } else { if (p.name() == "param") { for (const auto& v : p.values()) { paramsSingleLevel += paramIdsSingleLevel.count(v); } params = p.values().size() - paramsSingleLevel; } else { if (p.name() == "levtype") { ml = std::find(p.values().begin(), p.values().end(), "ml") != p.values().end(); } result *= p.count(); } } } if ((params + paramsSingleLevel) > 0) { if (ml) { result *= (levels * params + (hasLevelOne ? paramsSingleLevel : 0)); } else { result *= (levels * (params + paramsSingleLevel)); } } return result; } std::vector MarsRequest::params() const { std::vector p; getParams(p); return p; } MarsRequest::operator eckit::Value() const { NOTIMP; } // recursively expand along keys in expvalues void expand_along_keys(const MarsRequest& prototype, const std::vector>>& expvalues, std::vector& requests, size_t i) { if (i == expvalues.size()) { requests.push_back(prototype); return; } const std::string& key = expvalues[i].first; const std::vector& values = expvalues[i].second; MarsRequest req(prototype); for (auto& value : values) { req.setValue(key, value); expand_along_keys(req, expvalues, requests, i + 1); } } std::vector MarsRequest::split(const std::vector& keys) const { size_t n = 1; LOG_DEBUG_LIB(LibMetkit) << "Splitting request with keys" << keys << std::endl; std::vector>> expvalues; for (auto& key : keys) { std::vector v = values(key, true); // ok to be empty LOG_DEBUG_LIB(LibMetkit) << "splitting along key " << key << " n values " << v.size() << " values " << v << std::endl; if (v.empty()) continue; n *= v.size(); expvalues.emplace_back(std::make_pair(key, v)); } std::vector requests; requests.reserve(n); if (n == 1) { requests.push_back(*this); return requests; } expand_along_keys(*this, expvalues, requests, 0); return requests; } std::vector MarsRequest::split(const std::string& key) const { std::vector keys = {key}; return split(keys); } void MarsRequest::merge(const MarsRequest& other) { for (auto& param : params_) { LOG_DEBUG_LIB(LibMetkit) << "Merging parameter " << param << std::endl; auto it = other.find(param.name()); if (it != other.params_.end()) param.merge(*it); } } MarsRequest MarsRequest::subset(const std::set& keys) const { MarsRequest req(verb_); for (std::list::const_iterator it = params_.begin(); it != params_.end(); ++it) { if (keys.find(it->name()) != keys.end()) { req.params_.push_back(*it); } } return req; } MarsRequest MarsRequest::extract(const std::string& category) const { MarsRequest req(verb_); for (std::list::const_iterator it = params_.begin(); it != params_.end(); ++it) { if (it->type().category() == category) { req.params_.push_back(*it); } } return req; } void MarsRequest::verb(const std::string& verb) { verb_ = verb; } bool MarsRequest::operator<(const MarsRequest& other) const { if (verb_ != other.verb_) { return verb_ < other.verb_; } return params_ < other.params_; } void MarsRequest::setValue(const std::string& name, const char* value) { std::string v(value); setValue(name, v); } const std::string& MarsRequest::verb() const { return verb_; } std::list::const_iterator MarsRequest::find(const std::string& name) const { for (std::list::const_iterator i = params_.begin(); i != params_.end(); ++i) { if ((*i).name() == name) { return i; } } return params_.end(); } std::list::iterator MarsRequest::find(const std::string& name) { for (std::list::iterator i = params_.begin(); i != params_.end(); ++i) { if ((*i).name() == name) { return i; } } return params_.end(); } void MarsRequest::erase(const std::string& name) { auto it = find(name); if (it != params_.end()) { params_.erase(it); } } std::string MarsRequest::asString() const { std::ostringstream oss; oss << *this; return oss.str(); } //---------------------------------------------------------------------------------------------------------------------- std::vector MarsRequest::parse(std::istream& in, bool strict) { MarsParser parser(in); MarsExpansion expand(true, strict); return expand.expand(parser.parse()); } MarsRequest MarsRequest::parse(const std::string& s, bool strict) { std::istringstream in(s); std::vector v = parse(in, strict); ASSERT(v.size() == 1); return v[0]; } //---------------------------------------------------------------------------------------------------------------------- } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/TypeTime.cc0000664000175000017500000000347215246725757020404 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include "metkit/mars/TypeTime.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeTime::TypeTime(const std::string& name, const eckit::Value& settings) : Type(name, settings) { toByList_ = std::make_unique>(*this, settings); multiple_ = true; } bool TypeTime::expand(std::string& value, const MarsRequest&) const { eckit::Time time(value); std::ostringstream oss; if (time.seconds() != 0) { oss << "Cannot normalise time '" << value << "' - seconds not supported"; throw eckit::BadValue(oss.str(), Here()); } if (time.hours() >= 24) { oss << "Cannot normalise time '" << value << "' - " << time.hours() << " hours > 24 not supported"; throw eckit::BadValue(oss.str(), Here()); } oss << std::setfill('0') << std::setw(2) << time.hours() << std::setfill('0') << std::setw(2) << time.minutes(); value = oss.str(); return true; } void TypeTime::print(std::ostream& out) const { out << "TypeTime[name=" << name_ << "]"; } static TypeBuilder type("time"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeToByListQuantile.cc0000664000175000017500000000503015246725757022712 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "metkit/mars/TypeToByListQuantile.h" #include "eckit/exception/Exceptions.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Tokenizer.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Quantile.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeToByListQuantile::TypeToByListQuantile(const std::string& name, const eckit::Value& settings) : Type(name, settings) { eckit::Value values = settings["denominators"]; if (!values.isList()) { values = MarsLanguage::jsonFile(values); ASSERT(values.isList()); } for (size_t i = 0; i < values.size(); ++i) { const eckit::Value& val = values[i]; if (val.isNumber()) { long v = val; if (denominators_.find(v) == denominators_.end()) { denominators_.emplace(v); } else { std::ostringstream oss; oss << "Redefined " << v << "-quantile"; throw eckit::SeriousBug(oss.str()); } } } LOG_DEBUG_LIB(LibMetkit) << "TypeToByListQuantile name=" << name << " denominators " << denominators_ << std::endl; toByList_ = std::make_unique>(*this, settings); multiple_ = true; } void TypeToByListQuantile::print(std::ostream& out) const { out << "TypeToByListQuantile[name=" << name_ << "]"; } bool TypeToByListQuantile::expand(std::string& value, const MarsRequest&) const { Quantile q(value); if (denominators_.find(q.den()) == denominators_.end()) { std::ostringstream oss; oss << name_ << ": " << q.den() << "-quantile not supported."; throw eckit::BadValue(oss.str()); } return true; } static TypeBuilder type("to-by-list-quantile"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeRange.cc0000664000175000017500000000355515246725757020544 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include "metkit/mars/TypeRange.h" #include "eckit/exception/Exceptions.h" #include "eckit/utils/StringTools.h" #include "eckit/utils/Tokenizer.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/Quantile.h" #include "metkit/mars/StepRange.h" #include "metkit/mars/TypeTime.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class ExtendedTime : public eckit::Time { public: ExtendedTime(long seconds = 0) : Time(seconds, true) {} ExtendedTime(const std::string& time) : Time(time, true) {} }; //---------------------------------------------------------------------------------------------------------------------- TypeRange::TypeRange(const std::string& name, const eckit::Value& settings) : Type(name, settings) { toByList_ = std::make_unique>(*this, settings); multiple_ = true; } void TypeRange::print(std::ostream& out) const { out << "TypeRange[name=" << name_ << "]"; } bool TypeRange::expand(std::string& value, const MarsRequest&) const { value = StepRange{value}; return true; } static TypeBuilder type("range"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeDate.h0000664000175000017500000000233115246725757020216 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 TypeDate.h /// @author Baudouin Raoult /// @author Tiago Quintino /// @author Emanuele Danovaro /// @date April 2016 #pragma once #include "metkit/mars/Type.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- class TypeDate : public Type { public: // methods TypeDate(const std::string& name, const eckit::Value& settings); ~TypeDate() noexcept override = default; private: // methods void print(std::ostream& out) const override; void pass2(MarsRequest& request) override; bool expand(std::string& value, const MarsRequest& request) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsLanguage.h0000664000175000017500000000562315246725757021054 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #pragma once #include #include #include #include #include #include "eckit/memory/NonCopyable.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { class Context; class FlattenCallback; class Type; //---------------------------------------------------------------------------------------------------------------------- enum class ModifierType { DEFAULT, SET, UNSET }; //---------------------------------------------------------------------------------------------------------------------- class MarsLanguage : private eckit::NonCopyable { public: // methods MarsLanguage(const std::string& verb); ~MarsLanguage(); MarsRequest expand(const MarsRequest& r, bool inherit, bool strict); void reset(); const std::string& verb() const; void flatten(const MarsRequest& request, FlattenCallback& callback); static eckit::PathName languageYamlFile(); Type* type(const std::string& name) const; bool isData(const std::string& keyword) const; bool isPostProc(const std::string& keyword) const; bool isSink(const std::string& keyword) const; const std::set& sinkKeywords() const; public: // class methods static std::string expandVerb(const std::string& verb); static std::string bestMatch(const std::string& name, const std::vector& values, bool fail, bool quiet, bool fullMatch, const std::map& aliases = {}); static eckit::Value jsonFile(const std::string& name); private: // methods void flatten(const MarsRequest& request, const std::vector& params, size_t i, MarsRequest& result, FlattenCallback& callback); void parseModifier(ModifierType typ, std::shared_ptr ctx, size_t maxIndex, const eckit::Value& mod); private: // members std::string verb_; std::map types_; std::set dataKeywords_; std::set sinkKeywords_; std::set postProcKeywords_; std::vector> typesByAxisOrder_; std::vector keywords_; std::map aliases_; mutable std::map cache_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsExpansion.h0000664000175000017500000000373415246725757021276 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #ifndef metkit_MarsExpansion_H #define metkit_MarsExpansion_H #include "eckit/memory/NonCopyable.h" #include #include #include #include "metkit/mars/MarsParsedRequest.h" #include "metkit/mars/MarsRequest.h" namespace metkit::mars { class MarsLanguage; //---------------------------------------------------------------------------------------------------------------------- class FlattenCallback { public: virtual void operator()(const MarsRequest&) = 0; protected: virtual ~FlattenCallback() = default; }; class ExpandCallback { public: virtual void operator()(const MarsRequest&) = 0; protected: virtual ~ExpandCallback() = default; }; //---------------------------------------------------------------------------------------------------------------------- class MarsExpansion : public eckit::NonCopyable { public: MarsExpansion(bool inherit, bool strict = false); ~MarsExpansion(); void reset(); MarsRequest expand(const MarsRequest&); std::vector expand(const std::vector&); void expand(const MarsRequest&, ExpandCallback&); void flatten(const MarsRequest&, FlattenCallback&); private: MarsLanguage& language(const std::string& verb); std::map languages_; bool inherit_; bool strict_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/Param.cc0000664000175000017500000000346515246725757017706 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/Param.h" #include "eckit/exception/Exceptions.h" #include "eckit/persist/DumpLoad.h" #include "eckit/utils/Tokenizer.h" using namespace eckit; namespace metkit { //---------------------------------------------------------------------------------------------------------------------- Param::Param(const std::string& s) { Tokenizer parse("."); std::vector result; parse(s, result); switch (result.size()) { case 1: value_ = atol(result[0].c_str()); table_ = 0; break; case 2: value_ = atol(result[0].c_str()); table_ = atol(result[1].c_str()); break; default: throw eckit::BadValue("Invalid param"); } } Param::operator std::string() const { std::ostringstream os; os << *this; return os.str(); } void Param::print(std::ostream& s) const { if (table_) s << value_ << '.' << table_; else s << value_; } void Param::dump(DumpLoad& a) const { a.dump(value_); a.dump(table_); } void Param::load(DumpLoad& a) { a.load(value_); a.load(table_); } long Param::paramId() const { size_t t = table_ == 0 ? value_ / 1000 : table_; return (t == 128 ? 0 : t * 1000) + value_ % 1000; } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/mars/RequestEnvironment.h0000664000175000017500000000351315246725757022357 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @author Tiago Quintino #pragma once #include #include #include #include #include "metkit/mars/MarsRequest.h" namespace metkit::mars { /// Encapsulates the environment to be sent to the mars-server. /// This uses a mars request with 'environ' as the verb and each environment /// variable is treated as keyword with its respective value. class RequestEnvironment { public: RequestEnvironment(const RequestEnvironment& other); /// Create the actual "mars-request" /// @return the request const MarsRequest& request() const { return *env_; } /// @brief Initialize the RequestEnvironment with a map of KEYWORD:VALUE(s). /// @param env, map of KEYWORD:VALUE (or KEYWORD:VALUE1/VALUE2/.../VALUEn) to add to the environment. static void initialize(const std::map& env); /// @brief Update the RequestEnvironment with a map of KEYWORD:VALUE(s). /// @param env, map of KEYWORD:VALUE (or KEYWORD:VALUE1/VALUE2/.../VALUEn) to add to the environment. void update(const std::map& env); /// Access instance of RequestEnvironment /// @return RequestEnvironment static const RequestEnvironment& instance(); private: RequestEnvironment() = default; static RequestEnvironment& inst(); std::optional env_ = std::nullopt; std::recursive_mutex init_; }; } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsParsedRequest.cc0000664000175000017500000000130615246725757022250 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/MarsParsedRequest.h" namespace metkit::mars { MarsParsedRequest::MarsParsedRequest(const std::string& verb, size_t line) : MarsRequest(verb), line_(line) {} void MarsParsedRequest::info(std::ostream& out) const { out << " Request starting line " << line_; } } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/TypeInteger.cc0000664000175000017500000000571215246725757021102 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/utils/Translator.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/TypeInteger.h" #include "metkit/mars/TypeToByList.h" #include "metkit/mars/TypesFactory.h" namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeInteger::TypeInteger(const std::string& name, const eckit::Value& settings) : Type(name, settings) { // check if the settings contain a range if (settings.contains("range") && settings["range"].size() == 2) { range_ = {settings["range"][0], settings["range"][1]}; } } void TypeInteger::print(std::ostream& out) const { out << "TypeInteger[name=" << name() << "]"; } bool TypeInteger::ok(const std::string& value, long& n) const { n = 0; long sign = 1; for (std::string::const_iterator j = value.begin(); j != value.end(); ++j) { switch (*j) { case '-': if (j == value.begin()) { sign = -1; } else { return false; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n *= 10; n += (*j) - '0'; break; default: return false; } } n *= sign; return !range_ || (n >= range_->lower_ && n <= range_->upper_); } bool TypeInteger::expand(std::string& value, const MarsRequest&) const { long n = 0; if (ok(value, n)) { static eckit::Translator l2s; value = l2s(n); return true; } return false; } static TypeBuilder type("integer"); //---------------------------------------------------------------------------------------------------------------------- class TypeToByListInt : public TypeInteger { public: TypeToByListInt(const std::string& name, const eckit::Value& settings) : TypeInteger(name, settings) { toByList_ = std::make_unique>(*this, settings); multiple_ = true; } protected: void print(std::ostream& out) const override { out << "TypeToByListInt[name=" << name() << "]"; } }; static TypeBuilder typeList("to-by-list"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/ClientTask.cc0000664000175000017500000000345515246725757020706 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include "metkit/mars/ClientTask.h" #include "metkit/mars/MarsHandle.h" namespace metkit { namespace mars { // Call by the clien code ClientTask::ClientTask(const MarsRequest& r, const MarsRequest& e, const std::string& host, int port, unsigned long long id) : request_(r), environ_(e), metkitID_(id), port_(port), host_(host), handle_() { // Try something unique (per machine) // Warning: Servers recovers time(0) from ID // to compute request age. Not good if (!metkitID_) { typedef unsigned long long ull; metkitID_ = (ull(::getpid()) << ull(32 + 16)) | (ull(::pthread_self()) << ull(32)) | (ull(::time(0)) & ull(0xffffffff)); } handle_.reset(new MarsHandle(host_, port_, metkitID_)); } ClientTask::~ClientTask() {} void ClientTask::send(eckit::Stream& s) const { unsigned long long dummy = 0; s.startObject(); s << "MarsTask"; /* send mars request id */ s << dummy; /* Send requests */ s << request_; s << environ_; /* Send cb info */ s << host_; s << port_; s << metkitID_; /* Send datahandle */ s << *handle_; s.endObject(); } char ClientTask::receive(eckit::Stream& s) const { unsigned long long id; char mode; s >> id; ASSERT(id == metkitID_); s >> mode; return mode; } } // namespace mars } // namespace metkit metkit-1.20.2/src/metkit/mars/TypeParam.cc0000664000175000017500000003537615246725757020556 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/TypeParam.h" #include "eckit/config/Resource.h" #include "eckit/log/Log.h" #include "eckit/parser/YAMLParser.h" #include "eckit/thread/AutoLock.h" #include "eckit/types/Types.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/TypesFactory.h" using eckit::Log; using metkit::LibMetkit; namespace { static eckit::Mutex* local_mutex = 0; static pthread_once_t once = PTHREAD_ONCE_INIT; class Matcher { std::string name_; eckit::Value values_; public: Matcher(const std::string& name, const eckit::Value values); bool match(const metkit::mars::MarsRequest& request, bool partial = false) const; friend std::ostream& operator<<(std::ostream& out, const Matcher& matcher) { out << matcher.name_ << "=" << matcher.values_; return out; } }; Matcher::Matcher(const std::string& name, const eckit::Value values) : name_(name), values_(values) { if (!values_.isList()) { values_ = eckit::Value::makeList(values_); } } bool Matcher::match(const metkit::mars::MarsRequest& request, bool partial) const { std::vector vals = request.values(name_, true); if (vals.size() == 0) { return partial; } for (size_t i = 0; i < values_.size(); i++) { std::string v = values_[i]; if (v == vals[0]) { return true; } } return false; } //---------------------------------------------------------------------------------------------------------------------- class Rule { std::vector matchers_; std::vector values_; mutable std::map mapping_; static std::vector defaultValues_; static std::map defaultMapping_; public: bool match(const metkit::mars::MarsRequest& request, bool partial = false) const; std::string lookup(const std::string& s, bool fail) const; long toParamid(const std::string& param) const; Rule(const eckit::Value& matchers, const eckit::Value& setters, const eckit::Value& ids); static void setDefault(const eckit::Value& setters, const eckit::Value& ids); void print(std::ostream& out) const { out << "{"; const char* sep = ""; for (std::vector::const_iterator j = matchers_.begin(); j != matchers_.end(); ++j) { out << sep << (*j); sep = ","; } out << "}"; } friend std::ostream& operator<<(std::ostream& out, const Rule& rule) { rule.print(out); return out; } }; std::vector Rule::defaultValues_; std::map Rule::defaultMapping_; void Rule::setDefault(const eckit::Value& values, const eckit::Value& ids) { std::map precedence; for (size_t i = 0; i < values.size(); ++i) { const eckit::Value& id = values[i]; std::string first = id; defaultValues_.push_back(first); const eckit::Value& aliases = ids[id]; if (aliases.isNil()) { LOG_DEBUG_LIB(LibMetkit) << "No aliases for " << id << std::endl; continue; } for (size_t j = 0; j < aliases.size(); ++j) { std::string v = aliases[j]; if (defaultMapping_.find(v) != defaultMapping_.end()) { if (precedence[v] <= j) { LOG_DEBUG_LIB(LibMetkit) << "Redefinition ignored: param " << v << "='" << first << "', keeping previous value of '" << defaultMapping_[v] << "' " << std::endl; continue; } else { LOG_DEBUG_LIB(LibMetkit) << "Redefinition of param " << v << "='" << first << "', overriding previous value of '" << defaultMapping_[v] << "' " << std::endl; precedence[v] = j; } } else { precedence[v] = j; } defaultMapping_[v] = first; defaultValues_.push_back(v); } } } Rule::Rule(const eckit::Value& matchers, const eckit::Value& values, const eckit::Value& ids) { std::map precedence; const eckit::Value& keys = matchers.keys(); for (size_t i = 0; i < keys.size(); ++i) { std::string name = keys[i]; matchers_.push_back(Matcher(name, matchers[name])); } for (size_t i = 0; i < values.size(); ++i) { const eckit::Value& id = values[i]; std::string first = id; values_.push_back(first); const eckit::Value& aliases = ids[id]; if (aliases.isNil()) { LOG_DEBUG_LIB(LibMetkit) << "No aliases for " << id << " " << *this << std::endl; continue; } for (size_t j = 0; j < aliases.size(); ++j) { std::string v = aliases[j]; if (mapping_.find(v) != mapping_.end()) { if (precedence[v] <= j) { LOG_DEBUG_LIB(LibMetkit) << "Redefinition ignored: param " << v << "='" << first << "', keeping previous value of '" << mapping_[v] << "' " << *this << std::endl; continue; } else { LOG_DEBUG_LIB(LibMetkit) << "Redefinition of param " << v << "='" << first << "', overriding previous value of '" << mapping_[v] << "' " << *this << std::endl; precedence[v] = j; } } else { precedence[v] = j; } mapping_[v] = {first}; values_.push_back(v); } } } bool Rule::match(const metkit::mars::MarsRequest& request, bool partial) const { for (std::vector::const_iterator j = matchers_.begin(); j != matchers_.end(); ++j) { if (!(*j).match(request, partial)) { return false; } } return true; } std::string Rule::lookup(const std::string& s, bool fail) const { size_t table = 0; size_t param = 0; size_t* n = ¶m; bool ok = true; for (std::string::const_iterator k = s.begin(); k != s.end(); ++k) { switch (*k) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': (*n) *= 10; (*n) += (*k) - '0'; break; case '.': if (n == ¶m) { n = &table; } else { ok = false; } break; default: ok = false; break; } } if (ok && param > 0) { std::ostringstream oss; if (table == 128) { table = 0; } if (table > 0 && param >= 1000) { throw eckit::UserError("Unrecognised format for parameter " + s, Here()); } oss << table * 1000 + param; std::string p = oss.str(); for (std::vector::const_iterator j = values_.begin(); j != values_.end(); ++j) { if ((*j) == p) { return p; } } for (std::vector::const_iterator j = defaultValues_.begin(); j != defaultValues_.end(); ++j) { if ((*j) == p) { return p; } } throw eckit::UserError("Cannot match parameter " + p, Here()); } std::string paramid = metkit::mars::MarsLanguage::bestMatch(s, values_, false, false, true, mapping_); if (!paramid.empty()) { return paramid; } return metkit::mars::MarsLanguage::bestMatch(s, defaultValues_, fail, false, false, defaultMapping_); } static std::vector* rules = nullptr; } // namespace static void init() { local_mutex = new eckit::Mutex(); rules = new std::vector(); const eckit::Value ids = eckit::YAMLParser::decodeFile(LibMetkit::paramIDYamlFile()); ASSERT(ids.isOrderedMap()); eckit::ValueMap merge; static bool metkitLegacyParamCheck = eckit::Resource("metkitLegacyParamCheck;$METKIT_LEGACY_PARAM_CHECK", false); static bool metkitRawParam = eckit::Resource("metkitRawParam;$METKIT_RAW_PARAM", false); if (metkitLegacyParamCheck || (!metkitRawParam)) { eckit::Value r = eckit::YAMLParser::decodeFile(LibMetkit::paramYamlFile()); ASSERT(r.isList()); const eckit::Value rs = eckit::YAMLParser::decodeFile(LibMetkit::paramStaticYamlFile()); ASSERT(rs.isList()); // merge r and rs for (size_t i = 0; i < r.size(); ++i) { const eckit::Value& rule = r[i]; if (!rule.isList()) { rule.dump(Log::error()) << std::endl; } ASSERT(rule.isList()); ASSERT(rule.size() == 2); merge.emplace(rule[0], rule[1]); } for (size_t i = 0; i < rs.size(); ++i) { const eckit::Value& rule = rs[i]; if (!rule.isList()) { rule.dump(Log::error()) << std::endl; } ASSERT(rule.isList()); ASSERT(rule.size() == 2); auto it = merge.find(rule[0]); if (it == merge.end()) { merge.emplace(rule[0], rule[1]); } else { it->second += rule[1]; } } } if (metkitLegacyParamCheck) { for (auto it = merge.begin(); it != merge.end(); it++) { (*rules).push_back(Rule(it->first, it->second, ids)); } return; } auto keys = ids.keys(); Rule::setDefault(keys, ids); if (metkitRawParam) { // empty rule, to enable default (*rules).push_back(Rule(eckit::Value::makeMap(), eckit::Value::makeList(), eckit::Value::makeMap())); return; } std::set shortnames; std::set associatedIDs; const eckit::Value pc = eckit::YAMLParser::decodeFile(LibMetkit::shortnameContextYamlFile()); ASSERT(pc.isList()); for (size_t i = 0; i < pc.size(); i++) { shortnames.emplace(pc[i]); } for (size_t i = 0; i < keys.size(); i++) { auto el = ids.element(keys[i]); for (size_t j = 0; j < el.size(); j++) { if (shortnames.find(el[j]) != shortnames.end()) { associatedIDs.emplace(keys[i]); } } } for (auto it = merge.begin(); it != merge.end(); it++) { auto listIDs = eckit::Value::makeList(); for (size_t j = 0; j < it->second.size(); j++) { if (associatedIDs.find(it->second[j]) != associatedIDs.end()) { listIDs.append(it->second[j]); } } if (listIDs.size() > 0) { (*rules).push_back(Rule{it->first, listIDs, ids}); } } (*rules).push_back(Rule{eckit::Value::makeMap(), eckit::Value::makeList(), eckit::Value::makeMap()}); } namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- TypeParam::TypeParam(const std::string& name, const eckit::Value& settings) : Type(name, settings), firstRule_(false) { if (settings.contains("expand_with")) { expandWith_ = settings["expand_with"]; } if (settings.contains("first_rule")) { firstRule_ = settings["first_rule"]; } } void TypeParam::print(std::ostream& out) const { out << "TypeParam[name=" << name_ << "]"; } void TypeParam::pass2(MarsRequest& request) { pthread_once(&once, init); bool fail = true; const Rule* rule = 0; std::vector values = request.values(name_, true); if (values.size() == 1 && values[0] == "all") { return; } eckit::AutoLock lock(local_mutex); for (const auto& r : *rules) { if (r.match(request)) { rule = &r; break; } } if (!rule) { Log::warning() << "TypeParam: cannot find a context to expand 'param' in " << request << std::endl; if (firstRule_) { bool found = false; for (const auto& r : *rules) { if (r.match(request, true)) { for (std::vector::iterator j = values.begin(); j != values.end() && !rule; ++j) { std::string& s = (*j); try { s = r.lookup(s, fail); rule = &r; Log::warning() << "TypeParam: using 'first matching rule' option " << r << std::endl; } catch (...) { } } } } } else if (expandWith_.size()) { MarsRequest tmp(request); for (auto j = expandWith_.begin(); j != expandWith_.end(); ++j) { if (!tmp.has((*j).first)) { tmp.setValue((*j).first, (*j).second); } } for (const auto& r : *rules) { if (r.match(tmp)) { rule = &r; Log::warning() << "TypeParam using 'expand with' option " << *rule << std::endl; break; } } } if (!rule) { std::ostringstream oss; oss << "TypeParam: cannot find a context to expand 'param' in " << request; throw eckit::SeriousBug(oss.str()); } } for (std::vector::iterator j = values.begin(); j != values.end(); ++j) { std::string& s = (*j); try { s = rule->lookup(s, fail); } catch (...) { Log::error() << *rule << std::endl; throw; } } request.setValuesTyped(this, values); } bool TypeParam::expand(std::string&, const MarsRequest&) const { // Work done on pass2() return true; } void TypeParam::reset() { // cache_.clear(); Type::reset(); } static TypeBuilder type("param"); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsRequestHandle.cc0000664000175000017500000001010715246725757022224 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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 MarsRequestHandle.cc // Baudouin Raoult - (c) ECMWF Feb 12 #include "metkit/mars/MarsRequestHandle.h" #include "eckit/types/Types.h" #include "eckit/utils/StringTools.h" #include "metkit/config/LibMetkit.h" namespace metkit::mars { static eckit::Reanimator marsRequestHandleReanimator; const eckit::ReanimatorBase& MarsRequestHandle::reanimator() const { return marsRequestHandleReanimator; } const eckit::ClassSpec& MarsRequestHandle::classSpec() { static eckit::ClassSpec spec = {&eckit::DataHandle::classSpec(), "MarsRequestHandle"}; return spec; } bool shortName(const std::string& prefix, const std::string& s) { if (prefix.size() > s.size()) return false; return std::equal(prefix.begin(), prefix.end(), s.begin()); } MarsRequestHandle::MarsRequestHandle(eckit::Stream& s) : eckit::DataHandle(s), request_(s), protocol_(eckit::Reanimator::reanimate(s)), opened_(false) {} MarsRequestHandle::MarsRequestHandle(const MarsRequest& request, BaseProtocol* protocol) : request_(request), protocol_(protocol), opened_(false) { LOG_DEBUG_LIB(LibMetkit) << "MarsRequestHandle::MarsRequestHandle: request: " << request << " protocol: " << protocol << std::endl; ASSERT(protocol); } MarsRequestHandle::MarsRequestHandle(const metkit::mars::MarsRequest& request, const eckit::Configuration& database) : request_(request.verb()), protocol_(ProtocolFactory::build(database)), opened_(false) { for (const auto& p : request.parameters()) { request_.values(p.name(), p.values()); } } MarsRequestHandle::~MarsRequestHandle() { if (opened_) { close(); } } const eckit::StringDict& MarsRequestHandle::stats() const { return protocol_->stats(); } eckit::Length MarsRequestHandle::openForRead() { eckit::Log::info() << "MarsRequestHandle::openForRead[" << request_ << "]" << std::endl; ASSERT(!opened_); opened_ = true; const std::string v(eckit::StringTools::lower(request_.verb())); ASSERT(v == "retrieve" || v == "stage" || v == "list" || v == "get"); return protocol_->retrieve(request_); } void MarsRequestHandle::openForWrite(const eckit::Length& size) { eckit::Log::info() << "MarsRequestHandle::openForWrite[" << request_ << "]" << std::endl; ASSERT(!opened_); opened_ = true; ASSERT(eckit::StringTools::lower(request_.verb()) == "archive"); protocol_->archive(request_, size); } void MarsRequestHandle::openForAppend(const eckit::Length&) { eckit::Log::info() << "MarsRequestHandle::openForAppend[" << request_ << "]" << std::endl; ASSERT(!opened_); opened_ = true; NOTIMP; } long MarsRequestHandle::read(void* buffer, long len) { ASSERT(opened_); try { return protocol_->read(buffer, len); } catch (eckit::Exception& e) { std::ostringstream oss; oss << "Exception " << e.what() << " caught in MarsRequestHandle::read(" << *protocol_ << ")"; eckit::Log::error() << oss.str() << std::endl; throw RetryTransfer(oss.str()); } } long MarsRequestHandle::write(const void* buffer, long len) { ASSERT(opened_); return protocol_->write(buffer, len); } void MarsRequestHandle::close() { eckit::Log::info() << "MarsRequestHandle::close[" << request_ << "]" << std::endl; opened_ = false; protocol_->cleanup(); } void MarsRequestHandle::print(std::ostream& s) const { s << "MarsRequestHandle[" << *protocol_ << "," << request_ << "]"; } void MarsRequestHandle::encode(eckit::Stream& s) const { eckit::DataHandle::encode(s); s << request_; s << *protocol_; } bool MarsRequestHandle::canSeek() const { return false; } } // namespace metkit::mars metkit-1.20.2/src/metkit/mars/MarsRequest.h0000664000175000017500000001304515246725757020756 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Manuel Fuentes /// @author Baudouin Raoult /// @author Tiago Quintino /// @date Sep 96 #ifndef metkit_MarsRequest_H #define metkit_MarsRequest_H #include #include "eckit/value/Value.h" #include "metkit/mars/Parameter.h" namespace eckit { class MD5; namespace message { class Message; } } // namespace eckit struct metkit_marsrequest_t; namespace metkit::mars { class Type; class MarsRequest; //---------------------------------------------------------------------------------------------------------------------- class MarsRequest { public: // methods MarsRequest(); explicit MarsRequest(const std::string&); explicit MarsRequest(eckit::Stream&, bool lowercase = false); MarsRequest(const std::string&, const std::map&); MarsRequest(const std::string&, const eckit::Value&); explicit MarsRequest(const eckit::message::Message&); ~MarsRequest() = default; bool operator<(const MarsRequest& other) const; // eckit::Value& operator[](const std::string&); const std::string& operator[](const std::string&) const; operator eckit::Value() const; const std::string& verb() const; size_t countValues(const std::string&) const; bool has(const std::string&) const; bool is(const std::string& param, const std::string& value) const; const std::vector& values(const std::string&, bool emptyOk = false) const; // Returns reference to values or nullopt if not found std::optional>> get(const std::string& keyword) const; template size_t getValues(const std::string& name, std::vector& v, bool emptyOk = false) const; void getParams(std::vector&) const; std::vector params() const; std::list& parameters() { return params_; } const std::list& parameters() const { return params_; } void verb(const std::string&); void values(const std::string&, const std::vector&); template void setValue(const std::string& name, const T& value); void setValue(const std::string& name, const char* value); void unsetValues(const std::string&); /// Splits a MARS request into multiple requests along the provided key std::vector split(const std::string& keys) const; /// Splits a MARS request into multiple requests along the indicated keys std::vector split(const std::vector& keys) const; /// Merges one MarsRequest into another /// @todo Improve performance -- uses O(N^2) search / merge in std::list's void merge(const MarsRequest& other); /// Create a new MarsRequest from this one with only the given set of keys MarsRequest subset(const std::set&) const; void json(eckit::JSON&, bool array = false) const; void md5(eckit::MD5&) const; void dump(std::ostream&, const char* cr = "\n", const char* tab = "\t", bool verb = true) const; void setValuesTyped(Type*, const std::vector&); bool filter(const MarsRequest& filter); bool matches(const MarsRequest& filter) const; bool empty() const; size_t count() const; MarsRequest extract(const std::string& category) const; void erase(const std::string& param); std::string asString() const; public: // static methods static MarsRequest parse(const std::string& s, bool strict = false); static std::vector parse(std::istream&, bool strict = false); /// Implementation in api/metkit_c.cc static const MarsRequest& fromOpaque(const metkit_marsrequest_t* request); private: // members std::string verb_; std::list params_; private: // methods void print(std::ostream&) const; void encode(eckit::Stream&) const; std::list::const_iterator find(const std::string&) const; std::list::iterator find(const std::string&); // -- Class members static eckit::ClassSpec classSpec_; static eckit::Reanimator reanimator_; friend std::ostream& operator<<(std::ostream& s, const MarsRequest& r) { r.print(s); return s; } friend eckit::JSON& operator<<(eckit::JSON& s, const MarsRequest& r) { r.json(s); return s; } friend eckit::Stream& operator<<(eckit::Stream& s, const MarsRequest& r) { r.encode(s); return s; } }; template size_t MarsRequest::getValues(const std::string& name, std::vector& v, bool emptyOk) const { const std::vector& s = values(name, emptyOk); eckit::Translator t; v.clear(); for (std::vector::const_iterator j = s.begin(); j != s.end(); ++j) { v.push_back(t(*j)); } return v.size(); } template void MarsRequest::setValue(const std::string& name, const T& value) { eckit::Translator t; std::vector v(1, t(value)); values(name, v); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars #endif metkit-1.20.2/src/metkit/mars/MarsLanguage.cc0000664000175000017500000004314315246725757021211 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/mars/MarsLanguage.h" #include #include #include #include "eckit/config/Resource.h" #include "eckit/log/Log.h" #include "eckit/parser/YAMLParser.h" #include "eckit/types/Types.h" #include "eckit/utils/StringTools.h" #include "metkit/config/LibMetkit.h" #include "metkit/hypercube/HyperCube.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/Type.h" #include "metkit/mars/TypesFactory.h" //---------------------------------------------------------------------------------------------------------------------- static pthread_once_t once = PTHREAD_ONCE_INIT; static eckit::Value languages_; static std::vector modifiers_{}; static std::set verbs_; static std::map verbAliases_; static void init() { languages_ = eckit::YAMLParser::decodeFile(metkit::LibMetkit::languageYamlFile()); for (const auto& file : metkit::LibMetkit::modifiersYamlFiles()) { modifiers_.push_back(eckit::YAMLParser::decodeFile(file)); } const eckit::Value verbs = languages_.keys(); for (size_t i = 0; i < verbs.size(); ++i) { verbs_.insert(verbs[i]); eckit::Value lang = languages_[verbs[i]]; if (lang.contains("_aliases")) { eckit::Value aliases = lang["_aliases"]; ASSERT(aliases.isList()); for (size_t j = 0; j < aliases.size(); ++j) { verbAliases_.emplace(aliases[j], verbs[i]); } } } } namespace metkit::mars { //---------------------------------------------------------------------------------------------------------------------- void MarsLanguage::parseModifier(ModifierType typ, std::shared_ptr ctx, size_t maxIndex, const eckit::Value& mod) { eckit::Value keys; if (typ == ModifierType::UNSET) { ASSERT(mod.isList()); keys = mod; } else { ASSERT(mod.isMap()); keys = mod.keys(); } for (size_t j = 0; j < keys.size(); ++j) { std::string key = keys[j]; auto it = types_.find(key); if (it != types_.end()) { ASSERT(!isData(key) || maxIndex <= metkit::hypercube::AxisOrder::instance().index(key)); if (typ == ModifierType::UNSET) { it->second->unset(ctx); } else { eckit::Value vv = mod[key]; std::vector vals; if (vv.isList()) { for (size_t k = 0; k < vv.size(); ++k) { vals.push_back(vv[k]); } } else { vals.push_back(vv); } if (typ == ModifierType::DEFAULT) it->second->defaults(ctx, vals); else if (typ == ModifierType::SET) it->second->set(ctx, vals); } } } } MarsLanguage::MarsLanguage(const std::string& verb) { pthread_once(&once, init); verb_ = MarsLanguage::expandVerb(verb); eckit::Value lang = languages_[verb_]; eckit::Value params = lang.keys(); eckit::Value options = lang["_options"]; for (size_t i = 0; i < params.size(); ++i) { std::string keyword = params[i]; eckit::Value settings = lang[keyword]; if (keyword[0] == '_') { continue; } ASSERT(types_.find(keyword) == types_.end()); if (options.contains(keyword)) { eckit::ValueMap m = options[keyword]; for (auto j = m.begin(); j != m.end(); ++j) { settings[(*j).first] = (*j).second; } } auto [it, success] = types_.emplace(keyword, TypesFactory::build(keyword, settings)); it->second->attach(); keywords_.push_back(keyword); std::optional aliases; if (settings.contains("aliases")) { aliases = settings["aliases"]; } if (settings.contains("category") && settings["category"] == "data") { dataKeywords_.insert(keyword); if (aliases) { for (size_t j = 0; j < aliases->size(); ++j) { dataKeywords_.insert((*aliases)[j]); } } } if (settings.contains("category") && settings["category"] == "postproc") { postProcKeywords_.insert(keyword); if (aliases) { for (auto j = 0; j < aliases->size(); ++j) { postProcKeywords_.insert((*aliases)[j]); } } } if (settings.contains("category") && settings["category"] == "sink") { sinkKeywords_.insert(keyword); if (aliases) { for (auto j = 0; j < aliases->size(); ++j) { sinkKeywords_.insert((*aliases)[j]); } } } if (aliases) { for (size_t j = 0; j < aliases->size(); ++j) { aliases_[(*aliases)[j]] = keyword; keywords_.push_back((*aliases)[j]); } } } // load modifiers and associate to types for (const auto& modifierFile : modifiers_) { ASSERT(modifierFile.isList()); for (size_t i = 0; i < modifierFile.size(); ++i) { eckit::Value mod = modifierFile[i]; ASSERT(mod.isMap()); ASSERT(mod.contains("context")); std::shared_ptr ctx = Context::parseContext(mod["context"]); size_t maxIndex = ctx->maxAxisIndex(); if (mod.contains("defaults")) { parseModifier(ModifierType::DEFAULT, ctx, maxIndex, mod["defaults"]); } if (mod.contains("set")) { parseModifier(ModifierType::SET, ctx, maxIndex, mod["set"]); } if (mod.contains("unset")) { parseModifier(ModifierType::UNSET, ctx, maxIndex, mod["unset"]); } } } if (lang.contains("_clear_defaults")) { const auto& keywords = lang["_clear_defaults"]; for (auto i = 0; i < keywords.size(); ++i) { if (auto iter = types_.find(keywords[i]); iter != types_.end()) { iter->second->clearDefaults(); } } } for (const std::string& a : hypercube::AxisOrder::instance().axes()) { Type* t = nullptr; auto it = types_.find(a); if (it != types_.end()) { t = it->second; } typesByAxisOrder_.emplace_back(a, t); } for (auto& [k, t] : types_) { if (dataKeywords_.find(k) == dataKeywords_.end()) { typesByAxisOrder_.emplace_back(k, t); } } } bool MarsLanguage::isData(const std::string& keyword) const { return (dataKeywords_.find(keyword) != dataKeywords_.end()); } bool MarsLanguage::isPostProc(const std::string& keyword) const { return (postProcKeywords_.find(keyword) != postProcKeywords_.end()); } bool MarsLanguage::isSink(const std::string& keyword) const { return (sinkKeywords_.find(keyword) != sinkKeywords_.end()); } const std::set& MarsLanguage::sinkKeywords() const { return sinkKeywords_; } MarsLanguage::~MarsLanguage() { for (auto& [k, t] : types_) { t->detach(); } } eckit::PathName MarsLanguage::languageYamlFile() { return metkit::LibMetkit::languageYamlFile(); } void MarsLanguage::reset() { for (auto& [k, t] : types_) { t->reset(); } } eckit::Value MarsLanguage::jsonFile(const std::string& name) { // TODO: cache eckit::PathName path = metkit::LibMetkit::configFile(name); LOG_DEBUG_LIB(LibMetkit) << "MarsLanguage loading jsonFile " << path << std::endl; std::ifstream in(path.asString().c_str()); if (!in) { throw eckit::CantOpenFile(path); } eckit::YAMLParser parser(in); return parser.parse(); } static bool isnumeric(const std::string& s) { for (size_t i = 0; i < s.length(); i++) { if (!::isdigit(s[i])) { return false; } } return s.length() > 0; } std::string MarsLanguage::bestMatch(const std::string& name, const std::vector& values, bool fail, bool quiet, bool fullMatch, const std::map& aliases) { size_t score = (fullMatch ? name.length() : 1); std::vector best; for (size_t i = 0; i < values.size(); ++i) { const std::string& value = values[i]; size_t len = std::min(name.length(), value.length()); size_t s = 0; // Prefix comparison, break if there is a difference for (size_t j = 0; j < len; ++j) { if (::tolower(name[j]) == ::tolower(value[j])) { s++; } else { break; } } // if value and name are the same (in lowercase), look up aliases and return the alias // otherwise return the value (which is name) if (s == value.length() && s == name.length()) { if (aliases.find(value) != aliases.end()) { return aliases.find(value)->second; } return value; } // If fullmatch == true: // only option is s==score, otherwise the break above would have triggered // so best is only filled with exact matches // If fullmatch == false: // score keeps track of the best value found, if a better value (in terms of matching prefixes) // is found, clear the best vector and push the new best. If all matches are equally good, keep them in the best // vector if (s >= score) { if (s > score) { best.clear(); } best.push_back(value); score = s; } } if (!quiet && best.size() > 0) { std::cerr << "Matching '" << name << "' with " << best << std::endl; } static bool strict = eckit::Resource("$METKIT_LANGUAGE_STRICT_MODE", true); if (best.size() == 1) { // If the best entry is a number or not name (why is this even needed) if (isnumeric(best[0]) && (best[0] != name)) { best.clear(); } else { if (strict) { if (best[0] != name) { std::ostringstream oss; oss << "Cannot match [" << name << "] in " << values; throw eckit::UserError(oss.str()); } } if (aliases.find(best[0]) != aliases.end()) { return aliases.find(best[0])->second; } return best[0]; } } // In case the match is empty, return or fail, depending on the fail flag static std::string empty; if (best.empty()) { if (!fail) { return empty; } std::ostringstream oss; oss << "Cannot match [" << name << "] in " << values; throw eckit::UserError(oss.str()); } // Check the existing aliases for mappings of the matches to the canonical parameter name // A set is used to de-duplicate the findings std::set names; for (std::vector::const_iterator j = best.begin(); j != best.end(); ++j) { const auto k = aliases.find(*j); if (k == aliases.end()) { names.insert(*j); } else { names.insert((*k).second); } } // If there is only on match return the given one if (names.size() == 1) { if (aliases.find(best[0]) != aliases.end()) { return aliases.find(best[0])->second; } return best[0]; } // Otherwise there is an ambiguity and we want to return or fail (depending on the fail flag) if (!fail) { return empty; } std::ostringstream oss; oss << "Ambiguous value '" << name << "' could be"; for (std::vector::const_iterator j = best.begin(); j != best.end(); ++j) { auto k = aliases.find(*j); if (k == aliases.end()) { oss << " '" << *j << "'"; } else { oss << " '" << *j << "' ("; oss << (*k).second; oss << ")"; } } throw eckit::UserError(oss.str()); } std::string MarsLanguage::expandVerb(const std::string& verb) { pthread_once(&once, init); std::string v = eckit::StringTools::lower(verb); auto vv = verbs_.find(v); if (vv != verbs_.end()) { return v; } auto aa = verbAliases_.find(v); if (aa != verbAliases_.end()) { return aa->second; } std::ostringstream oss; oss << "Verb " << v << " is not supported"; throw eckit::UserError(oss.str()); } class TypeHidden : public Type { bool flatten() const override { return false; } void print(std::ostream& out) const override { out << "TypeHidden"; } bool expand(std::string&, const MarsRequest&) const override { return true; } public: TypeHidden() : Type("hidden", eckit::Value()) { attach(); } }; Type* MarsLanguage::type(const std::string& name) const { auto k = types_.find(name); if (k == types_.end()) { if (name[0] == '_') { static TypeHidden hidden; return &hidden; } throw eckit::SeriousBug("Cannot find a type for '" + name + "'"); } return k->second; } MarsRequest MarsLanguage::expand(const MarsRequest& r, bool inherit, bool strict) { MarsRequest result(verb_); try { std::vector> sortedParams; std::map paramSet; std::vector params; for (const auto& PP : r.params()) { auto c = cache_.find(PP); if (c != cache_.end()) { paramSet.emplace((*c).second, PP); } else { std::string p = eckit::StringTools::lower(PP); paramSet.emplace(cache_[p] = bestMatch(p, keywords_, true, false, true, aliases_), PP); } } { // sort the parameters, following the AxisOrder for (const auto& k : metkit::hypercube::AxisOrder::instance().axes()) { auto it = paramSet.find(k); if (it != paramSet.end()) { sortedParams.emplace_back(k, it->second); paramSet.erase(it); } } for (const auto& [k, PP] : paramSet) { sortedParams.emplace_back(k, PP); } } for (const auto& [p, PP] : sortedParams) { std::vector values = r.values(PP); if (values.size() == 1) { const std::string& s = eckit::StringTools::lower(values[0]); if (s == "off") { result.unsetValues(p); type(p)->reset(); continue; } if (s == "all" && type(p)->multiple()) { result.setValue(p, "all"); continue; } } auto t = type(p); t->expand(values, result); result.setValuesTyped(t, values); t->check(values); } if (inherit) { for (const auto& [k, t] : typesByAxisOrder_) { if (t != nullptr && result.countValues(k) == 0) { t->setDefaults(result); } } result.getParams(params); for (std::vector::const_iterator k = params.begin(); k != params.end(); ++k) { type(*k)->setInheritance(result.values(*k)); } } result.getParams(params); for (std::vector::const_iterator k = params.begin(); k != params.end(); ++k) { type(*k)->pass2(result); } for (const auto& [k, t] : typesByAxisOrder_) { if (t != nullptr) t->finalise(result, strict); } } catch (std::exception& e) { std::ostringstream oss; oss << e.what() << " request=" << r << ", expanded=" << result; throw eckit::UserError(oss.str()); } return result; } const std::string& MarsLanguage::verb() const { return verb_; } void MarsLanguage::flatten(const MarsRequest& request, const std::vector& params, size_t i, MarsRequest& result, FlattenCallback& callback) { if (i == params.size()) { callback(result); return; } const std::string& param = params[i]; Type* t = type(param); if (!t->flatten()) { flatten(request, params, i + 1, result, callback); return; } const std::vector& values = t->flattenValues(request); for (std::vector::const_iterator j = values.begin(); j != values.end(); ++j) { result.setValue(param, *j); flatten(request, params, i + 1, result, callback); } } void MarsLanguage::flatten(const MarsRequest& request, FlattenCallback& callback) { std::vector params; request.getParams(params); MarsRequest result(request); flatten(request, params, 0, result, callback); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::mars metkit-1.20.2/src/metkit/hypercube/0000775000175000017500000000000015246725757017353 5ustar alastairalastairmetkit-1.20.2/src/metkit/hypercube/HyperCube.cc0000664000175000017500000002332615246725757021556 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ #include "metkit/hypercube/HyperCube.h" #include #include "eckit/exception/Exceptions.h" #include "eckit/parser/YAMLParser.h" #include "metkit/mars/MarsLanguage.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" namespace metkit::hypercube { AxisOrder::AxisOrder() { eckit::Value axis = eckit::YAMLParser::decodeFile(axisYamlFile()); const eckit::Value axesNames = axis["axes"]; for (size_t i = 0; i < axesNames.size(); ++i) { axisIndex_[axesNames[i]] = i + 1; axes_.push_back(axesNames[i]); } } size_t AxisOrder::index(const std::string& axis) const { auto it = axisIndex_.find(axis); if (it == axisIndex_.end()) { return 0; } return it->second; } AxisOrder& AxisOrder::instance() { static AxisOrder instance; return instance; } class Axis { public: Axis(const std::string& name, const std::vector& values) : name_(name), values_(values) {} size_t size() const { return values_.size(); } const std::string& name() const { return name_; } int indexOf(const std::string& v) const { auto j = std::find(values_.begin(), values_.end(), v); if (j == values_.end()) { return -1; } return j - values_.begin(); } const std::string& valueOf(size_t index) const { if (values_.size() <= index) { std::ostringstream oss; oss << "Axis::valueOf no value for [axis=" << name() << ",index=" << index << "]"; throw eckit::UserError(oss.str()); } return values_[index]; } friend std::ostream& operator<<(std::ostream& s, const Axis& a) { s << "Axis[" << a.name_ << "]:"; for (size_t i = 0; i < a.values_.size(); ++i) { s << " " << a.values_[i]; } return s; } private: std::string name_; std::vector values_; }; HyperCube::HyperCube(const metkit::mars::MarsRequest& request) : verb_(request.verb()), cube_(std::vector()) { std::vector dimensions; for (auto& name : AxisOrder::instance().axes()) { std::vector values = request.values(name, true); if (!values.empty()) { Axis* a = new Axis(name, values); axes_.push_back(a); axesByName_[name] = a; dimensions.push_back(values.size()); } } cube_ = eckit::HyperCube(dimensions); count_ = cube_.count(); set_ = std::vector(count_, true); } HyperCube::~HyperCube() { for (auto& a : axes_) { delete a; } } bool HyperCube::contains(const metkit::mars::MarsRequest& r) const { int idx = indexOf(r); return (idx >= 0) and set_[idx]; } bool HyperCube::clear(int idx) { if (idx < 0) return false; if (!set_[idx]) return false; set_[idx] = false; count_--; return true; } bool HyperCube::clear(const metkit::mars::MarsRequest& r) { int idx = indexOf(r); return clear(idx); } int HyperCube::indexOf(const metkit::mars::MarsRequest& r) const { std::vector coords; for (auto& a : axes_) { const std::vector& values = r.values(a->name(), true); if (values.size() == 0) { std::ostringstream oss; oss << "HyperCube::indexOf no value for [" << a->name() << "] in request " << r; throw eckit::UserError(oss.str()); } if (values.size() > 1) { std::ostringstream oss; oss << "HyperCube::indexOf too many values for [" << a->name() << "] in request " << r; throw eckit::UserError(oss.str()); } int n = a->indexOf(values[0]); if (n < 0) { return -1; } coords.push_back(n); } return cube_.index(coords); } enum requestRelation { EMBEDDED, ADJACENT, DISJOINT }; requestRelation getRelation(const metkit::mars::MarsRequest& base, const size_t& baseSize, const metkit::mars::MarsRequest& additional, const size_t additionalSize) { metkit::mars::MarsRequest tmp(base); tmp.merge(additional); // creates the bounding box request /// @todo: building a hypercube *JUST* to get the size? Seems like we can do better size_t sizeAfter = tmp.count(); if (sizeAfter == baseSize) return requestRelation::EMBEDDED; if (baseSize + additionalSize == sizeAfter) return requestRelation::ADJACENT; return requestRelation::DISJOINT; } // Returns true only if the last request was merged into an adjacent bool mergeLast(std::vector>& requests) { size_t last = requests.size() - 1; size_t candidateIdx = std::numeric_limits::max(); size_t candidateSize = 0; // check if the new request is embedded or adjacent to existing requests for (size_t j = 0; j < requests.size() - 1; j++) { requestRelation relation = getRelation(requests[j].first, requests[j].second, requests[last].first, requests[last].second); if (relation == requestRelation::EMBEDDED) { requests.pop_back(); return false; } if (relation == requestRelation::ADJACENT) { // We can merge the two requests // Though we will only merge with the largest adjacent request, so dont merge yet. size_t combinedSize = requests[j].second + requests[last].second; if (candidateSize < combinedSize) { candidateIdx = j; candidateSize = combinedSize; } } } // Merge with the largest adjacent request if one was found if (candidateIdx != std::numeric_limits::max()) { requests[candidateIdx].first.merge(requests[last].first); requests[candidateIdx].second += requests[last].second; requests.pop_back(); return true; } return false; } std::vector> HyperCube::request(const std::set& idxs) const { using IndexSet = std::set; ASSERT(idxs.size() > 0); if (idxs.size() <= 1) { return {{requestOf(*idxs.begin()), 1}}; } // -- helper lambdas -------------------------------------------- // Partition the cube into a set of slices along the given axis. const auto sliceAlongAxis = [&](const IndexSet& set, std::size_t axis) { std::map slices; std::vector coords(axes_.size()); for (std::size_t idx : set) { cube_.coordinates(idx, coords); slices[coords[axis]].insert(idx); } return slices; }; // Pick the axis which can be partitioned into the smallest number of slices (>1). const auto pickBestAxis = [&](const IndexSet& set) -> std::size_t { std::size_t bestAxis = 0; std::size_t bestNSlices = std::numeric_limits::max(); for (std::size_t axis = 0; axis < axes_.size(); ++axis) { const std::size_t nSlices = sliceAlongAxis(set, axis).size(); if (nSlices > 1 && nSlices < bestNSlices) { bestAxis = axis; bestNSlices = nSlices; } } ASSERT(bestNSlices != std::numeric_limits::max()); return bestAxis; }; // --------------------------------------------------------------- const std::size_t axis = pickBestAxis(idxs); const auto slices = sliceAlongAxis(idxs, axis); std::vector> result; // Process each slice recursively, appending and merging on the fly. for (const auto& [coord, subIdxs] : slices) { auto subRequests = request(subIdxs); // recursion result.insert(result.end(), subRequests.begin(), subRequests.end()); while (mergeLast(result)) {} } return result; } std::vector HyperCube::aggregatedRequests(bool remaining) const { if (countVacant() == (remaining ? 0 : size())) return std::vector{}; std::set idxs; for (size_t i = 0; i < set_.size(); ++i) { if (set_[i] == remaining) idxs.emplace(i); } std::vector> requests = request(idxs); std::vector out; for (auto req : requests) out.push_back(req.first); return out; } metkit::mars::MarsRequest HyperCube::requestOf(size_t index) const { metkit::mars::MarsRequest request(verb_); std::vector coords(axes_.size()); cube_.coordinates(index, coords); for (size_t i = 0; i < axes_.size(); i++) { request.setValue(axes_[i]->name(), axes_[i]->valueOf(coords[i])); } return request; } size_t HyperCube::count() const { return count_; } size_t HyperCube::countVacant() const { return count_; } size_t HyperCube::fieldOrdinal(const metkit::mars::MarsRequest& r, bool noholes) const { int idx = indexOf(r); ASSERT(idx >= 0); if (noholes) { size_t p = 0; for (size_t i = 0; i < idx; ++i) { if (set_[i]) { p++; } } return p; } return idx; } void HyperCube::print(std::ostream& s) const { NOTIMP; ///@todo } } // namespace metkit::hypercube metkit-1.20.2/src/metkit/hypercube/HyperCubePayloaded.h0000664000175000017500000000362215246725757023240 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ /// @author Emanuele Danovaro /// @date Aug 2020 #ifndef metkit_HyperCubePayloaded_H #define metkit_HyperCubePayloaded_H #include "metkit/hypercube/HyperCube.h" namespace metkit { namespace hypercube { template class Deduplicator { public: virtual bool toReplace(const T& existing, const T& replacement) const = 0; }; template class HyperCubePayloaded : public HyperCube { public: HyperCubePayloaded(const metkit::mars::MarsRequest& request, const Deduplicator& deduplicator) : HyperCube(request), dedup_(deduplicator) { // throw error for default dedup } void add(const metkit::mars::MarsRequest& request, T payload) { int idx = indexOf(request); ASSERT(0 <= idx && idx < size()); auto entry = entries_.find(idx); if (entry == entries_.end()) { entries_.emplace(idx, payload); clear(idx); } else { if (dedup_.toReplace(entry->second, payload)) { entry->second = payload; } } } const T& at(size_t idx) { ASSERT(0 <= idx); ASSERT(idx < size()); return entries_[idx]; } bool find(size_t idx, T& payload) { auto it = entries_.find(idx); if (it == entries_.end()) return false; payload = it->second; return true; } private: const Deduplicator& dedup_; std::map entries_; }; } // namespace hypercube } // namespace metkit #endif metkit-1.20.2/src/metkit/hypercube/HyperCube.h0000664000175000017500000000534515246725757021421 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Aug 2017 #ifndef metkit_HyperCube_H #define metkit_HyperCube_H #include #include #include #include #include "eckit/utils/HyperCube.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsRequest.h" namespace metkit { namespace hypercube { class Axis; class AxisOrder { public: // methods static AxisOrder& instance(); const std::vector& axes() { return axes_; } size_t index(const std::string& axis) const; private: // methods AxisOrder(); eckit::PathName axisYamlFile() { return "~metkit/share/metkit/axis.yaml"; } private: // members std::vector axes_; std::map axisIndex_; }; class HyperCube { public: HyperCube(const metkit::mars::MarsRequest&); ~HyperCube(); bool contains(const metkit::mars::MarsRequest&) const; bool clear(const metkit::mars::MarsRequest&); size_t count() const; size_t countVacant() const; size_t size() const { return cube_.count(); } size_t fieldOrdinal(const metkit::mars::MarsRequest&, bool noholes = true) const; std::vector vacantRequests() const { return aggregatedRequests(true); } std::vector requests() const { return aggregatedRequests(false); } protected: std::vector aggregatedRequests(bool remaining) const; int indexOf(const metkit::mars::MarsRequest&) const; bool clear(int index); metkit::mars::MarsRequest requestOf(size_t index) const; // Given a set of indices, build the *minimal* collection of Mars requests that cover them. // Each entry in the result vector is: { merged_request, number_of_points_covered_by_that_request } /// @note: This does not take into account whether the point is "set" or not std::vector> request(const std::set& idxs) const; private: std::string verb_; std::vector axes_; std::map axesByName_; std::vector set_; eckit::HyperCube cube_; size_t count_; void print(std::ostream&) const; friend std::ostream& operator<<(std::ostream& s, const HyperCube& p) { p.print(s); return s; } }; } // namespace hypercube } // namespace metkit #endif metkit-1.20.2/src/metkit/codes/0000775000175000017500000000000015246725757016462 5ustar alastairalastairmetkit-1.20.2/src/metkit/codes/GRIBDecoder.cc0000664000175000017500000001524315246725757021007 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "eckit/config/Resource.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/Buffer.h" #include "eckit/message/Message.h" #include "eckit/serialisation/MemoryStream.h" #include "metkit/codes/GRIBDecoder.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/config/LibMetkit.h" #include "eccodes.h" #include #include namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- bool GRIBDecoder::match(const eckit::message::Message& msg) const { size_t len = msg.length(); const char* p = static_cast(msg.data()); return len >= 4 and ((p[0] == 'G' and p[1] == 'R' and p[2] == 'I' and p[3] == 'B') or (p[0] == 'T' and p[1] == 'I' and p[2] == 'D' and p[3] == 'E') or (p[0] == 'B' and p[1] == 'U' and p[2] == 'D' and p[3] == 'G')); } namespace { bool isInteger(double val) { double intpart; return std::modf(val, &intpart) == 0.0; } } // namespace void GRIBDecoder::getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer& gather, const eckit::message::GetMetadataOptions& options) const { static std::string gribToRequestNamespace = eckit::Resource("gribToRequestNamespace", "mars"); std::string nameSpace = options.nameSpace ? *options.nameSpace : gribToRequestNamespace; auto h = codesHandleFromMessage({static_cast(msg.data()), msg.length()}); for (const auto& k : h->keys(nameSpace)) { auto name = k.name(); if (name[0] == '_') continue; // skip silly underscores in GRIB // Get key size to see if it is an array // Only continue for scalar values const auto keySize = h->size(name); if (keySize != 1) { LOG_DEBUG_LIB(LibMetkit) << "GRIBDecoder::getMetadata skipping non-scalar key '" << name << "' (size=" << keySize << ")" << std::endl; continue; } switch (options.valueRepresentation) { case eckit::message::ValueRepresentation::String: { // TODO - remove as soon as https://jira.ecmwf.int/browse/ECC-2113 is fixed if (k.name() == "levelist") { double val = k.getDouble(); if (isInteger(val)) { gather.setValue(name, eckit::translate(static_cast(val))); } else { gather.setValue(name, eckit::translate(val)); } } else { gather.setValue(name, k.getString()); } break; } case eckit::message::ValueRepresentation::Native: { std::visit( [&](auto&& v) { using Type = std::decay_t; // TODO - remove as soon as https://jira.ecmwf.int/browse/ECC-2113 is fixed if (k.name() == "levelist") { double val = k.getDouble(); if (isInteger(val)) { gather.setValue(name, static_cast(val)); } else { gather.setValue(name, val); } } else if constexpr (std::is_same_v || std::is_arithmetic_v) { gather.setValue(name, std::forward(v)); } else if constexpr (std::is_same_v>) { gather.setValue(name, k.getString()); } else { // Unhandled types are all array types - the prior call checking `size != 1` only allows // for scalars. throw eckit::Exception( std::string("Unexpected type when accessing GRIB message metadata ") + typeid(v).name(), Here()); } }, k.get()); break; } } } // Explicit override for param (kludge for paramID handling) if (h->has("paramId")) { gather.setValue("param", h->getString("paramId")); } // Look for request embbeded in GRIB message if (h->has("localDefinitionNumber") && h->getLong("localDefinitionNumber") == 191) { /* TODO: Not grib2 compatible, but speed-up process */ if (h->has("freeFormData")) { auto buffer = h->getBytes("freeFormData"); eckit::MemoryStream s(buffer.data(), buffer.size()); int count; s >> count; // Number of requests ASSERT(count == 1); std::string tmp; s >> tmp; // verb s >> count; for (int i = 0; i < count; i++) { std::string keyword, value; int n; s >> keyword; std::transform(keyword.begin(), keyword.end(), keyword.begin(), tolower); s >> n; // Number of values ASSERT(n == 1); s >> value; std::transform(value.begin(), value.end(), value.begin(), tolower); gather.setValue(keyword, value); } } } } eckit::Buffer GRIBDecoder::decode(const eckit::message::Message& msg) const { std::size_t size = msg.getSize("values"); eckit::Buffer buf(size * sizeof(double)); msg.getDoubleArray("values", reinterpret_cast(buf.data()), size); return buf; } void GRIBDecoder::print(std::ostream& s) const { s << "GRIBDecoder[]"; } //---------------------------------------------------------------------------------------------------------------------- static GRIBDecoder gribDecoder; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/BufrContent.h0000664000175000017500000000215315246725757021065 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @author Emanuele Danovaro /// @date Mar 2022 #pragma once #include "metkit/codes/CodesDataContent.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class BufrContent : public CodesDataContent { public: BufrContent(std::unique_ptr handle); virtual ~BufrContent() = default; protected: using CodesDataContent::transform; void transform(const eckit::OrderedStringDict& dict) override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/CodesDataContent.cc0000664000175000017500000001230115246725757022150 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ #include "metkit/codes/CodesDataContent.h" #include "eccodes.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/DataHandle.h" #include "eckit/io/MemoryHandle.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- CodesDataContent::CodesDataContent(std::unique_ptr handle) : handle_(std::move(handle)), offset_(handle_->getLong("offset")) {} CodesDataContent::CodesDataContent(std::unique_ptr handle, eckit::Offset offset) : handle_(std::move(handle)), offset_(std::move(offset)) {} //---------------------------------------------------------------------------------------------------------------------- size_t CodesDataContent::length() const { return handle_->messageSize(); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::write(eckit::DataHandle& handle) const { auto data = handle_->messageData(); if (handle.write(data.data(), data.size()) != data.size()) { std::ostringstream oss; oss << "Write error to data handle " << handle; throw eckit::WriteError(oss.str(), Here()); } } //---------------------------------------------------------------------------------------------------------------------- eckit::DataHandle* CodesDataContent::readHandle() const { auto data = handle_->messageData(); return new eckit::MemoryHandle(data.data(), data.size()); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::print(std::ostream& s) const { s << "CodesDataContent[]"; } //---------------------------------------------------------------------------------------------------------------------- std::string CodesDataContent::getString(const std::string& key) const { return handle_->getString(key); } //---------------------------------------------------------------------------------------------------------------------- long CodesDataContent::getLong(const std::string& key) const { return handle_->getLong(key); } //---------------------------------------------------------------------------------------------------------------------- double CodesDataContent::getDouble(const std::string& key) const { return handle_->getDouble(key); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::getDoubleArray(const std::string& key, std::vector& values) const { values = handle_->getDoubleArray(key); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::getFloatArray(const std::string& key, std::vector& values) const { values = handle_->getFloatArray(key); } //---------------------------------------------------------------------------------------------------------------------- size_t CodesDataContent::getSize(const std::string& key) const { return handle_->size(key); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::getDoubleArray(const std::string& key, double* data, size_t len) const { auto arr = handle_->getDoubleArray(key); ASSERT(len == arr.size()); std::copy(arr.begin(), arr.end(), data); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::getFloatArray(const std::string& key, float* data, size_t len) const { auto arr = handle_->getFloatArray(key); ASSERT(len == arr.size()); std::copy(arr.begin(), arr.end(), data); } //---------------------------------------------------------------------------------------------------------------------- void CodesDataContent::transform(const eckit::OrderedStringDict& dict) { for (auto& [key, value] : dict) { handle_->set(key, value); } } //---------------------------------------------------------------------------------------------------------------------- eckit::Offset CodesDataContent::offset() const { return offset_; } //---------------------------------------------------------------------------------------------------------------------- const void* CodesDataContent::data() const { return handle_->messageData().data(); } //---------------------------------------------------------------------------------------------------------------------- const CodesHandle& CodesDataContent::codesHandle() const { return *handle_.get(); } CodesHandle& CodesDataContent::codesHandle() { return *handle_.get(); } } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/GRIBDecoder.h0000664000175000017500000000245715246725757020654 0ustar alastairalastair/* * (C) Copyright 2020- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #pragma once #include "eckit/message/Decoder.h" #include "eckit/message/Message.h" #include "eckit/io/Buffer.h" namespace metkit::codes { //---------------------------------------------------------------------------------------------------------------------- class GRIBDecoder : public eckit::message::MessageDecoder { public: // methods private: // methods bool match(const eckit::message::Message&) const override; void print(std::ostream&) const override; void getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer&, const eckit::message::GetMetadataOptions&) const override; eckit::Buffer decode(const eckit::message::Message& msg) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/BUFRDecoder.h0000664000175000017500000000260415246725757020661 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #pragma once #include "eckit/message/Decoder.h" #include "eckit/message/Message.h" #include "eckit/io/Buffer.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class BUFRDecoder : public eckit::message::MessageDecoder { public: // methods static bool typeBySubtype(long subtype, long& type); private: // methods bool match(const eckit::message::Message&) const override; void print(std::ostream&) const override; void getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer&, const eckit::message::GetMetadataOptions&) const override; eckit::Buffer decode(const eckit::message::Message& msg) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/CodesSplitter.cc0000664000175000017500000000530215246725757021555 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/codes/CodesSplitter.h" #include "eccodes.h" #include "eckit/io/PeekHandle.h" #include "eckit/message/Message.h" #include "metkit/codes/CodesDataContent.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/CodesTypes.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- CodesSplitter::CodesSplitter(eckit::PeekHandle& handle) : Splitter(handle) {} CodesSplitter::~CodesSplitter() {} eckit::message::Message CodesSplitter::next() { eckit::Offset off = handle_.position(); std::unique_ptr codesHandle; try { codesHandle = codesHandleFromStream([&](uint8_t* buffer, int64_t len) -> int64_t { return handle_.read(buffer, len); }); } catch (const CodesWrongLength& e) { // METK-103 - used in bufr-sanity-check tool // Resetting handle and skipping header (ie. GRIB or BUFR) // Allows ignoring messages inbetween and continuing handle_.seek(off + eckit::Offset(4)); throw e; } // Handle EOF if (!codesHandle) { return eckit::message::Message(); } return eckit::message::Message(new CodesDataContent(std::move(codesHandle), off)); } void CodesSplitter::print(std::ostream& s) const { s << "CodesSplitter[]"; } } // namespace codes } // namespace metkit //---------------------------------------------------------------------------------------------------------------------- namespace eckit { namespace message { template <> bool SplitterBuilder::match(eckit::PeekHandle& handle) const { unsigned char c0 = handle.peek(0); unsigned char c1 = handle.peek(1); unsigned char c2 = handle.peek(2); unsigned char c3 = handle.peek(3); if (c0 == 'G' and c1 == 'R' and c2 == 'I' and c3 == 'B') { return true; } if (c0 == 'B' and c1 == 'U' and c2 == 'F' and c3 == 'R') { return true; } if (c0 == 'B' and c1 == 'U' and c2 == 'D' and c3 == 'G') { return true; } if (c0 == 'T' and c1 == 'I' and c2 == 'D' and c3 == 'E') { return true; } return false; } } // namespace message } // namespace eckit static eckit::message::SplitterBuilder splitter; metkit-1.20.2/src/metkit/codes/LibEccodes.cc0000664000175000017500000000237115246725757020770 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Olivier Iffrig /// @date October 2019 #include "eccodes.h" #include "metkit/codes/LibEccodes.h" namespace metkit { //---------------------------------------------------------------------------------------------------------------------- REGISTER_LIBRARY(LibEccodes); LibEccodes::LibEccodes() : Library("eccodes") {} const LibEccodes& LibEccodes::instance() { static LibEccodes libeccodes; return libeccodes; } std::string LibEccodes::version() const { return ECCODES_VERSION_STR; } std::string LibEccodes::gitsha1(unsigned int count) const { std::string sha1(codes_get_git_sha1()); if (sha1.empty()) { return "not available"; } return sha1.substr(0, std::min(count, 40u)); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/codes/BufrContent.cc0000664000175000017500000000222715246725757021225 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ #include "metkit/codes/BufrContent.h" #include "eccodes.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- BufrContent::BufrContent(std::unique_ptr handle) : CodesDataContent(std::move(handle)) {} //---------------------------------------------------------------------------------------------------------------------- void BufrContent::transform(const eckit::OrderedStringDict& dict) { for (const auto& [key, value] : dict) { handle_->set(key, std::stol(value)); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/BUFRDecoder.cc0000664000175000017500000001344615246725757021025 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include #include #include "eckit/config/Resource.h" #include "eckit/message/Message.h" #include "eckit/parser/YAMLParser.h" #include "metkit/codes/BUFRDecoder.h" #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/CodesTypes.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsRequest.h" #include "eckit/exception/Exceptions.h" #include "eccodes.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- static std::map subtypes_; static pthread_once_t once = PTHREAD_ONCE_INIT; static void readTable() { eckit::PathName bufrSubtypesPath = eckit::Resource("bufrSubtypesPath;$BUFR_SUBTYPES_PATH", LibMetkit::bufrSubtypesYamlFile()); const eckit::Value bufrSubtypes = eckit::YAMLParser::decodeFile(bufrSubtypesPath); const eckit::Value subtypes = bufrSubtypes["subtypes"]; ASSERT(subtypes.isList()); for (size_t i = 0; i < subtypes.size(); ++i) { const eckit::Value s = subtypes[i]; ASSERT(s.isList()); ASSERT(s.size() == 2); subtypes_[s[0]] = s[1]; } } bool BUFRDecoder::typeBySubtype(long subtype, long& type) { pthread_once(&once, readTable); auto s = subtypes_.find(subtype); if (s != subtypes_.end()) { type = s->second; return true; } return false; } //---------------------------------------------------------------------------------------------------------------------- bool BUFRDecoder::match(const eckit::message::Message& msg) const { size_t len = msg.length(); const char* p = static_cast(msg.data()); return len >= 4 and p[0] == 'B' and p[1] == 'U' and p[2] == 'F' and p[3] == 'R'; } //---------------------------------------------------------------------------------------------------------------------- void BUFRDecoder::getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer& gather, const eckit::message::GetMetadataOptions& options) const { auto h(codesHandleFromMessage({static_cast(msg.data()), msg.length()})); /* // BUFR Performance improvement: https://confluence.ecmwf.int/display/UDOC/Performance+improvement+by+skipping+some+keys+-+ecCodes+BUFR+FAQ if ((unsigned long)(options.filter & eckit::message::MetadataFilter::IncludeExtraKeyAttributes) != 0) { CODES_CHECK(codes_set_long(h.get(), "skipExtraKeyAttributes", 1), 0); } */ // we need to instruct ecCodes to unpack the data values: // https://confluence.ecmwf.int/display/ECC/bufr_keys_iterator h->set("unpack", 1); for (const auto& k : h->keys()) { auto name = k.name(); if (name == "subsetNumber") { continue; } // Get key size to see if it is an array // Only continue for scalar values const auto keySize = h->size(name); if (keySize != 1) { LOG_DEBUG_LIB(LibMetkit) << "BUFRDecoder::getMetadata skipping non-scalar key '" << name << "' (size=" << keySize << ")" << std::endl; continue; } switch (options.valueRepresentation) { case eckit::message::ValueRepresentation::String: { gather.setValue(name, k.getString()); break; } case eckit::message::ValueRepresentation::Native: { std::visit( [&](auto&& v) { using Type = std::decay_t; if constexpr (std::is_same_v || std::is_arithmetic_v) { gather.setValue(name, std::forward(v)); } else if constexpr (std::is_same_v>) { gather.setValue(name, k.getString()); } else { // Unhandled types are all array types - the prior call checking `size != 1` only allows for // scalars. throw eckit::Exception( std::string("Unexpected type when accessing BURF message metadata ") + typeid(v).name(), Here()); } }, k.get()); break; } } } } //---------------------------------------------------------------------------------------------------------------------- eckit::Buffer BUFRDecoder::decode(const eckit::message::Message& msg) const { std::size_t size = msg.getSize("numericValues"); eckit::Buffer buf(size * sizeof(double)); msg.getDoubleArray("numericValues", reinterpret_cast(buf.data()), size); return buf; } //---------------------------------------------------------------------------------------------------------------------- void BUFRDecoder::print(std::ostream& s) const { s << "BUFRDecoder[]"; } //---------------------------------------------------------------------------------------------------------------------- static BUFRDecoder bufrDecoder; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/CodesSplitter.h0000664000175000017500000000205315246725757021417 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #pragma once #include "eckit/message/Splitter.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class CodesSplitter : public eckit::message::Splitter { public: CodesSplitter(eckit::PeekHandle&); ~CodesSplitter(); private: // methods eckit::message::Message next() override; void print(std::ostream&) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/CodesHandleDeleter.h0000664000175000017500000000231015246725757022305 0ustar alastairalastair/* * (C) Copyright 2022- ECMWF. * * 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. */ /// @author Philipp Geier /// @author Simon Smart /// @date Nov 2022 #pragma once #include #include //---------------------------------------------------------------------------------------------------------------------- namespace std { template <> struct default_delete { void operator()(codes_handle* h) { ::codes_handle_delete(h); } }; template <> struct default_delete { void operator()(codes_keys_iterator* it) { ::codes_keys_iterator_delete(it); } }; template <> struct default_delete { void operator()(codes_bufr_keys_iterator* it) { ::codes_bufr_keys_iterator_delete(it); } }; } // namespace std //---------------------------------------------------------------------------------------------------------------------- metkit-1.20.2/src/metkit/codes/api/0000775000175000017500000000000015246725757017233 5ustar alastairalastairmetkit-1.20.2/src/metkit/codes/api/CodesTypes.h0000664000175000017500000000553515246725757021476 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #pragma once #include #include #include #include #include #include "eckit/exception/Exceptions.h" namespace metkit::codes { /// Exception that is thrown by the API to wrap eccodes errors class CodesException : public eckit::Exception { public: CodesException(const std::string& reason, const eckit::CodeLocation& l = eckit::CodeLocation()); }; /// More specialized exception that is thrown in specific cases when a message could not be read properly, usually /// indicating corruption. class CodesWrongLength : public CodesException { public: CodesWrongLength(const std::string& reason, const eckit::CodeLocation& l = eckit::CodeLocation()); }; /// Enum to classify types of handles enum class Product { GRIB, BUFR, }; /// Enum that redefines eccodes specific key types enum class NativeType { Undefined, Long, Double, String, Bytes, Section, Label, Missing, }; /// Sum type of possible values returned by eccodes. using CodesValue = std::variant, std::vector, std::vector, std::vector, std::vector>; //---------------------------------------------------------------------------------------------------------------------- /// To be replaced with std::span in C++20. template struct Span { using element_type = T; using value_type = std::remove_cv_t; const value_type* data_ = nullptr; std::size_t size_ = 0; const value_type* data() { return data_; } std::size_t size() { return size_; } Span() = default; Span(const T* p, std::size_t n) : data_(p), size_(n) {} // from std::vector template Span(const std::vector& v) : data_(v.data()), size_(v.size()) {} // from std::array template Span(const std::array& arr) : data_(arr.data()), size_(N) {} // from C array template Span(const value_type (&arr)[N]) : data_(arr), size_(N) {} // from std::basic_string template ::value, bool> = true> Span(const std::basic_string& s) : data_(s.data()), size_(s.size()) {} }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/KeyIterator.cc0000664000175000017500000000345515246725757022013 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #include "metkit/codes/api/KeyIterator.h" #include "eccodes.h" namespace metkit::codes { KeyIteratorFlags operator|(KeyIteratorFlags a, KeyIteratorFlags b) { return static_cast(static_cast >(a) | static_cast >(b)); } KeyIteratorFlags operator&(KeyIteratorFlags a, KeyIteratorFlags b) { return static_cast(static_cast >(a) & static_cast >(b)); } KeyIteratorFlags operator^(KeyIteratorFlags a, KeyIteratorFlags b) { return static_cast(static_cast >(a) ^ static_cast >(b)); } KeyIteratorFlags operator~(KeyIteratorFlags a) { return static_cast(~static_cast >(a)); } bool hasFlag(KeyIteratorFlags value, KeyIteratorFlags flag) { return (static_cast >(value) & static_cast >(flag)) != 0; } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/CodesTypes.cc0000664000175000017500000000141615246725757021626 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #include "metkit/codes/api/CodesTypes.h" namespace metkit::codes { CodesException::CodesException(const std::string& reason, const eckit::CodeLocation& l) : eckit::Exception(std::string("CodesException: ") + reason, l) {}; CodesWrongLength::CodesWrongLength(const std::string& reason, const eckit::CodeLocation& l) : CodesException(reason, l) {}; } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/CodesAPI.cc0000664000175000017500000007334715246725757021147 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #include "metkit/codes/api/CodesAPI.h" #include "metkit/codes/api/CodesTypes.h" #include "eckit/log/CodeLocation.h" #include "eccodes.h" #include "metkit/config/LibMetkit.h" namespace std { template <> struct default_delete { void operator()(codes_handle* h) { ::codes_handle_delete(h); } }; template <> struct default_delete { void operator()(codes_keys_iterator* it) { ::codes_keys_iterator_delete(it); } }; template <> struct default_delete { void operator()(codes_iterator* it) { ::codes_grib_iterator_delete(it); } }; } // namespace std namespace metkit::codes { namespace { void throwOnError(int code, const eckit::CodeLocation& l, const char* details) { if (code != 0) { std::string msg = std::string(details) + std::string(": ") + std::string(codes_get_error_message(code)); if (code == GRIB_WRONG_LENGTH) { throw CodesWrongLength(msg, l); } else { throw CodesException(msg, l); } } }; void throwOnError(int code, const eckit::CodeLocation& l, const char* details, const std::string& key) { if (code != 0) { std::string msg = std::string(details) + std::string(": ") + std::string(codes_get_error_message(code)) + std::string(" for key ") + key; if (code == GRIB_WRONG_LENGTH) { throw CodesWrongLength(msg, l); } else { throw CodesException(msg, l); } } }; /// Concrete implementation of CodesHandle. /// ConcreteCodesHandle provides the common C++ wrapper implementation around /// a codes_handle* independently of the ownership model. class ConcreteCodesHandle : public CodesHandle { public: virtual ~ConcreteCodesHandle() {}; size_t messageSize() const override; Span messageData() const override; bool isDefined(const std::string& key) const override; bool isMissing(const std::string& key) const override; bool has(const std::string& key) const override; void setMissing(const std::string& key) override; void set(const std::string& key, const std::string& value) override; void set(const std::string& key, double value) override; void set(const std::string& key, long value) override; void set(const std::string& key, Span value) override; void set(const std::string& key, Span value) override; void set(const std::string& key, Span value) override; void set(const std::string& key, Span value) override; void set(const std::string& key, Span value) override; void set(const std::string& key, Span value) override; void forceSet(const std::string& key, Span value) override; void forceSet(const std::string& key, Span value) override; size_t size(const std::string& key) const override; size_t length(const std::string& key) const override; CodesValue get(const std::string& key) const override; NativeType type(const std::string& key) const override; long getLong(const std::string& key) const override; double getDouble(const std::string& key) const override; std::string getString(const std::string& key) const override; std::vector getLongArray(const std::string& key) const override; std::vector getDoubleArray(const std::string& key) const override; std::vector getFloatArray(const std::string& key) const override; std::vector getStringArray(const std::string& key) const override; std::vector getBytes(const std::string& key) const override; /// Clones the underyling handle. /// Uses `codes_handle_clone` internally. /// @return Unique pointer to a cloned `CodesHandle` instance. std::unique_ptr clone() const override; /// Copy the message into a new allocated buffer /// @param data Pointer to an allocated array /// @param size Size of the allocated array void copyInto(uint8_t* data, size_t size) const override; KeyRange keys(KeyIteratorFlags flags = KeyIteratorFlags::AllKeys, std::optional ns = std::optional{}) const override; KeyRange keys(Namespace ns) const override; GeoRange values() const override; /// Release the raw `codes_handle*` - used to pass ownership out of C++ (e.g. python) virtual void* release() override = 0; protected: codes_handle* raw() const { if (!handle()) { throw CodesException("CodesHandle has been released.", Here()); } return handle(); } virtual codes_handle* handle() const = 0; }; /// Concrete implementation of CodesHandle. /// OwningCodesHandle is a owning container around a codes_handle* that /// makes the C API accessible to C++ for any codes_handle*. /// It will properly delete the underlying codes_handle* on /// construction. class OwningCodesHandle : public ConcreteCodesHandle { public: OwningCodesHandle(std::unique_ptr handle) : handle_{std::move(handle)} {}; virtual ~OwningCodesHandle() {}; OwningCodesHandle(OwningCodesHandle&&) = default; OwningCodesHandle& operator=(OwningCodesHandle&&) = default; /// Release the raw `codes_handle*` - used to pass ownership out of C++ (e.g. python) void* release() override { return handle_.release(); }; private: codes_handle* handle() const override { return handle_.get(); } std::unique_ptr handle_; }; /// Concrete implementation of CodesHandle. /// NonOwningCodesHandle wraps a borrowed codes_handle* without taking /// ownership of its lifetime. class NonOwningCodesHandle : public ConcreteCodesHandle { public: NonOwningCodesHandle(codes_handle* handle) : handle_{handle} {}; virtual ~NonOwningCodesHandle() {}; NonOwningCodesHandle(NonOwningCodesHandle&&) = default; NonOwningCodesHandle& operator=(NonOwningCodesHandle&&) = default; void* release() override { throw CodesException("CodesHandle::release() is not supported on a non-owning handle.", Here()); }; private: codes_handle* handle() const override { return handle_; } codes_handle* handle_; }; size_t ConcreteCodesHandle::messageSize() const { size_t size; throwOnError(codes_get_message_size(raw(), &size), Here(), "CodesHandle::messageSize()"); return size; } bool ConcreteCodesHandle::isDefined(const std::string& key) const { return codes_is_defined(raw(), key.c_str()) == 1; } bool ConcreteCodesHandle::isMissing(const std::string& key) const { int err = 0; bool res = codes_is_missing(raw(), key.c_str(), &err) == 1; throwOnError(err, Here(), "CodesHandle::isMissing()", key); return res; } bool ConcreteCodesHandle::has(const std::string& key) const { return isDefined(key) && !isMissing(key); } /// Set a key to its missing value void ConcreteCodesHandle::setMissing(const std::string& key) { throwOnError(codes_set_missing(raw(), key.c_str()), Here(), "CodesHandle::setMissing()", key); } void ConcreteCodesHandle::set(const std::string& key, const std::string& value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API: " + key << " = '" << value << "'" << std::endl; size_t size = value.size(); throwOnError(codes_set_string(raw(), key.c_str(), value.c_str(), &size), Here(), "CodesHandle::set(string, string)", key); } void ConcreteCodesHandle::set(const std::string& key, double value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API: " + key << " = " << std::to_string(value) << std::endl; throwOnError(codes_set_double(raw(), key.c_str(), value), Here(), "CodesHandle::set(string, double)", key); } void ConcreteCodesHandle::set(const std::string& key, long value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API: " + key << " = " << std::to_string(value) << std::endl; throwOnError(codes_set_long(raw(), key.c_str(), value), Here(), "CodesHandle::set(string, long)", key); } /// Set arrays void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; std::vector out; out.reserve(value.size()); for (auto it = value.data(); it != value.data() + value.size(); ++it) { out.push_back(it->c_str()); } set(key, out); } void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_string_array(raw(), key.c_str(), const_cast(value.data()), value.size()), Here(), "CodesHandle::set(string, span)", key); } /// set string array void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_double_array(raw(), key.c_str(), value.data(), value.size()), Here(), "CodesHandle::set(string, span)", key); } void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_float_array(raw(), key.c_str(), value.data(), value.size()), Here(), "CodesHandle::set(string, span)", key); } void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_long_array(raw(), key.c_str(), value.data(), value.size()), Here(), "CodesHandle::set(string, span)", key); } void ConcreteCodesHandle::set(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; size_t size = value.size(); throwOnError(codes_set_bytes(raw(), key.c_str(), value.data(), &size), Here(), "CodesHandle::set(string, span)", key); } void ConcreteCodesHandle::forceSet(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_force_double_array(raw(), key.c_str(), value.data(), value.size()), Here(), "CodesHandle::forceSet(string, span)", key); } void ConcreteCodesHandle::forceSet(const std::string& key, Span value) { LOG_DEBUG_LIB(LibMetkit) << std::endl; LOG_DEBUG_LIB(LibMetkit) << "Codes API>: " + key << std::endl; throwOnError(codes_set_force_float_array(raw(), key.c_str(), value.data(), value.size()), Here(), "CodesHandle::forceSet(string, span)", key); } size_t ConcreteCodesHandle::size(const std::string& key) const { size_t size; throwOnError(codes_get_size(raw(), key.c_str(), &size), Here(), "CodesHandle::size(string)", key); return size; } size_t ConcreteCodesHandle::length(const std::string& key) const { size_t length; throwOnError(codes_get_length(raw(), key.c_str(), &length), Here(), "CodesHandle::length(string)", key); return length; } /// Get the value of the key CodesValue ConcreteCodesHandle::get(const std::string& key) const { NativeType ktype = type(key); bool isArray = size(key) > 1; switch (ktype) { case NativeType::Long: if (isArray) { return getLongArray(key); } else { return getLong(key); } case NativeType::Double: if (isArray) { return getDoubleArray(key); } else { return getDouble(key); } case NativeType::String: if (isArray) { return getStringArray(key); } else { return getString(key); } case NativeType::Bytes: return getBytes(key); case NativeType::Undefined: throw CodesException(std::string("CodesHandle::get(") + key + std::string("): Native type is UNDEFINED"), Here()); case NativeType::Section: case NativeType::Label: case NativeType::Missing: default: return getString(key); } } /// Get the type of the key NativeType ConcreteCodesHandle::type(const std::string& key) const { int type; throwOnError(codes_get_native_type(raw(), key.c_str(), &type), Here(), "CodesHandle::type(string)", key); switch (type) { case CODES_TYPE_LONG: return NativeType::Long; case CODES_TYPE_DOUBLE: return NativeType::Double; case CODES_TYPE_STRING: return NativeType::String; case CODES_TYPE_BYTES: return NativeType::Bytes; case CODES_TYPE_SECTION: return NativeType::Section; case CODES_TYPE_LABEL: return NativeType::Label; case CODES_TYPE_MISSING: return NativeType::Missing; default: return NativeType::Undefined; } } /// Explicit getters long ConcreteCodesHandle::getLong(const std::string& key) const { long value; throwOnError(codes_get_long(raw(), key.c_str(), &value), Here(), "CodesHandle::getLong(string)", key); return value; } double ConcreteCodesHandle::getDouble(const std::string& key) const { double value; throwOnError(codes_get_double(raw(), key.c_str(), &value), Here(), "CodesHandle::getDouble(string)", key); return value; } std::string ConcreteCodesHandle::getString(const std::string& key) const { std::string ret; std::size_t keylen = 1024; ret.resize(keylen); throwOnError(codes_get_string(raw(), key.c_str(), ret.data(), &keylen), Here(), "CodesHandle::getString(string)", key); ret.resize(strlen(ret.c_str())); return ret; } std::vector ConcreteCodesHandle::getLongArray(const std::string& key) const { std::vector ret; std::size_t ksize = size(key); ret.resize(ksize); throwOnError(codes_get_long_array(raw(), key.c_str(), ret.data(), &ksize), Here(), "CodesHandle::getLongArray(string)", key); ret.resize(ksize); return ret; } std::vector ConcreteCodesHandle::getDoubleArray(const std::string& key) const { std::vector ret; std::size_t ksize = size(key); ret.resize(ksize); throwOnError(codes_get_double_array(raw(), key.c_str(), ret.data(), &ksize), Here(), "CodesHandle::getDoubleArray(string)", key); ret.resize(ksize); return ret; } std::vector ConcreteCodesHandle::getFloatArray(const std::string& key) const { std::vector ret; std::size_t ksize = size(key); ret.resize(ksize); throwOnError(codes_get_float_array(raw(), key.c_str(), ret.data(), &ksize), Here(), "CodesHandle::getFloatArray(string)", key); ret.resize(ksize); return ret; } std::vector ConcreteCodesHandle::getStringArray(const std::string& key) const { std::vector cstrings; std::size_t ksize = size(key); cstrings.resize(ksize); throwOnError(codes_get_string_array(raw(), key.c_str(), cstrings.data(), &ksize), Here(), "CodesHandle::getStringArray(string)", key); cstrings.resize(ksize); std::vector ret; ret.reserve(cstrings.size()); for (char* cstr : cstrings) { ret.push_back(cstr); } return ret; } std::vector ConcreteCodesHandle::getBytes(const std::string& key) const { std::vector ret; /// Note: The returned length for bytes often is much higher than needed. /// For UIDs (i.e. "uuidOfHGrid"), the native type is BYTES, but length returns the number of characters in its /// string representation - which is twice the number of bytes. However, in contrast for fields like "bitmap" it /// directly returns the number of bytes. It has been discussed with Shahram several times without a change taking /// in. std::size_t ksize = length(key); ret.resize(ksize); throwOnError(codes_get_bytes(raw(), key.c_str(), ret.data(), &ksize), Here(), "CodesHandle::getBytes(string)", key); ret.resize(ksize); return ret; } /// Cloning the whole handle. Expected to be wrapped by the user explicitly std::unique_ptr ConcreteCodesHandle::clone() const { std::unique_ptr ret{codes_handle_clone(raw())}; if (!ret) { throw CodesException("CodesHandle::clone() failed", Here()); } return std::make_unique(std::move(ret)); } /// Copy the message into a new allocated buffer void ConcreteCodesHandle::copyInto(uint8_t* data, size_t size) const { std::size_t s = size; throwOnError(codes_get_message_copy(raw(), data, &s), Here(), "CodesHandle::copy(uint8_t*, size_t*)"); } /// Copy the message into a new allocated buffer Span ConcreteCodesHandle::messageData() const { size_t s; const uint8_t* data; throwOnError(codes_get_message(raw(), reinterpret_cast(&data), &s), Here(), "CodesHandle::messageData()"); return {data, s}; } class ConcreteKeyIterator : public KeyIterator { public: // methods ConcreteKeyIterator(const ConcreteCodesHandle& handle, std::unique_ptr it) : refHandle_{std::cref(handle)}, it_{std::move(it)}, isValid_{false} { next(); } virtual ~ConcreteKeyIterator() {} std::string name() const override { return codes_keys_iterator_get_name(it_.get()); }; /// Get the value of the key CodesValue get() const override { std::string key = name(); NativeType type = refHandle_.get().type(key); bool isArray = refHandle_.get().size(key) > 1; switch (type) { case NativeType::Long: if (isArray) { return getLongArray(); } else { return getLong(); } case NativeType::Double: if (isArray) { return getDoubleArray(); } else { return getDouble(); } case NativeType::String: if (isArray) { return getStringArray(); } else { return getString(); } case NativeType::Bytes: return getBytes(); case NativeType::Undefined: throw CodesException(std::string("IteratedKey::get(") + key + std::string("): Native type is UNDEFINED")); case NativeType::Section: case NativeType::Label: case NativeType::Missing: default: return getString(); } }; /// Get the type of the key NativeType type() const override { return refHandle_.get().type(name()); }; /// Explicit getters long getLong() const override { long value; size_t size = 1; throwOnError(codes_keys_iterator_get_long(it_.get(), &value, &size), Here(), "KeyIterator::getLong()"); return value; } double getDouble() const override { double value; size_t size = 1; throwOnError(codes_keys_iterator_get_double(it_.get(), &value, &size), Here(), "KeyIterator::getDouble()"); return value; } float getFloat() const override { float value; size_t size = 1; throwOnError(codes_keys_iterator_get_float(it_.get(), &value, &size), Here(), "KeyIterator::getFloat()"); return value; } std::string getString() const override { std::string ret; std::size_t keylen = 1024; ret.resize(keylen); throwOnError(codes_keys_iterator_get_string(it_.get(), ret.data(), &keylen), Here(), "KeyIterator::getString()"); ret.resize(strlen(ret.c_str())); return ret; } std::vector getLongArray() const override { std::vector ret; std::string key = name(); std::size_t size = refHandle_.get().size(key); ret.resize(size); throwOnError(codes_keys_iterator_get_long(it_.get(), ret.data(), &size), Here(), "KeyIterator::getLongArray()"); ret.resize(size); return ret; } std::vector getDoubleArray() const override { std::vector ret; std::string key = name(); std::size_t size = refHandle_.get().size(key); ret.resize(size); throwOnError(codes_keys_iterator_get_double(it_.get(), ret.data(), &size), Here(), "KeyIterator::getDoubleArray"); ret.resize(size); return ret; } std::vector getFloatArray() const override { std::vector ret; std::string key = name(); std::size_t size = refHandle_.get().size(key); ret.resize(size); throwOnError(codes_keys_iterator_get_float(it_.get(), ret.data(), &size), Here(), "KeyIterator::getFloatArray"); ret.resize(size); return ret; } std::vector getStringArray() const override { // There is no other option to get string array return refHandle_.get().getStringArray(name()); } std::vector getBytes() const override { std::vector ret; std::string key = name(); std::size_t size = refHandle_.get().length(key) / 2; ret.resize(size); throwOnError(codes_keys_iterator_get_bytes(it_.get(), ret.data(), &size), Here(), "KeyIterator::getBytes()"); ret.resize(size); return ret; } protected: void next() override { isValid_ = codes_keys_iterator_next(it_.get()) > 0; } bool isValid() const override { return isValid_; } private: std::reference_wrapper refHandle_; std::unique_ptr it_; bool isValid_; }; unsigned long mapFlags(KeyIteratorFlags flags) { unsigned long res = 0; if (hasFlag(flags, KeyIteratorFlags::AllKeys)) { res |= CODES_KEYS_ITERATOR_ALL_KEYS; } if (hasFlag(flags, KeyIteratorFlags::SkipReadOnly)) { res |= CODES_KEYS_ITERATOR_SKIP_READ_ONLY; } if (hasFlag(flags, KeyIteratorFlags::SkipOptional)) { res |= CODES_KEYS_ITERATOR_SKIP_OPTIONAL; } if (hasFlag(flags, KeyIteratorFlags::SkipEditionSpecific)) { res |= CODES_KEYS_ITERATOR_SKIP_EDITION_SPECIFIC; } if (hasFlag(flags, KeyIteratorFlags::SkipCoded)) { res |= CODES_KEYS_ITERATOR_SKIP_CODED; } if (hasFlag(flags, KeyIteratorFlags::SkipComputed)) { res |= CODES_KEYS_ITERATOR_SKIP_COMPUTED; } if (hasFlag(flags, KeyIteratorFlags::SkipDuplicates)) { res |= CODES_KEYS_ITERATOR_SKIP_DUPLICATES; } if (hasFlag(flags, KeyIteratorFlags::SkipFunction)) { res |= CODES_KEYS_ITERATOR_SKIP_FUNCTION; } return res; } /// Iterate keys on an iterator with a range based for loop KeyRange ConcreteCodesHandle::keys(KeyIteratorFlags flags, std::optional ns) const { return KeyRange{ std::make_unique(*this, std::unique_ptr(codes_keys_iterator_new( raw(), mapFlags(flags), ns ? ns->c_str() : NULL)))}; }; KeyRange ConcreteCodesHandle::keys(Namespace ns) const { return keys(KeyIteratorFlags::AllKeys, ns); }; class ConcreteIteratedGeoData : public GeoIterator { public: // methods ConcreteIteratedGeoData(const ConcreteCodesHandle& handle, std::unique_ptr it) : refHandle_{std::ref(handle)}, it_{std::move(it)}, data_{0.0, 0.0, 0.0}, isValid_{false} { if (hasNext()) { next(); } } virtual ~ConcreteIteratedGeoData() {} /// Returns the name of the key const GeoData& data() const override { return data_; }; bool hasNext() const override { return codes_grib_iterator_has_next(it_.get()) > 0; }; protected: void next() override { isValid_ = codes_grib_iterator_next(it_.get(), &data_.latitude, &data_.longitude, &data_.value) > 0; } bool isValid() const override { return isValid_; } private: std::reference_wrapper refHandle_; std::unique_ptr it_; GeoData data_; bool isValid_; }; /// Iterate ConcreteCodesHandle::values with longitude and latituted GeoRange ConcreteCodesHandle::values() const { int err; GeoRange res{std::make_unique( *this, std::unique_ptr(codes_grib_iterator_new(raw(), 0, &err)))}; throwOnError(err, Here(), "CodesHandle::values()"); return res; }; } // namespace std::string samplesPath() { return codes_samples_path(NULL); } std::string definitionPath() { return codes_definition_path(NULL); } long apiVersion() { return codes_get_api_version(); } std::string gitSha1() { return codes_get_git_sha1(); } std::string gitBranch() { return codes_get_git_branch(); } std::string buildDate() { return codes_get_build_date(); } std::string packageName() { return codes_get_package_name(); } std::unique_ptr codesHandleFromMessage(Span data) { return std::make_unique(std::unique_ptr( codes_handle_new_from_message(NULL, static_cast(data.data()), data.size()))); } std::unique_ptr codesHandleFromMessageCopy(Span data) { return std::make_unique(std::unique_ptr( codes_handle_new_from_message_copy(NULL, static_cast(data.data()), data.size()))); } std::unique_ptr codesHandleFromSample(const std::string& sampleName, std::optional product) { if (product) { switch (*product) { case Product::GRIB: return std::make_unique( std::unique_ptr(codes_grib_handle_new_from_samples(NULL, sampleName.c_str()))); case Product::BUFR: return std::make_unique( std::unique_ptr(codes_bufr_handle_new_from_samples(NULL, sampleName.c_str()))); default: return std::make_unique( std::unique_ptr(codes_handle_new_from_samples(NULL, sampleName.c_str()))); } } return std::make_unique( std::unique_ptr(codes_handle_new_from_samples(NULL, sampleName.c_str()))); } std::unique_ptr codesHandleFromFile(const std::string& fpath, Product product, std::optional offset) { int err = 0; std::unique_ptr ret; FILE* file = fopen(fpath.c_str(), "rb"); // "w" means write mode if (file == nullptr) { throw CodesException(std::string("Error opening file ") + fpath, Here()); } if (offset) { fseek(file, *offset, SEEK_SET); } switch (product) { case Product::GRIB: ret = std::unique_ptr(codes_grib_handle_new_from_file(NULL, file, &err)); break; case Product::BUFR: ret = std::unique_ptr(codes_bufr_handle_new_from_file(NULL, file, &err)); break; }; fclose(file); throwOnError(err, Here(), "codesHandleFromFile(FILE*, Product): "); if (!ret) { throw CodesException("codes_handle_new_from_file returned NULL without an additional error"); } return std::make_unique(std::move(ret)); } using ReadCBCtx = std::pair>, std::exception_ptr>; long readCallBack(void* ctx, void* buffer, long len) { auto& [func, eptr] = *static_cast(ctx); try { auto r = func.get()(static_cast(buffer), len); // API indicates EOF with 0. Codes expects -1 for EOF. return (r == 0) ? -1 : r; } catch (...) { // Capture exception for propagation and indicate error eptr = std::current_exception(); return -2; } } std::unique_ptr codesHandleFromStream(std::function readFunc) { int err = 0; ReadCBCtx ctx{readFunc, {}}; std::unique_ptr ret = std::unique_ptr(codes_handle_new_from_stream(NULL, &ctx, &readCallBack, &err)); if (ctx.second) { std::rethrow_exception(ctx.second); } throwOnError(err, Here(), "codesHandleFromStream(readFunc)"); if (ret) { return std::make_unique(std::move(ret)); } return {}; } std::unique_ptr codesHandleFromGRIBHandle(codes_handle* gribHandle) { if (gribHandle) { return std::make_unique(gribHandle); } return {}; } } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/GeoIterator.h0000664000175000017500000000530115246725757021627 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #pragma once #include "metkit/codes/api/CodesTypes.h" #include #include #include #include #include namespace metkit::codes { //---------------------------------------------------------------------------------------------------------------------- class GeoRange; /// Aggregate that stores a value with lon/lat. struct GeoData { double value; double longitude; double latitude; }; /// Abstract interface wrapping C API calls on on key_iterator. class GeoIterator { friend class GeoRange; public: virtual ~GeoIterator() = default; /// Access to the iterated data. /// @return Reference to the iterated geo data object. virtual const GeoData& data() const = 0; /// Check if there are follow up values. /// @return True if there are more values to iterate on. virtual bool hasNext() const = 0; protected: /// Iterates the next element. virtual void next() = 0; /// Check if the iterator is valid. /// @return True if the iterator is still valid and holds a value. False indicates end of iteration. virtual bool isValid() const = 0; }; //---------------------------------------------------------------------------------------------------------------------- /// Implements the geo iterator (codes_iterator) based on a C++ range. /// Enables use of range-based for-loop. class GeoRange { public: struct EndIterator; struct Iterator { friend struct EndIterator; Iterator& operator++() { state->next(); return *this; } const GeoData& operator*() const { return state->data(); } const GeoData* operator->() const { return &state->data(); } inline bool operator!=(const EndIterator&) { return state->isValid(); } GeoIterator* state; }; struct EndIterator { inline bool operator!=(const Iterator& it) { return it.state->isValid(); } }; Iterator begin() const { return Iterator{impl_.get()}; }; EndIterator end() const { return {}; } GeoRange(std::unique_ptr impl) : impl_{std::move(impl)} {}; private: std::unique_ptr impl_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/KeyIterator.h0000664000175000017500000001353415246725757021654 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #pragma once #include "metkit/codes/api/CodesTypes.h" #include #include #include namespace metkit::codes { /// A namepspace is defined through the definition files and represented as a string. using Namespace = std::string; /// Commonly used eccodes namespacess. namespace namespaces { /// LS namespace contains a collection of descriptive keys as used by `grib_ls`. const Namespace ls{"ls"}; const Namespace parameter{"parameter"}; const Namespace statistics{"statistics"}; const Namespace time{"time"}; const Namespace geography{"geography"}; const Namespace vertical{"vertical"}; /// MARS namespace contains keys used for indexation const Namespace mars{"mars"}; }; // namespace namespaces /// Flag to control/filter key iterator. /// /// Use `AllKeys` to disable filtering. /// Use combination of `Skip*` to filter. enum class KeyIteratorFlags : unsigned int { /// Default - Will iterate all keys in a namespace. AllKeys = 0, SkipReadOnly = 1 << 0, SkipOptional = 1 << 1, SkipEditionSpecific = 1 << 2, SkipCoded = 1 << 3, SkipComputed = 1 << 4, SkipDuplicates = 1 << 5, SkipFunction = 1 << 6, }; KeyIteratorFlags operator|(KeyIteratorFlags a, KeyIteratorFlags b); KeyIteratorFlags operator&(KeyIteratorFlags a, KeyIteratorFlags b); KeyIteratorFlags operator^(KeyIteratorFlags a, KeyIteratorFlags b); KeyIteratorFlags operator~(KeyIteratorFlags a); /// Optional helper to check if a flag is set. bool hasFlag(KeyIteratorFlags value, KeyIteratorFlags flag); //---------------------------------------------------------------------------------------------------------------------- class KeyRange; /// Abstract interface wrapping C API calls on on key_iterator. class KeyIterator { friend class KeyRange; public: virtual ~KeyIterator() = default; /// Get the name of the iterated key. /// @return Name of the iterated key. virtual std::string name() const = 0; /// Get the value of the key. /// /// High-level functionality: /// Inspection on the contained valued is performed with `type` and `size`. /// Then the more specific `getXXX` call is performed. /// @return Variant of all possible value types. virtual CodesValue get() const = 0; /// Get the type of the iterated key. /// @return Type of the iterated key. virtual NativeType type() const = 0; /// Get the contained value for a key as long. /// @return Retrieved value contained for the iterated key. virtual long getLong() const = 0; /// Get the contained value for a key as double. /// @return Retrieved value contained for the iterated key. virtual double getDouble() const = 0; /// Get the contained value for a key as float. /// @return Retrieved value contained for the iterated key. virtual float getFloat() const = 0; /// Get the contained value for a key as string. /// /// This should be possible for all key types. /// @return Retrieved value contained for the iterated key. virtual std::string getString() const = 0; /// Get the contained values for a key as array of long. /// @return Retrieved values contained for the iterated key. virtual std::vector getLongArray() const = 0; /// Get the contained values for a key as array of double. /// @return Retrieved values contained for the iterated key. virtual std::vector getDoubleArray() const = 0; /// Get the contained values for a key as array of float. /// @return Retrieved values contained for the iterated key. virtual std::vector getFloatArray() const = 0; /// Get the contained values for a key as array of string. /// @return Retrieved values contained for the iterated key. virtual std::vector getStringArray() const = 0; /// Get the contained values for a key as array of uint8_t (bytes). /// @return Retrieved values contained for the iterated key. virtual std::vector getBytes() const = 0; protected: /// Iterates the next element. virtual void next() = 0; /// Check if the iterator is valid. /// @return True if the iterator is still valid and holds a value. False indicates end of iteration. virtual bool isValid() const = 0; }; //---------------------------------------------------------------------------------------------------------------------- /// Implements the key iterator (codes_key_iterator) based on a C++ range. /// Enables use of range-based for-loop. class KeyRange { public: struct EndIterator; struct Iterator { friend struct EndIterator; Iterator& operator++() { state->next(); return *this; }; const KeyIterator& operator*() const { return *state; }; const KeyIterator* operator->() const { return state; }; inline bool operator!=(const KeyRange::EndIterator&) { return state->isValid(); } KeyIterator* state; }; struct EndIterator { inline bool operator!=(const KeyRange::Iterator& it) { return it.state->isValid(); } }; Iterator begin() const { return Iterator{impl_.get()}; }; EndIterator end() const { return {}; }; KeyRange(std::unique_ptr impl) : impl_{std::move(impl)} {}; private: std::unique_ptr impl_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/api/CodesAPI.h0000664000175000017500000004577315246725757021013 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #pragma once #include "metkit/codes/api/CodesTypes.h" #include "metkit/codes/api/GeoIterator.h" #include "metkit/codes/api/KeyIterator.h" #include "eccodes.h" #include #include #include #include #include #include /// This file contains the important parts for an eccodes c api wrapper. /// It does not wrap the whole `eccodes.h`. /// The most important interface is the `CodesHandle` - it use to work on specific GRIB or BUFR messages. /// An addition to that, a few factory functions (`codesHandleFromMessage`, `codesHandleFromSample`, /// `codesHandleFromFile`, `codesHandleFromStream`) and iterators are wrapped. /// /// Missing components: /// * codes_context /// * codes_index /// * codes_fieldset /// * codes_values /// * multio_handle /// * codes_nearest /// * GTS handling /// * GRIBEX namespace metkit::codes { //---------------------------------------------------------------------------------------------------------------------- /// Retrieve information about the configured samples path. /// @return The configured path to sample files. std::string samplesPath(); /// Retrieve information about the configured definition path. /// @return The configured path to the definition files. std::string definitionPath(); /// Retrieve eccodes version /// @return The eccodes version. long apiVersion(); /// Retrieve repository information of the built. /// @return The SHA1 of the latest commit the eccodes library has been built on. std::string gitSha1(); /// Retrieve repository information of the built. /// @return The branch the eccodes library has been built on. std::string gitBranch(); /// Retrieve repository information of the built. /// @return The built date of the eccodes library. std::string buildDate(); /// Retrieve repository information of the built. /// @return The package name the library has been distributed with. std::string packageName(); //---------------------------------------------------------------------------------------------------------------------- /// Abstract interface wrapping eccodes handles. /// /// Eccodes exposed `codes_handle*` is used to handle GRIB1, GRIB2 and BUFR data. class CodesHandle { friend class KeyRange; friend class GeoRange; public: virtual ~CodesHandle() = default; /// Check if a key is defined. /// @param key Name of the field that is checked to be defined. /// @return True if the field is defined. virtual bool isDefined(const std::string& key) const = 0; /// Check if a key is missing. /// @param key Name of the field that is checked to be missing. /// @return True if the field is missing. virtual bool isMissing(const std::string& key) const = 0; /// Check if a key is defined and not missing. /// @param key Name of the field that is checked to exist and have a value. /// @return True if the field exists and has a value. virtual bool has(const std::string& key) const = 0; /// Set a key to its missing value. /// @param key Name of the field that is supposed to be set to its missing value. virtual void setMissing(const std::string& key) = 0; /// Set scalar string value for a specific field. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set to. virtual void set(const std::string& key, const std::string& value) = 0; /// Set scalar double value for a specific field. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set to. virtual void set(const std::string& key, double value) = 0; /// Set scalar long value for a specific field. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set to. virtual void set(const std::string& key, long value) = 0; /// Set scalar floating point value for a specific field. /// /// Overload for other floating point types. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set to. template && std::is_floating_point_v, bool> = true> void set(const std::string& key, const T& v) { set(key, static_cast(v)); } /// Set scalar integral value for a specific field. /// /// Overload for other integral types. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set on. template && std::is_integral_v, bool> = true> void set(const std::string& key, const T& v) { set(key, static_cast(v)); } /// Set array of string through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set array of c-string through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set array of double through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set array of float through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set array of long through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set array of uint8_t (bytes) through `span` as general contiguous memory container. /// @param key Name of the field that is supposed to be set. /// @param value Contiguous array the field is supposed to be set to. virtual void set(const std::string& key, Span value) = 0; /// Set any type of value as sum type for a specific field. /// Visits the value and calls the corresponding `set` overload. /// A template and SFINAE is used to match exactly the type `CodesValue`, /// this avoids abiguous overloads through implict convertion. /// @param key Name of the field that is supposed to be set. /// @param value Value the field is supposed to be set to. template , bool> = true> void set(const std::string& key, const T& value) { std::visit([&](const auto& v) { this->set(key, v); }, value); } /// Force setting arrays of double through `span` as general contiguous memory container. /// /// Allows setting arrays that usually get computed through other keys /// @param key Name of the field that is supposed to be set to a specific value. /// @param value Value the field is supposed to be set on. virtual void forceSet(const std::string& key, Span value) = 0; /// Force setting arrays of float through `span` as general contiguous memory container /// /// Allows setting arrays that usually get computed through other keys /// @param key Name of the field that is supposed to be set to a specific value. /// @param value Value the field is supposed to be set on. virtual void forceSet(const std::string& key, Span value) = 0; /// Returns the number of elements contained for a given key. /// /// Can be used to determine if a field is storing a scalar or an array. /// @param key Name of the field that is supposed to be inspected. /// @return For given scalars 1 is returned. For given arrays the size of the array is returned. virtual size_t size(const std::string& key) const = 0; /// Calls `string_length` on the accessor of a field and returns its value. /// /// @param key Name of the field that is supposed to be inspected. /// @return Value of the `string_length` method of the underlying accessor of the key. /// @see ecCodes documentation for more information. virtual size_t length(const std::string& key) const = 0; /// Get the value of the key. /// /// High-level functionality: /// Inspection on the contained valued is performed with `type` and `size`. /// Then the more specific `getXXX` call is performed. /// @param key Name of the field that is supposed to be retrieved. /// @return Variant of all possible value types. virtual CodesValue get(const std::string& key) const = 0; /// Get the type of the key /// @param key Name of the field that is supposed to be inspected. /// @return Enum of the native types ECCODES exposes. virtual NativeType type(const std::string& key) const = 0; /// Get the contained value for a key as long. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved value contained for the passed key. virtual long getLong(const std::string& key) const = 0; /// Get the contained value for a key as double. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved value contained for the passed key. virtual double getDouble(const std::string& key) const = 0; /// Get the contained value for a key as string. /// /// This should be possible for all key types. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved value contained for the passed key. virtual std::string getString(const std::string& key) const = 0; /// Get the contained values for a key as array of long. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved values contained for the passed key. virtual std::vector getLongArray(const std::string& key) const = 0; /// Get the contained values for a key as array of double. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved values contained for the passed key. virtual std::vector getDoubleArray(const std::string& key) const = 0; /// Get the contained values for a key as array of float. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved values contained for the passed key. virtual std::vector getFloatArray(const std::string& key) const = 0; /// Get the contained values for a key as array of string. /// /// This should be possible for all key types. /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved values contained for the passed key. virtual std::vector getStringArray(const std::string& key) const = 0; /// Get the contained values for a key as array of uint8_t (bytes). /// @param key Name of the field that is supposed to be retrieved. /// @return Retrieved values contained for the passed key. virtual std::vector getBytes(const std::string& key) const = 0; /// Clones the underyling handle. /// Uses `codes_handle_clone` internally. /// @return Unique pointer to a cloned `CodesHandle` instance. virtual std::unique_ptr clone() const = 0; /// Copy the message into a new allocated buffer /// @param data Pointer to an allocated array /// @param size Size of the allocated array. /// Should be containing at least the size returned by `messageSize`. virtual void copyInto(uint8_t* data, size_t size) const = 0; /// Retrieve binary size of the handled message. /// @return Size in bytes of the message. virtual size_t messageSize() const = 0; /// Return the pointer to the underlying buffer. /// @return Contiguous array to the underlying buffer as a span. /// No data is copied or owned, lifetime is bound to this handle. /// @see messageSize() /// @throws CodesException on any error returned from eccodes virtual Span messageData() const = 0; /// Iterate keys in a GRIB2 or BUFR handle. /// /// @param flags Iterator flags to filter keys /// @param ns A specific namespace that is iterated (e.g. "mars" or "ls"). /// Namespaces are defined within definition files, /// that means custom namespaces can be provided by users. /// Common namespaces that are available by the default definition files /// are listed in the namespace `codes::namespaces` /// @return A Range that can be iterated on with a range-based for-loop. /// @see KeyIterator virtual KeyRange keys(KeyIteratorFlags flags = KeyIteratorFlags::AllKeys, std::optional ns = std::optional{}) const = 0; /// Iterate keys in a GRIB2 or BUFR handle. /// /// @param ns A specific namespace that is iterated (e.g. "mars" or "ls"). /// Namespaces are defined within definition files, /// that means custom namespaces can be provided by users. /// Common namespaces that are available by the default definition files /// are listed in the namespace `codes::namespaces` /// @return A Range that can be iterated on with a range-based for-loop. /// @see KeyIterator virtual KeyRange keys(Namespace ns) const = 0; /// Iterate values with longitude and latituted. /// /// @return A Range that can be iterated on with a range-based for-loop. /// The iterated value contains a type `GeoData` /// with members `longitude`, `latitude` and `value`. /// @see GeoIterator virtual GeoRange values() const = 0; /// Release the underlying `codes_handle*`. /// /// After calling `release` the instance of this type is in an inoperable state. /// Used to pass ownership out of C++ (e.g. python). /// /// Example use: /// ```cpp /// auto rawHandle = reinterpret_cast(handl->release()); /// ``` /// /// @return Pointer to `codes_handle*` [[nodiscard]] virtual void* release() = 0; }; /// Create a new `CodesHandle` from a byte array. /// /// ECCODES does not copy the array until a modification is made. /// The user needs to maintain the lifetime of the passed array. /// @param data Array to an contiguous array of bytes. /// Lifetime of the array need to be maintained till the first change is made on the handle. /// @return Instance of a `CodesHandle` wrapped in a `unique_ptr`. /// @see codesHandleFromMessageCopy std::unique_ptr codesHandleFromMessage(Span data); /// Create a new `CodesHandle` from a byte array. /// /// ECCODES copies the array. No lifetime requirements exist on `data`. /// @param data Array to an contiguous array of bytes the message is created from. /// @return Instance of a `CodesHandle` wrapped in a `unique_ptr`. /// @see codesHandleFromMessage std::unique_ptr codesHandleFromMessageCopy(Span data); /// Create a new `CodesHandle` from a sample existing in the configured samples path. /// /// ECCODES does not copy the array until a modification is made. /// The user needs to maintain the lifetime of the passed array. /// @param sample Name of the sample in the sample path. Usually without the ".tmpl" suffix, e.g. "GRIB2". /// @param product The intented type of handle that is supposed to be loaded (BUFR or GRIB). /// Does not need to be specified. /// @return Instance of a `CodesHandle` wrapped in a `unique_ptr`. /// @see samplesPath std::unique_ptr codesHandleFromSample(const std::string& sampleName, std::optional product = std::optional{}); /// Create a new `CodesHandle` from a file. /// /// ECCODES does not copy the array until a modification is made. /// The user needs to maintain the lifetime of the passed array. /// @param file Pointer to a implementation dependent file handle containing a BUFR or GRIB message. /// @param product The intented type of handle that is supposed to be loaded (BUFR or GRIB). /// @param offset Optional: An offset in bytes within the file where the actual message starts. /// @return Instance of a `CodesHandle` wrapped in a `unique_ptr`. std::unique_ptr codesHandleFromFile(const std::string& fpath, Product, std::optional offset = std::optional{}); /// Create a new `CodesHandle` from a stream /// /// The user needs to maintain the lifetime of the passed array. /// @param readFunc Function that accepts a buffer as uint8_t* and its length as int64_t. /// The function can fill the buffer and return the number of bytes filled/consumed. /// Exceptions thrown in the function are propagated. /// A return value of 0 indicates an stream end (EOF) and will result in a nullptr without an error. /// A negative return value indicates an error - in that case it is smarter to throw an exception. /// @return Instance of a `CodesHandle` wrapped in a `unique_ptr`. Nullptr on stream end. /// @throws CodesWrongLength if the message is corrupted, CodesException on any other error /// returned from eccodes, any expected propagated from readFunc. std::unique_ptr codesHandleFromStream(std::function readFunc); /// Temporary workaround. /// /// This function is a transitional API introduced to unblock the current /// implementation. It is intentionally not part of the intended long-term /// design and should be removed as soon as the underlying workflow is /// refactored. /// /// The returned wrapper borrows `gribHandle` without taking ownership. /// The caller keeps ownership of `gribHandle` and needs to maintain its /// lifetime for the whole lifetime of the returned `CodesHandle`. /// @param gribHandle Pointer to a `codes_handle` that is a GRIB handle. /// @return Instance of a `CodesHandle` wrapped in a `std::unique_ptr`. std::unique_ptr codesHandleFromGRIBHandle(codes_handle* gribHandle); //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/codes/CodesDataContent.h0000664000175000017500000000436515246725757022025 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ #pragma once #include "eckit/io/Offset.h" #include "metkit/codes/api/CodesAPI.h" #include "eckit/message/MessageContent.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class CodesDataContent : public eckit::message::MessageContent { public: CodesDataContent(std::unique_ptr handle, eckit::Offset offset); CodesDataContent(std::unique_ptr handle); virtual ~CodesDataContent() = default; const CodesHandle& codesHandle() const; CodesHandle& codesHandle(); protected: std::unique_ptr handle_; eckit::Offset offset_; using eckit::message::MessageContent::transform; void transform(const eckit::OrderedStringDict&) override; private: size_t length() const override; void write(eckit::DataHandle& handle) const override; eckit::DataHandle* readHandle() const override; void print(std::ostream& s) const override; std::string getString(const std::string& key) const override; long getLong(const std::string& key) const override; double getDouble(const std::string& key) const override; void getDoubleArray(const std::string& key, std::vector& values) const override; void getFloatArray(const std::string& key, std::vector& values) const override; size_t getSize(const std::string& key) const override; void getDoubleArray(const std::string& key, double* data, size_t lenExpected) const override; void getFloatArray(const std::string& key, float* data, size_t lenExpected) const override; eckit::Offset offset() const override; const void* data() const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/codes/LibEccodes.h0000664000175000017500000000177615246725757020642 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Olivier Iffrig /// @date October 2019 #pragma once #include "eckit/system/Library.h" namespace metkit { //---------------------------------------------------------------------------------------------------------------------- class LibEccodes : public eckit::system::Library { public: LibEccodes(); static const LibEccodes& instance(); protected: std::string version() const override; std::string gitsha1(unsigned int count) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit metkit-1.20.2/src/metkit/api/0000775000175000017500000000000015246725757016136 5ustar alastairalastairmetkit-1.20.2/src/metkit/api/metkit_c.cc0000664000175000017500000002621315246725757020250 0ustar alastairalastair#include "metkit_c.h" #include #include "eckit/runtime/Main.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsRequest.h" #include "metkit/metkit_version.h" // --------------------------------------------------------------------------------------------------------------------- struct metkit_marsrequest_t : public metkit::mars::MarsRequest { using metkit::mars::MarsRequest::MarsRequest; metkit_marsrequest_t(metkit::mars::MarsRequest&& req) : metkit::mars::MarsRequest(std::move(req)) {} }; struct metkit_requestiterator_t { explicit metkit_requestiterator_t(std::vector&& vec) : vector_(std::move(vec)), current_(vector_.begin()) {} metkit_iterator_status_t next() { if (first_) { first_ = false; if (current_ != vector_.end()) { return METKIT_ITERATOR_SUCCESS; } } if (current_ == vector_.end()) { return METKIT_ITERATOR_COMPLETE; } ++current_; return current_ == vector_.end() ? METKIT_ITERATOR_COMPLETE : METKIT_ITERATOR_SUCCESS; } // Note: expected to call next() before calling current() metkit_iterator_status_t current(metkit_marsrequest_t* out) { if (first_ || current_ == vector_.end()) { return METKIT_ITERATOR_ERROR; } *out = std::move(*current_); return METKIT_ITERATOR_SUCCESS; } private: bool first_ = true; std::vector vector_; std::vector::iterator current_; }; // --------------------------------------------------------------------------------------------------------------------- /// @note: Unlike metkit_requestiterator_t, lifetime of the char* returned from this iterator are have the same as the /// iterator's lifetime. struct metkit_paramiterator_t { metkit_paramiterator_t(std::vector vec) : vector_(vec), current_(vector_.begin()) {} metkit_iterator_status_t next() { if (first_) { first_ = false; if (current_ != vector_.end()) { return METKIT_ITERATOR_SUCCESS; } } if (current_ == vector_.end()) { return METKIT_ITERATOR_COMPLETE; } ++current_; return current_ == vector_.end() ? METKIT_ITERATOR_COMPLETE : METKIT_ITERATOR_SUCCESS; } // Note: expected to call next() before calling current() metkit_iterator_status_t current(const char** out) const { if (first_ || current_ == vector_.end()) { return METKIT_ITERATOR_ERROR; } *out = current_->c_str(); return METKIT_ITERATOR_SUCCESS; } bool first_ = true; std::vector vector_; std::vector::const_iterator current_; }; // --------------------------------------------------------------------------------------------------------------------- // ERROR HANDLING static thread_local std::string g_current_error_string; const char* metkit_get_error_string(metkit_error_t err) { switch (err) { case METKIT_SUCCESS: return "Success"; case METKIT_ERROR: case METKIT_ERROR_USER: case METKIT_ERROR_ASSERT: return g_current_error_string.c_str(); default: return ""; } } metkit_error_t innerWrapFn(std::function f) { return f(); } metkit_error_t innerWrapFn(std::function f) { f(); return METKIT_SUCCESS; } template [[nodiscard]] metkit_error_t tryCatch(FN&& fn) { try { return innerWrapFn(std::forward(fn)); } catch (const eckit::UserError& e) { g_current_error_string = e.what(); return METKIT_ERROR_USER; } catch (const eckit::AssertionFailed& e) { g_current_error_string = e.what(); return METKIT_ERROR_ASSERT; } catch (const eckit::Exception& e) { g_current_error_string = e.what(); return METKIT_ERROR; } catch (const std::exception& e) { g_current_error_string = e.what(); return METKIT_ERROR_UNKNOWN; } catch (...) { return METKIT_ERROR_UNKNOWN; } } // ----------------------------------------------------------------------------- // HELPERS // ----------------------------------------------------------------------------- metkit_error_t metkit_initialise() { return tryCatch([] { static bool initialised = false; if (initialised) { eckit::Log::warning() << "Initialising Metkit library twice" << std::endl; } if (!initialised) { const char* argv[2] = {"metkit-api", nullptr}; eckit::Main::initialise(1, const_cast(argv)); initialised = true; } }); } // ----------------------------------------------------------------------------- // PARSING // ----------------------------------------------------------------------------- metkit_error_t metkit_parse_marsrequests(const char* str, metkit_requestiterator_t** requests, bool strict) { return tryCatch([requests, str, strict] { ASSERT(requests); ASSERT(str); std::istringstream in(str); *requests = new metkit_requestiterator_t(metkit::mars::MarsRequest::parse(in, strict)); }); } metkit_error_t metkit_parse_marsrequest(const char* str, metkit_marsrequest_t* request, bool strict) { return tryCatch([request, str, strict] { ASSERT(request); ASSERT(str); *request = metkit::mars::MarsRequest::parse(str, strict); }); } // ----------------------------------------------------------------------------- // REQUEST // ----------------------------------------------------------------------------- metkit_error_t metkit_marsrequest_new(metkit_marsrequest_t** request) { return tryCatch([request] { ASSERT(request); *request = new metkit_marsrequest_t(); }); } metkit_error_t metkit_marsrequest_delete(const metkit_marsrequest_t* request) { return tryCatch([request] { delete request; }); } metkit_error_t metkit_marsrequest_set(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues) { return tryCatch([request, param, values, numValues] { ASSERT(request); ASSERT(param); ASSERT(values); std::string param_str(param); std::vector values_vec; values_vec.reserve(numValues); std::copy(values, values + numValues, std::back_inserter(values_vec)); request->values(param_str, values_vec); }); } metkit_error_t metkit_marsrequest_set_one(metkit_marsrequest_t* request, const char* param, const char* value) { return metkit_marsrequest_set(request, param, &value, 1); } metkit_error_t metkit_marsrequest_set_verb(metkit_marsrequest_t* request, const char* verb) { return tryCatch([request, verb] { ASSERT(request); ASSERT(verb); request->verb(verb); }); } metkit_error_t metkit_marsrequest_verb(const metkit_marsrequest_t* request, const char** verb) { return tryCatch([request, verb] { ASSERT(request); ASSERT(verb); *verb = request->verb().c_str(); }); } metkit_error_t metkit_marsrequest_has_param(const metkit_marsrequest_t* request, const char* param, bool* has) { return tryCatch([request, param, has] { ASSERT(request); ASSERT(param); ASSERT(has); *has = request->has(param); }); } metkit_error_t metkit_marsrequest_params(const metkit_marsrequest_t* request, metkit_paramiterator_t** params) { return tryCatch([request, params] { ASSERT(request); ASSERT(params); *params = new metkit_paramiterator_t(request->params()); }); } metkit_error_t metkit_marsrequest_count_values(const metkit_marsrequest_t* request, const char* param, size_t* count) { return tryCatch([request, param, count] { ASSERT(request); ASSERT(param); ASSERT(count); *count = request->countValues(param); }); } metkit_error_t metkit_marsrequest_value(const metkit_marsrequest_t* request, const char* param, int index, const char** value) { return tryCatch([request, param, index, value] { ASSERT(request); ASSERT(param); ASSERT(value); *value = request->values(param, false)[index].c_str(); }); } metkit_error_t metkit_marsrequest_expand(const metkit_marsrequest_t* request, bool inherit, bool strict, metkit_marsrequest_t* expandedRequest) { return tryCatch([request, expandedRequest, inherit, strict] { ASSERT(request); ASSERT(expandedRequest); ASSERT(expandedRequest->empty()); metkit::mars::MarsExpansion expand(inherit, strict); *expandedRequest = expand.expand(*request); }); } metkit_error_t metkit_marsrequest_merge(metkit_marsrequest_t* request, const metkit_marsrequest_t* otherRequest) { return tryCatch([request, otherRequest] { ASSERT(request); ASSERT(otherRequest); request->merge(*otherRequest); }); } void metkit_string_delete(const char* str) { delete[] str; } // ----------------------------------------------------------------------------- // REQUEST ITERATOR // ----------------------------------------------------------------------------- metkit_error_t metkit_requestiterator_delete(const metkit_requestiterator_t* it) { return tryCatch([it] { delete it; }); } metkit_iterator_status_t metkit_requestiterator_next(metkit_requestiterator_t* it) { if (!it) { return METKIT_ITERATOR_ERROR; } return it->next(); } metkit_iterator_status_t metkit_requestiterator_current(metkit_requestiterator_t* it, metkit_marsrequest_t* request) { if (!it || !request || !request->empty()) { return METKIT_ITERATOR_ERROR; } return it->current(request); } // ----------------------------------------------------------------------------- // PARAM ITERATOR // ----------------------------------------------------------------------------- metkit_error_t metkit_paramiterator_delete(const metkit_paramiterator_t* it) { return tryCatch([it] { delete it; }); } metkit_iterator_status_t metkit_paramiterator_next(metkit_paramiterator_t* it) { if (!it) { return METKIT_ITERATOR_ERROR; } return it->next(); } metkit_iterator_status_t metkit_paramiterator_current(const metkit_paramiterator_t* it, const char** param) { if (!it || !param) { return METKIT_ITERATOR_ERROR; } return it->current(param); } // --------------------------------------------------------------------------------------------------------------------- // Bridge between C and C++ const metkit::mars::MarsRequest& metkit::mars::MarsRequest::fromOpaque(const metkit_marsrequest_t* request) { return *static_cast(request); } // --------------------------------------------------------------------------------------------------------------------- metkit-1.20.2/src/metkit/api/metkit_c.h0000664000175000017500000002261715246725757020116 0ustar alastairalastair#pragma once #include #include #ifdef __cplusplus extern "C" { #endif /* --------------------------------------------------------------------------------------------------------------------- * TYPES * -----*/ struct metkit_marsrequest_t; typedef struct metkit_marsrequest_t metkit_marsrequest_t; struct metkit_requestiterator_t; /** RequestIterator for iterating over vector of Request instances */ typedef struct metkit_requestiterator_t metkit_requestiterator_t; struct metkit_paramiterator_t; /** Iterator for iterating over parameters in Request */ typedef struct metkit_paramiterator_t metkit_paramiterator_t; /* --------------------------------------------------------------------------------------------------------------------- * ERROR HANDLING * -------------- */ typedef enum metkit_error_values_t { METKIT_SUCCESS = 0, /* Operation succeded. */ METKIT_ERROR = 1, /* Operation failed. */ METKIT_ERROR_UNKNOWN = 2, /* Failed with an unknown error. */ METKIT_ERROR_USER = 3, /* Failed with an user error. */ METKIT_ERROR_ASSERT = 4 /* Failed with an assert() */ } metkit_error_t; typedef enum metkit_iterator_status_t { METKIT_ITERATOR_SUCCESS = 0, /* Operation succeded. */ METKIT_ITERATOR_COMPLETE = 1, /* All elements have been returned */ METKIT_ITERATOR_ERROR = 2 /* Operation failed. */ } metkit_iterator_status_t; const char* metkit_get_error_string(enum metkit_error_values_t err); /* ----------------------------------------------------------------------------- * HELPERS * ------- */ /** * @brief Get metkit version. * * @return const char* version string */ const char* metkit_version(); /** * @brief Get metkit git sha1 version. * * @return const char* git sha1 version string */ const char* metkit_git_sha1(); /** * @brief Initialise Main() context. * * @note This is ONLY required when Main() is NOT initialised, such as loading * the MetKit as shared library in Python. * @return metkit_error_t Error code */ metkit_error_t metkit_initialise(); /* --------------------------------------------------------------------------------------------------------------------- * PARSING * --- */ /** * Parse MARS requests into RequestIterator of Request instances. Resulting RequestIterator * must be deallocated with metkit_delete_requestiterator * @param str MARS requests * @param[out] requests Allocates RequestIterator object * @return metkit_error_t Error code */ metkit_error_t metkit_parse_marsrequests(const char* str, metkit_requestiterator_t** requests, bool strict); /** * Parse MARS request into Request instance * @note will error if request expands to multiple requests: use metkit_parse_marsrequests instead * @param str MARS request * @param[out] request Request instance * @param strict if true, raise error rather than warning on invalid values * @return metkit_error_t Error code */ metkit_error_t metkit_parse_marsrequest(const char* str, metkit_marsrequest_t* request, bool strict); /* --------------------------------------------------------------------------------------------------------------------- * REQUEST * --- */ /** Allocates new Request object. Must be deallocated with mekit_delete_request * @param[out] request new Request instance * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_new(metkit_marsrequest_t** request); /** Deallocates Request object and associated resources. * @param request Request instance * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_delete(const metkit_marsrequest_t* request); /** Add parameter and values to request * @param request Request instance * @param param parameter name * @param values array of values for parameter * @param numValues number of values * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_set(metkit_marsrequest_t* request, const char* param, const char* values[], int numValues); /** Add parameter and values to request * @param request Request instance * @param param parameter name * @param values value to add * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_set_one(metkit_marsrequest_t* request, const char* param, const char* value); /** Set verb in Request object * @param request Request instance * @param verb verb to set * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_set_verb(metkit_marsrequest_t* request, const char* verb); /** Returns the verb in Request object * @param request Request instance * @param[out] verb verb in request * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_verb(const metkit_marsrequest_t* request, const char** verb); /** Returns whether parameter is in Request object * @param request Request instance * @param param parameter name * @param[out] has whether parameter exists in request * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_has_param(const metkit_marsrequest_t* request, const char* param, bool* has); /** Returns parameter iterator for Request object * Must be deallocated with metkit_paramiterator_delete * @note: The strings obtained from next() are owned by the iterator and should be copied if they need to outlive it. * @param request Request instance * @param[out] params parameter iterator * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_params(const metkit_marsrequest_t* request, metkit_paramiterator_t** params); /** Returns number of values for specific parameter in Request object * @param request Request instance * @param param parameter name in request * @param[out] count number of values for param in request * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_count_values(const metkit_marsrequest_t* request, const char* param, size_t* count); /** Returns value for specific parameter and index in Request object * @param request Request instance * @param param parameter name in request * @param index index of value to retrieve for param in request * @param[out] value retrieved value * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_value(const metkit_marsrequest_t* request, const char* param, int index, const char** value); /** Populates empty Request object by expanding existing request * @param request Request instance to be expanded * @param inherit if true, populate expanded request with default values * @param strict it true, raise error rather than warning on invalid values * @param[out] expandedRequest empty Request instance to be populated * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_expand(const metkit_marsrequest_t* request, bool inherit, bool strict, metkit_marsrequest_t* expandedRequest); /** Merges other Request object into existing request * @param request Request instance to contain result of merge * @param otherRequest other Request instance to merge * @return metkit_error_t Error code */ metkit_error_t metkit_marsrequest_merge(metkit_marsrequest_t* request, const metkit_marsrequest_t* otherRequest); /* --------------------------------------------------------------------------------------------------------------------- * REQUEST ITERATOR * --- */ /** Deallocates RequestIterator object and associated resources. * @param it RequestIterator instance * @return metkit_error_t Error code */ metkit_error_t metkit_requestiterator_delete(const metkit_requestiterator_t* it); /** Moves to the next Request element in RequestIterator * @param it RequestIterator instance * @return metkit_iterator_status_t Status of iterator */ // metkit_error_t metkit_requestiterator_next(metkit_requestiterator_t* it); metkit_iterator_status_t metkit_requestiterator_next(metkit_requestiterator_t* it); /** Populates empty Request object with data from current element in RequestIterator * @param it RequestIterator instance * @param request empty Request instance to populate with data * @return metkit_iterator_status_t Status of iterator * @note must call metkit_requestiterator_next before calling metkit_requestiterator_current. * * Example usage: * while (metkit_requestiterator_next(it) == METKIT_ITERATOR_SUCCESS) { * metkit_marsrequest_t* req{}; * metkit_marsrequest_new(&req); * metkit_requestiterator_current(it, req); * // use req ... * } */ metkit_iterator_status_t metkit_requestiterator_current(metkit_requestiterator_t* it, metkit_marsrequest_t* request); /* --------------------------------------------------------------------------------------------------------------------- * PARAMETER ITERATOR * --- */ /** Deallocates ParamIterator object and the char* strings it owns. * @param it ParamIterator instance * @return metkit_error_t Error code */ metkit_error_t metkit_paramiterator_delete(const metkit_paramiterator_t* it); /** Moves to the next parameter in ParamIterator * @param it ParamIterator instance * @return metkit_iterator_status_t Status of iterator */ metkit_iterator_status_t metkit_paramiterator_next(metkit_paramiterator_t* it); /** Returns current parameter in ParamIterator * @param it ParamIterator instance * @param[out] param current parameter * @return metkit_iterator_status_t Status of iterator */ metkit_iterator_status_t metkit_paramiterator_current(const metkit_paramiterator_t* it, const char** param); #ifdef __cplusplus } #endif metkit-1.20.2/src/metkit/mars2mars/0000775000175000017500000000000015246725757017274 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/utils/0000775000175000017500000000000015246725757020434 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/utils/paramMatcher.h0000664000175000017500000000257615246725757023223 0ustar alastairalastair/* * (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 paramMatcher.h /// @brief Helpers for compact parameter matching in rule tables. #pragma once namespace metkit::mars2mars::util::param_matcher { /// @brief Inclusive integer range used by parameter predicates. struct Range { int first; int last; /// @brief Check whether a value lies inside the range. bool contains(int x) const { return x >= first && x <= last; } }; /// @brief Construct an inclusive integer range. inline Range range(int first, int last) { return {first, last}; } /// @brief Match a value against a range argument. inline bool matchSingle(int x, const Range& arg) { return arg.contains(x); } /// @brief Match a value against a scalar argument. inline bool matchSingle(int x, int y) { return x == y; } /// @brief Match a value against any supplied argument. template bool matchAny(int value, T... arg) { return (matchSingle(value, arg) || ...); } } // namespace metkit::mars2mars::util::param_matcher metkit-1.20.2/src/metkit/mars2mars/utils/logUtils.h0000664000175000017500000000534015246725757022411 0ustar alastairalastair/// @file logUtils.h /// @brief Logging macros used by mars2mars. #pragma once #include "eckit/log/Log.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2mars/utils/generalUtils.h" /// @brief Log a validation or guard check. #define MARS2MARS_LOG_CHECK(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-CHECK: " << msg << std::endl; \ } while (0) /// @brief Log a successful match. #define MARS2MARS_LOG_MATCH(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-MATCH: " << msg << std::endl; \ } while (0) /// @brief Log a resolved value. #define MARS2MARS_LOG_RESOLVE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-RESOLVE: " << msg << std::endl; \ } while (0) /// @brief Log an overridden value. #define MARS2MARS_LOG_OVERRIDE(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-OVERRIDE: " << msg << std::endl; \ } while (0) /// @brief Log a defaulted value. #define MARS2MARS_LOG_DEFAULT(msg) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-DEFAULT: " << msg << std::endl; \ } while (0) /// @brief Log a concept invocation. #define MARS2MARS_LOG_CONCEPT(CONCEPTNAME) \ do { \ LOG_DEBUG_LIB(LibMetkit) << " MARS2MARS-CONCEPT:" \ << "[Concept " << std::string(CONCEPTNAME##Name) << "] " \ << "Op called: " \ << "Stage=" << Stage << ", " \ << "Section=" << Section << ", " \ << "Variant=" << std::string(CONCEPTNAME##TypeName()) << std::endl; \ } while (0) metkit-1.20.2/src/metkit/mars2mars/utils/mars2marsExceptions.h0000664000175000017500000001303515246725757024560 0ustar alastairalastair/* * (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 mars2marsExceptions.h /// @brief Unified exception hierarchy for the mars2mars framework. /// /// This header defines the complete exception model used across /// mars2mars, covering: /// /// - Generic infrastructure errors /// - Layer-specific failures (matcher, rules, validation, tables, deduction) /// - Concept execution failures (with contextual metadata) /// - Encoder failures (with serialized dictionary state) /// /// The hierarchy is designed with the following goals: /// /// - Strong contextual diagnostics /// - Support for nested exception propagation /// - Structured debug frame printing /// - Clear separation between backend and frontend layers /// /// All exceptions ultimately derive from `eckit::Exception`, /// ensuring compatibility with the broader ECMWF ecosystem. /// /// Nested exception support allows propagation chains to be /// printed in a structured stack-like format. /// /// @ingroup mars2mars_utils #pragma once // System includes #include #include #include #include // Project includes #include "eckit/exception/Exceptions.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2mars/utils/generalUtils.h" namespace metkit::mars2mars::utils::exceptions { /// @brief Base exception for mars2mars. /// /// This is the root exception type for most mars2mars failures. /// It: /// /// - Inherits from `eckit::Exception` /// - Supports nested exceptions via `std::nested_exception` /// - Provides structured frame printing /// /// Derived exceptions typically extend this class with /// additional contextual metadata. /// /// The `printFrame()` method is designed to be used by /// extended stack printers. class Mars2marsGenericException : public eckit::Exception, public std::nested_exception { public: Mars2marsGenericException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} virtual ~Mars2marsGenericException() = default; virtual void printFrame(const std::string& pad, std::ostream& os) const { const auto& loc = location(); os << pad << "+ file: " << loc.file() << "\n" << pad << "+ function: " << loc.func() << "\n" << pad << "+ line: " << loc.line() << "\n" << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" << pad << "+ message: " << what() << "\n"; } }; /// @brief Exception raised in the rules layer. /// /// Used when evaluating rule-based logic fails. /// /// This class derives directly from `eckit::Exception` and /// supports nested exceptions. class Mars2marsRulesException : public eckit::Exception, public std::nested_exception { public: Mars2marsRulesException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : eckit::Exception(reason, loc) {} }; /// @brief Exception raised in the dictionary access layer. /// /// Used when dictionary validation or access fails. /// Inherits structured printing from the generic exception. class Mars2marsDictException : public Mars2marsGenericException { public: Mars2marsDictException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : Mars2marsGenericException(reason, loc) {} }; inline constexpr int tabSize = 4; inline constexpr int lineSize = 120; inline std::string indent(std::size_t level) { return std::string(level * tabSize, ' '); } /// @brief Print structured exception stack with detailed frames. /// /// For each nested exception frame: /// /// - Prints file, function, line, and message (if available) /// - Prints additional metadata for specialized exceptions /// /// This function detects `Mars2marsGenericException` /// via `dynamic_cast` and calls `printFrame()` /// to extract structured information. /// /// Nested exceptions are recursively printed. /// /// @param e Root exception /// @param level Indentation level /// @param frame Frame counter inline void printExtendedStack(const std::exception& e, std::ostream& os, std::size_t level = 0, std::size_t frame = 1) { const std::string pad = indent(level); os << pad << "+ " << std::string(lineSize, '=') << std::endl << pad << "+ frame " << frame << std::endl << pad << "+ " << std::string(lineSize, '-') << std::endl; if (const auto* me = dynamic_cast(&e)) { me->printFrame(pad, os); } else { os << pad << "+ message: " << e.what() << std::endl; } os << pad << "+ " << std::string(lineSize, '+') << std::endl; const auto* nested = dynamic_cast(&e); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nestedException) { printExtendedStack(nestedException, os, level + 1, frame + 1); } catch (...) { os << std::string(level + 1, ' ') << "[nested non-std exception]" << std::endl; } } } // namespace metkit::mars2mars::utils::exceptions metkit-1.20.2/src/metkit/mars2mars/utils/generalUtils.h0000664000175000017500000000027715246725757023251 0ustar alastairalastair/// @file generalUtils.h /// @brief Small utility macros shared by mars2mars. #pragma once /// @brief Mark a code path as unreachable. #define mars2marsUnreachable() __builtin_unreachable() metkit-1.20.2/src/metkit/mars2mars/utils/type_traits_name.h0000664000175000017500000001057515246725757024164 0ustar alastairalastair/* * (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 type_traits_name.h /// @brief Human-readable type names used in diagnostics. #pragma once #include #include #include #include #include #include #include "metkit/codes/api/CodesTypes.h" #include "metkit/mars2mars/utils/generalUtils.h" namespace metkit::mars2mars::utils { /// @brief Return a readable name for `T`. // ============================================================ // Primary template (deferred to fallback demangling) // ============================================================ template constexpr std::string_view type_name(); // ============================================================ // Base type specializations // ============================================================ template <> constexpr std::string_view type_name() { return "string"; } template <> constexpr std::string_view type_name() { return "bool"; } template <> constexpr std::string_view type_name() { return "char"; } template <> constexpr std::string_view type_name() { return "signed char"; } template <> constexpr std::string_view type_name() { return "unsigned char"; } template <> constexpr std::string_view type_name() { return "int"; } template <> constexpr std::string_view type_name() { return "long"; } template <> constexpr std::string_view type_name() { return "long long"; } template <> constexpr std::string_view type_name() { return "unsigned int"; } template <> constexpr std::string_view type_name() { return "unsigned long"; } template <> constexpr std::string_view type_name() { return "unsigned long long"; } template <> constexpr std::string_view type_name() { return "float"; } template <> constexpr std::string_view type_name() { return "double"; } template <> constexpr std::string_view type_name() { return "long double"; } // ============================================================ // std::vector specializations // ============================================================ template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } template <> constexpr std::string_view type_name>() { return "vector_view"; } } // namespace metkit::mars2mars::utils metkit-1.20.2/src/metkit/mars2mars/utils/dictionary_traits/0000775000175000017500000000000015246725757024167 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/utils/dictionary_traits/dictaccess_eckit_configuration.h0000664000175000017500000005371215246725757032563 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/utils/generalUtils.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/type_traits_name.h" // ============================================================================ // Helper macros to reduce boilerplate (similar to LocalConfiguration version) // ============================================================================ // ============================================================== // GET_OR_THROW // ============================================================== #define M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOrThrow { \ \ static CTYPE get_or_throw(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ /* Check key exists */ \ if (!cfg.has(k)) { \ throw exceptions::Mars2marsDictException("Missing key `"s + k + "` in dictionary type `"s + \ std::string(type_name()) + \ "`", \ Here()); \ } \ \ /* Check type */ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ throw exceptions::Mars2marsDictException( \ "Key `"s + k + "` is not of expected type `"s + std::string(type_name()) + \ "` for dictionary type `"s + std::string(type_name()) + "`", \ Here()); \ } \ } \ catch (const exceptions::Mars2marsGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2marsDictException( \ "Internal error while reading key `"s + k + "` as `" + std::string(type_name()) + \ "` from dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2marsUnreachable(); \ } \ }; // ============================================================== // GET_OPT // ============================================================== #define M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(CTYPE, ISFUNC, GETFUNC) \ template <> \ struct DictGetOpt { \ static std::optional get_opt(const eckit::LocalConfiguration& cfg, \ std::string_view key) noexcept(false) { \ const std::string k{key}; \ \ try { \ if (!cfg.has(k)) { \ return std::nullopt; \ } \ \ if (hacks::ISFUNC(cfg, k)) { \ return cfg.GETFUNC(k); \ } \ else { \ return std::nullopt; \ } \ } \ catch (...) { \ return std::nullopt; \ } \ mars2marsUnreachable(); \ } \ }; // ============================================================== // SET_OR_THROW // ============================================================== #define M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(CTYPE, SETFUNC) \ template <> \ struct DictSetOrIgnore { \ static void set_or_ignore(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (...) { \ } \ mars2marsUnreachable(); \ } \ }; // ============================================================== // SET_OR_IGNORE // ============================================================== #define M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(CTYPE, SETFUNC) \ template <> \ struct DictSetOrThrow { \ \ static void set_or_throw(eckit::LocalConfiguration& cfg, std::string_view key, \ const CTYPE& value) noexcept(false) { \ const std::string k{key}; \ \ try { \ cfg.SETFUNC(k, value); \ return; \ } \ catch (const exceptions::Mars2marsGenericException&) { \ throw; \ } \ catch (...) { \ std::throw_with_nested(exceptions::Mars2marsDictException( \ "Unable to set key `"s + k + "` with type `"s + std::string(type_name()) + \ "` in dictionary type `"s + std::string(type_name()) + "`", \ Here())); \ } \ mars2marsUnreachable(); \ } \ }; namespace metkit::mars2mars::utils { // ----------------------------------------------------------------------------- // type_name specialisation // ----------------------------------------------------------------------------- template <> constexpr std::string_view type_name() { return "eckit::LocalConfiguration"; } template <> constexpr std::string_view type_name>() { return "vector"; } } // namespace metkit::mars2mars::utils namespace metkit::mars2mars::utils::dict_traits { using std::operator""s; namespace hacks { inline bool isIntegral(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegral(std::string{key}); } inline bool isFloatingPoint(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPoint(std::string{key}) || conf.isIntegral(std::string{key}); } inline bool isBoolean(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isBoolean(std::string{key}); } inline bool isString(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isString(std::string{key}); } inline bool isSubConfiguration(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfiguration(std::string{key}); } inline bool isIntegralList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isIntegralList(std::string{key}); } inline bool isFloatingPointList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isFloatingPointList(std::string{key}) || conf.isIntegralList(std::string{key}); } inline bool isStringList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isStringList(std::string{key}); } inline bool isSubConfigurationList(const eckit::LocalConfiguration& conf, std::string_view key) { return conf.isSubConfigurationList(std::string{key}); } } // namespace hacks // ----------------------------------------------------------------------------- // to_json // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const eckit::LocalConfiguration& cfg) noexcept(true) { try { std::ostringstream os; os << cfg; return os.str(); } catch (...) { return "[to_json failed for eckit::LocalConfiguration]"; } } }; // ----------------------------------------------------------------------------- // DictCreateFromSample / Clone / NeedsChecks // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view name) { auto cfg = std::make_unique(); cfg->set("SampleName", std::string(name)); return cfg; } static std::unique_ptr clone_or_throw(const eckit::LocalConfiguration& cfg) { return std::make_unique(cfg); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { const std::string k{key}; try { return cfg.has(k); } catch (const exceptions::Mars2marsGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Mars2marsDictException( "Internal error while checking presence of key `"s + k + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2marsUnreachable(); } }; // ----------------------------------------------------------------------------- // DictMissing // ----------------------------------------------------------------------------- template <> struct DictMissing { static bool isMissing(const eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { const std::string k{key}; try { return !cfg.has(k); } catch (const exceptions::Mars2marsGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Mars2marsDictException( "Internal error while checking absence of key `"s + k + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2marsUnreachable(); } static void setMissing(eckit::LocalConfiguration& cfg, std::string_view key) noexcept(false) { const std::string k{key}; cfg.remove(k); try { cfg.remove(k); } catch (const exceptions::Mars2marsGenericException&) { throw; } catch (...) { std::throw_with_nested(exceptions::Mars2marsDictException( "Internal error while removing key `"s + k + "` in dictionary type `"s + std::string(type_name()) + "`", Here())); } mars2marsUnreachable(); } }; // ============================================================ // eckit::LocalConfiguration specializations via macros // ============================================================ //------------------------------------------------------------------------------ // Scalar types //------------------------------------------------------------------------------ // bool M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(bool, isBoolean, getBool) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(bool, isBoolean, getBool) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(bool, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(bool, set) // int M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(int, isIntegral, getInt) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(int, isIntegral, getInt) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(int, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(int, set) // long M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(long, isIntegral, getLong) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(long, isIntegral, getLong) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(long, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(long, set) // float M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(float, isFloatingPoint, getFloat) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(float, isFloatingPoint, getFloat) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(float, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(float, set) // double M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(double, isFloatingPoint, getDouble) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(double, isFloatingPoint, getDouble) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(double, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(double, set) // std::string M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::string, isString, getString) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::string, isString, getString) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::string, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::string, set) // eckit::LocalConfiguration (sub-configuration) M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(eckit::LocalConfiguration, isSubConfiguration, getSubConfiguration) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(eckit::LocalConfiguration, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(eckit::LocalConfiguration, set) //------------------------------------------------------------------------------ // Vector types //------------------------------------------------------------------------------ // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getIntVector) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getIntVector) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isIntegralList, getLongVector) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isIntegralList, getLongVector) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getFloatVector) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getFloatVector) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isFloatingPointList, getDoubleVector) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isFloatingPointList, getDoubleVector) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isStringList, getStringVector) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isStringList, getStringVector) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) // std::vector M2M_DEFINE_LOCALCONFIG_DICT_GET_OR_THROW(std::vector, isSubConfigurationList, getSubConfigurations) M2M_DEFINE_LOCALCONFIG_DICT_GET_OPT(std::vector, isSubConfigurationList, getSubConfigurations) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_THROW(std::vector, set) M2M_DEFINE_LOCALCONFIG_DICT_SET_OR_IGNORE(std::vector, set) } // namespace metkit::mars2mars::utils::dict_traits metkit-1.20.2/src/metkit/mars2mars/utils/dictionary_traits/dictaccess_options.h0000664000175000017500000001460015246725757030221 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file dictaccess_options.h /// @brief Read-only dictionary-access traits for Mars2Mars Options. /// /// This adapter exposes the strongly typed `Options` aggregate through the /// generic Mars2Mars dictionary API. /// /// Supported operations for the bool-backed and string-backed option keys: /// /// - `has(options, key)` /// - `has(options, key)` /// - `get_opt(options, key)` /// - `get_or_throw(options, key)` /// - `dict_to_json(options)` /// /// Deliberately unsupported operations: /// /// - mutation through dictionary traits; /// - cloning through dictionary traits; /// - construction from a sample; /// - typed access beyond the explicitly supported bool/string keys. /// /// The Options object is already a complete policy snapshot. The adapter is /// therefore read-only. /// #pragma once #include #include #include #include "metkit/mars2mars/api/Options.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/type_traits_name.h" namespace metkit::mars2mars::utils { /// /// @brief Human-readable type name for the Options dictionary. /// template <> constexpr std::string_view type_name() { return "metkit::mars2mars::Options"; } } // namespace metkit::mars2mars::utils namespace metkit::mars2mars::utils::dict_traits { namespace options_detail { inline void appendJsonBool(std::string& json, std::string_view key, bool value, bool trailingComma) { json += "\""; json += key; json += "\":"; json += value ? "true" : "false"; if (trailingComma) { json += ','; } } inline void appendJsonString(std::string& json, std::string_view key, std::string_view value, bool trailingComma) { json += '"'; json += key; json += "\":\""; for (const char c : value) { if (c == '\\' || c == '"') { json += '\\'; } json += c; } json += '"'; if (trailingComma) { json += ','; } } inline bool isBoolKey(std::string_view key) noexcept { return key == "saveErrorStack" || key == "printErrorStackToStdErr" || key == "skipSection3" || key == "tryFixBadInput_ZeroAccumulation"; } inline bool isStringKey(std::string_view key) noexcept { return key == "errorStackPath"; } inline bool isKnownKey(std::string_view key) noexcept { return isBoolKey(key) || isStringKey(key); } inline bool getBoolOrThrow(const Options& opts, std::string_view key) { if (key == "saveErrorStack") { return opts.saveErrorStack; } if (key == "printErrorStackToStdErr") { return opts.printErrorStackToStdErr; } if (key == "skipSection3") { return opts.skipSection3; } if (key == "tryFixBadInput_ZeroAccumulation") { return opts.tryFixBadInput_ZeroAccumulation; } throw exceptions::Mars2marsDictException("Key `" + std::string(key) + "` cannot be read as `" + "bool" + "` from dictionary type `metkit::mars2mars::Options`", Here()); } inline std::string getStringOrThrow(const Options& opts, std::string_view key) { if (key == "errorStackPath") { return opts.errorStackPath; } throw exceptions::Mars2marsDictException("Key `" + std::string(key) + "` cannot be read as `" + "std::string" + "` from dictionary type `metkit::mars2mars::Options`", Here()); } } // namespace options_detail template <> struct DictToJsonTraits { static std::string to_json(const Options& opts) noexcept(true) { try { std::string json; json.reserve(128); json += '{'; options_detail::appendJsonBool(json, "saveErrorStack", opts.saveErrorStack, true); options_detail::appendJsonBool(json, "skipSection3", opts.skipSection3, true); options_detail::appendJsonBool(json, "tryFixBadInput_ZeroAccumulation", opts.tryFixBadInput_ZeroAccumulation, true); options_detail::appendJsonString(json, "errorStackPath", opts.errorStackPath, true); options_detail::appendJsonBool(json, "printErrorStackToStdErr", opts.printErrorStackToStdErr, false); json += '}'; return json; } catch (...) { return "[to_json failed for metkit::mars2mars::Options]"; } } }; template <> struct DictHas { static bool has(const Options&, std::string_view key) noexcept(false) { if (options_detail::isKnownKey(key)) { return true; } return false; } }; template <> struct DictGetOrThrow { static bool get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isBoolKey(key)) { return std::nullopt; } return options_detail::getBoolOrThrow(opts, key); } }; template <> struct DictGetOrThrow { static std::string get_or_throw(const Options& opts, std::string_view key) noexcept(false) { return options_detail::getStringOrThrow(opts, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const Options& opts, std::string_view key) noexcept(false) { if (!options_detail::isStringKey(key)) { return std::nullopt; } return options_detail::getStringOrThrow(opts, key); } }; /* * No explicit function-template specialisation is required for `has()`. * * The generic dictionary API implements: * * has(options, key) * * by invoking: * * DictGetOpt::get_opt(options, key).has_value() * * Therefore the DictGetOpt specialisation above provides: * * - has(options, key) * - has(options, key) */ } // namespace metkit::mars2mars::utils::dict_traits metkit-1.20.2/src/metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h0000664000175000017500000002231615246725757031420 0ustar alastairalastair/* * (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 dictionary_access_traits.h /// @brief Generic dictionary access traits for mars2mars. #pragma once #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "metkit/mars2mars/utils/generalUtils.h" // Exceptions #include "metkit/config/LibMetkit.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/type_traits_name.h" namespace metkit::mars2mars::utils::dict_traits { using std::operator""s; template struct dependent_false : std::false_type {}; /// @brief Trait hook used to render a dictionary as JSON. template struct DictToJsonTraits { static std::string to_json(const Dict&) { return std::string{"[to_json not supported for this dictionary type]"}; } static void dump_or_ignore(const Dict&, const std::string&) { LOG_DEBUG_LIB(LibMetkit) << to_json(std::declval()); } }; /// @brief Trait hook for cloning and sample creation. template struct DictTraits { static constexpr bool support_checks = false; static std::unique_ptr make_from_sample_or_throw(std::string_view) { static_assert(dependent_false::value, "DictTraits::make_from_sample_or_throw not specialized"); } static std::unique_ptr clone_or_throw(const Dict&) { static_assert(dependent_false::value, "DictTraits::clone_or_throw not specialized"); } }; /// @brief Trait hook for key presence checks. template struct DictHas { static bool has(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictHas not specialized for this Dict"); mars2marsUnreachable(); } }; /// @brief Trait hook for missing-value checks. template struct DictMissing { static bool isMissing(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); mars2marsUnreachable(); } static void setMissing(Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictMissing not specialized for this Dict"); mars2marsUnreachable(); } }; /// @brief Trait hook for optional value retrieval. template struct DictGetOpt { static std::optional get_opt(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOpt not specialized for this Dict and type"); mars2marsUnreachable(); } }; /// @brief Trait hook for mandatory value retrieval. template struct DictGetOrThrow { static T get_or_throw(const Dict&, std::string_view) noexcept(false) { static_assert(dependent_false::value, "DictGetOrThrow not specialized for this Dict and type"); mars2marsUnreachable(); } }; /// @brief Trait hook for assignment that may be ignored. template struct DictSetOrIgnore { static void set_or_ignore(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrIgnore not specialized for this Dict and type"); mars2marsUnreachable(); } }; /// @brief Trait hook for assignment that must succeed. template struct DictSetOrThrow { static void set_or_throw(Dict&, std::string_view, const T&) noexcept(false) { static_assert(dependent_false::value, "DictSetOrThrow not specialized for this Dict and type"); mars2marsUnreachable(); } }; // ============================================================ // dict_to_json // ============================================================ /// @brief Convert a dictionary to JSON using the active traits. template std::string dict_to_json(const Dict& d) { return DictToJsonTraits::to_json(d); } // ============================================================ // clone / make_from_sample / needs_checks // ============================================================ /// @brief Report whether a dictionary supports consistency checks. template inline constexpr bool dict_supports_checks_v = DictTraits::support_checks; /// @brief Create a dictionary from a sample name. template std::unique_ptr make_from_sample_or_throw(std::string_view name) { return DictTraits::make_from_sample_or_throw(name); } /// @brief Clone a supported dictionary. template std::unique_ptr clone_or_throw(const Dict& d) { return DictTraits::clone_or_throw(d); } /// @brief Render a dictionary or ignore unsupported types. template void dump_or_ignore(const Dict& d, const std::string& f) { DictToJsonTraits::dump_or_ignore(d, f); } // ============================================================ // has / isMissing / setMissing // ============================================================ // has(dict,key) /// @brief Check whether a key exists in a dictionary. template inline bool has(const Dict& dict, std::string_view key) { return DictHas::has(dict, key); } // has(dict,key) /// @brief Check whether a typed value exists in a dictionary. template inline bool has(const Dict& dict, std::string_view key) { return DictGetOpt::get_opt(dict, key).has_value(); } // isMissing(dict,key) /// @brief Check whether a key is semantically missing. template inline bool isMissing(const Dict& dict, std::string_view key) { return DictMissing::isMissing(dict, key); } // setMissing(dict,key) /// @brief Mark a key as missing. template inline void setMissing_or_throw(Dict& dict, std::string_view key) { DictMissing::setMissing(dict, key); return; } // check(dict,key,cond) -> bool /// @brief Evaluate a predicate on a typed optional value. template inline bool check(const Dict& dict, std::string_view key, Cond&& condition) { if (auto v = DictGetOpt::get_opt(dict, key); v.has_value()) { return std::forward(condition)(*v); } return false; } // ============================================================ // GET UTILITIES // ============================================================ // get_or_throw(dict,key) -> T /// @brief Retrieve a mandatory typed value from a dictionary. template inline T get_or_throw(const Dict& dict, std::string_view key) { try { return DictGetOrThrow::get_or_throw(dict, key); } catch (...) { std::throw_with_nested( exceptions::Mars2marsDictException("Forwarding errors while getting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` from dictionary`"s, Here())); mars2marsUnreachable(); } mars2marsUnreachable(); } // get(dict,key) -> std::optional /// @brief Retrieve an optional typed value from a dictionary. template inline std::optional get_opt(const Dict& dict, std::string_view key) { try { return DictGetOpt::get_opt(dict, key); } catch (...) { return std::nullopt; } mars2marsUnreachable(); } // ============================================================ // SET UTILITIES // ============================================================ // set(dict,key,value) /// @brief Set a typed value in a dictionary and throw on failure. template inline void set_or_throw(Dict& dict, std::string_view key, const T& value) { try { DictSetOrThrow::set_or_throw(dict, key, value); return; } catch (...) { std::throw_with_nested(exceptions::Mars2marsDictException("Forwarding errors while setting key `"s + std::string(key) + "` as `" + std::string(type_name()) + "` to dictionary`"s, Here())); mars2marsUnreachable(); } mars2marsUnreachable(); } /// @brief Set a typed value in a dictionary and ignore failures. template inline void set_or_ignore(Dict& dict, std::string_view key, const T& value) { try { DictSetOrIgnore::set_or_ignore(dict, key, value); return; } catch (...) { // ignore exceptions mars2marsUnreachable(); } mars2marsUnreachable(); } } // namespace metkit::mars2mars::utils::dict_traits metkit-1.20.2/src/metkit/mars2mars/utils/dictionary_traits/dictaccess_mars_request.h0000664000175000017500000005654315246725757031254 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include #include #include #include #include #include "eckit/log/JSON.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/generalUtils.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/type_traits_name.h" namespace metkit::mars2mars::utils { template <> constexpr std::string_view type_name() { return "metkit::mars::MarsRequest"; } } // namespace metkit::mars2mars::utils namespace metkit::mars2mars::utils::dict_traits { using std::operator""s; namespace detail { inline const std::string& scalarStringOrThrow(const metkit::mars::MarsRequest& mars, std::string_view key) { const std::string k{key}; if (!mars.has(k)) { throw exceptions::Mars2marsDictException("Missing key `"s + k + "` in metkit::mars::MarsRequest", Here()); } const auto& values = mars.values(k); if (values.size() != 1) { throw exceptions::Mars2marsDictException( "Invalid non-scalar key `"s + k + "` in metkit::mars::MarsRequest: found `"s + std::to_string(values.size()) + "` values. mars2mars expects a single MARS point, not a hypercube.", Here()); } return values.front(); } inline std::optional scalarStringOpt(const metkit::mars::MarsRequest& mars, std::string_view key) { const std::string k{key}; if (!mars.has(k)) { return std::nullopt; } const auto& values = mars.values(k); if (values.empty()) { return std::nullopt; } if (values.size() != 1) { throw exceptions::Mars2marsDictException( "Invalid non-scalar key `"s + k + "` in metkit::mars::MarsRequest: found `"s + std::to_string(values.size()) + "` values. mars2mars expects a single MARS point, not a hypercube.", Here()); } return values.front(); } inline long parsePlainLong(std::string_view value, std::string_view key) { const std::string s{value}; std::size_t pos = 0; long result = 0; try { result = std::stol(s, &pos); } catch (...) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to long", Here()); } if (pos != s.size()) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to long without loss", Here()); } return result; } inline double parsePlainDouble(std::string_view value, std::string_view key) { const std::string s{value}; std::size_t pos = 0; double result = 0.0; try { result = std::stod(s, &pos); } catch (...) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to double", Here()); } if (pos != s.size()) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to double without loss", Here()); } return result; } inline long parseHoursAsLong(std::string_view value, std::string_view key) { const std::string s{value}; if (s.empty()) { throw exceptions::Mars2marsDictException("Cannot convert empty key `"s + std::string{key} + "` value to hours", Here()); } if (s.back() == 'h') { return parsePlainLong(std::string_view{s.data(), s.size() - 1}, key); } if (s.back() == 'm' || s.back() == 's') { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to hours: sub-hourly extents are not supported by this mars2mars path", Here()); } // Existing workflows treat bare numeric step/timespan as hours. return parsePlainLong(s, key); } inline std::string longToHours(long value) { return std::to_string(value) + "h"; } inline std::string doubleToString(double value) { std::ostringstream os; os.precision(std::numeric_limits::max_digits10); os << value; return os.str(); } // ----------------------------------------------------------------------------- // long converters // ----------------------------------------------------------------------------- using ToLong = long (*)(std::string_view value, std::string_view key); using FromLong = std::string (*)(long value); inline long paramToLong(std::string_view value, std::string_view key) { // Conservative first implementation: // param=130 -> OK // param=228.128 -> throw // // If later you need "228.128" -> some canonical long encoding, // add that rule here, not in generic parsing. // std::cout << "Parsing param value `" << value << "` as long for key `" << key << "`" << std::endl; return parsePlainLong(value, key); } inline long plainLong(std::string_view value, std::string_view key) { return parsePlainLong(value, key); } inline long hoursLong(std::string_view value, std::string_view key) { return parseHoursAsLong(value, key); } inline std::string plainLongString(long value) { return std::to_string(value); } inline bool allDigits(std::string_view s) { return std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); }); } inline bool isLeapYear(long year) { return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); } inline long daysInMonth(long year, long month) { switch (month) { case 1: return 31; case 2: return isLeapYear(year) ? 29 : 28; case 3: return 31; case 4: return 30; case 5: return 31; case 6: return 30; case 7: return 31; case 8: return 31; case 9: return 30; case 10: return 31; case 11: return 30; case 12: return 31; default: return 0; } } inline void checkYYYYMMDD(long yyyymmdd, std::string_view key) { const long year = yyyymmdd / 10000; const long month = (yyyymmdd / 100) % 100; const long day = yyyymmdd % 100; if (year <= 0 || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) { throw exceptions::Mars2marsDictException("Invalid MARS date `"s + std::to_string(yyyymmdd) + "` for key `"s + std::string{key} + "`. Expected a valid yyyymmdd date.", Here()); } } inline long marsDateToLong(std::string_view value, std::string_view key) { const std::string s{value}; std::string compact; if (s.size() == 8 && allDigits(s)) { compact = s; } else if (s.size() == 10 && s[4] == '-' && s[7] == '-' && allDigits(std::string_view{s.data(), 4}) && allDigits(std::string_view{s.data() + 5, 2}) && allDigits(std::string_view{s.data() + 8, 2})) { compact.reserve(8); compact.append(s, 0, 4); compact.append(s, 5, 2); compact.append(s, 8, 2); } else { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS date integer yyyymmdd. Supported forms are yyyymmdd and yyyy-mm-dd.", Here()); } const long result = parsePlainLong(compact, key); checkYYYYMMDD(result, key); return result; } inline std::string longToMarsDate(long value) { const std::string s = std::to_string(value); if (s.size() != 8 || !allDigits(s)) { throw exceptions::Mars2marsDictException("Cannot convert integer `"s + s + "` to MARS date. Expected yyyymmdd.", Here()); } checkYYYYMMDD(value, "date"); return s; } inline long parseTimePart(std::string_view part, std::string_view full, std::string_view key) { if (part.empty() || !allDigits(part)) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + std::string{full} + "` to MARS time.", Here()); } return parsePlainLong(part, key); } inline long makeHHMMSS(long hh, long mm, long ss, std::string_view value, std::string_view key) { if (hh < 0 || hh > 23 || mm < 0 || mm > 59 || ss < 0 || ss > 59) { throw exceptions::Mars2marsDictException("Invalid MARS time `"s + std::string{value} + "` for key `"s + std::string{key} + "`. Expected hh=[0,23], mm=[0,59], ss=[0,59].", Here()); } if (ss != 0) { throw exceptions::Mars2marsDictException("Invalid MARS time `"s + std::string{value} + "` for key `"s + std::string{key} + "`. Seconds are not supported by metkit MARS time normalisation.", Here()); } return hh * 10000 + mm * 100 + ss; } inline long parseSeparatedMarsTime(std::string_view value, std::string_view key, char sep) { const auto p0 = value.find(sep); if (p0 == std::string_view::npos) { throw exceptions::Mars2marsDictException( "Internal error while parsing separated MARS time `"s + std::string{value} + "`", Here()); } const auto p1 = value.find(sep, p0 + 1); const auto hhPart = value.substr(0, p0); if (p1 == std::string_view::npos) { const auto mmPart = value.substr(p0 + 1); const long hh = parseTimePart(hhPart, value, key); const long mm = parseTimePart(mmPart, value, key); return makeHHMMSS(hh, mm, 0, value, key); } if (value.find(sep, p1 + 1) != std::string_view::npos) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + std::string{value} + "` to MARS time.", Here()); } const auto mmPart = value.substr(p0 + 1, p1 - p0 - 1); const auto ssPart = value.substr(p1 + 1); const long hh = parseTimePart(hhPart, value, key); const long mm = parseTimePart(mmPart, value, key); const long ss = parseTimePart(ssPart, value, key); return makeHHMMSS(hh, mm, ss, value, key); } inline long marsTimeToLong(std::string_view value, std::string_view key) { const std::string s{value}; if (s.empty()) { throw exceptions::Mars2marsDictException( "Cannot convert empty key `"s + std::string{key} + "` value to MARS time.", Here()); } if (s.find(':') != std::string::npos) { return parseSeparatedMarsTime(s, key, ':'); } if (s.find('-') != std::string::npos) { return parseSeparatedMarsTime(s, key, '-'); } if (!allDigits(s)) { throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS time integer hhmmss.", Here()); } if (s.size() <= 2) { // H or HH -> HH:00:00 const long hh = parsePlainLong(s, key); return makeHHMMSS(hh, 0, 0, value, key); } if (s.size() <= 4) { // HMM or HHMM -> HH:MM:00 std::string hhmm = s; hhmm.insert(hhmm.begin(), 4 - hhmm.size(), '0'); const long hh = parsePlainLong(std::string_view{hhmm.data(), 2}, key); const long mm = parsePlainLong(std::string_view{hhmm.data() + 2, 2}, key); return makeHHMMSS(hh, mm, 0, value, key); } if (s.size() <= 6) { // HMMSS or HHMMSS -> HH:MM:SS std::string hhmmss = s; hhmmss.insert(hhmmss.begin(), 6 - hhmmss.size(), '0'); const long hh = parsePlainLong(std::string_view{hhmmss.data(), 2}, key); const long mm = parsePlainLong(std::string_view{hhmmss.data() + 2, 2}, key); const long ss = parsePlainLong(std::string_view{hhmmss.data() + 4, 2}, key); return makeHHMMSS(hh, mm, ss, value, key); } throw exceptions::Mars2marsDictException( "Cannot convert key `"s + std::string{key} + "` value `"s + s + "` to MARS time integer hhmmss.", Here()); } inline std::string longToMarsTime(long value) { if (value < 0 || value > 235959) { throw exceptions::Mars2marsDictException( "Cannot convert integer `"s + std::to_string(value) + "` to MARS time. Expected hhmmss.", Here()); } std::ostringstream os; os << std::setw(6) << std::setfill('0') << value; const std::string hhmmss = os.str(); const long hh = parsePlainLong(std::string_view{hhmmss.data(), 2}, "time"); const long mm = parsePlainLong(std::string_view{hhmmss.data() + 2, 2}, "time"); const long ss = parsePlainLong(std::string_view{hhmmss.data() + 4, 2}, "time"); makeHHMMSS(hh, mm, ss, hhmmss, "time"); // metkit canonical MARS time is HHMM, not HHMMSS. return hhmmss.substr(0, 4); } inline const std::unordered_map& toLongConverters() { static const std::unordered_map converters = { {"param", paramToLong}, {"levelist", plainLong}, {"chem", plainLong}, {"step", hoursLong}, {"timespan", hoursLong}, {"date", marsDateToLong}, {"hdate", marsDateToLong}, {"time", marsTimeToLong}, {"htime", marsTimeToLong}, }; return converters; } inline const std::unordered_map& fromLongConverters() { static const std::unordered_map converters = { {"param", plainLongString}, {"levelist", plainLongString}, {"chem", plainLongString}, {"step", longToHours}, {"timespan", longToHours}, {"date", longToMarsDate}, {"hdate", longToMarsDate}, {"time", longToMarsTime}, {"htime", longToMarsTime}, }; return converters; } inline long convertToLongOrThrow(std::string_view key, std::string_view value) { const std::string k{key}; const auto& converters = toLongConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Mars2marsDictException( "Key `"s + k + "` does not support conversion to long for metkit::mars::MarsRequest", Here()); } return it->second(value, key); } inline std::string convertFromLongOrThrow(std::string_view key, long value) { const std::string k{key}; const auto& converters = fromLongConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Mars2marsDictException( "Key `"s + k + "` does not support assignment from long for metkit::mars::MarsRequest", Here()); } return it->second(value); } // ----------------------------------------------------------------------------- // double converters // ----------------------------------------------------------------------------- using ToDouble = double (*)(std::string_view value, std::string_view key); using FromDouble = std::string (*)(double value); inline const std::unordered_map& toDoubleConverters() { static const std::unordered_map converters = { {"wavelength", parsePlainDouble}, }; return converters; } inline const std::unordered_map& fromDoubleConverters() { static const std::unordered_map converters = { {"wavelength", doubleToString}, }; return converters; } inline double convertToDoubleOrThrow(std::string_view key, std::string_view value) { const std::string k{key}; const auto& converters = toDoubleConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Mars2marsDictException( "Key `"s + k + "` does not support conversion to double for metkit::mars::MarsRequest", Here()); } return it->second(value, key); } inline std::string convertFromDoubleOrThrow(std::string_view key, double value) { const std::string k{key}; const auto& converters = fromDoubleConverters(); const auto it = converters.find(k); if (it == converters.end()) { throw exceptions::Mars2marsDictException( "Key `"s + k + "` does not support assignment from double for metkit::mars::MarsRequest", Here()); } return it->second(value); } } // namespace detail // ----------------------------------------------------------------------------- // DictToJsonTraits // ----------------------------------------------------------------------------- template <> struct DictToJsonTraits { static std::string to_json(const metkit::mars::MarsRequest& mars) noexcept(true) { try { std::ostringstream os; eckit::JSON json(os); mars.json(json); return os.str(); } catch (...) { return "[to_json failed for metkit::mars::MarsRequest]"; } } }; // ----------------------------------------------------------------------------- // DictTraits // ----------------------------------------------------------------------------- template <> struct DictTraits { static constexpr bool support_checks = false; // static std::unique_ptr // make_from_sample_or_throw(std::string_view name) { // return std::make_unique(std::string{name}); // } static std::unique_ptr clone_or_throw(const metkit::mars::MarsRequest& mars) { return std::make_unique(mars); } }; // ----------------------------------------------------------------------------- // DictHas // ----------------------------------------------------------------------------- template <> struct DictHas { static bool has(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return mars.has(std::string{key}); } }; // ----------------------------------------------------------------------------- // DictMissing // ----------------------------------------------------------------------------- template <> struct DictMissing { static bool isMissing(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const std::string k{key}; if (!mars.has(k)) { return true; } const auto& values = mars.values(k, true); return values.empty(); } static void setMissing(metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { mars.unsetValues(std::string{key}); } }; // ----------------------------------------------------------------------------- // std::string access: all keys, raw scalar string // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static std::string get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return detail::scalarStringOrThrow(mars, key); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { return detail::scalarStringOpt(mars, key); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const std::string& value) noexcept(false) { mars.setValue(std::string{key}, value); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const std::string& value) noexcept(false) { try { mars.setValue(std::string{key}, value); } catch (...) { } } }; // ----------------------------------------------------------------------------- // long access: explicit supported keys only // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static long get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto& raw = detail::scalarStringOrThrow(mars, key); return detail::convertToLongOrThrow(key, raw); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto raw = detail::scalarStringOpt(mars, key); if (!raw.has_value()) { return std::nullopt; } return detail::convertToLongOrThrow(key, *raw); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const long& value) noexcept(false) { mars.setValue(std::string{key}, detail::convertFromLongOrThrow(key, value)); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const long& value) noexcept(false) { try { mars.setValue(std::string{key}, detail::convertFromLongOrThrow(key, value)); } catch (...) { } } }; // ----------------------------------------------------------------------------- // double access: wavelength only // ----------------------------------------------------------------------------- template <> struct DictGetOrThrow { static double get_or_throw(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto& raw = detail::scalarStringOrThrow(mars, key); return detail::convertToDoubleOrThrow(key, raw); } }; template <> struct DictGetOpt { static std::optional get_opt(const metkit::mars::MarsRequest& mars, std::string_view key) noexcept(false) { const auto raw = detail::scalarStringOpt(mars, key); if (!raw.has_value()) { return std::nullopt; } return detail::convertToDoubleOrThrow(key, *raw); } }; template <> struct DictSetOrThrow { static void set_or_throw(metkit::mars::MarsRequest& mars, std::string_view key, const double& value) noexcept(false) { mars.setValue(std::string{key}, detail::convertFromDoubleOrThrow(key, value)); } }; template <> struct DictSetOrIgnore { static void set_or_ignore(metkit::mars::MarsRequest& mars, std::string_view key, const double& value) noexcept(false) { try { mars.setValue(std::string{key}, detail::convertFromDoubleOrThrow(key, value)); } catch (...) { } } }; } // namespace metkit::mars2mars::utils::dict_traitsmetkit-1.20.2/src/metkit/mars2mars/mappings/0000775000175000017500000000000015246725757021112 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/mappings/Mars2MarsReturnValue.h0000664000175000017500000000174415246725757025275 0ustar alastairalastair/* * (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 Mars2MarsReturnValue.h /// @brief Return type used by mars2mars conversion APIs. #pragma once #include "eckit/config/LocalConfiguration.h" namespace metkit::mars2mars { /// @brief Structured result returned by mars2mars conversions. /// /// @tparam MarsDict /// Dictionary type used for the converted request. template struct Mars2MarsResult { /// @brief Converted MARS request. MarsDict mars; /// @brief Auxiliary metadata produced during conversion. eckit::LocalConfiguration misc; }; } // namespace metkit::mars2mars metkit-1.20.2/src/metkit/mars2mars/mappings/mappings.h0000664000175000017500000000530415246725757023103 0ustar alastairalastair/* * (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 all.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/removeNumber.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" // All the rules #include "metkit/mars2mars/mappings/rules/chemical.h" #include "metkit/mars2mars/mappings/rules/ecc-1806.h" #include "metkit/mars2mars/mappings/rules/incremental.h" #include "metkit/mars2mars/mappings/rules/local2wmo.h" #include "metkit/mars2mars/mappings/rules/misc-params.h" #include "metkit/mars2mars/mappings/rules/sfc2sol.h" #include "metkit/mars2mars/mappings/rules/timespan.h" #include "metkit/mars2mars/mappings/rules/waveStreams.h" #include "metkit/mars2mars/mappings/rules/windspeed.h" namespace metkit::mars2mars::rules { /// @brief Apply all registered conversion rules and return a result object. template Mars2MarsResult convertAll(const InDict_t& in, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::clone_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { std::unique_ptr out = clone_or_throw(in); std::unique_ptr misc = std::make_unique(); // Apply all conversions in sequence impl::convertWaveStreams(in, *out, *misc, opts); impl::convertECC1806(in, *out, *misc, opts); impl::convertSFC2SOL(in, *out, *misc, opts); impl::convertLocal2WMO(in, *out, *misc, opts); impl::fixTimespan(in, *out, *misc, opts); impl::fixWindspeed(in, *out, *misc, opts); impl::convertChemical(in, *out, *misc, opts); impl::convertIncremental(in, *out, *misc, opts); impl::convertMiscParams(in, *out, *misc, opts); impl::removeNumber(in, *out, *misc, opts); return Mars2MarsResult{std::move(*out), std::move(*misc)}; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to convertAll input dictionaries", Here())); } } } // namespace metkit::mars2mars::rules metkit-1.20.2/src/metkit/mars2mars/mappings/rules/0000775000175000017500000000000015246725757022244 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/mappings/rules/windspeed.h0000664000175000017500000000501715246725757024402 0ustar alastairalastair/* * (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 windspeed.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert surface-like legacy requests into sol layer output. template inline void fixWindspeed(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_opt; using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto levtype = get_opt(in, "levtype"); if (levtype && *levtype == "sfc") { const auto param = get_or_throw(in, "param"); switch (param) { case 228246: // 100u return detail::setParamLevel(out, 131, "hl", 100); case 228239: // 200u return detail::setParamLevel(out, 131, "hl", 200); case 228247: // 100v return detail::setParamLevel(out, 132, "hl", 100); case 228240: // 200v return detail::setParamLevel(out, 132, "hl", 200); case 228249: // 100si return detail::setParamLevel(out, 10, "hl", 100); case 228241: // 200si return detail::setParamLevel(out, 10, "hl", 200); default: break; } } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to convert input dictionary in fixWindspeed", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/removeNumber.h0000664000175000017500000000307115246725757025064 0ustar alastairalastair/* * (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 removeNumber.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::rules::impl { /// @brief Remove MARS key number if no longer used template inline void removeNumber(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::setMissing_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; if (get_or_throw(in, "type") == "me") { setMissing_or_throw(out, "number"); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to convert input dictionary in removeNumber", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/misc-params.h0000664000175000017500000000504415246725757024634 0ustar alastairalastair/* * (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 misc-params.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert miscellaneous params template inline void convertMiscParams(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); switch (param) { case 151130: // Practical salinity (psu to g/kg) set_or_throw(out, "param", 262100); break; case 151131: // Eastward surface sea water velocity (m/s) set_or_throw(out, "param", 262140); break; case 151145: // Sea surface height (m) set_or_throw(out, "param", 262124); break; case 151148: // Mixed layer depth (m) set_or_throw(out, "param", 3067); break; case 151164: // Average potential temperature in the upper 300m (deg' C to K) set_or_throw(out, "param", 262144); misc.set("offsetValuesBy", 273.15); break; case 151175: // Average sea water practical salinity in the upper 300m (psu to g/kg) set_or_throw(out, "param", 262118); break; default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertMiscParams", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/sfc2sol.h0000664000175000017500000000655415246725757024002 0ustar alastairalastair/* * (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 sfc2sol.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert surface-like legacy requests into sol layer output. template inline void convertSFC2SOL(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_opt; using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); switch (param) { // (35, 36, 37, 39) -> 262024 case 35: return detail::setParamLevel(out, 262024, "sol", 1); case 36: return detail::setParamLevel(out, 262024, "sol", 2); case 37: return detail::setParamLevel(out, 262024, "sol", 3); case 38: return detail::setParamLevel(out, 262024, "sol", 4); // (39, 40, 41, 42) -> 260199 case 39: return detail::setParamLevel(out, 260199, "sol", 1); case 40: return detail::setParamLevel(out, 260199, "sol", 2); case 41: return detail::setParamLevel(out, 260199, "sol", 3); case 42: return detail::setParamLevel(out, 260199, "sol", 4); // (139, 170, 183, 236) -> 260360 case 139: return detail::setParamLevel(out, 260360, "sol", 1); case 170: return detail::setParamLevel(out, 260360, "sol", 2); case 183: return detail::setParamLevel(out, 260360, "sol", 3); case 236: return detail::setParamLevel(out, 260360, "sol", 4); // SFC with non-zero levelist to SOL case 33: case 238: case 228038: case 235080: case 237080: case 238080: case 239080: case 260199: case 260360: case 262000: case 262024: const auto levelist = get_opt(in, "levelist").value_or(0); if (levelist != 0) { return detail::setParamLevel(out, param, "sol", levelist); } break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to convertSFC2SOL input dictionaries", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/common.h0000664000175000017500000000314715246725757023712 0ustar alastairalastair/* * (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 common.h /// @brief Shared utilities to make conversion rules easier to implement. /// @todo: utils/paramMatcher.h needs to be merged into this file. #pragma once #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl::detail { /// @brief Assign `param`, `levtype`, and `levelist` together. template inline void setParamLevel(OutDict_t& out, long param, const std::string& levtype, long levelist) { using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { set_or_throw(out, "param", param); set_or_throw(out, "levtype", levtype); set_or_throw(out, "levelist", levelist); } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to setParamLevel to input dictionaries", Here())); } } } // namespace metkit::mars2mars::rules::impl::detail metkit-1.20.2/src/metkit/mars2mars/mappings/rules/ecc-1806.h0000664000175000017500000001221115246725757023540 0ustar alastairalastair/* * (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 ecc-1806.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert surface-like legacy requests into sol layer output. template inline void convertECC1806(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); // See: // https://github.com/ecmwf/eccodes/blob/develop/definitions/grib1/localConcepts/ecmf/paramIdForConversion.def switch (param) { case 55: set_or_throw(out, "param", 228004); set_or_throw(out, "timespan", "24h"); break; case 56: set_or_throw(out, "param", 235168); set_or_throw(out, "timespan", "24h"); break; case 130232: set_or_throw(out, "param", 235135); // TODO: Set timespan break; case 151163: set_or_throw(out, "param", 262104); break; case 151145: set_or_throw(out, "param", 262124); break; case 172146: set_or_throw(out, "param", 235033); // TODO: Set timespan break; case 172147: set_or_throw(out, "param", 235034); // TODO: Set timespan break; case 172169: set_or_throw(out, "param", 235035); // TODO: Set timespan break; case 172175: set_or_throw(out, "param", 235036); // TODO: Set timespan break; case 172176: set_or_throw(out, "param", 235037); // TODO: Set timespan break; case 172177: set_or_throw(out, "param", 235038); // TODO: Set timespan break; case 172178: set_or_throw(out, "param", 235039); // TODO: Set timespan break; case 172179: set_or_throw(out, "param", 235040); // TODO: Set timespan break; case 174098: set_or_throw(out, "param", 262000); break; case 151175: set_or_throw(out, "param", 262118); break; case 151132: set_or_throw(out, "param", 262139); break; case 151131: set_or_throw(out, "param", 262140); break; case 72: set_or_throw(out, "param", 260087); break; case 73: set_or_throw(out, "param", 260097); break; case 172050: set_or_throw(out, "param", 235026); // TODO: Set timespan break; case 172145: set_or_throw(out, "param", 235032); // TODO: Set timespan break; case 172189: set_or_throw(out, "param", 235189); // TODO: Set timespan break; case 172195: set_or_throw(out, "param", 235045); // TODO: Set timespan break; case 172196: set_or_throw(out, "param", 235046); // TODO: Set timespan break; case 172197: set_or_throw(out, "param", 235047); // TODO: Set timespan break; default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertECC1806", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/timespan.h0000664000175000017500000002530415246725757024241 0ustar alastairalastair/* * (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 timespan.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include #include #include #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Assign `param`, and `timespan` together. template inline void setParamTimespan(OutDict_t& out, long param, const std::string& timespan) { using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { set_or_throw(out, "param", param); set_or_throw(out, "timespan", timespan); } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to set param and/or timespan in output dictionary", Here())); } } /// @brief Detect a `step` of the form "-" and rewrite /// `step` + `timespan` on `out`. When `step` is a single value, default the /// output `timespan` to "none" if it is not already set on `in`. template inline void convertStepRangeToTimespan(const InDict_t& in, OutDict_t& out, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::has; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { if (!has(in, "step")) { return; } const std::string step = has(in, "step") ? std::to_string(get_or_throw(in, "step")) : get_or_throw(in, "step"); // Strict range detection: "-" const auto dash = step.find('-'); const auto isAllDigits = [](std::string_view v) { return !v.empty() && std::all_of(v.begin(), v.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); }; const bool isRange = dash != std::string::npos && step.find('-', dash + 1) == std::string::npos && isAllDigits(std::string_view{step.data(), dash}) && isAllDigits(std::string_view{step.data() + dash + 1, step.size() - dash - 1}); if (!isRange) { // Single-value step: leave step untouched. Default timespan to // "none" only when the input has no timespan yet. if (!has(in, "timespan")) { set_or_throw(out, "timespan", "none"); } return; } const long startStep = std::stol(std::string(step, 0, dash)); const long endStep = std::stol(std::string(step, dash + 1)); if (endStep < startStep) { throw Mars2marsGenericException("Invalid step range `" + step + "`: endStep < startStep (" + std::to_string(endStep) + " < " + std::to_string(startStep) + ")", Here()); } if (endStep == startStep) { if (endStep != 0) { const long marsParamId = get_or_throw(in, "param"); const bool canFixZeroAccumulation = marsParamId == 162110 && get_or_throw(opts, "tryFixBadInput_ZeroAccumulation"); if (!canFixZeroAccumulation) { const std::string marsClass = get_or_throw(in, "class"); const std::string marsStream = get_or_throw(in, "stream"); const std::string marsType = get_or_throw(in, "type"); throw Mars2marsGenericException( "Invalid step range `" + step + "`: endStep == startStep (" + std::to_string(endStep) + " == " + std::to_string(startStep) + ") is only allowed for step 0" + " (param=" + std::to_string(marsParamId) + ", class=" + marsClass + ", stream=" + marsStream + ", type=" + marsType + ")", Here()); } } set_or_throw(out, "step", endStep); set_or_throw(out, "timespan", "fs"); } else { set_or_throw(out, "step", endStep); set_or_throw(out, "timespan", std::to_string(endStep - startStep) + "h"); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to apply step-range timespan rule in output dictionary", Here())); } } /// @brief Fix timespan of statistical fields that have been wrongly encoded as instant at step 0. template inline void fixTimespanFS(const InDict_t& in, OutDict_t& out) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::has; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; // List of params that have been wrongly encoded as instant fields static const std::unordered_set paramsWithTimespanFS{ 8, 9, 20, 44, 45, 47, 49, 50, 57, 58, 121, 122, 123, 142, 143, 144, 146, 145, 147, 169, 175, 176, 177, 178, 179, 180, 181, 182, 189, 195, 196, 197, 201, 202, 205, 208, 209, 210, 211, 212, 213, 228, 228021, 228022, 228080, 228081, 228082, 228129, 228130, 228216, 228222, 228223, 228224, 228225, 228226, 228227, 228026, 228027, 228028, 228251}; try { const long param = get_or_throw(in, "param"); const bool isTimespanNone = get_or_throw(out, "timespan") == "none"; const bool isTimespanFSAllowed = paramsWithTimespanFS.find(param) != paramsWithTimespanFS.end(); if (isTimespanNone && isTimespanFSAllowed) { set_or_throw(out, "timespan", "fs"); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to apply step 0 timespan fix in output dictionary", Here())); } } /// @brief Convert surface-like legacy requests into sol layer output. template inline void fixTimespan(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { // Handle step ranges (e.g. "0-6") and default timespan to "none" for single-value steps. convertStepRangeToTimespan(in, out, opts); // Fix statistical fields that are wrongly encoded as instant fields at step 0. fixTimespanFS(in, out); const auto param = get_or_throw(in, "param"); switch (param) { // Max 2t case 121: return setParamTimespan(out, 237167, "6h"); case 228026: return setParamTimespan(out, 237167, "3h"); case 201: return set_or_throw(out, "param", 237167); // Max 6h CAPE case 228035: return setParamTimespan(out, 237117, "6h"); // Max 6h CAPES case 228036: return setParamTimespan(out, 237321, "6h"); // Max precipitation case 228222: return setParamTimespan(out, 237055, "3h"); case 228224: return setParamTimespan(out, 237055, "6h"); case 228226: return set_or_throw(out, "param", 237055); // Max wind gust case 123: return setParamTimespan(out, 237318, "6h"); case 228028: return setParamTimespan(out, 237318, "3h"); case 49: return set_or_throw(out, "param", 237318); // Mean flash density ? case 228051: return setParamTimespan(out, 235326, "1h"); case 228057: return setParamTimespan(out, 235326, "3h"); case 228058: return setParamTimespan(out, 235326, "6h"); // Min 2t case 122: return setParamTimespan(out, 238167, "6h"); case 228027: return setParamTimespan(out, 238167, "3h"); case 202: return set_or_throw(out, "param", 238167); // Min precipitation case 228223: return setParamTimespan(out, 238055, "3h"); case 228225: return setParamTimespan(out, 238055, "6h"); case 228227: return set_or_throw(out, "param", 238055); // Most frequent precipitation type case 260320: return setParamTimespan(out, 260683, "1h"); case 260321: return setParamTimespan(out, 260683, "3h"); case 260339: return setParamTimespan(out, 260683, "6h"); // Most severe precipitation type case 260318: return setParamTimespan(out, 260682, "1h"); case 260319: return setParamTimespan(out, 260682, "3h"); case 260338: return setParamTimespan(out, 260682, "6h"); default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2marsGenericException("Failed to convert input dictionary in fixTimespan", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/incremental.h0000664000175000017500000000433715246725757024725 0ustar alastairalastair/* * (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 incremental.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert incremental fields template inline void convertIncremental(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); switch (param) { case 200130: set_or_throw(out, "param", 130); break; case 200133: set_or_throw(out, "param", 133); break; case 200138: set_or_throw(out, "param", 138); break; case 200152: set_or_throw(out, "param", 152); break; case 200155: set_or_throw(out, "param", 155); break; case 200203: set_or_throw(out, "param", 203); break; default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertIncremental", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/chemical.h0000664000175000017500000014340715246725757024173 0ustar alastairalastair/* * (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.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Assign `param`, and `chem` together. template inline void setParamChem(OutDict_t& out, long param, long chem) { using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { set_or_throw(out, "param", param); set_or_throw(out, "chem", chem); } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to set param, and/or chem and/or in output dictionaries", Here())); } } /// @brief Assign `param`, `chem`, and `wavelength` together. template inline void setParamChemWavelength(OutDict_t& out, long param, long chem, double wavelength) { using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { set_or_throw(out, "param", param); set_or_throw(out, "chem", chem); set_or_throw(out, "wavelength", wavelength); } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to set param, chem and/or, wavelength in output dictionaries", Here())); } } /// @brief Convert surface-like legacy requests into sol layer output. template inline void convertChemical(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); switch (param) { case 210001: return setParamChem(out, 402000, 901); case 210002: return setParamChem(out, 402000, 902); case 210003: return setParamChem(out, 402000, 903); case 210004: return setParamChem(out, 402000, 904); case 210005: return setParamChem(out, 402000, 905); case 210006: return setParamChem(out, 402000, 906); case 210007: return setParamChem(out, 402000, 907); case 210008: return setParamChem(out, 402000, 908); case 210009: return setParamChem(out, 402000, 909); case 210010: return setParamChem(out, 402000, 910); case 210011: return setParamChem(out, 402000, 911); case 210022: return setParamChem(out, 453000, 922); case 210025: return setParamChem(out, 453000, 922); case 210048: return setParamChem(out, 402000, 924); case 210071: return setParamChem(out, 479000, 404); case 210072: return setParamChem(out, 400000, 929); case 210073: return setParamChem(out, 400000, 930); case 210074: return setParamChem(out, 400000, 931); case 210081: return setParamChem(out, 469000, 2); case 210085: return setParamChem(out, 469000, 129); case 210090: return setParamChem(out, 469000, 934); case 210091: return setParamChem(out, 469000, 933); case 210102: return setParamChem(out, 469000, 233); case 210103: return setParamChem(out, 469000, 42); case 210104: return setParamChem(out, 469000, 46); case 210105: return setParamChem(out, 469000, 47); case 210106: return setParamChem(out, 469000, 10); case 210107: return setParamChem(out, 469000, 48); case 210108: return setParamChem(out, 469000, 16); case 210113: return setParamChem(out, 469000, 5); case 210115: return setParamChem(out, 469000, 52); case 210116: return setParamChem(out, 469000, 19); case 210117: return setParamChem(out, 469000, 18); case 210118: return setParamChem(out, 469000, 45); case 210121: return setParamChem(out, 402000, 17); case 210122: return setParamChem(out, 402000, 233); case 210123: return setParamChem(out, 402000, 2); case 210124: return setParamChem(out, 402000, 5); case 210125: return setParamChem(out, 401000, 17); case 210126: return setParamChem(out, 401000, 233); case 210127: return setParamChem(out, 401000, 2); case 210128: return setParamChem(out, 401000, 5); case 210170: return setParamChem(out, 402000, 8); case 210181: return setParamChem(out, 402000, 11); case 210183: return setParamChem(out, 401000, 11); case 210203: return setParamChem(out, 402000, 236); case 210206: return setParamChem(out, 401000, 231); case 210207: return setParamChemWavelength(out, 457000, 922, 550.0); case 210208: return setParamChemWavelength(out, 457000, 936, 550.0); case 210209: return setParamChemWavelength(out, 457000, 935, 550.0); case 210210: return setParamChemWavelength(out, 457000, 934, 550.0); case 210211: return setParamChemWavelength(out, 457000, 933, 550.0); case 210212: return setParamChemWavelength(out, 457000, 911, 550.0); case 210213: return setParamChemWavelength(out, 457000, 922, 469.0); case 210214: return setParamChemWavelength(out, 457000, 922, 670.0); case 210215: return setParamChemWavelength(out, 457000, 922, 865.0); case 210216: return setParamChemWavelength(out, 457000, 922, 1240.0); case 210217: return setParamChemWavelength(out, 457000, 922, 340.0); case 210218: return setParamChemWavelength(out, 457000, 922, 355.0); case 210219: return setParamChemWavelength(out, 457000, 922, 380.0); case 210220: return setParamChemWavelength(out, 457000, 922, 400.0); case 210221: return setParamChemWavelength(out, 457000, 922, 440.0); case 210222: return setParamChemWavelength(out, 457000, 922, 500.0); case 210223: return setParamChemWavelength(out, 457000, 922, 532.0); case 210224: return setParamChemWavelength(out, 457000, 922, 645.0); case 210225: return setParamChemWavelength(out, 457000, 922, 800.0); case 210226: return setParamChemWavelength(out, 457000, 922, 858.0); case 210227: return setParamChemWavelength(out, 457000, 922, 1020.0); case 210228: return setParamChemWavelength(out, 457000, 922, 1064.0); case 210229: return setParamChemWavelength(out, 457000, 922, 1640.0); case 210230: return setParamChemWavelength(out, 457000, 922, 2130.0); case 210247: return setParamChem(out, 402000, 912); case 210248: return setParamChem(out, 402000, 913); case 210249: return setParamChem(out, 402000, 21); case 210250: return setParamChemWavelength(out, 457000, 900, 550.0); case 210251: return setParamChemWavelength(out, 457000, 914, 550.0); case 210252: return setParamChem(out, 402000, 915); case 210253: return setParamChem(out, 402000, 916); case 211001: return setParamChem(out, 402000, 901); case 211002: return setParamChem(out, 402000, 902); case 211003: return setParamChem(out, 402000, 903); case 211004: return setParamChem(out, 402000, 904); case 211005: return setParamChem(out, 402000, 905); case 211006: return setParamChem(out, 402000, 906); case 211007: return setParamChem(out, 402000, 907); case 211008: return setParamChem(out, 402000, 908); case 211009: return setParamChem(out, 402000, 909); case 211010: return setParamChem(out, 402000, 910); case 211011: return setParamChem(out, 402000, 911); case 211048: return setParamChem(out, 402000, 924); case 211121: return setParamChem(out, 402000, 17); case 211122: return setParamChem(out, 402000, 233); case 211123: return setParamChem(out, 402000, 2); case 211124: return setParamChem(out, 402000, 5); case 211170: return setParamChem(out, 402000, 8); case 211203: return setParamChem(out, 402000, 236); case 211247: return setParamChem(out, 402000, 900); case 211248: return setParamChem(out, 402000, 903); case 211249: return setParamChem(out, 402000, 914); case 211252: return setParamChem(out, 402000, 915); case 211253: return setParamChem(out, 402000, 916); case 215001: return setParamChem(out, 453000, 901); case 215002: return setParamChem(out, 453000, 902); case 215003: return setParamChem(out, 453000, 903); case 215004: return setParamChem(out, 406000, 901); case 215005: return setParamChem(out, 406000, 902); case 215006: return setParamChem(out, 406000, 903); case 215007: return setParamChem(out, 407000, 901); case 215008: return setParamChem(out, 407000, 902); case 215009: return setParamChem(out, 407000, 903); case 215010: return setParamChem(out, 410000, 901); case 215011: return setParamChem(out, 410000, 902); case 215012: return setParamChem(out, 410000, 903); case 215013: return setParamChem(out, 411000, 901); case 215014: return setParamChem(out, 411000, 902); case 215015: return setParamChem(out, 411000, 903); case 215016: return setParamChem(out, 451000, 901); case 215017: return setParamChem(out, 451000, 902); case 215018: return setParamChem(out, 451000, 903); case 215019: return setParamChem(out, 401000, 901); case 215020: return setParamChem(out, 401000, 902); case 215021: return setParamChem(out, 401000, 903); case 215022: return setParamChemWavelength(out, 457000, 901, 550.0); case 215023: return setParamChemWavelength(out, 457000, 902, 550.0); case 215024: return setParamChemWavelength(out, 457000, 903, 550.0); case 215025: return setParamChem(out, 453000, 904); case 215026: return setParamChem(out, 453000, 905); case 215027: return setParamChem(out, 453000, 906); case 215028: return setParamChem(out, 406000, 904); case 215029: return setParamChem(out, 406000, 905); case 215030: return setParamChem(out, 406000, 906); case 215031: return setParamChem(out, 407000, 904); case 215032: return setParamChem(out, 407000, 905); case 215033: return setParamChem(out, 407000, 906); case 215034: return setParamChem(out, 410000, 904); case 215035: return setParamChem(out, 410000, 905); case 215036: return setParamChem(out, 410000, 906); case 215037: return setParamChem(out, 411000, 904); case 215038: return setParamChem(out, 411000, 905); case 215039: return setParamChem(out, 411000, 906); case 215040: return setParamChem(out, 451000, 904); case 215041: return setParamChem(out, 451000, 905); case 215042: return setParamChem(out, 451000, 906); case 215043: return setParamChem(out, 401000, 904); case 215044: return setParamChem(out, 401000, 905); case 215045: return setParamChem(out, 401000, 906); case 215046: return setParamChemWavelength(out, 457000, 904, 550.0); case 215047: return setParamChemWavelength(out, 457000, 905, 550.0); case 215048: return setParamChemWavelength(out, 457000, 906, 550.0); case 215049: return setParamChem(out, 453000, 908); case 215050: return setParamChem(out, 453000, 907); case 215051: return setParamChem(out, 406000, 908); case 215052: return setParamChem(out, 406000, 907); case 215053: return setParamChem(out, 407000, 908); case 215054: return setParamChem(out, 407000, 907); case 215055: return setParamChem(out, 410000, 908); case 215056: return setParamChem(out, 410000, 907); case 215057: return setParamChem(out, 411000, 908); case 215058: return setParamChem(out, 411000, 907); case 215059: return setParamChem(out, 451000, 908); case 215060: return setParamChem(out, 451000, 907); case 215061: return setParamChem(out, 401000, 908); case 215062: return setParamChem(out, 401000, 907); case 215063: return setParamChemWavelength(out, 457000, 908, 550.0); case 215064: return setParamChemWavelength(out, 457000, 907, 550.0); case 215065: return setParamChem(out, 453000, 910); case 215066: return setParamChem(out, 453000, 909); case 215067: return setParamChem(out, 406000, 910); case 215068: return setParamChem(out, 406000, 909); case 215069: return setParamChem(out, 407000, 910); case 215070: return setParamChem(out, 407000, 909); case 215071: return setParamChem(out, 410000, 910); case 215072: return setParamChem(out, 410000, 909); case 215073: return setParamChem(out, 411000, 910); case 215074: return setParamChem(out, 411000, 909); case 215075: return setParamChem(out, 451000, 910); case 215076: return setParamChem(out, 451000, 909); case 215077: return setParamChem(out, 401000, 910); case 215078: return setParamChem(out, 401000, 909); case 215079: return setParamChemWavelength(out, 457000, 910, 550.0); case 215080: return setParamChemWavelength(out, 457000, 909, 550.0); case 215081: return setParamChem(out, 453000, 911); case 215082: return setParamChem(out, 406000, 911); case 215083: return setParamChem(out, 407000, 911); case 215084: return setParamChem(out, 410000, 911); case 215085: return setParamChem(out, 411000, 911); case 215086: return setParamChem(out, 451000, 911); case 215087: return setParamChem(out, 401000, 911); case 215088: return setParamChemWavelength(out, 457000, 911, 550.0); case 215093: return setParamChemWavelength(out, 457000, 922, 532.0); case 215094: return setParamChemWavelength(out, 457000, 904, 532.0); case 215095: return setParamChemWavelength(out, 457000, 910, 532.0); case 215096: return setParamChemWavelength(out, 472000, 922, 340.0); case 215097: return setParamChemWavelength(out, 472000, 922, 355.0); case 215098: return setParamChemWavelength(out, 472000, 922, 380.0); case 215099: return setParamChemWavelength(out, 472000, 922, 400.0); case 215100: return setParamChemWavelength(out, 472000, 922, 440.0); case 215101: return setParamChemWavelength(out, 472000, 922, 469.0); case 215102: return setParamChemWavelength(out, 472000, 922, 500.0); case 215103: return setParamChemWavelength(out, 472000, 922, 532.0); case 215104: return setParamChemWavelength(out, 472000, 922, 550.0); case 215105: return setParamChemWavelength(out, 472000, 922, 645.0); case 215106: return setParamChemWavelength(out, 472000, 922, 670.0); case 215107: return setParamChemWavelength(out, 472000, 922, 800.0); case 215108: return setParamChemWavelength(out, 472000, 922, 858.0); case 215109: return setParamChemWavelength(out, 472000, 922, 865.0); case 215110: return setParamChemWavelength(out, 472000, 922, 1020.0); case 215111: return setParamChemWavelength(out, 472000, 922, 1064.0); case 215112: return setParamChemWavelength(out, 472000, 922, 1240.0); case 215113: return setParamChemWavelength(out, 472000, 922, 1640.0); case 215114: return setParamChemWavelength(out, 457000, 923, 340.0); case 215115: return setParamChemWavelength(out, 457000, 923, 355.0); case 215116: return setParamChemWavelength(out, 457000, 923, 380.0); case 215117: return setParamChemWavelength(out, 457000, 923, 400.0); case 215118: return setParamChemWavelength(out, 457000, 923, 440.0); case 215119: return setParamChemWavelength(out, 457000, 923, 469.0); case 215120: return setParamChemWavelength(out, 457000, 923, 500.0); case 215121: return setParamChemWavelength(out, 457000, 923, 532.0); case 215122: return setParamChemWavelength(out, 457000, 923, 550.0); case 215123: return setParamChemWavelength(out, 457000, 923, 645.0); case 215124: return setParamChemWavelength(out, 457000, 923, 670.0); case 215125: return setParamChemWavelength(out, 457000, 923, 800.0); case 215126: return setParamChemWavelength(out, 457000, 923, 858.0); case 215127: return setParamChemWavelength(out, 457000, 923, 865.0); case 215128: return setParamChemWavelength(out, 457000, 923, 1020.0); case 215129: return setParamChemWavelength(out, 457000, 923, 1064.0); case 215130: return setParamChemWavelength(out, 457000, 923, 1240.0); case 215131: return setParamChemWavelength(out, 457000, 923, 1640.0); case 215132: return setParamChemWavelength(out, 458000, 922, 340.0); case 215133: return setParamChemWavelength(out, 458000, 922, 355.0); case 215134: return setParamChemWavelength(out, 458000, 922, 380.0); case 215135: return setParamChemWavelength(out, 458000, 922, 400.0); case 215136: return setParamChemWavelength(out, 458000, 922, 440.0); case 215137: return setParamChemWavelength(out, 458000, 922, 469.0); case 215138: return setParamChemWavelength(out, 458000, 922, 500.0); case 215139: return setParamChemWavelength(out, 458000, 922, 532.0); case 215140: return setParamChemWavelength(out, 458000, 922, 550.0); case 215141: return setParamChemWavelength(out, 458000, 922, 645.0); case 215142: return setParamChemWavelength(out, 458000, 922, 670.0); case 215143: return setParamChemWavelength(out, 458000, 922, 800.0); case 215144: return setParamChemWavelength(out, 458000, 922, 858.0); case 215145: return setParamChemWavelength(out, 458000, 922, 865.0); case 215146: return setParamChemWavelength(out, 458000, 922, 1020.0); case 215147: return setParamChemWavelength(out, 458000, 922, 1064.0); case 215148: return setParamChemWavelength(out, 458000, 922, 1240.0); case 215149: return setParamChemWavelength(out, 458000, 922, 1640.0); case 215150: return setParamChemWavelength(out, 459000, 922, 340.0); case 215151: return setParamChemWavelength(out, 459000, 922, 355.0); case 215152: return setParamChemWavelength(out, 459000, 922, 380.0); case 215153: return setParamChemWavelength(out, 459000, 922, 400.0); case 215154: return setParamChemWavelength(out, 459000, 922, 440.0); case 215155: return setParamChemWavelength(out, 459000, 922, 469.0); case 215156: return setParamChemWavelength(out, 459000, 922, 500.0); case 215157: return setParamChemWavelength(out, 459000, 922, 532.0); case 215158: return setParamChemWavelength(out, 459000, 922, 550.0); case 215159: return setParamChemWavelength(out, 459000, 922, 645.0); case 215160: return setParamChemWavelength(out, 459000, 922, 670.0); case 215161: return setParamChemWavelength(out, 459000, 922, 800.0); case 215162: return setParamChemWavelength(out, 459000, 922, 858.0); case 215163: return setParamChemWavelength(out, 459000, 922, 865.0); case 215164: return setParamChemWavelength(out, 459000, 922, 1020.0); case 215165: return setParamChemWavelength(out, 459000, 922, 1064.0); case 215166: return setParamChemWavelength(out, 459000, 922, 1240.0); case 215167: return setParamChemWavelength(out, 459000, 922, 1640.0); case 215176: return setParamChemWavelength(out, 472000, 922, 2130.0); case 215177: return setParamChemWavelength(out, 457000, 923, 2130.0); case 215178: return setParamChemWavelength(out, 458000, 922, 2130.0); case 215179: return setParamChemWavelength(out, 459000, 922, 2130.0); case 215180: return setParamChemWavelength(out, 462000, 922, 355.0); case 215181: return setParamChemWavelength(out, 462000, 922, 532.0); case 215182: return setParamChemWavelength(out, 462000, 922, 1064.0); case 215183: return setParamChemWavelength(out, 460000, 922, 355.0); case 215184: return setParamChemWavelength(out, 460000, 922, 532.0); case 215185: return setParamChemWavelength(out, 460000, 922, 1064.0); case 215186: return setParamChemWavelength(out, 461000, 922, 355.0); case 215187: return setParamChemWavelength(out, 461000, 922, 532.0); case 215188: return setParamChemWavelength(out, 461000, 922, 1064.0); case 215189: return setParamChem(out, 453000, 912); case 215190: return setParamChem(out, 453000, 913); case 215191: return setParamChem(out, 406000, 912); case 215192: return setParamChem(out, 406000, 913); case 215193: return setParamChem(out, 407000, 912); case 215194: return setParamChem(out, 407000, 913); case 215195: return setParamChem(out, 410000, 912); case 215196: return setParamChem(out, 410000, 913); case 215197: return setParamChem(out, 411000, 912); case 215198: return setParamChem(out, 411000, 913); case 215199: return setParamChem(out, 451000, 912); case 215200: return setParamChem(out, 451000, 913); case 215201: return setParamChem(out, 401000, 912); case 215202: return setParamChem(out, 401000, 913); case 215203: return setParamChemWavelength(out, 457000, 912, 550.0); case 215204: return setParamChemWavelength(out, 457000, 913, 550.0); case 215205: return setParamChem(out, 453000, 914); case 215206: return setParamChem(out, 406000, 914); case 215207: return setParamChem(out, 407000, 914); case 215208: return setParamChem(out, 410000, 914); case 215209: return setParamChem(out, 411000, 914); case 215210: return setParamChem(out, 451000, 914); case 215211: return setParamChem(out, 401000, 914); case 215212: return setParamChem(out, 453000, 915); case 215213: return setParamChem(out, 406000, 915); case 215214: return setParamChem(out, 407000, 915); case 215215: return setParamChem(out, 410000, 915); case 215216: return setParamChem(out, 411000, 915); case 215217: return setParamChem(out, 451000, 915); case 215218: return setParamChem(out, 401000, 915); case 215219: return setParamChem(out, 453000, 916); case 215220: return setParamChem(out, 406000, 916); case 215221: return setParamChem(out, 407000, 916); case 215222: return setParamChem(out, 410000, 916); case 215223: return setParamChem(out, 411000, 916); case 215224: return setParamChem(out, 451000, 916); case 215225: return setParamChem(out, 401000, 916); case 215226: return setParamChemWavelength(out, 457000, 918, 550.0); case 217003: return setParamChem(out, 402000, 3); case 217004: return setParamChem(out, 402000, 4); case 217006: return setParamChem(out, 402000, 6); case 217007: return setParamChem(out, 402000, 7); case 217009: return setParamChem(out, 402000, 9); case 217010: return setParamChem(out, 402000, 10); case 217011: return setParamChem(out, 402000, 311); case 217012: return setParamChem(out, 402000, 12); case 217013: return setParamChem(out, 402000, 13); case 217014: return setParamChem(out, 402000, 14); case 217015: return setParamChem(out, 402000, 15); case 217016: return setParamChem(out, 402000, 16); case 217018: return setParamChem(out, 402000, 18); case 217019: return setParamChem(out, 402000, 19); case 217020: return setParamChem(out, 402000, 20); case 217021: return setParamChem(out, 402000, 21); case 217022: return setParamChem(out, 402000, 22); case 217023: return setParamChem(out, 402000, 23); case 217024: return setParamChem(out, 402000, 24); case 217026: return setParamChem(out, 402000, 26); case 217027: return setParamChem(out, 402000, 27); case 217028: return setParamChem(out, 402000, 28); case 217029: return setParamChem(out, 402000, 29); case 217030: return setParamChem(out, 402000, 30); case 217032: return setParamChem(out, 402000, 32); case 217033: return setParamChem(out, 402000, 33); case 217034: return setParamChem(out, 402000, 34); case 217035: return setParamChem(out, 402000, 35); case 217036: return setParamChem(out, 402000, 36); case 217037: return setParamChem(out, 402000, 37); case 217038: return setParamChem(out, 402000, 38); case 217039: return setParamChem(out, 402000, 39); case 217040: return setParamChem(out, 402000, 40); case 217041: return setParamChem(out, 402000, 41); case 217042: return setParamChem(out, 402000, 42); case 217043: return setParamChem(out, 402000, 43); case 217044: return setParamChem(out, 402000, 44); case 217045: return setParamChem(out, 402000, 45); case 217046: return setParamChem(out, 402000, 46); case 217047: return setParamChem(out, 402000, 47); case 217048: return setParamChem(out, 402000, 48); case 217049: return setParamChem(out, 402000, 49); case 217050: return setParamChem(out, 402000, 50); case 217051: return setParamChem(out, 402000, 900); case 217052: return setParamChem(out, 402000, 52); case 217053: return setParamChem(out, 402000, 53); case 217054: return setParamChem(out, 402000, 112); case 217055: return setParamChem(out, 402000, 55); case 217056: return setParamChem(out, 402000, 56); case 217057: return setParamChem(out, 402000, 57); case 217058: return setParamChem(out, 402000, 58); case 217059: return setParamChem(out, 402000, 59); case 217063: return setParamChem(out, 402000, 63); case 217064: return setParamChem(out, 402000, 64); case 217065: return setParamChem(out, 402000, 65); case 217066: return setParamChem(out, 402000, 66); case 217067: return setParamChem(out, 402000, 67); case 217068: return setParamChem(out, 402000, 68); case 217069: return setParamChem(out, 402000, 69); case 217070: return setParamChem(out, 402000, 70); case 217071: return setParamChem(out, 402000, 71); case 217072: return setParamChem(out, 402000, 72); case 217073: return setParamChem(out, 402000, 73); case 217074: return setParamChem(out, 402000, 74); case 217075: return setParamChem(out, 402000, 75); case 217076: return setParamChem(out, 402000, 76); case 217077: return setParamChem(out, 402000, 77); case 217078: return setParamChem(out, 402000, 78); case 217079: return setParamChem(out, 402000, 79); case 217080: return setParamChem(out, 402000, 80); case 217082: return setParamChem(out, 402000, 82); case 217083: return setParamChem(out, 402000, 83); case 217085: return setParamChem(out, 402000, 85); case 217086: return setParamChem(out, 402000, 86); case 217099: return setParamChem(out, 402000, 99); case 217100: return setParamChem(out, 402000, 100); case 217101: return setParamChem(out, 402000, 101); case 217107: return setParamChem(out, 402000, 107); case 217118: return setParamChem(out, 402000, 118); case 217159: return setParamChem(out, 402000, 159); case 217161: return setParamChem(out, 402000, 161); case 217169: return setParamChem(out, 402000, 169); case 217173: return setParamChem(out, 402000, 173); case 217174: return setParamChem(out, 402000, 174); case 217175: return setParamChem(out, 402000, 175); case 217176: return setParamChem(out, 402000, 176); case 217177: return setParamChem(out, 402000, 177); case 217178: return setParamChem(out, 402000, 178); case 217186: return setParamChem(out, 402000, 186); case 217187: return setParamChem(out, 402000, 187); case 217188: return setParamChem(out, 402000, 188); case 217189: return setParamChem(out, 402000, 189); case 217190: return setParamChem(out, 402000, 190); case 217191: return setParamChem(out, 402000, 191); case 217192: return setParamChem(out, 402000, 192); case 217193: return setParamChem(out, 402000, 193); case 217194: return setParamChem(out, 402000, 194); case 217195: return setParamChem(out, 402000, 195); case 217196: return setParamChem(out, 402000, 196); case 217197: return setParamChem(out, 402000, 197); case 217198: return setParamChem(out, 402000, 198); case 217199: return setParamChem(out, 402000, 199); case 217200: return setParamChem(out, 402000, 200); case 217201: return setParamChem(out, 402000, 201); case 217202: return setParamChem(out, 402000, 202); case 217203: return setParamChem(out, 402000, 203); case 217204: return setParamChem(out, 402000, 204); case 217206: return setParamChem(out, 402000, 917); case 217222: return setParamChem(out, 402000, 222); case 217224: return setParamChem(out, 402000, 224); case 217225: return setParamChem(out, 402000, 225); case 217226: return setParamChem(out, 402000, 226); case 217227: return setParamChem(out, 402000, 227); case 217228: return setParamChem(out, 402000, 228); case 217229: return setParamChem(out, 402000, 229); case 217230: return setParamChem(out, 402000, 230); case 218003: return setParamChem(out, 401000, 3); case 218004: return setParamChem(out, 401000, 404); case 218006: return setParamChem(out, 401000, 6); case 218007: return setParamChem(out, 401000, 7); case 218009: return setParamChem(out, 401000, 9); case 218010: return setParamChem(out, 401000, 10); case 218011: return setParamChem(out, 401000, 311); case 218012: return setParamChem(out, 401000, 12); case 218013: return setParamChem(out, 401000, 13); case 218014: return setParamChem(out, 401000, 14); case 218015: return setParamChem(out, 401000, 15); case 218016: return setParamChem(out, 401000, 16); case 218018: return setParamChem(out, 401000, 18); case 218019: return setParamChem(out, 401000, 19); case 218020: return setParamChem(out, 401000, 20); case 218021: return setParamChem(out, 401000, 21); case 218022: return setParamChem(out, 401000, 22); case 218023: return setParamChem(out, 401000, 23); case 218024: return setParamChem(out, 401000, 24); case 218026: return setParamChem(out, 401000, 26); case 218027: return setParamChem(out, 401000, 27); case 218028: return setParamChem(out, 401000, 28); case 218029: return setParamChem(out, 401000, 29); case 218030: return setParamChem(out, 401000, 30); case 218032: return setParamChem(out, 401000, 32); case 218033: return setParamChem(out, 401000, 33); case 218034: return setParamChem(out, 401000, 34); case 218035: return setParamChem(out, 401000, 35); case 218036: return setParamChem(out, 401000, 36); case 218037: return setParamChem(out, 401000, 37); case 218038: return setParamChem(out, 401000, 38); case 218039: return setParamChem(out, 401000, 39); case 218040: return setParamChem(out, 401000, 40); case 218041: return setParamChem(out, 401000, 41); case 218042: return setParamChem(out, 401000, 42); case 218043: return setParamChem(out, 401000, 43); case 218044: return setParamChem(out, 401000, 44); case 218045: return setParamChem(out, 401000, 45); case 218046: return setParamChem(out, 401000, 46); case 218047: return setParamChem(out, 401000, 47); case 218048: return setParamChem(out, 401000, 48); case 218049: return setParamChem(out, 401000, 49); case 218050: return setParamChem(out, 401000, 50); case 218051: return setParamChem(out, 401000, 900); case 218052: return setParamChem(out, 401000, 52); case 218053: return setParamChem(out, 401000, 53); case 218054: return setParamChem(out, 401000, 112); case 218055: return setParamChem(out, 401000, 55); case 218056: return setParamChem(out, 401000, 56); case 218057: return setParamChem(out, 401000, 57); case 218058: return setParamChem(out, 401000, 58); case 218059: return setParamChem(out, 401000, 359); case 218063: return setParamChem(out, 401000, 63); case 218064: return setParamChem(out, 401000, 64); case 218065: return setParamChem(out, 401000, 65); case 218066: return setParamChem(out, 401000, 66); case 218067: return setParamChem(out, 401000, 67); case 218068: return setParamChem(out, 401000, 68); case 218069: return setParamChem(out, 401000, 69); case 218070: return setParamChem(out, 401000, 70); case 218071: return setParamChem(out, 401000, 71); case 218072: return setParamChem(out, 401000, 72); case 218073: return setParamChem(out, 401000, 73); case 218074: return setParamChem(out, 401000, 74); case 218075: return setParamChem(out, 401000, 75); case 218076: return setParamChem(out, 401000, 76); case 218077: return setParamChem(out, 401000, 77); case 218078: return setParamChem(out, 401000, 78); case 218079: return setParamChem(out, 401000, 79); case 218080: return setParamChem(out, 401000, 80); case 218082: return setParamChem(out, 401000, 82); case 218083: return setParamChem(out, 401000, 83); case 218085: return setParamChem(out, 401000, 85); case 218086: return setParamChem(out, 401000, 86); case 218099: return setParamChem(out, 401000, 99); case 218100: return setParamChem(out, 401000, 100); case 218101: return setParamChem(out, 401000, 101); case 218107: return setParamChem(out, 401000, 107); case 218117: return setParamChem(out, 401000, 14); case 218159: return setParamChem(out, 401000, 159); case 218161: return setParamChem(out, 401000, 161); case 218169: return setParamChem(out, 401000, 169); case 218173: return setParamChem(out, 401000, 173); case 218174: return setParamChem(out, 401000, 174); case 218175: return setParamChem(out, 401000, 175); case 218176: return setParamChem(out, 401000, 176); case 218177: return setParamChem(out, 401000, 177); case 218178: return setParamChem(out, 401000, 178); case 218186: return setParamChem(out, 401000, 186); case 218187: return setParamChem(out, 401000, 187); case 218188: return setParamChem(out, 401000, 188); case 218189: return setParamChem(out, 401000, 189); case 218190: return setParamChem(out, 401000, 190); case 218191: return setParamChem(out, 401000, 191); case 218192: return setParamChem(out, 401000, 192); case 218193: return setParamChem(out, 401000, 193); case 218194: return setParamChem(out, 401000, 194); case 218195: return setParamChem(out, 401000, 195); case 218196: return setParamChem(out, 401000, 196); case 218197: return setParamChem(out, 401000, 197); case 218198: return setParamChem(out, 401000, 198); case 218199: return setParamChem(out, 401000, 199); case 218200: return setParamChem(out, 401000, 200); case 218201: return setParamChem(out, 401000, 201); case 218202: return setParamChem(out, 401000, 202); case 218203: return setParamChem(out, 401000, 203); case 218204: return setParamChem(out, 401000, 204); case 218206: return setParamChem(out, 401000, 917); case 218221: return setParamChem(out, 401000, 8); case 218222: return setParamChem(out, 401000, 222); case 218224: return setParamChem(out, 401000, 224); case 218225: return setParamChem(out, 401000, 225); case 218226: return setParamChem(out, 401000, 226); case 218227: return setParamChem(out, 401000, 227); case 218228: return setParamChem(out, 401000, 228); case 218229: return setParamChem(out, 401000, 229); case 218230: return setParamChem(out, 401000, 230); case 219207: return setParamChem(out, 469000, 9); case 219208: return setParamChem(out, 469000, 311); case 219209: return setParamChem(out, 469000, 12); case 219212: return setParamChem(out, 469000, 99); case 219213: return setParamChem(out, 469000, 100); case 219219: return setParamChem(out, 469000, 226); case 219220: return setParamChem(out, 469000, 224); case 222001: return setParamChem(out, 445000, 236); case 222006: return setParamChem(out, 445000, 6); case 222013: return setParamChem(out, 445000, 13); case 222015: return setParamChem(out, 445000, 15); case 222017: return setParamChem(out, 445000, 8); case 222019: return setParamChem(out, 445000, 19); case 222027: return setParamChem(out, 445000, 27); case 222031: return setParamChem(out, 445000, 17); case 222033: return setParamChem(out, 445000, 33); case 223006: return setParamChem(out, 444000, 6); case 223013: return setParamChem(out, 444000, 13); case 223015: return setParamChem(out, 444000, 15); case 223017: return setParamChem(out, 444000, 8); case 223019: return setParamChem(out, 444000, 19); case 223026: return setParamChem(out, 444000, 26); case 223027: return setParamChem(out, 444000, 27); case 223031: return setParamChem(out, 444000, 17); case 223033: return setParamChem(out, 444000, 33); default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertChemical", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/waveStreams.h0000664000175000017500000000336615246725757024726 0ustar alastairalastair/* * (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 waveStreams.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::rules::impl { /// @brief Convert wave streams template inline void convertWaveStreams(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; if (get_or_throw(in, "stream") == "wave") { set_or_throw(out, "stream", "oper"); } else if (get_or_throw(in, "stream") == "waef") { set_or_throw(out, "stream", "enfo"); } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertWaveStreams", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/mappings/rules/local2wmo.h0000664000175000017500000001640115246725757024316 0ustar alastairalastair/* * (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 local2wmo.h /// @brief Conversion rules used by the mars2mars mapper. #pragma once #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" #include "metkit/mars2mars/mappings/rules/common.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" #include "metkit/mars2mars/utils/paramMatcher.h" namespace metkit::mars2mars::rules::impl { /// @brief Assign `param`, and `scaleFactor` together. template inline void setParamScale(OutDict_t& out, eckit::LocalConfiguration& misc, long param, double scaleValuesBy) { using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { set_or_throw(out, "param", param); set_or_throw(misc, "scaleValuesBy", scaleValuesBy); } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to set param and/or scaleFactor in output dictionaries", Here())); } } /// @brief Convert surface-like legacy requests into sol layer output. template inline void convertLocal2WMO(const InDict_t& in, OutDict_t& out, eckit::LocalConfiguration& misc, const OptDict_t& opts) { using metkit::mars2mars::utils::dict_traits::get_or_throw; using metkit::mars2mars::utils::dict_traits::set_or_throw; using metkit::mars2mars::utils::exceptions::Mars2marsGenericException; try { (void)opts; const auto param = get_or_throw(in, "param"); switch (param) { // Cloud cover (0-1 -> %) case 164: return setParamScale(out, misc, 228164, 100.0); case 186: return setParamScale(out, misc, 3073, 100.0); case 187: return setParamScale(out, misc, 3074, 100.0); case 188: return setParamScale(out, misc, 3075, 100.0); // Precipitation (m -> kg m-2) case 143: return setParamScale(out, misc, 228143, 1000.0); case 228: return setParamScale(out, misc, 228228, 1000.0); // Runoff water (m -> kg m-2) case 205: return setParamScale(out, misc, 231002, 1000.0); // Snowfall water equivalent (m -> kg m-2) case 144: return setParamScale(out, misc, 228144, 1000.0); // Runoff and water cycle (m -> kg m-2) case 8: return setParamScale(out, misc, 231010, 1000.0); case 9: return setParamScale(out, misc, 231012, 1000.0); case 141: return setParamScale(out, misc, 228141, 1000.0); case 142: return setParamScale(out, misc, 3062, 1000.0); case 182: return setParamScale(out, misc, 260259, 1000.0); case 198: return setParamScale(out, misc, 160198, 1000.0); case 239: return setParamScale(out, misc, 231057, 1000.0); case 240: return setParamScale(out, misc, 231058, 1000.0); case 228216: return setParamScale(out, misc, 231001, 1000.0); // Albedo (0-1 -> %) (for ERA) case 15: return setParamScale(out, misc, 210199, 100.0); case 16: return setParamScale(out, misc, 210198, 100.0); case 17: return setParamScale(out, misc, 210261, 100.0); case 18: return setParamScale(out, misc, 210260, 100.0); case 32: return setParamScale(out, misc, 228032, 100.0); case 174: setParamScale(out, misc, 260509, 100.0); set_or_throw(misc, "typeOfGeneratingProcess", 9); // Climatological return; case 243: return setParamScale(out, misc, 260509, 100.0); case 210186: return setParamScale(out, misc, 210201, 100.0); case 210187: return setParamScale(out, misc, 210202, 100.0); case 210188: return setParamScale(out, misc, 210200, 100.0); case 210189: return setParamScale(out, misc, 210263, 100.0); case 210190: return setParamScale(out, misc, 210264, 100.0); case 210191: return setParamScale(out, misc, 210262, 100.0); // Water cycle (m -> kg m-2) (for ERA) case 44: return setParamScale(out, misc, 231003, 1000.0); case 45: return setParamScale(out, misc, 3099, 1000.0); case 228251: return setParamScale(out, misc, 231005, 1000.0); // Total column ozone: tco3 -> tcioz (kg m-2 -> DU, 1 DU = 2.1415e-5 kg m-2) case 206: return setParamScale(out, misc, 260132, 46698.05); // Time-mean rate (m s-1 -> kg m-2 s-1) case 172008: return setParamScale(out, misc, 235020, 1000.0); case 172009: return setParamScale(out, misc, 235021, 1000.0); case 172044: return setParamScale(out, misc, 235023, 1000.0); case 172045: return setParamScale(out, misc, 235024, 1000.0); case 172142: return setParamScale(out, misc, 235029, 1000.0); case 172143: return setParamScale(out, misc, 235030, 1000.0); case 172144: return setParamScale(out, misc, 235031, 1000.0); case 172182: return setParamScale(out, misc, 235043, 1000.0); case 172205: return setParamScale(out, misc, 235048, 1000.0); case 172228: return setParamScale(out, misc, 235055, 1000.0); case 235141: return setParamScale(out, misc, 235078, 1000.0); // Time-mean cloud cover and albedo (0-1 -> %) case 235186: return setParamScale(out, misc, 235108, 100.0); case 235187: return setParamScale(out, misc, 235109, 100.0); case 235188: return setParamScale(out, misc, 235110, 100.0); case 235243: return setParamScale(out, misc, 235263, 100.0); default: break; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2marsGenericException("Failed to convert input dictionary in convertLocal2WMO", Here())); } } } // namespace metkit::mars2mars::rules::impl metkit-1.20.2/src/metkit/mars2mars/api/0000775000175000017500000000000015246725757020045 5ustar alastairalastairmetkit-1.20.2/src/metkit/mars2mars/api/Mars2Mars.cc0000664000175000017500000000605215246725757022166 0ustar alastairalastair/* * (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 Mars2Mars.cc /// @brief Implementation of the public Mars2Mars conversion API. /// /// The implementation forwards each supported conversion through the rule /// layer and wraps the result in the common API error-handling policy. /// #include "Mars2Mars.h" #include "eckit/config/LocalConfiguration.h" #include "eckit/exception/Exceptions.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars2mars/mappings/mappings.h" // dictionary access traits #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_eckit_configuration.h" #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_mars_request.h" #include "metkit/mars2mars/utils/dictionary_traits/dictaccess_options.h" #include "metkit/mars2mars/utils/dictionary_traits/dictionary_access_traits.h" // Error handling utilities #include "Mars2MarsApiErrorHandling.h" #include "metkit/mars2mars/api/readOptionsFromInitializerList.h" #include "metkit/mars2mars/api/readOptionsFromLocalConfiguration.h" namespace metkit::mars2mars { // ----------------------------------------------------------------------------- // Mars2Mars construction // ----------------------------------------------------------------------------- /// @brief Default construct a Mars2Mars converter. Mars2Mars::Mars2Mars() : opts_{} {} Mars2Mars::Mars2Mars(const Options& opts) : opts_{opts} {} Mars2Mars::Mars2Mars(const eckit::LocalConfiguration& opts) : opts_{detail::readOptions(opts)} {} Mars2Mars::Mars2Mars(OptionList opts) : opts_{detail::readOptions(opts)} {} /// @brief Convert an `eckit::LocalConfiguration` request. template <> Mars2MarsResult Mars2Mars::convert( const eckit::LocalConfiguration& mars) { using metkit::mars2mars::utils::exceptions::withMars2MarsApiErrorHandling; return withMars2MarsApiErrorHandling>( "Mars2Mars::convert", opts_, [&]() { return rules::convertAll(mars, opts_); }, Here()); } /// @brief Convert a `metkit::mars::MarsRequest` request. template <> Mars2MarsResult Mars2Mars::convert( const metkit::mars::MarsRequest& mars) { using metkit::mars2mars::utils::exceptions::withMars2MarsApiErrorHandling; return withMars2MarsApiErrorHandling>( "Mars2Mars::convert", opts_, [&]() { return rules::convertAll(mars, opts_); }, Here()); } } // namespace metkit::mars2mars metkit-1.20.2/src/metkit/mars2mars/api/readOptionsFromInitializerList.h0000664000175000017500000000557215246725757026402 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromInitializerList.h /// @brief Parse Mars2Mars Options from a compact initializer list. /// /// This file is intended to be included into `Mars2Mars.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/value/Value.h" #include "metkit/mars2mars/api/Mars2Mars.h" #include "metkit/mars2mars/api/Options.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::detail { namespace exceptions = metkit::mars2mars::utils::exceptions; [[noreturn]] inline void throwInvalidOptionType(std::string_view key, const eckit::Value& value, std::string_view expected) { throw exceptions::Mars2marsDictException("Option `" + std::string(key) + "` has value type `" + value.typeName() + "`; expected " + std::string(expected), Here()); } inline bool readBool(std::string_view key, const eckit::Value& value) { if (!value.isBool()) { throwInvalidOptionType(key, value, "bool"); } return value.as(); } inline std::string readString(std::string_view key, const eckit::Value& value) { if (!value.isString()) { throwInvalidOptionType(key, value, "string"); } return value.as(); } inline void applyOption(metkit::mars2mars::Options& opts, std::string_view key, const eckit::Value& value) { if (key == "saveErrorStack") { opts.saveErrorStack = readBool(key, value); return; } if (key == "skipSection3") { opts.skipSection3 = readBool(key, value); return; } if (key == "errorStackPath") { opts.errorStackPath = readString(key, value); return; } if (key == "printErrorStackToStdErr") { opts.printErrorStackToStdErr = readBool(key, value); return; } if (key == "tryFixBadInput_ZeroAccumulation") { opts.tryFixBadInput_ZeroAccumulation = readBool(key, value); return; } throw exceptions::Mars2marsDictException("Unknown Mars2Mars option `" + std::string(key) + "`", Here()); } inline metkit::mars2mars::Options readOptions(metkit::mars2mars::Mars2Mars::OptionList entries) { metkit::mars2mars::Options opts; std::unordered_set seen; seen.reserve(entries.size()); for (const auto& [key, value] : entries) { if (!seen.insert(key).second) { throw exceptions::Mars2marsDictException("Duplicate Mars2Mars option `" + key + "`", Here()); } applyOption(opts, key, value); } return opts; } } // namespace metkit::mars2mars::detail metkit-1.20.2/src/metkit/mars2mars/api/Mars2MarsApiErrorHandling.h0000664000175000017500000002771215246725757025147 0ustar alastairalastair/* * (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 Mars2MarsApiErrorHandling.h /// @brief Common error handling for mars2mars public API calls. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/filesystem/PathName.h" #include "metkit/mars2mars/api/Options.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::utils::exceptions { namespace detail { inline bool envVarEnablesStackMode(const char* name) { const char* value = std::getenv(name); if (value == nullptr) { return false; } std::string normalized; for (const unsigned char* p = reinterpret_cast(value); *p != '\0'; ++p) { normalized.push_back(static_cast(std::tolower(*p))); } return normalized == "1" || normalized == "true" || normalized == "on" || normalized == "yes"; } /// @brief Sanitize a string for use in a generated filename. inline std::string sanitizeForFileName(const std::string& value) { std::string out; out.reserve(value.size()); for (const unsigned char c : value) { if (std::isalnum(c) || c == '_' || c == '-' || c == '.') { out.push_back(static_cast(c)); } else { out.push_back('_'); } } return out; } /// @brief Convert the current thread identifier into a filename-safe string. inline std::string currentThreadIdAsString() { std::ostringstream os; os << std::this_thread::get_id(); return sanitizeForFileName(os.str()); } /// @brief Resolve the directory where stack files are stored. inline eckit::PathName errorStackDirectory(const metkit::mars2mars::Options& opts) { return eckit::PathName(opts.errorStackPath).fullName(); } /// @brief Build a unique error-stack file path for an API entry point. inline eckit::PathName makeErrorStackFilePath(const metkit::mars2mars::Options& opts, const std::string& apiName) { static std::atomic counter{0}; const eckit::PathName dir = errorStackDirectory(opts); try { dir.mkdir(); } catch (const std::exception& e) { throw eckit::UserError("Unable to create Mars2Mars error stack directory `" + dir.asString() + "`: " + e.what(), Here()); } std::ostringstream fileName; fileName << "mars2mars-error-stack-" << sanitizeForFileName(apiName) << "-pid" << static_cast(::getpid()) << "-tid" << currentThreadIdAsString() << "-" << counter.fetch_add(1, std::memory_order_relaxed) << ".log"; return dir / fileName.str(); } /// @brief Format an eckit code location as file:line. inline std::string formatCodeLocation(const eckit::CodeLocation& loc) { std::ostringstream os; os << loc.file() << ":" << loc.line(); return os.str(); } /// @brief Information extracted from the innermost exception. struct InnermostExceptionInfo { std::string message; eckit::CodeLocation location; }; /// @brief Extract the location from an eckit exception. /// /// If the exception is not an eckit exception, fall back to the API boundary /// location. inline eckit::CodeLocation exceptionLocationOrFallback(const std::exception& exception, const eckit::CodeLocation& fallback) { if (const auto* eckitException = dynamic_cast(&exception)) { return eckitException->location(); } return fallback; } /// @brief Recursively find the innermost std::nested_exception payload. inline InnermostExceptionInfo innermostExceptionInfo(const std::exception& exception, const eckit::CodeLocation& fallbackLocation) { const InnermostExceptionInfo current{ exception.what(), exceptionLocationOrFallback(exception, fallbackLocation), }; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return current; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return current; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { return innermostExceptionInfo(nested, current.location); } catch (...) { return InnermostExceptionInfo{ "", current.location, }; } return current; } /// @brief Serialize a mars2mars exception stack to a stream. inline void writeStructuredExceptionFrame(const std::exception& exception, std::ostream& out, std::size_t level, std::size_t frame) { const std::string pad = indent(level); out << pad << "+ " << std::string(lineSize, '=') << '\n' << pad << "+ frame " << frame << '\n' << pad << "+ " << std::string(lineSize, '-') << '\n'; if (const auto* me = dynamic_cast(&exception)) { me->printFrame(pad, out); } else { out << pad << "+ message: " << exception.what() << '\n'; } out << pad << "+ " << std::string(lineSize, '+') << '\n'; const auto* nested = dynamic_cast(&exception); if (nested == nullptr) { return; } const std::exception_ptr nestedPtr = nested->nested_ptr(); if (!nestedPtr) { return; } try { std::rethrow_exception(nestedPtr); } catch (const std::exception& nested) { writeStructuredExceptionFrame(nested, out, level + 1, frame + 1); } catch (...) { out << pad << "+ message: \n"; } } /// @brief Write the structured API-boundary stack header to a stream. inline void writeStructuredStackHeader(std::ostream& out, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation(), const std::string* errorStackFilePath = nullptr) { out << "Mars2Mars API failure\n"; out << "API entry point: " << apiName << "\n"; if (errorStackFilePath != nullptr) { out << "Error stack file: " << *errorStackFilePath << "\n"; } out << "\n"; out << "Nested exception stack:\n"; out << "-----------------------\n"; out << "+ " << std::string(lineSize, '=') << '\n' << "+ frame 0\n" << "+ " << std::string(lineSize, '-') << '\n'; out << "+ file: " << loc.file() << "\n" << "+ function: " << loc.func() << "\n" << "+ line: " << loc.line() << "\n" << "+ link: " << loc.file() << ":" << loc.line() << "\n" << "+ message: Error encountered at: " << apiName << "\n" << "+ " << std::string(lineSize, '+') << '\n'; } /// @brief Serialize a mars2mars exception stack to disk. inline void writeMars2MarsExceptionStackToFile(const Mars2marsGenericException& exception, const std::string& apiName, const eckit::PathName& errorStackFilePath, const eckit::CodeLocation& loc = eckit::CodeLocation()) { std::ofstream out(errorStackFilePath.asString()); if (!out) { throw eckit::UserError("Mars2Mars conversion failed, but the error stack file could not be opened: `" + errorStackFilePath.asString() + "`", Here()); } const std::string path = errorStackFilePath.asString(); writeStructuredStackHeader(out, apiName, loc, &path); writeStructuredExceptionFrame(exception, out, 1, 1); } /// @brief Print a mars2mars exception stack to standard error. inline void writeMars2MarsExceptionStackToStdErr(const Mars2marsGenericException& exception, const std::string& apiName, const eckit::CodeLocation& loc = eckit::CodeLocation()) { writeStructuredStackHeader(std::cerr, apiName, loc); writeStructuredExceptionFrame(exception, std::cerr, 1, 1); } /// @brief Build the short public-facing error message. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner, const eckit::PathName& errorStackFilePath) { std::ostringstream os; os << "Mars2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Full error stack written to `" << errorStackFilePath.asString() << "`"; return os.str(); } /// @brief Build the short public-facing error message when stack saving is disabled. inline std::string buildPublicErrorMessage(const InnermostExceptionInfo& inner) { std::ostringstream os; os << "Mars2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << ")."; return os.str(); } /// @brief Build the public-facing message when stack-file writing also failed. inline std::string buildPublicErrorMessageWithoutStackFile(const InnermostExceptionInfo& inner, const std::exception& stackWriteError) { std::ostringstream os; os << "Mars2Mars conversion failed. " << "Innermost error: " << inner.message << " " << "(" << formatCodeLocation(inner.location) << "). " << "Additionally, the full error stack could not be written: " << stackWriteError.what(); return os.str(); } } // namespace detail /// @brief Execute a callable under the mars2mars API error boundary. /// /// @tparam Result /// Callable return type. /// /// @tparam Callable /// Callable type. /// /// @param[in] apiName /// API entry-point name used in diagnostics. /// /// @param[in] callable /// Operation to execute. /// /// @param[in] loc /// Source location attached to user-facing failures. template Result withMars2MarsApiErrorHandling(const std::string& apiName, const metkit::mars2mars::Options& opts, Callable&& callable, const eckit::CodeLocation& loc = eckit::CodeLocation()) { try { return std::forward(callable)(); } catch (const Mars2marsGenericException& e) { const detail::InnermostExceptionInfo inner = detail::innermostExceptionInfo(e, loc); const bool printErrorStackToStdErr = opts.printErrorStackToStdErr || detail::envVarEnablesStackMode("MARS2MARS_PRINT_STACK_TO_STDERR"); const bool saveErrorStack = opts.saveErrorStack || detail::envVarEnablesStackMode("MARS2MARS_WRITE_STACK_TO_FILE"); if (printErrorStackToStdErr) { detail::writeMars2MarsExceptionStackToStdErr(e, apiName, loc); } if (!saveErrorStack) { throw eckit::UserError(detail::buildPublicErrorMessage(inner), inner.location); } try { const eckit::PathName errorStackFilePath = detail::makeErrorStackFilePath(opts, apiName); detail::writeMars2MarsExceptionStackToFile(e, apiName, errorStackFilePath, loc); throw eckit::UserError(detail::buildPublicErrorMessage(inner, errorStackFilePath), inner.location); } catch (const std::exception& stackWriteError) { throw eckit::UserError(detail::buildPublicErrorMessageWithoutStackFile(inner, stackWriteError), inner.location); } } } } // namespace metkit::mars2mars::utils::exceptions metkit-1.20.2/src/metkit/mars2mars/api/Options.h0000664000175000017500000000734715246725757021664 0ustar alastairalastair/* * (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 Options.h /// @brief Configuration options for the Mars2Mars conversion API. /// /// This header defines the public configuration structure used to control the /// behaviour of the Mars2Mars API error boundary. /// /// `Options` is a fully materialised, strongly typed policy object. A caller may /// construct it directly, or the API may populate it from another option source, /// such as an `eckit::LocalConfiguration` or an initializer list. /// /// Every member has an explicit default. Consequently, a default-constructed /// `Options` object is always valid as an option dictionary. /// /// The options control: /// /// - whether a detailed structured error stack is saved to a file; /// - where stack files are written when stack saving is enabled; /// - whether the detailed structured error stack is printed to standard error. /// /// @ingroup mars2mars_api /// #pragma once #include namespace metkit::mars2mars { namespace defaults { inline constexpr bool saveErrorStack = false; inline constexpr const char* errorStackPath = "./"; inline constexpr bool printErrorStackToStdErr = false; inline constexpr bool skipSection3 = false; inline constexpr bool tryFixBadInput_ZeroAccumulation = false; } // namespace defaults /// /// @brief API error-handling options for Mars2Mars. /// /// The structure is intentionally a plain aggregate: /// /// - direct programmatic construction remains simple; /// - every option can be copied into a stable policy snapshot; /// - dictionary traits can expose the structure through the same typed access /// API used for other Mars2Mars dictionaries; /// - adding an option does not require virtual dispatch or ownership machinery. /// struct Options { /// /// @brief Save the detailed API error stack to a file. /// /// When enabled, user-facing API failures persist the detailed structured /// exception stack to a file under `errorStackPath`, and the thrown public /// exception includes the full path to that file. /// /// @default false /// bool saveErrorStack = defaults::saveErrorStack; /// /// @brief Directory where API error-stack files are written. /// /// This path is used only when `saveErrorStack` is enabled. /// /// @default "./" /// std::string errorStackPath = defaults::errorStackPath; /// /// @brief Print the detailed API error stack to standard error. /// /// When enabled, user-facing API failures also emit the structured nested /// exception stack to `std::cerr`. /// /// @default false /// bool printErrorStackToStdErr = defaults::printErrorStackToStdErr; /// /// @brief Skip explicit encoding of GRIB Section 3. /// /// When enabled, the encoder does not encode the Grid Definition Section /// itself and leaves geometry handling to gridSpec/ecCodes. /// /// @default false /// bool skipSection3 = defaults::skipSection3; /// /// @brief Attempt to fix bad input when the accumulation period is zero. /// /// When enabled, the encoder attempts to fix bad input when the accumulation /// period is zero. /// /// @default false /// bool tryFixBadInput_ZeroAccumulation = defaults::tryFixBadInput_ZeroAccumulation; }; } // namespace metkit::mars2mars metkit-1.20.2/src/metkit/mars2mars/api/Mars2Mars.h0000664000175000017500000001057515246725757022035 0ustar alastairalastair/* * (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 Mars2Mars.h /// @brief Public conversion API for transforming MARS requests. /// /// This header exposes the primary mars2mars entry point. Callers provide a /// supported dictionary type and receive a structured result containing the /// converted MARS request plus auxiliary conversion metadata. /// /// The API is intentionally narrow and explicit: /// - only supported dictionary types may be converted /// - conversion is delegated to the rule engine /// - all failures are routed through the shared API error boundary /// /// @section References /// Implementation: /// - @ref Mars2Mars.cc /// /// Result type: /// - @ref Mars2MarsReturnValue.h /// /// @ingroup mars2mars_api /// #pragma once #include #include #include #include "eckit/config/LocalConfiguration.h" #include "eckit/value/Value.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars2mars/api/Options.h" #include "metkit/mars2mars/mappings/Mars2MarsReturnValue.h" namespace metkit::mars2mars { /// /// @brief Main API for converting MARS requests. /// /// The converter stores a fixed runtime options snapshot. It is a thin façade /// around the conversion rules and the shared API error handling layer. /// class Mars2Mars { public: using OptionEntry = std::pair; using OptionList = std::initializer_list; /// /// @brief Construct a Mars2Mars converter with default options. /// /// Default options correspond to standard mars2mars behavior. /// Mars2Mars(); /// /// @brief Construct a Mars2Mars converter with explicit options. /// /// @param[in] opts /// API options controlling error-stack persistence and diagnostics. /// explicit Mars2Mars(const Options& opts); /// /// @brief Construct a Mars2Mars converter from a configuration object. /// /// This constructor allows options to be provided via an /// `eckit::LocalConfiguration`, typically originating from YAML /// or JSON configuration files. /// /// @param[in] opts /// Configuration object describing converter options. /// explicit Mars2Mars(const eckit::LocalConfiguration& opts); /// /// @brief Construct a Mars2Mars converter from inline option entries. /// /// This constructor supports compact inline configuration such as: /// /// @code /// Mars2Mars{{"saveErrorStack", true}, {"errorStackPath", "./errors"}} /// @endcode /// /// @param[in] opts /// Initializer-list option entries. /// explicit Mars2Mars(OptionList opts); Mars2Mars(const Mars2Mars&) = delete; Mars2Mars(Mars2Mars&&) = delete; Mars2Mars operator=(const Mars2Mars&) = delete; Mars2Mars operator=(Mars2Mars&&) = delete; ~Mars2Mars() = default; /// @brief Convert a supported dictionary type. /// /// The primary template is deleted intentionally. Only explicit /// specializations are available through the public API. /// /// @tparam Dict_t /// Input dictionary type. /// /// @param[in] mars /// Input MARS dictionary. /// /// @return /// A converted result for the supported dictionary type. template Mars2MarsResult convert(const Dict_t& mars) = delete; private: const Options opts_; }; // ----------------------------------------------------------------------------- // Supported API specializations // ----------------------------------------------------------------------------- /// @brief Convert an `eckit::LocalConfiguration` request. template <> Mars2MarsResult Mars2Mars::convert( const eckit::LocalConfiguration& mars); /// @brief Convert a `metkit::mars::MarsRequest` request. template <> Mars2MarsResult Mars2Mars::convert( const metkit::mars::MarsRequest& mars); } // namespace metkit::mars2mars metkit-1.20.2/src/metkit/mars2mars/api/readOptionsFromLocalConfiguration.h0000664000175000017500000000416115246725757027036 0ustar alastairalastair/* * (C) Copyright 2026- ECMWF and individual contributors. * * This software is licensed under the terms of the Apache Licence Version 2.0. */ /// /// @file readOptionsFromLocalConfiguration.h /// @brief Parse Mars2Mars Options from eckit::LocalConfiguration. /// /// This file is intended to be included into `Mars2Mars.cc` so the parser can /// remain implementation-only while still being the single source of truth. /// #include #include #include "eckit/config/LocalConfiguration.h" #include "metkit/mars2mars/api/Options.h" #include "metkit/mars2mars/utils/mars2marsExceptions.h" namespace metkit::mars2mars::detail { inline bool readBoolOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isBoolean(std::string{key})) { throw utils::exceptions::Mars2marsDictException( "Option `" + std::string(key) + "` must be a bool in eckit::LocalConfiguration", Here()); } return conf.getBool(std::string{key}); } inline std::string readStringOption(const eckit::LocalConfiguration& conf, std::string_view key) { if (!conf.isString(std::string{key})) { throw utils::exceptions::Mars2marsDictException( "Option `" + std::string(key) + "` must be a string in eckit::LocalConfiguration", Here()); } return conf.getString(std::string{key}); } inline Options readOptions(const eckit::LocalConfiguration& conf) { Options opts; if (conf.has("saveErrorStack")) { opts.saveErrorStack = readBoolOption(conf, "saveErrorStack"); } if (conf.has("skipSection3")) { opts.skipSection3 = readBoolOption(conf, "skipSection3"); } if (conf.has("errorStackPath")) { opts.errorStackPath = readStringOption(conf, "errorStackPath"); } if (conf.has("printErrorStackToStdErr")) { opts.printErrorStackToStdErr = readBoolOption(conf, "printErrorStackToStdErr"); } if (conf.has("tryFixBadInput_ZeroAccumulation")) { opts.tryFixBadInput_ZeroAccumulation = readBoolOption(conf, "tryFixBadInput_ZeroAccumulation"); } return opts; } } // namespace metkit::mars2mars::detail metkit-1.20.2/src/metkit/CMakeLists.txt0000664000175000017500000001305215246725757020126 0ustar alastairalastair### config headers ecbuild_generate_config_headers( DESTINATION ${INSTALL_INCLUDE_DIR}/metkit ) configure_file( metkit_config.h.in metkit_config.h ) configure_file( metkit_version.h.in metkit_version.h ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/metkit_config.h ${CMAKE_CURRENT_BINARY_DIR}/metkit_version.h DESTINATION ${INSTALL_INCLUDE_DIR}/metkit ) ### metkit sources list( APPEND metkit_srcs metkit_version.c config/LibMetkit.cc config/LibMetkit.h mars/BaseProtocol.cc mars/BaseProtocol.h mars/ClientTask.cc mars/ClientTask.h mars/DHSProtocol.cc mars/DHSProtocol.h mars/Matcher.cc mars/Matcher.h mars/MarsExpansion.cc mars/MarsExpansion.h mars/MarsHandle.cc mars/MarsHandle.h mars/MarsLanguage.cc mars/MarsLanguage.h mars/MarsLocation.cc mars/MarsLocation.h mars/MarsParsedRequest.cc mars/MarsParsedRequest.h mars/MarsParser.cc mars/MarsParser.h mars/MarsRequest.cc mars/MarsRequest.h mars/MarsRequestHandle.cc mars/MarsRequestHandle.h mars/Parameter.cc mars/Parameter.h mars/ParamID.cc mars/ParamID.h mars/Quantile.cc mars/Quantile.h mars/RequestEnvironment.cc mars/RequestEnvironment.h mars/StepRangeNormalise.h mars/Type.cc mars/Type.h mars/TypeAny.cc mars/TypeAny.h mars/TypeDate.cc mars/TypeDate.h mars/TypeEnum.cc mars/TypeEnum.h mars/TypeExpver.cc mars/TypeExpver.h mars/TypeFloat.cc mars/TypeFloat.h mars/TypeInteger.cc mars/TypeInteger.h mars/TypeLowercase.cc mars/TypeLowercase.h mars/TypeMixed.cc mars/TypeMixed.h mars/TypeParam.cc mars/TypeParam.h mars/TypeRange.cc mars/TypeRange.h mars/TypeRegex.cc mars/TypeRegex.h mars/TypesFactory.cc mars/TypesFactory.h mars/TypeTime.cc mars/TypeTime.h mars/TypeToByList.h mars/TypeToByListQuantile.cc mars/TypeToByListQuantile.h tool/MetkitTool.cc tool/MetkitTool.h fields/FieldIndex.cc fields/FieldIndex.h fields/FieldIndexList.cc fields/FieldIndexList.h fields/SimpleFieldIndex.cc fields/SimpleFieldIndex.h hypercube/HyperCube.cc hypercube/HyperCube.h hypercube/HyperCubePayloaded.h api/metkit_c.cc api/metkit_c.h ) list( APPEND metkit_persistent_srcs mars/Param.cc mars/Param.h mars/Param mars/StepRange.cc mars/StepRange.h mars/StepRange ) list( APPEND metkit_srcs ${metkit_persistent_srcs} ) if ( HAVE_GRIB ) list( APPEND metkit_srcs pointdb/bits.h pointdb/DataSource.cc pointdb/DataSource.h pointdb/FieldIndexer.cc pointdb/FieldIndexer.h pointdb/GribDataSource.cc pointdb/GribDataSource.h pointdb/GribFieldInfo.cc pointdb/GribFieldInfo.h pointdb/GribHandleDataSource.cc pointdb/GribHandleDataSource.h pointdb/masks.h pointdb/PointIndex.cc pointdb/PointIndex.h codes/BUFRDecoder.cc codes/BUFRDecoder.h codes/GRIBDecoder.cc codes/GRIBDecoder.h codes/CodesDataContent.cc codes/CodesDataContent.h codes/BufrContent.cc codes/BufrContent.h codes/CodesSplitter.cc codes/CodesSplitter.h codes/CodesHandleDeleter.h codes/api/CodesAPI.h codes/api/CodesAPI.cc codes/api/CodesTypes.h codes/api/CodesTypes.cc codes/api/KeyIterator.h codes/api/KeyIterator.cc codes/api/GeoIterator.h ) if( NOT eccodes_HAVE_GEOGRAPHY OR NOT eccodes_HAVE_ECKIT_GEO ) list( APPEND metkit_srcs codes/LibEccodes.cc codes/LibEccodes.h ) endif() set( grib_libs eccodes ) endif () if ( HAVE_ODB ) list( APPEND metkit_srcs odb/IdMapper.cc odb/IdMapper.h odb/OdbToRequest.cc odb/OdbToRequest.h odb/OdbMetadataDecoder.cc odb/OdbMetadataDecoder.h odb/OdbDecoder.cc odb/OdbContent.cc odb/OdbContent.h odb/OdbDecoder.h odb/OdbSplitter.cc odb/OdbSplitter.h ) set( odc_libs odccore ) endif() if ( HAVE_GRIB2MARS ) list( APPEND metkit_srcs grib2mars/api/Grib2Mars.cc grib2mars/api/Grib2Mars.h grib2mars/api/Options.h grib2mars/mappings/Grib2MarsReturnValue.h ) endif() if ( HAVE_MARS2MARS ) list( APPEND metkit_srcs mars2mars/api/Mars2Mars.cc mars2mars/api/Mars2Mars.h mars2mars/api/Options.h mars2mars/mappings/Mars2MarsReturnValue.h ) endif() if ( HAVE_MARS2GRIB ) list( APPEND metkit_srcs mars2grib/api/Mars2Grib.cc mars2grib/api/Mars2Grib.h mars2grib/api/Mars2GribTestCaseGenerator.cc mars2grib/api/Mars2GribTestCaseGenerator.h mars2grib/api/Options.h ) set( mars2grib_libs eckit_geo ) endif() ecbuild_add_library( TARGET metkit INSTALL_HEADERS LISTED HEADER_DESTINATION ${INSTALL_INCLUDE_DIR}/metkit PERSISTENT ${metkit_persistent_srcs} SOURCES ${metkit_srcs} PUBLIC_INCLUDES $ $ $ # for metkit_version.h PRIVATE_LIBS "${odc_libs}" PUBLIC_LIBS eckit eckit_option ${grib_libs} ${mars2grib_libs} ) if( HAVE_MARS2GRIB_PYTHON ) add_subdirectory(mars2grib/api/pymars2grib) endif() metkit-1.20.2/src/metkit/odb/0000775000175000017500000000000015246725757016131 5ustar alastairalastairmetkit-1.20.2/src/metkit/odb/IdMapper.h0000664000175000017500000000350315246725757020004 0ustar alastairalastair/* * (C) Copyright 1996-2012 ECMWF. * * 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. */ /// @author Piotr Kuchta /// @date April 2011 /// @author Simon Smart /// @date April 2019 #ifndef metkit_odb_IdMapper_H #define metkit_odb_IdMapper_H #include #include #include #include namespace eckit { class PathName; } namespace metkit { namespace odb { //---------------------------------------------------------------------------------------------------------------------- class IdMap { public: // methods IdMap(const std::string& configFile, const std::string& fieldDelimiter = " \t", size_t numericIndex = 0, size_t alphanumericIndex = 1); ~IdMap(); std::string alphanumeric(long numeric); private: // methods std::map numeric2alpha_; }; //---------------------------------------------------------------------------------------------------------------------- class IdMapper { public: // methods static IdMapper& instance(); /// Returns true if modified bool alphanumeric(const std::string& keyword, long numeric, std::string& output); bool alphanumeric(const std::string& keyword, const std::set& numeric, std::set& output); private: // methods IdMapper(); ~IdMapper(); private: // members std::map maps_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace odb } // namespace metkit #endif metkit-1.20.2/src/metkit/odb/OdbToRequest.cc0000664000175000017500000000524615246725757021027 0ustar alastairalastair/* * (C) Copyright 1996-2012 ECMWF. * * 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. */ #include "metkit/odb/OdbToRequest.h" #include #include "eckit/io/DataHandle.h" #include "eckit/message/Message.h" #include "odc/api/Odb.h" #include "metkit/config/LibMetkit.h" #include "metkit/odb/OdbMetadataDecoder.h" using namespace eckit; using namespace odc::api; namespace metkit { using namespace mars; using namespace codes; namespace odb { //---------------------------------------------------------------------------------------------------------------------- class MarsRequestSetter : public eckit::message::MetadataGatherer { public: // methods MarsRequestSetter(MarsRequest& request) : request_(request) {} void setValue(const std::string& key, const std::string& value) override { request_.setValue(key, value); } void setValue(const std::string& key, long value) override { request_.setValue(key, value); } void setValue(const std::string& key, double value) override { request_.setValue(key, value); } private: // members MarsRequest& request_; }; //---------------------------------------------------------------------------------------------------------------------- OdbToRequest::OdbToRequest(const std::string& verb, bool one, bool constant) : verb_(verb), one_(one), onlyConstantColumns_(constant) { LOG_DEBUG_LIB(LibMetkit) << "OdbToRequest one: " << one << " constant: " << constant << std::endl; } OdbToRequest::~OdbToRequest() {} std::vector OdbToRequest::odbToRequest(DataHandle& dh) const { LOG_DEBUG_LIB(LibMetkit) << "OdbToRequest::odbToRequest() dh: " << dh << std::endl; Reader reader(dh, false); Frame frame; std::vector requests; MarsLanguage language(verb_); while ((frame = reader.next())) { Span span = frame.span(OdbMetadataDecoder::columnNames(), onlyConstantColumns_); MarsRequest r(verb_); MarsRequestSetter setter(r); OdbMetadataDecoder decoder(setter, {}, language); span.visit(decoder); if (one_ and requests.size()) { requests.back().merge(r); } else { requests.push_back(r); } } return requests; } //---------------------------------------------------------------------------------------------------------------------- } // namespace odb } // namespace metkit metkit-1.20.2/src/metkit/odb/OdbMetadataDecoder.cc0000664000175000017500000001056015246725757022075 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/odb/OdbMetadataDecoder.h" #include #include #include #include "eckit/config/Resource.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/utils/StringTools.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars/MarsRequest.h" #include "metkit/mars/Type.h" #include "metkit/odb/IdMapper.h" namespace { class OdbColumnNameMapping { public: static OdbColumnNameMapping& instance() { static OdbColumnNameMapping mapping; return mapping; } const std::vector& columnNames() { return columnNames_; } const std::map& table() { return mapping_; } private: // methods OdbColumnNameMapping() { static eckit::PathName configPath( eckit::Resource("odbMarsRequestMapping", "~metkit/share/metkit/odb/marsrequest.yaml")); eckit::YAMLConfiguration config(configPath); for (const auto& key : config.keys()) { mapping_[config.getString(key)] = eckit::StringTools::lower(key); } columnNames_.reserve(mapping_.size()); for (const auto& kv : mapping_) { columnNames_.push_back(kv.first); } } private: // members std::map mapping_; std::vector columnNames_; }; } // namespace //---------------------------------------------------------------------------------------------------------------------- namespace metkit::codes { //---------------------------------------------------------------------------------------------------------------------- const std::vector& OdbMetadataDecoder::columnNames() { return OdbColumnNameMapping::instance().columnNames(); } template void OdbMetadataDecoder::visit(const std::string& columnName, const std::set& vals, const metkit::mars::MarsLanguage& language) { auto mapitr = OdbColumnNameMapping::instance().table().find(columnName); ASSERT(mapitr != OdbColumnNameMapping::instance().table().end()); std::string keyword = eckit::StringTools::lower(mapitr->second); metkit::mars::Type* t = language.type(keyword); ASSERT(options_.valueRepresentation == eckit::message::ValueRepresentation::String); for (auto val : vals) { std::string stringVal = eckit::Translator()(val); std::string tidyVal = t->tidy(stringVal); if (stringVal == tidyVal) // if t->tidy had no effect, set the original value gather_.setValue(keyword, val); else gather_.setValue(keyword, tidyVal); } } OdbMetadataDecoder::OdbMetadataDecoder(eckit::message::MetadataGatherer& gather, const eckit::message::GetMetadataOptions& options, metkit::mars::MarsLanguage& language) : language_(language), gather_(gather), options_(options) {} void OdbMetadataDecoder::operator()(const std::string& columnName, const std::set& vals) { LOG_DEBUG_LIB(LibMetkit) << "OdbMetadataDecoder::operator() columnName: " << columnName << " vals: " << vals << std::endl; auto mapitr = OdbColumnNameMapping::instance().table().find(columnName); ASSERT(mapitr != OdbColumnNameMapping::instance().table().end()); std::set mapped; if (metkit::odb::IdMapper::instance().alphanumeric(mapitr->second, vals, mapped)) { visit(columnName, mapped, language_); } else { visit(columnName, vals, language_); } } void OdbMetadataDecoder::operator()(const std::string& columnName, const std::set& vals) { visit(columnName, vals, language_); } void OdbMetadataDecoder::operator()(const std::string& columnName, const std::set& vals) { visit(columnName, vals, language_); } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes metkit-1.20.2/src/metkit/odb/OdbContent.cc0000664000175000017500000000253515246725757020504 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #include "metkit/odb/OdbContent.h" #include "eckit/exception/Exceptions.h" #include "eckit/io/DataHandle.h" #include "eckit/io/MemoryHandle.h" namespace metkit { namespace codes { OdbContent::OdbContent(eckit::Buffer&& buffer) : frame_(std::move(buffer)) {} OdbContent::OdbContent(eckit::DataHandle& handle, size_t size) : frame_(size) { ASSERT(handle.read(frame_, frame_.size()) == frame_.size()); } OdbContent::~OdbContent() {} const void* OdbContent::data() const { return frame_; } size_t OdbContent::length() const { return frame_.size(); } eckit::DataHandle* OdbContent::readHandle() const { return new eckit::MemoryHandle(frame_, frame_.size()); } void OdbContent::write(eckit::DataHandle& handle) const { ASSERT(handle.write(frame_, frame_.size()) == frame_.size()); } void OdbContent::print(std::ostream& s) const { s << "OdbContent[]"; } } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/odb/OdbSplitter.cc0000664000175000017500000000633015246725757020675 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/odb/OdbSplitter.h" #include "eckit/io/BufferList.h" #include "eckit/io/PeekHandle.h" #include "eckit/message/Message.h" #include "metkit/config/LibMetkit.h" #include "metkit/odb/OdbContent.h" #include "metkit/odb/OdbMetadataDecoder.h" namespace metkit { namespace codes { OdbSplitter::OdbSplitter(eckit::PeekHandle& handle) : Splitter(handle), handleWrapper_(handle), reader_(handleWrapper_, false) {} OdbSplitter::~OdbSplitter() {} eckit::message::Message OdbSplitter::next() { if (!lastFrame_) { lastFrame_ = reader_.next(); if (!lastFrame_) { return eckit::message::Message(); } } eckit::BufferList buffers; odc::api::Span reference = lastFrame_.span(OdbMetadataDecoder::columnNames(), true); buffers.append(lastFrame_.encodedData()); handleWrapper_.clear(); LOG_DEBUG_LIB(LibMetkit) << "ODB frame: " << buffers.count() << ", size: " << lastFrame_.length() << ", total:" << buffers.size() << std::endl; lastFrame_ = odc::api::Frame(); //< we have consumed lastFrame_ odc::api::Frame frame; // aggregate all frames with the same metadata Span as reference Span while ((frame = reader_.next())) { odc::api::Span span = frame.span(OdbMetadataDecoder::columnNames(), true); if (span == reference) { buffers.append(frame.encodedData()); handleWrapper_.clear(); LOG_DEBUG_LIB(LibMetkit) << "ODB frame: " << buffers.count() << ", size: " << frame.length() << ", total:" << buffers.size() << std::endl; } else { // remember last frame to reuse as reference on the following next() lastFrame_ = frame; break; } } LOG_DEBUG_LIB(LibMetkit) << "Consolidating buffers of " << buffers.count() << " frames" << ", total size: " << buffers.size() << std::endl; return eckit::message::Message{new OdbContent(buffers.consolidate())}; } void OdbSplitter::print(std::ostream& s) const { s << "OdbSplitter[]"; } } // namespace codes } // namespace metkit //---------------------------------------------------------------------------------------------------------------------- namespace eckit { namespace message { template <> bool SplitterBuilder::match(eckit::PeekHandle& handle) const { unsigned char c0 = handle.peek(0); unsigned char c1 = handle.peek(1); unsigned char c2 = handle.peek(2); unsigned char c3 = handle.peek(3); unsigned char c4 = handle.peek(4); if (c0 == 0xff and c1 == 0xff and c2 == 'O' and c3 == 'D' and c4 == 'A') { return true; } return false; } } // namespace message } // namespace eckit static eckit::message::SplitterBuilder splitter; metkit-1.20.2/src/metkit/odb/OdbToRequest.h0000664000175000017500000000257315246725757020671 0ustar alastairalastair/* * (C) Copyright 1996-2012 ECMWF. * * 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. */ #ifndef metkit_OdbToRequest_H #define metkit_OdbToRequest_H #include #include #include "eckit/filesystem/PathName.h" #include "eckit/io/Length.h" #include "eckit/io/Offset.h" #include "metkit/mars/MarsRequest.h" namespace eckit { class DataHandle; } namespace metkit { using namespace mars; namespace odb { //---------------------------------------------------------------------------------------------------------------------- class OdbToRequest { public: // methods OdbToRequest(const std::string& verb, bool one, bool constant); ~OdbToRequest(); std::vector odbToRequest(eckit::DataHandle& dh) const; private: // members std::string verb_ = "retrieve"; bool one_ = false; // bool mergeSimilarBlocks_ = false; // unused bool onlyConstantColumns_ = false; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace odb } // namespace metkit #endif metkit-1.20.2/src/metkit/odb/OdbDecoder.cc0000664000175000017500000000446515246725757020443 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ #include "metkit/odb/OdbDecoder.h" #include #include "eckit/config/Resource.h" #include "eckit/config/YAMLConfiguration.h" #include "eckit/io/DataHandle.h" #include "eckit/message/Message.h" #include "eckit/serialisation/MemoryStream.h" #include "eckit/types/Types.h" #include "eckit/utils/StringTools.h" #include "metkit/mars/MarsRequest.h" #include "metkit/odb/OdbMetadataDecoder.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- bool OdbDecoder::match(const eckit::message::Message& msg) const { size_t len = msg.length(); const unsigned char* p = static_cast(msg.data()); return len >= 5 and ((p[0] == 0xff and p[1] == 0xff and p[2] == 'O' and p[3] == 'D' and p[4] == 'A')); } void OdbDecoder::getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer& gather, const eckit::message::GetMetadataOptions& options) const { std::unique_ptr handle(msg.readHandle()); handle->openForRead(); eckit::AutoClose close(*handle); odc::api::Reader reader(*handle, false); odc::api::Frame frame; metkit::mars::MarsLanguage language("retrieve"); OdbMetadataDecoder setter(gather, options, language); while ((frame = reader.next())) { odc::api::Span span = frame.span(OdbMetadataDecoder::columnNames(), true); span.visit(setter); } } eckit::Buffer OdbDecoder::decode(const eckit::message::Message& msg) const { NOTIMP; // Not relevant for MultIO hackathon. Implement as needed in future. } void OdbDecoder::print(std::ostream& s) const { s << "OdbDecoder[]"; } static OdbDecoder decoder; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/odb/OdbMetadataDecoder.h0000664000175000017500000000370515246725757021742 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #ifndef metkit_OdbMetadataDecoder_h #define metkit_OdbMetadataDecoder_h #include "eckit/message/Message.h" #include "metkit/mars/MarsLanguage.h" #include "odc/api/Odb.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------------------------- class OdbMetadataDecoder : public odc::api::SpanVisitor { public: OdbMetadataDecoder(eckit::message::MetadataGatherer& gatherer, const eckit::message::GetMetadataOptions& options, metkit::mars::MarsLanguage& language); virtual void operator()(const std::string& columnName, const std::set& vals); virtual void operator()(const std::string& columnName, const std::set& vals); virtual void operator()(const std::string& columnName, const std::set& vals); static const std::vector& columnNames(); private: // methods template void visit(const std::string& columnName, const std::set& vals, const metkit::mars::MarsLanguage& language); private: // members metkit::mars::MarsLanguage& language_; eckit::message::MetadataGatherer& gather_; eckit::message::GetMetadataOptions options_; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit #endif metkit-1.20.2/src/metkit/odb/OdbContent.h0000664000175000017500000000220415246725757020337 0ustar alastairalastair/* * (C) Copyright 2017- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #ifndef metkit_codes_OdbContent_H #define metkit_codes_OdbContent_H #include "eckit/io/Buffer.h" #include "eckit/message/MessageContent.h" namespace metkit { namespace codes { class OdbContent : public eckit::message::MessageContent { public: OdbContent(eckit::Buffer&&); OdbContent(eckit::DataHandle&, size_t size); ~OdbContent(); private: eckit::Buffer frame_; virtual size_t length() const override; const void* data() const override; virtual eckit::DataHandle* readHandle() const override; virtual void write(eckit::DataHandle& handle) const override; virtual void print(std::ostream& s) const override; }; } // namespace codes } // namespace metkit #endif metkit-1.20.2/src/metkit/odb/OdbSplitter.h0000664000175000017500000000254115246725757020537 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #ifndef metkit_OdbSplitter_h #define metkit_OdbSplitter_h #include "eckit/io/SeekableHandle.h" #include "eckit/message/Splitter.h" #include "odc/api/Odb.h" namespace eckit { class PeekHandle; } namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class OdbSplitter : public eckit::message::Splitter { public: explicit OdbSplitter(eckit::PeekHandle&); ~OdbSplitter() override; private: // members eckit::SeekableHandle handleWrapper_; odc::api::Reader reader_; odc::api::Frame lastFrame_; private: // methods virtual eckit::message::Message next() override; virtual void print(std::ostream&) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit #endif metkit-1.20.2/src/metkit/odb/IdMapper.cc0000664000175000017500000000705115246725757020144 0ustar alastairalastair/* * (C) Copyright 1996-2012 ECMWF. * * 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. */ #include "metkit/odb/IdMapper.h" #include #include #include #include "eckit/config/Resource.h" #include "eckit/filesystem/PathName.h" #include "eckit/log/Log.h" #include "eckit/utils/StringTools.h" #include "metkit/config/LibMetkit.h" using namespace eckit; namespace metkit { namespace odb { //---------------------------------------------------------------------------------------------------------------------- IdMapper::IdMapper() : maps_{{"CLASS", IdMap{"class.table"}}, {"TYPE", IdMap{"type.table"}}, {"STREAM", IdMap{"stream.table"}}, {"OBSGROUP", IdMap{"group.txt", ";", 0, 3}}} {} IdMapper::~IdMapper() {} IdMapper& IdMapper::instance() { static IdMapper themapper; return themapper; } //---------------------------------------------------------------------------------------------------------------------- bool IdMapper::alphanumeric(const std::string& keyword, long numeric, std::string& output) { auto idmap = maps_.find(StringTools::upper(keyword)); if (idmap == maps_.end()) { return false; } output = idmap->second.alphanumeric(numeric); return true; } bool IdMapper::alphanumeric(const std::string& keyword, const std::set& numeric, std::set& output) { auto idmap = maps_.find(StringTools::upper(keyword)); if (idmap == maps_.end()) { return false; } output.clear(); std::transform(numeric.begin(), numeric.end(), std::inserter(output, output.begin()), [&idmap](long l) { return idmap->second.alphanumeric(l); }); return true; } static PathName& codes_path() { static PathName p = Resource("$ODB_CODES", "~metkit/share/metkit/odb"); return p; } IdMap::IdMap(const std::string& configFile, const std::string& fieldDelimiter, size_t numericIndex, size_t alphanumericIndex) { PathName configPath = codes_path() / configFile; LOG_DEBUG_LIB(LibMetkit) << "GribCodesBase::GribCodesBase: config file:" << configPath << std::endl; numeric2alpha_.clear(); std::ifstream fin(configPath.asString()); std::string line; while (std::getline(fin, line)) { std::vector words = StringTools::split(fieldDelimiter, line); if (words.size() >= 2) { long num = eckit::Translator()(StringTools::trim(words[numericIndex])); std::string alpha(StringTools::trim(words[alphanumericIndex])); numeric2alpha_[num] = StringTools::lower(alpha); LOG_DEBUG_LIB(LibMetkit) << "GribCodesBase::readConfig: num='" << num << "' alpha='" << alpha << "'" << std::endl; } } } IdMap::~IdMap() {} std::string IdMap::alphanumeric(long numeric) { auto it = numeric2alpha_.find(numeric); if (it == numeric2alpha_.end()) { std::stringstream ss; ss << "Numeric code " << numeric << " not found"; throw UserError(ss.str(), Here()); } return it->second; } //---------------------------------------------------------------------------------------------------------------------- } // namespace odb } // namespace metkit metkit-1.20.2/src/metkit/odb/OdbDecoder.h0000664000175000017500000000236315246725757020300 0ustar alastairalastair/* * (C) Copyright 1996- ECMWF. * * 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. */ /// @author Baudouin Raoult /// @date Jun 2020 #pragma once #include "eckit/message/Decoder.h" namespace metkit { namespace codes { //---------------------------------------------------------------------------------------------------------------------- class OdbDecoder : public eckit::message::MessageDecoder { private: // methods bool match(const eckit::message::Message&) const override; void print(std::ostream&) const override; void getMetadata(const eckit::message::Message& msg, eckit::message::MetadataGatherer&, const eckit::message::GetMetadataOptions&) const override; eckit::Buffer decode(const eckit::message::Message& msg) const override; }; //---------------------------------------------------------------------------------------------------------------------- } // namespace codes } // namespace metkit metkit-1.20.2/src/metkit/metkit_version.c0000664000175000017500000000065215246725757020576 0ustar alastairalastair#include "metkit_version.h" #ifdef __cplusplus extern "C" { #endif const char* metkit_version() { return metkit_VERSION; } unsigned int metkit_version_int() { return 10000 * metkit_VERSION_MAJOR + 100 * metkit_VERSION_MINOR + 1 * metkit_VERSION_PATCH; } const char* metkit_version_str() { return metkit_VERSION_STR; } const char* metkit_git_sha1() { return metkit_GIT_SHA1; } #ifdef __cplusplus } #endif metkit-1.20.2/src/pymetkit_bindings/0000775000175000017500000000000015246725757017613 5ustar alastairalastairmetkit-1.20.2/src/pymetkit_bindings/bindings.cc0000664000175000017500000000677615246725757021737 0ustar alastairalastair/* * (C) Copyright 2025- ECMWF. * * 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. */ #include #include #include #include #include #include #include "eckit/runtime/Main.h" #include "eckit/system/Library.h" #include "eckit/system/LibraryManager.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsRequest.h" namespace py = pybind11; namespace mars = metkit::mars; PYBIND11_MODULE(pymetkit_bindings, m) { m.def("init_bindings", []() { char arg0[] = "pymetkit"; char* argv[] = {arg0, nullptr}; eckit::Main::initialise(1, argv); }); m.def("version_info", []() { std::vector> dependencyInformation; for (const std::string& libname : eckit::system::LibraryManager::list()) { const eckit::system::Library& lib = eckit::system::LibraryManager::lookup(libname); dependencyInformation.emplace_back(lib.name(), lib.version(), lib.gitsha1(), lib.libraryPath()); } return dependencyInformation; }); //-------------------------------------------------- // @brief MarsRequest //-------------------------------------------------- py::class_(m, "MarsRequest") .def(py::init()) .def(py::init([](const std::string& verb) { mars::MarsRequest request; request.verb(verb); return request; })) .def("verb", [](const mars::MarsRequest& request) { return request.verb(); }) .def("set_verb", [](mars::MarsRequest& request, const std::string& verb) { request.verb(verb); }) .def("set", [](mars::MarsRequest& request, const std::string& param, const std::vector& values) { request.values(param, values); }) .def("has", [](const mars::MarsRequest& request, const std::string& param) { return request.has(param); }) .def("params", [](const mars::MarsRequest& request) { return request.params(); }) .def("values", [](const mars::MarsRequest& request, const std::string& param) { const std::vector& values = request.values(param, false); return std::vector{values.begin(), values.end()}; }) .def("merge", [](mars::MarsRequest& request, const mars::MarsRequest& other) { request.merge(other); }) .def("expand", [](const mars::MarsRequest& request, bool inherit, bool strict) { mars::MarsExpansion expansion(inherit, strict); return expansion.expand(request); }) .def("__repr__", [](const mars::MarsRequest& request) { return request.asString(); }); //-------------------------------------------------- // @brief Parsing //-------------------------------------------------- m.def("parse_marsrequests", [](const std::string& str, bool strict) { std::istringstream in(str); return mars::MarsRequest::parse(in, strict); }); m.def("parse_marsrequest", [](const std::string& str, bool strict) { return mars::MarsRequest::parse(str, strict); }); } metkit-1.20.2/src/pymetkit_bindings/CMakeLists.txt0000664000175000017500000000130415246725757022351 0ustar alastairalastairpybind11_add_module(pymetkit_bindings MODULE bindings.cc) target_link_libraries(pymetkit_bindings PRIVATE pybind11::module pybind11::lto eckit metkit # We need to explicitly link to stdc++fs on gcc 8.x $<$,$,8.0>,$,9.0>>:stdc++fs> ) if(NOT MSVC AND NOT ${CMAKE_BUILD_TYPE} MATCHES Debug|RelWithDebInfo) # Strip unnecessary sections of the binary on Linux/macOS pybind11_strip(pymetkit_bindings) endif() set_target_properties(pymetkit_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PYMETKIT_STAGING}/pymetkit_bindings ) metkit-1.20.2/src/pymetkit/0000775000175000017500000000000015246725757015736 5ustar alastairalastairmetkit-1.20.2/src/pymetkit/__init__.py0000664000175000017500000000060715246725757020052 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 from pymetkit._internal import MetKitException from pymetkit.pymetkit import MarsRequest, parse_mars_request from pymetkit.pymetkit_type import MarsSelection __all__ = [ "MarsRequest", "MarsSelection", "parse_mars_request", "MetKitException", ] metkit-1.20.2/src/pymetkit/pymetkit_type.py0000664000175000017500000000732015246725757021221 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 from collections.abc import Collection, Mapping InternalMarsSelection = dict[str, list[str]] """ Internal representation of a MARS selection. A key-value map, mapping MARS keys to a list of string values. This is the form handed to the ``pymetkit_bindings`` layer. """ MarsSelection = Mapping[str, "str | int | float | Collection[str | int | float]"] """ Selection part of a MARS request: a mapping from MARS keys to user-supplied values. Values may be a scalar (``str``, ``int``, ``float``) or a collection of those. A ``str`` containing ``/`` is treated as a MARS range/list expression and split on ``/`` by :meth:`UserInputMapper.map_selection_to_internal`. """ class UserInputMapper: """ Normalises user-supplied MARS selections to and from the internal ``dict[str, list[str]]`` representation used by the bindings layer. - :meth:`map_selection_to_internal` converts a user-facing :data:`MarsSelection` (scalars, collections, range expressions) to an :data:`InternalMarsSelection`. - :meth:`map_selection_to_external` converts an :data:`InternalMarsSelection` back to a user-friendly form, collapsing single-element lists to scalars. """ @classmethod def map_selection_to_internal(cls, selection: MarsSelection) -> InternalMarsSelection: """Normalise a user-supplied selection to ``dict[str, list[str]]``. Each value is converted to a list of strings: scalars are wrapped in a one-element list, collections are stringified element-by-element, and ``str`` values containing ``/`` are split on ``/``. """ result: InternalMarsSelection = {} for key, values in selection.items(): result[key] = cls._normalize_values(key, values) return result @classmethod def map_selection_to_external(cls, selection: InternalMarsSelection) -> MarsSelection: """Convert an internal selection back to a user-friendly form. Each ``list[str]`` value is collapsed to a plain ``str`` if it contains a single element, or left as a ``list[str]`` if it contains multiple. """ result = {} for key, values in selection.items(): result[key] = cls.map_values_to_external(values) return result @staticmethod def map_values_to_external(values: Collection[str]) -> str | list[str]: """Collapse a single-element list to a scalar; return multi-element lists as-is.""" values = list(values) if len(values) == 1: return values[0] return values @staticmethod def _normalize_values(key: str, values) -> list[str]: if not isinstance(values, (int, float, str, Collection)) or isinstance(values, Mapping): raise ValueError( f"MarsSelection: the value for key '{key}' is not valid. Values must be " "int, float, str or a collection of those." ) # Values is a collection but not a single string if isinstance(values, Collection) and not isinstance(values, str): return [str(value) if isinstance(value, (float, int)) else value for value in values] # Single numeric value if isinstance(values, (int, float)): return [str(values)] # Single string; '/'-separated range/list expressions are split if isinstance(values, str): if "/" in values: return values.split("/") return [values] raise ValueError( f"MarsSelection: unknown type for key '{key}'. Values must be int, float, " "str or a collection of those." ) metkit-1.20.2/src/pymetkit/README.md0000664000175000017500000000330215246725757017213 0ustar alastairalastair# pymetkit `pymetkit` is a Python interface to [metkit](https://github.com/ecmwf/metkit), ECMWF's meteorological toolkit. It exposes the MARS request model in a Pythonic way, built on a [pybind11](https://github.com/pybind/pybind11) extension module (`pymetkit_bindings`) that binds the metkit C++ library directly. The native `libmetkit` shared library and its dependencies are located at runtime via [findlibs](https://github.com/ecmwf/findlibs). ## Architecture - `pymetkit_bindings` — compiled pybind11 module binding `metkit::mars::MarsRequest` and `metkit::mars::MarsExpansion`. - `pymetkit._internal` — loads the native library via `findlibs`, initialises the bindings, and re-exports the raw symbols. - `pymetkit` — the Pythonic layer: `MarsRequest` (a verb plus a `MarsSelection`), `MarsSelection` (a type alias for the user-facing key-value mapping), `UserInputMapper` (normalises `MarsSelection` values to and from the internal `dict[str, list[str]]` representation), and `parse_mars_request`. ## Usage ```python from pymetkit import MarsRequest, parse_mars_request # Build a request from a verb and a selection request = MarsRequest( "retrieve", { "class": "od", "domain": "g", "date": "-1", "expver": "0001", "step": range(0, 13, 6), }, ) # Expand against the MARS language definition expanded = request.expand() print(expanded.verb(), dict(expanded)) # Parse requests from a string or a file requests = parse_mars_request("retrieve,class=od,date=-1,param=129,step=12") ``` ## Command line ```bash python -m pymetkit --print-home # metkit library home python -m pymetkit --print-home-deps # all dependency homes and versions ``` metkit-1.20.2/src/pymetkit/pymetkit.py0000664000175000017500000001607515246725757020167 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 from collections.abc import Mapping from typing import IO, Iterator from pymetkit._internal import ( MetKitException, _MarsRequest, parse_marsrequests, ) from pymetkit.pymetkit_type import InternalMarsSelection, MarsSelection, UserInputMapper class MarsRequest: """ A MARS request: a verb (e.g. ``retrieve``) together with a :data:`MarsSelection` describing the parameters and their values. Parameters ---------- verb : str The request verb, e.g. ``retrieve``. selection : MarsSelection, optional Initial parameter values. Scalars are wrapped in a singleton list, collections are stringified, and ``/``-separated strings are split. Examples -------- >>> request = MarsRequest("retrieve", {"class": "od", "date": "20200101/20200102", "param": [151, 129]}) >>> request.verb() 'retrieve' >>> request["class"] 'od' >>> request["date"] ['20200101', '20200102'] >>> request["param"] ['151', '129'] Iterating yields ``(name, value)`` pairs: >>> for key, value in request: ... print(key, value) class od date ['20200101', '20200102'] param ['151', '129'] """ def __init__(self, verb: str, selection: MarsSelection | None = None, /): self._verb = verb if selection is not None and not isinstance(selection, Mapping): raise ValueError(f"MarsRequest: expected a mapping, got {type(selection).__name__}.") combined: MarsSelection = selection if selection is not None else {} self.selection: InternalMarsSelection = UserInputMapper.map_selection_to_internal(combined) # -- Construction / conversion helpers --------------------------------- def _to_internal(self) -> _MarsRequest: internal = _MarsRequest(self._verb) for param, values in self.selection.items(): internal.set(param, list(values)) return internal @classmethod def _from_internal(cls, internal: _MarsRequest) -> "MarsRequest": request = cls(internal.verb()) for param in internal.params(): request.selection[param] = internal.values(param) return request # -- Queries ----------------------------------------------------------- def verb(self) -> str: """Return the request verb.""" return self._verb def keys(self) -> Iterator[str]: """Return an iterator over the parameter names in the request.""" return iter(self.selection.keys()) def num_values(self, param: str) -> int: """Return the number of values for a parameter.""" return len(self.selection[param]) # -- Operations backed by the MARS language engine -------------------- def expand(self, inherit: bool = True, strict: bool = False) -> "MarsRequest": """ Return the expanded request. Parameters ---------- inherit : bool If True, populate the expanded request with default values. strict : bool If True, raise an error instead of a warning for invalid values. Returns ------- MarsRequest The request resulting from expansion. """ try: expanded = self._to_internal().expand(inherit, strict) except RuntimeError as error: raise MetKitException(str(error)) from error return MarsRequest._from_internal(expanded) def validate(self) -> None: """ Check that the request is valid against the MARS language definition. Does not inherit missing parameters. Raises ------ MetKitException If the request is incompatible with the MARS language definition. """ self.expand(inherit=False, strict=True) def merge(self, other: "MarsRequest") -> "MarsRequest": """ Merge the values of another request into this one and return the result as a new request. Does not modify either input. Both requests must contain the same parameters and the result must be compatible with the MARS language definition. Parameters ---------- other : MarsRequest The request to merge with self. Returns ------- MarsRequest The result of the merge. Raises ------ ValueError If the parameters in the two requests do not match. MetKitException If the resulting request is not compatible with the MARS language definition. """ if set(self.keys()) != set(other.keys()): raise ValueError("Cannot merge requests with different parameters.") internal = self._to_internal() try: internal.merge(other._to_internal()) except RuntimeError as error: raise MetKitException(str(error)) from error result = MarsRequest._from_internal(internal) result.validate() return result # -- Mapping-like interface ------------------------------------------- def __iter__(self) -> Iterator[tuple[str, str | list[str]]]: for key in self.selection: yield key, UserInputMapper.map_values_to_external(self.selection[key]) def __getitem__(self, param: str) -> str | list[str]: return UserInputMapper.map_values_to_external(self.selection[param]) def __setitem__(self, param: str, values) -> None: self.selection[param] = UserInputMapper._normalize_values(param, values) def __contains__(self, param: str) -> bool: return param in self.selection def __eq__(self, other: object) -> bool: if not isinstance(other, MarsRequest): return NotImplemented if self.verb() != other.verb(): return False return dict(self.expand()) == dict(other.expand()) def __hash__(self) -> int: expanded = self.expand() return hash( ( expanded.verb(), frozenset((k, tuple(v)) for k, v in expanded.selection.items()), ) ) def __repr__(self) -> str: return repr(self._to_internal()) def parse_mars_request(file_or_str: IO | str, strict: bool = False) -> list[MarsRequest]: """ Parse one or more MARS requests from a file-like object or a string. Parameters ---------- file_or_str : str | IO A string or file-like object containing one or more MARS requests. strict : bool Whether to raise an error (True) or a warning (False) when a request is not compatible with the MARS language definition. When False, the incompatible parameters are unset from the request. Returns ------- list[MarsRequest] """ text = file_or_str if isinstance(file_or_str, str) else file_or_str.read() try: requests = parse_marsrequests(text, strict) except RuntimeError as error: raise MetKitException(str(error)) from error return [MarsRequest._from_internal(request) for request in requests] metkit-1.20.2/src/pymetkit/__main__.py0000664000175000017500000000650515246725757020036 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 import argparse import logging import os import sys import findlibs import pymetkit._internal as _internal # INFO: This is in place because we currently can't (at runtime) # tell which order the dependencies are in. This needs to be available in findlibs DEPENDENCY_ORDER = ["eckit", "eccodes", "metkit"] OPTIONAL_DEPENDENCIES = ["eccodes"] def main(): parser = argparse.ArgumentParser(description="pymetkit command line interface") parser.add_argument( "--print-home", action="store_true", help="Print the home directory of the metkit library", ) parser.add_argument( "--print-home-deps", action="store_true", help="Print the home directories of all pymetkit dependencies", ) parser.add_argument( "-v", "--verbose", action="store_true", help="Enable DEBUG logging (default: INFO)", ) args = parser.parse_args() logging.basicConfig( format="%(asctime)s | %(levelname)-6s | %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG if args.verbose else logging.INFO, ) if not (args.print_home or args.print_home_deps): parser.print_help() sys.exit(2) def _lib_home(lib_path): lib_dir = os.path.dirname(os.path.realpath(lib_path)) return ( os.path.dirname(lib_dir) if os.path.basename(lib_dir) in ("lib", "lib64") else lib_dir ) def _print_dep_path(lib, dependency_path, optional): missing = [] if dependency_path is None: msg = f"\t{lib} [Optional]" if optional else f"\t{lib}" msg += ": not found by findlibs" missing.append(lib) if optional: logging.info(msg) else: logging.error(msg) else: msg = f"\t{lib} [Optional]" if optional else f"\t{lib}" msg += f": {_lib_home(dependency_path)}" logging.info(msg) return missing library_info_tuple = _internal.version_info() if args.print_home: dependency_path = findlibs.find("metkit") if dependency_path is None: logging.error("metkit library not found by findlibs") sys.exit(1) for name, version, gitSha, path in library_info_tuple: if name == "metkit": logging.info(f"\t{name} {version} ({gitSha}) {path}") if args.print_home_deps: logging.info("Findlibs Environment:") for key, value in os.environ.items(): if key.upper().startswith("FINDLIBS_DISABLE"): logging.info(f"\t{key: <15}: {value: <10}") logging.info("Findlibs Lookup") missing = [] for lib in DEPENDENCY_ORDER: dependency_path = findlibs.find(lib) missing += _print_dep_path( lib, dependency_path, lib in OPTIONAL_DEPENDENCIES ) logging.info("Dependency Versions:") for name, version, gitSha, path in library_info_tuple: logging.info(f"\t{name} {version} ({gitSha}) {path}") if any(lib not in OPTIONAL_DEPENDENCIES for lib in missing): sys.exit(1) if __name__ == "__main__": main() metkit-1.20.2/src/pymetkit/_internal/0000775000175000017500000000000015246725757017711 5ustar alastairalastairmetkit-1.20.2/src/pymetkit/_internal/__init__.py0000664000175000017500000000127015246725757022022 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 # libmetkit.so and dependencies have to be loaded prior to importing # pymetkit import findlibs findlibs.load("metkit") from pymetkit._internal.pymetkit_internal import ( MetKitException, ) from pymetkit_bindings.pymetkit_bindings import ( MarsRequest as _MarsRequest, ) from pymetkit_bindings.pymetkit_bindings import ( init_bindings, parse_marsrequest, parse_marsrequests, version_info, ) __all__ = [ "init_bindings", "version_info", "parse_marsrequest", "parse_marsrequests", "_MarsRequest", "MetKitException", ] metkit-1.20.2/src/pymetkit/_internal/pymetkit_internal.py0000664000175000017500000000060215246725757024023 0ustar alastairalastair# SPDX-FileCopyrightText: 2026 European Centre for Medium-Range Weather Forecasts (ECMWF) # SPDX-License-Identifier: Apache-2.0 from pymetkit_bindings import pymetkit_bindings as pymetkit_internal # Initial setup of binding via eckit main pymetkit_internal.init_bindings() class MetKitException(RuntimeError): """Raised when the MetKit library throws an exception.""" pass metkit-1.20.2/src/CMakeLists.txt0000664000175000017500000000505415246725757016634 0ustar alastairalastairadd_subdirectory( metkit ) add_subdirectory( tools ) if( HAVE_EXPERIMENTAL ) add_subdirectory( experimental ) endif() if( HAVE_PYTHON_METKIT_INTERFACE ) # We create the complete python package layout at this location. # This allows us to run python wheel creation at this path and # to put this path on the PYTHONPATH to allow direct use of # pymetkit, e.g. for testing or local exploration. set(PYMETKIT_STAGING "${CMAKE_BINARY_DIR}/pymetkit-python-package-staging") file(MAKE_DIRECTORY "${PYMETKIT_STAGING}") file(CREATE_LINK "${CMAKE_CURRENT_SOURCE_DIR}/pymetkit" "${PYMETKIT_STAGING}/pymetkit" SYMBOLIC ) # Copy README.md and LICENSE at build time so changes are picked up # without needing to re-run cmake configure add_custom_command( OUTPUT ${PYMETKIT_STAGING}/README.md COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/pymetkit/README.md" "${PYMETKIT_STAGING}/README.md" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/pymetkit/README.md" COMMENT "Copying pymetkit README.md to staging..." ) add_custom_command( OUTPUT ${PYMETKIT_STAGING}/LICENSE COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE" "${PYMETKIT_STAGING}/LICENSE" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE" COMMENT "Copying LICENSE to staging..." ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/pymetkit_setup.py.in ${PYMETKIT_STAGING}/setup.py @ONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/pymetkit_setup.cfg.in ${PYMETKIT_STAGING}/setup.cfg @ONLY ) add_subdirectory(pymetkit_bindings) file(GLOB_RECURSE _pymetkit_package_files "pymetkit/*.py" "pymetkit/_internal/*.py" ) list(APPEND _pymetkit_package_files "${CMAKE_CURRENT_SOURCE_DIR}/pymetkit/README.md" "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE" ) add_custom_command( OUTPUT ${CMAKE_BINARY_DIR}/pymetkit.wheel.stamp COMMAND ${Python_EXECUTABLE} -m build --wheel ${PYMETKIT_STAGING} -o . COMMAND ${CMAKE_COMMAND} -E touch pymetkit.wheel.stamp WORKING_DIRECTORY ${CMAKE_BINARY_DIR} DEPENDS ${_pymetkit_package_files} pymetkit_bindings ${PYMETKIT_STAGING}/README.md ${PYMETKIT_STAGING}/LICENSE COMMENT "Building Python wheel for pymetkit..." ) add_custom_target(pymetkit-wheel ALL DEPENDS ${CMAKE_BINARY_DIR}/pymetkit.wheel.stamp) endif() metkit-1.20.2/bamboo/0000775000175000017500000000000015246725757014540 5ustar alastairalastairmetkit-1.20.2/bamboo/env.sh0000664000175000017500000000007615246725757015667 0ustar alastairalastair#!/usr/bin/env bash # export VAR=VALUE # ctest_parallel="no" metkit-1.20.2/bamboo/MACOSX-env.sh0000664000175000017500000000007515246725757016656 0ustar alastairalastairexport PATH=$HOME/Applications/CMake.app/Contents/bin:$PATH metkit-1.20.2/bamboo/INTEL-env.sh0000664000175000017500000000066215246725757016541 0ustar alastairalastair#!/bin/bash [[ $(uname) == "Darwin" ]] && return # no module environment on the Mac # initialise module environment if it is not if [[ ! $(command -v module > /dev/null 2>&1) ]]; then . /usr/local/apps/module/init/bash fi module unload grib_api module unload eccodes module unload emos module unload fftw module unload libemos module unload metview module unload netcdf4 module load cmake/3.16.5 module switch gnu intel/17.0.3 metkit-1.20.2/bamboo/CLANG-env.sh0000664000175000017500000000065315246725757016512 0ustar alastairalastair#!/bin/bash [[ $(uname) == "Darwin" ]] && return # no module environment on the Mac # initialise module environment if it is not if [[ ! $(command -v module > /dev/null 2>&1) ]]; then . /usr/local/apps/module/init/bash fi module unload grib_api module unload eccodes module unload emos module unload fftw module unload libemos module unload metview module unload netcdf4 module load cmake/3.16.5 module switch gnu clang metkit-1.20.2/bamboo/GCC-env.sh0000664000175000017500000000062215246725757016256 0ustar alastairalastair#!/bin/bash [[ $(uname) == "Darwin" ]] && return # no module environment on the Mac # initialise module environment if it is not if [[ ! $(command -v module > /dev/null 2>&1) ]]; then . /usr/local/apps/module/init/bash fi module unload grib_api module unload eccodes module unload emos module unload fftw module unload libemos module unload metview module unload netcdf4 module load cmake/3.16.5 metkit-1.20.2/bamboo/CMakeLists.txt0000664000175000017500000000025415246725757017301 0ustar alastairalastairfile( GLOB_RECURSE bamboo_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*" ) ecbuild_add_resources( TARGET ${PROJECT_NAME}_bamboo SOURCES_DONT_PACK ${bamboo_files} ) metkit-1.20.2/.git-blame-ignore-revs0000664000175000017500000000070015246725757017376 0ustar alastairalastair# The following refs can be ignored by git blame if 'git blame' is called with # --ignore-revs-file .git-blame-ignore-revs # Alternatively configure git to always ignore refs stored in this file by # calling: # git config blame.ignoreRevsFile .git-blame-ignore-revs # Or extend your git config manually with # [blame] # ignoreRevsFile = .git-blame-ignore-revs # Reformatted codebase with clang-format caa877e4812ef00c792326f8d71683f95422c33f metkit-1.20.2/share/0000775000175000017500000000000015246725757014403 5ustar alastairalastairmetkit-1.20.2/share/metkit/0000775000175000017500000000000015246725757015700 5ustar alastairalastairmetkit-1.20.2/share/metkit/axis.yaml0000664000175000017500000000161715246725757017535 0ustar alastairalastair--- axes: - class - country - type - stream - levtype - origin - product - section - method - system - date - refdate - hdate - offsetdate - time - offsettime - anoffset - reference - dataset - step - fcmonth - fcperiod - leadtime - opttime - expver - domain - diagnostic - iteration - quantile - number - levelist - latitude - longitude - range - param - chem - wavelength - timespan - stattype - ident - obstype - instrument - frequency - direction - channel - obsgroup - reportype - activity - experiment - generation - model - realization - resolution - year - month - bcmodel - icmodel - grib - georef - coeffindex - forcing - configuration - obscutoff metkit-1.20.2/share/metkit/prodgen-paramids.yaml0000664000175000017500000000503615246725757022024 0ustar alastairalastair2: - vpot 15: - auvp 16: - auvd 17: - anip 18: - anid 34: - sstk 39: - swv1 40: - swv2 41: - swv3 42: - swv4 66: - lailv 67: - laihv 77: - etad 139: - st 148: - ss 209: - ttru 928: - thk - thicknes 956: - thka - thicknes anomaly 999: - tc - tropical cyclone 132049: - 10gi 171039: - swv1 - swvl1a 171040: - swv2 - swvl2a 171041: - swv3 - swvl3a 171042: - swv4 - swvl4a # 171031: - ci 172205: - ro # Anomalies ############################################################ 171129: - z 171130: - t 171131: - u 171132: - v 171138: - vo 171139: - stl1 - st 171140: - swl1 171151: - msl 171155: - d 171156: - gh 171157: - r 171170: - slal2 - stl2 171171: - swl2 #171180: #- ewssa 171183: - stl3 171184: - swl3 171207: - 10si 171237: - swl4 # # ############################################################ 172044: - es ##172045: ##- smlt ##- snowmelt ## ##172048: ##- magss ## 172050: - lspf ## 172142: - lsp ## 172143: - cp ## 172144: - sf ## 172146: - sshf - 146 ## 172147: - slhf - 147 ## 172149: - snr ## 172153: - swhr ## 172154: - lwhr ## 172169: - ssrd ## 172175: - strd ## 172176: - ssr ## 172177: - str ## 172178: - tsr ## 172179: - ttr ## 172180: - ewss ## 172181: - nsss ## 172182: - e ## 172189: - sund ## ##172205: ##- ro ##- runoff ## ##172208: ##- tsrc ## ##172209: ##- ttrc ## ##172210: ##- ssrc ## ##172211: ##- strc ## 172228: - tp ## ##172239: ##- csf ## ##172240: ##- lsf ## ##173044: ##- esa ## ##173045: ##- smlta ## ##173048: ##- magssa ## ##173050: ##- lspfa ## 173142: - lspa ## 173143: - cpa ## ##173145: ##- blda ## ##173146: ##- sshfa ## ##173147: ##- slhfa ## ##173149: ##- snra ## ##173153: ##- swhra ## ##173154: ##- lwhra ## 173169: - ssrda - ssrd ## 173175: - strda ## 173176: - ssra - ssr ## 173177: - stra - str ## 173178: - tsra - tsr ## 173179: - ttra - ttr # ##173180: ##- ewssa ## ##173181: ##- nsssa ## 173182: - ea - e ## ##173195: ##- lgwsa ## ##173196: ##- mgwa ## ##173197: ##- gwa ## ##173205: ##- roa ## ##173208: ##- tsrca ## ##173209: ##- ttrca ## ##173210: ##- ssrca ## ##173211: ##- strca ## ##173212: ##- tisra ## ##173239: ##- csfa ## ##173240: ##- lsfa ## ##217004: ##- ch4 ##- methane 228024: - deg0 ## ##228028: ##- i10fg3 ##228053: ##- licga # In the param database, its sysa +2 k 131020: - temperature anomaly of at most -2 k 131021: - temperature anomaly of at least 2 k 131022: - temperature anomaly of at most -8 k 131023: - temperature anomaly of at most -4 k 131024: - temperature anomaly greater than 4 k - temperature anomaly of at least 4 k 131025: - temperature anomaly greater than 8 k - temperature anomaly of at least 8 k metkit-1.20.2/share/metkit/prodgen-params-extra.yaml0000664000175000017500000000103315246725757022621 0ustar alastairalastair# Stuff that is not archived in mars # - - stream: enfh type: - cf - pf - cv - - 1 - 2 - - stream: enfo type: cm - - 130 - - stream: msmm type: em - - 9 # ssro - 8 # sro - 205 # ro - 212 # tisr - - stream: mmsa type: em - - 39 # swvl1 - 40 # swvl2 - 41 # swvl3 - 42 # swvl4 - 172212 #soira - - stream: msmm type: fcmean - - 8 - 205 # ro - 212 # tisr - - stream: mmsa type: fcmean - - 39 # swvl1 - 40 # swvl2 - 41 # swvl3 - 42 # swvl4 metkit-1.20.2/share/metkit/params-static.yaml0000664000175000017500000000632515246725757021342 0ustar alastairalastair- - class: mc levtype: sfc stream: oper type: an - - 32 - 130 - 134 - 164 - 187 - 188 - - class: mc levtype: sfc stream: oper type: fc - - 32 - 58 - 134 - 142 - 143 - 164 - 176 - 177 - 178 - 179 - 187 - 188 - 228 - - class: mc levtype: pl stream: oper type: an - - 130 - 135 - 152 - 157 - 210181 - 217009 - - class: mc levtype: pl stream: oper type: fc - - 130 - 135 - 152 - 210181 - 217009 - - class: mc levtype: ml stream: oper type: an - - 131 - 132 - 135 - 152 - 210181 - 217009 - - class: mc levtype: ml stream: oper type: fc - - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 210181 - 217009 - - class: od levtype: sfc stream: oper type: fc - - 3100 - - class: od levtype: sfc stream: enfo type: pf - - 3100 - - class: ai levtype: sfc stream: oper type: fc - - 140229 - 228141 - 228143 - - class: ai levtype: sfc stream: enfo type: pf - - 140229 - 228141 - 228143 - - class: od levtype: ml stream: oper type: fc - - 210131 - - class: od levtype: pl stream: dacl type: pb - - 129 - 130 - - class: od levtype: ml stream: sens type: sg - - 129 - 155 - 129155 - 155129 - - type: ob - - 129 - - type: ofb - - 129 - - levtype: o2d - - 250001 - 250002 - 250003 - 250004 - 250005 - 250006 - 250007 - 250008 - 250009 - 250010 - 250011 - 250012 - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - 263000 - 263001 - 263002 - 263003 - 263004 - 263005 - 263006 - 263007 - 263100 - 263101 - 263102 - 263103 - 263104 - 263105 - 263106 - 263107 - 263108 - 263109 - 263110 - 263111 - 263112 - 263113 - 263114 - 263115 - 263116 - 263117 - 263118 - 263119 - 263120 - 263121 - 263122 - 263123 - 263124 - 263125 - 263126 - 263127 - 263128 - - levtype: o3d - - 262500 - 262501 - 262502 - 262505 - 262506 - 262507 - 263500 - 263501 - 263502 - 263503 - 263504 - 263505 - 263506 - 263507 - - class: od levtype: sfc stream: oper type: fc - - 160 - 163 - 3062 - 3099 - 160198 - 228032 - 228143 - 228144 - 228164 - 228228 - 228236 - 231001 - 231002 - 231003 - 231010 - 231012 - 260199 - 260227 - 260228 - 260259 - 260360 - 260509 - 262121 - - stream: oper type: ssd - - 260512 metkit-1.20.2/share/metkit/make-yaml.py0000775000175000017500000001154415246725757020137 0ustar alastairalastair#!/usr/bin/env python3 # (c) Copyright 1996-2012 ECMWF. # # 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. # Documentation is available on Confluence # https://confluence.ecmwf.int/display/METK/MetKit+configuration import mysql.connector import yaml import re import os connectionDetails = dict( host = "webapps-db-prod", user = os.environ['PARAM_DB_USER'], passwd = os.environ['PARAM_DB_PASSWORD']) def execute(database, fun, prefix=None, asList=False, lowercase=True): connection = mysql.connector.connect(**connectionDetails) cursor = connection.cursor() cursor.execute("USE " + database) r = fun(cursor, prefix, asList, lowercase) if (prefix) else fun(cursor) cursor.close() connection.close() return r ################################################################################################ def ids(cursor, prefix, asList, lowercase): PRODGEN = {} if os.path.exists("prodgen-" + prefix + "ids.yaml"): with open("prodgen-" + prefix + "ids.yaml") as f: PRODGEN = yaml.load(f.read(), Loader=yaml.FullLoader) IDS = {} if os.path.exists(prefix + "ids.yaml"): with open(prefix + "ids.yaml") as f: IDS = yaml.load(f.read(), Loader=yaml.FullLoader) if asList: # Convert list of lists to dictionary IDS = {item[0]: item[1:] for item in IDS} cursor.execute("select * from " + prefix) for data in cursor.fetchall(): paramid, abbr, longname = int(data[0]), data[1], data[2] if lowercase: abbr = abbr.lower() abbr = re.sub(r"\W", "_", abbr) abbr = re.sub(r"_+", "_", abbr) abbr = re.sub(r"^_", "", abbr) abbr = re.sub(r"_$", "", abbr) if not abbr: abbr = "_%s_%06d" % (prefix, paramid,) entry = [abbr.strip(), longname.strip()] if paramid in PRODGEN: pgen = [str(x).lower() for x in PRODGEN[paramid]] p = [] for n in pgen: if ( n not in entry ): # and (' ' not in n) and ('.' not in n): # and ('-' not in n): entry.append(n) p.append(n) entry = tuple(entry) if entry[0].lower() == entry[1].lower(): entry = tuple([entry[0]]) else: entry = tuple(entry) if paramid in IDS: before = tuple(IDS[paramid]) if before != entry: print( "WARNING! updated {}id: {}, {} => {}".format(prefix, paramid, before, entry) ) IDS[paramid] = list(entry) else: print("new {}id: {} {}".format(prefix, paramid, entry)) IDS[paramid] = list(entry) for paramid, entry in PRODGEN.items(): if paramid not in IDS: print("WARNING! adding pseudo-{}id: {}, {}".format(prefix, paramid, tuple(entry))) IDS[paramid] = entry if asList: # Sort by key and convert to list of lists return [[k] + v for k, v in sorted(IDS.items(), key=lambda item: item[0])] return IDS ################################################################################################ def getListOfRT(cursor, group_id): def sorted(cursor): cursor.execute("SELECT code FROM odbadmin_reporttype WHERE group_id = " + str(group_id)) r = [r[0] for r in cursor.fetchall()] r.sort() return r return sorted(cursor) T = """- context:\n type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa]\n obsgroup: [%(OBSGROUP)s]\n defaults:\n reportype: [%(REPORTYPE_LIST)s]\n""" def reportype(cursor): cursor.execute("SELECT id,marsname FROM odbadmin_group") GROUP = dict([(r[0],str(r[1]).lower()) for r in cursor.fetchall()]) cursor.execute("SELECT DISTINCT group_id FROM odbadmin_reporttype ORDER BY group_id") groups = [r[0] for r in cursor.fetchall()] def expand(g): return T % dict(OBSGROUP = GROUP[g], REPORTYPE_LIST = "/".join([str(r) for r in getListOfRT(cursor, g)])) return "\n".join([expand(group_id) for group_id in groups]) ################################################################################################ def header(f,o): f.write( "---\n# File automatically generated by %s --%s\n# Do not edit\n\n" % (os.path.basename(__file__), o) ) options = 'chem param reportype'.split() def main(): import sys opts = sys.argv[1:] if len(opts) != 1 or opts[0].lstrip('-') not in options: print(sys.argv[0] + ": Possible options: --" + ", --".join(options)) sys.exit(1) o = opts[0].lstrip('-') match o: case 'chem': content = execute("param", ids, o, True, False) with open(o + "ids.yaml", "w") as f: header(f, o) yaml.safe_dump(content, f, default_flow_style=None) case 'param': content = execute("param", ids, o) with open(o + "ids.yaml", "w") as f: header(f, o) yaml.safe_dump(content, f, default_flow_style=False) case 'reportype': with open("reportype.yaml", "w") as f: header(f, o) f.write(execute("odbgov", reportype)) if __name__ == "__main__": main() metkit-1.20.2/share/metkit/obstype.yaml0000664000175000017500000001662615246725757020264 0ustar alastairalastair--- - name: all group: - name: [nsd, conv, conventional, non satellite data all] group: - name: [lsd, land surface data] group: - [1, s, synop land] - [2, s2, synop record 2 land] - [3, sa, synop auto land] - [4, sa2, synop auto record 2 land] - [7, set, soil and earth temperature] - [28, snowd, snow depth] - [140, metar] - [147, ametar, auto metar] - [165, snow, imssnow, snowcover, snow cover] - [170, bs, bufr land synop] - [172, bs2, bufr land synop 2] - [176, bsr6, bufr land synop wmo region 6] - [178, bs1h, bufr land synop 1 hourly] - name: [ssd, sea surface data] group: - [9, sab, synop abbreviated ship] - [11, ss, synop ship] - [12, ss2, synop record 2 ship] - [13,sas, synop auto ship] - [14, sas2, synop auto record 2 ship] - [19, sh, shred] - [21, dsu, dribu surface] - [22, bsu, bathy surface] - [23, tsu, tesac surface] - [26, ostia] - [27, buoy] - [180, bss, bufr ship synop] - [181, bmb, bufr moored buoys] - [182, bdb, bufr drifting buoys] - name: [vsns, vertical soundings not satellite] group: - [91, p, pilot land] - [92, ps, pilot ship] - [95, wp, wind profile] - [96, ewp, european wind profile] - [97, ewtp, european wind and temperature profile] - [101, t, temp land] - [102, ts, temp ship] - [103, td, temp drop] - [104, r, rocob land] - [105, rs, rocob ship] - [106, tm, temp mobile] - [107, ct, climate temp] - [109, hrt, high resolution temp land] - [111, hrts, high resolution temp ship] - [112, bp, bufr land pilot] - [113, bps, bufr ship pilot] - [230, btd, bufr temp drop] - [231, btdc, bufr temp descent] - name: [slns, single level upper air not satellite] group: - [141, cod, codar] - [142, air, airep] - [143, col, colba] - [144, am, amdar] - [145, ac, acars] - [146, eam, e-amdar] - [149, acmr, acars with mixing ratio] - name: [sls, single level upper air satellite] group: - [82, sat2, satob section 2] - [83, sat3, satob section 3] - [84, sat4, satob section 4] - [85, sat5, atob section 5] - [86, sat6, high resolution winds] - [87, sat7, high density unified format satellite winds] - [88, geor, geostationary radiances] - [89, gcsr, geostationary clear sky radiances] - [189, gmsgr, geostationary msg radiances] - [190, gasr, geostationary all sky radiances] - name: [od, oceanographic data] group: - [131, do, dribu oceanographic] - [132, bo, bathy oceanographic] - [133, to, tesac oceanographic] - [184, argo, bufr argo] - name: [dd, derived data] group: - [164, pa, paobs] - [237, bice, baltic_ci, baltic sea ice] - name: [cc, chemical constituent] group: # - [157, so, surface ozone] # - [158, soa, surface ozone averaged] - [162, ovs, ozone vertical sounding] - [159, op, ozone partial] - name: [sf, synoptic features] group: - [31, stor, storm] - [32, tc, tropical cyclone] # ???? - [110, pgps, gbgps, ground based gps] - name: [sd, satellite data all] group: - name: [sds, ers1, surface data satellite] group: - [121, was, wavescat, wave scatterometer] - [122, wis, scat, wind scatterometer] - [123, ral, ralt, radar altimeter] - [124, sst, sea surface temperature] - [210, esar, wavesar, envisat sar] - [212, emeris, envisat_meris, envisat meris] - [213, era, envisat radar altimeter] - [214, jra, jason radar altimeter] - [216, j2ra, jason2 radar altimeter] - [217, cryora, cryosat-2 radar altimeter] - [218, saral, saral radar altimeter] - name: [vss, vertical soundings satellite] group: - [51, hr, high resolution] - [53, rtov, fbtovs, tovh, rtovs] - [54, tovb, tovs1b] - [55, atov, atovs] - [56, atop, atovs products] - [57, airs, fbairs] - [58, amsr3, advanced microwave scanning radiometer 3] - [59, amsre, fbamsre, advanced microwave scanning radiometer] - [60, amsr2, amsr2_gc, advanced microwave scanning radiometer 2] # ???? - [61, smt, satem mean temperature] - [62, sw, satem water] - [63, shl, satem higher level] - [65, slm, satem levels merged] - [71, mt, mean temperature] - [72, w, water] - [73, hl, higher level] - [75, tlm, tovs levels merged] - [138, oscat] - [139, ascat] - [153, mwribt, micro wave radiation imager] - [155, ateu, amsu_ears, atovs eumetsat] - [211, saphir] - [240, iasi, fbiasi, iasi_screened] # ???? - [250, gpsro, fbgpsro, gps radio occultation] - [126, ssmi, microwave images] - [49, ssmis, fbssmis, special sensor microwave imager sounder] - [127, ssbt, fbssmi, sea surface brigthness temperature] - name: trmm group: - [129, tbt, fbtmi, tmi brightness temperatures] - [130, ti3r, tmi instantaneous 3-d rainfall structure] - name: qscat group: - [137, qsca, quikscat] - [138, qssw, quikscat seawinds] - name: reo3 group: - [206, reo3, fbreo3, ozone] - [207, modis, modis aerosol] - [208, limd, layer integrated mass density] - [209, limd_ak, layer integrated mass density with averaging kernels] - [156, wsat, fbwindsat, windsat] - [154, vass, vertical atmospheric sounder system] - [191, mws, microwave sounder] - [201, atms, advanced technology microwave sounder] - [202, cris, cross-track infrared sounder ] - [252, giirs] - [245, hiras2] - [246, aws] - [235, alt] - [226, earthcare_cpr] - [227, earthcare_atl] - [243, visrad] - name: bufr group: [1, 3, 9, 11, 13, 19, 21, 22, 23, 28, 61, 62, 63, 82, 83, 84, 85, 86, 87, 91, 92, 95, 96, 101, 102, 103, 106, 110, 109, 111, 112, 113, 140, 142, 143, 144, 145, 146, 147, 149, 164, 170, 172, 176, 178, 180, 230, 231] - name: fbconv group: [1, 3, 9, 11, 13, 19, 21, 22, 23, 28, 65, 82, 83, 86, 87, 91, 92, 95, 96, 101, 102, 103, 106, 110, 140, 142, 143, 144, 145, 164] - name: fbscat group: [122, 136, 137, 139] - name: fbrad1c group: [54, 55] - name: fbgeos group: [89, 189, 190] - [255, fbtrmr] - [125, rw, nexrad, nexrad_rr, reprocessed wind] - [224, gbt, gpm, gmi brightness temperatures] - name: grad group: [88, 89, 189, 190] - [203, smos] - [204, smap] - name: dmbo group: [181, 182] - [251, aeolus] metkit-1.20.2/share/metkit/reportype.yaml0000664000175000017500000003312215246725757020616 0ustar alastairalastair--- # File automatically generated by make-yaml.py --reportype # Do not edit - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [hirs] defaults: reportype: [1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsua] defaults: reportype: [1001/1002/1003/1004/1005/1007/1008/1009/1010] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsub] defaults: reportype: [2001/2002/2003/2004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mhs] defaults: reportype: [3001/3002/3003/3004/3005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [geos] defaults: reportype: [4002/4003/4004/4005/4006/4007/4008/4009/4010/4011/4012/4013/4014/4015/4016/4017/4018/4019/4020/4021/4022/4023/4024/4025/4026/4027/4028/4029/4030/4031/4032/4033/4034/4035/4036] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [resat] defaults: reportype: [5001/5002/5003/5004/5005/5006/5007/5008/5009/5010/5011/5012/5013/5014/5015/5016/5017/5018/5019/5020/5021/5022/5023/5024/5025/5026/5027/5028/5029/5030/5031/5032/5033/5034/5035/5036/5037/5038/5039/5040/5041/5042/5043/5044/5045/5046/5047/5048/5049/5050/5051/5052/5053/5054/5055/5056/5057/5058/5059/5060/5061/5062/5063/5064/5065/5066/5067/5068/5069/5070/5071/5072/5073/5074/5075/5076/5077/5078/5079/5080/5081/5082/5083/5084/5085/5086/5087/5088/5089/5090/5091/5092/5093/5094/5096/5097/5098/5099/5100/5101/5102/5103/5104/5105/5106/5107/5108/5109] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [meris] defaults: reportype: [6001/6002/6003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [gpsro] defaults: reportype: [7001/7002/7003/7004/7005/7006/7007/7008/7009/7010/7011/7012/7013/7014/7015/7016/7017/7018/7019/7020/7021/7022/7023/7024/7025/7026/7027/7028/7029/7030/7031/7032/7033/7034/7035/7036/7037] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [satob] defaults: reportype: [8001/8002/8003/8004/8005/8006/8007/8008/8009/8010/8011/8012/8013/8014/8015/8016/8017/8018/8019/8020/8021/8022/8023/8024/8025/8026/8027/8028/8029/8030/8031/8032/8033/8034/8035/8036/8037/8038/8039/8040/8041/8042/8043/8044/8045/8046/8047/8048/8049/8050/8051/8052/8053/8054/8055/8056/8057/8058/8059/8060/8061/8062/8063/8064/8065/8066/8067/8068/8069] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [scatt] defaults: reportype: [9001/9002/9003/9004/9005/9006/9007/9008/9009/9010/9011/9012/9013/9014/9015/9016/9018/9019/9020] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmi_as] defaults: reportype: [10001/10002/10003/10004/10005/10006/10007/10008/10009/10010/10011/10012/10013/10014] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [iasi] defaults: reportype: [11001/11002/11003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [airs] defaults: reportype: [12001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmis_as] defaults: reportype: [13001/13002/13003/13004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [tmi_as] defaults: reportype: [14001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsre_as] defaults: reportype: [15001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [conv] defaults: reportype: [16001/16002/16003/16004/16005/16006/16007/16008/16009/16010/16011/16012/16013/16014/16015/16016/16017/16018/16019/16020/16021/16022/16023/16024/16025/16026/16027/16028/16029/16030/16031/16032/16037/16038/16039/16040/16041/16042/16043/16044/16045/16046/16047/16048/16049/16050/16051/16052/16053/16054/16055/16056/16057/16058/16059/16060/16061/16062/16063/16064/16065/16066/16067/16068/16069/16070/16071/16072/16073/16074/16075/16076/16077/16078/16079/16080/16081/16082/16083/16084/16085/16086/16087/16088/16089/16090/16091] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [storm] defaults: reportype: [17001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [smos] defaults: reportype: [18001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [windsat_as] defaults: reportype: [19001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmi] defaults: reportype: [20001/20002/20003/20004/20005/20006/20007] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsua_as] defaults: reportype: [21001/21002/21003/21004/21005/21007/21008/21009/21010] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsre] defaults: reportype: [22001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [tmi] defaults: reportype: [23001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmis] defaults: reportype: [24001/24002/24003/24004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [gbrad] defaults: reportype: [25001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwhs] defaults: reportype: [26001/26002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwts] defaults: reportype: [27001/27002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwri_as] defaults: reportype: [28001/28002/28003/28004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [iras] defaults: reportype: [29001/29002/29003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [msu] defaults: reportype: [30001/30002/30003/30004/30005/30006/30007/30008/30009/30010/30011/30012/30013/30014/30015/30016/30017/30018] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssu] defaults: reportype: [31001/31002/31003/31004/31005/31006/31007/31008/31009/31010/31011/31012/31013/31014/31015/31016] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [vtpr1] defaults: reportype: [32001/32002/32003/32004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [vtpr2] defaults: reportype: [33001/33002/33003/33004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [atms] defaults: reportype: [34001/34002/34003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [resat_ak] defaults: reportype: [35001/35002/35003/35004/35005/35006/35007/35008/35009/35010/35011/35012/35013/35014/35015/35016/35017/35018/35019/35020/35021/35022/35023/35024/35025/35026/35027/35028/35029/35030/35031/35032/35033/35034/35035/35036/35037] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [cris] defaults: reportype: [36001/36002/36003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [wave_ip] defaults: reportype: [37001/37002/37003/37004/37005/37006/37007/37008/37009/37010/37012/37013] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [wave_sp] defaults: reportype: [38001/38002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [raingg] defaults: reportype: [39001/39002/39003/39004/39005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [sfc_ms] defaults: reportype: [40001/40002/40003/40004/40005/40006/40007/40008/40009] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsr2_as] defaults: reportype: [41001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [saphir_as] defaults: reportype: [42001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsub_as] defaults: reportype: [43001/43002/43003/43004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mhs_as] defaults: reportype: [44001/44002/44003/44004/44005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [dwl] defaults: reportype: [45001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [iris] defaults: reportype: [46001/46002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [alt] defaults: reportype: [47001/47002/47003/47004/47005/47006/47007/47008/47009/47010/47011/47012/47013/47014/47015/47016/47017] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [aatsr] defaults: reportype: [48001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [atms_as] defaults: reportype: [49001/49002/49003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [gmi_as] defaults: reportype: [50001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [godae_sst] defaults: reportype: [51001/51002/51003/51004/51005/51006/51007/51008/51009] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [atovs_ms] defaults: reportype: [52001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [atmospheric_composition] defaults: reportype: [53001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [non_sfc_ms] defaults: reportype: [54001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwts2] defaults: reportype: [55001/55002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmi_1d] defaults: reportype: [56001/56002/56003/56004/56005/56006] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwhs2_as] defaults: reportype: [57001/57002/57003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmt2] defaults: reportype: [58001/58002/58003/58004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [smap] defaults: reportype: [59001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [tovs_ms] defaults: reportype: [60001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [cloud_r] defaults: reportype: [61001/61002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [cloud_l] defaults: reportype: [62001/62002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [satellite_lightning] defaults: reportype: [63001/63002/63003/63004/63005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [geos_vis] defaults: reportype: [64001/64002/64003/64004/64005/64006/64007/64008] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [oconv] defaults: reportype: [65001/65002/65049/65050/65051/65052/65053/65054/65056/65057/65061/65062/65063/65064/65083/65084/65090] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwts3_as] defaults: reportype: [66001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [giirs] defaults: reportype: [67001/67002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [hiras] defaults: reportype: [68001/68002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [tropics_as] defaults: reportype: [69001/69002/69003/69004/69005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmt1] defaults: reportype: [70001/70002/70003/70004/70005/70006] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmt1_as] defaults: reportype: [71001/71002/71003/71004/71005/71006] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mws_as] defaults: reportype: [72001/72002/72003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [mwiici_as] defaults: reportype: [73001/73002/73003] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [aws_as] defaults: reportype: [74001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [leos_vis] defaults: reportype: [75001/75002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssh] defaults: reportype: [76001/76002/76003/76004] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [sirs] defaults: reportype: [77001/77002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [scr] defaults: reportype: [78001/78002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [scams_as] defaults: reportype: [79001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [nems_as] defaults: reportype: [80001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [amsr3_as] defaults: reportype: [81001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [irs] defaults: reportype: [82001/82002] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [iasing] defaults: reportype: [83001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [smap_as] defaults: reportype: [84001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [ssmt2_as] defaults: reportype: [85001/85002/85003/85004/85005] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [smmr_as] defaults: reportype: [86001] - context: type: [ofb,mfb,oai,sfb,fsoifb,fcdfb,ofa] obsgroup: [test] defaults: reportype: [98001/98002/98003/98004/98005/98006/98007/98008/98009/98010/98011/98012/98013/98014/98015/98016/98017/98018/98019/98020/98021/98022/98023/98024/98025/98026/98027/98028/98029/98030/98031/98032/98033/98034/98035/98036/98037/98038/98039/98040] metkit-1.20.2/share/metkit/param-matching.yaml0000664000175000017500000000117415246725757021457 0ustar alastairalastair# wind families used by ParamID normalisation to retrieve u/v from vo/d and related gradients/anomalies wind: # GRIB2 - [131,132,138,155] - [129131,129132,129138,129155] - [171131,171132,171138,171155] - [200131,200132,200138,200155] # GRIB1 - [131.128,132.128,138.128,155.128] - [131.129,132.129,138.129,155.129] - [131.171,132.171,138.171,155.171] - [131.200,132.200,138.200,155.200] # list of tables used by ParamID normalisation to limit the param-table expansion to wind, ocean and reanalysis drop-tables: [140,150,151,160,162,170,180,190] full-table-dropping: true ml-params-single-level: [22, 127, 128, 129, 152] metkit-1.20.2/share/metkit/language.yaml0000664000175000017500000015452215246725757020360 0ustar alastairalastair--- _field: &_field style: type: enum values: - [dissemination] class: category: data defaults: - vals: [od] flatten: false type: enum values: - [a5, ECMWF Atmospheric Composition reanalysis version 5] - [ai, operational aifs] - [at, austria] - [be, belgium] - [c3, c3s] - [ce, cems] - [ch, switzerland] - [ci, cerise] - [co, cosmo] - [cr, cams research] - [cs, ecsn] - [d1, destine] - [de, germany] - [dk, denmark] - [dm, demeter] - [dt, dts] - [e2, e20c] - [e4, reanalyse40] - [e6, era6] - [ea, era5, esat] - [ed, eerie] - [ef, efas] - [eh, c3s europe] - [ei, era interim] - [el, eldas] - [em, e20cm] - [en, ensembles] - [ep, cera-20c, cera20c] - [er, reanalyse] - [es, spain] - [et, cera-sat, cerasat] - [fi, finland] - [fr, france] - [gf, glofas] - [gg, greenhouse gases] - [gh, c3s global] - [gr, greece] - [gw, global wildfire information system] - [hr, croatia] - [hu, hungary] - [ie, ireland] - [is, iceland] - [it, italy] - [j5, jra55] - [l5, era5l] - [l6, era6l] - [la, aladin-laef, laef, lace] - [lr, WMO Lead Centre for Global Climate Re-Analyses] - [lw, WMO lead centre wave forecast verification] - [ma, metaps] - [mc, macc] - [me, mersea] - [ml, machine learning] - [ms, member states] - [ng, nextgems] - [nl, netherlands] - ['no', norway] - [nr, ncep 20cr, 20cr] - [o6, 'ocean 6 reanalysis'] - [od, operations] - [pe, permanent experiment] - [pt, portugal] - [pv, provost] - [rd, research] - [rm, euro4m] - [rr, regional reanalysis] - [s2, s2s] - [se, sweden] - [si, slovenia] - [sr, sreps] - [te, test] - [ti, tigge] - [to, tost] - [tr, turkey] - [uk, united kingdom] - [ul, ulysses] - [ur, uerra] - [yp, yopp] - [yt, yotc] type: category: data defaults: - vals: [an] flatten: false type: enum values: - [3g, 3d variational gradients] - [3v, 3d variational analysis] - [4g, 4d variational gradients] - [4i, 4d variational increments] - [4v, 4d variational analysis] - [ab, analysis bias] - [af, analysis feedback] - [ai, analysis input] - [an, analysis] - [as, adjoint singular vector] - [bf, bias-corrected forecast] - [cd, climate distribution] - [cf, control forecast] - [ci, clustering information] - [cl, climatology] - [cm, cluster means] - [cpf, crossing-point forecast] - [cr, cluster representative] - [cs, cluster std deviations] - [cv, calibration validation forecast] - [ea, errors in analysis] - [ed, empirical distribution] - [ef, errors in first guess] - [efi, extreme forecast index] - [efic, extreme forecast index control] - [em, ensemble mean] - [emdn, ensemble median] - [eme, ensemble data assimilation model errors] - [emtm, ensemble mean of temporal mean] - [ep, event probability] - [es, ensemble standard deviation] - [est, ensemble statistics] - [estdtm, ensemble standard deviation of temporal mean] - [fa, forecast accumulation] - [fb, feedback] - [fc, forecast] - [fcdfb, forecast departures feedback] - [fcmax, forecast maximum] - [fcmean, forecast mean] - [fcmin, forecast minimum] - [fcstdev, forecast standard deviation] - [ff, flux forcing realtime] - [fg, first guess] - [fp, forecast probability] - [fsoifb, forecast sensitivity to observations impact feedback] - [fu, fill-up] - [fx, flux forcing] - [ga, gfas analysis] - [gbf, bias-corrected gridbox] - [gai, gridded analysis input] - [go, gridded observations] - [gsd, gridded satellite data] - [gwt, weather type gridbox] - [hcmean, hindcast mean] - [ia, init. analysis] - [icp, initial condition perturbation] - [mpp, model physics perturbation] - [if, interim forecast] - [im, images] - [me, model errors] - [mfb, mondb feedback] - [oai, odb analysis input] - [ob, observations] - [of, ocean forward] - [ofa, odb feedback from analysis] - [ofb, odb feedback] - [oi, oi analysis] - [oldim, old format images] - [or, ocean reanalysis] - [pa, perturbed analysis] - [pb, probability boundary] - [pd, probability distribution] - [pf, perturbed forecast] - [pfc, point values] - [ppm, point value metrics] - [s3, climate 30 days simulation] - [ses, scaled ensemble standard deviation] - [sf, sensitivity forecast] - [sfb, summary feedback] - [sfo, simulations with forcing] - [sg, sensitivity gradient] - [si, climate simulation] - [sim, simulated images] - [sot, shift of tails] - [ssd, simulated satellite data] - [sv, singular vector] - [svar, signal variance] - [taem, time average ensemble mean] - [taes, time average ensemble standard deviation] - [tpa, time processed analysis] - [tf, trajectory forecast] - [tu, tube] - [wem, weighted ensemble mean] - [wes, weighted ensemble standard deviation] - [wp, weather parameters] stream: category: data flatten: false type: enum values: - [amap, analysis for multianalysis project] - [ammc, melbourne] - [cher, ch, chernobyl] - [clte, climate, Climate run output] - [clmn, climate-monthly, Climate run monthly means output] - [cnrm, meteo france climate centre] - [cwao, montreal] - [dacl, daily climatology] - [dacw, daily climatology wave] - [dahc, daily archive hindcast] - [dame] - [dcda, atmospheric model (delayed cutoff)] - [dcwv, wave model (delayed cutoff)] - [edmm, ensemble data assimilation monthly means] - [edmo, ensemble data assimilation monthly means of daily means] - [edzw, offenbach] - [eefh, extended ensemble forecast hindcast] - [eefo, extended ensemble prediction system] - [eehs, extended ensemble forecast hindcast statistics] - [efas, european flood awareness system (efas)] - [efcl, european flood awareness system (efas) climatology] - [efhc, ensemble forecast hindcasts (obsolete)] - [efho, ensemble forecast hindcast overlap] - [efhs, ensemble forecast hindcast statistics] - [efov, ensemble forecast overlap] - [efrf, european flood awareness system (efas) reforecasts] - [efse, european flood awareness system (efas) seasonal forecasts] - [efsr, european flood awareness system (efas) seasonal reforecasts] - [egrr, exeter, bracknell] - [ehmm, combined multi-model hindcast monthly means] - [elda, ensemble long window data assimilation] - [enda, ensemble data assimilation] - [enfh, ensemble forecast hindcasts] - [enfo, ef, ensemble prediction system] - [enwh, ensemble forecast wave hindcasts] - [esmm, combined multi-model monthly means] - [espd, ensemble supplementary data] - [ewda, ensemble wave data assimilation] - [ewhc, wave ensemble forecast hindcast (obsolete)] - [ewho, ensemble forecast wave hindcast overlap] - [ewla, ensemble wave long window data assimilation] - [ewmm, ensemble wave data assimilation monthly means] - [ewmo, ensemble wave data assimilation monthly means of daily means] - [fgge, fg] - [fsob, forecast sensitivity to observations] - [fsow, forecast sensitivity to observations wave] - [gfas, global fire assimilation system] - [gfra, global fire assimilation system reanalysis] - [kwbc, washington] - [ldas, land data-assimilation system (near real time)] - [ldbt, land data-assimilation system behind real time] - [ldsb, land data-assimilation system statistics behind real time] - [ldst, land data-assimilation system statistics (near real time)] - [lfpw, paris, toulouse] - [lwda, long window daily archive] - [lwwv, long window wave] - [ma, means archive] - [maed, multianalysis ensemble data] - [mawm, wave anomaly means] - [mawv, multianalysis wave data] - [mdfa, monthly means of daily forecast accumulations] - [mfam, anomaly means] - [mfaw, wave anomalies] - [mfhm, hindcast means] - [mfhw, monthly forecast hindcasts wave] - [mfwm, wave real-time means] - [mhwm, wave hindcast means] - [mmaf, multi-model multi-annual forecast] - [mmam, multi-model multi-annual forecast means] - [mmaw, multi-model multi-annual forecast wave] - [mmsa, multi-model seasonal forecast monthly anomalies] - [mmsf, multi-model seasonal forecast] - [mmwm, multi-model multi-annual forecast wave means] - [mnfa, anomalies] - [mnfc, real-time] - [mnfh, hindcasts] - [mnfm, real-time means] - [mnfw, wave real-time] - [mnth, mo, monthly, monthly means] - [mnvr, monthly variance and covariance data using g. boer's step function] - [moda, monthly means of daily means] - [mofc, monthly forecast] - [mofm, monthly forecast means] - [monr, monthly means using g. boer's step function] - [mpic, max plank institute] - [msda, monthly standard deviation and covariance of daily means] - [msdc, mv, monthly standard deviation and covariance] - [msmm, multi-model seasonal forecast atmospheric monthly means] - [mswm, multi-model seasonal forecast wave monthly means] - [ocda, ocean data assimilation] - [ocea, ocean] - [olda, ocean Long window data assimilation] - [oper, da, daily archive, atmospheric model] - [rjtd, tokyo] - [rfsd, retrospective forcing and simulation data] - [scda, atmospheric model (short cutoff)] - [scwv, wave model (short cutoff)] - [seap, sensitive area prediction] - [seas, seasonal forecast] - [sens, sf, sensitivity forecast] - [sfdd, seasonal forecast daily and sub-daily data] - [sfdp, seasonal forecast derived products] - [sfmd, seasonal forecast monthly data] - [sfmm, seasonal forecast atmospheric monthly means] - [shdd, seasonal hindcast daily and sub-daily data] - [shdp, seasonal hindcast derived products] - [shmd, seasonal hindcast monthly data] - [smma, seasonal monthly means anomalies] - [sttd, Statistics of deterministic data] - [stte, Statistics of ensemble data] - [supd, sd, deterministic supplementary data] - [swmm, seasonal forecast wave monthly means] - [toga, tg] - [ukmo, ukmo climate centre] - [waef, we, wave ensemble forecast] - [wamd, wave monthly means of daily means] - [wamf, wave monthly forecast] - [wamo, wave monthly means] - [wams, multi-model seasonal forecast wave] - [wasf, wave seasonal forecast] - [wave, wv, wave model] - [wavm, wave model (standalone)] - [weef, wave extended ensemble forecast] - [weeh, wave extended ensemble forecast hindcast] - [wees, wave extended ensemble forecast hindcast statistics] - [wehs, wave ensemble forecast hindcast statistics] - [weov, wave ensemble forecast overlap] - [wfas, global flood awareness system (glofas)] - [wfcl, global flood awareness system (glofas) climatology] - [wfrf, global flood awareness system (glofas) reforecasts] - [wfse, global flood awareness system (glofas) seasonal forecasts] - [wfsr, global flood awareness system (glofas) seasonal reforecasts] - [wmfm, wave monthly forecast means] - [wvhc, wave hindcast] - [xwda, extended window data assimilation] product: category: data type: enum values: - [inst, instantaneous] - [tims, time-series] - [tavg, time-average] - [tacc, time-accumulation] section: category: data type: enum values: - [h, horizontal] - [v, vertical] - [z, zonal] - [m, meridional] range: category: data type: any use: flatten: false multiple: true type: enum values: - [frequent, f] - [infrequent, i] - [normal, n] - compare - [monthly run] - intent - disk # - [bc, boundary conditions] # - [monday, mon] # - [tuesday, tue] # - [wednesday, wed] # - [thursday, thu] # - [friday, fri] # - [saturday, sat] # - [sunday, sun] # product: # %if LEVTYPE = DP # %and TYPE = OR # %and (%not PRODUCT) # %then # %warning "Default PRODUCT for Ocean ReAnalysis set to Instantaneous" # %set PRODUCT = INST expver: category: data flatten: false type: expver defaults: - vals: ['0001'] dataset: category: data multiple: false type: - context: class: [d1] type: enum values: - [climate-dt, Climate change adaptation digital twin] - [extremes-dt, Weather and geophysical extremes digital twin] - [on-demand-climate-dt, On-demand climate change adaptation digital twin] - [on-demand-extremes-dt, On-demand weather and geophysical extremes digital twin] - type: lowercase model: category: data type: - context: class: [ms] country: [it] type: enum values: - [lm] - [hrm] - [wam] - context: class: [ti, lw, s2] type: enum values: - [lam] - [global, glob] - type: enum values: - [ecmf] - [edzw] - [egrr] - [lfpw] - [kwbc] - [none] - [hrm] - [ifs, IFS with no ocean model] - [ifs-nemo, IFS with NEMO ocean model] - [ifs-fesom, IFS with FESOM ocean model] - [icon, ICON-A with ICON-O ocean model] - [lisflood] - [lm] - [lam] - [aifs] - [aifs-single] - [aifs-single-mse] - [aifs-ens] - [aifs-ens-crps] - [aifs-ens-diff] - [aifs-subs] - [aifs-subs-crps] - [aifs-compo-single] - [aifs-compo-ens] - [aifs-compo-single-mse] - [aifs-compo-ens-crps] - [alaro] - [arome] - [harmonie-arome] - [global, glob] - [ecpoint] - [ecland] - [htessel-lisflood] - [hype] - [jules] - [mhm] - [pcr-globwb] - [vic-wur] - [geff] - [sparky] forcing: category: data multiple: true type: enum values: - [ecmf-ifs] - [ecmf-aifs] - [ecmf-era5] - [ecmf-seas] - [edzw-icon] - [cosmo-cosmo] - [cmcc-sps] - [obs] configuration: category: data multiple: true type: enum values: - [v1.0] - [v1.0-catchment] - [v1.0-grid] - [v1.0-c3s] - [v2.0] - [v2.1] - [v3.0] - [v3.1] - [v3.5] - [v4.0] - [v5.0] - [v6.0] - [49r2-sfdd] - [50r1-oper] - [50r1-enfo] - [50r1-sfdd] - [50r2-oper] - [50r2-enfo] - [50r2-sfdd] georef: category: data type: lowercase repres: flatten: false multiple: false type: enum values: - [bu, 'bufr'] - [sh, 'spherical harmonics'] - [ll, 'lat long grid'] - [gg, 'gaussian grid'] - [sv, 'space view'] - [og, 'ocean grid'] - [np] - [rl] coeffindex: category: data multiple: true type: to-by-list by: 1 unset: - context: type: op: '!' vals: [me, eme] obscutoff: category: data multiple: true type: time obsgroup: category: data multiple: true type: enum values: # - [conventional] - [sat, satellite] - [ers1] - [trmm] - [qscat] - [reo3] # reo3 needs to stay for compatibility # previously in "obsgroups.def" - [hirs, 1] - [amsua, 2] - [amsub, 3] - [mhs, 4] - [geos, 5] - [resat, 6] - [meris, 7] - [gpsro, 8] - [satob, 9] - [scatt, 10] - [ssmi_as, 11, SSMI ALL-SKY] - [iasi, 12] - [airs, 13] - [ssmis_as, 14, SSMIS ALL-SKY] - [tmi_as, 15, TMI ALL-SKY] - [amsre_as, 16, AMSRE ALL-SKY] - [conv, 17] - [smos, 19] - [windsat_as, 20, WINDSAT ALL-SKY] - [ssmi, 21] - [amsua_as, 22, AMSUA ALL-SKY] - [amsre, 23] - [tmi, 24] - [ssmis, 25] - [gbrad, 26] - [mwhs, 27] - [mwts, 28] - [mwri_as, 29, MWRI ALL-SKY] - [iras, 30] - [msu, 31] - [ssu, 32] - [vtpr1, 33] - [vtpr2, 34] - [atms, 35] - [resat_ak, 36, RESAT AVERAGING KERNELS] - [cris, 37] - [wave_ip, 38, WAVE INTEGRATED PARAMETERS] - [wave_sp, 39, WAVE SPECTRA] - [raingg, 40] - [sfc_ms, 41, SURFACE MULTISENSOR] - [amsr2_as, 42, AMSR-2 ALL-SKY] - [saphir_as, 43, SAPHIR ALL-SKY] - [amsub_as, 44, AMSUB ALL-SKY] - [mhs_as, 45, MHS ALL-SKY] - [dwl, 46, DOPPLER WIND LIDAR] - [iris, 47] - [alt, 48, Altimeter] - [aatsr, 49] - [atms_as, 50, ATMS ALL-SKY] - [gmi_as, 51, GMI ALL-SKY] - [godae_sst, 52, GODAE SEA SURFACE TEMPERATURES] - [atovs_ms, 53, ATOVS MULTISENSOR] - [atmospheric_composition, 54, ATMOSPHERIC COMPOSITION] - [non_sfc_ms, 55, NON-SURFACE MULTISENSOR] - [mwts2, 56] - [ssmi_1d, 57, SSMI 1DVAR TCWV CLOUDY-SKY] - [mwhs2_as, 58, MWHS2 ALL-SKY] - [ssmt2, 59] - [smap, 60] - [tovs_ms, 61, TOVS MULTISENSOR] - [cloud_r, 62, CLOUD REFLECTIVITY] - [cloud_l, 63, CLOUD LIDAR] - [satellite_lightning, 64, SATELLITE LIGHTNING] - [geos_vis, 65, GEOS VIS] - [oconv, 66] - [mwts3_as, 67, MWTS3 All-sky] - [giirs, 68] - [hiras, 69] - [tropics_as, 70, TROPICS All-sky] - [ssmt1, 71] - [ssmt1_as, 72, SSMT1 All-sky] - [mws_as, 73, MWS All-sky] - [mwiici_as, 74, MWIICI All-sky] - [aws_as, 75, AWS All-sky] - [leos_vis, 76, LEOS VIS] - [ssh, 77] - [sirs, 78] - [scr, 79] - [scams_as, 80, SCAMS All-sky] - [nems_as, 81, NEMS All-sky] - [amsr3_as, 82, AMSR-3 All-sky] - [irs, 83] - [iasi_ng, 84, IASI-NG] - [smap_as, 85, SMAP All-sky] - [ssmt2_as, 86, SSMT2 All-sky] - [test, 99] reportype: category: data type: any multiple: true # rdbprefix levtype: category: data aliases: [levtyp] defaults: - context: stream: ['!', dacw, dcwv, ewda, ewhc, ewla, ewmm, ewmo, lwwv, mawm, mawv, mfaw, mfhw, mfwm, mhwm, mnfw, mswm, scwv, swmm, waef, wamd, wamf, wamo, wams, wasf, wave, weef, weeh, wees, wehs, weov, wmfm, wvhc] vals: [pl] - context: stream: [dacw, dcwv, ewda, ewhc, ewla, ewmm, ewmo, lwwv, mawm, mawv, mfaw, mfhw, mfwm, mhwm, mnfw, mswm, scwv, swmm, waef, wamd, wamf, wamo, wams, wasf, wave, weef, weeh, wees, wehs, weov, wmfm, wvhc] vals: [sfc] flatten: false type: [enum, integer] values: - [cat, category] - [dp, depth] - [layer] - [al, abstract levels] - [ml, model levels] - [pl, pressure levels] - [fl, flight levels] - [hl, height levels] - [pt, potential temperature] - [pv, potential vorticity] - [sfc, surf, surface] # sl ?? - [sol, surface other (multi)levels] - [wv, ocean wave] - [o2d, ocean surface] - [o3d, ocean model levels] levelist: category: data aliases: [leve,level,levellist] multiple: true type: to-by-list-float by: 1 units: category: postproc type: enum values: - [si] - [av] param: category: data aliases: [parameter,parameters] multiple: true type: param defaults: - vals: [129] chem: category: data multiple: true type: enum values: "chemids.yaml" wavelength: category: data multiple: true type: range default: none timespan: category: data type: enum values: - [fs, from-start, fromstart] - [10m] - [15m] - [20m] - [30m] - [1h] - [3h] - [6h] - [12h] - [18h] - [24h] - [48h] - [72h] - [120h] - [168h] - [240h] - [360h] - [none, inst, instantaneous] default: none multiple: true paramtype: category: derived type: enum values: - [chemical] - [chemical_optical] - [base] - [wave] - [wave_spectra] - [optical] - [tile] stattype: category: data type: enum values: - [moav, monthly average] - [momn, monthly minimum] - [momx, monthly maximum] - [mosd, monthly standard-deviation] - [daac, daily accumulation] - [daav, daily average] - [damn, daily minimum] - [damx, daily maximum] - [dasd, daily standard-deviation] - [moav_daav, monthly average of daily average] - [moav_damn, monthly average of daily minimum] - [moav_damx, monthly average of daily maximum] - [moav_dasd, monthly average of daily standard-deviation] - [momn_daav, monthly minimum of daily average] - [momn_damn, monthly minimum of daily minimum] - [momn_damx, monthly minimum of daily maximum] - [momn_dasd, monthly minimum of daily standard-deviation] - [momx_daav, monthly maximum of daily average] - [momx_damn, monthly maximum of daily minimum] - [momx_damx, monthly maximum of daily maximum] - [momx_dasd, monthly maximum of daily standard-deviation] - [mosd_daav, monthly standard-deviation of daily average] - [mosd_damn, monthly standard-deviation of daily minimum] - [mosd_damx, monthly standard-deviation of daily maximum] - [mosd_dasd, monthly standard-deviation of daily standard-deviation] multiple: false tile: category: data type: enum values: # 1-1000 Reserved for intercomparison tile classes/groupings - [seoc, 1, 'Seas and oceans'] - [inwb, 2, 'Inland water bodies'] - [bare, 3, 'Bare areas'] - [hiv, 4, 'High vegetation'] - [lov, 5, 'Low vegetation'] - [urban, 6] # 1001-1500 Reserved for tile class entries for tile models using the ECOCLIMAP-SG land cover survey - [seao, 1001, 'Sea and oceans'] - [lake, 1002, 'Lakes'] - [rive, 1003, 'Rivers'] - [no, 1004, 'Bare land'] - [rock, 1005, 'Bare rock'] - [snow, 1006, 'Permanent snow'] - [bobd, 1007, 'Boreal broadleaf deciduous'] - [tebd, 1008, 'Temperate broadleaf deciduous'] - [trbd, 1009, 'Tropical broadleaf deciduous'] - [tebe, 1010, 'Temperate broadleaf evergreen'] - [trbe, 1011, 'Tropical broadleaf evergreen'] - [bone, 1012, 'Boreal needleleaf evergreen'] - [tene, 1013, 'Temperate needleleaf evergreen'] - [bond, 1014, 'Boreal needleleaf deciduous'] - [shrb, 1015, 'Shrubs'] - [bogr, 1016, 'Boreal grassland'] - [gras, 1017, 'Temperate grassland'] - [trog, 1018, 'Tropical grassland'] - [c3w, 1019, 'Winter C3 crops'] - [c3s, 1020, 'Summer C3 crops'] - [c4, 1021, 'C4 crops'] - [fltr, 1022, 'Flooded trees'] - [flgr, 1023, 'Flooded grassland'] - [lcz1, 1024, 'Local climate zone 1 - compact high-rise (buildings)'] - [lcz2, 1025, 'Local climate zone 2 - compact midrise (buildings)'] - [lcz3, 1026, 'Local climate zone 3 - compact low-rise (buildings)'] - [lcz4, 1027, 'Local climate zone 4 - open high-rise (buildings)'] - [lcz5, 1028, 'Local climate zone 5 - open midrise (buildings)'] - [lcz6, 1029, 'Local climate zone 6 - open low-rise (buildings)'] - [lcz7, 1030, 'Local climate zone 7 - lightweight low-rise (buildings)'] - [lcz8, 1031, 'Local climate zone 8 - large low-rise (buildings)'] - [lcz9, 1032, 'Local climate zone 9 - sparsely built (buildings)'] - [lcz10, 1033, 'Local climate zone 10 - heavy industry (buildings)'] - [tebdu, 1034, 'Urban temperate broadleaf deciduous'] - [trbdu, 1035, 'Urban tropical broadleaf deciduous'] - [tebeu, 1036, 'Urban temperate broadleaf evergreen'] - [trbeu, 1037, 'Urban tropical broadleaf evergreen'] - [boneu, 1038, 'Urban boreal needleleaf evergreen'] - [teneu, 1039, 'Urban temperate needleleaf evergreen'] - [bondu, 1040, 'Urban boreal needleleaf deciduous'] # 1501-2000 Reserved for tile grouping entries for tile models using the ECOCLIMAP-SG land cover survey - [gnatu, 1501, 'Nature grouping (NO + ROCK + SNOW + BOBD + TEBD + TRBD + TEBE + TRBE + BONE + TENE + BOND + SHRB + BOGR + GRAS + TROG + C3W + C3S + C4 + FLTR + FLGR)'] - [gnofo, 1502, 'Non-forest grouping (NO + ROCK + SNOW + BOGR + GRAS + TROG + C3W + C3S + C4 + FLTR + FLGR)'] - [gfore, 1503, 'Forest grouping (BOBD + TEBD + TRBD + TEBE + TRBE + BONE + TENE + BOND + SHRB)'] - [gnove, 1504, 'Non-vegetated grouping (NO + ROCK + SNOW)'] - [gtree, 1505, 'Trees grouping (BOBD + TEBD + TRBD + TEBE + TRBE + BONE + TENE + BOND + SHRB + FLTR)'] - [glveg, 1506, 'Low vegetation grouping (BOGR + GRAS + TROG + C3W + C3S + C4 + FLGR)'] - [gnflv, 1507, 'Non-flooded low vegetation grouping (BOGR + GRAS + TROG + C3W + C3S + C4)'] - [gcrop, 1508, 'Crops grouping (C3W + C3S + C4)'] - [ggras, 1509, 'Grassland grouping (BOGR + GRAS + TROG)'] - [gbaro, 1510, 'Bare and rock grouping (NO + ROCK)'] - [gc3cr, 1511, 'C3 crops grouping (C3W + C3S)'] - [gbfor, 1512, 'Broadleaf forest grouping (BOBD + TEBD + TRBD + TEBE + TRBE + SHRB)'] - [gnfor, 1513, 'Needleleaf forest grouping (BONE + TENE + BOND)'] - [gdbfo, 1514, 'Deciduous broadleaf forest grouping (BOBD + TEBD + TRBD + TEBE + SHRB)'] - [gtgra, 1515, 'Temperate grassland grouping (BOGR + GRAS)'] - [gnfnf, 1516, 'Non-forest (no FLTR) grouping (NO + ROCK + SNOW + BOGR + GRAS + TROG + C3W + C3S + C4 + FLGR)'] - [gbfft, 1517, 'Broadleaf forest (with FLTR) grouping (BOBD + TEBD + TRBD + TEBE + TRBE + SHRB + FLTR)'] - [gbtre, 1518, 'Broadleaf trees grouping (BOBD + TEBD + TRBD + TEBE + TRBE + FLTR)'] - [gcgra, 1519, 'C3/C4 grasslands grouping (BOGR + GRAS + FLGR)'] - [gbdtr, 1520, 'Broadleaf deciduous trees grouping (BOBD + TEBD + TRBD + FLTR)'] - [gbetr, 1521, 'Broadleaf evergreen trees grouping (TEBE + TRBE)'] - [gnetr, 1522, 'Needleleaf evergreen trees grouping (BONE + TENE)'] - [glczu, 1523, 'LCZ or urban grouping (LCZ1 + LCZ2 + LCZ3 + LCZ4 + LCZ5 + LCZ6 + LCZ7 + LCZ8 + LCZ9 + LCZ10)'] - [giwat, 1524, 'Inland water grouping (LAKE + RIVE)'] - [gptebd, 1525, 'Urban parks and gardens TEBD grouping (NO + GRAS + TEBDU)'] - [gptrbd, 1526, 'Urban parks and gardens TRBD grouping (NO + GRAS + TRBDU)'] - [gptebe, 1527, 'Urban parks and gardens TEBE grouping (NO + GRAS + TEBEU)'] - [gptrbe, 1528, 'Urban parks and gardens TRBE grouping (NO + GRAS + TRBEU)'] - [gpbone, 1529, 'Urban parks and gardens BONE grouping (NO + GRAS + BONEU)'] - [gptene, 1530, 'Urban parks and gardens TENE grouping (NO + GRAS + TENEU)'] - [gpbond, 1531, 'Urban parks and gardens BOND grouping (NO + GRAS + BONDU)'] # 2001-2500 Reserved for tile class entries for tile models using the GLCC v2.0 BATS Classification survey - [crmf, 2001, 'Crops, mixed farming'] - [shgr, 2002, 'Short grass'] - [evne, 2003, 'Evergreen needleleaf trees'] - [dene, 2004, 'Deciduous needleleaf trees'] - [debr, 2005, 'Deciduous broadleaf trees'] - [evbr, 2006, 'Evergreen broadleaf trees'] - [tagr, 2007, 'Tall grass'] - [dese, 2008, 'Desert'] - [tund, 2009, 'Tundra'] - [ircr, 2010, 'Irrigated crops'] - [sede, 2011, 'Semidesert'] - [icgl, 2012, 'Ice caps and glaciers'] - [boma, 2013, 'Bogs and marshes'] - [inwa, 2014, 'Inland water'] - [ocea, 2015, 'Ocean'] - [evsh, 2016, 'Evergreen shrubs'] - [desh, 2017, 'Deciduous shrubs'] - [mfwo, 2018, 'Mixed forest/woodland'] - [info, 2019, 'Interrupted forest'] - [wlmi, 2020, 'Water and land mixtures'] - [baso, 2021, 'Bare soil'] - [urba, 2022, 'Urban (GLCC BATS)'] - [lori, 2023, 'Low-rise building'] - [hiri, 2024, 'High-rise building'] # 2501-3000 Reserved for tile grouping entries for tile models using the GLCC v2.0 BATS Classification survey - [glve, 2501, 'Low vegetation grouping (CRMF + SHGR + TAGR + TUND + IRCR + SEDE + BOMA + EVSH + DESH + WLMI)'] - [ghve, 2502, 'High vegetation grouping (EVNE + DENE + DEBR + EVBR + MFWO + INFO)'] # 3001-3500 Reserved for tile class entries for tile models using the ESA-GlobCover GCV2009 survey - [pfic, 3001, 'Post-flooding or irrigated croplands'] - [racr, 3002, 'Rainfed croplands'] - [mcve, 3003, 'Mosaic cropland (50%-70%)/vegetation (grassland, shrubland, forest) (20-50%)'] - [mvcr, 3004, 'Mosaic vegetation (grassland, shrubland, forest) (50%-70%)/cropland (20%-50%)'] - [cobs, 3005, 'Closed to open (>15%) broadleaved evergreen and/or semi-deciduous forest (>5m)'] - [clbd, 3006, 'Closed (>40%) broadleaved deciduous forest (>5m)'] - [opbd, 3007, 'Open (15-40%) broadleaved deciduous forest (>5m)'] - [clne, 3008, 'Closed (>40%) needleleaved evergreen forest (>5m)'] - [onde, 3009, 'Open (15-40%) needleleaved deciduous or evergreen forest (>5m)'] - [cobn, 3010, 'Closed to open (>15%) mixed broadleaved and needleleaved forest (>5m)'] - [mfsg, 3011, 'Mosaic forest/shrubland (50%-70%)/grassland (20%-50%)'] - [mgfs, 3012, 'Mosaic grassland (50%-70%)/forest/shrubland (20%-50%)'] - [cosh, 3013, 'Closed to open (>15%) shrubland (<5m)'] - [cohv, 3014, 'Closed to open (>15%) herbaceous vegetation'] - [spve, 3015, 'Sparse (<15%) vegetation (woody vegetation, shrubs, grassland)'] - [cobff, 3016, 'Closed to open (>15%) broadleaved forest regularly flooded (semi-permanently or temporarily) - fresh or brackish water'] - [clbff, 3017, 'Closed (>40%) broadleaved forest or shrubland permanently flooded - saline or brackish water'] - [covef, 3018, 'Closed to open (>15%) vegetation (grassland, shrubland, woody vegetation) on regularly flooded or waterlogged soil - fresh, brackish or saline water'] - [asaa, 3019, 'Artificial surfaces and associated areas (urban areas >50%)'] - [baar, 3020, 'Bare areas (GCV2009)'] - [wabo, 3021, 'Water bodies'] - [pesi, 3022, 'Permanent snow and ice'] # 3501-4000 Reserved for tile grouping entries for tile models using the ESA-GlobCover GCV2009 survey - [grdt, 3501, 'Grouping using the dominant tiles approach (dominant tile varies per gridbox)'] # 4001-4500 Reserved for tile class entries for tile models using the European Commission-Global Land Cover Project GLC2000 survey - [tbeco, 4001, 'Tree cover, broadleaved evergreen, closed to open (>15%)'] - [tbdc, 4002, 'Tree cover, broadleaved deciduous, closed (>40%)'] - [tbdo, 4003, 'Tree cover, broadleaved deciduous, open (15-40%)'] - [tneco, 4004, 'Tree cover, needleleaved evergreen, closed to open (>15%)'] - [tndco, 4005, 'Tree cover, needleleaved deciduous, closed to open (>15%)'] - [tmlco, 4006, 'Tree cover, mixed leaftype, closed to open (>15%)'] - [tcoff, 4007, 'Tree cover, closed to open (>15%), regularly flooded, fresh or brackish water - swamp forests'] - [tcofs, 4008, 'Tree cover, closed to open (>15%), regularly flooded, saline water - mangrove forests'] - [mtvg, 4009, 'Mosaic of tree cover and other natural vegetation (crop component possible)'] - [tburn, 4010, 'Tree cover, burnt (mainly boreal forests)'] - [scoe, 4011, 'Shrubcover, closed to open (>15%), evergreen (broadleaved or needleleaved)'] - [scod, 4012, 'Shrubcover, closed to open (>15%), deciduous (broadleaved)'] - [hcco, 4013, 'Herbaceous cover, closed to open (>15%)'] - [shsc, 4014, 'Sparse herbaceous or sparse shrub cover'] - [fshco, 4015, 'Regularly flooded (>2 month) shrub or herbaceous cover, closed to open'] - [crop, 4016, 'Cropland (upland crops or inundated/ flooded crops)'] - [mctnv, 4017, 'Mosaic of cropland / tree cover/ other natural vegetation'] - [mcsh, 4018, 'Mosaic of cropland / shrub or herbaceous cover'] - [baare, 4019, 'Bare areas (GLC2000)'] - [wbna, 4020, 'Water bodies (natural or artificial)'] - [sina, 4021, 'Snow or ice (natural or artificial)'] - [urar, 4022, 'Urban areas'] # 4501-5000 Reserved for tile grouping entries for tile models using the European Commission-Global Land Cover Project GLC2000 survey default: none multiple: true tileattribute: category: data type: enum values: - [undef, 0, Undefined] - [unmod, 1, Unmodified] - [snow, 2, Snow covered] - [flood, 3, Flooded] - [ice, 4, Ice covered] - [itcw, 5, With intercepted water] - [itcs, 6, With intercepted snow] - [agg, 7, Aggregated] - [snow_flood, Snow covered and flooded] - [snow_ice, Snow covered and ice covered] - [snow_itcw, Snow covered and with intercepted water] - [snow_itcs, Snow covered and with intercepted snow] - [flood_ice, Flooded and ice covered] - [flood_itcw, Flooded and with intercepted water] - [flood_itcs, Flooded and with intercepted snow] - [ice_itcw, Ice covered and with intercepted water] - [ice_itcs, Ice covered and with intercepted snow] - [itcw_itcs, With intercepted water and with intercepted snow] - [snow_flood_ice, Snow covered and flooded and ice covered] - [snow_flood_itcw, Snow covered and flooded and with intercepted water] - [snow_flood_itcs, Snow covered and flooded and with intercepted snow] - [snow_ice_itcw, Snow covered and ice covered and with intercepted water] - [snow_ice_itcs, Snow covered and ice covered and with intercepted snow] - [snow_itcw_itcs, Snow covered and with intercepted water and with intercepted snow] - [flood_ice_itcw, Flooded and ice covered and with intercepted water] - [flood_ice_itcs, Flooded and ice covered and with intercepted snow] - [flood_itcw_itcs, Flooded and with intercepted water and with intercepted snow] - [ice_itcw_itcs, Ice covered and with intercepted water and with intercepted snow] - [snow_flood_ice_itcw, Snow covered and flooded and ice covered and with intercepted water] - [snow_flood_ice_itcs, Snow covered and flooded and ice covered and with intercepted snow] - [snow_flood_itcw_itcs, Snow covered and flooded and with intercepted water and with intercepted snow] - [snow_ice_itcw_itcs, Snow covered and ice covered and with intercepted water and with intercepted snow] - [flood_ice_itcw_itcs, Flooded and ice covered and with intercepted water and with intercepted snow] - [snow_flood_ice_itcw_itcs, Snow covered and flooded and ice covered and with intercepted water and with intercepted snow] default: none multiple: true tilescheme: category: data type: - context: class: [d1] dataset: [on-demand-extremes-dt] type: enum values: - [unknown, 0] - [simple, 1, Simple tile scheme configuration with most-grouped tiles] - [granular, 2, Granular tile scheme configuration with most-separated tile groupings] default: none multiple: true ################################################################# # year # decade # month date: category: data multiple: true type: date by: 1 defaults: - vals: [-1] year: category: data type: to-by-list multiple: true by: 1 month: category: data flatten: true type: enum multiple: true values: - [1, jan, january] - [2, feb, february] - [3, mar, march] - [4, apr, april] - [5, may] - [6, jun, june] - [7, jul, july] - [8, aug, august] - [9, sep, september] - [10, oct, october] - [11, nov, november] - [12, dec, december] hdate: category: data multiple: true type: date offsetdate: category: data multiple: true type: date fcmonth: category: data multiple: true by: 1 type: to-by-list fcperiod: category: data multiple: true type: any time: category: data multiple: true type: time by: 6h defaults: - vals: ['1200'] offsettime: category: data multiple: true type: time leadtime: category: data multiple: true type: any opttime: category: data multiple: true type: any step: category: data multiple: true by: 12 type: range defaults: - vals: [0] anoffset: category: data multiple: true type: integer reference: category: data multiple: true type: integer ################################################################# # cluster # probability number: category: data multiple: true type: to-by-list by: 1 quantile: category: data multiple: true type: to-by-list-quantile denominators: [2,3,4,5,10,100,1000] by: 1 domain: category: data type: - context: class: [ms] country: [it] type: enum values: - [italy] - [euroatl] - [europe] - [mediterranean] - type: enum flatten: false type: enum values: - [a, north west europe] - [b, north east europe, baltic and black sea] - [c, south west europe] - [d, south east europe] - [e, europe] - [f, fastex] - [g, globe, general european area] - [h] - [i] - [j] - [k] - [l] - [m, mediterranean] - ['n', northern hemisphere] - [o] - [p] - [q] - [r] - [s, southern hemisphere] - [t, tropics] - [u, tropics 2] - [v] - [w, western atlantic] - [x] - ['y'] - [z] bcmodel: category: data type: - context: class: [ms] country: [it] type: enum values: - [gme] - [ifs] icmodel: category: data type: - context: class: [ms] country: [it] type: enum values: - [gme] - [ifs] - [mvoi] - [nudging] - [3dvar] country: category: data multiple: false type: - context: class: [ms] type: enum values: - [it] - [de] grib: category: data multiple: false type: - context: class: [ms] country: [it] type: enum values: - [original] - [ecmwf] frequency: category: data multiple: true by: 1 type: to-by-list direction: category: data multiple: true by: 1 type: to-by-list diagnostic: category: data type: integer multiple: true iteration: category: data type: integer multiple: true channel: category: data type: integer multiple: true # TODO : Migration from old images to new images ident: category: data type: integer multiple: true instrument: category: data type: integer multiple: true # TODO : Migration from old images to new images method: category: data type: integer origin: category: data multiple: true type: enum values: - [aladinhuneps-omsz-eu] - [aladinlaef-zamg-eu] - [ammc, 1, melbourne] - [anso] - [aromeeps-mf-eu] - [babj, 38, beijing] - [cmcc] - [cnmc, 80] - [consensus, 255] - [cosmo] - [cosmodeeps-dwd-eu] - [cosmoleps-arpasimc-eu] - [crfc, 239, cerfacs] - [cwao, 54, montreal] - [dems] - [ecmf, 98, ecmwf] - [ecmf_ea, ecmwf_ea] - [ecmf_od, ecmwf_od] - [edzw, dwd, 78, offenbach] - [egrr, 74, exeter, bracknell] - [ekmi] - [enmi, 88, oslo] - [eswi] - [fapr] - [fnmo, fnmoc, 58, fleet numerical] - [fr-ms-ec] - [glameps-hirlamcons-eu] - [hadc, 247, hadley centre] - [hirlam-dmi-eu] - [ifmk, 246] - [ingv, 235] - [isac] - [knmi, 245] - [kwbc, 7, washington] - [lemm, 214, madrid] - [lfpw, 84, 85, paris, toulouse] - [lops] - [mogreps-mo-eua] - [nasa] - [niwa] - [no-ar-ce] - [no-ar-cw] - [no-ar-pa] - [nzkl] - [pearp-mf-eu] - [rjtd, 34, tokyo] - [rksl, 40, seoul] - [rums] - [sabm] - [sbsj, 46, cptec] - [se-al-ec] - [sreps-aemet-eua] - [srnwppeps-dwd-eua] - [vabb] - [vuwien, 244, university of vienna] system: category: data type: integer ####################################################################### # DestinE ClimateDT related keywords activity: category: data type: enum values: - [cmip6, Coupled Model Intercomparison Project Phase 6] - [scenariomip, Scenario Model Intercomparison Project] - [highresmip, High Resolution Model Intercomparison Project] - [story-nudging, Climate storylines by nudging to reanalysis] - [baseline, Baseline simulations for climate model evaluation] - [projections, Future climate projections] experiment: category: data type: enum values: - [hist, Historical] - [cont, Control] - [amip, Atmospheric Model Intercomparison Project] - [ssp1-1.9, Shared Socio-economic Pathways 1-1.9] - [ssp1-2.6, Shared Socio-economic Pathways 1-2.6] - [ssp2-4.5, Shared Socio-economic Pathways 2-4.5] - [ssp3-7.0, Shared Socio-economic Pathways 3-7.0] - [ssp5-8.5, Shared Socio-economic Pathways 5-8.5] - [tplus1.5k, Warmer world at 1.5 degrees K above pre-industrial temperatures] - [tplus2.0k, Warmer world at 2.0 degrees K above pre-industrial temperatures] - [tplus3.0k, Warmer world at 3.0 degrees K above pre-industrial temperatures] - [tplus4.0k, Warmer world at 4.0 degrees K above pre-industrial temperatures] - [abrupt4xco2, CO2 abruptly quadrupled and then held constant] generation: category: data type: integer realization: category: data multiple: true type: to-by-list by: 1 resolution: category: data type: enum values: - [standard, Standard resolution model output with longer availability] - [high, High resolution model output with limited availability] # - product # - section # - refdate # - latitude # - longitude # - range ####################################################################### _observation: &_observation obstype: category: data multiple: true defaults: - context: _verb: [retrieve] type: [ob] vals: [1] type: enum values: "obstype.yaml" # TODO : Migration from old images to new images range: category: data type: any latitude: category: data type: any longitude: category: data type: any # type: [im] # obstype: [=0 %and (ident=52 %or ident=53 %or ident=54)) %then # %warning "replacing obstype=0 by channel=2, instrument=205" # %unset obstype # %set channel = 2 # %set instrument = 205 # %if (type=im %and obstype=10 %and (ident=52 %or ident=53 %or ident=54)) %then # %warning "replacing obstype=10 by channel=3, instrument=205" # %unset obstype # %set channel = 3 # %set instrument = 205 # %if (type=im %and obstype=20 %and (ident=52 %or ident=53 %or ident=54)) %then # %warning "replacing obstype=10 by channel=1, instrument=205" # %unset obstype # %set channel = 1 # %set instrument = 205 # # goes # %if (type=im %and obstype=0 %and (ident=252 %or ident=253 %or ident=254 %or ident=255 %or ident=256)) %then # %warning "replacing obstype=0 by channel=4, instrument=615" # %unset obstype # %set channel = 4 # %set instrument = 615 # %if (type=im %and obstype=10 %and (ident=252 %or ident=253 %or ident=254 %or ident=255 %or ident=256)) %then # %warning "replacing obstype=10 by channel=1, instrument=615" # %unset obstype # %set channel = 1 # %set instrument = 615 # %if (type=im %and obstype=20 %and (ident=252 %or ident=253 %or ident=254 %or ident=255 %or ident=256)) %then # %warning "replacing obstype=20 by channel=3, instrument=615" # %unset obstype ####################################################################### _postproc: &_postproc accuracy: category: postproc flatten: false type: [enum, integer] values: - [av, archived value] - ['n', normal, auto] - [r, reduced] - [l, low] bitmap: category: postproc flatten: false type: any format: category: postproc flatten: false type: enum values: - [grib1, grib, gb, grib edition 1] - [grib2, grib edition 2] - [bufr, bf] - [grid, gd] - [netcdf] - [odb] - [ascii] - [p, packed] - [u, unpacked] frame: category: postproc type: integer gaussian: category: postproc type: enum values: - reduced - regular area: category: postproc flatten: false multiple: true type: [float, enum] values: - [g, global] - [e, europe] grid: category: postproc flatten: false multiple: true type: [enum, regex, float] values: - auto - [av, archived value] - [F16, 16] - [F24, 24] - [F32, 32] - [F48, 48] - [F64, 64] - [F80, 80] - [F96, 96] - [F128, 128] - [F160, 160] - [F192, 192] - [F200, 200] - [F256, 256] - [F320, 320] - [F400, 400] - [F512, 512] - [F576, 576] - [F640, 640] - [F800, 800] - [F912, 912] - [F1024, 1024] - [F1280, 1280] - [F1600, 1600] - [F2000, 2000] - [F2560, 2560] - [F4000, 4000] - [F8000, 8000] regex: - '^[oOfF][1-9][0-9]*$' - '^[nN](32|48|64|80|96|128|160|200|256|320|400|512|640|800|1024|1280|8000)$' - '^[hH][rRnN]?(1|2|4|8|16|32|64|128|256|512|1024|2048|4096|8192)$' uppercase: true interpolation: category: postproc flatten: false type: enum values: - [linear] - [bilinear] - [nearest-lsm, nlsm, nearest lsm] - [nearest-neighbour, nn, nearest neighbour] - [grid-box-average, average] packing: category: postproc flatten: false type: enum values: - [so, second order] - [av] - [complex, co] - [simple, si] - [ccsds] resol: category: postproc flatten: false type: [enum, integer] values: - [av, archived value] - [auto, automatic resolution] - [N128, reduced gaussian 128] - [N200, reduced gaussian 200] rotation: category: postproc flatten: false multiple: true type: float intgrid: category: postproc flatten: false type: [enum, regex] values: - auto - none - source - N32 - N48 - N64 - N80 - N96 - N128 - N160 - N192 - N200 - N256 - N320 - N400 - N512 - N640 - N800 - N912 - N1024 - N1280 regex: - '^[oOfFhH][1-9][0-9]+$' truncation: category: postproc flatten: false type: [enum, integer] values: - auto - none process: category: postproc flatten: false type: enum values: - local - server ####################################################################### _obspproc: &_obspproc filter: type: any category: postproc ident: type: any category: postproc ####################################################################### disseminate: <<: *_field <<: *_postproc requirements: type: any resol: category: postproc type: any # repres: # category: postproc # type: any day: category: postproc multiple: true type: to-by-list by: 1 range: [1, 31] use: category: sink flatten: false multiple: true type: enum values: # - [frequent, f] # - [infrequent, i] # - [normal, n] # - compare # - [monthly run] # - intent # - disk - [bc, boundary conditions] - [monday, mon] - [tuesday, tue] - [wednesday, wed] - [thursday, thu] - [friday, fri] - [saturday, sat] - [sunday, sun] option: category: sink default: normal flatten: false multiple: true type: enum values: - normal - delay - asap - gts - opendata compatibility: category: postproc flatten: false multiple: true type: enum values: - 'no-local-extension' - 'adjust-spectral-packing' priority: category: sink flatten: false type: integer target: category: sink flatten: false type: any filename: category: sink flatten: false type: any ############################################################## archive: <<: *_field <<: *_observation resol: category: postproc type: any # repres: # category: postproc # type: any database: flatten: false multiple: true type: any # TODO set source: category: sink flatten: false multiple: true type: any sourcebase: category: sink flatten: false multiple: true type: any expect: category: sink flatten: false multiple: false type: [enum, integer] values: - any disp: category: sink flatten: false multiple: false type: enum values: - [n, new] - [old] - [remove] - [fail] _clear_defaults: [step] ############################################################## retrieve: _aliases: [r, ret, retr] <<: *_field <<: *_observation <<: *_postproc <<: *_obspproc target: category: sink flatten: false multiple: true type: any source: category: sink flatten: false multiple: true type: any expect: category: sink flatten: false multiple: false type: [enum, integer] values: - any fieldset: category: sink aliases: [field] flatten: false multiple: false type: any database: category: sink flatten: false multiple: true aliases: [dbase] type: any optimise: category: sink type: enum values: - 'on' - 'off' defaults: - vals: ["off"] duplicates: category: sink defaults: - context: _verb: [retrieve] type: [ob] vals: [keep] type: enum values: - keep - remove padding: category: sink flatten: false type: enum defaults: - context: _verb: [retrieve] class: [ti] vals: [0] values: - [0, none, n] - [auto, automatic] ############################################################## read: source: category: sink flatten: false multiple: true type: any <<: *_field <<: *_observation <<: *_postproc <<: *_obspproc target: category: sink flatten: false multiple: true type: any fieldset: category: sink aliases: [field] flatten: false multiple: false type: any _clear_defaults: [class, date, domain, expver, levelist, levtype, param, step, stream, time, type] _options: param: # expand_with: # In case not type/stream/levtype is provided # type: an # stream: oper # levtype: pl first_rule: true ############################################################## flush: <<: *_field <<: *_observation <<: *_postproc <<: *_obspproc wait: flatten: false multiple: false type: enum values: - [true, yes, y, on, 1] - [false, no, n, off, 0] decache: flatten: false multiple: false type: enum values: - [true, yes, y, on, 1] - [false, no, n, off, 0] _clear_defaults: [class, date, domain, expver, levelist, levtype, param, step, stream, time, type] ############################################################## get: tape: category: sink flatten: false multiple: false type: any database: category: sink flatten: false multiple: true type: any target: category: sink flatten: false multiple: true type: any ############################################################## list: <<: *_field <<: *_observation database: category: sink flatten: false multiple: true type: any target: category: sink flatten: false multiple: true type: any pseudodate: flatten: false multiple: false type: enum values: - [latest] - [earliest] output: category: sink flatten: false multiple: false defaults: - vals: [table] type: enum values: - [table] - [browse] - [cost] - [search] - [tree] - [json] _clear_defaults: [date, domain, expver, levelist, levtype, param, step, stream, time, type] _multiple: [class, expver, stream, type] ############################################################## compute: formula: flatten: false multiple: false type: any fieldset: category: sink aliases: [field] flatten: false multiple: false type: any ############################################################## write: fieldset: category: sink aliases: [field] flatten: false multiple: false type: any target: category: sink flatten: false multiple: true type: any ############################################################## pointdb: lat: multiple: false type: float lon: multiple: false type: float <<: *_field _clear_defaults: [class, date, domain, expver, levelist, levtype, param, step, stream, time, type] _options: param: # expand_with: # In case not type/stream/levtype is provided # type: an # stream: oper # levtype: pl first_rule: true end: {} metkit-1.20.2/share/metkit/check-params-yaml.py0000775000175000017500000000106715246725757021557 0ustar alastairalastair#!/usr/bin/env python import MySQLdb import yaml import re import os db = MySQLdb.connect( "grib-param-db-prod.ecmwf.int", os.environ['PARAM_DB_USER'], os.environ['PARAM_DB_PASSWORD'], "param") cursor = db.cursor() with open("params.yaml") as f: PARAMS = yaml.load(f.read()) for entry in PARAMS: when, parms = entry for p in parms: cursor.execute("select count(*) from param where id=%s" % p) for data in cursor.fetchall(): if data[0] == 0: print("%s: %s is not in param DB" % (when, p)) metkit-1.20.2/share/metkit/modifiers.yaml0000664000175000017500000001530015246725757020544 0ustar alastairalastair--- - context: _verb: [list] output: ['!', browser] class: 'undefined' set: class: [od] # type = ob is already the default && stream comes after type in axis order # - context: # _verb: [retrieve] # stream: [ssmi] # set: # type: [ob] - context: _verb: [list] output: ['!', browser] class: ['!', rd'] defaults: type: [an] stream: [oper] expver: [0001] - context: _verb: [list] output: ['!', browser] class: ['!', rd] pseudodate: 'undefined' defaults: date: [-1] - context: _verb: [list] output: ['!', browser] class: [rd] expver: 'undefined' error: "Missing expver. Please, specify one" - context: class: [ti] defaults: stream: [enfo] - context: class: ['!', ti] defaults: stream: [oper] - context: _verb: [retrieve] stream: [ssbt, e1, ssmi] set: stream: [oper] warn: "Changed stream=oper" - context: class: [ti] type: [fc] set: stream: [oper] - context: class: [ti, s2, ur, rr, lw] expver: 'undefined' set: expver: [prod] - context: class: [ml, ur, rr] unset: [model] - context: _verb: [retrieve] class: [ai] defaults: model: [aifs-single-mse] - context: class: ['!', d1] dataset: ['!', on-demand-extremes-dt] unset: [georef] - context: type: ['!', oldim, ob, fb, ai, af, ab, tf, ofb, mfb, oai, sfb, fsoifb, fcdfb, ofa] unset: [obsgroup, obstype, duplicates] - context: stream: ['!', ssmi] type: [im, sim, oldim] unset: [levtype, levelist, param, grid, rotation, duplicates] - context: type: [ssd, mpp, gsd, ob, fb, ai, af, ab, ofb, mfb, oai, sfb, fsoifb, fcdfb, ofa] unset: [levtype] - context: stream: [ssmi] unset: [levtype, levelist, param, grid, rotation] - context: levtype: [ml] defaults: levelist: [1] - context: levtype: [pl] defaults: levelist: [1000, 850, 700, 500, 400, 300] - context: levtype: [fl] defaults: levelist: [100, 140, 180, 240, 300, 340, 390, 450] - context: stream: ['!', xwda] unset: [obscutoff] - context: type: [ssd, mpp, gsd, ob, fb, ai, af, ab, ofb, mfb, oai, sfb, fsoifb, fcdfb, tf, ofa] unset: [levelist] - context: levtype: [o2d, sfc, wave, al] unset: [levelist] - context: class: [er] levtype: [o3d] unset: [levelist] - context: section: [m, z] product: [inst, tacc, tavg] levtype: [dp] unset: [levelist] - context: section: [v] product: [tims] levtype: [dp] unset: [levelist] - context: type: [ob, fb, ai, af, ab, ofb, mfb, oai, sfb, fsoifb, fcdfb, tf, ofa] unset: [param] - context: stream: [mfhm] type: [em, es, ses] unset: [date] - context: stream: [mnfh] type: [em, es, ses, ed] unset: [date] - context: stream: [clmn] unset: [date] - context: stream: ['!', enfh, enwh, efho, ehmm, ewho, eefh, weeh, efrf, wfrf, efsr, wfsr, wfcl, efcl] unset: [hdate] - context: class: [dm] type: [fc] set: time: [0] - context: type: [ab] unset: [time] - context: stream: [clmn, dame, mdfa] unset: [time] - context: class: ['!', er] stream: [moda, msda, edmo, ewmo, wamd] unset: [time] - context: class: ['!', to, dt] unset: [leadtim, opttime] - context: stream: ['!', seap] unset: [leadtime, opttime] - context: stream: ['!', ssmi] type: [im, oldim] unset: [step] - context: stream: [ssmi, mnfm, mfhm, mfam, mfwm, mhwm, esmm, ehmm, mawm, dame, sfmm, swmm, smma, mmsa] unset: [step] - context: type: [gsd, ob, fb, ai, af, ab, ofb, mfb, oai, sfb, fsoifb, fcdfb, cl, ofa] unset: [step] - context: class: ['!', er] stream: [moda, msda, edmo, ewmo, wamd] unset: [step] - context: class: [ed,ng] unset: [step] - context: class: [d1] dataset: [climate-dt] unset: [step] - context: stream: ['!', lwda, lwwv, elda, ewla, wfas, wfcl, wfse, wfrf, wfsr, efas, efcl, efse, efrf, efsr] unset: [anoffset] - context: stream: [efas, efcl, efse, efrf, efsr, wfas, wfcl, wfse, wfrf, wfsr] type: ['!', go, sfo, fu] unset: [anoffset] - context: class: [ti, s2] type: [cf] defaults: number: [0] - context: type: ['!', pf, cm, cs, cr, cv, sv, as, fp, ed, tu, ci, me, ff, icp, sot, fcmean, fcmax, fcmin, fcstdev, svar, gwt, gbf, bf, wp] class: ['!', ti, lw, s2] stream: ['!', mofc, wamf, mofm, wmfm, seas, wasf, wamf, sfmm, smma, seap, swmm, ocea, mnfc, mnfh, mnfa, mnfw, mfhw, mfaw, mnfm, mfhm, mfam, mfwm, mhwm, mawm, mmsf, msmm, wams, mswm, mmsa, enda, ewda, edmm, edmo, ewmm, ewmo, elda, ewla, espd, mmaf, mmam, mmaw, mmwm, seas, efsr, wfsr, efse, wfse, ocda] unset: [number] - context: _verb: [retrieve, archive] class: ['!', ti, s2] type: [cf, em, es, ses, taem, taes] stream: ['!', mofc, wamf, mofm, wmfm] unset: [number] - context: type: ['!', pd, cd, pb, taem, pfc] unset: [quantile] - context: class: ['!', d1, ed, ng, ms] defaults: domain: [g] - context: class: [od] type: [cm, cs, cr] domain: [g] set: domain: [e] - context: class: [ep] levtype: [o2d, o3d] unset: [domain] - context: class: [d1, ng, ti, s2, ur, lw, rr, ed] unset: [domain] - context: type: ['!', im, sim, oldim, ob, fb, ai, af, ab, tf, ofb, mfb, ssd, oai, gsd, sfb, fsoifb, fcdfb, ofa] unset: [ident] - context: class: [ci, dt] unset: [method] - context: class: [ti, s2] defaults: origin: [ecmf] - context: class: ['!', od, en, me, c3, ci, rd] unset: [system] - context: accuracy: ['n'] unset: [accuracy] - context: accuracy: [l, r] set: accuracy: [8] - context: format: [p] unset: [format] - context: area: [e] set: area: [73.5,-27,33,45] - context: grid: [av] unset: [grid] - context: type: [ai, ob, fb, af, ab, ofb, mfb, oai, sfb, fsoifb, fcdfb, tf, ofa] unset: [grid, rotation] - context: style: [dissemination] set: resol: [av] warn: "Force resol=av when style=dissemination" - context: style: [dissemination] resol: [auto] set: resol: [av] warn: "Force resol=av when style=dissemination" - context: resol: [av] set: intgrid: [source] truncation: [none] - context: resol: [N128,N200] set: intgrid: _resol - context: resol: ['!',av,auto,N128,N200] set: truncation: _resol - context: _verb: [retrieve] class: [ti] defaults: process: [local] - context: _verb: [retrieve] source: 'defined' defaults: database: [file] - context: obsgroup: 'defined' defaults: duplicates: [keep] - context: obstype: 'defined' defaults: duplicates: [keep] - context: _verb: [retrieve] class: [ti] defaults: padding: [0] - context: class: [ms] country: [it] defaults: grib: [ecmwf] metkit-1.20.2/share/metkit/bufr-subtypes.yaml0000664000175000017500000000343515246725757021403 0ustar alastairalastair--- subtypes: - [1, 1] - [2, 1] - [3, 1] - [4, 1] - [7, 1] - [9, 1] - [11, 1] - [12, 1] - [13, 1] - [14, 1] - [19, 1] - [21, 1] - [22, 1] - [23, 1] - [26, 1] - [28, 1] - [31, 8] - [32, 8] - [49, 2] - [51, 2] - [53, 2] - [54, 2] - [55, 2] - [56, 2] - [57, 2] - [58, 3] - [59, 2] - [60, 2] - [61, 2] - [62, 2] - [63, 2] - [65, 2] - [71, 2] - [72, 2] - [73, 2] - [75, 2] - [82, 3] - [83, 3] - [84, 3] - [85, 3] - [86, 3] - [87, 3] - [88, 3] - [89, 3] - [91, 4] - [92, 4] - [95, 4] - [96, 4] - [97, 4] - [101, 5] - [102, 5] - [103, 5] - [104, 5] - [106, 5] - [109, 5] - [110, 1] - [111, 5] - [112, 5] - [113, 5] - [121, 12] - [122, 12] - [123, 12] - [124, 12] - [125, 6] - [126, 12] - [127, 12] - [129, 2] - [130, 2] - [131, 6] - [132, 6] - [133, 6] - [136, 12] - [137, 12] - [138, 12] - [139, 12] - [140, 1] - [142, 7] - [143, 7] - [144, 7] - [145, 7] - [146, 7] - [147, 1] - [148, 7] - [149, 7] - [150, 7] - [151, 7] - [153, 12] - [154, 2] - [155, 2] - [156, 2] - [159, 9] - [161, 2] - [162, 9] - [164, 10] - [165, 1] - [170, 1] - [172, 1] - [176, 1] - [178, 1] - [180, 1] - [181, 1] - [182, 1] - [184, 1] - [189, 3] - [190, 3] - [191, 3] - [201, 12] - [202, 12] - [203, 30] - [204, 30] - [206, 2] - [207, 2] - [208, 2] - [209, 2] - [210, 12] - [211, 2] - [212, 3] - [213, 12] - [214, 12] - [216, 2] - [217, 12] - [218, 12] - [220, 12] - [224, 12] - [226, 2] - [227, 2] - [228, 3] - [230, 5] - [231, 5] - [233, 12] - [235, 12] - [237, 1] - [240, 2] - [243, 5] - [244, 30] - [245, 12] - [246, 12] - [250, 2] - [251, 2] - [252, 21] - [255, 2] metkit-1.20.2/share/metkit/prodgen-params.yaml0000664000175000017500000000265615246725757021514 0ustar alastairalastair- - file: bin repres: np - - 129 - 130 - 131 - 132 - 133 - 135 - 151 - 152 - 156 - 157 - 164 - 166 - 167 - 228 - 928 - 3025 - - type: tf - - 129 # This is just because param expands to 129 - 999 - - stream: enfo type: cs - - 129 - 130 - 156 - - stream: enfo type: fp - - 131130 - - stream: efov - - 230008 - 230009 - 230020 - 230021 - 230022 - 230044 - 230045 - 230046 - 230047 - 230050 - 230057 - 230058 - 230080 - 230081 - 230082 - 230129 - 230130 - 230142 - 230143 - 230144 - 230145 - 230146 - 230147 - 230169 - 230174 - 230175 - 230176 - 230177 - 230178 - 230179 - 230180 - 230181 - 230182 - 230189 - 230195 - 230196 - 230197 - 230198 - 230205 - 230208 - 230209 - 230210 - 230211 - 230212 - 230213 - 230216 - 230228 - 230239 - 230240 - 230251 - - type: wp levtype: pl - - 129 - 131 - 132 - 157 - 130 - - type: wp levtype: sfc - - 121 - 122 - 123 - 129 - 151 - 134 - 139 - 141 - 142 - 143 - 144 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 183 - 186 - 187 - 188 - 189 - 228 - 236 - 39 - 40 - 41 - 42 - 59 metkit-1.20.2/share/metkit/params.yaml0000664000175000017500000033160515246725757020057 0ustar alastairalastair# File automatically generated by make-params-yaml.py # Do not edit - - class: ai levtype: pl stream: enfo type: cf - - 129 - 130 - 131 - 132 - 133 - 135 - 156 - - class: ai levtype: sfc stream: enfo type: cf - - 129 - 134 - 136 - 151 - 156 - 160 - 163 - 165 - 166 - 167 - 168 - 169 - 172 - 175 - 235 - 3073 - 3074 - 3075 - 228143 - 228144 - 228164 - 228228 - 228246 - 228247 - 231002 - 260289 - - class: ai levtype: sol stream: enfo type: cf - - 260199 - 260360 - - class: ai levtype: pl stream: enfo type: em - - 10 - 129 - 130 - 156 - - class: ai levtype: sfc stream: enfo type: em - - 151 - 167 - 207 - 228249 - - class: ai levtype: sfc stream: enfo type: ep - - 131060 - 131061 - 131062 - 131063 - 131064 - 131065 - 131066 - 131067 - 131068 - 131069 - 131073 - 131085 - 131098 - 131099 - - class: ai levtype: pl stream: enfo type: es - - 10 - 129 - 130 - 156 - - class: ai levtype: sfc stream: enfo type: es - - 151 - 167 - 207 - 228249 - - class: ai levtype: pl stream: enfo type: pf - - 129 - 130 - 131 - 132 - 133 - 135 - 156 - - class: ai levtype: sfc stream: enfo type: pf - - 129 - 134 - 136 - 151 - 156 - 160 - 163 - 165 - 166 - 167 - 168 - 169 - 172 - 175 - 235 - 3073 - 3074 - 3075 - 228143 - 228144 - 228164 - 228228 - 228246 - 228247 - 231002 - 260289 - - class: ai levtype: sol stream: enfo type: pf - - 260199 - 260360 - - class: ai levtype: pl stream: oper type: fc - - 129 - 130 - 131 - 132 - 133 - 135 - 156 - - class: ai levtype: sfc stream: oper type: fc - - 129 - 134 - 136 - 151 - 156 - 160 - 163 - 165 - 166 - 167 - 168 - 169 - 172 - 175 - 235 - 3073 - 3074 - 3075 - 228143 - 228144 - 228164 - 228228 - 228246 - 228247 - 231002 - 260289 - - class: ai levtype: sol stream: oper type: fc - - 260199 - 260360 - - class: ai levtype: sfc stream: waef type: cf - - 3100 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140219 - 140230 - 140232 - 140233 - - class: ai levtype: sfc stream: waef type: ep - - 131074 - 131075 - 131076 - 131077 - 131078 - 131079 - 131080 - 131081 - - class: ai levtype: sfc stream: waef type: pf - - 3100 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140219 - 140230 - 140232 - 140233 - - class: ai levtype: sfc stream: wave type: fc - - 3100 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140219 - 140230 - 140232 - 140233 - - class: mc levtype: ml stream: oper type: an - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 246 - 247 - 248 - 210001 - 210002 - 210003 - 210004 - 210005 - 210006 - 210007 - 210008 - 210009 - 210010 - 210011 - 210121 - 210122 - 210123 - 210124 - 210170 - 210181 - 210203 - 210247 - 210248 - 210249 - 210252 - 210253 - 217003 - 217004 - 217006 - 217007 - 217010 - 217011 - 217012 - 217013 - 217014 - 217015 - 217016 - 217018 - 217019 - 217021 - 217022 - 217023 - 217024 - 217026 - 217027 - 217028 - 217029 - 217030 - 217032 - 217033 - 217034 - 217035 - 217036 - 217040 - 217042 - 217043 - 217044 - 217045 - 217046 - 217047 - 217048 - 217049 - 217050 - 217051 - 217052 - 217053 - 217059 - 217063 - 217064 - 217065 - 217066 - 217067 - 217068 - 217069 - 217070 - 217071 - 217072 - 217073 - 217074 - 217075 - 217076 - 217077 - 217078 - 217079 - 217080 - 217082 - 217083 - 217107 - 217174 - 217175 - 217176 - 217191 - 217192 - 217193 - 217194 - 217195 - 217197 - 217198 - 217200 - 217202 - 217224 - 217226 - - class: mc levtype: pl stream: oper type: an - - 60 - 129 - 130 - 133 - 135 - 156 - 157 - 210001 - 210002 - 210003 - 210004 - 210005 - 210006 - 210007 - 210008 - 210009 - 210010 - 210011 - 210121 - 210122 - 210123 - 210124 - 210170 - 210181 - 210203 - 210247 - 210248 - 210249 - 210252 - 210253 - 217003 - 217004 - 217006 - 217007 - 217010 - 217011 - 217012 - 217013 - 217014 - 217015 - 217016 - 217018 - 217019 - 217021 - 217022 - 217023 - 217024 - 217026 - 217027 - 217028 - 217029 - 217030 - 217032 - 217033 - 217034 - 217035 - 217036 - 217040 - 217042 - 217043 - 217044 - 217045 - 217046 - 217047 - 217048 - 217049 - 217050 - 217051 - 217052 - 217053 - 217059 - 217063 - 217064 - 217065 - 217066 - 217067 - 217068 - 217069 - 217070 - 217071 - 217072 - 217073 - 217074 - 217075 - 217076 - 217077 - 217078 - 217079 - 217080 - 217082 - 217083 - 217107 - 217174 - 217175 - 217176 - 217191 - 217192 - 217193 - 217194 - 217195 - 217197 - 217198 - 217200 - 217202 - 217224 - 217226 - - class: mc levtype: sfc stream: oper type: an - - 26 - 31 - 32 - 34 - 66 - 67 - 78 - 79 - 129 - 134 - 136 - 137 - 141 - 151 - 156 - 164 - 165 - 166 - 167 - 168 - 172 - 186 - 187 - 188 - 198 - 235 - 210072 - 210073 - 210074 - 210125 - 210126 - 210127 - 210128 - 210183 - 210206 - 210207 - 210208 - 210209 - 210210 - 210211 - 210212 - 210213 - 210214 - 210215 - 210216 - 210217 - 210218 - 210219 - 210220 - 210221 - 210222 - 210223 - 210224 - 210225 - 210226 - 210227 - 210228 - 210229 - 210230 - 210250 - 210251 - 214002 - 214003 - 215019 - 215020 - 215021 - 215022 - 215023 - 215024 - 215043 - 215044 - 215045 - 215046 - 215047 - 215048 - 215061 - 215062 - 215063 - 215064 - 215077 - 215078 - 215079 - 215080 - 215087 - 215096 - 215097 - 215098 - 215099 - 215100 - 215101 - 215102 - 215103 - 215104 - 215105 - 215106 - 215107 - 215108 - 215109 - 215110 - 215111 - 215112 - 215113 - 215114 - 215115 - 215116 - 215117 - 215118 - 215119 - 215120 - 215121 - 215122 - 215123 - 215124 - 215125 - 215126 - 215127 - 215128 - 215129 - 215130 - 215131 - 215132 - 215133 - 215134 - 215135 - 215136 - 215137 - 215138 - 215139 - 215140 - 215141 - 215142 - 215143 - 215144 - 215145 - 215146 - 215147 - 215148 - 215149 - 215150 - 215151 - 215152 - 215153 - 215154 - 215155 - 215156 - 215157 - 215158 - 215159 - 215160 - 215161 - 215162 - 215163 - 215164 - 215165 - 215166 - 215167 - 215176 - 215177 - 215178 - 215179 - 215201 - 215202 - 215203 - 215204 - 215211 - 215226 - 218003 - 218004 - 218006 - 218007 - 218009 - 218010 - 218011 - 218012 - 218013 - 218014 - 218015 - 218016 - 218018 - 218019 - 218021 - 218022 - 218023 - 218024 - 218026 - 218027 - 218028 - 218029 - 218030 - 218032 - 218033 - 218034 - 218035 - 218036 - 218038 - 218039 - 218040 - 218041 - 218042 - 218043 - 218044 - 218045 - 218046 - 218047 - 218048 - 218049 - 218050 - 218051 - 218052 - 218053 - 218054 - 218055 - 218056 - 218059 - 218063 - 218064 - 218065 - 218066 - 218067 - 218068 - 218069 - 218070 - 218071 - 218072 - 218073 - 218074 - 218075 - 218076 - 218077 - 218078 - 218079 - 218080 - 218082 - 218083 - 218107 - 218174 - 218175 - 218176 - 218191 - 218192 - 218193 - 218194 - 218195 - 218197 - 218198 - 218200 - 218202 - 218221 - 218224 - 218226 - 228089 - 228090 - - class: mc levtype: ml stream: oper type: fc - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 246 - 247 - 248 - 210001 - 210002 - 210003 - 210004 - 210005 - 210006 - 210007 - 210008 - 210009 - 210010 - 210011 - 210121 - 210122 - 210123 - 210124 - 210170 - 210181 - 210203 - 210247 - 210248 - 210249 - 210252 - 210253 - 215180 - 215181 - 215182 - 215183 - 215184 - 215185 - 215186 - 215187 - 215188 - 217003 - 217004 - 217006 - 217007 - 217010 - 217011 - 217012 - 217013 - 217014 - 217015 - 217016 - 217018 - 217019 - 217021 - 217022 - 217023 - 217024 - 217026 - 217027 - 217028 - 217029 - 217030 - 217032 - 217033 - 217034 - 217035 - 217036 - 217040 - 217042 - 217043 - 217044 - 217045 - 217046 - 217047 - 217048 - 217049 - 217050 - 217051 - 217052 - 217053 - 217059 - 217063 - 217064 - 217065 - 217066 - 217067 - 217068 - 217069 - 217070 - 217071 - 217072 - 217073 - 217074 - 217075 - 217076 - 217077 - 217078 - 217079 - 217080 - 217082 - 217083 - 217107 - 217174 - 217175 - 217176 - 217191 - 217192 - 217193 - 217194 - 217195 - 217197 - 217198 - 217200 - 217202 - 217224 - 217226 - - class: mc levtype: pl stream: oper type: fc - - 60 - 129 - 130 - 133 - 135 - 156 - 246 - 247 - 248 - 210001 - 210002 - 210003 - 210004 - 210005 - 210006 - 210007 - 210008 - 210009 - 210010 - 210011 - 210121 - 210122 - 210123 - 210124 - 210170 - 210181 - 210203 - 210247 - 210248 - 210249 - 210252 - 210253 - 215180 - 215181 - 215182 - 215183 - 215184 - 215185 - 215186 - 215187 - 215188 - 217003 - 217004 - 217006 - 217007 - 217010 - 217011 - 217012 - 217013 - 217014 - 217015 - 217016 - 217018 - 217019 - 217021 - 217022 - 217023 - 217024 - 217026 - 217027 - 217028 - 217029 - 217030 - 217032 - 217033 - 217034 - 217035 - 217036 - 217040 - 217042 - 217043 - 217044 - 217045 - 217046 - 217047 - 217048 - 217049 - 217050 - 217051 - 217052 - 217053 - 217059 - 217063 - 217064 - 217065 - 217066 - 217067 - 217068 - 217069 - 217070 - 217071 - 217072 - 217073 - 217074 - 217075 - 217076 - 217077 - 217078 - 217079 - 217080 - 217082 - 217083 - 217107 - 217174 - 217175 - 217176 - 217191 - 217192 - 217193 - 217194 - 217195 - 217197 - 217198 - 217200 - 217202 - 217224 - 217226 - - class: mc levtype: sfc stream: oper type: fc - - 20 - 26 - 31 - 32 - 34 - 47 - 57 - 66 - 67 - 78 - 79 - 129 - 134 - 136 - 137 - 141 - 142 - 143 - 146 - 147 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 172 - 175 - 176 - 177 - 178 - 179 - 182 - 186 - 187 - 188 - 189 - 198 - 208 - 209 - 210 - 211 - 212 - 213 - 228 - 235 - 243 - 3020 - 210072 - 210073 - 210074 - 210125 - 210126 - 210127 - 210128 - 210183 - 210206 - 210207 - 210208 - 210209 - 210210 - 210211 - 210212 - 210213 - 210214 - 210215 - 210216 - 210217 - 210218 - 210219 - 210220 - 210221 - 210222 - 210223 - 210224 - 210225 - 210226 - 210227 - 210228 - 210229 - 210230 - 210250 - 210251 - 214002 - 214003 - 215001 - 215002 - 215003 - 215004 - 215005 - 215006 - 215007 - 215008 - 215009 - 215010 - 215011 - 215012 - 215013 - 215014 - 215015 - 215019 - 215020 - 215021 - 215022 - 215023 - 215024 - 215025 - 215026 - 215027 - 215028 - 215029 - 215030 - 215031 - 215032 - 215033 - 215034 - 215035 - 215036 - 215037 - 215038 - 215039 - 215043 - 215044 - 215045 - 215046 - 215047 - 215048 - 215049 - 215050 - 215051 - 215052 - 215053 - 215054 - 215055 - 215056 - 215057 - 215058 - 215061 - 215062 - 215063 - 215064 - 215065 - 215066 - 215067 - 215068 - 215069 - 215070 - 215071 - 215072 - 215073 - 215074 - 215077 - 215078 - 215079 - 215080 - 215081 - 215082 - 215083 - 215084 - 215085 - 215087 - 215096 - 215097 - 215098 - 215099 - 215100 - 215101 - 215102 - 215103 - 215104 - 215105 - 215106 - 215107 - 215108 - 215109 - 215110 - 215111 - 215112 - 215113 - 215114 - 215115 - 215116 - 215117 - 215118 - 215119 - 215120 - 215121 - 215122 - 215123 - 215124 - 215125 - 215126 - 215127 - 215128 - 215129 - 215130 - 215131 - 215132 - 215133 - 215134 - 215135 - 215136 - 215137 - 215138 - 215139 - 215140 - 215141 - 215142 - 215143 - 215144 - 215145 - 215146 - 215147 - 215148 - 215149 - 215150 - 215151 - 215152 - 215153 - 215154 - 215155 - 215156 - 215157 - 215158 - 215159 - 215160 - 215161 - 215162 - 215163 - 215164 - 215165 - 215166 - 215167 - 215176 - 215177 - 215178 - 215179 - 215189 - 215190 - 215191 - 215192 - 215193 - 215194 - 215195 - 215196 - 215197 - 215198 - 215201 - 215202 - 215203 - 215204 - 215205 - 215206 - 215207 - 215208 - 215209 - 215211 - 215226 - 218003 - 218004 - 218006 - 218007 - 218009 - 218010 - 218011 - 218012 - 218013 - 218014 - 218015 - 218016 - 218018 - 218019 - 218021 - 218022 - 218023 - 218024 - 218026 - 218027 - 218028 - 218029 - 218030 - 218032 - 218033 - 218034 - 218035 - 218036 - 218038 - 218039 - 218040 - 218041 - 218042 - 218043 - 218044 - 218045 - 218046 - 218047 - 218048 - 218049 - 218050 - 218051 - 218052 - 218053 - 218054 - 218055 - 218056 - 218059 - 218063 - 218064 - 218065 - 218066 - 218067 - 218068 - 218069 - 218070 - 218071 - 218072 - 218073 - 218074 - 218075 - 218076 - 218077 - 218078 - 218079 - 218080 - 218082 - 218083 - 218107 - 218174 - 218175 - 218176 - 218191 - 218192 - 218193 - 218194 - 218195 - 218197 - 218198 - 218200 - 218202 - 218221 - 218224 - 218226 - 222001 - 222006 - 222013 - 222015 - 222017 - 222019 - 222027 - 222031 - 222033 - 223006 - 223013 - 223015 - 223017 - 223019 - 223027 - 223031 - 223033 - 228003 - 228021 - 228022 - 228023 - 228028 - 228046 - 228088 - 228089 - 228090 - 228129 - 228130 - 228235 - 228236 - 228251 - 260015 - - class: od levtype: ml stream: eefh type: cf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: eefh type: cf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: eefh type: cf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: eefh type: cf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: eefh type: cf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: eefh type: cf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 74 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 234 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: eefh type: cf - - 33 - 238 - 228038 - 228141 - - class: od levtype: sfc stream: eefh type: fcmax - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 237117 - - class: od levtype: pl stream: eefh type: fcmean - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - - class: od levtype: pt stream: eefh type: fcmean - - 60 - - class: od levtype: pv stream: eefh type: fcmean - - 3 - - class: od levtype: sfc stream: eefh type: fcmean - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: sfc stream: eefh type: fcmin - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 228246 - 228247 - 238117 - - class: od levtype: sfc stream: eefh type: fcstdev - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 239117 - - class: od levtype: ml stream: eefh type: pf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: eefh type: pf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: eefh type: pf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: eefh type: pf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: eefh type: pf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: eefh type: pf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: eefh type: pf - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: eefo type: cf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: eefo type: cf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: eefo type: cf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: eefo type: cf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: eefo type: cf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: eefo type: cf - - 8 - 9 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 66 - 67 - 74 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 234 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228249 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: eefo type: cf - - 33 - 238 - 228038 - 228141 - - class: od levtype: pl stream: eefo type: ed - - 130 - - class: od levtype: sfc stream: eefo type: ed - - 164 - 167 - 207 - 228 - - class: od levtype: sfc stream: eefo type: efi - - 132167 - 132228 - - class: od levtype: pl stream: eefo type: em - - 10 - 129 - 130 - 156 - - class: od levtype: sfc stream: eefo type: em - - 121 - 122 - 151 - 167 - 207 - - class: od levtype: sfc stream: eefo type: ep - - 131001 - 131002 - 131003 - 131004 - 131005 - 131006 - 131007 - 131008 - 131009 - 131010 - - class: od levtype: pl stream: eefo type: es - - 10 - 129 - 130 - 156 - - class: od levtype: sfc stream: eefo type: es - - 121 - 122 - 151 - 167 - 207 - - class: od levtype: sfc stream: eefo type: fcmax - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228235 - 228246 - 228247 - 235117 - 237117 - - class: od levtype: pl stream: eefo type: fcmean - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - 171001 - 171002 - 171129 - 171130 - 171131 - 171132 - 171133 - 171156 - - class: od levtype: pt stream: eefo type: fcmean - - 60 - - class: od levtype: pv stream: eefo type: fcmean - - 3 - - class: od levtype: sfc stream: eefo type: fcmean - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 171006 - 171007 - 171033 - 171034 - 171078 - 171079 - 171121 - 171122 - 171136 - 171137 - 171139 - 171141 - 171151 - 171164 - 171165 - 171166 - 171167 - 171168 - 171201 - 171202 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 173144 - 173189 - 173228 - 228235 - 228246 - 228247 - 235117 - - class: od levtype: sfc stream: eefo type: fcmin - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228235 - 228246 - 228247 - 235117 - 238117 - - class: od levtype: sfc stream: eefo type: fcstdev - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228235 - 228246 - 228247 - 235117 - 239117 - - class: od levtype: pl stream: eefo type: pb - - 171129 - 171156 - - class: od levtype: sfc stream: eefo type: pb - - 171139 - 171151 - 171167 - 173228 - - class: od levtype: pl stream: eefo type: pd - - 131129 - - class: od levtype: sfc stream: eefo type: pd - - 131139 - 131151 - 131167 - 131228 - - class: od levtype: ml stream: eefo type: pf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: eefo type: pf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: eefo type: pf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: eefo type: pf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: eefo type: pf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: eefo type: pf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228249 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: eefo type: pf - - 33 - 238 - 228038 - 228141 - - class: od levtype: sfc stream: eefo type: sot - - 132167 - 132228 - - class: od levtype: pl stream: eefo type: taem - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - 171001 - 171002 - 171129 - 171130 - 171131 - 171132 - 171133 - 171156 - - class: od levtype: sfc stream: eefo type: taem - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 171006 - 171007 - 171033 - 171034 - 171078 - 171079 - 171121 - 171122 - 171136 - 171137 - 171139 - 171141 - 171151 - 171164 - 171165 - 171166 - 171167 - 171168 - 171201 - 171202 - 172142 - 172143 - 172144 - 172189 - 172228 - 173144 - 173189 - 173228 - 228235 - 228246 - 228247 - 234139 - 234151 - 234167 - 234228 - 235117 - - class: od levtype: pl stream: eefo type: taes - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - - class: od levtype: sfc stream: eefo type: taes - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 172142 - 172143 - 172144 - 172189 - 172228 - 228235 - 228246 - 228247 - 235117 - - class: od levtype: sfc stream: eehs type: cd - - 228 - 228004 - - class: od levtype: pl stream: eehs type: em - - 129 - 130 - 156 - - class: od levtype: sfc stream: eehs type: em - - 228 - 228004 - - class: od levtype: pl stream: eehs type: es - - 129 - 130 - 156 - - class: od levtype: sfc stream: eehs type: es - - 228 - 228004 - - class: od levtype: pl stream: eehs type: taem - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - - class: od levtype: sfc stream: eehs type: taem - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 172142 - 172143 - 172144 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: pl stream: eehs type: taes - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - - class: od levtype: sfc stream: eehs type: taes - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 172142 - 172143 - 172144 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: sfc stream: efhs type: cd - - 49 - 59 - 144 - 201 - 202 - 228 - 162045 - 228004 - 228005 - 228006 - 228044 - - class: od levtype: pl stream: efhs type: em - - 129 - 130 - 156 - - class: od levtype: sfc stream: efhs type: em - - 49 - 59 - 144 - 201 - 202 - 228 - 162045 - 228004 - 228005 - 228006 - 228044 - - class: od levtype: pl stream: efhs type: es - - 129 - 130 - 156 - - class: od levtype: sfc stream: efhs type: es - - 49 - 59 - 144 - 201 - 202 - 228 - 162045 - 228004 - 228005 - 228006 - 228044 - - class: od levtype: pl stream: efhs type: taem - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - - class: od levtype: sfc stream: efhs type: taem - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 172142 - 172143 - 172144 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: pl stream: efhs type: taes - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 156 - - class: od levtype: sfc stream: efhs type: taes - - 33 - 34 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 201 - 202 - 172142 - 172143 - 172144 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: ml stream: elda type: 4i - - 200130 - 200131 - 200132 - 200133 - 200138 - 200152 - 200155 - 200203 - - class: od levtype: ml stream: elda type: 4v - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: elda type: 4v - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: elda type: 4v - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: elda type: 4v - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: elda type: 4v - - 8 - 9 - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 57 - 58 - 59 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 183 - 186 - 187 - 188 - 195 - 196 - 197 - 198 - 205 - 206 - 228 - 229 - 230 - 234 - 235 - 236 - 238 - 244 - 245 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228131 - 228132 - 228239 - 228240 - 228246 - 228247 - - class: od levtype: al stream: elda type: an - - 213101 - 213102 - 213103 - 213104 - 213105 - 213106 - 213107 - 213108 - 213109 - 213110 - 213111 - 213112 - 213113 - 213114 - 213115 - 213116 - 213117 - 213118 - 213119 - 213120 - 213121 - 213122 - 213123 - 213124 - 213125 - 213126 - 213127 - 213128 - 213129 - 213130 - 213131 - 213132 - 213133 - 213134 - 213135 - 213136 - 213137 - 213138 - 213139 - 213140 - 213141 - 213142 - 213143 - 213144 - 213145 - 213146 - 213147 - 213148 - 213149 - 213150 - 213151 - 213152 - 213153 - 213154 - 213155 - 213156 - 213157 - 213158 - 213159 - 213160 - - class: od levtype: ml stream: elda type: an - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: elda type: an - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: elda type: an - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: elda type: an - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: elda type: an - - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 170 - 172 - 173 - 174 - 183 - 186 - 187 - 188 - 198 - 206 - 229 - 230 - 234 - 235 - 236 - 238 - 129172 - 151131 - 151132 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228239 - 228240 - 228246 - 228247 - 229001 - 260289 - - class: od levtype: sol stream: elda type: an - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: elda type: em - - 21 - 22 - 23 - 130 - 131 - 132 - 133 - 138 - 152 - 156 - 157 - 203 - 246 - 247 - 228055 - 228056 - - class: od levtype: sfc stream: elda type: em - - 194 - - class: od levtype: ml stream: elda type: eme - - 200130 - 200131 - 200132 - 200138 - 200152 - 200155 - - class: od levtype: ml stream: elda type: es - - 21 - 22 - 23 - 130 - 131 - 132 - 133 - 138 - 152 - 156 - 157 - 203 - 246 - 247 - 228055 - 228056 - - class: od levtype: sfc stream: elda type: es - - 39 - 40 - 41 - 42 - 139 - 140 - 170 - 171 - 183 - 184 - 194 - 235 - 236 - 237 - - class: od levtype: al stream: elda type: fc - - 213101 - 213102 - 213103 - 213104 - 213105 - 213106 - 213107 - 213108 - 213109 - 213110 - 213111 - 213112 - 213113 - 213114 - 213115 - 213116 - 213117 - 213118 - 213119 - 213120 - 213121 - 213122 - 213123 - 213124 - 213125 - 213126 - 213127 - 213128 - 213129 - 213130 - 213131 - 213132 - 213133 - 213134 - 213135 - 213136 - 213137 - 213138 - 213139 - 213140 - 213141 - 213142 - 213143 - 213144 - 213145 - 213146 - 213147 - 213148 - 213149 - 213150 - 213151 - 213152 - 213153 - 213154 - 213155 - 213156 - 213157 - 213158 - 213159 - 213160 - - class: od levtype: ml stream: elda type: fc - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: o2d stream: elda type: fc - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: o3d stream: elda type: fc - - 262500 - 262501 - 262502 - 262505 - 262506 - 262507 - - class: od levtype: pl stream: elda type: fc - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: elda type: fc - - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: elda type: fc - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: elda type: fc - - 8 - 9 - 20 - 26 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 44 - 45 - 47 - 49 - 50 - 57 - 58 - 66 - 67 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 208 - 209 - 210 - 211 - 212 - 213 - 228 - 229 - 230 - 231 - 232 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151131 - 151132 - 162071 - 162072 - 174096 - 228003 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228129 - 228130 - 228131 - 228132 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - 260318 - 260319 - 260320 - 260321 - 260338 - 260339 - - class: od levtype: sol stream: elda type: fc - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: elda type: me - - 200130 - 200131 - 200132 - 200138 - 200152 - 200155 - - class: od levtype: ml stream: elda type: ses - - 21 - 22 - 23 - 130 - 131 - 132 - 133 - 138 - 152 - 156 - 157 - 203 - 246 - 247 - 228055 - 228056 - - class: od levtype: sfc stream: elda type: ses - - 194 - - class: od levtype: ml stream: enfh type: cf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: enfh type: cf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: enfh type: cf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: enfh type: cf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: enfh type: cf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: enfh type: cf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 74 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 234 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: enfh type: cf - - 33 - 238 - 228038 - 228141 - - class: od levtype: sfc stream: enfh type: fcmax - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 237117 - - class: od levtype: pl stream: enfh type: fcmean - - 1 - 2 - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - - class: od levtype: pt stream: enfh type: fcmean - - 60 - - class: od levtype: pv stream: enfh type: fcmean - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: enfh type: fcmean - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 235117 - - class: od levtype: sfc stream: enfh type: fcmin - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 228246 - 228247 - 238117 - - class: od levtype: sfc stream: enfh type: fcstdev - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 78 - 79 - 121 - 122 - 136 - 137 - 139 - 141 - 151 - 159 - 164 - 165 - 166 - 167 - 168 - 170 - 201 - 202 - 207 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172228 - 228246 - 228247 - 239117 - - class: od levtype: ml stream: enfh type: icp - - 130 - 131 - 132 - 133 - 138 - 152 - 155 - - class: od levtype: ml stream: enfh type: pf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: enfh type: pf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: pl stream: enfh type: pf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: enfh type: pf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: enfh type: pf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: enfh type: pf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228235 - 228236 - 228239 - 228240 - 228241 - 228246 - 228247 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - - class: od levtype: sol stream: enfh type: pf - - 33 - 238 - 228038 - 228141 - - class: od levtype: pl stream: enfo type: cm - - 129 - 156 - - class: od levtype: pl stream: enfo type: cr - - 129 - 156 - - class: od levtype: sfc stream: enfo type: efi - - 132044 - 132045 - 132049 - 132059 - 132144 - 132165 - 132167 - 132201 - 132202 - 132228 - - class: od levtype: sfc stream: enfo type: efic - - 132044 - 132045 - 132049 - 132059 - 132144 - 132165 - 132167 - 132201 - 132202 - 132228 - - class: od levtype: pl stream: enfo type: em - - 10 - 129 - 130 - 156 - - class: od levtype: sfc stream: enfo type: em - - 121 - 122 - 151 - 167 - 207 - 228026 - 228027 - 228249 - - class: od levtype: pl stream: enfo type: ep - - 131020 - 131021 - 131022 - 131023 - 131024 - 131025 - 133093 - 133094 - 133095 - 133096 - 133097 - 133098 - - class: od levtype: sfc stream: enfo type: ep - - 131060 - 131061 - 131062 - 131063 - 131064 - 131065 - 131066 - 131067 - 131068 - 131069 - 131070 - 131071 - 131072 - 131073 - 131085 - 131089 - 131090 - 131091 - 131098 - 131099 - 131100 - - class: od levtype: pl stream: enfo type: es - - 10 - 129 - 130 - 156 - - class: od levtype: sfc stream: enfo type: es - - 121 - 122 - 151 - 167 - 207 - 228026 - 228027 - 228249 - - class: od levtype: ml stream: enfo type: icp - - 130 - 131 - 132 - 138 - 152 - 155 - - class: od levtype: fl stream: enfo type: pf - - 260290 - - class: od levtype: ml stream: enfo type: pf - - 75 - 76 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 260290 - - class: od levtype: o2d stream: enfo type: pf - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: o3d stream: enfo type: pf - - 262500 - 262501 - 262502 - 262505 - 262506 - 262507 - - class: od levtype: pl stream: enfo type: pf - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 260290 - - class: od levtype: pt stream: enfo type: pf - - 54 - 60 - 131 - 132 - 138 - 155 - 203 - - class: od levtype: pv stream: enfo type: pf - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: enfo type: pf - - 8 - 9 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 45 - 47 - 49 - 57 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 210 - 228 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174098 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228020 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228131 - 228132 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228231 - 228232 - 228233 - 228234 - 228235 - 228236 - 228237 - 228239 - 228240 - 228241 - 228246 - 228247 - 228249 - 228251 - 260004 - 260005 - 260015 - 260048 - 260109 - 260121 - 260123 - 260242 - 260255 - 260289 - 260318 - 260319 - 260320 - 260321 - 260338 - 260339 - 261001 - 261002 - 261014 - 261015 - 261016 - 261018 - 261023 - - class: od levtype: sol stream: enfo type: pf - - 33 - 238 - 228038 - 228141 - - class: od levtype: sfc stream: enfo type: sot - - 132044 - 132045 - 132049 - 132059 - 132144 - 132165 - 132167 - 132201 - 132202 - 132228 - - class: od stream: enfo type: tf - - 129 - 999 - - class: od levtype: pl stream: enfo type: wp - - 129 - 130 - 131 - 132 - 157 - - class: od levtype: sfc stream: enfo type: wp - - 39 - 40 - 41 - 42 - 121 - 122 - 123 - 129 - 134 - 139 - 141 - 142 - 143 - 144 - 151 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 183 - 186 - 187 - 188 - 189 - 228 - 236 - 228235 - - class: od levtype: sfc stream: enwh type: cf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: enwh type: fcmax - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: enwh type: fcmean - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: enwh type: fcmin - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: enwh type: fcstdev - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: enwh type: pf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: ewla type: 4v - - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: ewla type: an - - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: ewla type: fc - - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: ml stream: lwda type: 4i - - 200130 - 200131 - 200132 - 200133 - 200138 - 200152 - 200155 - 200203 - - class: od levtype: ml stream: lwda type: 4v - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: lwda type: 4v - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: lwda type: 4v - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: lwda type: 4v - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: lwda type: 4v - - 8 - 9 - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 57 - 58 - 59 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 183 - 186 - 187 - 188 - 195 - 196 - 197 - 198 - 205 - 206 - 228 - 229 - 230 - 234 - 235 - 236 - 238 - 244 - 245 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228131 - 228132 - 228239 - 228240 - 228246 - 228247 - - class: od levtype: ml stream: lwda type: an - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: lwda type: an - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: lwda type: an - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: lwda type: an - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: lwda type: an - - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 170 - 172 - 173 - 174 - 183 - 186 - 187 - 188 - 198 - 206 - 229 - 230 - 234 - 235 - 236 - 238 - 129172 - 151131 - 151132 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228239 - 228240 - 228246 - 228247 - 229001 - 260289 - - class: od levtype: sol stream: lwda type: an - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: lwda type: fc - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 162110 - 260290 - - class: od levtype: o2d stream: lwda type: fc - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: o3d stream: lwda type: fc - - 262500 - 262501 - 262502 - 262505 - 262506 - 262507 - - class: od levtype: pl stream: lwda type: fc - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: lwda type: fc - - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: lwda type: fc - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: lwda type: fc - - 8 - 9 - 20 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 47 - 49 - 50 - 57 - 58 - 66 - 67 - 74 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 208 - 209 - 210 - 211 - 212 - 213 - 228 - 229 - 230 - 231 - 232 - 234 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174096 - 174098 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228003 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228020 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228045 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228129 - 228130 - 228131 - 228132 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228231 - 228232 - 228233 - 228234 - 228235 - 228236 - 228237 - 228239 - 228240 - 228241 - 228246 - 228247 - 228249 - 228251 - 260015 - 260048 - 260109 - 260121 - 260123 - 260289 - 260318 - 260319 - 260320 - 260321 - 260338 - 260339 - - class: od levtype: sol stream: lwda type: fc - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: lwda type: me - - 200130 - 200131 - 200132 - 200138 - 200152 - 200155 - - class: od stream: lwda type: ssd - - 260510 - - class: od levtype: sfc stream: lwwv type: 4v - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: lwwv type: an - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: lwwv type: fc - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: pl stream: mmsa type: em - - 171129 - 171130 - 171131 - 171132 - 171133 - 171138 - 171155 - 171156 - 171203 - - class: od levtype: pt stream: mmsa type: em - - 171060 - - class: od levtype: pv stream: mmsa type: em - - 171003 - - class: od levtype: sfc stream: mmsa type: em - - 171024 - 171025 - 171031 - 171033 - 171034 - 171039 - 171040 - 171041 - 171042 - 171049 - 171051 - 171052 - 171078 - 171079 - 171137 - 171139 - 171141 - 171151 - 171164 - 171165 - 171166 - 171167 - 171168 - 171170 - 171183 - 171186 - 171206 - 171207 - 171229 - 171230 - 171236 - 171243 - 173008 - 173009 - 173142 - 173143 - 173144 - 173146 - 173147 - 173169 - 173175 - 173176 - 173177 - 173178 - 173179 - 173180 - 173181 - 173182 - 173189 - 173205 - 173212 - 173228 - - class: od levtype: pl stream: mmsa type: fcmean - - 171129 - 171130 - 171131 - 171132 - 171133 - 171138 - 171155 - 171156 - 171203 - - class: od levtype: pt stream: mmsa type: fcmean - - 171060 - - class: od levtype: pv stream: mmsa type: fcmean - - 171003 - - class: od levtype: sfc stream: mmsa type: fcmean - - 171024 - 171025 - 171031 - 171033 - 171034 - 171039 - 171040 - 171041 - 171042 - 171049 - 171051 - 171052 - 171078 - 171079 - 171137 - 171139 - 171141 - 171151 - 171164 - 171165 - 171166 - 171167 - 171168 - 171170 - 171183 - 171186 - 171206 - 171207 - 171229 - 171230 - 171236 - 171243 - 173008 - 173009 - 173142 - 173143 - 173144 - 173146 - 173147 - 173169 - 173175 - 173176 - 173177 - 173178 - 173179 - 173180 - 173181 - 173182 - 173189 - 173205 - 173212 - 173228 - - class: od levtype: ml stream: mmsf type: fc - - 129 - 130 - 131 - 132 - 133 - 138 - 152 - 155 - 156 - 246 - 247 - - class: od levtype: pl stream: mmsf type: fc - - 129 - 130 - 131 - 132 - 133 - 138 - 152 - 155 - 156 - 203 - - class: od levtype: pt stream: mmsf type: fc - - 60 - - class: od levtype: pv stream: mmsf type: fc - - 3 - - class: od levtype: sfc stream: mmsf type: fc - - 8 - 9 - 26 - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 55 - 78 - 79 - 129 - 137 - 139 - 141 - 142 - 143 - 144 - 146 - 147 - 151 - 156 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 189 - 201 - 202 - 205 - 206 - 212 - 228 - 229 - 230 - 236 - 243 - 228007 - 228008 - 228014 - - class: od levtype: ml stream: mmsf type: icp - - 130 - 131 - 132 - 133 - 138 - 152 - 155 - - class: od levtype: pl stream: msmm type: em - - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - 203 - - class: od levtype: pt stream: msmm type: em - - 60 - - class: od levtype: pv stream: msmm type: em - - 3 - - class: od levtype: sfc stream: msmm type: em - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 172228 - 228008 - 228014 - - class: od levtype: sfc stream: msmm type: fcmax - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 172228 - 228008 - 228014 - - class: od levtype: pl stream: msmm type: fcmean - - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - 203 - - class: od levtype: pt stream: msmm type: fcmean - - 60 - - class: od levtype: pv stream: msmm type: fcmean - - 3 - - class: od levtype: sfc stream: msmm type: fcmean - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 172228 - 228008 - 228014 - - class: od levtype: sfc stream: msmm type: fcmin - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 228008 - 228014 - - class: od levtype: sfc stream: msmm type: fcstdev - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 172228 - 228008 - 228014 - - class: od levtype: pl stream: msmm type: hcmean - - 129 - 130 - 131 - 132 - 133 - 138 - 155 - 156 - 203 - - class: od levtype: pt stream: msmm type: hcmean - - 60 - - class: od levtype: pv stream: msmm type: hcmean - - 3 - - class: od levtype: sfc stream: msmm type: hcmean - - 31 - 33 - 34 - 39 - 40 - 41 - 42 - 49 - 51 - 52 - 78 - 79 - 137 - 139 - 141 - 151 - 164 - 165 - 166 - 167 - 168 - 170 - 183 - 186 - 206 - 207 - 229 - 230 - 236 - 243 - 172008 - 172009 - 172142 - 172143 - 172144 - 172146 - 172147 - 172169 - 172175 - 172176 - 172177 - 172178 - 172179 - 172180 - 172181 - 172182 - 172189 - 172205 - 172212 - 172228 - 228008 - 228014 - - class: od levtype: ml stream: oper type: 4i - - 200130 - 200131 - 200132 - 200133 - 200138 - 200152 - 200155 - 200203 - - class: od levtype: ml stream: oper type: 4v - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: oper type: 4v - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: oper type: 4v - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: oper type: 4v - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: oper type: 4v - - 8 - 9 - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 57 - 58 - 59 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 183 - 186 - 187 - 188 - 195 - 196 - 197 - 198 - 205 - 206 - 228 - 229 - 230 - 234 - 235 - 236 - 238 - 244 - 245 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228131 - 228132 - 228239 - 228240 - 228246 - 228247 - - class: od levtype: ml stream: oper type: an - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - - class: od levtype: pl stream: oper type: an - - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: oper type: an - - 53 - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: oper type: an - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: oper type: an - - 15 - 16 - 17 - 18 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 66 - 67 - 74 - 78 - 79 - 129 - 134 - 136 - 137 - 139 - 141 - 148 - 151 - 156 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 170 - 172 - 173 - 174 - 183 - 186 - 187 - 188 - 198 - 206 - 229 - 230 - 234 - 235 - 236 - 238 - 129172 - 151131 - 151132 - 174096 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228021 - 228022 - 228089 - 228090 - 228239 - 228240 - 228246 - 228247 - 229001 - 260289 - - class: od levtype: sol stream: oper type: an - - 33 - 238 - 228038 - 228141 - - class: od levtype: fl stream: oper type: fc - - 260290 - - class: od levtype: ml stream: oper type: fc - - 75 - 76 - 77 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 203 - 246 - 247 - 248 - 162110 - 260290 - - class: od levtype: o2d stream: oper type: fc - - 262000 - 262001 - 262002 - 262003 - 262004 - 262005 - 262006 - 262008 - 262009 - 262011 - 262014 - 262015 - 262017 - 262018 - 262023 - 262100 - 262101 - 262102 - 262103 - 262104 - 262105 - 262106 - 262108 - 262113 - 262114 - 262115 - 262116 - 262118 - 262119 - 262120 - 262121 - 262122 - 262123 - 262124 - 262125 - 262130 - 262139 - 262140 - 262141 - 262143 - - class: od levtype: o3d stream: oper type: fc - - 262500 - 262501 - 262502 - 262505 - 262506 - 262507 - - class: od levtype: pl stream: oper type: fc - - 1 - 2 - 60 - 129 - 130 - 131 - 132 - 133 - 135 - 138 - 152 - 155 - 156 - 157 - 203 - 246 - 247 - 248 - - class: od levtype: pt stream: oper type: fc - - 54 - 60 - 131 - 132 - 133 - 138 - 155 - 203 - - class: od levtype: pv stream: oper type: fc - - 3 - 54 - 129 - 131 - 132 - 133 - 156 - 203 - - class: od levtype: sfc stream: oper type: fc - - 8 - 9 - 20 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 47 - 49 - 50 - 57 - 58 - 66 - 67 - 74 - 78 - 79 - 121 - 122 - 123 - 129 - 134 - 136 - 137 - 139 - 141 - 142 - 143 - 144 - 145 - 146 - 147 - 148 - 151 - 156 - 159 - 160 - 161 - 162 - 163 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 173 - 174 - 175 - 176 - 177 - 178 - 179 - 180 - 181 - 182 - 183 - 186 - 187 - 188 - 189 - 195 - 196 - 197 - 198 - 201 - 202 - 205 - 206 - 207 - 208 - 209 - 210 - 211 - 212 - 213 - 228 - 229 - 230 - 231 - 232 - 234 - 235 - 236 - 238 - 243 - 244 - 245 - 3020 - 151130 - 151131 - 151132 - 151145 - 151148 - 151163 - 151164 - 151175 - 162071 - 162072 - 174096 - 174098 - 210186 - 210187 - 210188 - 210189 - 210190 - 210191 - 228003 - 228007 - 228008 - 228009 - 228010 - 228011 - 228012 - 228013 - 228014 - 228020 - 228021 - 228022 - 228023 - 228024 - 228026 - 228027 - 228028 - 228029 - 228035 - 228036 - 228044 - 228045 - 228046 - 228047 - 228048 - 228050 - 228051 - 228057 - 228058 - 228080 - 228081 - 228082 - 228083 - 228084 - 228085 - 228088 - 228089 - 228090 - 228129 - 228130 - 228131 - 228132 - 228216 - 228217 - 228218 - 228219 - 228220 - 228221 - 228222 - 228223 - 228224 - 228225 - 228226 - 228227 - 228231 - 228232 - 228233 - 228234 - 228235 - 228236 - 228237 - 228239 - 228240 - 228241 - 228246 - 228247 - 228249 - 228251 - 260004 - 260005 - 260015 - 260048 - 260109 - 260121 - 260123 - 260242 - 260255 - 260289 - 260318 - 260319 - 260320 - 260321 - 260338 - 260339 - 261001 - 261002 - 261014 - 261015 - 261016 - 261018 - 261023 - - class: od levtype: sol stream: oper type: fc - - 33 - 238 - 228038 - 228141 - - class: od levtype: ml stream: oper type: me - - 200130 - 200131 - 200132 - 200138 - 200152 - 200155 - - class: od stream: oper type: ssd - - 260510 - 260512 - - class: od stream: oper type: tf - - 129 - 999 - - class: od levtype: pl stream: oper type: wp - - 129 - 130 - 131 - 132 - 157 - - class: od levtype: sfc stream: oper type: wp - - 39 - 40 - 41 - 42 - 121 - 122 - 123 - 129 - 134 - 139 - 141 - 142 - 143 - 144 - 151 - 164 - 165 - 166 - 167 - 168 - 169 - 170 - 172 - 183 - 186 - 187 - 188 - 189 - 228 - 236 - 228235 - - class: od stream: scda type: tf - - 129 - 999 - - class: od levtype: sfc stream: swmm type: fcmax - - 140220 - 140221 - 140229 - 140231 - 140232 - 140233 - 140244 - 140245 - - class: od levtype: sfc stream: swmm type: fcmean - - 140220 - 140221 - 140229 - 140231 - 140232 - 140233 - 140244 - 140245 - - class: od levtype: sfc stream: swmm type: fcmin - - 140220 - 140221 - 140229 - 140231 - 140232 - 140233 - 140244 - 140245 - - class: od levtype: sfc stream: swmm type: fcstdev - - 140220 - 140221 - 140229 - 140231 - 140232 - 140233 - 140244 - 140245 - - class: od levtype: sfc stream: waef type: efi - - 132216 - - class: od levtype: sfc stream: waef type: efic - - 132216 - - class: od levtype: sfc stream: waef type: ep - - 131074 - 131075 - 131076 - 131077 - 131078 - 131079 - 131080 - 131081 - - class: od levtype: sfc stream: waef type: pf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: waef type: sot - - 132216 - - class: od levtype: sfc stream: wasf type: fc - - 140220 - 140221 - 140229 - 140230 - 140231 - 140232 - 140233 - 140244 - 140245 - 140249 - - class: od levtype: sfc stream: wave type: 4v - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: wave type: an - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140246 - 140247 - 140248 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: wave type: fc - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: weef type: cf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: weef type: fcmax - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weef type: fcmean - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weef type: fcmin - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weef type: fcstdev - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weef type: pf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140211 - 140212 - 140214 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: weeh type: cf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: weeh type: fcmax - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weeh type: fcmean - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weeh type: fcmin - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weeh type: fcstdev - - 140229 - 140231 - 140232 - 140233 - 140245 - - class: od levtype: sfc stream: weeh type: pf - - 140098 - 140099 - 140100 - 140101 - 140102 - 140103 - 140104 - 140105 - 140112 - 140113 - 140114 - 140115 - 140116 - 140117 - 140118 - 140119 - 140120 - 140121 - 140122 - 140123 - 140124 - 140125 - 140126 - 140127 - 140128 - 140129 - 140131 - 140132 - 140133 - 140134 - 140207 - 140208 - 140209 - 140215 - 140216 - 140217 - 140218 - 140219 - 140220 - 140221 - 140222 - 140223 - 140224 - 140225 - 140226 - 140227 - 140228 - 140229 - 140230 - 140231 - 140232 - 140233 - 140234 - 140235 - 140236 - 140237 - 140238 - 140239 - 140244 - 140245 - 140249 - 140251 - 140252 - 140253 - 140254 - - class: od levtype: sfc stream: wehs type: cd - - 140200 metkit-1.20.2/share/metkit/.gitignore0000664000175000017500000000001315246725757017662 0ustar alastairalastair*.list tmp metkit-1.20.2/share/metkit/chemids.yaml0000664000175000017500000001702215246725757020202 0ustar alastairalastair--- # File automatically generated by make-yaml.py --chem # Do not edit - [2, CO, Carbon monoxide] - [3, H2O2, Hydrogen peroxide] - [4, CH4_c, Methane (chemistry)] - [5, HCHO, Formaldehyde] - [6, HNO3, Nitric acid] - [7, CH3OOH, Methyl peroxide] - [8, SO2_v, Volcanic sulphur dioxide] - [9, par, Paraffins] - [10, C2H4, Ethene] - [11, Rn, Radon] - [12, ALD2, Aldehydes] - [13, PAN, Peroxyacetyl nitrate] - [14, ROOH, Peroxides] - [15, NO3_org, Organic nitrates] - [16, C5H8, Isoprene] - [17, NO2, Nitrogen dioxide] - [18, DMS, Dimethyl sulfide] - [19, NH3, Ammonia] - [20, SO4, Sulfate] - [21, NH4, Ammonium] - [22, MSA, Methane sulfonic acid] - [23, CH3COCHO, Methyl glyoxal] - [24, O3S, Stratospheric ozone] - [25, CO2, Carbon dioxide] - [26, Pb, Lead] - [27, 'NO', Nitrogen monoxide] - [28, HO2_r, Hydroperoxy radical] - [29, CH3O2_r, Methylperoxy radical] - [30, OH_r, Hydroxyl radical] - [31, N2O, Nitrous oxide] - [32, NO3, Nitrate radical] - [33, N2O5, Dinitrogen pentoxide] - [34, HO2NO2, Pernitric acid] - [35, C2O3_r, Peroxy acetyl radical] - [36, ROR, Organic ethers] - [37, rxpar, PAR budget corrector] - [38, xo2, NO to NO2 operator] - [39, xo2n, NO to alkyl nitrate operator] - [40, NH2, Amine] - [41, psc, Polar stratospheric cloud] - [42, CH3OH, Methanol] - [43, HCOOH, Formic acid] - [44, MCOOH, Methacrylic acid] - [45, C2H6, Ethane] - [46, C2H5OH, Ethanol] - [47, C3H8, Propane] - [48, C3H6, Propene] - [49, C10H16, Terpenes] - [50, ISPD, Methacrolein MVK] - [52, CH3COCH3, Acetone] - [53, aco2, Acetone product] - [55, hypropo2] - [56, noxa, Nitrogen oxides Transp] - [57, CO2_c, Carbon dioxide (chemistry)] - [58, N2O_c, Nitrous oxide (chemistry)] - [59, H2O_c, Water vapour (chemistry)] - [60, O2, Dioxygen] - [61, O2_1s, Singlet sigma dioxygen] - [62, O2_1d, Singlet Delta dioxygen] - [63, OClO, Chlorine dioxide] - [64, ClONO2, Chlorine nitrate] - [65, HOCl, Hypochlorous acid] - [66, Cl2, Chlorine] - [67, ClNO2, Nitryl chloride] - [68, HBr, Hydrogen bromide] - [69, Cl2O2, Dichlorine dioxide] - [70, HOBr, Hypobromous acid] - [71, CFC11, Trichlorofluoromethane] - [72, CFC12, Dichlorodifluoromethane] - [73, CFC113, Trichlorotrifluoroethane] - [74, CFC114, Dichlorotetrafluoroethane] - [75, CFC115, Chloropentafluoroethane] - [76, ccl4, Tetrachloromethane] - [77, ch3ccl3, Methyl chloroform] - [78, CH3Cl, Methyl chloride] - [79, hcfc22, Chlorodifluoromethane] - [80, CH3Br, Methyl bromide] - [82, ha1211, Bromochlorodifluoromethane] - [83, ha1301, Trifluorobromomethane] - [85, H2SO4, Sulfuric acid] - [86, HONO, Nitrous acid] - [93, DIEN, Butadiene] - [94, C2H5OOH, Ethyl hydroperoxide] - [96, CH3COOH, Acetic acid] - [98, CH3CHO, Acetaldehyde] - [99, tol, Toluene and less reactive aromatics] - [100, xyl, Xylene and more reactive aromatics] - [101, GLYALD, Glycolaldehyde] - [102, CRESOL] - [104, CH3COOOH, Peracetic acid] - [105, KET, Ketones] - [106, EO2, Hoch2ch2o2] - [107, GLYOXAL] - [108, EO, Hoch2ch2o] - [109, DCB, Unsaturated dicarbonyls] - [110, MACR, Methacrolein] - [111, UDD, Unsaturated hydroxy dicarbonyl] - [112, C3H7O2, Isopropyldioxidanyl] - [113, HKET, Hydroxy ketone] - [114, C3H7OOH, Isopropyl hydroperoxide] - [115, PO2, C3h6oho2] - [116, POOH, C3h6ohooh] - [118, HYAC, Hydroxyacetone] - [119, PAA, Peroxyacetic acid] - [124, BIGENE, Lumped alkenes] - [126, BIGALK, Lumped alkanes] - [129, NOx, Nitrogen oxides] - [138, MVK, Methylvinylketone] - [139, PhO, Phenoxy radical] - [155, Ox, Oxides] - [159, BrOx, Bromine oxides] - [161, ISOPOOH, Hoch2c(ooh)(ch3)ch=ch2] - [163, DMSO, Dimethyl sulfoxyde] - [166, H2S, Hydrogensulfide] - [168, NOy, All nitrogen oxides] - [169, cly, Chlorine family] - [171, bry, Bromine family] - [173, N, Nitrogen atom] - [174, ClO, Chlorine monoxide] - [175, Cl, Chlorine atom] - [176, BrO, Bromine monoxide] - [177, h_c, Hydrogen atom] - [178, ch3, Methyl group] - [186, sog1, Condensable gas type 1] - [187, sog2a, Condensable gas type 2a] - [188, sog2b, Condensable gas type 2b] - [189, SO3, Sulfur trioxide] - [190, OCS_c, Carbonyl sulfide] - [191, Br, Bromine atom] - [192, Br2, Bromine] - [193, BrCl, Bromine monochloride] - [194, BrONO2, Bromine nitrate] - [195, CH2Br2, Dibromomethane] - [196, CH3O, Methoxy radical] - [197, CHBr3, Tribromomethane] - [198, cloo, Asymmetric chlorine dioxide radical] - [199, H2, Dihydrogen] - [200, HCl, Hydrogen chloride] - [201, HCO, Formyl radical] - [202, HF, Hydrogen fluoride] - [203, O, Oxygen atom] - [204, O_1d, Excited oxygen atom] - [205, O_3p, Ground state oxygen atom] - [222, AROO2, Aromatic peroxide radical] - [224, CH3CN, Acetonitrile] - [225, CH3O2NO2, Methyl peroxy nitrate] - [226, HCN, Hydrogen cyanide] - [227, HPALD1, Hydroperoxy aldehydes type 1] - [228, HPALD2, Hydroperoxy aldehydes type 2] - [229, ISOPBO2, Isoprene peroxy type B] - [230, ISOPDO2, Isoprene peroxy type D] - [231, gO3, GEMS ozone] - [232, SF6, Sulphur hexafluoride] - [233, SO2, Sulphur dioxide] - [236, O3, Ozone] - [311, ole, Olefins] - [331, aVOC, Anthropogenic volatile organic compounds] - [332, bbVOC, Biomass burning volatile organic compounds] - [359, H2O, Water vapour] - [360, H2Orain, Rain water] - [361, H2Ocloud, Cloud water] - [400, gasmol, Gaseous molecules] - [404, CH4, Methane] - [431, C7H8, Toluene] - [432, C6H6, Benzene] - [433, C8H10, Xylene] - [438, C4H10, Butane] - [439, C4H8, Butene] - [440, C5H10, Pentene] - [441, C6H12, Hexene] - [442, C7H14, Heptene] - [443, C8H16, Octene] - [444, C5H12, Pentane] - [445, C6H14, Hexane] - [446, C7H16, Heptane] - [447, C8H18, Octane] - [900, aer_NO3, Nitrate] - [901, aer_seasalt_003_05, Sea salt aerosol (0.03 - 0.5 um)] - [902, aer_seasalt_05_5, Sea salt aerosol (0.5 - 5 um)] - [903, aer_seasalt_5_20, Sea salt aerosol (5 - 20 um)] - [904, aer_dust_003_055, Dust aerosol (0.03 - 0.55 um)] - [905, aer_dust_055_09, Dust aerosol (0.55 - 0.9 um)] - [906, aer_dust_09_20, Dust aerosol (0.9 - 20 um)] - [907, aer_hicorgmat, Hydrophilic organic matter aerosol] - [908, aer_hocorgmat, Hydrophobic organic matter aerosol] - [909, aer_hicblackcarb, Hydrophilic black carbon aerosol] - [910, aer_hocblackcarb, Hydrophobic black carbon aerosol] - [911, aer_sulph, Sulphate aerosol] - [912, aer_nitrfine, Nitrate fine mode aerosol (<= 2.5 um)] - [913, aer_nitrcoars, Nitrate coarse mode aerosol (>2.5 um)] - [914, aer_nh4, Ammonium aerosol] - [915, aer_biosecorg, Biogenic secondary organic aerosol] - [916, aer_antsecorg, Anthropogenic secondary organic aerosol] - [917, strataer, Stratospheric aerosol] - [918, SOA, Secondary particulate organic matter dry] - [919, aer_biosecorg_pre, Biogenic secondary organic aerosol precursor] - [920, aer_antsecorg_pre, Anthropogenic secondary organic aerosol precursor] - [921, aer_so2_pre, SO2 precursor] - [922, aer_total, Total aerosol] - [923, aer_sm, Aerosol small mode] - [924, aer_lm, Aerosol large mode] - [925, aer_dust_09_2p5, Dust aerosol (0.9 - 2.5 um)] - [926, aer_dust_2p5_5, Dust aerosol (2.5 - 5.0 um)] - [927, aer_dust_5_10, Dust aerosol (5.0 - 10.0 um)] - [928, aer_dust_10_20, Dust aerosol (10.0 - 20.0 um)] - [929, aer_pm_1, Particulate matter <= 1 um] - [930, aer_pm_2p5, Particulate matter <= 2.5 um] - [931, aer_pm_10, Particulate matter <= 10 um] - [932, volash, Volcanic Ash] - [933, aer_blackcarb, Black carbon organic aerosol] - [934, aer_orgmat, Organic matter aerosol] - [935, aer_dust, Dust aerosol] - [936, aer_seasalt, Sea salt aerosol] - [937, aer_dust_1, Dust aerosol smaller than 1 um] - [938, aer_dust_2p5, Dust aerosol smaller than 2.5 um] - [939, aer_dust_10, Dust aerosol smaller than 10 um] - [940, aer_dust_20, Dust aerosol smaller than 20 um] - [941, SO4_v, Volcanic Sulphate] - [942, aer_seasalt_wet80p, Sea salt wet at 80% relative humidity] metkit-1.20.2/share/metkit/make-params-yaml.py0000664000175000017500000000477315246725757021423 0ustar alastairalastair# (c) Copyright 1996-2012 ECMWF. # # 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. # Documentation is available on Confluence # https://confluence.ecmwf.int/display/METK/MetKit+configuration import os import sys from collections import OrderedDict import psycopg2 import yaml DEFAULT_PORT = 5432 def main(): try: HOST = os.environ['PRODUCT_CATALOGUE_HOST'] DB = os.environ['PRODUCT_CATALOGUE_DB'] USER = os.environ['PRODUCT_CATALOGUE_USER'] PASSWORD = os.environ['PRODUCT_CATALOGUE_PASSWORD'] PORT = ( int(os.environ['PRODUCT_CATALOGUE_PORT']) if 'PRODUCT_CATALOGUE_PORT' in os.environ else DEFAULT_PORT ) except KeyError as e: print("ERROR: Environment variable not found: {}".format(e)) sys.exit(1) with psycopg2.connect(host=HOST, dbname=DB, user=USER, password=PASSWORD, port=PORT) as conn: with conn.cursor() as cur: cur.execute( "SELECT DISTINCT class, stream, type, levtype, param::INTEGER FROM fields WHERE param != '' ORDER BY class, stream, type, levtype, param::INTEGER" ) rows = cur.fetchall() index = OrderedDict() for row in rows: cl, stream, type_, levtype, param = row key = (cl, stream, type_, levtype) if key not in index: index[key] = [] index[key].append(param) # Manually add type=tf parameters for PGEN index[("od", "oper", "tf", "")] = [129, 999] index[("od", "scda", "tf", "")] = [129, 999] index[("od", "enfo", "tf", "")] = [129, 999] yaml_dump_data = [] for key, vals in sorted(index.items()): if key[3]: yaml_dump_data.append( [{"class":key[0], "stream":key[1], "type":key[2], "levtype":key[3]}, vals] ) else: yaml_dump_data.append([{"class":key[0], "stream":key[1], "type":key[2]}, vals]) with open("params.yaml", "w") as f: f.write( "# File automatically generated by %s\n# Do not edit\n\n" % (os.path.basename(__file__)) ) f.write(yaml.safe_dump(yaml_dump_data, default_flow_style=False)) if __name__ == "__main__": main() metkit-1.20.2/share/metkit/CMakeLists.txt0000664000175000017500000000110715246725757020437 0ustar alastairalastairlist( APPEND files language.yaml modifiers.yaml reportype.yaml obstype.yaml paramids.yaml params.yaml params-static.yaml param-matching.yaml chemids.yaml bufr-subtypes.yaml axis.yaml shortname-context.yaml ) foreach( _file ${files} ) configure_file( ${_file} ${CMAKE_BINARY_DIR}/share/metkit/${_file} COPYONLY ) if( HAVE_METKIT_CONFIG ) install( FILES ${CMAKE_BINARY_DIR}/share/metkit/${_file} DESTINATION "share/metkit" PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ ) endif() endforeach() add_subdirectory( odb ) metkit-1.20.2/share/metkit/shortname-context.yaml0000664000175000017500000000254315246725757022252 0ustar alastairalastair--- - 10spg10 - 10spg15 - al - apab_s - apt - asn - bas_con - bld - bli - btp - cap - cape - cape_con - ccc - cdct - clc - cp - cpa - d - dhcc - dhlc - dhr - dhvd - dirc - dqv_con - dslm - dt_con - du_con - dv_con - e - ewgd - gh - hbas_con - hc300m - hc700m - hcbtm - hcc - hfds - htcc - htlc - htop_con - htop_dc - hzerocl - icec - iced - iceg - imgd - lai - lcc - lnsp - lsp - lspa - lwhr - mcc - mdps - mdww - mld - mlotst010 - mlotst030 - mlotst125 - mlott02 - mlott05 - mpps - mpww - msl - msror - mssror - mtha - mthd - mucape - mwd - ndvi - nlwrs - nlwrt - nsf - nsgd - nswrt - o3 - par - pp - pp1d - prec - pres - prr_con - prr_gsp - prs_con - prs_gsp - pta - qc - qi - r - ra - rain_con - rain_gsp - rn - ro - ru-103 - sc300m - sc700m - sd - sf - shf - shww - sialb - sigmat - sipd - sisnthick - sitemptop - sithick - siue - sivn - slhf - snowlmt - so - sohr_rad - sos - sp - spc - sr - sro - srweq - sshf - ssr - ssrd - sst - stfbarot - str - strd - strda - sund - swdi - swh - swhr - swl2 - swmp - t - t14d - t17d - t20d - t26d - t28d - t_ice - t_snow - ta - tauuo - tauvo - tcc - tcond - thetao - thhr_rad - tke - tmax - top_con - tos - tp - tprate - tsec - tsr - tsw - ttr - twater - u - ucq - uflx - ustr - ut - uu - uv - uvi - v - vapp - vdh - vdmw - vdzw - veg - vflx - vmax_10m - vo - vp - vt - vv - w - w_i - wilt - wind - wo - wvsp1 - wvsp2 - wvsp3 - z - zos metkit-1.20.2/share/metkit/odb/0000775000175000017500000000000015246725757016444 5ustar alastairalastairmetkit-1.20.2/share/metkit/odb/group.txt0000664000175000017500000001114415246725757020342 0ustar alastairalastair id↑ ; name ; kind_id ; marsname ; description ; 1 ; HIRS ; 2 ; HIRS ; ; 2 ; AMSUA ; 2 ; AMSUA ; ; 3 ; AMSUB ; 2 ; AMSUB ; ; 4 ; MHS ; 2 ; MHS ; ; 5 ; GEOS ; 2 ; GEOS ; ; 6 ; RESAT ; 2 ; RESAT ; ; 7 ; MERIS ; 2 ; MERIS ; ; 8 ; GPSRO ; 2 ; GPSRO ; ; 9 ; SATOB ; 3 ; SATOB ; ; 10 ; SCATT ; 2 ; SCATT ; ; 11 ; SSMI All-sky ; 2 ; SSMI_AS ; ; 12 ; IASI ; 2 ; IASI ; ; 13 ; AIRS ; 2 ; AIRS ; ; 14 ; SSMIS All-sky ; 2 ; SSMIS_AS ; ; 15 ; TMI All-sky ; 2 ; TMI_AS ; ; 16 ; AMSRE All-sky ; 2 ; AMSRE_AS ; ; 17 ; CONV ; 1 ; CONV ; ; 18 ; STORM ; 1 ; STORM ; STORM ; 19 ; SMOS ; 2 ; SMOS ; ; 20 ; WINDSAT All-sky ; 2 ; WINDSAT_AS ; ; 21 ; SSMI ; 2 ; SSMI ; ; 22 ; AMSUA All-sky ; 2 ; AMSUA_AS ; ; 23 ; AMSRE ; 2 ; AMSRE ; ; 24 ; TMI ; 2 ; TMI ; ; 25 ; SSMIS ; 2 ; SSMIS ; ; 26 ; GBRAD ; 1 ; GBRAD ; ; 27 ; MWHS ; 2 ; MWHS ; ; 28 ; MWTS ; 2 ; MWTS ; ; 29 ; MWRI All-sky ; 2 ; MWRI_AS ; ; 30 ; IRAS ; 2 ; IRAS ; ; 31 ; MSU ; 2 ; MSU ; ; 32 ; SSU ; 2 ; SSU ; ; 33 ; VTPR1 ; 2 ; VTPR1 ; ; 34 ; VTPR2 ; 2 ; VTPR2 ; ; 35 ; ATMS ; 2 ; ATMS ; ; 36 ; RESAT Averaging Kernels ; 2 ; RESAT_AK ; ; 37 ; CRIS ; 2 ; CRIS ; Cross-track Infrared Sounder ; 38 ; WAVE integrated Parameters ; 3 ; WAVE_IP ; WAVE integrated Parameters ; 39 ; WAVE spectra ; 3 ; WAVE_SP ; WAVE spectra ; 40 ; RAINGG ; 5 ; RAINGG ; Rain Gauge ; 41 ; SURFACE MULTISENSOR ; 1 ; SFC_MS ; SURFACE MULTISENSOR ; 42 ; AMSR-2 All-sky ; 2 ; AMSR2_AS ; AMSR-2 All-sky ; 43 ; SAPHIR All-sky ; 2 ; SAPHIR_AS ; SAPHIR All-sky ; 44 ; AMSUB All-sky ; 2 ; AMSUB_AS ; AMSUB All-sky ; 45 ; MHS All-sky ; 2 ; MHS_AS ; MHS All-sky ; 46 ; Doppler Wind Lidar ; 2 ; DWL ; Doppler Wind Lidar ; 47 ; IRIS ; 2 ; IRIS ; Infrared Interferometer Spectrometer ; 48 ; Altimeter ; 3 ; ALT ; Altimeter ; 49 ; AATSR ; 2 ; AATSR ; Advanced Along Track Scanning Radiometer ; 50 ; ATMS All-sky ; 2 ; ATMS_AS ; ATMS All-sky ; 51 ; GMI All-sky ; 2 ; GMI_AS ; GMI All-sky ; 52 ; GODAE Sea Surface Temperatures ; 4 ; GODAE_SST ; GODAE Sea Surface Temperatures ; 53 ; ATOVS MULTISENSOR ; 4 ; ATOVS_MS ; ATOVS MULTISENSOR ; 54 ; Atmospheric composition ; 1 ; ATMOSPHERIC_COMPOSITION ; Atmospheric composition ; 55 ; NON-SURFACE MULTISENSOR ; 4 ; NON_SFC_MS ; NON-SURFACE MULTISENSOR ; 56 ; MWTS2 ; 2 ; MWTS2 ; Microwave Temperature Sounder 2 ; 57 ; SSMI 1DVAR TCWV Cloudy-Sky ; 2 ; SSMI_1D ; SSMI 1DVAR TCWV Cloudy-Sky ; 58 ; MWHS2 All-sky ; 2 ; MWHS2_AS ; Microwave Humidity Sounder 2 ; 59 ; SSMT2 ; 2 ; SSMT2 ; SSMT2 ; 60 ; SMAP ; 2 ; SMAP ; Soil Moisture Active and Passive ; 61 ; TOVS MULTISENSOR ; 4 ; TOVS_MS ; TOVS MULTISENSOR ; 62 ; Cloud reflectivity ; 2 ; CLOUD_R ; Cloud reflectivity ; 63 ; Cloud Lidar ; 2 ; CLOUD_L ; Cloud Lidar ; 64 ; Satellite Lightning ; 2 ; SATELLITE_LIGHTNING ; Satellite Lightning ; 65 ; GEOS VIS ; 2 ; GEOS_VIS ; GEOS visible reflectances ; 66 ; OCONV ; 1 ; OCONV ; Ocean conventional in situ ; 67 ; MWTS3 All-sky ; 2 ; MWTS3_AS ; MWTS3 All-sky ; 68 ; GIIRS ; 2 ; GIIRS ; GIIRS ; 69 ; HIRAS ; 2 ; HIRAS ; HIRAS ; 70 ; TROPICS All-sky ; 2 ; TROPICS_AS ; TROPICS All-sky ; 71 ; SSMT1 ; 2 ; SSMT1 ; SSMT1 ; 72 ; SSMT1 All-sky ; 2 ; SSMT1_AS ; SSMT1 All-sky ; 73 ; MWS All-sky ; 2 ; MWS_AS ; MWS All-sky ; 74 ; MWIICI All-sky ; 2 ; MWIICI_AS ; MWIICI All-sky ; 75 ; AWS All-sky ; 2 ; AWS_AS ; AWS All-sky ; 76 ; LEOS VIS ; 2 ; LEOS_VIS ; LEOS visible reflectances ; 77 ; SSH ; 2 ; SSH ; SSH ; 78 ; SIRS ; 2 ; SIRS ; SIRS ; 79 ; SCR ; 2 ; SCR ; SCR (Selective Chopper Radiometer) ; 80 ; SCAMS All-sky ; 2 ; SCAMS_AS ; SCAMS All-sky ; 81 ; NEMS All-sky ; 2 ; NEMS_AS ; NEMS All-sky ; 82 ; AMSR-3 All-sky ; 2 ; AMSR3_AS ; AMSR-3 All-sky ; 83 ; IRS ; 2 ; IRS ; IRS ; 84 ; IASI-NG ; 2 ; IASING ; IASI-NG ; 85 ; SMAP All-sky ; 2 ; SMAP_AS ; SMAP All-sky ; 86 ; SSMT2 All-sky ; 2 ; SSMT2_AS ; SSMT2 All-sky ; 87 ; SMMR All-sky ; 2 ; SMMR_AS ; SMMR All-sky ; 99 ; TEST ; 4 ; TEST ; ; metkit-1.20.2/share/metkit/odb/class.table0000664000175000017500000000413015246725757020560 0ustar alastairalastair0 0 Unknown 1 od Operational archive 2 rd Research department 3 er 15 years reanalysis (ERA15) 4 cs ECSN 5 e4 40 years reanalysis (ERA40) 6 dm DEMETER 7 pv PROVOST 8 el ELDAS 9 to TOST 10 co COSMO-LEPS 11 en ENSEMBLES 12 ti TIGGE 13 me MERSEA 14 ei ERA Interim 15 sr Short-Range Ensemble Prediction System 16 dt Data Targeting System 17 la ALADIN-LAEF 18 yt YOTC 19 mc Copernicus Atmosphere Monitoring Service (CAMS, previously MACC) 20 pe Permanent experiments 21 em ERA-CLIM model integration for the 20th-century (ERA-20CM) 22 e2 ERA-CLIM reanalysis of the 20th-century using surface observations only (ERA-20C) 23 ea ERA5 24 ep ERA-CLIM2 coupled reanalysis of the 20th-century (CERA-20C) 25 rm EURO4M 26 nr NOAA/CIRES 20th Century Reanalysis version II 27 s2 Sub-seasonal to seasonal prediction project (S2S) 28 j5 Japanese 55 year Reanalysis (JRA55) 29 ur UERRA 30 et ERA-CLIM2 coupled reanalysis of the satellite era (CERA-SAT) 31 c3 Copernicus Climate Change Service (C3S) 32 yp YOPP 33 l5 ERA5/LAND 34 lw WMO Lead Centre Wave Forecast Verification 35 ce Copernicus Emergency Management Service (CEMS) 36 cr Copernicus Atmosphere Monitoring Service (CAMS) Research 37 rr Copernicus Regional ReAnalysis (CARRA/CERRA) 38 ul Project ULYSSES 39 gw Global Wildfire Information System 40 e6 ERA6 41 l6 ERA6/LAND 42 ef EFAS (European flood awareness system) 43 gf GLOFAS (Global flood awareness system) 44 gg Greenhouse Gases 45 ml Machine learning 46 d1 Destination Earth 47 o6 Ocean ReAnalysis 6 48 eh C3S European hydrology 49 gh C3S Global hydrology 50 ci CERISE project 51 ai Operational AIFS 52 ed EERIE project 53 ng nextGEMS project 54 lr WMO Lead Centre for Global Climate Re-Analyses 55 a5 ECMWF Atmospheric Composition reanalysis version 5 (EAC5) 99 te Test 100 at Austria 101 be Belgium 102 hr Croatia 103 dk Denmark 104 fi Finland 105 fr France 106 de Germany 107 gr Greece 108 hu Hungary 109 is Iceland 110 ie Ireland 111 it Italy 112 nl Netherlands 113 no Norway 114 pt Portugal 115 si Slovenia 116 es Spain 117 se Sweden 118 ch Switzerland 119 tr Turkey 120 uk United Kingdom 121 ms Member States projects 199 ma Metaps metkit-1.20.2/share/metkit/odb/marsrequest.yaml0000664000175000017500000000017715246725757021710 0ustar alastairalastair--- CLASS: class DATE: andate TIME: antime TYPE: type OBSGROUP: groupid@hdr REPORTYPE: reportype STREAM: stream EXPVER: expver metkit-1.20.2/share/metkit/odb/type.table0000664000175000017500000000035415246725757020440 0ustar alastairalastair262 mfb MonDB feedback 263 ofb ODB feedback 264 oai ODB analysis input 265 sfb Summary Feedback 266 fsoifb Forecast Sensitivity to Observations Impact Feedback 267 fcdfb Forecast Departures Feedback 268 ofa ODB Feedback from Analysis metkit-1.20.2/share/metkit/odb/stream.table0000664000175000017500000001275015246725757020755 0ustar alastairalastair0 0 Unknown 1022 fsob Forecast sensitivity to observations 1023 fsow Forecast sensitivity to observations wave 1024 dahc Daily archive hindcast 1025 oper Atmospheric model 1026 scda Atmospheric model (short cutoff) 1027 scwv Wave model (short cutoff) 1028 dcda Atmospheric model (delayed cutoff) 1029 dcwv Wave model (delayed cutoff) 1030 enda Ensemble data assimilation 1032 efho Ensemble forecast hindcast overlap 1033 enfh Ensemble forecast hindcasts 1034 efov Ensemble forecast overlap 1035 enfo Ensemble prediction system 1036 sens Sensitivity forecast 1037 maed Multianalysis ensemble data 1038 amap Analysis for multianalysis project 1039 efhc Ensemble forecast hindcasts (obsolete) 1040 efhs Ensemble forecast hindcast statistics 1041 toga TOGA 1042 cher Chernobyl 1043 mnth Monthly means 1044 supd Deterministic supplementary data 1045 wave Wave model 1046 ocea Ocean 1047 fgge FGGE 1050 egrr Bracknell 1051 kwbc Washington 1052 edzw Offenbach 1053 lfpw Toulouse 1054 rjtd Tokyo 1055 cwao Montreal 1056 ammc Melbourne 1057 efas European flood awareness system (EFAS) 1058 efse European flood awareness system (EFAS) seasonal forecasts 1059 efcl European flood awareness system (EFAS) climatology 1060 wfas Global flood awareness system (GLOFAS) 1061 wfcl Global flood awareness system (GLOFAS) climatology 1062 wfse Global flood awareness system (GLOFAS) seasonal forecasts 1063 efrf European flood awareness system (EFAS) reforecasts 1064 efsr European flood awareness system (EFAS) seasonal reforecasts 1065 wfrf Global flood awareness system (GLOFAS) reforecasts 1066 wfsr Global flood awareness system (GLOFAS) seasonal reforecasts 1070 msdc Monthly standard deviation and covariance 1071 moda Monthly means of daily means 1072 monr Monthly means using G. Boer's step function 1073 mnvr Monthly variance and covariance data using G. Boer's step function 1074 msda Monthly standard deviation and covariance of daily means 1075 mdfa Monthly means of daily forecast accumulations 1076 dacl Daily climatology 1077 wehs Wave ensemble forecast hindcast statistics 1078 ewho Ensemble forecast wave hindcast overlap 1079 enwh Ensemble forecast wave hindcasts 1080 wamo Wave monthly means 1081 waef Wave ensemble forecast 1082 wasf Wave seasonal forecast 1083 mawv Multianalysis wave data 1084 ewhc Wave ensemble forecast hindcast (obsolete) 1085 wvhc Wave hindcast 1086 weov Wave ensemble forecast overlap 1087 wavm Wave model (standalone) 1088 ewda Ensemble wave data assimilation 1089 dacw Daily climatology wave 1090 seas Seasonal forecast 1091 sfmm Seasonal forecast atmospheric monthly means 1092 swmm Seasonal forecast wave monthly means 1093 mofc Monthly forecast 1094 mofm Monthly forecast means 1095 wamf Wave monthly forecast 1096 wmfm Wave monthly forecast means 1097 smma Seasonal monthly means anomalies 1098 clte Climate run output 1099 clmn Climate run monthly means output 1100 dame Daily means 1110 seap Sensitive area prediction 1120 eefh Extended ensemble forecast hindcast 1121 eehs Extended ensemble forecast hindcast statistics 1122 eefo Extended ensemble prediction system 1123 weef Wave extended ensemble forecast 1124 weeh Wave extended ensemble forecast hindcast 1125 wees Wave extended ensemble forecast hindcast statistics 1200 mnfc Real-time 1201 mnfh Hindcasts 1202 mnfa Anomalies 1203 mnfw Wave real-time 1204 mfhw Monthly forecast hindcasts wave 1205 mfaw Wave anomalies 1206 mnfm Real-time means 1207 mfhm Hindcast means 1208 mfam Anomaly means 1209 mfwm Wave real-time means 1210 mhwm Wave hindcast means 1211 mawm Wave anomaly means 1220 mmsf Multi-model seasonal forecast 1221 msmm Multi-model seasonal forecast atmospheric monthly means 1222 wams Multi-model seasonal forecast wave 1223 mswm Multi-model seasonal forecast wave monthly means 1224 mmsa Multi-model seasonal forecast monthly anomalies 1230 mmaf Multi-model multi-annual forecast 1231 mmam Multi-model multi-annual forecast means 1232 mmaw Multi-model multi-annual forecast wave 1233 mmwm Multi-model multi-annual forecast wave means 1240 esmm Combined multi-model monthly means 1241 ehmm Combined multi-model hindcast monthly means 1242 edmm Ensemble data assimilation monthly means 1243 edmo Ensemble data assimilation monthly means of daily means 1244 ewmo Ensemble wave data assimilation monthly means of daily means 1245 ewmm Ensemble wave data assimilation monthly means 1246 espd Ensemble supplementary data 1247 lwda Long window daily archive 1248 lwwv Long window wave 1249 elda Ensemble Long window Data Assimilation 1250 ewla Ensemble Wave Long window data Assimilation 1251 wamd Wave monthly means of daily means 1252 gfas Global fire assimilation system 1253 ocda Ocean data assimilation 1254 olda Ocean Long window data assimilation 1255 gfra Global Fire assimilation system reanalysis 1256 rfsd Retrospective forcing and simulation data 1257 sttd Statistics of deterministic data 1258 stte Statistics of ensemble data 1259 xwda eXtended Window Data Assimilation 1260 sfdd Seasonal forecast daily and sub-daily data 1261 sfmd Seasonal forecast monthly data 1262 shdd Seasonal hindcast daily and sub-daily data 1263 shmd Seasonal hindcast monthly data 1264 sfdp Seasonal forecast derived products 1265 shdp Seasonal hindcast derived products 1266 ldas Land data-assimilation system (near real time) 1267 ldbt Land data-assimilation system behind real time 1268 ldst Land data-assimilation system statistics (near real time) 1269 ldsb Land data-assimilation system statistics behind real time 2231 cnrm Meteo France climate centre 2232 mpic Max Plank Institute 2233 ukmo UKMO climate centre metkit-1.20.2/share/metkit/odb/CMakeLists.txt0000664000175000017500000000071315246725757021205 0ustar alastairalastairlist( APPEND files_odb marsrequest.yaml class.table type.table stream.table group.txt ) foreach( _file ${files_odb} ) configure_file( ${_file} ${CMAKE_BINARY_DIR}/share/metkit/odb/${_file} COPYONLY ) if( HAVE_METKIT_CONFIG ) install( FILES ${CMAKE_BINARY_DIR}/share/metkit/odb/${_file} DESTINATION "share/metkit/odb" PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ ) endif() endforeach() metkit-1.20.2/share/metkit/paramids.yaml0000664000175000017500000171764315246725757020407 0ustar alastairalastair--- # File automatically generated by make-yaml.py --param # Do not edit 1: - strf - Stream function 2: - vp - Velocity potential - vpot 3: - pt - Potential temperature 4: - eqpt - Equivalent potential temperature 5: - sept - Saturated equivalent potential temperature 6: - ssfr - Soil sand fraction 7: - scfr - Soil clay fraction 8: - sro - Surface runoff 9: - ssro - Sub-surface runoff 10: - ws - Wind speed 11: - udvw - U component of divergent wind 12: - vdvw - V component of divergent wind 13: - urtw - U component of rotational wind 14: - vrtw - V component of rotational wind 15: - aluvp - UV visible albedo for direct radiation (climatological) - auvp 16: - aluvd - UV visible albedo for diffuse radiation (climatological) - auvd 17: - alnip - Near IR albedo for direct radiation (climatological) - anip 18: - alnid - Near IR albedo for diffuse radiation (climatological) - anid 19: - uvcs - Clear sky surface UV 20: - parcs - Surface photosynthetically active radiation, clear sky 21: - uctp - Unbalanced component of temperature 22: - ucln - Unbalanced component of logarithm of surface pressure 23: - ucdv - Unbalanced component of divergence 24: - _param_000024 - Reserved for future unbalanced components 25: - _param_000025 - Reserved for future unbalanced components 26: - cl - Lake cover 27: - cvl - Low vegetation cover 28: - cvh - High vegetation cover 29: - tvl - Type of low vegetation 30: - tvh - Type of high vegetation 31: - ci - Sea ice area fraction 32: - asn - Snow albedo 33: - rsn - Snow density 34: - sst - Sea surface temperature - sstk 35: - istl1 - Ice temperature layer 1 36: - istl2 - Ice temperature layer 2 37: - istl3 - Ice temperature layer 3 38: - istl4 - Ice temperature layer 4 39: - swvl1 - Volumetric soil water layer 1 - swv1 40: - swvl2 - Volumetric soil water layer 2 - swv2 41: - swvl3 - Volumetric soil water layer 3 - swv3 42: - swvl4 - Volumetric soil water layer 4 - swv4 43: - slt - Soil type 44: - es - Snow evaporation 45: - smlt - Snowmelt 46: - sdur - Solar duration 47: - dsrp - Surface direct normal short-wave (solar) radiation 48: - magss - Time-integrated magnitude of turbulent surface stress 49: - 10fg - Maximum 10 metre wind gust since previous post-processing 50: - lspf - Large-scale precipitation fraction 51: - mx2t24 - Maximum temperature at 2 metres in the last 24 hours 52: - mn2t24 - Minimum temperature at 2 metres in the last 24 hours 53: - mont - Montgomery potential 54: - pres - Pressure 55: - mean2t24 - Mean temperature at 2 metres in the last 24 hours 56: - mn2d24 - Mean 2 metre dewpoint temperature in the last 24 hours 57: - uvb - Surface downward UV radiation 58: - par - Photosynthetically active radiation at the surface 59: - cape - Convective available potential energy 60: - pv - Potential vorticity 62: - obct - Observation count 63: - stsktd - Start time for skin temperature difference 64: - ftsktd - Finish time for skin temperature difference 65: - sktd - Skin temperature difference 66: - lai_lv - Leaf area index, low vegetation - lailv 67: - lai_hv - Leaf area index, high vegetation - laihv 68: - msr_lv - Minimum stomatal resistance, low vegetation 69: - msr_hv - Minimum stomatal resistance, high vegetation 70: - bc_lv - Biome cover, low vegetation 71: - bc_hv - Biome cover, high vegetation 72: - issrd - Instantaneous surface solar radiation downwards 73: - istrd - Instantaneous surface thermal radiation downwards 74: - sdfor - Standard deviation of filtered subgrid orography (climatological) 75: - crwc - Specific rain water content 76: - cswc - Specific snow water content 77: - etadot - Eta-coordinate vertical velocity - etad 78: - tclw - Total column cloud liquid water 79: - tciw - Total column cloud ice water 80: - _param_000080 - Experimental product 81: - _param_000081 - Experimental product 82: - _param_000082 - Experimental product 83: - _param_000083 - Experimental product 84: - _param_000084 - Experimental product 85: - _param_000085 - Experimental product 86: - _param_000086 - Experimental product 87: - _param_000087 - Experimental product 88: - _param_000088 - Experimental product 89: - _param_000089 - Experimental product 90: - _param_000090 - Experimental product 91: - _param_000091 - Experimental product 92: - _param_000092 - Experimental product 93: - _param_000093 - Experimental product 94: - _param_000094 - Experimental product 95: - _param_000095 - Experimental product 96: - _param_000096 - Experimental product 97: - _param_000097 - Experimental product 98: - _param_000098 - Experimental product 99: - _param_000099 - Experimental product 100: - _param_000100 - Experimental product 101: - _param_000101 - Experimental product 102: - _param_000102 - Experimental product 103: - _param_000103 - Experimental product 104: - _param_000104 - Experimental product 105: - _param_000105 - Experimental product 106: - _param_000106 - Experimental product 107: - _param_000107 - Experimental product 108: - _param_000108 - Experimental product 109: - _param_000109 - Experimental product 110: - _param_000110 - Experimental product 111: - _param_000111 - Experimental product 112: - _param_000112 - Experimental product 113: - _param_000113 - Experimental product 114: - _param_000114 - Experimental product 115: - _param_000115 - Experimental product 116: - _param_000116 - Experimental product 117: - _param_000117 - Experimental product 118: - _param_000118 - Experimental product 119: - _param_000119 - Experimental product 120: - _param_000120 - Experimental product 121: - mx2t6 - Maximum temperature at 2 metres in the last 6 hours 122: - mn2t6 - Minimum temperature at 2 metres in the last 6 hours 123: - 10fg6 - Maximum 10 metre wind gust in the last 6 hours 124: - emis - Surface emissivity 125: - vite - Vertically integrated total energy 126: - _param_000126 - Generic parameter for sensitive area prediction 127: - at - Atmospheric tide 128: - bv - Budget values 129: - z - Geopotential 130: - t - Temperature 131: - u - U component of wind 132: - v - V component of wind 133: - q - Specific humidity 134: - sp - Surface pressure 135: - w - Vertical velocity 136: - tcw - Total column water 137: - tcwv - Total column vertically-integrated water vapour 138: - vo - Vorticity (relative) 139: - stl1 - Soil temperature level 1 - st 140: - swl1 - Soil wetness level 1 141: - sd - Snow depth 142: - lsp - Large-scale precipitation 143: - cp - Convective precipitation 144: - sf - Snowfall 145: - bld - Time-integrated boundary layer dissipation 146: - sshf - Time-integrated surface sensible heat net flux 147: - slhf - Time-integrated surface latent heat net flux 148: - chnk - Charnock - ss 149: - snr - Surface net radiation (SW and LW) 150: - tnr - Top net radiation (SW and LW) 151: - msl - Mean sea level pressure 152: - lnsp - Logarithm of surface pressure 153: - swhr - Short-wave heating rate 154: - lwhr - Long-wave heating rate 155: - d - Divergence 156: - gh - Geopotential height 157: - r - Relative humidity 158: - tsp - Tendency of surface pressure 159: - blh - Boundary layer height 160: - sdor - Standard deviation of sub-gridscale orography 161: - isor - Anisotropy of sub-gridscale orography 162: - anor - Angle of sub-gridscale orography 163: - slor - Slope of sub-gridscale orography 164: - tcc - Total cloud cover 165: - 10u - 10 metre U wind component 166: - 10v - 10 metre V wind component 167: - 2t - 2 metre temperature 168: - 2d - 2 metre dewpoint temperature 169: - ssrd - Surface short-wave (solar) radiation downwards 170: - stl2 - Soil temperature level 2 171: - swl2 - Soil wetness level 2 172: - lsm - Land-sea mask 173: - sr - Surface roughness (climatological) 174: - al - Albedo (climatological) 175: - strd - Surface long-wave (thermal) radiation downwards 176: - ssr - Surface net short-wave (solar) radiation 177: - str - Surface net long-wave (thermal) radiation 178: - tsr - Top net short-wave (solar) radiation 179: - ttr - Top net long-wave (thermal) radiation 180: - ewss - Time-integrated eastward turbulent surface stress 181: - nsss - Time-integrated northward turbulent surface stress 182: - e - Evaporation 183: - stl3 - Soil temperature level 3 184: - swl3 - Soil wetness level 3 185: - ccc - Convective cloud cover 186: - lcc - Low cloud cover 187: - mcc - Medium cloud cover 188: - hcc - High cloud cover 189: - sund - Sunshine duration 190: - ewov - East-West component of sub-gridscale orographic variance 191: - nsov - North-South component of sub-gridscale orographic variance 192: - nwov - North-West/South-East component of sub-gridscale orographic variance 193: - neov - North-East/South-West component of sub-gridscale orographic variance 194: - btmp - Brightness temperature 195: - lgws - Eastward gravity wave surface stress 196: - mgws - Northward gravity wave surface stress 197: - gwd - Gravity wave dissipation 198: - src - Skin reservoir content 199: - veg - Vegetation fraction 200: - vso - Variance of sub-gridscale orography 201: - mx2t - Maximum temperature at 2 metres since previous post-processing 202: - mn2t - Minimum temperature at 2 metres since previous post-processing 203: - o3 - Ozone mass mixing ratio 204: - paw - Precipitation analysis weights 205: - ro - Runoff 206: - tco3 - Total column ozone 207: - 10si - 10 metre wind speed 208: - tsrc - Top net short-wave (solar) radiation, clear sky 209: - ttrc - Top net long-wave (thermal) radiation, clear sky - ttru 210: - ssrc - Surface net short-wave (solar) radiation, clear sky 211: - strc - Surface net long-wave (thermal) radiation, clear sky 212: - tisr - TOA incident short-wave (solar) radiation 213: - vimd - Vertically integrated moisture divergence 214: - dhr - Diabatic heating by radiation 215: - dhvd - Diabatic heating by vertical diffusion 216: - dhcc - Diabatic heating by cumulus convection 217: - dhlc - Diabatic heating large-scale condensation 218: - vdzw - Vertical diffusion of zonal wind 219: - vdmw - Vertical diffusion of meridional wind 220: - ewgd - East-West gravity wave drag tendency 221: - nsgd - North-South gravity wave drag tendency 222: - ctzw - Convective tendency of zonal wind 223: - ctmw - Convective tendency of meridional wind 224: - vdh - Vertical diffusion of humidity 225: - htcc - Humidity tendency by cumulus convection 226: - htlc - Humidity tendency by large-scale condensation 227: - crnh - Tendency due to removal of negative humidity 228: - tp - Total precipitation 229: - iews - Instantaneous eastward turbulent surface stress 230: - inss - Instantaneous northward turbulent surface stress 231: - ishf - Instantaneous surface sensible heat net flux 232: - ie - Instantaneous moisture flux 233: - asq - Apparent surface humidity 234: - lsrh - Logarithm of surface roughness length for heat (climatological) 235: - skt - Skin temperature 236: - stl4 - Soil temperature level 4 237: - swl4 - Soil wetness level 4 238: - tsn - Temperature of snow layer 239: - csf - Convective snowfall 240: - lsf - Large-scale snowfall 241: - acf - Accumulated cloud fraction tendency 242: - alw - Accumulated liquid water tendency 243: - fal - Forecast albedo 244: - fsr - Forecast surface roughness 245: - flsr - Forecast logarithm of surface roughness for heat 246: - clwc - Specific cloud liquid water content 247: - ciwc - Specific cloud ice water content 248: - cc - Fraction of cloud cover 249: - aiw - Accumulated ice water tendency 250: - ice - Ice age 251: - atte - Adiabatic tendency of temperature 252: - athe - Adiabatic tendency of humidity 253: - atze - Adiabatic tendency of zonal wind 254: - atmw - Adiabatic tendency of meridional wind 255: - _param_000255 - Indicates a missing value 928: - thk - thicknes 956: - thka - thicknes anomaly 999: - tc - tropical cyclone 3003: - ptend - Pressure tendency 3005: - icaht - ICAO Standard Atmosphere reference height 3008: - h - Geometrical height above ground 3009: - hstdv - Standard deviation of height 3012: - vptmp - Virtual potential temperature 3014: - papt - Pseudo-adiabatic potential temperature 3015: - tmax - Maximum temperature 3016: - tmin - Minimum temperature 3017: - dpt - Dew point temperature 3018: - depr - Dew point depression (or deficit) 3019: - lapr - Lapse rate 3020: - vis - Visibility 3021: - rdsp1 - Radar spectra (1) 3022: - rdsp2 - Radar spectra (2) 3023: - rdsp3 - Radar spectra (3) 3024: - pli - Parcel lifted index (to 500 hPa) 3025: - ta - Temperature anomaly 3026: - presa - Pressure anomaly 3027: - gpa - Geopotential height anomaly 3028: - wvsp1 - Wave spectra (1) 3029: - wvsp2 - Wave spectra (2) 3030: - wvsp3 - Wave spectra (3) 3031: - wdir - Wind direction 3037: - mntsf - Montgomery stream Function 3038: - sgcvv - Sigma coordinate vertical velocity 3041: - absv - Absolute vorticity 3042: - absd - Absolute divergence 3045: - vucsh - Vertical u-component shear 3046: - vvcsh - Vertical v-component shear 3047: - dirc - Direction of current 3048: - spc - Speed of current 3049: - ucurr - U-component of current 3050: - vcurr - V-component of current 3053: - mixr - Humidity mixing ratio 3054: - pwat - Precipitable water 3055: - vp - Vapour pressure 3056: - satd - Saturation deficit 3059: - prate - Precipitation rate 3060: - tstm - Thunderstorm probability 3062: - lsp - Large-scale precipitation 3063: - acpcp - Convective precipitation (water) 3064: - srweq - Snow fall rate water equivalent 3066: - sde - Snow depth 3067: - mld - Mixed layer depth 3068: - tthdp - Transient thermocline depth 3069: - mthd - Main thermocline depth 3070: - mtha - Main thermocline anomaly 3072: - ccc - Convective cloud cover 3073: - lcc - Low cloud cover 3074: - mcc - Medium cloud cover 3075: - hcc - High cloud cover 3077: - bli - Best lifted index (to 500 hPa) 3079: - lssf - Large scale snow 3080: - wtmp - Water temperature 3082: - dslm - Deviation of sea-level from mean 3086: - ssw - Soil moisture content 3088: - s - Salinity 3089: - den - Density 3091: - icec - Ice cover (1=ice, 0=no ice) 3092: - icetk - Ice thickness 3093: - diced - Direction of ice drift 3094: - siced - Speed of ice drift 3095: - uice - U-component of ice drift 3096: - vice - V-component of ice drift 3097: - iceg - Ice growth rate 3098: - iced - Ice divergence 3099: - snom - Snowmelt 3100: - swh - Significant height of combined wind waves and swell 3101: - mdww - Mean direction of wind waves 3102: - shww - Significant height of wind waves 3103: - mpww - Mean period of wind waves 3104: - swdir - Direction of swell waves 3105: - swell - Significant height of swell waves 3106: - swper - Mean period of swell waves 3107: - mdps - Primary wave direction 3108: - mpps - Primary wave mean period 3109: - dirsw - Secondary wave direction 3110: - swp - Secondary wave mean period 3111: - nswrs - Net short-wave radiation flux (surface) 3112: - nlwrs - Net long-wave radiation flux (surface) 3113: - nswrt - Net short-wave radiation flux(atmosph.top) 3114: - nlwrt - Net long-wave radiation flux(atmosph.top) 3115: - lwavr - Long wave radiation flux 3116: - swavr - Short wave radiation flux 3117: - grad - Global radiation flux 3119: - lwrad - Radiance (with respect to wave number) 3120: - swrad - Radiance (with respect to wave length) 3121: - lhf - Latent heat flux 3122: - shf - Sensible heat flux 3123: - bld - Boundary layer dissipation 3124: - uflx - Momentum flux, u-component 3125: - vflx - Momentum flux, v-component 3126: - wmixe - Wind mixing energy 3127: - imgd - Image data 129001: - strfgrd - Stream function gradient 129002: - vpotgrd - Velocity potential gradient 129003: - ptgrd - Potential temperature gradient 129004: - eqptgrd - Equivalent potential temperature gradient 129005: - septgrd - Saturated equivalent potential temperature gradient 129011: - udvwgrd - U component of divergent wind gradient 129012: - vdvwgrd - V component of divergent wind gradient 129013: - urtwgrd - U component of rotational wind gradient 129014: - vrtwgrd - V component of rotational wind gradient 129021: - uctpgrd - Unbalanced component of temperature gradient 129022: - uclngrd - Unbalanced component of logarithm of surface pressure gradient 129023: - ucdvgrd - Unbalanced component of divergence gradient 129024: - _param_129024 - Reserved for future unbalanced components 129025: - _param_129025 - Reserved for future unbalanced components 129026: - clgrd - Lake cover gradient 129027: - cvlgrd - Low vegetation cover gradient 129028: - cvhgrd - High vegetation cover gradient 129029: - tvlgrd - Type of low vegetation gradient 129030: - tvhgrd - Type of high vegetation gradient 129031: - sicgrd - Sea-ice cover gradient 129032: - asngrd - Snow albedo gradient 129033: - rsngrd - Snow density gradient 129034: - sstkgrd - Sea surface temperature gradient 129035: - istl1grd - Ice surface temperature layer 1 gradient 129036: - istl2grd - Ice surface temperature layer 2 gradient 129037: - istl3grd - Ice surface temperature layer 3 gradient 129038: - istl4grd - Ice surface temperature layer 4 gradient 129039: - swvl1grd - Volumetric soil water layer 1 gradient 129040: - swvl2grd - Volumetric soil water layer 2 gradient 129041: - swvl3grd - Volumetric soil water layer 3 gradient 129042: - swvl4grd - Volumetric soil water layer 4 gradient 129043: - sltgrd - Soil type gradient 129044: - esgrd - Snow evaporation gradient 129045: - smltgrd - Snowmelt gradient 129046: - sdurgrd - Solar duration gradient 129047: - dsrpgrd - Direct solar radiation gradient 129048: - magssgrd - Magnitude of turbulent surface stress gradient 129049: - 10fggrd - 10 metre wind gust gradient 129050: - lspfgrd - Large-scale precipitation fraction gradient 129051: - mx2t24grd - Maximum 2 metre temperature gradient 129052: - mn2t24grd - Minimum 2 metre temperature gradient 129053: - montgrd - Montgomery potential gradient 129054: - presgrd - Pressure gradient 129055: - mean2t24grd - Mean 2 metre temperature in the last 24 hours gradient 129056: - mn2d24grd - Mean 2 metre dewpoint temperature in the last 24 hours gradient 129057: - uvbgrd - Downward UV radiation at the surface gradient 129058: - pargrd - Photosynthetically active radiation at the surface gradient 129059: - capegrd - Convective available potential energy gradient 129060: - pvgrd - Potential vorticity gradient 129061: - tpogrd - Total precipitation from observations gradient 129062: - obctgrd - Observation count gradient 129063: - _param_129063 - Start time for skin temperature difference 129064: - _param_129064 - Finish time for skin temperature difference 129065: - _param_129065 - Skin temperature difference 129066: - _param_129066 - Leaf area index, low vegetation 129067: - _param_129067 - Leaf area index, high vegetation 129070: - _param_129070 - Biome cover, low vegetation 129071: - _param_129071 - Biome cover, high vegetation 129078: - _param_129078 - Total column liquid water 129079: - _param_129079 - Total column ice water 129080: - _param_129080 - Experimental product 129081: - _param_129081 - Experimental product 129082: - _param_129082 - Experimental product 129083: - _param_129083 - Experimental product 129084: - _param_129084 - Experimental product 129085: - _param_129085 - Experimental product 129086: - _param_129086 - Experimental product 129087: - _param_129087 - Experimental product 129088: - _param_129088 - Experimental product 129089: - _param_129089 - Experimental product 129090: - _param_129090 - Experimental product 129091: - _param_129091 - Experimental product 129092: - _param_129092 - Experimental product 129093: - _param_129093 - Experimental product 129094: - _param_129094 - Experimental product 129095: - _param_129095 - Experimental product 129096: - _param_129096 - Experimental product 129097: - _param_129097 - Experimental product 129098: - _param_129098 - Experimental product 129099: - _param_129099 - Experimental product 129100: - _param_129100 - Experimental product 129101: - _param_129101 - Experimental product 129102: - _param_129102 - Experimental product 129103: - _param_129103 - Experimental product 129104: - _param_129104 - Experimental product 129105: - _param_129105 - Experimental product 129106: - _param_129106 - Experimental product 129107: - _param_129107 - Experimental product 129108: - _param_129108 - Experimental product 129109: - _param_129109 - Experimental product 129110: - _param_129110 - Experimental product 129111: - _param_129111 - Experimental product 129112: - _param_129112 - Experimental product 129113: - _param_129113 - Experimental product 129114: - _param_129114 - Experimental product 129115: - _param_129115 - Experimental product 129116: - _param_129116 - Experimental product 129117: - _param_129117 - Experimental product 129118: - _param_129118 - Experimental product 129119: - _param_129119 - Experimental product 129120: - _param_129120 - Experimental product 129121: - mx2t6grd - Maximum temperature at 2 metres gradient 129122: - mn2t6grd - Minimum temperature at 2 metres gradient 129123: - 10fg6grd - 10 metre wind gust in the last 6 hours gradient 129125: - _param_129125 - Vertically integrated total energy 129126: - _param_129126 - Generic parameter for sensitive area prediction 129127: - atgrd - Atmospheric tide gradient 129128: - bvgrd - Budget values gradient 129129: - zgrd - Geopotential gradient 129130: - tgrd - Temperature gradient 129131: - ugrd - U component of wind gradient 129132: - vgrd - V component of wind gradient 129133: - qgrd - Specific humidity gradient 129134: - spgrd - Surface pressure gradient 129135: - wgrd - vertical velocity (pressure) gradient 129136: - tcwgrd - Total column water gradient 129137: - tcwvgrd - Total column water vapour gradient 129138: - vogrd - Vorticity (relative) gradient 129139: - stl1grd - Soil temperature level 1 gradient 129140: - swl1grd - Soil wetness level 1 gradient 129141: - sdgrd - Snow depth gradient 129142: - lspgrd - Stratiform precipitation (Large-scale precipitation) gradient 129143: - cpgrd - Convective precipitation gradient 129144: - sfgrd - Snowfall (convective + stratiform) gradient 129145: - bldgrd - Boundary layer dissipation gradient 129146: - sshfgrd - Surface sensible heat flux gradient 129147: - slhfgrd - Surface latent heat flux gradient 129148: - chnkgrd - Charnock gradient 129149: - snrgrd - Surface net radiation gradient 129150: - tnrgrd - Top net radiation gradient 129151: - mslgrd - Mean sea level pressure gradient 129152: - lnspgrd - Logarithm of surface pressure gradient 129153: - swhrgrd - Short-wave heating rate gradient 129154: - lwhrgrd - Long-wave heating rate gradient 129155: - dgrd - Divergence gradient 129156: - ghgrd - Height gradient 129157: - rgrd - Relative humidity gradient 129158: - tspgrd - Tendency of surface pressure gradient 129159: - blhgrd - Boundary layer height gradient 129160: - sdorgrd - Standard deviation of orography gradient 129161: - isorgrd - Anisotropy of sub-gridscale orography gradient 129162: - anorgrd - Angle of sub-gridscale orography gradient 129163: - slorgrd - Slope of sub-gridscale orography gradient 129164: - tccgrd - Total cloud cover gradient 129165: - 10ugrd - 10 metre U wind component gradient 129166: - 10vgrd - 10 metre V wind component gradient 129167: - 2tgrd - 2 metre temperature gradient 129168: - 2dgrd - 2 metre dewpoint temperature gradient 129169: - ssrdgrd - Surface solar radiation downwards gradient 129170: - stl2grd - Soil temperature level 2 gradient 129171: - swl2grd - Soil wetness level 2 gradient 129172: - lsmgrd - Land-sea mask gradient 129173: - srgrd - Surface roughness gradient 129174: - algrd - Albedo gradient 129175: - strdgrd - Surface thermal radiation downwards gradient 129176: - ssrgrd - Surface net solar radiation gradient 129177: - strgrd - Surface net thermal radiation gradient 129178: - tsrgrd - Top net solar radiation gradient 129179: - ttrgrd - Top net thermal radiation gradient 129180: - ewssgrd - East-West surface stress gradient 129181: - nsssgrd - North-South surface stress gradient 129182: - egrd - Evaporation gradient 129183: - stl3grd - Soil temperature level 3 gradient 129184: - swl3grd - Soil wetness level 3 gradient 129185: - cccgrd - Convective cloud cover gradient 129186: - lccgrd - Low cloud cover gradient 129187: - mccgrd - Medium cloud cover gradient 129188: - hccgrd - High cloud cover gradient 129189: - sundgrd - Sunshine duration gradient 129190: - ewovgrd - East-West component of sub-gridscale orographic variance gradient 129191: - nsovgrd - North-South component of sub-gridscale orographic variance gradient 129192: - nwovgrd - North-West/South-East component of sub-gridscale orographic variance gradient 129193: - neovgrd - North-East/South-West component of sub-gridscale orographic variance gradient 129194: - btmpgrd - Brightness temperature gradient 129195: - lgwsgrd - Longitudinal component of gravity wave stress gradient 129196: - mgwsgrd - Meridional component of gravity wave stress gradient 129197: - gwdgrd - Gravity wave dissipation gradient 129198: - srcgrd - Skin reservoir content gradient 129199: - veggrd - Vegetation fraction gradient 129200: - vsogrd - Variance of sub-gridscale orography gradient 129201: - mx2tgrd - Maximum temperature at 2 metres since previous post-processing gradient 129202: - mn2tgrd - Minimum temperature at 2 metres since previous post-processing gradient 129203: - o3grd - Ozone mass mixing ratio gradient 129204: - pawgrd - Precipitation analysis weights gradient 129205: - rogrd - Runoff gradient 129206: - tco3grd - Total column ozone gradient 129207: - 10sigrd - 10 metre wind speed gradient 129208: - tsrcgrd - Top net solar radiation, clear sky gradient 129209: - ttrcgrd - Top net thermal radiation, clear sky gradient 129210: - ssrcgrd - Surface net solar radiation, clear sky gradient 129211: - strcgrd - Surface net thermal radiation, clear sky gradient 129212: - tisrgrd - TOA incident solar radiation gradient 129214: - dhrgrd - Diabatic heating by radiation gradient 129215: - dhvdgrd - Diabatic heating by vertical diffusion gradient 129216: - dhccgrd - Diabatic heating by cumulus convection gradient 129217: - dhlcgrd - Diabatic heating large-scale condensation gradient 129218: - vdzwgrd - Vertical diffusion of zonal wind gradient 129219: - vdmwgrd - Vertical diffusion of meridional wind gradient 129220: - ewgdgrd - East-West gravity wave drag tendency gradient 129221: - nsgdgrd - North-South gravity wave drag tendency gradient 129222: - ctzwgrd - Convective tendency of zonal wind gradient 129223: - ctmwgrd - Convective tendency of meridional wind gradient 129224: - vdhgrd - Vertical diffusion of humidity gradient 129225: - htccgrd - Humidity tendency by cumulus convection gradient 129226: - htlcgrd - Humidity tendency by large-scale condensation gradient 129227: - crnhgrd - Change from removal of negative humidity gradient 129228: - tpgrd - Total precipitation gradient 129229: - iewsgrd - Instantaneous X surface stress gradient 129230: - inssgrd - Instantaneous Y surface stress gradient 129231: - ishfgrd - Instantaneous surface heat flux gradient 129232: - iegrd - Instantaneous moisture flux gradient 129233: - asqgrd - Apparent surface humidity gradient 129234: - lsrhgrd - Logarithm of surface roughness length for heat gradient 129235: - sktgrd - Skin temperature gradient 129236: - stl4grd - Soil temperature level 4 gradient 129237: - swl4grd - Soil wetness level 4 gradient 129238: - tsngrd - Temperature of snow layer gradient 129239: - csfgrd - Convective snowfall gradient 129240: - lsfgrd - Large scale snowfall gradient 129241: - acfgrd - Accumulated cloud fraction tendency gradient 129242: - alwgrd - Accumulated liquid water tendency gradient 129243: - falgrd - Forecast albedo gradient 129244: - fsrgrd - Forecast surface roughness gradient 129245: - flsrgrd - Forecast logarithm of surface roughness for heat gradient 129246: - clwcgrd - Specific cloud liquid water content gradient 129247: - ciwcgrd - Specific cloud ice water content gradient 129248: - ccgrd - Cloud cover gradient 129249: - aiwgrd - Accumulated ice water tendency gradient 129250: - icegrd - Ice age gradient 129251: - attegrd - Adiabatic tendency of temperature gradient 129252: - athegrd - Adiabatic tendency of humidity gradient 129253: - atzegrd - Adiabatic tendency of zonal wind gradient 129254: - atmwgrd - Adiabatic tendency of meridional wind gradient 129255: - _param_129255 - Indicates a missing value 130208: - tsru - Top solar radiation upward 130209: - ttru - Top thermal radiation upward 130210: - tsuc - Top solar radiation upward, clear sky 130211: - ttuc - Top thermal radiation upward, clear sky 130212: - clw - Cloud liquid water 130213: - cf - Cloud fraction 130214: - dhr - Diabatic heating by radiation 130215: - dhvd - Diabatic heating by vertical diffusion 130216: - dhcc - Diabatic heating by cumulus convection 130217: - dhlc - Diabatic heating by large-scale condensation 130218: - vdzw - Vertical diffusion of zonal wind 130219: - vdmw - Vertical diffusion of meridional wind 130220: - ewgd - East-West gravity wave drag 130221: - nsgd - North-South gravity wave drag 130224: - vdh - Vertical diffusion of humidity 130225: - htcc - Humidity tendency by cumulus convection 130226: - htlc - Humidity tendency by large-scale condensation 130228: - att - Adiabatic tendency of temperature 130229: - ath - Adiabatic tendency of humidity 130230: - atzw - Adiabatic tendency of zonal wind 130231: - atmwax - Adiabatic tendency of meridional wind 130232: - mvv - Mean vertical velocity 131001: - 2tag2 - 2m temperature anomaly of at least +2K 131002: - 2tag1 - 2m temperature anomaly of at least +1K 131003: - 2tag0 - 2m temperature anomaly of at least 0K 131004: - 2talm1 - 2m temperature anomaly of at most -1K 131005: - 2talm2 - 2m temperature anomaly of at most -2K 131006: - tpag20 - Total precipitation anomaly of at least 20 mm 131007: - tpag10 - Total precipitation anomaly of at least 10 mm 131008: - tpag0 - Total precipitation anomaly of at least 0 mm 131009: - stag0 - Surface temperature anomaly of at least 0K 131010: - mslag0 - Mean sea level pressure anomaly of at least 0 Pa 131015: - h0dip - Height of 0 degree isotherm probability 131016: - hslp - Height of snowfall limit probability 131017: - saip - Showalter index probability 131018: - whip - Whiting index probability 131020: - talm2 - Temperature anomaly less than -2 K - temperature anomaly of at most -2 k 131021: - tag2 - Temperature anomaly of at least +2 K - temperature anomaly of at least 2 k 131022: - talm8 - Temperature anomaly less than -8 K - temperature anomaly of at most -8 k 131023: - talm4 - Temperature anomaly less than -4 K - temperature anomaly of at most -4 k 131024: - tag4 - Temperature anomaly greater than +4 K - temperature anomaly greater than 4 k - temperature anomaly of at least 4 k 131025: - tag8 - Temperature anomaly greater than +8 K - temperature anomaly greater than 8 k - temperature anomaly of at least 8 k 131049: - 10gp - 10 metre wind gust probability 131059: - capep - Convective available potential energy probability 131060: - tpg1 - Total precipitation of at least 1 mm 131061: - tpg5 - Total precipitation of at least 5 mm 131062: - tpg10 - Total precipitation of at least 10 mm 131063: - tpg20 - Total precipitation of at least 20 mm 131064: - tpl01 - Total precipitation less than 0.1 mm 131065: - tprl1 - Total precipitation rate less than 1 mm/day 131066: - tprg3 - Total precipitation rate of at least 3 mm/day 131067: - tprg5 - Total precipitation rate of at least 5 mm/day 131068: - 10spg10 - 10 metre Wind speed of at least 10 m/s 131069: - 10spg15 - 10 metre Wind speed of at least 15 m/s 131070: - 10fgg15 - 10 metre wind gust of at least 15 m/s 131071: - 10fgg20 - 10 metre wind gust of at least 20 m/s 131072: - 10fgg25 - 10 metre wind gust of at least 25 m/s 131073: - 2tl273 - 2 metre temperature less than 273.15 K 131074: - swhg2 - Significant wave height of at least 2 m 131075: - swhg4 - Significant wave height of at least 4 m 131076: - swhg6 - Significant wave height of at least 6 m 131077: - swhg8 - Significant wave height of at least 8 m 131078: - mwpg8 - Mean wave period of at least 8 s 131079: - mwpg10 - Mean wave period of at least 10 s 131080: - mwpg12 - Mean wave period of at least 12 s 131081: - mwpg15 - Mean wave period of at least 15 s 131082: - tpg40 - Total precipitation of at least 40 mm 131083: - tpg60 - Total precipitation of at least 60 mm 131084: - tpg80 - Total precipitation of at least 80 mm 131085: - tpg100 - Total precipitation of at least 100 mm 131086: - tpg150 - Total precipitation of at least 150 mm 131087: - tpg200 - Total precipitation of at least 200 mm 131088: - tpg300 - Total precipitation of at least 300 mm 131089: - pts - Probability of a tropical storm 131090: - ph - Probability of a hurricane 131091: - ptd - Probability of a tropical depression 131092: - cpts - Climatological probability of a tropical storm 131093: - cph - Climatological probability of a hurricane 131094: - cptd - Climatological probability of a tropical depression 131095: - pats - Probability anomaly of a tropical storm 131096: - pah - Probability anomaly of a hurricane 131097: - patd - Probability anomaly of a tropical depression 131098: - tpg25 - Total precipitation of at least 25 mm 131099: - tpg50 - Total precipitation of at least 50 mm 131100: - 10fgg10 - 10 metre wind gust of at least 10 m/s 131129: - zp - Geopotential probability 131130: - tap - Temperature anomaly probability 131139: - stl1p - Soil temperature level 1 probability 131144: - sfp - Snowfall (convective + stratiform) probability 131151: - mslpp - Mean sea level pressure probability 131164: - tccp - Total cloud cover probability 131165: - 10sp - 10 metre speed probability 131167: - 2tp - 2 metre temperature probability 131201: - mx2tp - Maximum 2 metre temperature probability 131202: - mn2tp - Minimum 2 metre temperature probability 131228: - tpp - Total precipitation probability 131229: - swhp - Significant wave height probability 131232: - mwpp - Mean wave period probability 131255: - _param_131255 - Indicates a missing value 131256: - 10cogug25 - 10 metre convective gust of at least 25 m/s 132044: - capesi - Convective available potential energy shear index 132045: - wvfi - Water vapour flux index 132049: - 10fgi - 10 metre wind gust index - 10gi 132059: - capei - Convective available potential energy index 132144: - sfi - Snowfall index 132165: - 10wsi - 10 metre speed index 132167: - 2ti - 2 metre temperature index 132201: - mx2ti - Maximum temperature at 2 metres index 132202: - mn2ti - Minimum temperature at 2 metres index 132216: - maxswhi - Maximum of significant wave height index 132228: - tpi - Total precipitation index 133001: - 2tplm10 - 2m temperature probability less than -10 C 133002: - 2tplm5 - 2m temperature probability less than -5 C 133003: - 2tpl0 - 2m temperature probability less than 0 C 133004: - 2tpl5 - 2m temperature probability less than 5 C 133005: - 2tpl10 - 2m temperature probability less than 10 C 133006: - 2tpg25 - 2m temperature probability greater than 25 C 133007: - 2tpg30 - 2m temperature probability greater than 30 C 133008: - 2tpg35 - 2m temperature probability greater than 35 C 133009: - 2tpg40 - 2m temperature probability greater than 40 C 133010: - 2tpg45 - 2m temperature probability greater than 45 C 133011: - mn2tplm10 - Minimum 2 metre temperature probability less than -10 C 133012: - mn2tplm5 - Minimum 2 metre temperature probability less than -5 C 133013: - mn2tpl0 - Minimum 2 metre temperature probability less than 0 C 133014: - mn2tpl5 - Minimum 2 metre temperature probability less than 5 C 133015: - mn2tpl10 - Minimum 2 metre temperature probability less than 10 C 133016: - mx2tpg25 - Maximum 2 metre temperature probability greater than 25 C 133017: - mx2tpg30 - Maximum 2 metre temperature probability greater than 30 C 133018: - mx2tpg35 - Maximum 2 metre temperature probability greater than 35 C 133019: - mx2tpg40 - Maximum 2 metre temperature probability greater than 40 C 133020: - mx2tpg45 - Maximum 2 metre temperature probability greater than 45 C 133021: - 10spg10 - 10 metre wind speed probability of at least 10 m/s 133022: - 10spg15 - 10 metre wind speed probability of at least 15 m/s 133023: - 10spg20 - 10 metre wind speed probability of at least 20 m/s 133024: - 10spg35 - 10 metre wind speed probability of at least 35 m/s 133025: - 10spg50 - 10 metre wind speed probability of at least 50 m/s 133026: - 10gpg20 - 10 metre wind gust probability of at least 20 m/s 133027: - 10gpg35 - 10 metre wind gust probability of at least 35 m/s 133028: - 10gpg50 - 10 metre wind gust probability of at least 50 m/s 133029: - 10gpg75 - 10 metre wind gust probability of at least 75 m/s 133030: - 10gpg100 - 10 metre wind gust probability of at least 100 m/s 133031: - tppg1 - Total precipitation probability of at least 1 mm 133032: - tppg5 - Total precipitation probability of at least 5 mm 133033: - tppg10 - Total precipitation probability of at least 10 mm 133034: - tppg20 - Total precipitation probability of at least 20 mm 133035: - tppg40 - Total precipitation probability of at least 40 mm 133036: - tppg60 - Total precipitation probability of at least 60 mm 133037: - tppg80 - Total precipitation probability of at least 80 mm 133038: - tppg100 - Total precipitation probability of at least 100 mm 133039: - tppg150 - Total precipitation probability of at least 150 mm 133040: - tppg200 - Total precipitation probability of at least 200 mm 133041: - tppg300 - Total precipitation probability of at least 300 mm 133042: - sfpg1 - Snowfall probability of at least 1 mm 133043: - sfpg5 - Snowfall probability of at least 5 mm 133044: - sfpg10 - Snowfall probability of at least 10 mm 133045: - sfpg20 - Snowfall probability of at least 20 mm 133046: - sfpg40 - Snowfall probability of at least 40 mm 133047: - sfpg60 - Snowfall probability of at least 60 mm 133048: - sfpg80 - Snowfall probability of at least 80 mm 133049: - sfpg100 - Snowfall probability of at least 100 mm 133050: - sfpg150 - Snowfall probability of at least 150 mm 133051: - sfpg200 - Snowfall probability of at least 200 mm 133052: - sfpg300 - Snowfall probability of at least 300 mm 133053: - tccpg10 - Total Cloud Cover probability greater than 10% 133054: - tccpg20 - Total Cloud Cover probability greater than 20% 133055: - tccpg30 - Total Cloud Cover probability greater than 30% 133056: - tccpg40 - Total Cloud Cover probability greater than 40% 133057: - tccpg50 - Total Cloud Cover probability greater than 50% 133058: - tccpg60 - Total Cloud Cover probability greater than 60% 133059: - tccpg70 - Total Cloud Cover probability greater than 70% 133060: - tccpg80 - Total Cloud Cover probability greater than 80% 133061: - tccpg90 - Total Cloud Cover probability greater than 90% 133062: - tccpg99 - Total Cloud Cover probability greater than 99% 133063: - hccpg10 - High Cloud Cover probability greater than 10% 133064: - hccpg20 - High Cloud Cover probability greater than 20% 133065: - hccpg30 - High Cloud Cover probability greater than 30% 133066: - hccpg40 - High Cloud Cover probability greater than 40% 133067: - hccpg50 - High Cloud Cover probability greater than 50% 133068: - hccpg60 - High Cloud Cover probability greater than 60% 133069: - hccpg70 - High Cloud Cover probability greater than 70% 133070: - hccpg80 - High Cloud Cover probability greater than 80% 133071: - hccpg90 - High Cloud Cover probability greater than 90% 133072: - hccpg99 - High Cloud Cover probability greater than 99% 133073: - mccpg10 - Medium Cloud Cover probability greater than 10% 133074: - mccpg20 - Medium Cloud Cover probability greater than 20% 133075: - mccpg30 - Medium Cloud Cover probability greater than 30% 133076: - mccpg40 - Medium Cloud Cover probability greater than 40% 133077: - mccpg50 - Medium Cloud Cover probability greater than 50% 133078: - mccpg60 - Medium Cloud Cover probability greater than 60% 133079: - mccpg70 - Medium Cloud Cover probability greater than 70% 133080: - mccpg80 - Medium Cloud Cover probability greater than 80% 133081: - mccpg90 - Medium Cloud Cover probability greater than 90% 133082: - mccpg99 - Medium Cloud Cover probability greater than 99% 133083: - lccpg10 - Low Cloud Cover probability greater than 10% 133084: - lccpg20 - Low Cloud Cover probability greater than 20% 133085: - lccpg30 - Low Cloud Cover probability greater than 30% 133086: - lccpg40 - Low Cloud Cover probability greater than 40% 133087: - lccpg50 - Low Cloud Cover probability greater than 50% 133088: - lccpg60 - Low Cloud Cover probability greater than 60% 133089: - lccpg70 - Low Cloud Cover probability greater than 70% 133090: - lccpg80 - Low Cloud Cover probability greater than 80% 133091: - lccpg90 - Low Cloud Cover probability greater than 90% 133092: - lccpg99 - Low Cloud Cover probability greater than 99% 133093: - ptsa_gt_1stdev - Probability of temperature standardized anomaly greater than 1 standard deviation 133094: - ptsa_gt_1p5stdev - Probability of temperature standardized anomaly greater than 1.5 standard deviation 133095: - ptsa_gt_2stdev - Probability of temperature standardized anomaly greater than 2 standard deviation 133096: - ptsa_lt_1stdev - Probability of temperature standardized anomaly less than -1 standard deviation 133097: - ptsa_lt_1p5stdev - Probability of temperature standardized anomaly less than -1.5 standard deviation 133098: - ptsa_lt_2stdev - Probability of temperature standardized anomaly less than -2 standard deviation 140080: - wx1 - Wave experimental parameter 1 140081: - wx2 - Wave experimental parameter 2 140082: - wx3 - Wave experimental parameter 3 140083: - wx4 - Wave experimental parameter 4 140084: - wx5 - Wave experimental parameter 5 140098: - weta - Wave induced mean sea level correction 140099: - wraf - Ratio of wave angular and frequency width 140100: - wnslc - Number of events in freak waves statistics 140101: - utaua - U-component of atmospheric surface momentum flux 140102: - vtaua - V-component of atmospheric surface momentum flux 140103: - utauo - U-component of surface momentum flux into ocean 140104: - vtauo - V-component of surface momentum flux into ocean 140105: - wphio - Wave turbulent energy flux into ocean 140106: - wdw1 - Wave directional width of first swell partition 140107: - wfw1 - Wave frequency width of first swell partition 140108: - wdw2 - Wave directional width of second swell partition 140109: - wfw2 - Wave frequency width of second swell partition 140110: - wdw3 - Wave directional width of third swell partition 140111: - wfw3 - Wave frequency width of third swell partition 140112: - wefxm - Wave energy flux magnitude 140113: - wefxd - Wave energy flux mean direction 140114: - h1012 - Significant wave height of all waves with periods within the inclusive range from 10 to 12 seconds 140115: - h1214 - Significant wave height of all waves with periods within the inclusive range from 12 to 14 seconds 140116: - h1417 - Significant wave height of all waves with periods within the inclusive range from 14 to 17 seconds 140117: - h1721 - Significant wave height of all waves with periods within the inclusive range from 17 to 21 seconds 140118: - h2125 - Significant wave height of all waves with periods within the inclusive range from 21 to 25 seconds 140119: - h2530 - Significant wave height of all waves with periods within the inclusive range from 25 to 30 seconds 140120: - sh10 - Significant wave height of all waves with period larger than 10s 140121: - swh1 - Significant wave height of first swell partition 140122: - mwd1 - Mean wave direction of first swell partition 140123: - mwp1 - Mean wave period of first swell partition 140124: - swh2 - Significant wave height of second swell partition 140125: - mwd2 - Mean wave direction of second swell partition 140126: - mwp2 - Mean wave period of second swell partition 140127: - swh3 - Significant wave height of third swell partition 140128: - mwd3 - Mean wave direction of third swell partition 140129: - mwp3 - Mean wave period of third swell partition 140131: - tdcmax - Time domain maximum individual crest height 140132: - tdhmax - Time domain maximum individual wave height 140133: - stcmax - Space time maximum individual crest height 140134: - sthmax - Space time maximum individual wave height 140135: - pp1dw - Peak wave period of wind waves 140136: - pp1ds - Peak wave period of total swell 140137: - pp1d1 - Peak wave period of first swell partition 140138: - pp1d2 - Peak wave period of second swell partition 140139: - pp1d3 - Peak wave period of third swell partition 140140: - pwd - Peak wave direction (total) 140141: - pwdw - Peak wave direction of wind waves 140142: - pwds - Peak wave direction of total swell 140143: - pwd1 - Peak wave direction of first swell partition 140144: - pwd2 - Peak wave direction of second swell partition 140145: - pwd3 - Peak wave direction of third swell partition 140146: - wcfr - Whitecap fraction 140147: - bfi2d - Benjamin-Feir index 2D 140148: - ctc - Crest-trough correlation 140149: - xwrs - X component of the wave radiative stress to sea ice 140150: - ywrs - Y component of the wave radiative stress to sea ice 140200: - maxswh - Maximum of significant wave height 140207: - wss - Wave Spectral Skewness 140208: - wstar - Free convective velocity over the oceans 140209: - rhoao - Air density over the oceans 140210: - mswsi - Mean square wave strain in sea ice 140211: - phiaw - Normalized energy flux into waves 140212: - phioc - Normalized energy flux into ocean 140213: - tla - Turbulent Langmuir number 140214: - tauoc - Normalized stress into ocean 140215: - ust - U-component surface stokes drift 140216: - vst - V-component surface stokes drift 140217: - tmax - Period corresponding to maximum individual wave height 140218: - hmax - Envelop-maximum individual wave height 140219: - wmb - Model bathymetry 140220: - mp1 - Mean wave period based on first moment 140221: - mp2 - Mean zero-crossing wave period 140222: - wdw - Wave spectral directional width 140223: - p1ww - Mean wave period based on first moment for wind waves 140224: - p2ww - Mean wave period based on second moment for wind waves 140225: - dwww - Wave spectral directional width for wind waves 140226: - p1ps - Mean wave period based on first moment for swell 140227: - p2ps - Mean wave period based on second moment for swell 140228: - dwps - Wave spectral directional width for swell 140229: - swh - Significant height of combined wind waves and swell 140230: - mwd - Mean wave direction 140231: - pp1d - Peak wave period 140232: - mwp - Mean wave period 140233: - cdww - Coefficient of drag with waves 140234: - shww - Significant height of wind waves 140235: - mdww - Mean direction of wind waves 140236: - mpww - Mean period of wind waves 140237: - shts - Significant height of total swell 140238: - mdts - Mean direction of total swell 140239: - mpts - Mean period of total swell 140240: - sdhs - Standard deviation wave height 140241: - mu10 - Mean of 10 metre wind speed 140242: - mdwi - Mean wind direction 140243: - sdu - Standard deviation of 10 metre wind speed 140244: - msqs - Mean square slope of waves 140245: - wind - 10 metre wind speed 140246: - awh - Altimeter wave height 140247: - acwh - Altimeter corrected wave height 140248: - arrc - Altimeter range relative correction 140249: - dwi - 10 metre wind direction 140250: - 2dsp - 2D wave spectra (multiple) 140251: - 2dfd - 2D wave spectra (single) 140252: - wsk - Wave spectral kurtosis 140253: - bfi - Benjamin-Feir index 140254: - wsp - Wave spectral peakedness 140255: - _param_140255 - Indicates a missing value 141098: - avg_weta - Time-mean wave induced mean sea level correction 141099: - avg_wraf - Time-mean ratio of wave angular and frequency width 141100: - avg_wnslc - Time-mean number of events in freak waves statistics 141101: - avg_utaua - Time-mean U-component of atmospheric surface momentum flux 141102: - avg_vtaua - Time-mean V-component of atmospheric surface momentum flux 141103: - avg_utauo - Time-mean U-component of surface momentum flux into ocean 141104: - avg_vtauo - Time-mean V-component of surface momentum flux into ocean 141105: - avg_wphio - Time-mean wave turbulent energy flux into ocean 141106: - avg_wdw1 - Time-mean wave directional width of first swell partition 141107: - avg_wfw1 - Time-mean wave frequency width of first swell partition 141108: - avg_wdw2 - Time-mean wave directional width of second swell partition 141109: - avg_wfw2 - Time-mean wave frequency width of second swell partition 141110: - avg_wdw3 - Time-mean wave directional width of third swell partition 141111: - avg_wfw3 - Time-mean wave frequency width of third swell partition 141112: - avg_wefxm - Time-mean wave energy flux magnitude 141113: - avg_wefxd - Time-mean wave energy flux mean direction 141114: - avg_h1012 - Time-mean significant wave height of all waves with periods within the inclusive range from 10 to 12 seconds 141115: - avg_h1214 - Time-mean significant wave height of all waves with periods within the inclusive range from 12 to 14 seconds 141116: - avg_h1417 - Time-mean significant wave height of all waves with periods within the inclusive range from 14 to 17 seconds 141117: - avg_h1721 - Time-mean significant wave height of all waves with periods within the inclusive range from 17 to 21 seconds 141118: - avg_h2125 - Time-mean significant wave height of all waves with periods within the inclusive range from 21 to 25 seconds 141119: - avg_h2530 - Time-mean significant wave height of all waves with periods within the inclusive range from 25 to 30 seconds 141120: - avg_sh10 - Time-mean significant wave height of all waves with period larger than 10s 141121: - avg_swh1 - Time-mean significant wave height of first swell partition 141122: - avg_mwd1 - Time-mean mean wave direction of first swell partition 141123: - avg_mwp1 - Time-mean mean wave period of first swell partition 141124: - avg_swh2 - Time-mean significant wave height of second swell partition 141125: - avg_mwd2 - Time-mean mean wave direction of second swell partition 141126: - avg_mwp2 - Time-mean mean wave period of second swell partition 141127: - avg_swh3 - Time-mean significant wave height of third swell partition 141128: - avg_mwd3 - Time-mean mean wave direction of third swell partition 141129: - avg_mwp3 - Time-mean mean wave period of third swell partition 141131: - avg_tdcmax - Time-mean time domain maximum individual crest height 141132: - avg_tdhmax - Time-mean time domain maximum individual wave height 141133: - avg_stcmax - Time-mean space time maximum individual crest height 141134: - avg_sthmax - Time-mean space time maximum individual wave height 141135: - avg_pp1dw - Time-mean peak wave period of wind waves 141136: - avg_pp1ds - Time-mean peak wave period of total swell 141137: - avg_pp1d1 - Time-mean peak wave period of first swell partition 141138: - avg_pp1d2 - Time-mean peak wave period of second swell partition 141139: - avg_pp1d3 - Time-mean peak wave period of third swell partition 141140: - avg_pwd - Time-mean peak wave direction (total) 141141: - avg_pwdw - Time-mean peak wave direction of wind waves 141142: - avg_pwds - Time-mean peak wave direction of total swell 141143: - avg_pwd1 - Time-mean peak wave direction of first swell partition 141144: - avg_pwd2 - Time-mean peak wave direction of second swell partition 141145: - avg_pwd3 - Time-mean peak wave direction of third swell partition 141146: - avg_wcfr - Time-mean whitecap fraction 141147: - avg_bfi2d - Time-mean Benjamin-Feir index 2D 141148: - avg_ctc - Time-mean crest-trough correlation 141149: - avg_xwrs - Time-mean X component of the wave radiative stress to sea ice 141150: - avg_ywrs - Time-mean Y component of the wave radiative stress to sea ice 141207: - avg_wss - Time-mean wave Spectral Skewness 141208: - avg_wstar - Time-mean free convective velocity over the oceans 141209: - avg_rhoao - Time-mean air density over the oceans 141211: - avg_phiaw - Time-mean normalized energy flux into waves 141212: - avg_phioc - Time-mean normalized energy flux into ocean 141214: - avg_tauoc - Time-mean normalized stress into ocean 141215: - avg_ust - Time-mean U-component surface stokes drift 141216: - avg_vst - Time-mean V-component surface stokes drift 141217: - avg_tmax - Time-mean period corresponding to maximum individual wave height 141218: - avg_hmax - Time-mean envelop-maximum individual wave height 141220: - avg_mp1 - Time-mean mean wave period based on first moment 141221: - avg_mp2 - Time-mean mean zero-crossing wave period 141222: - avg_wdw - Time-mean wave spectral directional width 141223: - avg_p1ww - Time-mean mean wave period based on first moment for wind waves 141224: - avg_p2ww - Time-mean mean wave period based on second moment for wind waves 141225: - avg_dwww - Time-mean wave spectral directional width for wind waves 141226: - avg_p1ps - Time-mean mean wave period based on first moment for swell 141227: - avg_p2ps - Time-mean mean wave period based on second moment for swell 141228: - avg_dwps - Time-mean wave spectral directional width for swell 141229: - avg_swh - Time-mean significant height of combined wind waves and swell 141230: - avg_mwd - Time-mean mean wave direction 141231: - avg_pp1d - Time-mean peak wave period 141232: - avg_mwp - Time-mean mean wave period 141233: - avg_cdww - Time-mean coefficient of drag with waves 141234: - avg_shww - Time-mean significant height of wind waves 141235: - avg_mdww - Time-mean mean direction of wind waves 141236: - avg_mpww - Time-mean mean period of wind waves 141237: - avg_shts - Time-mean significant height of total swell 141238: - avg_mdts - Time-mean mean direction of total swell 141239: - avg_mpts - Time-mean mean period of total swell 141244: - avg_msqs - Time-mean mean square slope of waves 141245: - avg_wind - Time-mean 10 metre wind speed 141249: - avg_dwi - Time-mean 10 metre wind direction 141252: - avg_wsk - Time-mean wave spectral kurtosis 141253: - avg_bfi - Time-mean benjamin-Feir index 141254: - avg_wsp - Time-mean wave spectral peakedness 143098: - max_weta - Time-maximum wave induced mean sea level correction 143099: - max_wraf - Time-maximum ratio of wave angular and frequency width 143100: - max_wnslc - Time-maximum number of events in freak waves statistics 143101: - max_utaua - Time-maximum U-component of atmospheric surface momentum flux 143102: - max_vtaua - Time-maximum V-component of atmospheric surface momentum flux 143103: - max_utauo - Time-maximum U-component of surface momentum flux into ocean 143104: - max_vtauo - Time-maximum V-component of surface momentum flux into ocean 143105: - max_wphio - Time-maximum wave turbulent energy flux into ocean 143106: - max_wdw1 - Time-maximum wave directional width of first swell partition 143107: - max_wfw1 - Time-maximum wave frequency width of first swell partition 143108: - max_wdw2 - Time-maximum wave directional width of second swell partition 143109: - max_wfw2 - Time-maximum wave frequency width of second swell partition 143110: - max_wdw3 - Time-maximum wave directional width of third swell partition 143111: - max_wfw3 - Time-maximum wave frequency width of third swell partition 143112: - max_wefxm - Time-maximum wave energy flux magnitude 143113: - max_wefxd - Time-maximum wave energy flux mean direction 143114: - max_h1012 - Time-maximum significant wave height of all waves with periods within the inclusive range from 10 to 12 seconds 143115: - max_h1214 - Time-maximum significant wave height of all waves with periods within the inclusive range from 12 to 14 seconds 143116: - max_h1417 - Time-maximum significant wave height of all waves with periods within the inclusive range from 14 to 17 seconds 143117: - max_h1721 - Time-maximum significant wave height of all waves with periods within the inclusive range from 17 to 21 seconds 143118: - max_h2125 - Time-maximum significant wave height of all waves with periods within the inclusive range from 21 to 25 seconds 143119: - max_h2530 - Time-maximum significant wave height of all waves with periods within the inclusive range from 25 to 30 seconds 143120: - max_sh10 - Time-maximum significant wave height of all waves with period larger than 10s 143121: - max_swh1 - Time-maximum significant wave height of first swell partition 143122: - max_mwd1 - Time-maximum mean wave direction of first swell partition 143123: - max_mwp1 - Time-maximum mean wave period of first swell partition 143124: - max_swh2 - Time-maximum significant wave height of second swell partition 143125: - max_mwd2 - Time-maximum mean wave direction of second swell partition 143126: - max_mwp2 - Time-maximum mean wave period of second swell partition 143127: - max_swh3 - Time-maximum significant wave height of third swell partition 143128: - max_mwd3 - Time-maximum mean wave direction of third swell partition 143129: - max_mwp3 - Time-maximum mean wave period of third swell partition 143131: - max_tdcmax - Time-maximum time domain maximum individual crest height 143132: - max_tdhmax - Time-maximum time domain maximum individual wave height 143133: - max_stcmax - Time-maximum space time maximum individual crest height 143134: - max_sthmax - Time-maximum space time maximum individual wave height 143135: - max_pp1dw - Time-maximum peak wave period of wind waves 143136: - max_pp1ds - Time-maximum peak wave period of total swell 143137: - max_pp1d1 - Time-maximum peak wave period of first swell partition 143138: - max_pp1d2 - Time-maximum peak wave period of second swell partition 143139: - max_pp1d3 - Time-maximum peak wave period of third swell partition 143140: - max_pwd - Time-maximum peak wave direction (total) 143141: - max_pwdw - Time-maximum peak wave direction of wind waves 143142: - max_pwds - Time-maximum peak wave direction of total swell 143143: - max_pwd1 - Time-maximum peak wave direction of first swell partition 143144: - max_pwd2 - Time-maximum peak wave direction of second swell partition 143145: - max_pwd3 - Time-maximum peak wave direction of third swell partition 143146: - max_wcfr - Time-maximum whitecap fraction 143147: - max_bfi2d - Time-maximum Benjamin-Feir index 2D 143148: - max_ctc - Time-maximum crest-trough correlation 143149: - max_xwrs - Time-maximum X component of the wave radiative stress to sea ice 143150: - max_ywrs - Time-maximum Y component of the wave radiative stress to sea ice 143207: - max_wss - Time-maximum wave Spectral Skewness 143208: - max_wstar - Time-maximum free convective velocity over the oceans 143209: - max_rhoao - Time-maximum air density over the oceans 143211: - max_phiaw - Time-maximum normalized energy flux into waves 143212: - max_phioc - Time-maximum normalized energy flux into ocean 143214: - max_tauoc - Time-maximum normalized stress into ocean 143215: - max_ust - Time-maximum U-component surface stokes drift 143216: - max_vst - Time-maximum V-component surface stokes drift 143217: - max_tmax - Time-maximum period corresponding to maximum individual wave height 143218: - max_hmax - Time-maximum envelop-maximum individual wave height 143220: - max_mp1 - Time-maximum mean wave period based on first moment 143221: - max_mp2 - Time-maximum mean zero-crossing wave period 143222: - max_wdw - Time-maximum wave spectral directional width 143223: - max_p1ww - Time-maximum mean wave period based on first moment for wind waves 143224: - max_p2ww - Time-maximum mean wave period based on second moment for wind waves 143225: - max_dwww - Time-maximum wave spectral directional width for wind waves 143226: - max_p1ps - Time-maximum mean wave period based on first moment for swell 143227: - max_p2ps - Time-maximum mean wave period based on second moment for swell 143228: - max_dwps - Time-maximum wave spectral directional width for swell 143229: - max_swh - Time-maximum significant height of combined wind waves and swell 143230: - max_mwd - Time-maximum mean wave direction 143231: - max_pp1d - Time-maximum peak wave period 143232: - max_mwp - Time-maximum mean wave period 143233: - max_cdww - Time-maximum coefficient of drag with waves 143234: - max_shww - Time-maximum significant height of wind waves 143235: - max_mdww - Time-maximum mean direction of wind waves 143236: - max_mpww - Time-maximum mean period of wind waves 143237: - max_shts - Time-maximum significant height of total swell 143238: - max_mdts - Time-maximum mean direction of total swell 143239: - max_mpts - Time-maximum mean period of total swell 143244: - max_msqs - Time-maximum mean square slope of waves 143245: - max_wind - Time-maximum 10 metre wind speed 143249: - max_dwi - Time-maximum 10 metre wind direction 143252: - max_wsk - Time-maximum wave spectral kurtosis 143253: - max_bfi - Time-maximum benjamin-Feir index 143254: - max_wsp - Time-maximum wave spectral peakedness 144098: - min_weta - Time-minimum wave induced mean sea level correction 144099: - min_wraf - Time-minimum ratio of wave angular and frequency width 144100: - min_wnslc - Time-minimum number of events in freak waves statistics 144101: - min_utaua - Time-minimum U-component of atmospheric surface momentum flux 144102: - min_vtaua - Time-minimum V-component of atmospheric surface momentum flux 144103: - min_utauo - Time-minimum U-component of surface momentum flux into ocean 144104: - min_vtauo - Time-minimum V-component of surface momentum flux into ocean 144105: - min_wphio - Time-minimum wave turbulent energy flux into ocean 144106: - min_wdw1 - Time-minimum wave directional width of first swell partition 144107: - min_wfw1 - Time-minimum wave frequency width of first swell partition 144108: - min_wdw2 - Time-minimum wave directional width of second swell partition 144109: - min_wfw2 - Time-minimum wave frequency width of second swell partition 144110: - min_wdw3 - Time-minimum wave directional width of third swell partition 144111: - min_wfw3 - Time-minimum wave frequency width of third swell partition 144112: - min_wefxm - Time-minimum wave energy flux magnitude 144113: - min_wefxd - Time-minimum wave energy flux mean direction 144114: - min_h1012 - Time-minimum significant wave height of all waves with periods within the inclusive range from 10 to 12 seconds 144115: - min_h1214 - Time-minimum significant wave height of all waves with periods within the inclusive range from 12 to 14 seconds 144116: - min_h1417 - Time-minimum significant wave height of all waves with periods within the inclusive range from 14 to 17 seconds 144117: - min_h1721 - Time-minimum significant wave height of all waves with periods within the inclusive range from 17 to 21 seconds 144118: - min_h2125 - Time-minimum significant wave height of all waves with periods within the inclusive range from 21 to 25 seconds 144119: - min_h2530 - Time-minimum significant wave height of all waves with periods within the inclusive range from 25 to 30 seconds 144120: - min_sh10 - Time-minimum significant wave height of all waves with period larger than 10s 144121: - min_swh1 - Time-minimum significant wave height of first swell partition 144122: - min_mwd1 - Time-minimum mean wave direction of first swell partition 144123: - min_mwp1 - Time-minimum mean wave period of first swell partition 144124: - min_swh2 - Time-minimum significant wave height of second swell partition 144125: - min_mwd2 - Time-minimum mean wave direction of second swell partition 144126: - min_mwp2 - Time-minimum mean wave period of second swell partition 144127: - min_swh3 - Time-minimum significant wave height of third swell partition 144128: - min_mwd3 - Time-minimum mean wave direction of third swell partition 144129: - min_mwp3 - Time-minimum mean wave period of third swell partition 144131: - min_tdcmax - Time-minimum time domain maximum individual crest height 144132: - min_tdhmax - Time-minimum time domain maximum individual wave height 144133: - min_stcmax - Time-minimum space time maximum individual crest height 144134: - min_sthmax - Time-minimum space time maximum individual wave height 144135: - min_pp1dw - Time-minimum peak wave period of wind waves 144136: - min_pp1ds - Time-minimum peak wave period of total swell 144137: - min_pp1d1 - Time-minimum peak wave period of first swell partition 144138: - min_pp1d2 - Time-minimum peak wave period of second swell partition 144139: - min_pp1d3 - Time-minimum peak wave period of third swell partition 144140: - min_pwd - Time-minimum peak wave direction (total) 144141: - min_pwdw - Time-minimum peak wave direction of wind waves 144142: - min_pwds - Time-minimum peak wave direction of total swell 144143: - min_pwd1 - Time-minimum peak wave direction of first swell partition 144144: - min_pwd2 - Time-minimum peak wave direction of second swell partition 144145: - min_pwd3 - Time-minimum peak wave direction of third swell partition 144146: - min_wcfr - Time-minimum whitecap fraction 144147: - min_bfi2d - Time-minimum Benjamin-Feir index 2D 144148: - min_ctc - Time-minimum crest-trough correlation 144149: - min_xwrs - Time-minimum X component of the wave radiative stress to sea ice 144150: - min_ywrs - Time-minimum Y component of the wave radiative stress to sea ice 144207: - min_wss - Time-minimum wave Spectral Skewness 144208: - min_wstar - Time-minimum free convective velocity over the oceans 144209: - min_rhoao - Time-minimum air density over the oceans 144211: - min_phiaw - Time-minimum normalized energy flux into waves 144212: - min_phioc - Time-minimum normalized energy flux into ocean 144214: - min_tauoc - Time-minimum normalized stress into ocean 144215: - min_ust - Time-minimum U-component surface stokes drift 144216: - min_vst - Time-minimum V-component surface stokes drift 144217: - min_tmax - Time-minimum period corresponding to maximum individual wave height 144218: - min_hmax - Time-minimum envelop-maximum individual wave height 144220: - min_mp1 - Time-minimum mean wave period based on first moment 144221: - min_mp2 - Time-minimum mean zero-crossing wave period 144222: - min_wdw - Time-minimum wave spectral directional width 144223: - min_p1ww - Time-minimum mean wave period based on first moment for wind waves 144224: - min_p2ww - Time-minimum mean wave period based on second moment for wind waves 144225: - min_dwww - Time-minimum wave spectral directional width for wind waves 144226: - min_p1ps - Time-minimum mean wave period based on first moment for swell 144227: - min_p2ps - Time-minimum mean wave period based on second moment for swell 144228: - min_dwps - Time-minimum wave spectral directional width for swell 144229: - min_swh - Time-minimum significant height of combined wind waves and swell 144230: - min_mwd - Time-minimum mean wave direction 144231: - min_pp1d - Time-minimum peak wave period 144232: - min_mwp - Time-minimum mean wave period 144233: - min_cdww - Time-minimum coefficient of drag with waves 144234: - min_shww - Time-minimum significant height of wind waves 144235: - min_mdww - Time-minimum mean direction of wind waves 144236: - min_mpww - Time-minimum mean period of wind waves 144237: - min_shts - Time-minimum significant height of total swell 144238: - min_mdts - Time-minimum mean direction of total swell 144239: - min_mpts - Time-minimum mean period of total swell 144244: - min_msqs - Time-minimum mean square slope of waves 144245: - min_wind - Time-minimum 10 metre wind speed 144249: - min_dwi - Time-minimum 10 metre wind direction 144252: - min_wsk - Time-minimum wave spectral kurtosis 144253: - min_bfi - Time-minimum benjamin-Feir index 144254: - min_wsp - Time-minimum wave spectral peakedness 145098: - std_weta - Time-standard-deviation wave induced mean sea level correction 145099: - std_wraf - Time-standard-deviation ratio of wave angular and frequency width 145100: - std_wnslc - Time-standard-deviation number of events in freak waves statistics 145101: - std_utaua - Time-standard-deviation U-component of atmospheric surface momentum flux 145102: - std_vtaua - Time-standard-deviation V-component of atmospheric surface momentum flux 145103: - std_utauo - Time-standard-deviation U-component of surface momentum flux into ocean 145104: - std_vtauo - Time-standard-deviation V-component of surface momentum flux into ocean 145105: - std_wphio - Time-standard-deviation wave turbulent energy flux into ocean 145106: - std_wdw1 - Time-standard-deviation wave directional width of first swell partition 145107: - std_wfw1 - Time-standard-deviation wave frequency width of first swell partition 145108: - std_wdw2 - Time-standard-deviation wave directional width of second swell partition 145109: - std_wfw2 - Time-standard-deviation wave frequency width of second swell partition 145110: - std_wdw3 - Time-standard-deviation wave directional width of third swell partition 145111: - std_wfw3 - Time-standard-deviation wave frequency width of third swell partition 145112: - std_wefxm - Time-standard-deviation wave energy flux magnitude 145113: - std_wefxd - Time-standard-deviation wave energy flux mean direction 145114: - std_h1012 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 10 to 12 seconds 145115: - std_h1214 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 12 to 14 seconds 145116: - std_h1417 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 14 to 17 seconds 145117: - std_h1721 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 17 to 21 seconds 145118: - std_h2125 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 21 to 25 seconds 145119: - std_h2530 - Time-standard-deviation significant wave height of all waves with periods within the inclusive range from 25 to 30 seconds 145120: - std_sh10 - Time-standard-deviation significant wave height of all waves with period larger than 10s 145121: - std_swh1 - Time-standard-deviation significant wave height of first swell partition 145122: - std_mwd1 - Time-standard-deviation mean wave direction of first swell partition 145123: - std_mwp1 - Time-standard-deviation mean wave period of first swell partition 145124: - std_swh2 - Time-standard-deviation significant wave height of second swell partition 145125: - std_mwd2 - Time-standard-deviation mean wave direction of second swell partition 145126: - std_mwp2 - Time-standard-deviation mean wave period of second swell partition 145127: - std_swh3 - Time-standard-deviation significant wave height of third swell partition 145128: - std_mwd3 - Time-standard-deviation mean wave direction of third swell partition 145129: - std_mwp3 - Time-standard-deviation mean wave period of third swell partition 145131: - std_tdcmax - Time-standard-deviation time domain maximum individual crest height 145132: - std_tdhmax - Time-standard-deviation time domain maximum individual wave height 145133: - std_stcmax - Time-standard-deviation space time maximum individual crest height 145134: - std_sthmax - Time-standard-deviation space time maximum individual wave height 145135: - std_pp1dw - Time-standard-deviation peak wave period of wind waves 145136: - std_pp1ds - Time-standard-deviation peak wave period of total swell 145137: - std_pp1d1 - Time-standard-deviation peak wave period of first swell partition 145138: - std_pp1d2 - Time-standard-deviation peak wave period of second swell partition 145139: - std_pp1d3 - Time-standard-deviation peak wave period of third swell partition 145140: - std_pwd - Time-standard-deviation peak wave direction (total) 145141: - std_pwdw - Time-standard-deviation peak wave direction of wind waves 145142: - std_pwds - Time-standard-deviation peak wave direction of total swell 145143: - std_pwd1 - Time-standard-deviation peak wave direction of first swell partition 145144: - std_pwd2 - Time-standard-deviation peak wave direction of second swell partition 145145: - std_pwd3 - Time-standard-deviation peak wave direction of third swell partition 145146: - std_wcfr - Time-standard-deviation whitecap fraction 145147: - std_bfi2d - Time-standard-deviation Benjamin-Feir index 2D 145148: - std_ctc - Time-standard-deviation crest-trough correlation 145149: - std_xwrs - Time-standard-deviation X component of the wave radiative stress to sea ice 145150: - std_ywrs - Time-standard-deviation Y component of the wave radiative stress to sea ice 145207: - std_wss - Time-standard-deviation wave Spectral Skewness 145208: - std_wstar - Time-standard-deviation free convective velocity over the oceans 145209: - std_rhoao - Time-standard-deviation air density over the oceans 145211: - std_phiaw - Time-standard-deviation normalized energy flux into waves 145212: - std_phioc - Time-standard-deviation normalized energy flux into ocean 145214: - std_tauoc - Time-standard-deviation normalized stress into ocean 145215: - std_ust - Time-standard-deviation U-component surface stokes drift 145216: - std_vst - Time-standard-deviation V-component surface stokes drift 145217: - std_tmax - Time-standard-deviation period corresponding to maximum individual wave height 145218: - std_hmax - Time-standard-deviation envelop-maximum individual wave height 145220: - std_mp1 - Time-standard-deviation mean wave period based on first moment 145221: - std_mp2 - Time-standard-deviation mean zero-crossing wave period 145222: - std_wdw - Time-standard-deviation wave spectral directional width 145223: - std_p1ww - Time-standard-deviation mean wave period based on first moment for wind waves 145224: - std_p2ww - Time-standard-deviation mean wave period based on second moment for wind waves 145225: - std_dwww - Time-standard-deviation wave spectral directional width for wind waves 145226: - std_p1ps - Time-standard-deviation mean wave period based on first moment for swell 145227: - std_p2ps - Time-standard-deviation mean wave period based on second moment for swell 145228: - std_dwps - Time-standard-deviation wave spectral directional width for swell 145229: - std_swh - Time-standard-deviation significant height of combined wind waves and swell 145230: - std_mwd - Time-standard-deviation mean wave direction 145231: - std_pp1d - Time-standard-deviation peak wave period 145232: - std_mwp - Time-standard-deviation mean wave period 145233: - std_cdww - Time-standard-deviation coefficient of drag with waves 145234: - std_shww - Time-standard-deviation significant height of wind waves 145235: - std_mdww - Time-standard-deviation mean direction of wind waves 145236: - std_mpww - Time-standard-deviation mean period of wind waves 145237: - std_shts - Time-standard-deviation significant height of total swell 145238: - std_mdts - Time-standard-deviation mean direction of total swell 145239: - std_mpts - Time-standard-deviation mean period of total swell 145244: - std_msqs - Time-standard-deviation mean square slope of waves 145245: - std_wind - Time-standard-deviation 10 metre wind speed 145249: - std_dwi - Time-standard-deviation 10 metre wind direction 145252: - std_wsk - Time-standard-deviation wave spectral kurtosis 145253: - std_bfi - Time-standard-deviation benjamin-Feir index 145254: - std_wsp - Time-standard-deviation wave spectral peakedness 150129: - ocpt - Ocean potential temperature 150130: - ocs - Ocean salinity 150131: - ocpd - Ocean potential density 150133: - _param_150133 - Ocean U wind component 150134: - _param_150134 - Ocean V wind component 150135: - ocw - Ocean W wind component 150137: - rn - Richardson number 150139: - uv - U*V product 150140: - ut - U*T product 150141: - vt - V*T product 150142: - uu - U*U product 150143: - vv - V*V product 150144: - _param_150144 - UV - U~V~ 150145: - _param_150145 - UT - U~T~ 150146: - _param_150146 - VT - V~T~ 150147: - _param_150147 - UU - U~U~ 150148: - _param_150148 - VV - V~V~ 150152: - sl - Sea level 150153: - _param_150153 - Barotropic stream function 150154: - mld - Mixed layer depth 150155: - _param_150155 - Depth 150168: - _param_150168 - U stress 150169: - _param_150169 - V stress 150170: - _param_150170 - Turbulent kinetic energy input 150171: - nsf - Net surface heat flux 150172: - _param_150172 - Surface solar radiation 150173: - _param_150173 - P-E 150180: - _param_150180 - Diagnosed sea surface temperature error 150181: - _param_150181 - Heat flux correction 150182: - _param_150182 - Observed sea surface temperature 150183: - _param_150183 - Observed heat flux 150255: - _param_150255 - Indicates a missing value 151126: - mswpt300m - Mean sea water potential temperature in the upper 300 m 151127: - mswt300m - Mean sea water temperature in the upper 300 m 151128: - _param_151128 - In situ Temperature 151129: - thetao - Sea water potential temperature 151130: - so - Sea water practical salinity 151131: - ocu - Eastward surface sea water velocity 151132: - ocv - Northward surface sea water velocity 151133: - wo - Upward sea water velocity 151134: - mst - Modulus of strain rate tensor 151135: - vvs - Vertical viscosity 151136: - vdf - Vertical diffusivity 151137: - dep - Bottom level Depth 151138: - sigmat - Sea water sigma theta 151139: - rn - Richardson number 151140: - uv - UV product 151141: - ut - UT product 151142: - vt - VT product 151143: - uu - UU product 151144: - vv - VV product 151145: - zos - Sea surface height 151146: - sl_1 - Sea level previous timestep 151147: - stfbarot - Ocean barotropic stream function 151148: - mld - Mixed layer depth 151149: - btp - Bottom Pressure (equivalent height) 151150: - sh - Steric height 151151: - crl - Curl of Wind Stress 151152: - _param_151152 - Divergence of wind stress 151153: - taueo - Surface downward eastward stress 151154: - tauno - Surface downward northward stress 151155: - tki - Turbulent kinetic energy input 151156: - nsf - Net surface heat flux 151157: - asr - Absorbed solar radiation 151158: - pme - Precipitation - evaporation 151159: - sst - Specified sea surface temperature 151160: - shf - Specified surface heat flux 151161: - dte - Diagnosed sea surface temperature error 151162: - hfc - Heat flux correction 151163: - t20d - Depth of 20C isotherm 151164: - tav300 - Average potential temperature in the upper 300m 151165: - uba1 - Vertically integrated zonal velocity (previous time step) 151166: - vba1 - Vertically Integrated meridional velocity (previous time step) 151167: - ztr - Vertically integrated zonal volume transport 151168: - mtr - Vertically integrated meridional volume transport 151169: - zht - Vertically integrated zonal heat transport 151170: - mht - Vertically integrated meridional heat transport 151171: - umax - U velocity maximum 151172: - dumax - Depth of the velocity maximum 151173: - smax - Salinity maximum 151174: - dsmax - Depth of salinity maximum 151175: - sav300 - Average sea water practical salinity in the upper 300m 151176: - ldp - Layer Thickness at scalar points 151177: - ldu - Layer Thickness at vector points 151178: - pti - Potential temperature increment 151179: - ptae - Potential temperature analysis error 151180: - bpt - Background potential temperature 151181: - apt - Analysed potential temperature 151182: - ptbe - Potential temperature background error 151183: - as - Analysed salinity 151184: - sali - Salinity increment 151185: - ebt - Estimated Bias in Temperature 151186: - ebs - Estimated Bias in Salinity 151187: - uvi - Zonal Velocity increment (from balance operator) 151188: - vvi - Meridional Velocity increment (from balance operator) 151189: - tos - Sea surface temperature in degC 151190: - subi - Salinity increment (from salinity data) 151191: - sale - Salinity analysis error 151192: - bsal - Background Salinity 151193: - _param_151193 - Reserved 151194: - salbe - Salinity background error 151195: - t14d - Depth of 14C isotherm 151196: - t17d - Depth of 17C isotherm 151197: - t26d - Depth of 26C isotherm 151198: - t28d - Depth of 28C isotherm 151199: - ebta - Estimated temperature bias from assimilation 151200: - ebsa - Estimated salinity bias from assimilation 151201: - lti - Temperature increment from relaxation term 151202: - lsi - Salinity increment from relaxation term 151203: - bzpga - Bias in the zonal pressure gradient (applied) 151204: - bmpga - Bias in the meridional pressure gradient (applied) 151205: - ebtl - Estimated temperature bias from relaxation 151206: - ebsl - Estimated salinity bias from relaxation 151207: - fgbt - First guess bias in temperature 151208: - fgbs - First guess bias in salinity 151209: - bpa - Applied bias in pressure 151210: - fgbp - FG bias in pressure 151211: - pta - Bias in temperature(applied) 151212: - psa - Bias in salinity (applied) 151213: - hfds - Surface downward heat flux in sea water 151214: - hc300m - Ocean heat content 0-300m 151215: - hc700m - Ocean heat content 0-700m 151216: - hcbtm - Ocean heat content 0-bottom 151217: - wfcorr - Water flux correction 151219: - sos - Sea surface practical salinity 151222: - tauvo - Surface downward y stress 151223: - tauuo - Surface downward x stress 151224: - mlotdev - Ocean mixed layer thickness defined by vertical tracer diffusivity threshold 151225: - mlotst010 - Ocean mixed layer thickness defined by sigma theta 0.01 kg/m3 151226: - mlotst030 - Ocean mixed layer thickness defined by sigma theta 0.03 kg/m3 151227: - mlotst125 - Ocean mixed layer thickness defined by sigma theta 0.125 kg/m3 151228: - mlott02 - Ocean mixed layer thickness defined by temperature 0.2C 151229: - mlott05 - Ocean mixed layer thickness defined by temperature 0.5C 151230: - vsf - Virtual salt flux into sea water 151232: - sc700m - Integrated salinity 0-700m 151233: - sabtm - Integrated salinity 0-bottom 151234: - swmth - Sea water mass per unit area expressed as thickness 151235: - sc300m - Integrated salinity 0-300m 151236: - rss - Surface net downward shortwave flux into ocean 151250: - voy - Sea water y velocity 151251: - uox - Sea water x velocity 151255: - _param_151255 - Indicates a missing value 160049: - 10fgrea - 10 metre wind gust during averaging time 160135: - wrea - vertical velocity (pressure) 160137: - pwcrea - Precipitable water content 160140: - swl1rea - Soil wetness level 1 160141: - sdrea - Snow depth 160142: - lsprea - Large-scale precipitation 160143: - cprea - Convective precipitation 160144: - sfrea - Snowfall 160156: - ghrea - Height 160157: - rrea - Relative humidity 160171: - swl2rea - Soil wetness level 2 160180: - ewssrea - East-West surface stress 160181: - nsssrea - North-South surface stress 160182: - erea - Evaporation 160184: - swl3rea - Soil wetness level 3 160198: - srcon - Skin reservoir content 160199: - vegrea - Percentage of vegetation 160205: - rorea - Runoff 160207: - tzrea - Covariance of temperature and geopotential 160209: - qzrea - Covariance of specific humidity and geopotential 160210: - qtrea - Covariance of specific humidity and temperature 160212: - uzrea - Covariance of U component and geopotential 160213: - utrea - Covariance of U component and temperature 160214: - uqrea - Covariance of U component and specific humidity 160216: - vzrea - Covariance of V component and geopotential 160217: - vtrea - Covariance of V component and temperature 160218: - vqrea - Covariance of V component and specific humidity 160219: - vurea - Covariance of V component and U component 160221: - wzrea - Covariance of W component and geopotential 160222: - wtrea - Covariance of W component and temperature 160223: - wqrea - Covariance of W component and specific humidity 160224: - wurea - Covariance of W component and U component 160225: - wvrea - Covariance of W component and V component 160231: - ishfrea - Instantaneous surface heat flux 160239: - csfrea - Convective snowfall 160240: - lsfrea - Large scale snowfall 160241: - clwcerrea - Cloud liquid water content 160242: - ccrea - Cloud cover 160243: - falrea - Forecast albedo 160246: - 10wsrea - 10 metre wind speed 160247: - moflrea - Momentum flux 160249: - _param_160249 - Gravity wave dissipation flux 160254: - hsdrea - Heaviside beta function 162031: - viwgd - Total column vertically-integrated divergence of water geopotential flux 162032: - viee - Total column vertically-integrated eastward enthalpy flux 162033: - vien - Total column vertically-integrated northward enthalpy flux 162034: - vied - Total column vertically-integrated divergence of enthalpy flux 162035: - viwed - Total column vertically-integrated divergence of water enthalpy flux 162036: - viwvd - Total column vertically-integrated divergence of water vapour flux 162037: - viclwd - Total column vertically-integrated divergence of cloud liquid water flux 162038: - viciwd - Total column vertically-integrated divergence of cloud ice water flux 162039: - vird - Total column vertically-integrated divergence of rain flux 162040: - visd - Total column vertically-integrated divergence of snow flux 162041: - viclwe - Total column vertically-integrated eastward cloud liquid water flux 162042: - viclwn - Total column vertically-integrated northward cloud liquid water flux 162043: - viciwe - Total column vertically-integrated eastward cloud ice water flux 162044: - viciwn - Total column vertically-integrated northward cloud ice water flux 162045: - wvf - Water vapour flux 162046: - vire - Total column vertically-integrated eastward rain flux 162047: - virn - Total column vertically-integrated northward rain flux 162048: - vise - Total column vertically-integrated eastward snow flux 162049: - visn - Total column vertically-integrated northward snow flux 162050: - vions - Total column vertically-integrated net source of ozone 162051: - _param_162051 - Surface geopotential 162053: - vima - Total column vertically-integrated mass of atmosphere 162054: - vit - Total column vertically-integrated temperature 162055: - viwv - Total column vertically-integrated water vapour 162056: - vilw - Total column vertically-integrated cloud liquid water 162057: - viiw - Total column vertically-integrated cloud frozen water 162058: - vioz - Total column vertically-integrated ozone 162059: - vike - Total column vertically-integrated kinetic energy 162060: - vithe - Total column vertically-integrated enthalpy 162061: - vipie - Total column vertically-integrated potential + internal energy 162062: - vipile - Total column vertically-integrated potential+internal+latent energy 162063: - vitoe - Total column vertically-integrated total energy 162064: - viec - Total column vertically-integrated energy conversion 162065: - vimae - Total column vertically-integrated eastward mass flux 162066: - viman - Total column vertically-integrated northward mass flux 162067: - vikee - Total column vertically-integrated eastward kinetic energy flux 162068: - viken - Total column vertically-integrated northward kinetic energy flux 162069: - vithee - Total column vertically-integrated eastward heat flux 162070: - vithen - Total column vertically-integrated northward heat flux 162071: - viwve - Total column vertically-integrated eastward water vapour flux 162072: - viwvn - Total column vertically-integrated northward water vapour flux 162073: - vige - Total column vertically-integrated eastward geopotential flux 162074: - vign - Total column vertically-integrated northward geopotential flux 162075: - vitee - Total column vertically-integrated eastward total energy flux 162076: - viten - Total column vertically-integrated northward total energy flux 162077: - vioze - Total column vertically-integrated eastward ozone flux 162078: - viozn - Total column vertically-integrated northward ozone flux 162079: - vilwd - Total column vertically-integrated divergence of cloud liquid water flux 162080: - viiwd - Total column vertically-integrated divergence of cloud frozen water flux 162081: - vimad - Total column vertically-integrated divergence of mass flux 162082: - viked - Total column vertically-integrated divergence of kinetic energy flux 162083: - vithed - Total column vertically-integrated divergence of thermal energy flux 162084: - vimdf - Total column vertically-integrated moisture divergence flux 162085: - vigd - Total column vertically-integrated divergence of geopotential flux 162086: - vited - Total column vertically-integrated divergence of total energy flux 162087: - viozd - Total column vertically-integrated divergence of ozone flux 162088: - vilwe - Total column vertically-integrated eastward cloud liquid water flux 162089: - vilwn - Total column vertically-integrated northward cloud liquid water flux 162090: - viiwe - Total column vertically-integrated eastward cloud frozen water flux 162091: - viiwn - Total column vertically-integrated northward cloud frozen water flux 162092: - vimat - Total column vertically-integrated mass tendency 162093: - viwe - Total column vertically-integrated water enthalpy 162100: - srta - Time-integrated temperature tendency due to short-wave radiation 162101: - trta - Time-integrated temperature tendency due to long-wave radiation 162102: - srtca - Time-integrated temperature tendency due to short wave radiation, clear sky 162103: - trtca - Time-integrated temperature tendency due to long-wave radiation, clear sky 162104: - umfa - Time-integrated updraught mass flux 162105: - dmfa - Time-integrated downdraught mass flux 162106: - udra - Time-integrated updraught detrainment rate 162107: - ddra - Time-integrated downdraught detrainment rate 162108: - tpfa - Time-integrated total precipitation flux 162109: - tdcha - Time-integrated turbulent diffusion coefficient for heat 162110: - ttpha - Time-integrated temperature tendency due to parametrisations 162111: - qtpha - Time-integrated specific humidity tendency due to parametrisations 162112: - utpha - Time-integrated eastward wind tendency due to parametrisations 162113: - vtpha - Time-integrated northward wind tendency due to parametrisations 162114: - utendd - U-tendency from dynamics 162115: - vtendd - V-tendency from dynamics 162116: - ttendd - T-tendency from dynamics 162117: - qtendd - q-tendency from dynamics 162118: - ttendr - T-tendency from radiation 162119: - utendts - U-tendency from turbulent diffusion + subgrid orography 162120: - vtendts - V-tendency from turbulent diffusion + subgrid orography 162121: - ttendts - T-tendency from turbulent diffusion + subgrid orography 162122: - qtendt - q-tendency from turbulent diffusion 162123: - utends - U-tendency from subgrid orography 162124: - vtends - V-tendency from subgrid orography 162125: - ttends - T-tendency from subgrid orography 162126: - utendcds - U-tendency from convection (deep+shallow) 162127: - vtendcds - V-tendency from convection (deep+shallow) 162128: - ttendcds - T-tendency from convection (deep+shallow) 162129: - qtendcds - q-tendency from convection (deep+shallow) 162130: - lpc - Liquid Precipitation flux from convection 162131: - ipc - Ice Precipitation flux from convection 162132: - ttendcs - T-tendency from cloud scheme 162133: - qtendcs - q-tendency from cloud scheme 162134: - qltendcs - ql-tendency from cloud scheme 162135: - qitendcs - qi-tendency from cloud scheme 162136: - lpcs - Liquid Precip flux from cloud scheme (stratiform) 162137: - ipcs - Ice Precip flux from cloud scheme (stratiform) 162138: - utendcs - U-tendency from shallow convection 162139: - vtendcs - V-tendency from shallow convection 162140: - ttendsc - T-tendency from shallow convection 162141: - qtendsc - q-tendency from shallow convection 162150: - ttswr - Temperature tendency due to short-wave radiation 162151: - ttlwr - Temperature tendency due to long-wave radiation 162152: - ttswrcs - Temperature tendency due to short-wave radiation, clear sky 162153: - ttlwrcs - Temperature tendency due to long-wave radiation, clear sky 162154: - umf - Updraught mass flux 162155: - dmf - Downdraught mass flux 162156: - udr - Updraught detrainment rate 162157: - ddr - Downdraught detrainment rate 162158: - tpf - Total precipitation flux 162159: - tdch - Turbulent diffusion coefficient for heat 162160: - ttpm - Temperature tendency due to parametrisations 162161: - qtpm - Specific humidity tendency due to parametrisations 162162: - utpm - Eastward wind tendency due to parametrisations 162163: - vtpm - Northward wind tendency due to parametrisations 162206: - _param_162206 - Variance of geopotential 162207: - _param_162207 - Covariance of geopotential/temperature 162208: - _param_162208 - Variance of temperature 162209: - _param_162209 - Covariance of geopotential/specific humidity 162210: - _param_162210 - Covariance of temperature/specific humidity 162211: - _param_162211 - Variance of specific humidity 162212: - _param_162212 - Covariance of u component/geopotential 162213: - _param_162213 - Covariance of u component/temperature 162214: - _param_162214 - Covariance of u component/specific humidity 162215: - _param_162215 - Variance of u component 162216: - _param_162216 - Covariance of v component/geopotential 162217: - _param_162217 - Covariance of v component/temperature 162218: - _param_162218 - Covariance of v component/specific humidity 162219: - _param_162219 - Covariance of v component/u component 162220: - _param_162220 - Variance of v component 162221: - _param_162221 - Covariance of omega/geopotential 162222: - _param_162222 - Covariance of omega/temperature 162223: - _param_162223 - Covariance of omega/specific humidity 162224: - _param_162224 - Covariance of omega/u component 162225: - _param_162225 - Covariance of omega/v component 162226: - _param_162226 - Variance of omega 162227: - _param_162227 - Variance of surface pressure 162229: - _param_162229 - Variance of relative humidity 162230: - _param_162230 - Covariance of u component/ozone 162231: - _param_162231 - Covariance of v component/ozone 162232: - _param_162232 - Covariance of omega/ozone 162233: - _param_162233 - Variance of ozone 162255: - _param_162255 - Indicates a missing value 170001: - spi03 - Standardised precipitation index valid in the last 3 months 170002: - spi06 - Standardised precipitation index valid in the last 6 months 170003: - spi12 - Standardised precipitation index valid in the last 12 months 170149: - tsw - Total soil moisture 170171: - swl2 - Soil wetness level 2 170179: - ttr - Top net thermal radiation 171001: - strfa - Stream function anomaly 171002: - vpota - Velocity potential anomaly 171003: - pta - Potential temperature anomaly 171004: - epta - Equivalent potential temperature anomaly 171005: - septa - Saturated equivalent potential temperature anomaly 171006: - 100ua - 100 metre U wind component anomaly 171007: - 100va - 100 metre V wind component anomaly 171008: - 100sia - 100 metre wind speed anomaly 171011: - udwa - U component of divergent wind anomaly 171012: - vdwa - V component of divergent wind anomaly 171013: - urwa - U component of rotational wind anomaly 171014: - vrwa - V component of rotational wind anomaly 171021: - uctpa - Unbalanced component of temperature anomaly 171022: - uclna - Unbalanced component of logarithm of surface pressure anomaly 171023: - ucdva - Unbalanced component of divergence anomaly 171024: - lmlta - Lake mix-layer temperature anomaly 171025: - licda - Lake ice depth anomaly 171026: - cla - Lake cover anomaly 171027: - cvla - Low vegetation cover anomaly 171028: - cvha - High vegetation cover anomaly 171029: - tvla - Type of low vegetation anomaly 171030: - tvha - Type of high vegetation anomaly 171031: - sica - Sea-ice cover anomaly - ci 171032: - asna - Snow albedo anomaly 171033: - rsna - Snow density anomaly 171034: - ssta - Sea surface temperature anomaly 171035: - istal1 - Ice surface temperature anomaly layer 1 171036: - istal2 - Ice surface temperature anomaly layer 2 171037: - istal3 - Ice surface temperature anomaly layer 3 171038: - istal4 - Ice surface temperature anomaly layer 4 171039: - swval1 - Volumetric soil water anomaly layer 1 - swv1 - swvl1a 171040: - swval2 - Volumetric soil water anomaly layer 2 - swv2 - swvl2a 171041: - swval3 - Volumetric soil water anomaly layer 3 - swv3 - swvl3a 171042: - swval4 - Volumetric soil water anomaly layer 4 - swv4 - swvl4a 171043: - slta - Soil type anomaly 171044: - esa - Snow evaporation anomaly 171045: - smlta - Snowmelt anomaly 171046: - sdura - Solar duration anomaly 171047: - dsrpa - Direct solar radiation anomaly 171048: - magssa - Magnitude of turbulent surface stress anomaly 171049: - 10fga - 10 metre wind gust anomaly 171050: - lspfa - Large-scale precipitation fraction anomaly 171051: - mx2t24a - Maximum 2 metre temperature in the last 24 hours anomaly 171052: - mn2t24a - Minimum 2 metre temperature in the last 24 hours anomaly 171053: - monta - Montgomery potential anomaly 171054: - pa - Pressure anomaly 171055: - mean2t24a - Mean 2 metre temperature in the last 24 hours anomaly 171056: - mn2d24a - Mean 2 metre dewpoint temperature in the last 24 hours anomaly 171057: - uvba - Downward UV radiation at the surface anomaly 171058: - para - Photosynthetically active radiation at the surface anomaly 171059: - capea - Convective available potential energy anomaly 171060: - pva - Potential vorticity anomaly 171061: - tpoa - Total precipitation from observations anomaly 171062: - obcta - Observation count anomaly 171063: - stsktda - Start time for skin temperature difference anomaly 171064: - ftsktda - Finish time for skin temperature difference anomaly 171065: - sktda - Skin temperature difference anomaly 171078: - tclwa - Total column liquid water anomaly 171079: - tciwa - Total column ice water anomaly 171121: - mx2t6a - Maximum temperature at 2 metres in the last 6 hours anomaly 171122: - mn2t6a - Minimum temperature at 2 metres in the last 6 hours anomaly 171125: - vitea - Vertically integrated total energy anomaly 171126: - _param_171126 - Generic parameter for sensitive area prediction 171127: - ata - Atmospheric tide anomaly 171128: - bva - Budget values anomaly 171129: - za - Geopotential anomaly - z 171130: - ta - Temperature anomaly - t 171131: - ua - U component of wind anomaly - u 171132: - va - V component of wind anomaly - v 171133: - qa - Specific humidity anomaly 171134: - spa - Surface pressure anomaly 171135: - wa - Vertical velocity (pressure) anomaly 171136: - tcwa - Total column water anomaly 171137: - tcwva - Total column water vapour anomaly 171138: - voa - Relative vorticity anomaly - vo 171139: - stal1 - Soil temperature anomaly level 1 - stl1 - st 171140: - swal1 - Soil wetness anomaly level 1 - swl1 171141: - sda - Snow depth anomaly 171142: - lspa - Stratiform precipitation (Large-scale precipitation) anomaly 171143: - cpa - Convective precipitation anomaly 171144: - sfa - Snowfall (convective + stratiform) anomaly 171145: - blda - Boundary layer dissipation anomaly 171146: - sshfa - Surface sensible heat flux anomaly 171147: - slhfa - Surface latent heat flux anomaly 171148: - chnka - Charnock anomaly 171149: - snra - Surface net radiation anomaly 171150: - tnra - Top net radiation anomaly 171151: - msla - Mean sea level pressure anomaly - msl 171152: - lspa - Logarithm of surface pressure anomaly 171153: - swhra - Short-wave heating rate anomaly 171154: - lwhra - Long-wave heating rate anomaly 171155: - da - Relative divergence anomaly - d 171156: - gha - Height anomaly - gh 171157: - ra - Relative humidity anomaly - r 171158: - tspa - Tendency of surface pressure anomaly 171159: - blha - Boundary layer height anomaly 171160: - sdora - Standard deviation of orography anomaly 171161: - isora - Anisotropy of sub-gridscale orography anomaly 171162: - anora - Angle of sub-gridscale orography anomaly 171163: - slora - Slope of sub-gridscale orography anomaly 171164: - tcca - Total cloud cover anomaly 171165: - 10ua - 10 metre U wind component anomaly 171166: - 10va - 10 metre V wind component anomaly 171167: - 2ta - 2 metre temperature anomaly 171168: - 2da - 2 metre dewpoint temperature anomaly 171169: - ssrda - Surface solar radiation downwards anomaly 171170: - stal2 - Soil temperature anomaly level 2 - slal2 - stl2 171171: - swal2 - Soil wetness anomaly level 2 - swl2 171173: - sra - Surface roughness anomaly 171174: - ala - Albedo anomaly 171175: - strda - Surface thermal radiation downwards anomaly 171176: - ssra - Surface net solar radiation anomaly 171177: - stra - Surface net thermal radiation anomaly 171178: - tsra - Top net solar radiation anomaly 171179: - ttra - Top net thermal radiation anomaly 171180: - eqssa - East-West surface stress anomaly 171181: - nsssa - North-South surface stress anomaly 171182: - ea - Evaporation anomaly 171183: - stal3 - Soil temperature anomaly level 3 - stl3 171184: - swal3 - Soil wetness anomaly level 3 - swl3 171185: - ccca - Convective cloud cover anomaly 171186: - lcca - Low cloud cover anomaly 171187: - mcca - Medium cloud cover anomaly 171188: - hcca - High cloud cover anomaly 171189: - sunda - Sunshine duration anomaly 171190: - ewova - East-West component of sub-gridscale orographic variance anomaly 171191: - nsova - North-South component of sub-gridscale orographic variance anomaly 171192: - nwova - North-West/South-East component of sub-gridscale orographic variance anomaly 171193: - neova - North-East/South-West component of sub-gridscale orographic variance anomaly 171194: - btmpa - Brightness temperature anomaly 171195: - lgwsa - Longitudinal component of gravity wave stress anomaly 171196: - mgwsa - Meridional component of gravity wave stress anomaly 171197: - gwda - Gravity wave dissipation anomaly 171198: - srca - Skin reservoir content anomaly 171199: - vfa - Vegetation fraction anomaly 171200: - vsoa - Variance of sub-gridscale orography anomaly 171201: - mx2ta - Maximum temperature at 2 metres anomaly 171202: - mn2ta - Minimum temperature at 2 metres anomaly 171203: - o3a - Ozone mass mixing ratio anomaly 171204: - pawa - Precipitation analysis weights anomaly 171205: - roa - Runoff anomaly 171206: - tco3a - Total column ozone anomaly 171207: - 10sia - 10 metre wind speed anomaly - 10si 171208: - tsrca - Top net solar radiation clear sky anomaly 171209: - ttrca - Top net thermal radiation clear sky anomaly 171210: - ssrca - Surface net solar radiation clear sky anomaly 171211: - strca - Surface net thermal radiation, clear sky anomaly 171212: - sia - Solar insolation anomaly 171214: - dhra - Diabatic heating by radiation anomaly 171215: - dhvda - Diabatic heating by vertical diffusion anomaly 171216: - dhcca - Diabatic heating by cumulus convection anomaly 171217: - dhlca - Diabatic heating by large-scale condensation anomaly 171218: - vdzwa - Vertical diffusion of zonal wind anomaly 171219: - vdmwa - Vertical diffusion of meridional wind anomaly 171220: - ewgda - East-West gravity wave drag tendency anomaly 171221: - nsgda - North-South gravity wave drag tendency anomaly 171222: - ctzwa - Convective tendency of zonal wind anomaly 171223: - ctmwa - Convective tendency of meridional wind anomaly 171224: - vdha - Vertical diffusion of humidity anomaly 171225: - htcca - Humidity tendency by cumulus convection anomaly 171226: - htlca - Humidity tendency by large-scale condensation anomaly 171227: - crnha - Change from removal of negative humidity anomaly 171228: - tpa - Total precipitation anomaly 171229: - iewsa - Instantaneous X surface stress anomaly 171230: - inssa - Instantaneous Y surface stress anomaly 171231: - ishfa - Instantaneous surface heat flux anomaly 171232: - iea - Instantaneous moisture flux anomaly 171233: - asqa - Apparent surface humidity anomaly 171234: - lsrha - Logarithm of surface roughness length for heat anomaly 171235: - skta - Skin temperature anomaly 171236: - stal4 - Soil temperature level 4 anomaly 171237: - swal4 - Soil wetness level 4 anomaly - swl4 171238: - tsna - Temperature of snow layer anomaly 171239: - csfa - Convective snowfall anomaly 171240: - lsfa - Large scale snowfall anomaly 171241: - acfa - Accumulated cloud fraction tendency anomaly 171242: - alwa - Accumulated liquid water tendency anomaly 171243: - fala - Forecast albedo anomaly 171244: - fsra - Forecast surface roughness anomaly 171245: - flsra - Forecast logarithm of surface roughness for heat anomaly 171246: - clwca - Cloud liquid water content anomaly 171247: - ciwca - Cloud ice water content anomaly 171248: - cca - Cloud cover anomaly 171249: - aiwa - Accumulated ice water tendency anomaly 171250: - iaa - Ice age anomaly 171251: - attea - Adiabatic tendency of temperature anomaly 171252: - athea - Adiabatic tendency of humidity anomaly 171253: - atzea - Adiabatic tendency of zonal wind anomaly 171254: - atmwa - Adiabatic tendency of meridional wind anomaly 171255: - _param_171255 - Indicates a missing value 172008: - msror - Time-mean surface runoff rate 172009: - mssror - Time-mean sub-surface runoff rate 172044: - esrate - Time-mean snow evaporation rate - es 172045: - _param_172045 - Time-mean snowmelt rate 172050: - mlspfr - Mean large-scale precipitation fraction - lspf 172142: - mlsprt - Time-mean large-scale precipitation rate - lsp 172143: - cprate - Time-mean convective precipitation rate - cp 172144: - mtsfr - Time-mean total snowfall rate - sf 172145: - bldrate - Boundary layer dissipation 172146: - msshfl - Time-mean surface sensible heat flux - sshf - '146' 172147: - mslhfl - Time-mean surface latent heat flux - slhf - '147' 172149: - msnrf - Mean surface net radiation flux - snr 172153: - mswhr - Time-mean short-wave (solar) heating rate - swhr 172154: - mlwhr - Time-mean long-wave (thermal) heating rate - lwhr 172169: - msdsrf - Time-mean surface downward short-wave (solar) radiation flux - ssrd 172175: - msdtrf - Time-mean surface downward long-wave (thermal) radiation flux - strd 172176: - msnsrf - Time-mean surface net short-wave (solar) radiation flux - ssr 172177: - msntrf - Time-mean surface net long-wave (thermal) radiation flux - str 172178: - mtnsrf - Time-mean top net short-wave (solar) radiation flux - tsr 172179: - mtntrf - Time-mean top net long-wave (thermal) radiation flux - ttr 172180: - ewssra - Time-mean eastward turbulent surface stress - ewss 172181: - nsssra - Time-mean northward turbulent surface stress - nsss 172182: - erate - Time-mean evaporation rate - e 172189: - msdr - Mean sunshine duration rate - sund 172195: - _param_172195 - Longitudinal component of gravity wave stress 172196: - _param_172196 - Meridional component of gravity wave stress 172197: - gwdrate - Gravity wave dissipation 172205: - mrort - Time-mean runoff rate - ro 172208: - _param_172208 - Time-mean top net short-wave (solar) radiation flux, clear sky 172209: - _param_172209 - Time-mean top net long-wave (thermal) radiation flux, clear sky 172210: - _param_172210 - Time-mean surface net short-wave (solar) radiation flux, clear sky 172211: - _param_172211 - Time-mean surface net long-wave (thermal) radiation flux, clear sky 172212: - soira - Time-mean short-wave (solar) insolation rate 172228: - tprate - Time-mean total precipitation rate - tp 172239: - _param_172239 - Time-mean convective snowfall rate 172240: - _param_172240 - Time-mean large-scale snowfall rate 172255: - _param_172255 - Indicates a missing value 173008: - msrora - Mean surface runoff rate anomaly 173009: - mssrora - Mean sub-surface runoff rate anomaly 173044: - _param_173044 - Time-mean snow evaporation anomalous rate of accumulation 173045: - _param_173045 - Time-mean snowmelt anomalous rate of accumulation 173048: - _param_173048 - Magnitude of turbulent surface stress anomaly 173050: - _param_173050 - Large-scale precipitation fraction anomaly 173142: - lspara - Stratiform precipitation (Large-scale precipitation) anomalous rate of accumulation - lspa 173143: - mcpra - Mean convective precipitation rate anomaly - cpa 173144: - sfara - Snowfall (convective + stratiform) anomalous rate of accumulation 173145: - _param_173145 - Boundary layer dissipation anomaly 173146: - sshfara - Surface sensible heat flux anomalous rate of accumulation 173147: - slhfara - Surface latent heat flux anomalous rate of accumulation 173149: - _param_173149 - Surface net radiation anomaly 173153: - _param_173153 - Short-wave heating rate anomaly 173154: - _param_173154 - Long-wave heating rate anomaly 173169: - ssrdara - Surface solar radiation downwards anomalous rate of accumulation - ssrda - ssrd 173175: - strdara - Surface thermal radiation downwards anomalous rate of accumulation - strda 173176: - ssrara - Surface solar radiation anomalous rate of accumulation - ssra - ssr 173177: - strara - Surface thermal radiation anomalous rate of accumulation - stra - str 173178: - tsrara - Top solar radiation anomalous rate of accumulation - tsra - tsr 173179: - ttrara - Top thermal radiation anomalous rate of accumulation - ttra - ttr 173180: - ewssara - East-West surface stress anomalous rate of accumulation 173181: - nsssara - North-South surface stress anomalous rate of accumulation 173182: - evara - Time-mean evaporation anomalous rate of accumulation - ea - e 173189: - sundara - Sunshine duration anomalous rate of accumulation 173195: - _param_173195 - Longitudinal component of gravity wave stress anomaly 173196: - _param_173196 - Meridional component of gravity wave stress anomaly 173197: - _param_173197 - Gravity wave dissipation anomaly 173205: - roara - Runoff anomalous rate of accumulation 173208: - _param_173208 - Top net solar radiation, clear sky anomaly 173209: - _param_173209 - Top net thermal radiation, clear sky anomaly 173210: - _param_173210 - Surface net solar radiation, clear sky anomaly 173211: - _param_173211 - Surface net thermal radiation, clear sky anomaly 173212: - soiara - Solar insolation anomalous rate of accumulation 173228: - tpara - Total precipitation anomalous rate of accumulation 173239: - _param_173239 - Convective snowfall anomaly 173240: - _param_173240 - Large scale snowfall anomaly 173255: - _param_173255 - Indicates a missing value 174006: - _param_174006 - Total soil moisture 174008: - sro - Surface runoff 174010: - sswcsdown - Clear-sky (II) down surface sw flux 174013: - sswcsup - Clear-sky (II) up surface sw flux 174025: - vis15 - Visibility at 1.5m 174031: - _param_174031 - Fraction of sea-ice in sea 174034: - _param_174034 - Open-sea surface temperature 174039: - _param_174039 - Volumetric soil water layer 1 174040: - _param_174040 - Volumetric soil water layer 2 174041: - _param_174041 - Volumetric soil water layer 3 174042: - _param_174042 - Volumetric soil water layer 4 174049: - _param_174049 - 10 metre wind gust in the last 24 hours 174050: - mn15t - Minimum temperature at 1.5m since previous post-processing 174051: - mx15t - Maximum temperature at 1.5m since previous post-processing 174052: - rhum - Relative humidity at 1.5m 174055: - _param_174055 - 1.5m temperature - mean in the last 24 hours 174083: - _param_174083 - Net primary productivity 174085: - _param_174085 - 10m U wind over land 174086: - _param_174086 - 10m V wind over land 174087: - _param_174087 - 1.5m temperature over land 174088: - _param_174088 - 1.5m dewpoint temperature over land 174089: - _param_174089 - Top incoming solar radiation 174090: - _param_174090 - Top outgoing solar radiation 174092: - sialb - Sea ice albedo 174093: - sitemptop - Sea ice surface temperature 174094: - _param_174094 - Mean sea surface temperature 174095: - _param_174095 - 1.5m specific humidity 174096: - 2sh - 2 metre specific humidity 174097: - sisnthick - Sea ice snow thickness 174098: - sithick - Sea-ice thickness 174099: - _param_174099 - Liquid water potential temperature 174110: - _param_174110 - Ocean ice concentration 174111: - _param_174111 - Ocean mean ice depth 174112: - siue - Sea ice velocity along x 174113: - sivn - Sea ice velocity along y 174116: - swrsurf - Short wave radiation flux at surface 174117: - swrtop - Short wave radiation flux at top of atmosphere 174137: - tcwvap - Total column water vapour 174139: - _param_174139 - Soil temperature layer 1 174142: - lsrrate - Large scale rainfall rate 174143: - crfrate - Convective rainfall rate 174164: - _param_174164 - Average potential temperature in upper 293.4m 174167: - _param_174167 - 1.5m temperature 174168: - _param_174168 - 1.5m dewpoint temperature 174170: - _param_174170 - Soil temperature layer 2 174175: - _param_174175 - Average salinity in upper 293.4m 174183: - _param_174183 - Soil temperature layer 3 174186: - vlca - Very low cloud amount 174201: - _param_174201 - 1.5m temperature - maximum in the last 24 hours 174202: - _param_174202 - 1.5m temperature - minimum in the last 24 hours 174236: - _param_174236 - Soil temperature layer 4 174239: - csfrate - Convective snowfall rate 174240: - lsfrate - Large scale snowfall rate 174248: - tccro - Total cloud amount - random overlap 174249: - tcclwr - Total cloud amount in lw radiation 174255: - _param_174255 - Indicates a missing value 175006: - _param_175006 - Total soil moisture 175031: - _param_175031 - Fraction of sea-ice in sea 175034: - _param_175034 - Open-sea surface temperature 175039: - _param_175039 - Volumetric soil water layer 1 175040: - _param_175040 - Volumetric soil water layer 2 175041: - _param_175041 - Volumetric soil water layer 3 175042: - _param_175042 - Volumetric soil water layer 4 175049: - _param_175049 - 10m wind gust in the last 24 hours 175055: - _param_175055 - 1.5m temperature - mean in the last 24 hours 175083: - _param_175083 - Net primary productivity 175085: - _param_175085 - 10m U wind over land 175086: - _param_175086 - 10m V wind over land 175087: - _param_175087 - 1.5m temperature over land 175088: - _param_175088 - 1.5m dewpoint temperature over land 175089: - _param_175089 - Top incoming solar radiation 175090: - _param_175090 - Top outgoing solar radiation 175110: - _param_175110 - Ocean ice concentration 175111: - _param_175111 - Ocean mean ice depth 175139: - _param_175139 - Soil temperature layer 1 175164: - _param_175164 - Average potential temperature in upper 293.4m 175167: - _param_175167 - 1.5m temperature 175168: - _param_175168 - 1.5m dewpoint temperature 175170: - _param_175170 - Soil temperature layer 2 175175: - _param_175175 - Average salinity in upper 293.4m 175183: - _param_175183 - Soil temperature layer 3 175201: - _param_175201 - 1.5m temperature - maximum in the last 24 hours 175202: - _param_175202 - 1.5m temperature - minimum in the last 24 hours 175236: - _param_175236 - Soil temperature layer 4 175255: - _param_175255 - Indicates a missing value 180149: - tsw - Total soil wetness 180176: - ssr - Surface net solar radiation 180177: - str - Surface net thermal radiation 180178: - tsr - Top net solar radiation 180179: - ttr - Top net thermal radiation 190141: - sdsien - Snow depth 190170: - cap - Field capacity 190171: - wiltsien - Wilting point 190173: - sr - Roughness length 190229: - tsm - Total soil moisture 200001: - strfdiff - Stream function difference 200002: - vpotdiff - Velocity potential difference 200003: - ptdiff - Potential temperature difference 200004: - eqptdiff - Equivalent potential temperature difference 200005: - septdiff - Saturated equivalent potential temperature difference 200011: - udvwdiff - U component of divergent wind difference 200012: - vdvwdiff - V component of divergent wind difference 200013: - urtwdiff - U component of rotational wind difference 200014: - vrtwdiff - V component of rotational wind difference 200021: - uctpdiff - Unbalanced component of temperature difference 200022: - uclndiff - Unbalanced component of logarithm of surface pressure difference 200023: - ucdvdiff - Unbalanced component of divergence difference 200024: - _param_200024 - Reserved for future unbalanced components 200025: - _param_200025 - Reserved for future unbalanced components 200026: - cldiff - Lake cover difference 200027: - cvldiff - Low vegetation cover difference 200028: - cvhdiff - High vegetation cover difference 200029: - tvldiff - Type of low vegetation difference 200030: - tvhdiff - Type of high vegetation difference 200031: - sicdiff - Sea-ice cover difference 200032: - asndiff - Snow albedo difference 200033: - rsndiff - Snow density difference 200034: - sstdiff - Sea surface temperature difference 200035: - istl1diff - Ice surface temperature layer 1 difference 200036: - istl2diff - Ice surface temperature layer 2 difference 200037: - istl3diff - Ice surface temperature layer 3 difference 200038: - istl4diff - Ice surface temperature layer 4 difference 200039: - swvl1diff - Volumetric soil water layer 1 difference 200040: - swvl2diff - Volumetric soil water layer 2 difference 200041: - swvl3diff - Volumetric soil water layer 3 difference 200042: - swvl4diff - Volumetric soil water layer 4 difference 200043: - sltdiff - Soil type difference 200044: - esdiff - Snow evaporation difference 200045: - smltdiff - Snowmelt difference 200046: - sdurdiff - Solar duration difference 200047: - dsrpdiff - Direct solar radiation difference 200048: - magssdiff - Magnitude of turbulent surface stress difference 200049: - 10fgdiff - 10 metre wind gust difference 200050: - lspfdiff - Large-scale precipitation fraction difference 200051: - mx2t24diff - Maximum 2 metre temperature difference 200052: - mn2t24diff - Minimum 2 metre temperature difference 200053: - montdiff - Montgomery potential difference 200054: - presdiff - Pressure difference 200055: - mean2t24diff - Mean 2 metre temperature in the last 24 hours difference 200056: - mn2d24diff - Mean 2 metre dewpoint temperature in the last 24 hours difference 200057: - uvbdiff - Downward UV radiation at the surface difference 200058: - pardiff - Photosynthetically active radiation at the surface difference 200059: - capediff - Convective available potential energy difference 200060: - pvdiff - Potential vorticity difference 200061: - tpodiff - Total precipitation from observations difference 200062: - obctdiff - Observation count difference 200063: - _param_200063 - Start time for skin temperature difference 200064: - _param_200064 - Finish time for skin temperature difference 200065: - _param_200065 - Skin temperature difference 200066: - _param_200066 - Leaf area index, low vegetation 200067: - _param_200067 - Leaf area index, high vegetation 200070: - _param_200070 - Biome cover, low vegetation 200071: - _param_200071 - Biome cover, high vegetation 200078: - _param_200078 - Total column liquid water 200079: - _param_200079 - Total column ice water 200080: - _param_200080 - Experimental product 200081: - _param_200081 - Experimental product 200082: - _param_200082 - Experimental product 200083: - _param_200083 - Experimental product 200084: - _param_200084 - Experimental product 200085: - _param_200085 - Experimental product 200086: - _param_200086 - Experimental product 200087: - _param_200087 - Experimental product 200088: - _param_200088 - Experimental product 200089: - _param_200089 - Experimental product 200090: - _param_200090 - Experimental product 200091: - _param_200091 - Experimental product 200092: - _param_200092 - Experimental product 200093: - _param_200093 - Experimental product 200094: - _param_200094 - Experimental product 200095: - _param_200095 - Experimental product 200096: - _param_200096 - Experimental product 200097: - _param_200097 - Experimental product 200098: - _param_200098 - Experimental product 200099: - _param_200099 - Experimental product 200100: - _param_200100 - Experimental product 200101: - _param_200101 - Experimental product 200102: - _param_200102 - Experimental product 200103: - _param_200103 - Experimental product 200104: - _param_200104 - Experimental product 200105: - _param_200105 - Experimental product 200106: - _param_200106 - Experimental product 200107: - _param_200107 - Experimental product 200108: - _param_200108 - Experimental product 200109: - _param_200109 - Experimental product 200110: - _param_200110 - Experimental product 200111: - _param_200111 - Experimental product 200112: - _param_200112 - Experimental product 200113: - _param_200113 - Experimental product 200114: - _param_200114 - Experimental product 200115: - _param_200115 - Experimental product 200116: - _param_200116 - Experimental product 200117: - _param_200117 - Experimental product 200118: - _param_200118 - Experimental product 200119: - _param_200119 - Experimental product 200120: - _param_200120 - Experimental product 200121: - mx2t6diff - Maximum temperature at 2 metres difference 200122: - mn2t6diff - Minimum temperature at 2 metres difference 200123: - 10fg6diff - 10 metre wind gust in the last 6 hours difference 200125: - _param_200125 - Vertically integrated total energy 200126: - _param_200126 - Generic parameter for sensitive area prediction 200127: - atdiff - Atmospheric tide difference 200128: - bvdiff - Budget values difference 200129: - zdiff - Geopotential difference 200130: - tdiff - Temperature difference 200131: - udiff - U component of wind difference 200132: - vdiff - V component of wind difference 200133: - qdiff - Specific humidity difference 200134: - spdiff - Surface pressure difference 200135: - wdiff - Vertical velocity (pressure) difference 200136: - tcwdiff - Total column water difference 200137: - tcwvdiff - Total column water vapour difference 200138: - vodiff - Vorticity (relative) difference 200139: - stl1diff - Soil temperature level 1 difference 200140: - swl1diff - Soil wetness level 1 difference 200141: - sddiff - Snow depth difference 200142: - lspdiff - Stratiform precipitation (Large-scale precipitation) difference 200143: - cpdiff - Convective precipitation difference 200144: - sfdiff - Snowfall (convective + stratiform) difference 200145: - blddiff - Boundary layer dissipation difference 200146: - sshfdiff - Surface sensible heat flux difference 200147: - slhfdiff - Surface latent heat flux difference 200148: - chnkdiff - Charnock difference 200149: - snrdiff - Surface net radiation difference 200150: - tnrdiff - Top net radiation difference 200151: - msldiff - Mean sea level pressure difference 200152: - lnspdiff - Logarithm of surface pressure difference 200153: - swhrdiff - Short-wave heating rate difference 200154: - lwhrdiff - Long-wave heating rate difference 200155: - ddiff - Divergence difference 200156: - ghdiff - Height difference 200157: - rdiff - Relative humidity difference 200158: - tspdiff - Tendency of surface pressure difference 200159: - blhdiff - Boundary layer height difference 200160: - sdordiff - Standard deviation of orography difference 200161: - isordiff - Anisotropy of sub-gridscale orography difference 200162: - anordiff - Angle of sub-gridscale orography difference 200163: - slordiff - Slope of sub-gridscale orography difference 200164: - tccdiff - Total cloud cover difference 200165: - 10udiff - 10 metre U wind component difference 200166: - 10vdiff - 10 metre V wind component difference 200167: - 2tdiff - 2 metre temperature difference 200168: - 2ddiff - 2 metre dewpoint temperature difference 200169: - ssrddiff - Surface solar radiation downwards difference 200170: - stl2diff - Soil temperature level 2 difference 200171: - swl2diff - Soil wetness level 2 difference 200172: - lsmdiff - Land-sea mask difference 200173: - srdiff - Surface roughness difference 200174: - aldiff - Albedo difference 200175: - strddiff - Surface thermal radiation downwards difference 200176: - ssrdiff - Surface net solar radiation difference 200177: - strdiff - Surface net thermal radiation difference 200178: - tsrdiff - Top net solar radiation difference 200179: - ttrdiff - Top net thermal radiation difference 200180: - ewssdiff - East-West surface stress difference 200181: - nsssdiff - North-South surface stress difference 200182: - ediff - Evaporation difference 200183: - stl3diff - Soil temperature level 3 difference 200184: - swl3diff - Soil wetness level 3 difference 200185: - cccdiff - Convective cloud cover difference 200186: - lccdiff - Low cloud cover difference 200187: - mccdiff - Medium cloud cover difference 200188: - hccdiff - High cloud cover difference 200189: - sunddiff - Sunshine duration difference 200190: - ewovdiff - East-West component of sub-gridscale orographic variance difference 200191: - nsovdiff - North-South component of sub-gridscale orographic variance difference 200192: - nwovdiff - North-West/South-East component of sub-gridscale orographic variance difference 200193: - neovdiff - North-East/South-West component of sub-gridscale orographic variance difference 200194: - btmpdiff - Brightness temperature difference 200195: - lgwsdiff - Longitudinal component of gravity wave stress difference 200196: - mgwsdiff - Meridional component of gravity wave stress difference 200197: - gwddiff - Gravity wave dissipation difference 200198: - srcdiff - Skin reservoir content difference 200199: - vegdiff - Vegetation fraction difference 200200: - vsodiff - Variance of sub-gridscale orography difference 200201: - mx2tdiff - Maximum temperature at 2 metres since previous post-processing difference 200202: - mn2tdiff - Minimum temperature at 2 metres since previous post-processing difference 200203: - o3diff - Ozone mass mixing ratio difference 200204: - pawdiff - Precipitation analysis weights difference 200205: - rodiff - Runoff difference 200206: - tco3diff - Total column ozone difference 200207: - 10sidiff - 10 metre wind speed difference 200208: - tsrcdiff - Top net solar radiation, clear sky difference 200209: - ttrcdiff - Top net thermal radiation, clear sky difference 200210: - ssrcdiff - Surface net solar radiation, clear sky difference 200211: - strcdiff - Surface net thermal radiation, clear sky difference 200212: - tisrdiff - TOA incident solar radiation difference 200214: - dhrdiff - Diabatic heating by radiation difference 200215: - dhvddiff - Diabatic heating by vertical diffusion difference 200216: - dhccdiff - Diabatic heating by cumulus convection difference 200217: - dhlcdiff - Diabatic heating large-scale condensation difference 200218: - vdzwdiff - Vertical diffusion of zonal wind difference 200219: - vdmwdiff - Vertical diffusion of meridional wind difference 200220: - ewgddiff - East-West gravity wave drag tendency difference 200221: - nsgddiff - North-South gravity wave drag tendency difference 200222: - ctzwdiff - Convective tendency of zonal wind difference 200223: - ctmwdiff - Convective tendency of meridional wind difference 200224: - vdhdiff - Vertical diffusion of humidity difference 200225: - htccdiff - Humidity tendency by cumulus convection difference 200226: - htlcdiff - Humidity tendency by large-scale condensation difference 200227: - crnhdiff - Change from removal of negative humidity difference 200228: - tpdiff - Total precipitation difference 200229: - iewsdiff - Instantaneous X surface stress difference 200230: - inssdiff - Instantaneous Y surface stress difference 200231: - ishfdiff - Instantaneous surface heat flux difference 200232: - iediff - Instantaneous moisture flux difference 200233: - asqdiff - Apparent surface humidity difference 200234: - lsrhdiff - Logarithm of surface roughness length for heat difference 200235: - sktdiff - Skin temperature difference 200236: - stl4diff - Soil temperature level 4 difference 200237: - swl4diff - Soil wetness level 4 difference 200238: - tsndiff - Temperature of snow layer difference 200239: - csfdiff - Convective snowfall difference 200240: - lsfdiff - Large scale snowfall difference 200241: - acfdiff - Accumulated cloud fraction tendency difference 200242: - alwdiff - Accumulated liquid water tendency difference 200243: - faldiff - Forecast albedo difference 200244: - fsrdiff - Forecast surface roughness difference 200245: - flsrdiff - Forecast logarithm of surface roughness for heat difference 200246: - clwcdiff - Specific cloud liquid water content difference 200247: - ciwcdiff - Specific cloud ice water content difference 200248: - ccdiff - Cloud cover difference 200249: - aiwdiff - Accumulated ice water tendency difference 200250: - icediff - Ice age difference 200251: - attediff - Adiabatic tendency of temperature difference 200252: - athediff - Adiabatic tendency of humidity difference 200253: - atzediff - Adiabatic tendency of zonal wind difference 200254: - atmwdiff - Adiabatic tendency of meridional wind difference 200255: - _param_200255 - Indicates a missing value 201001: - _param_201001 - downward shortwave radiant flux density 201002: - _param_201002 - upward shortwave radiant flux density 201003: - _param_201003 - downward longwave radiant flux density 201004: - _param_201004 - upward longwave radiant flux density 201005: - apab_s - downwd photosynthetic active radiant flux density 201006: - _param_201006 - net shortwave flux 201007: - _param_201007 - net longwave flux 201008: - _param_201008 - total net radiative flux density 201009: - _param_201009 - downw shortw radiant flux density, cloudfree part 201010: - _param_201010 - upw shortw radiant flux density, cloudy part 201011: - _param_201011 - downw longw radiant flux density, cloudfree part 201012: - _param_201012 - upw longw radiant flux density, cloudy part 201013: - sohr_rad - shortwave radiative heating rate 201014: - thhr_rad - longwave radiative heating rate 201015: - _param_201015 - total radiative heating rate 201016: - _param_201016 - soil heat flux, surface 201017: - _param_201017 - soil heat flux, bottom of layer 201029: - clc - fractional cloud cover 201030: - _param_201030 - cloud cover, grid scale 201031: - qc - specific cloud water content 201032: - _param_201032 - cloud water content, grid scale, vert integrated 201033: - qi - specific cloud ice content, grid scale 201034: - _param_201034 - cloud ice content, grid scale, vert integrated 201035: - _param_201035 - specific rainwater content, grid scale 201036: - _param_201036 - specific snow content, grid scale 201037: - _param_201037 - specific rainwater content, gs, vert. integrated 201038: - _param_201038 - specific snow content, gs, vert. integrated 201041: - twater - total column water 201042: - _param_201042 - vert. integral of divergence of tot. water content 201050: - ch_cm_cl - cloud covers CH_CM_CL (000...888) 201051: - _param_201051 - cloud cover CH (0..8) 201052: - _param_201052 - cloud cover CM (0..8) 201053: - _param_201053 - cloud cover CL (0..8) 201054: - _param_201054 - total cloud cover (0..8) 201055: - _param_201055 - fog (0..8) 201056: - _param_201056 - fog 201060: - _param_201060 - cloud cover, convective cirrus 201061: - _param_201061 - specific cloud water content, convective clouds 201062: - _param_201062 - cloud water content, conv clouds, vert integrated 201063: - _param_201063 - specific cloud ice content, convective clouds 201064: - _param_201064 - cloud ice content, conv clouds, vert integrated 201065: - _param_201065 - convective mass flux 201066: - _param_201066 - Updraft velocity, convection 201067: - _param_201067 - entrainment parameter, convection 201068: - hbas_con - cloud base, convective clouds (above msl) 201069: - htop_con - cloud top, convective clouds (above msl) 201070: - _param_201070 - convective layers (00...77) (BKE) 201071: - _param_201071 - KO-index 201072: - bas_con - convection base index 201073: - top_con - convection top index 201074: - dt_con - convective temperature tendency 201075: - dqv_con - convective tendency of specific humidity 201076: - _param_201076 - convective tendency of total heat 201077: - _param_201077 - convective tendency of total water 201078: - du_con - convective momentum tendency (X-component) 201079: - dv_con - convective momentum tendency (Y-component) 201080: - _param_201080 - convective vorticity tendency 201081: - _param_201081 - convective divergence tendency 201082: - htop_dc - top of dry convection (above msl) 201083: - _param_201083 - dry convection top index 201084: - hzerocl - height of 0 degree Celsius isotherm above msl 201085: - snowlmt - height of snow-fall limit 201099: - qrs_gsp - spec. content of precip. particles 201100: - prr_gsp - surface precipitation rate, rain, grid scale 201101: - prs_gsp - surface precipitation rate, snow, grid scale 201102: - rain_gsp - surface precipitation amount, rain, grid scale 201111: - prr_con - surface precipitation rate, rain, convective 201112: - prs_con - surface precipitation rate, snow, convective 201113: - rain_con - surface precipitation amount, rain, convective 201150: - _param_201150 - coefficient of horizontal diffusion 201187: - vmax_10m - Maximum wind velocity 201200: - w_i - water content of interception store 201203: - t_snow - snow temperature 201215: - t_ice - ice surface temperature 201241: - cape_con - convective available potential energy 201255: - _param_201255 - Indicates a missing value 210001: - aermr01 - Sea Salt Aerosol (0.03 - 0.5 um) Mixing Ratio 210002: - aermr02 - Sea Salt Aerosol (0.5 - 5 um) Mixing Ratio 210003: - aermr03 - Sea Salt Aerosol (5 - 20 um) Mixing Ratio 210004: - aermr04 - Dust Aerosol (0.03 - 0.55 um) Mixing Ratio 210005: - aermr05 - Dust Aerosol (0.55 - 0.9 um) Mixing Ratio 210006: - aermr06 - Dust Aerosol (0.9 - 20 um) Mixing Ratio 210007: - aermr07 - Hydrophilic Organic Matter Aerosol Mixing Ratio 210008: - aermr08 - Hydrophobic Organic Matter Aerosol Mixing Ratio 210009: - aermr09 - Hydrophilic Black Carbon Aerosol Mixing Ratio 210010: - aermr10 - Hydrophobic Black Carbon Aerosol Mixing Ratio 210011: - aermr11 - Sulphate Aerosol Mixing Ratio 210012: - aermr12 - SO2 precursor mixing ratio 210013: - aermr13 - Volcanic ash aerosol mixing ratio 210014: - aermr14 - Volcanic sulphate aerosol mixing ratio 210015: - aermr15 - Volcanic SO2 precursor mixing ratio 210016: - aergn01 - Aerosol type 1 source/gain accumulated 210017: - aergn02 - Aerosol type 2 source/gain accumulated 210018: - aergn03 - Aerosol type 3 source/gain accumulated 210019: - aergn04 - Aerosol type 4 source/gain accumulated 210020: - aergn05 - Aerosol type 5 source/gain accumulated 210021: - aergn06 - Aerosol type 6 source/gain accumulated 210022: - aergn07 - Aerosol type 7 source/gain accumulated 210023: - aergn08 - Aerosol type 8 source/gain accumulated 210024: - aergn09 - Aerosol type 9 source/gain accumulated 210025: - aergn10 - Aerosol type 10 source/gain accumulated 210026: - aergn11 - Aerosol type 11 source/gain accumulated 210027: - aergn12 - Aerosol type 12 source/gain accumulated 210028: - aerpr03 - SO4 aerosol precursor mass mixing ratio 210029: - aerwv01 - Water vapour mixing ratio for hydrophilic aerosols in mode 1 210030: - aerwv02 - Water vapour mixing ratio for hydrophilic aerosols in mode 2 210031: - aerls01 - Aerosol type 1 sink/loss accumulated 210032: - aerls02 - Aerosol type 2 sink/loss accumulated 210033: - aerls03 - Aerosol type 3 sink/loss accumulated 210034: - aerls04 - Aerosol type 4 sink/loss accumulated 210035: - aerls05 - Aerosol type 5 sink/loss accumulated 210036: - aerls06 - Aerosol type 6 sink/loss accumulated 210037: - aerls07 - Aerosol type 7 sink/loss accumulated 210038: - aerls08 - Aerosol type 8 sink/loss accumulated 210039: - aerls09 - Aerosol type 9 sink/loss accumulated 210040: - aerls10 - Aerosol type 10 sink/loss accumulated 210041: - aerls11 - Aerosol type 11 sink/loss accumulated 210042: - aerls12 - Aerosol type 12 sink/loss accumulated 210043: - emdms - DMS surface emission 210044: - aerwv03 - Water vapour mixing ratio for hydrophilic aerosols in mode 3 210045: - aerwv04 - Water vapour mixing ratio for hydrophilic aerosols in mode 4 210046: - aerpr - Aerosol precursor mixing ratio 210047: - aersm - Aerosol small mode mixing ratio 210048: - aerlg - Aerosol large mode mixing ratio 210049: - aodpr - Aerosol precursor optical depth 210050: - aodsm - Aerosol small mode optical depth 210051: - aodlg - Aerosol large mode optical depth 210052: - aerdep - Dust emission potential 210053: - aerlts - Lifting threshold speed 210054: - aerscc - Soil clay content 210055: - _param_210055 - Experimental product 210056: - _param_210056 - Experimental product 210057: - ocnuc - Mixing ration of organic carbon aerosol, nucleation mode 210058: - monot - Monoterpene precursor mixing ratio 210059: - soapr - Secondary organic precursor mixing ratio 210060: - injh - Injection height (from IS4FIRES) 210061: - co2 - Carbon dioxide mass mixing ratio 210062: - ch4 - Methane 210063: - n2o - Nitrous oxide 210064: - tcco2 - CO2 column-mean molar fraction 210065: - tcch4 - CH4 column-mean molar fraction 210066: - tcn2o - Total column Nitrous oxide 210067: - co2of - Ocean flux of Carbon Dioxide 210068: - co2nbf - Natural biosphere flux of Carbon Dioxide 210069: - co2apf - Anthropogenic emissions of Carbon Dioxide 210070: - ch4f - Methane Surface Fluxes 210071: - kch4 - Methane loss rate due to radical hydroxyl (OH) 210072: - pm1 - Particulate matter d <= 1 um 210073: - pm2p5 - Particulate matter d <= 2.5 um 210074: - pm10 - Particulate matter d <= 10 um 210079: - vafire - Wildfire viewing angle of observation 210080: - co2fire - Wildfire flux of Carbon Dioxide 210081: - cofire - Wildfire flux of Carbon Monoxide 210082: - ch4fire - Wildfire flux of Methane 210083: - nmhcfire - Wildfire flux of Non-Methane Hydro-Carbons 210084: - h2fire - Wildfire flux of Hydrogen 210085: - noxfire - Wildfire flux of Nitrogen Oxides NOx 210086: - n2ofire - Wildfire flux of Nitrous Oxide 210087: - pm2p5fire - Wildfire flux of Particulate Matter PM2.5 210088: - tpmfire - Wildfire flux of Total Particulate Matter 210089: - tcfire - Wildfire flux of Total Carbon in Aerosols 210090: - ocfire - Wildfire flux of Organic Carbon 210091: - bcfire - Wildfire flux of Black Carbon 210092: - cfire - Wildfire overall flux of burnt Carbon 210093: - c4ffire - Wildfire fraction of C4 plants 210094: - vegfire - Wildfire vegetation map index 210095: - ccfire - Wildfire Combustion Completeness 210096: - flfire - 'Wildfire Fuel Load: Carbon per unit area' 210097: - offire - Wildfire fraction of area observed 210098: - nofrp - Number of positive FRP pixels per grid cell 210099: - frpfire - Wildfire radiative power 210100: - crfire - Wildfire combustion rate 210101: - maxfrpfire - Wildfire radiative power maximum 210102: - so2fire - Wildfire flux of Sulfur Dioxide 210103: - ch3ohfire - Wildfire Flux of Methanol (CH3OH) 210104: - c2h5ohfire - Wildfire Flux of Ethanol (C2H5OH) 210105: - c3h8fire - Wildfire Flux of Propane (C3H8) 210106: - c2h4fire - Wildfire Flux of Ethene (C2H4) 210107: - c3h6fire - Wildfire Flux of Propene (C3H6) 210108: - c5h8fire - Wildfire Flux of Isoprene (C5H8) 210109: - terpenesfire - Wildfire Flux of Terpenes (C5H8)n 210110: - toluenefire - Wildfire Flux of Toluene_lump (C7H8+ C6H6 + C8H10) 210111: - hialkenesfire - Wildfire Flux of Higher Alkenes (CnH2n, C>=4) 210112: - hialkanesfire - Wildfire Flux of Higher Alkanes (CnH2n+2, C>=4) 210113: - ch2ofire - Wildfire Flux of Formaldehyde (CH2O) 210114: - c2h4ofire - Wildfire Flux of Acetaldehyde (C2H4O) 210115: - c3h6ofire - Wildfire Flux of Acetone (C3H6O) 210116: - nh3fire - Wildfire Flux of Ammonia (NH3) 210117: - c2h6sfire - Wildfire Flux of Dimethyl Sulfide (DMS) (C2H6S) 210118: - c2h6fire - Wildfire Flux of Ethane (C2H6) 210119: - mami - Mean height of maximum injection 210120: - apt - Plume top height above surface 210121: - no2 - Nitrogen dioxide mass mixing ratio 210122: - so2 - Sulphur dioxide mass mixing ratio 210123: - co - Carbon monoxide mass mixing ratio 210124: - hcho - Formaldehyde 210125: - tcno2 - Total column Nitrogen dioxide 210126: - tcso2 - Total column Sulphur dioxide 210127: - tcco - Total column Carbon monoxide 210128: - tchcho - Total column Formaldehyde 210129: - nox - Nitrogen Oxides 210130: - tcnox - Total Column Nitrogen Oxides 210131: - grg1 - Reactive tracer 1 mass mixing ratio 210132: - tcgrg1 - Total column GRG tracer 1 210133: - grg2 - Reactive tracer 2 mass mixing ratio 210134: - tcgrg2 - Total column GRG tracer 2 210135: - grg3 - Reactive tracer 3 mass mixing ratio 210136: - tcgrg3 - Total column GRG tracer 3 210137: - grg4 - Reactive tracer 4 mass mixing ratio 210138: - tcgrg4 - Total column GRG tracer 4 210139: - grg5 - Reactive tracer 5 mass mixing ratio 210140: - tcgrg5 - Total column GRG tracer 5 210141: - grg6 - Reactive tracer 6 mass mixing ratio 210142: - tcgrg6 - Total column GRG tracer 6 210143: - grg7 - Reactive tracer 7 mass mixing ratio 210144: - tcgrg7 - Total column GRG tracer 7 210145: - grg8 - Reactive tracer 8 mass mixing ratio 210146: - tcgrg8 - Total column GRG tracer 8 210147: - grg9 - Reactive tracer 9 mass mixing ratio 210148: - tcgrg9 - Total column GRG tracer 9 210149: - grg10 - Reactive tracer 10 mass mixing ratio 210150: - tcgrg10 - Total column GRG tracer 10 210151: - sfnox - Surface flux Nitrogen oxides 210152: - sfno2 - Surface flux Nitrogen dioxide 210153: - sfso2 - Surface flux Sulphur dioxide 210154: - sfco2 - Surface flux Carbon monoxide 210155: - sfhcho - Surface flux Formaldehyde 210156: - sfgo3 - Surface flux GEMS Ozone 210157: - sfgr1 - Surface flux reactive tracer 1 210158: - sfgr2 - Surface flux reactive tracer 2 210159: - sfgr3 - Surface flux reactive tracer 3 210160: - sfgr4 - Surface flux reactive tracer 4 210161: - sfgr5 - Surface flux reactive tracer 5 210162: - sfgr6 - Surface flux reactive tracer 6 210163: - sfgr7 - Surface flux reactive tracer 7 210164: - sfgr8 - Surface flux reactive tracer 8 210165: - sfgr9 - Surface flux reactive tracer 9 210166: - sfgr10 - Surface flux reactive tracer 10 210167: - frpdayfire - Wildfire day-time radiative power 210169: - frpngtfire - Wildfire night-time radiative power 210170: - vso2 - Volcanic sulfur dioxide mass mixing ratio 210177: - frpdayivar - Wildfire day-time inverse variance of radiative power 210179: - frpngtivar - Wildfire night-time inverse variance of radiative power 210181: - ra - Radon 210182: - sf6 - Sulphur Hexafluoride 210183: - tcra - Total column Radon 210184: - tcsf6 - Total column Sulphur Hexafluoride 210185: - sf6apf - Anthropogenic Emissions of Sulphur Hexafluoride 210186: - aluvpi - UV visible albedo for direct radiation, isotropic component (climatological) 210187: - aluvpv - UV visible albedo for direct radiation, volumetric component (climatological) 210188: - aluvpg - UV visible albedo for direct radiation, geometric component (climatological) 210189: - alnipi - Near IR albedo for direct radiation, isotropic component (climatological) 210190: - alnipv - Near IR albedo for direct radiation, volumetric component (climatological) 210191: - alnipg - Near IR albedo for direct radiation, geometric component (climatological) 210192: - aluvdi - UV visible albedo for diffuse radiation, isotropic component (climatological) 210193: - aluvdv - UV visible albedo for diffuse radiation, volumetric component (climatological) 210194: - aluvdg - UV visible albedo for diffuse radiation, geometric component (climatological) 210195: - alnidi - Near IR albedo for diffuse radiation, isotropic component (climatological) 210196: - alnidv - Near IR albedo for diffuse radiation, volumetric component (climatological) 210197: - alnidg - Near IR albedo for diffuse radiation, geometric component (climatological) 210198: - aluvd_p - UV visible albedo for diffuse radiation (climatological) 210199: - aluvp_p - UV visible albedo for direct radiation (climatological) 210200: - aluvpg_p - UV visible albedo for direct radiation, geometric component (climatological) 210201: - aluvpi_p - UV visible albedo for direct radiation, isotropic component (climatological) 210202: - aluvpv_p - UV visible albedo for direct radiation, volumetric component (climatological) 210203: - go3 - Ozone mass mixing ratio (full chemistry scheme) 210206: - gtco3 - GEMS Total column ozone 210207: - aod550 - Total Aerosol Optical Depth at 550nm 210208: - ssaod550 - Sea Salt Aerosol Optical Depth at 550nm 210209: - duaod550 - Dust Aerosol Optical Depth at 550nm 210210: - omaod550 - Organic Matter Aerosol Optical Depth at 550nm 210211: - bcaod550 - Black Carbon Aerosol Optical Depth at 550nm 210212: - suaod550 - Sulphate Aerosol Optical Depth at 550nm 210213: - aod469 - Total Aerosol Optical Depth at 469nm 210214: - aod670 - Total Aerosol Optical Depth at 670nm 210215: - aod865 - Total Aerosol Optical Depth at 865nm 210216: - aod1240 - Total Aerosol Optical Depth at 1240nm 210217: - aod340 - Total aerosol optical depth at 340 nm 210218: - aod355 - Total aerosol optical depth at 355 nm 210219: - aod380 - Total aerosol optical depth at 380 nm 210220: - aod400 - Total aerosol optical depth at 400 nm 210221: - aod440 - Total aerosol optical depth at 440 nm 210222: - aod500 - Total aerosol optical depth at 500 nm 210223: - aod532 - Total aerosol optical depth at 532 nm 210224: - aod645 - Total aerosol optical depth at 645 nm 210225: - aod800 - Total aerosol optical depth at 800 nm 210226: - aod858 - Total aerosol optical depth at 858 nm 210227: - aod1020 - Total aerosol optical depth at 1020 nm 210228: - aod1064 - Total aerosol optical depth at 1064 nm 210229: - aod1640 - Total aerosol optical depth at 1640 nm 210230: - aod2130 - Total aerosol optical depth at 2130 nm 210231: - c7h8fire - Wildfire Flux of Toluene (C7H8) 210232: - c6h6fire - Wildfire Flux of Benzene (C6H6) 210233: - c8h10fire - Wildfire Flux of Xylene (C8H10) 210234: - c4h8fire - Wildfire Flux of Butenes (C4H8) 210235: - c5h10fire - Wildfire Flux of Pentenes (C5H10) 210236: - c6h12fire - Wildfire Flux of Hexene (C6H12) 210237: - c8h16fire - Wildfire Flux of Octene (C8H16) 210238: - c4h10fire - Wildfire Flux of Butanes (C4H10) 210239: - c5h12fire - Wildfire Flux of Pentanes (C5H12) 210240: - c6h14fire - Wildfire Flux of Hexanes (C6H14) 210241: - c7h16fire - Wildfire Flux of Heptane (C7H16) 210242: - apb - Plume bottom height above surface 210243: - vsuaod550 - Volcanic sulphate aerosol optical depth at 550 nm 210244: - vashaod550 - Volcanic ash optical depth at 550 nm 210245: - taedec550 - Profile of total aerosol dry extinction coefficient 210246: - taedab550 - Profile of total aerosol dry absorption coefficient 210247: - aermr16 - Nitrate fine mode aerosol mass mixing ratio 210248: - aermr17 - Nitrate coarse mode aerosol mass mixing ratio 210249: - aermr18 - Ammonium aerosol mass mixing ratio 210250: - niaod550 - Nitrate aerosol optical depth at 550 nm 210251: - amaod550 - Ammonium aerosol optical depth at 550 nm 210252: - aermr19 - Biogenic secondary organic aerosol mass mixing ratio 210253: - aermr20 - Anthropogenic secondary organic aerosol mass mixing ratio 210260: - alnid_p - Near IR albedo for diffuse radiation (climatological) 210261: - alnip_p - Near IR albedo for direct radiation (climatological) 210262: - alnipg_p - Near IR albedo for direct radiation, geometric component (climatological) 210263: - alnipi_p - Near IR albedo for direct radiation, isotropic component (climatological) 210264: - alnipv_p - Near IR albedo for direct radiation, volumetric component (climatological) 211001: - aermr01diff - Sea Salt Aerosol (0.03 - 0.5 um) Mixing Ratio 211002: - aermr02diff - Sea Salt Aerosol (0.5 - 5 um) Mixing Ratio 211003: - aermr03diff - Sea Salt Aerosol (5 - 20 um) Mixing Ratio 211004: - aermr04diff - Dust Aerosol (0.03 - 0.55 um) Mixing Ratio 211005: - aermr05diff - Dust Aerosol (0.55 - 0.9 um) Mixing Ratio 211006: - aermr06diff - Dust Aerosol (0.9 - 20 um) Mixing Ratio 211007: - aermr07diff - Hydrophilic Organic Matter Aerosol Mixing Ratio 211008: - aermr08diff - Hydrophobic Organic Matter Aerosol Mixing Ratio 211009: - aermr09diff - Hydrophilic Black Carbon Aerosol Mixing Ratio 211010: - aermr10diff - Hydrophobic Black Carbon Aerosol Mixing Ratio 211011: - aermr11diff - Sulphate Aerosol Mixing Ratio 211012: - aermr12diff - Aerosol type 12 mixing ratio 211013: - aermr13diff - Aerosol type 13 mass mixing ratio 211014: - aermr14diff - Aerosol type 14 mass mixing ratio 211015: - aermr15diff - Aerosol type 15 mass mixing ratio 211016: - aergn01diff - Aerosol type 1 source/gain accumulated 211017: - aergn02diff - Aerosol type 2 source/gain accumulated 211018: - aergn03diff - Aerosol type 3 source/gain accumulated 211019: - aergn04diff - Aerosol type 4 source/gain accumulated 211020: - aergn05diff - Aerosol type 5 source/gain accumulated 211021: - aergn06diff - Aerosol type 6 source/gain accumulated 211022: - aergn07diff - Aerosol type 7 source/gain accumulated 211023: - aergn08diff - Aerosol type 8 source/gain accumulated 211024: - aergn09diff - Aerosol type 9 source/gain accumulated 211025: - aergn10diff - Aerosol type 10 source/gain accumulated 211026: - aergn11diff - Aerosol type 11 source/gain accumulated 211027: - aergn12diff - Aerosol type 12 source/gain accumulated 211028: - aerpr03diff - SO4 aerosol precursor mass mixing ratio 211029: - aerwv01diff - Water vapour mixing ratio for hydrophilic aerosols in mode 1 211030: - aerwv02diff - Water vapour mixing ratio for hydrophilic aerosols in mode 2 211031: - aerls01diff - Aerosol type 1 sink/loss accumulated 211032: - aerls02diff - Aerosol type 2 sink/loss accumulated 211033: - aerls03diff - Aerosol type 3 sink/loss accumulated 211034: - aerls04diff - Aerosol type 4 sink/loss accumulated 211035: - aerls05diff - Aerosol type 5 sink/loss accumulated 211036: - aerls06diff - Aerosol type 6 sink/loss accumulated 211037: - aerls07diff - Aerosol type 7 sink/loss accumulated 211038: - aerls08diff - Aerosol type 8 sink/loss accumulated 211039: - aerls09diff - Aerosol type 9 sink/loss accumulated 211040: - aerls10diff - Aerosol type 10 sink/loss accumulated 211041: - aerls11diff - Aerosol type 11 sink/loss accumulated 211042: - aerls12diff - Aerosol type 12 sink/loss accumulated 211043: - emdmsdiff - DMS surface emission 211044: - aerwv03diff - Water vapour mixing ratio for hydrophilic aerosols in mode 3 211045: - aerwv04diff - Water vapour mixing ratio for hydrophilic aerosols in mode 4 211046: - aerprdiff - Aerosol precursor mixing ratio 211047: - aersmdiff - Aerosol small mode mixing ratio 211048: - aerlgdiff - Aerosol large mode mixing ratio 211049: - aodprdiff - Aerosol precursor optical depth 211050: - aodsmdiff - Aerosol small mode optical depth 211051: - aodlgdiff - Aerosol large mode optical depth 211052: - aerdepdiff - Dust emission potential 211053: - aerltsdiff - Lifting threshold speed 211054: - aersccdiff - Soil clay content 211055: - _param_211055 - Experimental product 211056: - _param_211056 - Experimental product 211059: - h2odiff - Water vapour (chemistry) difference 211061: - co2diff - Carbon Dioxide 211062: - ch4diff - Methane 211063: - n2odiff - Nitrous oxide 211064: - tcco2diff - Total column Carbon Dioxide 211065: - tcch4diff - Total column Methane 211066: - tcn2odiff - Total column Nitrous oxide 211067: - co2ofdiff - Ocean flux of Carbon Dioxide 211068: - co2nbfdiff - Natural biosphere flux of Carbon Dioxide 211069: - co2apfdiff - Anthropogenic emissions of Carbon Dioxide 211070: - ch4fdiff - Methane Surface Fluxes 211071: - kch4diff - Methane loss rate due to radical hydroxyl (OH) 211080: - co2firediff - Wildfire flux of Carbon Dioxide 211081: - cofirediff - Wildfire flux of Carbon Monoxide 211082: - ch4firediff - Wildfire flux of Methane 211083: - nmhcfirediff - Wildfire flux of Non-Methane Hydro-Carbons 211084: - h2firediff - Wildfire flux of Hydrogen 211085: - noxfirediff - Wildfire flux of Nitrogen Oxides NOx 211086: - n2ofirediff - Wildfire flux of Nitrous Oxide 211087: - pm2p5firediff - Wildfire flux of Particulate Matter PM2.5 211088: - tpmfirediff - Wildfire flux of Total Particulate Matter 211089: - tcfirediff - Wildfire flux of Total Carbon in Aerosols 211090: - ocfirediff - Wildfire flux of Organic Carbon 211091: - bcfirediff - Wildfire flux of Black Carbon 211092: - cfirediff - Wildfire overall flux of burnt Carbon 211093: - c4ffirediff - Wildfire fraction of C4 plants 211094: - vegfirediff - Wildfire vegetation map index 211095: - ccfirediff - Wildfire Combustion Completeness 211096: - flfirediff - 'Wildfire Fuel Load: Carbon per unit area' 211097: - offirediff - Wildfire fraction of area observed 211098: - oafirediff - Wildfire observed area 211099: - frpfirediff - Wildfire radiative power 211100: - crfirediff - Wildfire combustion rate 211101: - maxfrpfirediff - Wildfire radiative power maximum 211102: - so2firediff - Wildfire flux of Sulfur Dioxide 211103: - ch3ohfirediff - Wildfire Flux of Methanol (CH3OH) 211104: - c2h5ohfirediff - Wildfire Flux of Ethanol (C2H5OH) 211105: - c3h8firediff - Wildfire Flux of Propane (C3H8) 211106: - c2h4firediff - Wildfire Flux of Ethene (C2H4) 211107: - c3h6firediff - Wildfire Flux of Propene (C3H6) 211108: - c5h8firediff - Wildfire Flux of Isoprene (C5H8) 211109: - terpenesfirediff - Wildfire Flux of Terpenes (C5H8)n 211110: - toluenefirediff - Wildfire Flux of Toluene_lump (C7H8+ C6H6 + C8H10) 211111: - hialkenesfirediff - Wildfire Flux of Higher Alkenes (CnH2n, C>=4) 211112: - hialkanesfirediff - Wildfire Flux of Higher Alkanes (CnH2n+2, C>=4) 211113: - ch2ofirediff - Wildfire Flux of Formaldehyde (CH2O) 211114: - c2h4ofirediff - Wildfire Flux of Acetaldehyde (C2H4O) 211115: - c3h6ofirediff - Wildfire Flux of Acetone (C3H6O) 211116: - nh3firediff - Wildfire Flux of Ammonia (NH3) 211117: - c2h6sfirediff - Wildfire Flux of Dimethyl Sulfide (DMS) (C2H6S) 211118: - c2h6firediff - Wildfire Flux of Ethane (C2H6) 211119: - alediff - Altitude of emitter 211120: - aptdiff - Altitude of plume top 211121: - no2diff - Nitrogen dioxide mass mixing ratio difference 211122: - so2diff - Sulphur dioxide mass mixing ratio difference 211123: - codiff - Carbon monoxide mass mixing ratio difference 211124: - hchodiff - Formaldehyde 211125: - tcno2diff - Total column Nitrogen dioxide 211126: - tcso2diff - Total column Sulphur dioxide 211127: - tccodiff - Total column Carbon monoxide 211128: - tchchodiff - Total column Formaldehyde 211129: - noxdiff - Nitrogen Oxides 211130: - tcnoxdiff - Total Column Nitrogen Oxides 211131: - grg1diff - Reactive tracer 1 mass mixing ratio 211132: - tcgrg1diff - Total column GRG tracer 1 211133: - grg2diff - Reactive tracer 2 mass mixing ratio 211134: - tcgrg2diff - Total column GRG tracer 2 211135: - grg3diff - Reactive tracer 3 mass mixing ratio 211136: - tcgrg3diff - Total column GRG tracer 3 211137: - grg4diff - Reactive tracer 4 mass mixing ratio 211138: - tcgrg4diff - Total column GRG tracer 4 211139: - grg5diff - Reactive tracer 5 mass mixing ratio 211140: - tcgrg5diff - Total column GRG tracer 5 211141: - grg6diff - Reactive tracer 6 mass mixing ratio 211142: - tcgrg6diff - Total column GRG tracer 6 211143: - grg7diff - Reactive tracer 7 mass mixing ratio 211144: - tcgrg7diff - Total column GRG tracer 7 211145: - grg8diff - Reactive tracer 8 mass mixing ratio 211146: - tcgrg8diff - Total column GRG tracer 8 211147: - grg9diff - Reactive tracer 9 mass mixing ratio 211148: - tcgrg9diff - Total column GRG tracer 9 211149: - grg10diff - Reactive tracer 10 mass mixing ratio 211150: - tcgrg10diff - Total column GRG tracer 10 211151: - sfnoxdiff - Surface flux Nitrogen oxides 211152: - sfno2diff - Surface flux Nitrogen dioxide 211153: - sfso2diff - Surface flux Sulphur dioxide 211154: - sfco2diff - Surface flux Carbon monoxide 211155: - sfhchodiff - Surface flux Formaldehyde 211156: - sfgo3diff - Surface flux GEMS Ozone 211157: - sfgr1diff - Surface flux reactive tracer 1 211158: - sfgr2diff - Surface flux reactive tracer 2 211159: - sfgr3diff - Surface flux reactive tracer 3 211160: - sfgr4diff - Surface flux reactive tracer 4 211161: - sfgr5diff - Surface flux reactive tracer 5 211162: - sfgr6diff - Surface flux reactive tracer 6 211163: - sfgr7diff - Surface flux reactive tracer 7 211164: - sfgr8diff - Surface flux reactive tracer 8 211165: - sfgr9diff - Surface flux reactive tracer 9 211166: - sfgr10diff - Surface flux reactive tracer 10 211170: - vso2diff - Volcanic sulfur dioxide mass mixing ratio increment 211181: - radiff - Radon 211182: - sf6diff - Sulphur Hexafluoride 211183: - tcradiff - Total column Radon 211184: - tcsf6diff - Total column Sulphur Hexafluoride 211185: - sf6apfdiff - Anthropogenic Emissions of Sulphur Hexafluoride 211203: - go3diff - Ozone mass mixing ratio difference (full chemistry scheme) 211206: - gtco3diff - GEMS Total column ozone 211207: - aod550diff - Total Aerosol Optical Depth at 550nm 211208: - ssaod550diff - Sea Salt Aerosol Optical Depth at 550nm 211209: - duaod550diff - Dust Aerosol Optical Depth at 550nm 211210: - omaod550diff - Organic Matter Aerosol Optical Depth at 550nm 211211: - bcaod550diff - Black Carbon Aerosol Optical Depth at 550nm 211212: - suaod550diff - Sulphate Aerosol Optical Depth at 550nm 211213: - aod469diff - Total Aerosol Optical Depth at 469nm 211214: - aod670diff - Total Aerosol Optical Depth at 670nm 211215: - aod865diff - Total Aerosol Optical Depth at 865nm 211216: - aod1240diff - Total Aerosol Optical Depth at 1240nm 211247: - aermr16diff - Nitrate fine mode aerosol mass mixing ratio 211248: - aermr17diff - Nitrate coarse mode aerosol mass mixing ratio 211249: - aermr18diff - Ammonium aerosol mass mixing ratio 211252: - aermr19diff - Biogenic secondary organic aerosol mass mixing ratio increment 211253: - aermr20diff - Anthropogenic secondary organic aerosol mass mixing ratio increment 212001: - _param_212001 - Experimental product 212002: - _param_212002 - Experimental product 212003: - _param_212003 - Experimental product 212004: - _param_212004 - Experimental product 212005: - _param_212005 - Experimental product 212006: - _param_212006 - Experimental product 212007: - _param_212007 - Experimental product 212008: - _param_212008 - Experimental product 212009: - _param_212009 - Experimental product 212010: - _param_212010 - Experimental product 212011: - _param_212011 - Experimental product 212012: - _param_212012 - Experimental product 212013: - _param_212013 - Experimental product 212014: - _param_212014 - Experimental product 212015: - _param_212015 - Experimental product 212016: - _param_212016 - Experimental product 212017: - _param_212017 - Experimental product 212018: - _param_212018 - Experimental product 212019: - _param_212019 - Experimental product 212020: - _param_212020 - Experimental product 212021: - _param_212021 - Experimental product 212022: - _param_212022 - Experimental product 212023: - _param_212023 - Experimental product 212024: - _param_212024 - Experimental product 212025: - _param_212025 - Experimental product 212026: - _param_212026 - Experimental product 212027: - _param_212027 - Experimental product 212028: - _param_212028 - Experimental product 212029: - _param_212029 - Experimental product 212030: - _param_212030 - Experimental product 212031: - _param_212031 - Experimental product 212032: - _param_212032 - Experimental product 212033: - _param_212033 - Experimental product 212034: - _param_212034 - Experimental product 212035: - _param_212035 - Experimental product 212036: - _param_212036 - Experimental product 212037: - _param_212037 - Experimental product 212038: - _param_212038 - Experimental product 212039: - _param_212039 - Experimental product 212040: - _param_212040 - Experimental product 212041: - _param_212041 - Experimental product 212042: - _param_212042 - Experimental product 212043: - _param_212043 - Experimental product 212044: - _param_212044 - Experimental product 212045: - _param_212045 - Experimental product 212046: - _param_212046 - Experimental product 212047: - _param_212047 - Experimental product 212048: - _param_212048 - Experimental product 212049: - _param_212049 - Experimental product 212050: - _param_212050 - Experimental product 212051: - _param_212051 - Experimental product 212052: - _param_212052 - Experimental product 212053: - _param_212053 - Experimental product 212054: - _param_212054 - Experimental product 212055: - _param_212055 - Experimental product 212056: - _param_212056 - Experimental product 212057: - _param_212057 - Experimental product 212058: - _param_212058 - Experimental product 212059: - _param_212059 - Experimental product 212060: - _param_212060 - Experimental product 212061: - _param_212061 - Experimental product 212062: - _param_212062 - Experimental product 212063: - _param_212063 - Experimental product 212064: - _param_212064 - Experimental product 212065: - _param_212065 - Experimental product 212066: - _param_212066 - Experimental product 212067: - _param_212067 - Experimental product 212068: - _param_212068 - Experimental product 212069: - _param_212069 - Experimental product 212070: - _param_212070 - Experimental product 212071: - _param_212071 - Experimental product 212072: - _param_212072 - Experimental product 212073: - _param_212073 - Experimental product 212074: - _param_212074 - Experimental product 212075: - _param_212075 - Experimental product 212076: - _param_212076 - Experimental product 212077: - _param_212077 - Experimental product 212078: - _param_212078 - Experimental product 212079: - _param_212079 - Experimental product 212080: - _param_212080 - Experimental product 212081: - _param_212081 - Experimental product 212082: - _param_212082 - Experimental product 212083: - _param_212083 - Experimental product 212084: - _param_212084 - Experimental product 212085: - _param_212085 - Experimental product 212086: - _param_212086 - Experimental product 212087: - _param_212087 - Experimental product 212088: - _param_212088 - Experimental product 212089: - _param_212089 - Experimental product 212090: - _param_212090 - Experimental product 212091: - _param_212091 - Experimental product 212092: - _param_212092 - Experimental product 212093: - _param_212093 - Experimental product 212094: - _param_212094 - Experimental product 212095: - _param_212095 - Experimental product 212096: - _param_212096 - Experimental product 212097: - _param_212097 - Experimental product 212098: - _param_212098 - Experimental product 212099: - _param_212099 - Experimental product 212100: - _param_212100 - Experimental product 212101: - _param_212101 - Experimental product 212102: - _param_212102 - Experimental product 212103: - _param_212103 - Experimental product 212104: - _param_212104 - Experimental product 212105: - _param_212105 - Experimental product 212106: - _param_212106 - Experimental product 212107: - _param_212107 - Experimental product 212108: - _param_212108 - Experimental product 212109: - _param_212109 - Experimental product 212110: - _param_212110 - Experimental product 212111: - _param_212111 - Experimental product 212112: - _param_212112 - Experimental product 212113: - _param_212113 - Experimental product 212114: - _param_212114 - Experimental product 212115: - _param_212115 - Experimental product 212116: - _param_212116 - Experimental product 212117: - _param_212117 - Experimental product 212118: - _param_212118 - Experimental product 212119: - _param_212119 - Experimental product 212120: - _param_212120 - Experimental product 212121: - _param_212121 - Experimental product 212122: - _param_212122 - Experimental product 212123: - _param_212123 - Experimental product 212124: - _param_212124 - Experimental product 212125: - _param_212125 - Experimental product 212126: - _param_212126 - Experimental product 212127: - _param_212127 - Experimental product 212128: - _param_212128 - Experimental product 212129: - _param_212129 - Experimental product 212130: - _param_212130 - Experimental product 212131: - _param_212131 - Experimental product 212132: - _param_212132 - Experimental product 212133: - _param_212133 - Experimental product 212134: - _param_212134 - Experimental product 212135: - _param_212135 - Experimental product 212136: - _param_212136 - Experimental product 212137: - _param_212137 - Experimental product 212138: - _param_212138 - Experimental product 212139: - _param_212139 - Experimental product 212140: - _param_212140 - Experimental product 212141: - _param_212141 - Experimental product 212142: - _param_212142 - Experimental product 212143: - _param_212143 - Experimental product 212144: - _param_212144 - Experimental product 212145: - _param_212145 - Experimental product 212146: - _param_212146 - Experimental product 212147: - _param_212147 - Experimental product 212148: - _param_212148 - Experimental product 212149: - _param_212149 - Experimental product 212150: - _param_212150 - Experimental product 212151: - _param_212151 - Experimental product 212152: - _param_212152 - Experimental product 212153: - _param_212153 - Experimental product 212154: - _param_212154 - Experimental product 212155: - _param_212155 - Experimental product 212156: - _param_212156 - Experimental product 212157: - _param_212157 - Experimental product 212158: - _param_212158 - Experimental product 212159: - _param_212159 - Experimental product 212160: - _param_212160 - Experimental product 212161: - _param_212161 - Experimental product 212162: - _param_212162 - Experimental product 212163: - _param_212163 - Experimental product 212164: - _param_212164 - Experimental product 212165: - _param_212165 - Experimental product 212166: - _param_212166 - Experimental product 212167: - _param_212167 - Experimental product 212168: - _param_212168 - Experimental product 212169: - _param_212169 - Experimental product 212170: - _param_212170 - Experimental product 212171: - _param_212171 - Experimental product 212172: - _param_212172 - Experimental product 212173: - _param_212173 - Experimental product 212174: - _param_212174 - Experimental product 212175: - _param_212175 - Experimental product 212176: - _param_212176 - Experimental product 212177: - _param_212177 - Experimental product 212178: - _param_212178 - Experimental product 212179: - _param_212179 - Experimental product 212180: - _param_212180 - Experimental product 212181: - _param_212181 - Experimental product 212182: - _param_212182 - Experimental product 212183: - _param_212183 - Experimental product 212184: - _param_212184 - Experimental product 212185: - _param_212185 - Experimental product 212186: - _param_212186 - Experimental product 212187: - _param_212187 - Experimental product 212188: - _param_212188 - Experimental product 212189: - _param_212189 - Experimental product 212190: - _param_212190 - Experimental product 212191: - _param_212191 - Experimental product 212192: - _param_212192 - Experimental product 212193: - _param_212193 - Experimental product 212194: - _param_212194 - Experimental product 212195: - _param_212195 - Experimental product 212196: - _param_212196 - Experimental product 212197: - _param_212197 - Experimental product 212198: - _param_212198 - Experimental product 212199: - _param_212199 - Experimental product 212200: - _param_212200 - Experimental product 212201: - _param_212201 - Experimental product 212202: - _param_212202 - Experimental product 212203: - _param_212203 - Experimental product 212204: - _param_212204 - Experimental product 212205: - _param_212205 - Experimental product 212206: - _param_212206 - Experimental product 212207: - _param_212207 - Experimental product 212208: - _param_212208 - Experimental product 212209: - _param_212209 - Experimental product 212210: - _param_212210 - Experimental product 212211: - _param_212211 - Experimental product 212212: - _param_212212 - Experimental product 212213: - _param_212213 - Experimental product 212214: - _param_212214 - Experimental product 212215: - _param_212215 - Experimental product 212216: - _param_212216 - Experimental product 212217: - _param_212217 - Experimental product 212218: - _param_212218 - Experimental product 212219: - _param_212219 - Experimental product 212220: - _param_212220 - Experimental product 212221: - _param_212221 - Experimental product 212222: - _param_212222 - Experimental product 212223: - _param_212223 - Experimental product 212224: - _param_212224 - Experimental product 212225: - _param_212225 - Experimental product 212226: - _param_212226 - Experimental product 212227: - _param_212227 - Experimental product 212228: - _param_212228 - Experimental product 212229: - _param_212229 - Experimental product 212230: - _param_212230 - Experimental product 212231: - _param_212231 - Experimental product 212232: - _param_212232 - Experimental product 212233: - _param_212233 - Experimental product 212234: - _param_212234 - Experimental product 212235: - _param_212235 - Experimental product 212236: - _param_212236 - Experimental product 212237: - _param_212237 - Experimental product 212238: - _param_212238 - Experimental product 212239: - _param_212239 - Experimental product 212240: - _param_212240 - Experimental product 212241: - _param_212241 - Experimental product 212242: - _param_212242 - Experimental product 212243: - _param_212243 - Experimental product 212244: - _param_212244 - Experimental product 212245: - _param_212245 - Experimental product 212246: - _param_212246 - Experimental product 212247: - _param_212247 - Experimental product 212248: - _param_212248 - Experimental product 212249: - _param_212249 - Experimental product 212250: - _param_212250 - Experimental product 212251: - _param_212251 - Experimental product 212252: - _param_212252 - Experimental product 212253: - _param_212253 - Experimental product 212254: - _param_212254 - Experimental product 212255: - _param_212255 - Experimental product 213001: - sppt1 - Random pattern 1 for sppt 213002: - sppt2 - Random pattern 2 for sppt 213003: - sppt3 - Random pattern 3 for sppt 213004: - sppt4 - Random pattern 4 for sppt 213005: - sppt5 - Random pattern 5 for sppt 213101: - spp1 - Random pattern 1 for SPP scheme 213102: - spp2 - Random pattern 2 for SPP scheme 213103: - spp3 - Random pattern 3 for SPP scheme 213104: - spp4 - Random pattern 4 for SPP scheme 213105: - spp5 - Random pattern 5 for SPP scheme 213106: - spp6 - Random pattern 6 for SPP scheme 213107: - spp7 - Random pattern 7 for SPP scheme 213108: - spp8 - Random pattern 8 for SPP scheme 213109: - spp9 - Random pattern 9 for SPP scheme 213110: - spp10 - Random pattern 10 for SPP scheme 213111: - spp11 - Random pattern 11 for SPP scheme 213112: - spp12 - Random pattern 12 for SPP scheme 213113: - spp13 - Random pattern 13 for SPP scheme 213114: - spp14 - Random pattern 14 for SPP scheme 213115: - spp15 - Random pattern 15 for SPP scheme 213116: - spp16 - Random pattern 16 for SPP scheme 213117: - spp17 - Random pattern 17 for SPP scheme 213118: - spp18 - Random pattern 18 for SPP scheme 213119: - spp19 - Random pattern 19 for SPP scheme 213120: - spp20 - Random pattern 20 for SPP scheme 213121: - spp21 - Random pattern 21 for SPP scheme 213122: - spp22 - Random pattern 22 for SPP scheme 213123: - spp23 - Random pattern 23 for SPP scheme 213124: - spp24 - Random pattern 24 for SPP scheme 213125: - spp25 - Random pattern 25 for SPP scheme 213126: - spp26 - Random pattern 26 for SPP scheme 213127: - spp27 - Random pattern 27 for SPP scheme 213128: - spp28 - Random pattern 28 for SPP scheme 213129: - spp29 - Random pattern 29 for SPP scheme 213130: - spp30 - Random pattern 30 for SPP scheme 213131: - spp31 - Random pattern 31 for SPP scheme 213132: - spp32 - Random pattern 32 for SPP scheme 213133: - spp33 - Random pattern 33 for SPP scheme 213134: - spp34 - Random pattern 34 for SPP scheme 213135: - spp35 - Random pattern 35 for SPP scheme 213136: - spp36 - Random pattern 36 for SPP scheme 213137: - spp37 - Random pattern 37 for SPP scheme 213138: - spp38 - Random pattern 38 for SPP scheme 213139: - spp39 - Random pattern 39 for SPP scheme 213140: - spp40 - Random pattern 40 for SPP scheme 213141: - spp41 - Random pattern 41 for SPP scheme 213142: - spp42 - Random pattern 42 for SPP scheme 213143: - spp43 - Random pattern 43 for SPP scheme 213144: - spp44 - Random pattern 44 for SPP scheme 213145: - spp45 - Random pattern 45 for SPP scheme 213146: - spp46 - Random pattern 46 for SPP scheme 213147: - spp47 - Random pattern 47 for SPP scheme 213148: - spp48 - Random pattern 48 for SPP scheme 213149: - spp49 - Random pattern 49 for SPP scheme 213150: - spp50 - Random pattern 50 for SPP scheme 213151: - spp51 - Random pattern 51 for SPP scheme 213152: - spp52 - Random pattern 52 for SPP scheme 213153: - spp53 - Random pattern 53 for SPP scheme 213154: - spp54 - Random pattern 54 for SPP scheme 213155: - spp55 - Random pattern 55 for SPP scheme 213156: - spp56 - Random pattern 56 for SPP scheme 213157: - spp57 - Random pattern 57 for SPP scheme 213158: - spp58 - Random pattern 58 for SPP scheme 213159: - spp59 - Random pattern 59 for SPP scheme 213160: - spp60 - Random pattern 60 for SPP scheme 213161: - spp61 - Random pattern 61 for SPP scheme 213162: - spp62 - Random pattern 62 for SPP scheme 213163: - spp63 - Random pattern 63 for SPP scheme 213164: - spp64 - Random pattern 64 for SPP scheme 213165: - spp65 - Random pattern 65 for SPP scheme 213166: - spp66 - Random pattern 66 for SPP scheme 213167: - spp67 - Random pattern 67 for SPP scheme 213168: - spp68 - Random pattern 68 for SPP scheme 213169: - spp69 - Random pattern 69 for SPP scheme 213170: - spp70 - Random pattern 70 for SPP scheme 213171: - spp71 - Random pattern 71 for SPP scheme 213172: - spp72 - Random pattern 72 for SPP scheme 213173: - spp73 - Random pattern 73 for SPP scheme 213174: - spp74 - Random pattern 74 for SPP scheme 213175: - spp75 - Random pattern 75 for SPP scheme 213176: - spp76 - Random pattern 76 for SPP scheme 213177: - spp77 - Random pattern 77 for SPP scheme 213178: - spp78 - Random pattern 78 for SPP scheme 213179: - spp79 - Random pattern 79 for SPP scheme 213180: - spp80 - Random pattern 80 for SPP scheme 213181: - spp81 - Random pattern 81 for SPP scheme 213182: - spp82 - Random pattern 82 for SPP scheme 213183: - spp83 - Random pattern 83 for SPP scheme 213184: - spp84 - Random pattern 84 for SPP scheme 213185: - spp85 - Random pattern 85 for SPP scheme 213186: - spp86 - Random pattern 86 for SPP scheme 213187: - spp87 - Random pattern 87 for SPP scheme 213188: - spp88 - Random pattern 88 for SPP scheme 213189: - spp89 - Random pattern 89 for SPP scheme 213190: - spp90 - Random pattern 90 for SPP scheme 213191: - spp91 - Random pattern 91 for SPP scheme 213192: - spp92 - Random pattern 92 for SPP scheme 213193: - spp93 - Random pattern 93 for SPP scheme 213194: - spp94 - Random pattern 94 for SPP scheme 213195: - spp95 - Random pattern 95 for SPP scheme 213196: - spp96 - Random pattern 96 for SPP scheme 213197: - spp97 - Random pattern 97 for SPP scheme 213198: - spp98 - Random pattern 98 for SPP scheme 213199: - spp99 - Random pattern 99 for SPP scheme 213200: - spp100 - Random pattern 100 for SPP scheme 213201: - spp101 - Random pattern 101 for SPP scheme 213202: - spp102 - Random pattern 102 for SPP scheme 213203: - spp103 - Random pattern 103 for SPP scheme 213204: - spp104 - Random pattern 104 for SPP scheme 213205: - spp105 - Random pattern 105 for SPP scheme 213206: - spp106 - Random pattern 106 for SPP scheme 213207: - spp107 - Random pattern 107 for SPP scheme 213208: - spp108 - Random pattern 108 for SPP scheme 213209: - spp109 - Random pattern 109 for SPP scheme 213210: - spp110 - Random pattern 110 for SPP scheme 213211: - spp111 - Random pattern 111 for SPP scheme 213212: - spp112 - Random pattern 112 for SPP scheme 213213: - spp113 - Random pattern 113 for SPP scheme 213214: - spp114 - Random pattern 114 for SPP scheme 213215: - spp115 - Random pattern 115 for SPP scheme 213216: - spp116 - Random pattern 116 for SPP scheme 213217: - spp117 - Random pattern 117 for SPP scheme 213218: - spp118 - Random pattern 118 for SPP scheme 213219: - spp119 - Random pattern 119 for SPP scheme 213220: - spp120 - Random pattern 120 for SPP scheme 213221: - spp121 - Random pattern 121 for SPP scheme 214001: - cossza - Cosine of solar zenith angle 214002: - uvbed - UV biologically effective dose 214003: - uvbedcs - UV biologically effective dose clear-sky 214004: - uvsflxt280285 - Total surface UV spectral flux (280-285 nm) 214005: - uvsflxt285290 - Total surface UV spectral flux (285-290 nm) 214006: - uvsflxt290295 - Total surface UV spectral flux (290-295 nm) 214007: - uvsflxt295300 - Total surface UV spectral flux (295-300 nm) 214008: - uvsflxt300305 - Total surface UV spectral flux (300-305 nm) 214009: - uvsflxt305310 - Total surface UV spectral flux (305-310 nm) 214010: - uvsflxt310315 - Total surface UV spectral flux (310-315 nm) 214011: - uvsflxt315320 - Total surface UV spectral flux (315-320 nm) 214012: - uvsflxt320325 - Total surface UV spectral flux (320-325 nm) 214013: - uvsflxt325330 - Total surface UV spectral flux (325-330 nm) 214014: - uvsflxt330335 - Total surface UV spectral flux (330-335 nm) 214015: - uvsflxt335340 - Total surface UV spectral flux (335-340 nm) 214016: - uvsflxt340345 - Total surface UV spectral flux (340-345 nm) 214017: - uvsflxt345350 - Total surface UV spectral flux (345-350 nm) 214018: - uvsflxt350355 - Total surface UV spectral flux (350-355 nm) 214019: - uvsflxt355360 - Total surface UV spectral flux (355-360 nm) 214020: - uvsflxt360365 - Total surface UV spectral flux (360-365 nm) 214021: - uvsflxt365370 - Total surface UV spectral flux (365-370 nm) 214022: - uvsflxt370375 - Total surface UV spectral flux (370-375 nm) 214023: - uvsflxt375380 - Total surface UV spectral flux (375-380 nm) 214024: - uvsflxt380385 - Total surface UV spectral flux (380-385 nm) 214025: - uvsflxt385390 - Total surface UV spectral flux (385-390 nm) 214026: - uvsflxt390395 - Total surface UV spectral flux (390-395 nm) 214027: - uvsflxt395400 - Total surface UV spectral flux (395-400 nm) 214028: - uvsflxcs280285 - Clear-sky surface UV spectral flux (280-285 nm) 214029: - uvsflxcs285290 - Clear-sky surface UV spectral flux (285-290 nm) 214030: - uvsflxcs290295 - Clear-sky surface UV spectral flux (290-295 nm) 214031: - uvsflxcs295300 - Clear-sky surface UV spectral flux (295-300 nm) 214032: - uvsflxcs300305 - Clear-sky surface UV spectral flux (300-305 nm) 214033: - uvsflxcs305310 - Clear-sky surface UV spectral flux (305-310 nm) 214034: - uvsflxcs310315 - Clear-sky surface UV spectral flux (310-315 nm) 214035: - uvsflxcs315320 - Clear-sky surface UV spectral flux (315-320 nm) 214036: - uvsflxcs320325 - Clear-sky surface UV spectral flux (320-325 nm) 214037: - uvsflxcs325330 - Clear-sky surface UV spectral flux (325-330 nm) 214038: - uvsflxcs330335 - Clear-sky surface UV spectral flux (330-335 nm) 214039: - uvsflxcs335340 - Clear-sky surface UV spectral flux (335-340 nm) 214040: - uvsflxcs340345 - Clear-sky surface UV spectral flux (340-345 nm) 214041: - uvsflxcs345350 - Clear-sky surface UV spectral flux (345-350 nm) 214042: - uvsflxcs350355 - Clear-sky surface UV spectral flux (350-355 nm) 214043: - uvsflxcs355360 - Clear-sky surface UV spectral flux (355-360 nm) 214044: - uvsflxcs360365 - Clear-sky surface UV spectral flux (360-365 nm) 214045: - uvsflxcs365370 - Clear-sky surface UV spectral flux (365-370 nm) 214046: - uvsflxcs370375 - Clear-sky surface UV spectral flux (370-375 nm) 214047: - uvsflxcs375380 - Clear-sky surface UV spectral flux (375-380 nm) 214048: - uvsflxcs380385 - Clear-sky surface UV spectral flux (380-385 nm) 214049: - uvsflxcs385390 - Clear-sky surface UV spectral flux (385-390 nm) 214050: - uvsflxcs390395 - Clear-sky surface UV spectral flux (390-395 nm) 214051: - uvsflxcs395400 - Clear-sky surface UV spectral flux (395-400 nm) 214052: - aot340 - Profile of optical thickness at 340 nm 215001: - aersrcsss - Source/gain of sea salt aerosol (0.03 - 0.5 um) 215002: - aersrcssm - Source/gain of sea salt aerosol (0.5 - 5 um) 215003: - aersrcssl - Source/gain of sea salt aerosol (5 - 20 um) 215004: - aerddpsss - Dry deposition of sea salt aerosol (0.03 - 0.5 um) 215005: - aerddpssm - Dry deposition of sea salt aerosol (0.5 - 5 um) 215006: - aerddpssl - Dry deposition of sea salt aerosol (5 - 20 um) 215007: - aersdmsss - Sedimentation of sea salt aerosol (0.03 - 0.5 um) 215008: - aersdmssm - Sedimentation of sea salt aerosol (0.5 - 5 um) 215009: - aersdmssl - Sedimentation of sea salt aerosol (5 - 20 um) 215010: - aerwdlssss - Wet deposition of sea salt aerosol (0.03 - 0.5 um) by large-scale precipitation 215011: - aerwdlsssm - Wet deposition of sea salt aerosol (0.5 - 5 um) by large-scale precipitation 215012: - aerwdlsssl - Wet deposition of sea salt aerosol (5 - 20 um) by large-scale precipitation 215013: - aerwdccsss - Wet deposition of sea salt aerosol (0.03 - 0.5 um) by convective precipitation 215014: - aerwdccssm - Wet deposition of sea salt aerosol (0.5 - 5 um) by convective precipitation 215015: - aerwdccssl - Wet deposition of sea salt aerosol (5 - 20 um) by convective precipitation 215016: - aerngtsss - Negative fixer of sea salt aerosol (0.03 - 0.5 um) 215017: - aerngtssm - Negative fixer of sea salt aerosol (0.5 - 5 um) 215018: - aerngtssl - Negative fixer of sea salt aerosol (5 - 20 um) 215019: - aermsssss - Vertically integrated mass of sea salt aerosol (0.03 - 0.5 um) 215020: - aermssssm - Vertically integrated mass of sea salt aerosol (0.5 - 5 um) 215021: - aermssssl - Vertically integrated mass of sea salt aerosol (5 - 20 um) 215022: - aerodsss - Sea salt aerosol (0.03 - 0.5 um) optical depth 215023: - aerodssm - Sea salt aerosol (0.5 - 5 um) optical depth 215024: - aerodssl - Sea salt aerosol (5 - 20 um) optical depth 215025: - aersrcdus - Source/gain of dust aerosol (0.03 - 0.55 um) 215026: - aersrcdum - Source/gain of dust aerosol (0.55 - 0.9 um) 215027: - aersrcdul - Source/gain of dust aerosol (0.9 - 20 um) 215028: - aerddpdus - Dry deposition of dust aerosol (0.03 - 0.55 um) 215029: - aerddpdum - Dry deposition of dust aerosol (0.55 - 0.9 um) 215030: - aerddpdul - Dry deposition of dust aerosol (0.9 - 20 um) 215031: - aersdmdus - Sedimentation of dust aerosol (0.03 - 0.55 um) 215032: - aersdmdum - Sedimentation of dust aerosol (0.55 - 0.9 um) 215033: - aersdmdul - Sedimentation of dust aerosol (0.9 - 20 um) 215034: - aerwdlsdus - Wet deposition of dust aerosol (0.03 - 0.55 um) by large-scale precipitation 215035: - aerwdlsdum - Wet deposition of dust aerosol (0.55 - 0.9 um) by large-scale precipitation 215036: - aerwdlsdul - Wet deposition of dust aerosol (0.9 - 20 um) by large-scale precipitation 215037: - aerwdccdus - Wet deposition of dust aerosol (0.03 - 0.55 um) by convective precipitation 215038: - aerwdccdum - Wet deposition of dust aerosol (0.55 - 0.9 um) by convective precipitation 215039: - aerwdccdul - Wet deposition of dust aerosol (0.9 - 20 um) by convective precipitation 215040: - aerngtdus - Negative fixer of dust aerosol (0.03 - 0.55 um) 215041: - aerngtdum - Negative fixer of dust aerosol (0.55 - 0.9 um) 215042: - aerngtdul - Negative fixer of dust aerosol (0.9 - 20 um) 215043: - aermssdus - Vertically integrated mass of dust aerosol (0.03 - 0.55 um) 215044: - aermssdum - Vertically integrated mass of dust aerosol (0.55 - 0.9 um) 215045: - aermssdul - Vertically integrated mass of dust aerosol (0.9 - 20 um) 215046: - aeroddus - Dust aerosol (0.03 - 0.55 um) optical depth 215047: - aeroddum - Dust aerosol (0.55 - 0.9 um) optical depth 215048: - aeroddul - Dust aerosol (0.9 - 20 um) optical depth 215049: - aersrcomhphob - Source/gain of hydrophobic organic matter aerosol 215050: - aersrcomhphil - Source/gain of hydrophilic organic matter aerosol 215051: - aerddpomhphob - Dry deposition of hydrophobic organic matter aerosol 215052: - aerddpomhphil - Dry deposition of hydrophilic organic matter aerosol 215053: - aersdmomhphob - Sedimentation of hydrophobic organic matter aerosol 215054: - aersdmomhphil - Sedimentation of hydrophilic organic matter aerosol 215055: - aerwdlsomhphob - Wet deposition of hydrophobic organic matter aerosol by large-scale precipitation 215056: - aerwdlsomhphil - Wet deposition of hydrophilic organic matter aerosol by large-scale precipitation 215057: - aerwdccomhphob - Wet deposition of hydrophobic organic matter aerosol by convective precipitation 215058: - aerwdccomhphil - Wet deposition of hydrophilic organic matter aerosol by convective precipitation 215059: - aerngtomhphob - Negative fixer of hydrophobic organic matter aerosol 215060: - aerngtomhphil - Negative fixer of hydrophilic organic matter aerosol 215061: - aermssomhphob - Vertically integrated mass of hydrophobic organic matter aerosol 215062: - aermssomhphil - Vertically integrated mass of hydrophilic organic matter aerosol 215063: - aerodomhphob - Hydrophobic organic matter aerosol optical depth 215064: - aerodomhphil - Hydrophilic organic matter aerosol optical depth 215065: - aersrcbchphob - Source/gain of hydrophobic black carbon aerosol 215066: - aersrcbchphil - Source/gain of hydrophilic black carbon aerosol 215067: - aerddpbchphob - Dry deposition of hydrophobic black carbon aerosol 215068: - aerddpbchphil - Dry deposition of hydrophilic black carbon aerosol 215069: - aersdmbchphob - Sedimentation of hydrophobic black carbon aerosol 215070: - aersdmbchphil - Sedimentation of hydrophilic black carbon aerosol 215071: - aerwdlsbchphob - Wet deposition of hydrophobic black carbon aerosol by large-scale precipitation 215072: - aerwdlsbchphil - Wet deposition of hydrophilic black carbon aerosol by large-scale precipitation 215073: - aerwdccbchphob - Wet deposition of hydrophobic black carbon aerosol by convective precipitation 215074: - aerwdccbchphil - Wet deposition of hydrophilic black carbon aerosol by convective precipitation 215075: - aerngtbchphob - Negative fixer of hydrophobic black carbon aerosol 215076: - aerngtbchphil - Negative fixer of hydrophilic black carbon aerosol 215077: - aermssbchphob - Vertically integrated mass of hydrophobic black carbon aerosol 215078: - aermssbchphil - Vertically integrated mass of hydrophilic black carbon aerosol 215079: - aerodbchphob - Hydrophobic black carbon aerosol optical depth 215080: - aerodbchphil - Hydrophilic black carbon aerosol optical depth 215081: - aersrcsu - Source/gain of sulphate aerosol 215082: - aerddpsu - Dry deposition of sulphate aerosol 215083: - aersdmsu - Sedimentation of sulphate aerosol 215084: - aerwdlssu - Wet deposition of sulphate aerosol by large-scale precipitation 215085: - aerwdccsu - Wet deposition of sulphate aerosol by convective precipitation 215086: - aerngtsu - Negative fixer of sulphate aerosol 215087: - aermsssu - Vertically integrated mass of sulphate aerosol 215088: - aerodsu - Sulphate aerosol optical depth 215089: - accaod550 - Accumulated total aerosol optical depth at 550 nm 215090: - aluvpsn - Effective (snow effect included) UV visible albedo for direct radiation 215091: - aerdep10si - 10 metre wind speed dust emission potential 215092: - aerdep10fg - 10 metre wind gustiness dust emission potential 215093: - aot532 - Total aerosol optical thickness at 532 nm 215094: - naot532 - Natural (sea-salt and dust) aerosol optical thickness at 532 nm 215095: - aaot532 - Anthropogenic (black carbon, organic matter, sulphate) aerosol optical thickness at 532 nm 215096: - aodabs340 - Total absorption aerosol optical depth at 340 nm 215097: - aodabs355 - Total absorption aerosol optical depth at 355 nm 215098: - aodabs380 - Total absorption aerosol optical depth at 380 nm 215099: - aodabs400 - Total absorption aerosol optical depth at 400 nm 215100: - aodabs440 - Total absorption aerosol optical depth at 440 nm 215101: - aodabs469 - Total absorption aerosol optical depth at 469 nm 215102: - aodabs500 - Total absorption aerosol optical depth at 500 nm 215103: - aodabs532 - Total absorption aerosol optical depth at 532 nm 215104: - aodabs550 - Total absorption aerosol optical depth at 550 nm 215105: - aodabs645 - Total absorption aerosol optical depth at 645 nm 215106: - aodabs670 - Total absorption aerosol optical depth at 670 nm 215107: - aodabs800 - Total absorption aerosol optical depth at 800 nm 215108: - aodabs858 - Total absorption aerosol optical depth at 858 nm 215109: - aodabs865 - Total absorption aerosol optical depth at 865 nm 215110: - aodabs1020 - Total absorption aerosol optical depth at 1020 nm 215111: - aodabs1064 - Total absorption aerosol optical depth at 1064 nm 215112: - aodabs1240 - Total absorption aerosol optical depth at 1240 nm 215113: - aodabs1640 - Total absorption aerosol optical depth at 1640 nm 215114: - aodfm340 - Total fine mode (r < 0.5 um) aerosol optical depth at 340 nm 215115: - aodfm355 - Total fine mode (r < 0.5 um) aerosol optical depth at 355 nm 215116: - aodfm380 - Total fine mode (r < 0.5 um) aerosol optical depth at 380 nm 215117: - aodfm400 - Total fine mode (r < 0.5 um) aerosol optical depth at 400 nm 215118: - aodfm440 - Total fine mode (r < 0.5 um) aerosol optical depth at 440 nm 215119: - aodfm469 - Total fine mode (r < 0.5 um) aerosol optical depth at 469 nm 215120: - aodfm500 - Total fine mode (r < 0.5 um) aerosol optical depth at 500 nm 215121: - aodfm532 - Total fine mode (r < 0.5 um) aerosol optical depth at 532 nm 215122: - aodfm550 - Total fine mode (r < 0.5 um) aerosol optical depth at 550 nm 215123: - aodfm645 - Total fine mode (r < 0.5 um) aerosol optical depth at 645 nm 215124: - aodfm670 - Total fine mode (r < 0.5 um) aerosol optical depth at 670 nm 215125: - aodfm800 - Total fine mode (r < 0.5 um) aerosol optical depth at 800 nm 215126: - aodfm858 - Total fine mode (r < 0.5 um) aerosol optical depth at 858 nm 215127: - aodfm865 - Total fine mode (r < 0.5 um) aerosol optical depth at 865 nm 215128: - aodfm1020 - Total fine mode (r < 0.5 um) aerosol optical depth at 1020 nm 215129: - aodfm1064 - Total fine mode (r < 0.5 um) aerosol optical depth at 1064 nm 215130: - aodfm1240 - Total fine mode (r < 0.5 um) aerosol optical depth at 1240 nm 215131: - aodfm1640 - Total fine mode (r < 0.5 um) aerosol optical depth at 1640 nm 215132: - ssa340 - Single scattering albedo at 340 nm 215133: - ssa355 - Single scattering albedo at 355 nm 215134: - ssa380 - Single scattering albedo at 380 nm 215135: - ssa400 - Single scattering albedo at 400 nm 215136: - ssa440 - Single scattering albedo at 440 nm 215137: - ssa469 - Single scattering albedo at 469 nm 215138: - ssa500 - Single scattering albedo at 500 nm 215139: - ssa532 - Single scattering albedo at 532 nm 215140: - ssa550 - Single scattering albedo at 550 nm 215141: - ssa645 - Single scattering albedo at 645 nm 215142: - ssa670 - Single scattering albedo at 670 nm 215143: - ssa800 - Single scattering albedo at 800 nm 215144: - ssa858 - Single scattering albedo at 858 nm 215145: - ssa865 - Single scattering albedo at 865 nm 215146: - ssa1020 - Single scattering albedo at 1020 nm 215147: - ssa1064 - Single scattering albedo at 1064 nm 215148: - ssa1240 - Single scattering albedo at 1240 nm 215149: - ssa1640 - Single scattering albedo at 1640 nm 215150: - asymmetry340 - Asymmetry factor at 340 nm 215151: - asymmetry355 - Asymmetry factor at 355 nm 215152: - asymmetry380 - Asymmetry factor at 380 nm 215153: - asymmetry400 - Asymmetry factor at 400 nm 215154: - asymmetry440 - Asymmetry factor at 440 nm 215155: - asymmetry469 - Asymmetry factor at 469 nm 215156: - asymmetry500 - Asymmetry factor at 500 nm 215157: - asymmetry532 - Asymmetry factor at 532 nm 215158: - asymmetry550 - Asymmetry factor at 550 nm 215159: - asymmetry645 - Asymmetry factor at 645 nm 215160: - asymmetry670 - Asymmetry factor at 670 nm 215161: - asymmetry800 - Asymmetry factor at 800 nm 215162: - asymmetry858 - Asymmetry factor at 858 nm 215163: - asymmetry865 - Asymmetry factor at 865 nm 215164: - asymmetry1020 - Asymmetry factor at 1020 nm 215165: - asymmetry1064 - Asymmetry factor at 1064 nm 215166: - asymmetry1240 - Asymmetry factor at 1240 nm 215167: - asymmetry1640 - Asymmetry factor at 1640 nm 215168: - aersrcso2 - Source/gain of sulphur dioxide 215169: - aerddpso2 - Dry deposition of sulphur dioxide 215170: - aersdmso2 - Sedimentation of sulphur dioxide 215171: - aerwdlsso2 - Wet deposition of sulphur dioxide by large-scale precipitation 215172: - aerwdccso2 - Wet deposition of sulphur dioxide by convective precipitation 215173: - aerngtso2 - Negative fixer of sulphur dioxide 215174: - aermssso2 - Vertically integrated mass of sulphur dioxide 215175: - aerodso2 - Sulphur dioxide optical depth 215176: - aodabs2130 - Total absorption aerosol optical depth at 2130 nm 215177: - aodfm2130 - Total fine mode (r < 0.5 um) aerosol optical depth at 2130 nm 215178: - ssa2130 - Single scattering albedo at 2130 nm 215179: - asymmetry2130 - Asymmetry factor at 2130 nm 215180: - aerext355 - Aerosol extinction coefficient at 355 nm 215181: - aerext532 - Aerosol extinction coefficient at 532 nm 215182: - aerext1064 - Aerosol extinction coefficient at 1064 nm 215183: - aerbackscattoa355 - Aerosol backscatter coefficient at 355 nm (from top of atmosphere) 215184: - aerbackscattoa532 - Aerosol backscatter coefficient at 532 nm (from top of atmosphere) 215185: - aerbackscattoa1064 - Aerosol backscatter coefficient at 1064 nm (from top of atmosphere) 215186: - aerbackscatgnd355 - Aerosol backscatter coefficient at 355 nm (from ground) 215187: - aerbackscatgnd532 - Aerosol backscatter coefficient at 532 nm (from ground) 215188: - aerbackscatgnd1064 - Aerosol backscatter coefficient at 1064 nm (from ground) 215189: - aersrcnif - Source/gain of fine-mode nitrate aerosol 215190: - aersrcnic - Source/gain of coarse-mode nitrate aerosol 215191: - aerddpnif - Dry deposition of fine-mode nitrate aerosol 215192: - aerddpnic - Dry deposition of coarse-mode nitrate aerosol 215193: - aersdmnif - Sedimentation of fine-mode nitrate aerosol 215194: - aersdmnic - Sedimentation of coarse-mode nitrate aerosol 215195: - aerwdlnif - Wet deposition of fine-mode nitrate aerosol by large-scale precipitation 215196: - aerwdlnic - Wet deposition of coarse-mode nitrate aerosol by large-scale precipitation 215197: - aerwdcnif - Wet deposition of fine-mode nitrate aerosol by convective precipitation 215198: - aerwdcnic - Wet deposition of coarse-mode nitrate aerosol by convective precipitation 215199: - aerngtnif - Negative fixer of fine-mode nitrate aerosol 215200: - aerngtnic - Negative fixer of coarse-mode nitrate aerosol 215201: - aermssnif - Vertically integrated mass of fine-mode nitrate aerosol 215202: - aermssnic - Vertically integrated mass of coarse-mode nitrate aerosol 215203: - aerodnif - Fine-mode nitrate aerosol optical depth at 550 nm 215204: - aerodnic - Coarse-mode nitrate aerosol optical depth at 550 nm 215205: - aersrcam - Source/gain of ammonium aerosol 215206: - aerddpam - Dry deposition of ammonium aerosol 215207: - aersdmam - Sedimentation of ammonium aerosol 215208: - aerwdlam - Wet deposition of ammonium aerosol by large-scale precipitation 215209: - aerwdcam - Wet deposition of ammonium aerosol by convective precipitation 215210: - aerngtam - Negative fixer of ammonium aerosol 215211: - aermssam - Vertically integrated mass of ammonium aerosol 215212: - aersrcsoab - Source/gain of biogenic secondary organic aerosol 215213: - aerddpsoab - Dry deposition of biogenic secondary organic aerosol 215214: - aersdmsoab - Sedimentation of biogenic secondary organic aerosol 215215: - aerwdlsoab - Wet deposition of biogenic secondary organic aerosol by large-scale precipitation 215216: - aerwdcsoab - Wet deposition of biogenic secondary organic aerosol by convective precipitation 215217: - aerngtsoab - Negative fixer of biogenic secondary organic aerosol 215218: - aermsssoab - Vertically integrated mass of biogenic secondary organic aerosol 215219: - aersrcsoaa - Source/gain of anthropogenic secondary organic aerosol 215220: - aerddpsoaa - Dry deposition of anthropogenic secondary organic aerosol 215221: - aersdmsoaa - Sedimentation of anthropogenic secondary organic aerosol 215222: - aerwdlsoaa - Wet deposition of anthropogenic secondary organic aerosol by large-scale precipitation 215223: - aerwdcsoaa - Wet deposition of anthropogenic secondary organic aerosol by convective precipitation 215224: - aerngtsoaa - Negative fixer of anthropogenic secondary organic aerosol 215225: - aermsssoaa - Vertically integrated mass of anthropogenic secondary organic aerosol 215226: - soaod550 - Secondary organic aerosol optical depth at 550 nm 216001: - _param_216001 - Experimental product 216002: - _param_216002 - Experimental product 216003: - _param_216003 - Experimental product 216004: - _param_216004 - Experimental product 216005: - _param_216005 - Experimental product 216006: - _param_216006 - Experimental product 216007: - _param_216007 - Experimental product 216008: - _param_216008 - Experimental product 216009: - _param_216009 - Experimental product 216010: - _param_216010 - Experimental product 216011: - _param_216011 - Experimental product 216012: - _param_216012 - Experimental product 216013: - _param_216013 - Experimental product 216014: - _param_216014 - Experimental product 216015: - _param_216015 - Experimental product 216016: - _param_216016 - Experimental product 216017: - _param_216017 - Experimental product 216018: - _param_216018 - Experimental product 216019: - _param_216019 - Experimental product 216020: - _param_216020 - Experimental product 216021: - _param_216021 - Experimental product 216022: - _param_216022 - Experimental product 216023: - _param_216023 - Experimental product 216024: - _param_216024 - Experimental product 216025: - _param_216025 - Experimental product 216026: - _param_216026 - Experimental product 216027: - _param_216027 - Experimental product 216028: - _param_216028 - Experimental product 216029: - _param_216029 - Experimental product 216030: - _param_216030 - Experimental product 216031: - _param_216031 - Experimental product 216032: - _param_216032 - Experimental product 216033: - _param_216033 - Experimental product 216034: - _param_216034 - Experimental product 216035: - _param_216035 - Experimental product 216036: - _param_216036 - Experimental product 216037: - _param_216037 - Experimental product 216038: - _param_216038 - Experimental product 216039: - _param_216039 - Experimental product 216040: - _param_216040 - Experimental product 216041: - _param_216041 - Experimental product 216042: - _param_216042 - Experimental product 216043: - _param_216043 - Experimental product 216044: - _param_216044 - Experimental product 216045: - _param_216045 - Experimental product 216046: - _param_216046 - Experimental product 216047: - _param_216047 - Experimental product 216048: - _param_216048 - Experimental product 216049: - _param_216049 - Experimental product 216050: - _param_216050 - Experimental product 216051: - _param_216051 - Experimental product 216052: - _param_216052 - Experimental product 216053: - _param_216053 - Experimental product 216054: - _param_216054 - Experimental product 216055: - _param_216055 - Experimental product 216056: - _param_216056 - Experimental product 216057: - _param_216057 - Experimental product 216058: - _param_216058 - Experimental product 216059: - _param_216059 - Experimental product 216060: - _param_216060 - Experimental product 216061: - _param_216061 - Experimental product 216062: - _param_216062 - Experimental product 216063: - _param_216063 - Experimental product 216064: - _param_216064 - Experimental product 216065: - _param_216065 - Experimental product 216066: - _param_216066 - Experimental product 216067: - _param_216067 - Experimental product 216068: - _param_216068 - Experimental product 216069: - _param_216069 - Experimental product 216070: - _param_216070 - Experimental product 216071: - _param_216071 - Experimental product 216072: - _param_216072 - Experimental product 216073: - _param_216073 - Experimental product 216074: - _param_216074 - Experimental product 216075: - _param_216075 - Experimental product 216076: - _param_216076 - Experimental product 216077: - _param_216077 - Experimental product 216078: - _param_216078 - Experimental product 216079: - _param_216079 - Experimental product 216080: - _param_216080 - Experimental product 216081: - _param_216081 - Experimental product 216082: - _param_216082 - Experimental product 216083: - _param_216083 - Experimental product 216084: - _param_216084 - Experimental product 216085: - _param_216085 - Experimental product 216086: - _param_216086 - Experimental product 216087: - _param_216087 - Experimental product 216088: - _param_216088 - Experimental product 216089: - _param_216089 - Experimental product 216090: - _param_216090 - Experimental product 216091: - _param_216091 - Experimental product 216092: - _param_216092 - Experimental product 216093: - _param_216093 - Experimental product 216094: - _param_216094 - Experimental product 216095: - _param_216095 - Experimental product 216096: - _param_216096 - Experimental product 216097: - _param_216097 - Experimental product 216098: - _param_216098 - Experimental product 216099: - _param_216099 - Experimental product 216100: - _param_216100 - Experimental product 216101: - _param_216101 - Experimental product 216102: - _param_216102 - Experimental product 216103: - _param_216103 - Experimental product 216104: - _param_216104 - Experimental product 216105: - _param_216105 - Experimental product 216106: - _param_216106 - Experimental product 216107: - _param_216107 - Experimental product 216108: - _param_216108 - Experimental product 216109: - _param_216109 - Experimental product 216110: - _param_216110 - Experimental product 216111: - _param_216111 - Experimental product 216112: - _param_216112 - Experimental product 216113: - _param_216113 - Experimental product 216114: - _param_216114 - Experimental product 216115: - _param_216115 - Experimental product 216116: - _param_216116 - Experimental product 216117: - _param_216117 - Experimental product 216118: - _param_216118 - Experimental product 216119: - _param_216119 - Experimental product 216120: - _param_216120 - Experimental product 216121: - _param_216121 - Experimental product 216122: - _param_216122 - Experimental product 216123: - _param_216123 - Experimental product 216124: - _param_216124 - Experimental product 216125: - _param_216125 - Experimental product 216126: - _param_216126 - Experimental product 216127: - _param_216127 - Experimental product 216128: - _param_216128 - Experimental product 216129: - _param_216129 - Experimental product 216130: - _param_216130 - Experimental product 216131: - _param_216131 - Experimental product 216132: - _param_216132 - Experimental product 216133: - _param_216133 - Experimental product 216134: - _param_216134 - Experimental product 216135: - _param_216135 - Experimental product 216136: - _param_216136 - Experimental product 216137: - _param_216137 - Experimental product 216138: - _param_216138 - Experimental product 216139: - _param_216139 - Experimental product 216140: - _param_216140 - Experimental product 216141: - _param_216141 - Experimental product 216142: - _param_216142 - Experimental product 216143: - _param_216143 - Experimental product 216144: - _param_216144 - Experimental product 216145: - _param_216145 - Experimental product 216146: - _param_216146 - Experimental product 216147: - _param_216147 - Experimental product 216148: - _param_216148 - Experimental product 216149: - _param_216149 - Experimental product 216150: - _param_216150 - Experimental product 216151: - _param_216151 - Experimental product 216152: - _param_216152 - Experimental product 216153: - _param_216153 - Experimental product 216154: - _param_216154 - Experimental product 216155: - _param_216155 - Experimental product 216156: - _param_216156 - Experimental product 216157: - _param_216157 - Experimental product 216158: - _param_216158 - Experimental product 216159: - _param_216159 - Experimental product 216160: - _param_216160 - Experimental product 216161: - _param_216161 - Experimental product 216162: - _param_216162 - Experimental product 216163: - _param_216163 - Experimental product 216164: - _param_216164 - Experimental product 216165: - _param_216165 - Experimental product 216166: - _param_216166 - Experimental product 216167: - _param_216167 - Experimental product 216168: - _param_216168 - Experimental product 216169: - _param_216169 - Experimental product 216170: - _param_216170 - Experimental product 216171: - _param_216171 - Experimental product 216172: - _param_216172 - Experimental product 216173: - _param_216173 - Experimental product 216174: - _param_216174 - Experimental product 216175: - _param_216175 - Experimental product 216176: - _param_216176 - Experimental product 216177: - _param_216177 - Experimental product 216178: - _param_216178 - Experimental product 216179: - _param_216179 - Experimental product 216180: - _param_216180 - Experimental product 216181: - _param_216181 - Experimental product 216182: - _param_216182 - Experimental product 216183: - _param_216183 - Experimental product 216184: - _param_216184 - Experimental product 216185: - _param_216185 - Experimental product 216186: - _param_216186 - Experimental product 216187: - _param_216187 - Experimental product 216188: - _param_216188 - Experimental product 216189: - _param_216189 - Experimental product 216190: - _param_216190 - Experimental product 216191: - _param_216191 - Experimental product 216192: - _param_216192 - Experimental product 216193: - _param_216193 - Experimental product 216194: - _param_216194 - Experimental product 216195: - _param_216195 - Experimental product 216196: - _param_216196 - Experimental product 216197: - _param_216197 - Experimental product 216198: - _param_216198 - Experimental product 216199: - _param_216199 - Experimental product 216200: - _param_216200 - Experimental product 216201: - _param_216201 - Experimental product 216202: - _param_216202 - Experimental product 216203: - _param_216203 - Experimental product 216204: - _param_216204 - Experimental product 216205: - _param_216205 - Experimental product 216206: - _param_216206 - Experimental product 216207: - _param_216207 - Experimental product 216208: - _param_216208 - Experimental product 216209: - _param_216209 - Experimental product 216210: - _param_216210 - Experimental product 216211: - _param_216211 - Experimental product 216212: - _param_216212 - Experimental product 216213: - _param_216213 - Experimental product 216214: - _param_216214 - Experimental product 216215: - _param_216215 - Experimental product 216216: - _param_216216 - Experimental product 216217: - _param_216217 - Experimental product 216218: - _param_216218 - Experimental product 216219: - _param_216219 - Experimental product 216220: - _param_216220 - Experimental product 216221: - _param_216221 - Experimental product 216222: - _param_216222 - Experimental product 216223: - _param_216223 - Experimental product 216224: - _param_216224 - Experimental product 216225: - _param_216225 - Experimental product 216226: - _param_216226 - Experimental product 216227: - _param_216227 - Experimental product 216228: - _param_216228 - Experimental product 216229: - _param_216229 - Experimental product 216230: - _param_216230 - Experimental product 216231: - _param_216231 - Experimental product 216232: - _param_216232 - Experimental product 216233: - _param_216233 - Experimental product 216234: - _param_216234 - Experimental product 216235: - _param_216235 - Experimental product 216236: - _param_216236 - Experimental product 216237: - _param_216237 - Experimental product 216238: - _param_216238 - Experimental product 216239: - _param_216239 - Experimental product 216240: - _param_216240 - Experimental product 216241: - _param_216241 - Experimental product 216242: - _param_216242 - Experimental product 216243: - _param_216243 - Experimental product 216244: - _param_216244 - Experimental product 216245: - _param_216245 - Experimental product 216246: - _param_216246 - Experimental product 216247: - _param_216247 - Experimental product 216248: - _param_216248 - Experimental product 216249: - _param_216249 - Experimental product 216250: - _param_216250 - Experimental product 216251: - _param_216251 - Experimental product 216252: - _param_216252 - Experimental product 216253: - _param_216253 - Experimental product 216254: - _param_216254 - Experimental product 216255: - _param_216255 - Experimental product 217003: - h2o2 - Hydrogen peroxide 217004: - ch4_c - Methane (chemistry) 217006: - hno3 - Nitric acid 217007: - ch3ooh - Methyl peroxide 217009: - par - Paraffins 217010: - c2h4 - Ethene 217011: - ole - Olefins 217012: - ald2 - Aldehydes 217013: - pan - Peroxyacetyl nitrate 217014: - rooh - Peroxides 217015: - onit - Organic nitrates 217016: - c5h8 - Isoprene 217018: - dms - Dimethyl sulfide 217019: - nh3 - Ammonia mass mixing ratio 217020: - so4 - Sulfate 217021: - nh4 - Ammonium 217022: - msa - Methane sulfonic acid 217023: - ch3cocho - Methyl glyoxal 217024: - o3s - Stratospheric ozone 217026: - pb - Lead 217027: - 'no' - Nitrogen monoxide mass mixing ratio 217028: - ho2 - Hydroperoxy radical 217029: - ch3o2 - Methylperoxy radical 217030: - oh - Hydroxyl radical 217032: - no3 - Nitrate radical 217033: - n2o5 - Dinitrogen pentoxide 217034: - ho2no2 - Pernitric acid 217035: - c2o3 - Peroxy acetyl radical 217036: - ror - Organic ethers 217037: - rxpar - PAR budget corrector 217038: - xo2 - NO to NO2 operator 217039: - xo2n - NO to alkyl nitrate operator 217040: - nh2 - Amine 217041: - psc - Polar stratospheric cloud 217042: - ch3oh - Methanol 217043: - hcooh - Formic acid 217044: - mcooh - Methacrylic acid 217045: - c2h6 - Ethane 217046: - c2h5oh - Ethanol 217047: - c3h8 - Propane 217048: - c3h6 - Propene 217049: - c10h16 - Terpenes 217050: - ispd - Methacrolein MVK 217051: - no3_a - Nitrate 217052: - ch3coch3 - Acetone 217053: - aco2 - Acetone product 217054: - ic3h7o2 217055: - hypropo2 217056: - noxa - Nitrogen oxides Transp 217057: - co2_c - Carbon dioxide (chemistry) 217058: - n2o_c - Nitrous oxide (chemistry) 217059: - h2o - Water vapour (chemistry) 217060: - o2 - Oxygen 217061: - o2_1s - Singlet oxygen 217062: - o2_1d - Singlet delta oxygen 217063: - oclo - Chlorine dioxide 217064: - clono2 - Chlorine nitrate 217065: - hocl - Hypochlorous acid 217066: - cl2 - Chlorine 217067: - clno2 - Nitryl chloride 217068: - hbr - Hydrogen bromide 217069: - cl2o2 - Dichlorine dioxide 217070: - hobr - Hypobromous acid 217071: - cfc11 - Trichlorofluoromethane 217072: - cfc12 - Dichlorodifluoromethane 217073: - cfc113 - Trichlorotrifluoroethane 217074: - cfc114 - Dichlorotetrafluoroethane 217075: - cfc115 - Chloropentafluoroethane 217076: - ccl4 - Tetrachloromethane 217077: - ch3ccl3 - Methyl chloroform 217078: - ch3cl - Methyl chloride 217079: - hcfc22 - Chlorodifluoromethane 217080: - ch3br - Methyl bromide 217081: - ha1202 - Dibromodifluoromethane 217082: - ha1211 - Bromochlorodifluoromethane 217083: - ha1301 - Trifluorobromomethane 217084: - ha2402 - Cbrf2cbrf2 217085: - h2so4 - Sulfuric acid 217086: - hono - Nitrous acid 217087: - hc3 - Alkanes low oh rate 217088: - hc5 - Alkanes med oh rate 217089: - hc8 - Alkanes high oh rate 217090: - olt - Terminal alkenes 217091: - oli - Internal alkenes 217092: - c2h5o2 - Ethylperoxy radical 217093: - dien - Butadiene 217094: - c2h5ooh - Ethyl hydroperoxide 217095: - api - A-pinene cyclic terpenes 217096: - ch3cooh - Acetic acid 217097: - lim - D-limonene cyclic diene 217098: - ch3cho - Acetaldehyde 217099: - tol - Toluene and less reactive aromatics 217100: - xyl - Xylene and more reactive aromatics 217101: - glyald - Glycolaldehyde 217102: - cresol 217103: - ald - Acetaldehyde and higher 217104: - ch3coooh - Peracetic acid 217105: - ket - Ketones 217106: - eo2 - Hoch2ch2o2 217107: - glyoxal 217108: - eo - Hoch2ch2o 217109: - dcb - Unsaturated dicarbonyls 217110: - macr - Methacrolein 217111: - udd - Unsaturated hydroxy dicarbonyl 217112: - c3h7o2 - Isopropyldioxidanyl 217113: - hket - Hydroxy ketone 217114: - c3h7ooh - Isopropyl hydroperoxide 217115: - po2 - C3h6oho2 217116: - pooh - C3h6ohooh 217117: - op2 - Higher organic peroxides 217118: - hyac - Hydroxyacetone 217119: - paa - Peroxyacetic acid 217120: - ro2 - Ch3coch2o2 217121: - ethp - Peroxy radical from c2h6 217122: - hc3p - Peroxy radical from hc3 217123: - hc5p - Peroxy radical from hc5 217124: - bigene - Lumped alkenes 217125: - hc8p - Peroxy radical from hc8 217126: - bigalk - Lumped alkanes 217127: - etep - Peroxy radical from c2h4 217128: - mek - C4h8o 217129: - oltp - Peroxy radical from terminal alkenes 217130: - eneo2 - C4h9o3 217131: - olip - Peroxy radical from internal alkenes 217132: - meko2 - Ch3coch(oo)ch3 217133: - isopo2 - Peroxy radical from c5h8 217134: - mekooh - Ch3coch(ooh)ch3 217135: - apip - Peroxy radical from a-pinene cyclic terpenes 217136: - mco3 - Ch2=c(ch3)co3 217137: - limp - Peroxy radical from d-limonene cyclic diene 217138: - mvk - Methylvinylketone 217139: - pho - Phenoxy radical 217140: - tolp - Peroxy radical from toluene and less reactive aromatics 217141: - macro2 - Ch3c(o)ch(oo)ch2oh 217142: - xylp - Peroxy radical from xylene and more reactive aromatics 217143: - macrooh - H3c(o)ch(ooh)ch2oh 217144: - cslp - Peroxy radical from cresol 217145: - mpan - Unsaturated pans 217146: - tco3_c - Unsaturated acyl peroxy radical 217147: - ketp - Peroxy radical from ketones 217148: - alko2 - C5h11o2 217149: - olnn - No3-alkenes adduct reacting to form carbonitrates 217150: - alkooh - C5h11ooh 217151: - olnd - No3-alkenes adduct reacting via decomposition 217152: - bigald - Hoch2c(ch3)=chcho 217153: - hydrald - C5h6o2 217154: - sulf - Trop sulfuric acid 217155: - ox - Oxides 217156: - isopno3 - Ch2chc(ch3)(oo)ch2ono2 217157: - onitr - C3 organic nitrate 217158: - clox - Chlorine oxides 217159: - brox - Bromine oxides 217160: - xooh - Hoch2c(ooh)(ch3)chchoh 217161: - isopooh - Hoch2c(ooh)(ch3)ch=ch2 217162: - toluene - Lumped aromatics 217163: - dmso - Dimethyl sulfoxyde 217164: - tolo2 - C7h9o5 217165: - tolooh - C7h10o5 217166: - h2s - Hydrogensulfide 217167: - xoh - C7h10o6 217168: - noy - All nitrogen oxides 217169: - cly - Chlorine family 217170: - terpo2 - C10h16(oh)(oo) 217171: - bry - Bromine family 217172: - terpooh - C10h18o3 217173: - n - Nitrogen atom 217174: - clo - Chlorine monoxide 217175: - cl_c - Chlorine atom 217176: - bro - Bromine monoxide 217177: - h_c - Hydrogen atom 217178: - ch3 - Methyl group 217179: - addt - Aromatic-ho from toluene and less reactive aromatics 217180: - addx - Aromatic-ho from xylene and more reactive aromatics 217181: - nh4no3 - Ammonium nitrate 217182: - addc - Aromatic-ho from csl 217183: - soa1 - Secondary organic aerosol type 1 217184: - soa2a - Secondary organic aerosol type 2a 217185: - soa2b - Secondary organic aerosol type 2b 217186: - sog1 - Condensable gas type 1 217187: - sog2a - Condensable gas type 2a 217188: - sog2b - Condensable gas type 2b 217189: - so3 - Sulfur trioxide 217190: - ocs_c - Carbonyl sulfide 217191: - br - Bromine atom 217192: - br2 - Bromine 217193: - brcl - Bromine monochloride 217194: - brono2 - Bromine nitrate 217195: - ch2br2 - Dibromomethane 217196: - ch3o - Methoxy radical 217197: - chbr3 - Tribromomethane 217198: - cloo - Asymmetric chlorine dioxide radical 217199: - h2 - Hydrogen 217200: - hcl - Hydrogen chloride 217201: - hco - Formyl radical 217202: - hf - Hydrogen fluoride 217203: - o - Oxygen atom 217204: - o1d - Excited oxygen atom 217205: - o3p - Ground state oxygen atom 217206: - strataer - Stratospheric aerosol 217222: - aroo2 - Aromatic peroxy radical mass mixing ratio 217223: - c2h2 - Ethyne mass mixing ratio 217224: - ch3cn - Acetonitrile mass mixing ratio 217225: - ch3o2no2 - Methyl peroxy nitrate mass mixing ratio 217226: - hcn - Hydrogen cyanide mass mixing ratio 217227: - hpald1 - Hydroperoxy aldehydes type 1 mass mixing ratio 217228: - hpald - Hydroperoxy aldehydes type 2 mass mixing ratio 217229: - isopbo2 - Isoprene peroxy type B mass mixing ratio 217230: - isopdo2 - Isoprene peroxy type D mass mixing ratio 217231: - voca - Anthropogenic volatile organic compounds mass mixing ratio 217232: - vocbb - Biomass burning volatile organic compounds mass mixing ratio 218003: - tc_h2o2 - Total column hydrogen peroxide 218004: - tc_ch4 - Total column methane 218006: - tc_hno3 - Total column nitric acid 218007: - tc_ch3ooh - Total column methyl peroxide 218009: - tc_par - Total column paraffins 218010: - tc_c2h4 - Total column ethene 218011: - tc_ole - Total column olefins 218012: - tc_ald2 - Total column aldehydes 218013: - tc_pan - Total column peroxyacetyl nitrate 218014: - tc_rooh - Total column peroxides 218015: - tc_onit - Total column organic nitrates 218016: - tc_c5h8 - Total column isoprene 218018: - tc_dms - Total column dimethyl sulfide 218019: - tc_nh3 - Total column ammonia 218020: - tc_so4 - Total column sulfate 218021: - tc_nh4 - Total column ammonium 218022: - tc_msa - Total column methane sulfonic acid 218023: - tc_ch3cocho - Total column methyl glyoxal 218024: - tc_o3s - Total column stratospheric ozone 218026: - tc_pb - Total column lead 218027: - tc_no - Total column nitrogen monoxide 218028: - tc_ho2 - Total column hydroperoxy radical 218029: - tc_ch3o2 - Total column methylperoxy radical 218030: - tc_oh - Total column hydroxyl radical 218032: - tc_no3 - Total column nitrate radical 218033: - tc_n2o5 - Total column dinitrogen pentoxide 218034: - tc_ho2no2 - Total column pernitric acid 218035: - tc_c2o3 - Total column peroxy acetyl radical 218036: - tc_ror - Total column organic ethers 218037: - tc_rxpar - Total column PAR budget corrector 218038: - tc_xo2 - Total column NO to NO2 operator 218039: - tc_xo2n - Total column NO to alkyl nitrate operator 218040: - tc_nh2 - Total column amine 218041: - tc_psc - Total column polar stratospheric cloud 218042: - tc_ch3oh - Total column methanol 218043: - tc_hcooh - Total column formic acid 218044: - tc_mcooh - Total column methacrylic acid 218045: - tc_c2h6 - Total column ethane 218046: - tc_c2h5oh - Total column ethanol 218047: - tc_c3h8 - Total column propane 218048: - tc_c3h6 - Total column propene 218049: - tc_c10h16 - Total column terpenes 218050: - tc_ispd - Total column methacrolein MVK 218051: - tc_no3_a - Total column nitrate 218052: - tc_ch3coch3 - Total column acetone 218053: - tc_aco2 - Total column acetone product 218054: - tc_ic3h7o2 - Total column IC3H7O2 218055: - tc_hypropo2 - Total column HYPROPO2 218056: - tc_noxa - Total column nitrogen oxides Transp 218057: - tc_co2_c - Total column of carbon dioxide (chemistry) 218058: - tc_n2o_c - Total column of nitrous oxide (chemistry) 218059: - tc_h2o - Total column of water vapour (chemistry) 218060: - tc_o2 - Total column of oxygen 218061: - tc_o2_1s - Total column of singlet oxygen 218062: - tc_o2_1d - Total column of singlet delta oxygen 218063: - tc_oclo - Total column of chlorine dioxide 218064: - tc_clono2 - Total column of chlorine nitrate 218065: - tc_hocl - Total column of hypochlorous acid 218066: - tc_cl2 - Total column of chlorine 218067: - tc_clno2 - Total column of nitryl chloride 218068: - tc_hbr - Total column of hydrogen bromide 218069: - tc_cl2o2 - Total column of dichlorine dioxide 218070: - tc_hobr - Total column of hypobromous acid 218071: - tc_cfc11 - Total column of trichlorofluoromethane 218072: - tc_cfc12 - Total column of dichlorodifluoromethane 218073: - tc_cfc113 - Total column of trichlorotrifluoroethane 218074: - tc_cfc114 - Total column of dichlorotetrafluoroethane 218075: - tc_cfc115 - Total column of chloropentafluoroethane 218076: - tc_ccl4 - Total column of tetrachloromethane 218077: - tc_ch3ccl3 - Total column of methyl chloroform 218078: - tc_ch3cl - Total column of methyl chloride 218079: - tc_hcfc22 - Total column of chlorodifluoromethane 218080: - tc_ch3br - Total column of methyl bromide 218081: - tc_ha1202 - Total column of dibromodifluoromethane 218082: - tc_ha1211 - Total column of bromochlorodifluoromethane 218083: - tc_ha1301 - Total column of trifluorobromomethane 218084: - tc_ha2402 - Total column of cbrf2cbrf2 218085: - tc_h2so4 - Total column of sulfuric acid 218086: - tc_hono - Total column of nitrous acid 218087: - tc_hc3 - Total column of alkanes low oh rate 218088: - tc_hc5 - Total column of alkanes med oh rate 218089: - tc_hc8 - Total column of alkanes high oh rate 218090: - tc_olt - Total column of terminal alkenes 218091: - tc_oli - Total column of internal alkenes 218092: - tc_c2h5o2 - Total column of ethylperoxy radical 218093: - tc_dien - Total column of butadiene 218094: - tc_c2h5ooh - Total column of ethyl hydroperoxide 218095: - tc_api - Total column of a-pinene cyclic terpenes 218096: - tc_ch3cooh - Total column of acetic acid 218097: - tc_lim - Total column of d-limonene cyclic diene 218098: - tc_ch3cho - Total column of acetaldehyde 218099: - tc_tol - Total column of toluene and less reactive aromatics 218100: - tc_xyl - Total column of xylene and more reactive aromatics 218101: - tc_glyald - Total column of glycolaldehyde 218102: - tc_cresol - Total column of cresol 218103: - tc_ald - Total column of acetaldehyde and higher 218104: - tc_ch3coooh - Total column of peracetic acid 218105: - tc_ket - Total column of ketones 218106: - tc_eo2 - Total column of hoch2ch2o2 218107: - tc_glyoxal - Total column of glyoxal 218108: - tc_eo - Total column of hoch2ch2o 218109: - tc_dcb - Total column of unsaturated dicarbonyls 218110: - tc_macr - Total column of methacrolein 218111: - tc_udd - Total column of unsaturated hydroxy dicarbonyl 218112: - tc_c3h7o2 - Total column of isopropyldioxidanyl 218113: - tc_hket - Total column of hydroxy ketone 218114: - tc_c3h7ooh - Total column of isopropyl hydroperoxide 218115: - tc_po2 - Total column of c3h6oho2 218116: - tc_pooh - Total column of c3h6ohooh 218117: - tc_op2 - Total column of higher organic peroxides 218118: - tc_hyac - Total column of hydroxyacetone 218119: - tc_paa - Total column of peroxyacetic acid 218120: - tc_ro2 - Total column of ch3coch2o2 218121: - tc_ethp - Total column of peroxy radical from c2h6 218122: - tc_hc3p - Total column of peroxy radical from hc3 218123: - tc_hc5p - Total column of peroxy radical from hc5 218124: - tc_bigene - Total column of lumped alkenes 218125: - tc_hc8p - Total column of peroxy radical from hc8 218126: - tc_bigalk - Total column of lumped alkanes 218127: - tc_etep - Total column of peroxy radical from c2h4 218128: - tc_mek - Total column of c4h8o 218129: - tc_oltp - Total column of peroxy radical from terminal alkenes 218130: - tc_eneo2 - Total column of c4h9o3 218131: - tc_olip - Total column of peroxy radical from internal alkenes 218132: - tc_meko2 - Total column of ch3coch(oo)ch3 218133: - tc_isopo2 - Total column of peroxy radical from c5h8 218134: - tc_mekooh - Total column of ch3coch(ooh)ch3 218135: - tc_apip - Total column of peroxy radical from a-pinene cyclic terpenes 218136: - tc_mco3 - Total column of ch2=c(ch3)co3 218137: - tc_limp - Total column of peroxy radical from d-limonene cyclic diene 218138: - tc_mvk - Total column of methylvinylketone 218139: - tc_pho - Total column of phenoxy radical 218140: - tc_tolp - Total column of peroxy radical from toluene and less reactive aromatics 218141: - tc_macro2 - Total column of ch3c(o)ch(oo)ch2oh 218142: - tc_xylp - Total column of peroxy radical from xylene and more reactive aromatics 218143: - tc_macrooh - Total column of h3c(o)ch(ooh)ch2oh 218144: - tc_cslp - Total column of peroxy radical from cresol 218145: - tc_mpan - Total column of unsaturated pans 218146: - tc_tco3_c - Total column of unsaturated acyl peroxy radical 218147: - tc_ketp - Total column of peroxy radical from ketones 218148: - tc_alko2 - Total column of c5h11o2 218149: - tc_olnn - Total column of no3-alkenes adduct reacting to form carbonitrates 218150: - tc_alkooh - Total column of c5h11ooh 218151: - tc_olnd - Total column of no3-alkenes adduct reacting via decomposition 218152: - tc_bigald - Total column of hoch2c(ch3)=chcho 218153: - tc_hydrald - Total column of c5h6o2 218154: - tc_sulf - Total column of trop sulfuric acid 218155: - tc_ox - Total column of oxides 218156: - tc_isopno3 - Total column of ch2chc(ch3)(oo)ch2ono2 218157: - tc_onitr - Total column of c3 organic nitrate 218158: - tc_clox - Total column of chlorine oxides 218159: - tc_brox - Total column of bromine oxides 218160: - tc_xooh - Total column of hoch2c(ooh)(ch3)chchoh 218161: - tc_isopooh - Total column of hoch2c(ooh)(ch3)ch=ch2 218162: - tc_toluene - Total column of lumped aromatics 218163: - tc_dmso - Total column of dimethyl sulfoxyde 218164: - tc_tolo2 - Total column of c7h9o5 218165: - tc_tolooh - Total column of c7h10o5 218166: - tc_h2s - Total column of hydrogensulfide 218167: - tc_xoh - Total column of c7h10o6 218168: - tc_noy - Total column of all nitrogen oxides 218169: - tc_cly - Total column of chlorine family 218170: - tc_terpo2 - Total column of c10h16(oh)(oo) 218171: - tc_bry - Total column of bromine family 218172: - tc_terpooh - Total column of c10h18o3 218173: - tc_n - Total column of nitrogen atom 218174: - tc_clo - Total column of chlorine monoxide 218175: - tc_cl_c - Total column of chlorine atom 218176: - tc_bro - Total column of bromine monoxide 218177: - tc_h_c - Total column of hydrogen atom 218178: - tc_ch3 - Total column of methyl group 218179: - tc_addt - Total column of aromatic-ho from toluene and less reactive aromatics 218180: - tc_addx - Total column of aromatic-ho from xylene and more reactive aromatics 218181: - tc_nh4no3 - Total column of ammonium nitrate 218182: - tc_addc - Total column of aromatic-ho from csl 218183: - tc_soa1 - Total column of secondary organic aerosol type 1 218184: - tc_soa2a - Total column of secondary organic aerosol type 2a 218185: - tc_soa2b - Total column of secondary organic aerosol type 2b 218186: - tc_sog1 - Total column of condensable gas type 1 218187: - tc_sog2a - Total column of condensable gas type 2a 218188: - tc_sog2b - Total column of condensable gas type 2b 218189: - tc_so3 - Total column of sulfur trioxide 218190: - tc_ocs_c - Total column of carbonyl sulfide 218191: - tc_br - Total column of bromine atom 218192: - tc_br2 - Total column of bromine 218193: - tc_brcl - Total column of bromine monochloride 218194: - tc_brono2 - Total column of bromine nitrate 218195: - tc_ch2br2 - Total column of dibromomethane 218196: - tc_ch3o - Total column of methoxy radical 218197: - tc_chbr3 - Total column of tribromomethane 218198: - tc_cloo - Total column of asymmetric chlorine dioxide radical 218199: - tc_h2 - Total column of hydrogen 218200: - tc_hcl - Total column of hydrogen chloride 218201: - tc_hco - Total column of formyl radical 218202: - tc_hf - Total column of hydrogen fluoride 218203: - tc_o - Total column of oxygen atom 218204: - tc_o1d - Total column of excited oxygen atom 218205: - tc_o3p - Total column of ground state oxygen atom 218206: - tc_strataer - Total column of stratospheric aerosol 218221: - tc_vso2 - Column integrated mass density of Volcanic sulfur dioxide 218222: - tc_aroo2 - Column integrated mass density of Aromatic peroxy radical 218223: - tc_c2h2 - Column integrated mass density of Ethyne 218224: - tc_ch3cn - Column integrated mass density of Acetonitrile 218225: - tc_ch3o2no2 - Column integrated mass density of Methyl peroxy nitrate 218226: - tc_hcn - Column integrated mass density of Hydrogen cyanide 218227: - tc_hpald1 - Column integrated mass density of Hydroperoxy aldehydes type 1 218228: - tc_hpald2 - Column integrated mass density of Hydroperoxy aldehydes type 2 218229: - tc_isopbo2 - Column integrated mass density of Isoprene peroxy type B 218230: - tc_isopdo2 - Column integrated mass density of Isoprene peroxy type D 218231: - tc_voca - Column integrated mass density of Anthropogenic volatile organic compounds 218232: - tc_vocbb - Column integrated mass density of Biomass burning volatile organic compounds 219001: - e_go3 - Ozone emissions 219002: - e_nox - Nitrogen oxides emissions 219003: - e_h2o2 - Hydrogen peroxide emissions 219004: - e_ch4 - Methane emissions 219005: - e_co - Carbon monoxide emissions 219006: - e_hno3 - Nitric acid emissions 219007: - e_ch3ooh - Methyl peroxide emissions 219008: - e_hcho - Formaldehyde emissions 219009: - e_par - Paraffins emissions 219010: - e_c2h4 - Ethene emissions 219011: - e_ole - Olefins emissions 219012: - e_ald2 - Aldehydes emissions 219013: - e_pan - Peroxyacetyl nitrate emissions 219014: - e_rooh - Peroxides emissions 219015: - e_onit - Organic nitrates emissions 219016: - e_c5h8 - Isoprene emissions 219017: - e_so2 - Sulfur dioxide emissions 219018: - e_dms - Dimethyl sulfide emissions 219019: - e_nh3 - Ammonia emissions 219020: - e_so4 - Sulfate emissions 219021: - e_nh4 - Ammonium emissions 219022: - e_msa - Methane sulfonic acid emissions 219023: - e_ch3cocho - Methyl glyoxal emissions 219024: - e_o3s - Stratospheric ozone emissions 219025: - e_ra - Radon emissions 219026: - e_pb - Lead emissions 219027: - e_no - Nitrogen monoxide emissions 219028: - e_ho2 - Hydroperoxy radical emissions 219029: - e_ch3o2 - Methylperoxy radical emissions 219030: - e_oh - Hydroxyl radical emissions 219031: - e_no2 - Nitrogen dioxide emissions 219032: - e_no3 - Nitrate radical emissions 219033: - e_n2o5 - Dinitrogen pentoxide emissions 219034: - e_ho2no2 - Pernitric acid emissions 219035: - e_c2o3 - Peroxy acetyl radical emissions 219036: - e_ror - Organic ethers emissions 219037: - e_rxpar - PAR budget corrector emissions 219038: - e_xo2 - NO to NO2 operator emissions 219039: - e_xo2n - NO to alkyl nitrate operator emissions 219040: - e_nh2 - Amine emissions 219041: - e_psc - Polar stratospheric cloud emissions 219042: - e_ch3oh - Methanol emissions 219043: - e_hcooh - Formic acid emissions 219044: - e_mcooh - Methacrylic acid emissions 219045: - e_c2h6 - Ethane emissions 219046: - e_c2h5oh - Ethanol emissions 219047: - e_c3h8 - Propane emissions 219048: - e_c3h6 - Propene emissions 219049: - e_c10h16 - Terpenes emissions 219050: - e_ispd - Methacrolein MVK emissions 219051: - e_no3_a - Nitrate emissions 219052: - e_ch3coch3 - Acetone emissions 219053: - e_aco2 - Acetone product emissions 219054: - e_ic3h7o2 - IC3H7O2 emissions 219055: - e_hypropo2 - HYPROPO2 emissions 219056: - e_noxa - Nitrogen oxides Transp emissions 219057: - e_co2_c - Emissions of carbon dioxide (chemistry) 219058: - e_n2o_c - Emissions of nitrous oxide (chemistry) 219059: - e_h2o - Emissions of water vapour (chemistry) 219060: - e_o2 - Emissions of oxygen 219061: - e_o2_1s - Emissions of singlet oxygen 219062: - e_o2_1d - Emissions of singlet delta oxygen 219063: - e_oclo - Emissions of chlorine dioxide 219064: - e_clono2 - Emissions of chlorine nitrate 219065: - e_hocl - Emissions of hypochlorous acid 219066: - e_cl2 - Emissions of chlorine 219067: - e_clno2 - Emissions of nitryl chloride 219068: - e_hbr - Emissions of hydrogen bromide 219069: - e_cl2o2 - Emissions of dichlorine dioxide 219070: - e_hobr - Emissions of hypobromous acid 219071: - e_cfc11 - Emissions of trichlorofluoromethane 219072: - e_cfc12 - Emissions of dichlorodifluoromethane 219073: - e_cfc113 - Emissions of trichlorotrifluoroethane 219074: - e_cfc114 - Emissions of dichlorotetrafluoroethane 219075: - e_cfc115 - Emissions of chloropentafluoroethane 219076: - e_ccl4 - Emissions of tetrachloromethane 219077: - e_ch3ccl3 - Emissions of methyl chloroform 219078: - e_ch3cl - Emissions of methyl chloride 219079: - e_hcfc22 - Emissions of chlorodifluoromethane 219080: - e_ch3br - Emissions of methyl bromide 219081: - e_ha1202 - Emissions of dibromodifluoromethane 219082: - e_ha1211 - Emissions of bromochlorodifluoromethane 219083: - e_ha1301 - Emissions of trifluorobromomethane 219084: - e_ha2402 - Emissions of cbrf2cbrf2 219085: - e_h2so4 - Emissions of sulfuric acid 219086: - e_hono - Emissions of nitrous acid 219087: - e_hc3 - Emissions of alkanes low oh rate 219088: - e_hc5 - Emissions of alkanes med oh rate 219089: - e_hc8 - Emissions of alkanes high oh rate 219090: - e_olt - Emissions of terminal alkenes 219091: - e_oli - Emissions of internal alkenes 219092: - e_c2h5o2 - Emissions of ethylperoxy radical 219093: - e_dien - Emissions of butadiene 219094: - e_c2h5ooh - Emissions of ethyl hydroperoxide 219095: - e_api - Emissions of a-pinene cyclic terpenes 219096: - e_ch3cooh - Emissions of acetic acid 219097: - e_lim - Emissions of d-limonene cyclic diene 219098: - e_ch3cho - Emissions of acetaldehyde 219099: - e_tol - Emissions of toluene and less reactive aromatics 219100: - e_xyl - Emissions of xylene and more reactive aromatics 219101: - e_glyald - Emissions of glycolaldehyde 219102: - e_cresol - Emissions of cresol 219103: - e_ald - Emissions of acetaldehyde and higher 219104: - e_ch3coooh - Emissions of peracetic acid 219105: - e_ket - Emissions of ketones 219106: - e_eo2 - Emissions of hoch2ch2o2 219107: - e_glyoxal - Emissions of glyoxal 219108: - e_eo - Emissions of hoch2ch2o 219109: - e_dcb - Emissions of unsaturated dicarbonyls 219110: - e_macr - Emissions of methacrolein 219111: - e_udd - Emissions of unsaturated hydroxy dicarbonyl 219112: - e_c3h7o2 - Emissions of isopropyldioxidanyl 219113: - e_hket - Emissions of hydroxy ketone 219114: - e_c3h7ooh - Emissions of isopropyl hydroperoxide 219115: - e_po2 - Emissions of c3h6oho2 219116: - e_pooh - Emissions of c3h6ohooh 219117: - e_op2 - Emissions of higher organic peroxides 219118: - e_hyac - Emissions of hydroxyacetone 219119: - e_paa - Emissions of peroxyacetic acid 219120: - e_ro2 - Emissions of ch3coch2o2 219121: - e_ethp - Emissions of peroxy radical from c2h6 219122: - e_hc3p - Emissions of peroxy radical from hc3 219123: - e_hc5p - Emissions of peroxy radical from hc5 219124: - e_bigene - Emissions of lumped alkenes 219125: - e_hc8p - Emissions of peroxy radical from hc8 219126: - e_bigalk - Emissions of lumped alkanes 219127: - e_etep - Emissions of peroxy radical from c2h4 219128: - e_mek - Emissions of c4h8o 219129: - e_oltp - Emissions of peroxy radical from terminal alkenes 219130: - e_eneo2 - Emissions of c4h9o3 219131: - e_olip - Emissions of peroxy radical from internal alkenes 219132: - e_meko2 - Emissions of ch3coch(oo)ch3 219133: - e_isopo2 - Emissions of peroxy radical from c5h8 219134: - e_mekooh - Emissions of ch3coch(ooh)ch3 219135: - e_apip - Emissions of peroxy radical from a-pinene cyclic terpenes 219136: - e_mco3 - Emissions of ch2=c(ch3)co3 219137: - e_limp - Emissions of peroxy radical from d-limonene cyclic diene 219138: - e_mvk - Emissions of methylvinylketone 219139: - e_pho - Emissions of phenoxy radical 219140: - e_tolp - Emissions of peroxy radical from toluene and less reactive aromatics 219141: - e_macro2 - Emissions of ch3c(o)ch(oo)ch2oh 219142: - e_xylp - Emissions of peroxy radical from xylene and more reactive aromatics 219143: - e_macrooh - Emissions of h3c(o)ch(ooh)ch2oh 219144: - e_cslp - Emissions of peroxy radical from cresol 219145: - e_mpan - Emissions of unsaturated pans 219146: - e_tco3_c - Emissions of unsaturated acyl peroxy radical 219147: - e_ketp - Emissions of peroxy radical from ketones 219148: - e_alko2 - Emissions of c5h11o2 219149: - e_olnn - Emissions of no3-alkenes adduct reacting to form carbonitrates 219150: - e_alkooh - Emissions of c5h11ooh 219151: - e_olnd - Emissions of no3-alkenes adduct reacting via decomposition 219152: - e_bigald - Emissions of hoch2c(ch3)=chcho 219153: - e_hydrald - Emissions of c5h6o2 219154: - e_sulf - Emissions of trop sulfuric acid 219155: - e_ox - Emissions of oxides 219156: - e_isopno3 - Emissions of ch2chc(ch3)(oo)ch2ono2 219157: - e_onitr - Emissions of c3 organic nitrate 219158: - e_clox - Emissions of chlorine oxides 219159: - e_brox - Emissions of bromine oxides 219160: - e_xooh - Emissions of hoch2c(ooh)(ch3)chchoh 219161: - e_isopooh - Emissions of hoch2c(ooh)(ch3)ch=ch2 219162: - e_toluene - Emissions of lumped aromatics 219163: - e_dmso - Emissions of dimethyl sulfoxyde 219164: - e_tolo2 - Emissions of c7h9o5 219165: - e_tolooh - Emissions of c7h10o5 219166: - e_h2s - Emissions of hydrogensulfide 219167: - e_xoh - Emissions of c7h10o6 219168: - e_noy - Emissions of all nitrogen oxides 219169: - e_cly - Emissions of chlorine family 219170: - e_terpo2 - Emissions of c10h16(oh)(oo) 219171: - e_bry - Emissions of bromine family 219172: - e_terpooh - Emissions of c10h18o3 219173: - e_n - Emissions of nitrogen atom 219174: - e_clo - Emissions of chlorine monoxide 219175: - e_cl_c - Emissions of chlorine atom 219176: - e_bro - Emissions of bromine monoxide 219177: - e_h_c - Emissions of hydrogen atom 219178: - e_ch3 - Emissions of methyl group 219179: - e_addt - Emissions of aromatic-ho from toluene and less reactive aromatics 219180: - e_addx - Emissions of aromatic-ho from xylene and more reactive aromatics 219181: - e_nh4no3 - Emissions of ammonium nitrate 219182: - e_addc - Emissions of aromatic-ho from csl 219183: - e_soa1 - Emissions of secondary organic aerosol type 1 219184: - e_soa2a - Emissions of secondary organic aerosol type 2a 219185: - e_soa2b - Emissions of secondary organic aerosol type 2b 219186: - e_sog1 - Emissions of condensable gas type 1 219187: - e_sog2a - Emissions of condensable gas type 2a 219188: - e_sog2b - Emissions of condensable gas type 2b 219189: - e_so3 - Emissions of sulfur trioxide 219190: - e_ocs_c - Emissions of carbonyl sulfide 219191: - e_br - Emissions of bromine atom 219192: - e_br2 - Emissions of bromine 219193: - e_brcl - Emissions of bromine monochloride 219194: - e_brono2 - Emissions of bromine nitrate 219195: - e_ch2br2 - Emissions of dibromomethane 219196: - e_ch3o - Emissions of methoxy radical 219197: - e_chbr3 - Emissions of tribromomethane 219198: - e_cloo - Emissions of asymmetric chlorine dioxide radical 219199: - e_h2 - Emissions of hydrogen 219200: - e_hcl - Emissions of hydrogen chloride 219201: - e_hco - Emissions of formyl radical 219202: - e_hf - Emissions of hydrogen fluoride 219203: - e_o - Emissions of oxygen atom 219204: - e_o1d - Emissions of excited oxygen atom 219205: - e_o3p - Emissions of ground state oxygen atom 219206: - e_strataer - Emissions of stratospheric aerosol 219207: - parfire - Wildfire flux of paraffins 219208: - olefire - Wildfire flux of olefines 219209: - ald2fire - Wildfire flux of aldehydes 219210: - ketfire - Wildfire flux of ketones 219211: - apifire - Wildfire flux of f a-pinene cyclic terpenes 219212: - tolfire - Wildfire flux of toluene less reactive aromatics 219213: - xylfire - Wildfire flux of xylene more reactive aromatics 219214: - limfire - Wildfire flux of d-limonene cyclic diene 219215: - oltfire - Wildfire flux of terminal alkenes 219216: - hc3fire - Wildfire flux of alkanes low oh rate 219217: - hc5fire - Wildfire flux of alkanes med oh rate 219218: - hc8fire - Wildfire flux of alkanes high oh rate 219219: - hcnfire - Wildfire flux of hydrogen cyanide 219220: - ch3cnfire - Wildfire flux of acetonitrile 219221: - e_vso2 - Atmosphere emission mass flux of Volcanic sulfur dioxide 219222: - e_aroo2 - Atmosphere emission mass flux of Aromatic peroxy radical 219223: - e_c2h2 - Atmosphere emission mass flux of Ethyne 219224: - e_ch3cn - Atmosphere emission mass flux of Acetonitrile 219225: - e_ch3o2no2 - Atmosphere emission mass flux of Methyl peroxy nitrate 219226: - e_hcn - Atmosphere emission mass flux of Hydrogen cyanide 219227: - e_hpald1 - Atmosphere emission mass flux of Hydroperoxy aldehydes type 1 219228: - e_hpald2 - Atmosphere emission mass flux of Hydroperoxy aldehydes type 2 219229: - e_isopbo2 - Atmosphere emission mass flux of Isoprene peroxy type B 219230: - e_isopdo2 - Atmosphere emission mass flux of Isoprene peroxy type D 219231: - e_voca - Atmosphere emission mass flux of Anthropogenic volatile organic compounds 219232: - e_vocbb - Atmosphere emission mass flux of Biomass burning volatile organic compounds 220228: - tpoc - Total precipitation observation count 221001: - dv_go3 - Ozone deposition velocity 221002: - dv_nox - Nitrogen oxides deposition velocity 221003: - dv_h2o2 - Hydrogen peroxide deposition velocity 221004: - dv_ch4 - Methane deposition velocity 221005: - dv_co - Carbon monoxide deposition velocity 221006: - dv_hno3 - Nitric acid deposition velocity 221007: - dv_ch3ooh - Methyl peroxide deposition velocity 221008: - dv_hcho - Formaldehyde deposition velocity 221009: - dv_par - Paraffins deposition velocity 221010: - dv_c2h4 - Ethene deposition velocity 221011: - dv_ole - Olefins deposition velocity 221012: - dv_ald2 - Aldehydes deposition velocity 221013: - dv_pan - Peroxyacetyl nitrate deposition velocity 221014: - dv_rooh - Peroxides deposition velocity 221015: - dv_onit - Organic nitrates deposition velocity 221016: - dv_c5h8 - Isoprene deposition velocity 221017: - dv_so2 - Sulfur dioxide deposition velocity 221018: - dv_dms - Dimethyl sulfide deposition velocity 221019: - dv_nh3 - Ammonia deposition velocity 221020: - dv_so4 - Sulfate deposition velocity 221021: - dv_nh4 - Ammonium deposition velocity 221022: - dv_msa - Methane sulfonic acid deposition velocity 221023: - dv_ch3cocho - Methyl glyoxal deposition velocity 221024: - dv_o3s - Stratospheric ozone deposition velocity 221025: - dv_ra - Radon deposition velocity 221026: - dv_pb - Lead deposition velocity 221027: - dv_no - Nitrogen monoxide deposition velocity 221028: - dv_ho2 - Hydroperoxy radical deposition velocity 221029: - dv_ch3o2 - Methylperoxy radical deposition velocity 221030: - dv_oh - Hydroxyl radical deposition velocity 221031: - dv_no2 - Nitrogen dioxide deposition velocity 221032: - dv_no3 - Nitrate radical deposition velocity 221033: - dv_n2o5 - Dinitrogen pentoxide deposition velocity 221034: - dv_ho2no2 - Pernitric acid deposition velocity 221035: - dv_c2o3 - Peroxy acetyl radical deposition velocity 221036: - dv_ror - Organic ethers deposition velocity 221037: - dv_rxpar - PAR budget corrector deposition velocity 221038: - dv_xo2 - NO to NO2 operator deposition velocity 221039: - dv_xo2n - NO to alkyl nitrate operator deposition velocity 221040: - dv_nh2 - Amine deposition velocity 221041: - dv_psc - Polar stratospheric cloud deposition velocity 221042: - dv_ch3oh - Methanol deposition velocity 221043: - dv_hcooh - Formic acid deposition velocity 221044: - dv_mcooh - Methacrylic acid deposition velocity 221045: - dv_c2h6 - Ethane deposition velocity 221046: - dv_c2h5oh - Ethanol deposition velocity 221047: - dv_c3h8 - Propane deposition velocity 221048: - dv_c3h6 - Propene deposition velocity 221049: - dv_c10h16 - Terpenes deposition velocity 221050: - dv_ispd - Methacrolein MVK deposition velocity 221051: - dv_no3_a - Nitrate deposition velocity 221052: - dv_ch3coch3 - Acetone deposition velocity 221053: - dv_aco2 - Acetone product deposition velocity 221054: - dv_ic3h7o2 - IC3H7O2 deposition velocity 221055: - dv_hypropo2 - HYPROPO2 deposition velocity 221056: - dv_noxa - Nitrogen oxides Transp deposition velocity 221057: - dv_co2_c - Dry deposition velocity of carbon dioxide (chemistry) 221058: - dv_n2o_c - Dry deposition velocity of nitrous oxide (chemistry) 221059: - dv_h2o - Dry deposition velocity of water vapour (chemistry) 221060: - dv_o2 - Dry deposition velocity of oxygen 221061: - dv_o2_1s - Dry deposition velocity of singlet oxygen 221062: - dv_o2_1d - Dry deposition velocity of singlet delta oxygen 221063: - dv_oclo - Dry deposition velocity of chlorine dioxide 221064: - dv_clono2 - Dry deposition velocity of chlorine nitrate 221065: - dv_hocl - Dry deposition velocity of hypochlorous acid 221066: - dv_cl2 - Dry deposition velocity of chlorine 221067: - dv_clno2 - Dry deposition velocity of nitryl chloride 221068: - dv_hbr - Dry deposition velocity of hydrogen bromide 221069: - dv_cl2o2 - Dry deposition velocity of dichlorine dioxide 221070: - dv_hobr - Dry deposition velocity of hypobromous acid 221071: - dv_cfc11 - Dry deposition velocity of trichlorofluoromethane 221072: - dv_cfc12 - Dry deposition velocity of dichlorodifluoromethane 221073: - dv_cfc113 - Dry deposition velocity of trichlorotrifluoroethane 221074: - dv_cfc114 - Dry deposition velocity of dichlorotetrafluoroethane 221075: - dv_cfc115 - Dry deposition velocity of chloropentafluoroethane 221076: - dv_ccl4 - Dry deposition velocity of tetrachloromethane 221077: - dv_ch3ccl3 - Dry deposition velocity of methyl chloroform 221078: - dv_ch3cl - Dry deposition velocity of methyl chloride 221079: - dv_hcfc22 - Dry deposition velocity of chlorodifluoromethane 221080: - dv_ch3br - Dry deposition velocity of methyl bromide 221081: - dv_ha1202 - Dry deposition velocity of dibromodifluoromethane 221082: - dv_ha1211 - Dry deposition velocity of bromochlorodifluoromethane 221083: - dv_ha1301 - Dry deposition velocity of trifluorobromomethane 221084: - dv_ha2402 - Dry deposition velocity of cbrf2cbrf2 221085: - dv_h2so4 - Dry deposition velocity of sulfuric acid 221086: - dv_hono - Dry deposition velocity of nitrous acid 221087: - dv_hc3 - Dry deposition velocity of alkanes low oh rate 221088: - dv_hc5 - Dry deposition velocity of alkanes med oh rate 221089: - dv_hc8 - Dry deposition velocity of alkanes high oh rate 221090: - dv_olt - Dry deposition velocity of terminal alkenes 221091: - dv_oli - Dry deposition velocity of internal alkenes 221092: - dv_c2h5o2 - Dry deposition velocity of ethylperoxy radical 221093: - dv_dien - Dry deposition velocity of butadiene 221094: - dv_c2h5ooh - Dry deposition velocity of ethyl hydroperoxide 221095: - dv_api - Dry deposition velocity of a-pinene cyclic terpenes 221096: - dv_ch3cooh - Dry deposition velocity of acetic acid 221097: - dv_lim - Dry deposition velocity of d-limonene cyclic diene 221098: - dv_ch3cho - Dry deposition velocity of acetaldehyde 221099: - dv_tol - Dry deposition velocity of toluene and less reactive aromatics 221100: - dv_xyl - Dry deposition velocity of xylene and more reactive aromatics 221101: - dv_glyald - Dry deposition velocity of glycolaldehyde 221102: - dv_cresol - Dry deposition velocity of cresol 221103: - dv_ald - Dry deposition velocity of acetaldehyde and higher 221104: - dv_ch3coooh - Dry deposition velocity of peracetic acid 221105: - dv_ket - Dry deposition velocity of ketones 221106: - dv_eo2 - Dry deposition velocity of hoch2ch2o2 221107: - dv_glyoxal - Dry deposition velocity of glyoxal 221108: - dv_eo - Dry deposition velocity of hoch2ch2o 221109: - dv_dcb - Dry deposition velocity of unsaturated dicarbonyls 221110: - dv_macr - Dry deposition velocity of methacrolein 221111: - dv_udd - Dry deposition velocity of unsaturated hydroxy dicarbonyl 221112: - dv_c3h7o2 - Dry deposition velocity of isopropyldioxidanyl 221113: - dv_hket - Dry deposition velocity of hydroxy ketone 221114: - dv_c3h7ooh - Dry deposition velocity of isopropyl hydroperoxide 221115: - dv_po2 - Dry deposition velocity of c3h6oho2 221116: - dv_pooh - Dry deposition velocity of c3h6ohooh 221117: - dv_op2 - Dry deposition velocity of higher organic peroxides 221118: - dv_hyac - Dry deposition velocity of hydroxyacetone 221119: - dv_paa - Dry deposition velocity of peroxyacetic acid 221120: - dv_ro2 - Dry deposition velocity of ch3coch2o2 221121: - dv_ethp - Dry deposition velocity of peroxy radical from c2h6 221122: - dv_hc3p - Dry deposition velocity of peroxy radical from hc3 221123: - dv_hc5p - Dry deposition velocity of peroxy radical from hc5 221124: - dv_bigene - Dry deposition velocity of lumped alkenes 221125: - dv_hc8p - Dry deposition velocity of peroxy radical from hc8 221126: - dv_bigalk - Dry deposition velocity of lumped alkanes 221127: - dv_etep - Dry deposition velocity of peroxy radical from c2h4 221128: - dv_mek - Dry deposition velocity of c4h8o 221129: - dv_oltp - Dry deposition velocity of peroxy radical from terminal alkenes 221130: - dv_eneo2 - Dry deposition velocity of c4h9o3 221131: - dv_olip - Dry deposition velocity of peroxy radical from internal alkenes 221132: - dv_meko2 - Dry deposition velocity of ch3coch(oo)ch3 221133: - dv_isopo2 - Dry deposition velocity of peroxy radical from c5h8 221134: - dv_mekooh - Dry deposition velocity of ch3coch(ooh)ch3 221135: - dv_apip - Dry deposition velocity of peroxy radical from a-pinene cyclic terpenes 221136: - dv_mco3 - Dry deposition velocity of ch2=c(ch3)co3 221137: - dv_limp - Dry deposition velocity of peroxy radical from d-limonene cyclic diene 221138: - dv_mvk - Dry deposition velocity of methylvinylketone 221139: - dv_pho - Dry deposition velocity of phenoxy radical 221140: - dv_tolp - Dry deposition velocity of peroxy radical from toluene and less reactive aromatics 221141: - dv_macro2 - Dry deposition velocity of ch3c(o)ch(oo)ch2oh 221142: - dv_xylp - Dry deposition velocity of peroxy radical from xylene and more reactive aromatics 221143: - dv_macrooh - Dry deposition velocity of h3c(o)ch(ooh)ch2oh 221144: - dv_cslp - Dry deposition velocity of peroxy radical from cresol 221145: - dv_mpan - Dry deposition velocity of unsaturated pans 221146: - dv_tco3_c - Dry deposition velocity of unsaturated acyl peroxy radical 221147: - dv_ketp - Dry deposition velocity of peroxy radical from ketones 221148: - dv_alko2 - Dry deposition velocity of c5h11o2 221149: - dv_olnn - Dry deposition velocity of no3-alkenes adduct reacting to form carbonitrates 221150: - dv_alkooh - Dry deposition velocity of c5h11ooh 221151: - dv_olnd - Dry deposition velocity of no3-alkenes adduct reacting via decomposition 221152: - dv_bigald - Dry deposition velocity of hoch2c(ch3)=chcho 221153: - dv_hydrald - Dry deposition velocity of c5h6o2 221154: - dv_sulf - Dry deposition velocity of trop sulfuric acid 221155: - dv_ox - Dry deposition velocity of oxides 221156: - dv_isopno3 - Dry deposition velocity of ch2chc(ch3)(oo)ch2ono2 221157: - dv_onitr - Dry deposition velocity of c3 organic nitrate 221158: - dv_clox - Dry deposition velocity of chlorine oxides 221159: - dv_brox - Dry deposition velocity of bromine oxides 221160: - dv_xooh - Dry deposition velocity of hoch2c(ooh)(ch3)chchoh 221161: - dv_isopooh - Dry deposition velocity of hoch2c(ooh)(ch3)ch=ch2 221162: - dv_toluene - Dry deposition velocity of lumped aromatics 221163: - dv_dmso - Dry deposition velocity of dimethyl sulfoxyde 221164: - dv_tolo2 - Dry deposition velocity of c7h9o5 221165: - dv_tolooh - Dry deposition velocity of c7h10o5 221166: - dv_h2s - Dry deposition velocity of hydrogensulfide 221167: - dv_xoh - Dry deposition velocity of c7h10o6 221168: - dv_noy - Dry deposition velocity of all nitrogen oxides 221169: - dv_cly - Dry deposition velocity of chlorine family 221170: - dv_terpo2 - Dry deposition velocity of c10h16(oh)(oo) 221171: - dv_bry - Dry deposition velocity of bromine family 221172: - dv_terpooh - Dry deposition velocity of c10h18o3 221173: - dv_n - Dry deposition velocity of nitrogen atom 221174: - dv_clo - Dry deposition velocity of chlorine monoxide 221175: - dv_cl_c - Dry deposition velocity of chlorine atom 221176: - dv_bro - Dry deposition velocity of bromine monoxide 221177: - dv_h_c - Dry deposition velocity of hydrogen atom 221178: - dv_ch3 - Dry deposition velocity of methyl group 221179: - dv_addt - Dry deposition velocity of aromatic-ho from toluene and less reactive aromatics 221180: - dv_addx - Dry deposition velocity of aromatic-ho from xylene and more reactive aromatics 221181: - dv_nh4no3 - Dry deposition velocity of ammonium nitrate 221182: - dv_addc - Dry deposition velocity of aromatic-ho from csl 221183: - dv_soa1 - Dry deposition velocity of secondary organic aerosol type 1 221184: - dv_soa2a - Dry deposition velocity of secondary organic aerosol type 2a 221185: - dv_soa2b - Dry deposition velocity of secondary organic aerosol type 2b 221186: - dv_sog1 - Dry deposition velocity of condensable gas type 1 221187: - dv_sog2a - Dry deposition velocity of condensable gas type 2a 221188: - dv_sog2b - Dry deposition velocity of condensable gas type 2b 221189: - dv_so3 - Dry deposition velocity of sulfur trioxide 221190: - dv_ocs_c - Dry deposition velocity of carbonyl sulfide 221191: - dv_br - Dry deposition velocity of bromine atom 221192: - dv_br2 - Dry deposition velocity of bromine 221193: - dv_brcl - Dry deposition velocity of bromine monochloride 221194: - dv_brono2 - Dry deposition velocity of bromine nitrate 221195: - dv_ch2br2 - Dry deposition velocity of dibromomethane 221196: - dv_ch3o - Dry deposition velocity of methoxy radical 221197: - dv_chbr3 - Dry deposition velocity of tribromomethane 221198: - dv_cloo - Dry deposition velocity of asymmetric chlorine dioxide radical 221199: - dv_h2 - Dry deposition velocity of hydrogen 221200: - dv_hcl - Dry deposition velocity of hydrogen chloride 221201: - dv_hco - Dry deposition velocity of formyl radical 221202: - dv_hf - Dry deposition velocity of hydrogen fluoride 221203: - dv_o - Dry deposition velocity of oxygen atom 221204: - dv_o1d - Dry deposition velocity of excited oxygen atom 221205: - dv_o3p - Dry deposition velocity of ground state oxygen atom 221206: - dv_strataer - Dry deposition velocity of stratospheric aerosol 221221: - dv_vso2 - Dry deposition velocity of Volcanic sulfur dioxide 221222: - dv_aroo2 - Dry deposition velocity of Aromatic peroxy radical 221223: - dv_c2h2 - Dry deposition velocity of Ethyne 221224: - dv_ch3cn - Dry deposition velocity of Acetonitrile 221225: - dv_ch3o2no2 - Dry deposition velocity of Methyl peroxy nitrate 221226: - dv_hcn - Dry deposition velocity of Hydrogen cyanide 221227: - dv_hpald1 - Dry deposition velocity of Hydroperoxy aldehydes type 1 221228: - dv_hpald2 - Dry deposition velocity of Hydroperoxy aldehydes type 2 221229: - dv_isopbo2 - Dry deposition velocity of Isoprene peroxy type B 221230: - dv_isopdo2 - Dry deposition velocity of Isoprene peroxy type D 221231: - dv_voca - Dry deposition velocity of Anthropogenic volatile organic compounds 221232: - dv_vocbb - Dry deposition velocity of Biomass burning volatile organic compounds 222001: - acc_dry_depm_o3 - Time-integrated dry deposition mass flux of Ozone 222003: - acc_dry_depm_h2o2 - Time-integrated dry deposition mass flux of Hydrogen peroxide 222005: - acc_dry_depm_co - Time-integrated dry deposition mass flux of Carbon monoxide 222006: - acc_dry_depm_hno3 - Time-integrated dry deposition mass flux of Nitric acid 222007: - acc_dry_depm_ch3ooh - Time-integrated dry deposition mass flux of Methyl peroxide 222008: - acc_dry_depm_hcho - Time-integrated dry deposition mass flux of Formaldehyde 222012: - acc_dry_depm_ald2 - Time-integrated dry deposition mass flux of Aldehydes 222013: - acc_dry_depm_pan - Time-integrated dry deposition mass flux of Peroxyacetyl nitrate 222014: - acc_dry_depm_rooh - Time-integrated dry deposition mass flux of Peroxides 222015: - acc_dry_depm_onit - Time-integrated dry deposition mass flux of Organic nitrates 222016: - acc_dry_depm_c5h8 - Time-integrated dry deposition mass flux of Isoprene 222017: - acc_dry_depm_so2 - Time-integrated dry deposition mass flux of Sulphur dioxide 222019: - acc_dry_depm_nh3 - Time-integrated dry deposition mass flux of Ammonia 222020: - acc_dry_depm_so4 - Time-integrated dry deposition mass flux of Sulfate 222021: - acc_dry_depm_nh4 - Time-integrated dry deposition mass flux of Ammonium 222023: - acc_dry_depm_ch3cocho - Time-integrated dry deposition mass flux of Methyl glyoxal 222024: - acc_dry_depm_o3s - Time-integrated dry deposition mass flux of Ozone (stratospheric) 222027: - acc_dry_depm_no - Time-integrated dry deposition mass flux of Nitrogen monoxide 222028: - acc_dry_depm_ho2 - Time-integrated dry deposition mass flux of Hydroperoxy radical 222029: - acc_dry_depm_ch3o2 - Time-integrated dry deposition mass flux of Methylperoxy radical 222031: - acc_dry_depm_no2 - Time-integrated dry deposition mass flux of Nitrogen dioxide 222032: - acc_dry_depm_no3 - Time-integrated dry deposition mass flux of Nitrate radical 222033: - acc_dry_depm_n2o5 - Time-integrated dry deposition mass flux of Dinitrogen pentoxide 222034: - acc_dry_depm_ho2no2 - Time-integrated dry deposition mass flux of Pernitric acid 222042: - acc_dry_depm_ch3oh - Time-integrated dry deposition mass flux of Methanol 222043: - acc_dry_depm_hcooh - Time-integrated dry deposition mass flux of Formic acid 222044: - acc_dry_depm_mcooh - Time-integrated dry deposition mass flux of Methacrylic acid 222045: - acc_dry_depm_c2h6 - Time-integrated dry deposition mass flux of Ethane 222046: - acc_dry_depm_c2h5oh - Time-integrated dry deposition mass flux of Ethanol 222050: - acc_dry_depm_ispd - Time-integrated dry deposition mass flux of Methacrolein 222051: - acc_dry_depm_no3_a - Time-integrated dry deposition mass flux of Nitrate 222052: - acc_dry_depm_ch3coch3 - Time-integrated dry deposition mass flux of Acetone 222086: - acc_dry_depm_hono - Time-integrated dry deposition mass flux of Nitrous acid 222099: - acc_dry_depm_tol - Time-integrated dry deposition mass flux of Toluene and less reactive aromatics 222100: - acc_dry_depm_xyl - Time-integrated dry deposition mass flux of Xylene and more reactive aromatics 222101: - acc_dry_depm_glyald - Time-integrated dry deposition mass flux of Glycolaldehyde 222107: - acc_dry_depm_gly - Time-integrated dry deposition mass flux of Glyoxal 222118: - acc_dry_depm_hyac - Time-integrated dry deposition mass flux of Hydroxyacetone 222161: - acc_dry_depm_isopooh - Time-integrated dry deposition mass flux of all hydroxy-peroxides products of the reaction of hydroxy-isoprene adducts with O2 222224: - acc_dry_depm_ch3cn - Time-integrated dry deposition mass flux of Acetonitrile 222226: - acc_dry_depm_hcn - Time-integrated dry deposition mass flux of Hydrogen cyanide 223001: - acc_wet_depm_o3 - Time-integrated wet deposition mass flux of Ozone 223003: - acc_wet_depm_h2o2 - Time-integrated wet deposition mass flux of Hydrogen peroxide 223006: - acc_wet_depm_hno3 - Time-integrated wet deposition mass flux of Nitric acid 223007: - acc_wet_depm_ch3ooh - Time-integrated wet deposition mass flux of Methyl peroxide 223008: - acc_wet_depm_hcho - Time-integrated wet deposition mass flux of Formaldehyde 223012: - acc_wet_depm_ald2 - Time-integrated wet deposition mass flux of Aldehydes 223013: - acc_wet_depm_pan - Time-integrated wet deposition mass flux of Peroxyacetyl nitrate 223014: - acc_wet_depm_rooh - Time-integrated wet deposition mass flux of Peroxides 223015: - acc_wet_depm_onit - Time-integrated wet deposition mass flux of Organic nitrates 223016: - acc_wet_depm_c5h8 - Time-integrated wet deposition mass flux of Isoprene 223017: - acc_wet_depm_so2 - Time-integrated wet deposition mass flux of Sulphur dioxide 223019: - acc_wet_depm_nh3 - Time-integrated wet deposition mass flux of Ammonia 223020: - acc_wet_depm_so4 - Time-integrated wet deposition mass flux of Sulfate 223021: - acc_wet_depm_nh4 - Time-integrated wet deposition mass flux of Ammonium 223022: - acc_wet_depm_msa - Time-integrated wet deposition mass flux of Methane sulfonic acid 223023: - acc_wet_depm_ch3cocho - Time-integrated wet deposition mass flux of Methyl glyoxal 223025: - acc_wet_depm_co - Time-integrated wet deposition mass flux of Carbon monoxide 223026: - acc_wet_depm_pb - Time-integrated wet deposition mass flux of Lead 223027: - acc_wet_depm_no - Time-integrated wet deposition mass flux of Nitrogen monoxide 223028: - acc_wet_depm_ho2 - Time-integrated wet deposition mass flux of Hydroperoxy radical 223029: - acc_wet_depm_ch3o2 - Time-integrated wet deposition mass flux of Methylperoxy radical 223031: - acc_wet_depm_no2 - Time-integrated wet deposition mass flux of Nitrogen dioxide 223032: - acc_wet_depm_no3 - Time-integrated wet deposition mass flux of Nitrate radical 223033: - acc_wet_depm_n2o5 - Time-integrated wet deposition mass flux of Dinitrogen pentoxide 223034: - acc_wet_depm_ho2no2 - Time-integrated wet deposition mass flux of Pernitric acid 223042: - acc_wet_depm_ch3oh - Time-integrated wet deposition mass flux of Methanol 223043: - acc_wet_depm_hcooh - Time-integrated wet deposition mass flux of Formic acid 223044: - acc_wet_depm_mcooh - Time-integrated wet deposition mass flux of Methacrylic acid 223045: - acc_wet_depm_c2h6 - Time-integrated wet deposition mass flux of Ethane 223046: - acc_wet_depm_c2h5oh - Time-integrated wet deposition mass flux of Ethanol 223050: - acc_wet_depm_ispd - Time-integrated wet deposition mass flux of Methacrolein 223051: - acc_wet_depm_no3_a - Time-integrated wet deposition mass flux of Nitrate 223052: - acc_wet_depm_ch3coch3 - Time-integrated wet deposition mass flux of Acetone 223064: - acc_wet_depm_clono2 - Time-integrated wet deposition mass flux of Chlorine nitrate 223065: - acc_wet_depm_hocl - Time-integrated wet deposition mass flux of Hypochlorous acid 223068: - acc_wet_depm_hbr - Time-integrated wet deposition mass flux of Hydrogen bromide 223070: - acc_wet_depm_hobr - Time-integrated wet deposition mass flux of Hypobromous acid 223086: - acc_wet_depm_hono - Time-integrated wet deposition mass flux of Nitrous acid 223099: - acc_wet_depm_tol - Time-integrated wet deposition mass flux of Toluene and less reactive aromatics 223100: - acc_wet_depm_xyl - Time-integrated wet deposition mass flux of Xylene and more reactive aromatics 223101: - acc_wet_depm_glyald - Time-integrated wet deposition mass flux of Glycolaldehyde 223107: - acc_wet_depm_gly - Time-integrated wet deposition mass flux of Glyoxal 223118: - acc_wet_depm_hyac - Time-integrated wet deposition mass flux of Hydroxyacetone 223161: - acc_wet_depm_isopooh - Time-integrated wet deposition mass flux of all hydroxy-peroxides products of the reaction of hydroxy-isoprene adducts with O2 223186: - acc_wet_depm_sog1 - Time-integrated wet deposition mass flux of Condensable gas type 1 223187: - acc_wet_depm_sog2a - Time-integrated wet deposition mass flux of Condensable gas type 2a 223188: - acc_wet_depm_sog2b - Time-integrated wet deposition mass flux of Condensable gas type 2b 223194: - acc_wet_depm_brono2 - Time-integrated wet deposition mass flux of Bromine nitrate 223200: - acc_wet_depm_hcl - Time-integrated wet deposition mass flux of Hydrogen chloride 223224: - acc_wet_depm_ch3cn - Time-integrated wet deposition mass flux of Acetonitrile 223226: - acc_wet_depm_hcn - Time-integrated wet deposition mass flux of Hydrogen cyanide 223227: - acc_wet_depm_hpald1 - Time-integrated wet deposition mass flux of Hydroperoxy aldehydes type 1 223228: - acc_wet_depm_hpald2 - Time-integrated wet deposition mass flux of Hydroperoxy aldehydes type 2 228001: - cin - Convective inhibition 228002: - orog - Orography 228003: - zust - Friction velocity 228004: - avg_2t - Time-mean 2 metre temperature 228005: - avg_10ws - Time-mean 10 metre wind speed 228006: - meantcc - Mean total cloud cover 228007: - dl - Lake total depth 228008: - lmlt - Lake mix-layer temperature 228009: - lmld - Lake mix-layer depth 228010: - lblt - Lake bottom temperature 228011: - ltlt - Lake total layer temperature 228012: - lshf - Lake shape factor 228013: - lict - Lake ice surface temperature 228014: - licd - Lake ice total depth 228015: - dndzn - Minimum vertical gradient of refractivity inside trapping layer 228016: - dndza - Mean vertical gradient of refractivity inside trapping layer 228017: - dctb - Duct base height 228018: - tplb - Trapping layer base height 228019: - tplt - Trapping layer top height 228020: - degm10l - Geometric height of -10 degrees C atmospheric isothermal level above ground 228021: - fdir - Surface direct short-wave (solar) radiation 228022: - cdir - Surface direct short-wave radiation, clear sky 228023: - cbh - Cloud base height 228024: - deg0l - Geometric height of 0 degrees C atmospheric isothermal level above ground - deg0 228025: - hvis - Horizontal visibility 228026: - mx2t3 - Maximum temperature at 2 metres in the last 3 hours 228027: - mn2t3 - Minimum temperature at 2 metres in the last 3 hours 228028: - 10fg3 - Maximum 10 metre wind gust in the last 3 hours 228029: - i10fg - Instantaneous 10 metre wind gust 228030: - rhw - Relative humidity with respect to water 228031: - rhi - Relative humidity with respect to ice 228032: - asn - Snow albedo 228034: - fcpc - Fraction of convective precipitation cover 228035: - mxcape6 - Maximum CAPE in the last 6 hours 228036: - mxcapes6 - Maximum CAPES in the last 6 hours 228037: - 2rhw - 2 metre relative humidity with respect to water 228038: - lwcs - Liquid water content in snow pack 228039: - sm - Soil moisture 228040: - swi1 - Soil wetness index in layer 1 228041: - swi2 - Soil wetness index in layer 2 228042: - swi3 - Soil wetness index in layer 3 228043: - swi4 - Soil wetness index in layer 4 228044: - capes - Convective available potential energy shear 228045: - trpp - Tropopause pressure 228046: - hcct - Height of convective cloud top 228047: - hwbt0 - Height of zero-degree wet-bulb temperature 228048: - hwbt1 - Height of one-degree wet-bulb temperature 228050: - litoti - Instantaneous total lightning flash density 228051: - litota1 - Averaged total lightning flash density in the last hour 228052: - licgi - Instantaneous cloud-to-ground lightning flash density 228053: - licga1 - Averaged cloud-to-ground lightning flash density in the last hour 228054: - ucq - Unbalanced component of specific humidity 228055: - ucclwc - Unbalanced component of specific cloud liquid water content 228056: - ucciwc - Unbalanced component of specific cloud ice water content 228057: - litota3 - Averaged total lightning flash density in the last 3 hours 228058: - litota6 - Averaged total lightning flash density in the last 6 hours 228059: - licga3 - Averaged cloud-to-ground lightning flash density in the last 3 hours 228060: - licga6 - Averaged cloud-to-ground lightning flash density in the last 6 hours 228070: - smnnob - SMOS observed soil moisture retrieved using neural network 228071: - smnner - SMOS observed soil moisture uncertainty retrieved using neural network 228072: - smnnrfi - SMOS radio frequency interference probability 228073: - smnnnb - SMOS number of observations per grid point 228074: - smnntim - SMOS observation time for the satellite soil moisture data 228078: - gppbfas - GPP coefficient from Biogenic Flux Adjustment System 228079: - recbfas - Rec coefficient from Biogenic Flux Adjustment System 228080: - aco2nee - Accumulated Carbon Dioxide Net Ecosystem Exchange 228081: - aco2gpp - Accumulated Carbon Dioxide Gross Primary Production 228082: - aco2rec - Accumulated Carbon Dioxide Ecosystem Respiration 228083: - fco2nee - Carbon dioxide net ecosystem exchange flux 228084: - fco2gpp - Carbon dioxide gross primary production flux 228085: - fco2rec - Carbon dioxide ecosystem respiration flux 228086: - sm20 - Soil moisture top 20 cm 228087: - sm100 - Soil moisture top 100 cm 228088: - tcslw - Total column supercooled liquid water 228089: - tcrw - Total column rain water 228090: - tcsw - Total column snow water 228091: - ccf - Canopy cover fraction 228092: - stf - Soil texture fraction 228093: - swv - Volumetric soil moisture 228094: - ist - Ice temperature 228095: - st20 - Soil temperature top 20 cm 228096: - st100 - Soil temperature top 100 cm 228100: - evatc - Evaporation from the top of canopy 228101: - evabs - Evaporation from bare soil 228102: - evaow - Evaporation from open water surfaces excluding oceans 228103: - evavt - Evaporation from vegetation transpiration 228104: - e_wlch4 - Atmosphere emission mass flux of Methane from Wetlands 228105: - sif740 - Solar induced Chlorophyll fluorescence at 740nm 228106: - sif755 - Solar induced Chlorophyll fluorescence at 755nm 228107: - sif771 - Solar induced Chlorophyll fluorescence at 771nm 228108: - sif757 - Solar induced Chlorophyll fluorescence at 757nm 228109: - acc_e_wlch4 - Accumulated mass emission of methane from Wetlands 228129: - ssrdc - Surface short-wave (solar) radiation downward clear-sky 228130: - strdc - Surface long-wave (thermal) radiation downward clear-sky 228131: - u10n - 10 metre u-component of neutral wind 228132: - v10n - 10 metre v-component of neutral wind 228134: - vtnowd - V-tendency from non-orographic wave drag 228136: - utnowd - U-tendency from non-orographic wave drag 228139: - st - Soil temperature 228141: - sd - Snow depth water equivalent 228143: - cp - Convective precipitation 228144: - sf - Snowfall water equivalent 228164: - tcc - Total Cloud Cover 228170: - cap - Field capacity 228171: - wilt - Wilting point 228205: - ro - Water runoff and drainage 228216: - fzra - Accumulated freezing rain 228217: - ilspf - Instantaneous large-scale precipitation fraction 228218: - crr - Convective rain rate 228219: - lsrr - Large scale rain rate 228220: - csfr - Convective snowfall rate water equivalent 228221: - lssfr - Large scale snowfall rate water equivalent 228222: - mxtpr3 - Maximum total precipitation rate in the last 3 hours 228223: - mntpr3 - Minimum total precipitation rate in the last 3 hours 228224: - mxtpr6 - Maximum total precipitation rate in the last 6 hours 228225: - mntpr6 - Minimum total precipitation rate in the last 6 hours 228226: - mxtpr - Maximum total precipitation rate since previous post-processing 228227: - mntpr - Minimum total precipitation rate since previous post-processing 228228: - tp - Total Precipitation 228229: - smos_tb_cdfa - SMOS first Brightness Temperature Bias Correction parameter 228230: - smos_tb_cdfb - SMOS second Brightness Temperature Bias Correction parameter 228231: - mlcape50 - Mixed-layer CAPE in the lowest 50 hPa 228232: - mlcin50 - Mixed-layer CIN in the lowest 50 hPa 228233: - mlcape100 - Mixed-layer CAPE in the lowest 100 hPa 228234: - mlcin100 - Mixed-layer CIN in the lowest 100 hPa 228235: - mucape - Most-unstable CAPE 228236: - mucin - Most-unstable CIN 228237: - mudlp - Departure level of the most unstable parcel expressed as Pressure 228239: - 200u - 200 metre U wind component 228240: - 200v - 200 metre V wind component 228241: - 200si - 200 metre wind speed 228242: - fdif - Surface solar radiation diffuse total sky 228243: - cdif - Surface solar radiation diffuse clear-sky 228244: - aldr - Surface albedo of direct radiation 228245: - aldf - Surface albedo of diffuse radiation 228246: - 100u - 100 metre U wind component 228247: - 100v - 100 metre V wind component 228248: - tccsw - Surface short wave-effective total cloudiness 228249: - 100si - 100 metre wind speed 228250: - irrfr - Irrigation fraction 228251: - pev - Potential evaporation 228252: - irr - Irrigation 228253: - ascat_sm_cdfa - ASCAT first soil moisture CDF matching parameter 228254: - ascat_sm_cdfb - ASCAT second soil moisture CDF matching parameter 228255: - tcclw - Surface long wave-effective total cloudiness 229001: - cur - Urban cover 229002: - cro - Road Cover 229003: - cbu - Building cover 229004: - bldh - Building height 229005: - hwr - Vertical-to-horizontal area ratio 229006: - bhstd - Standard deviation of building height 229007: - cwe - Wetland cover 229008: - twe - Wetland type 229009: - cirr - Irrigation cover 229010: - c4cr - C4 crop cover 229011: - c4gr - C4 grass cover 230008: - srovar - Surface runoff (variable resolution) 230009: - ssrovar - Sub-surface runoff (variable resolution) 230020: - parcsvar - Clear sky surface photosynthetically active radiation (variable resolution) 230021: - fdirvar - Total sky direct solar radiation at surface (variable resolution) 230022: - cdirvar - Clear-sky direct solar radiation at surface (variable resolution) 230044: - esvar - Snow evaporation (variable resolution) 230045: - smltvar - Snowmelt (variable resolution) 230046: - sdurvar - Solar duration (variable resolution) 230047: - dsrpvar - Direct solar radiation (variable resolution) 230050: - lspfvar - Large-scale precipitation fraction (variable resolution) 230057: - uvbvar - Downward UV radiation at the surface (variable resolution) 230058: - parvar - Photosynthetically active radiation at the surface (variable resolution) 230080: - aco2neevar - Accumulated Carbon Dioxide Net Ecosystem Exchange (variable resolution) 230081: - aco2gppvar - Accumulated Carbon Dioxide Gross Primary Production (variable resolution) 230082: - aco2recvar - Accumulated Carbon Dioxide Ecosystem Respiration (variable resolution) 230129: - ssrdcvar - Surface solar radiation downward clear-sky (variable resolution) 230130: - strdcvar - Surface thermal radiation downward clear-sky (variable resolution) 230142: - lspvar - Stratiform precipitation (Large-scale precipitation) (variable resolution) 230143: - cpvar - Convective precipitation (variable resolution) 230144: - sfvar - Snowfall (convective + stratiform) (variable resolution) 230145: - bldvar - Boundary layer dissipation (variable resolution) 230146: - sshfvar - Surface sensible heat flux (variable resolution) 230147: - slhfvar - Surface latent heat flux (variable resolution) 230169: - ssrdvar - Surface solar radiation downwards (variable resolution) 230174: - alvar - Albedo (variable resolution) 230175: - strdvar - Surface thermal radiation downwards (variable resolution) 230176: - ssrvar - Surface net solar radiation (variable resolution) 230177: - strvar - Surface net thermal radiation (variable resolution) 230178: - tsrvar - Top net solar radiation (variable resolution) 230179: - ttrvar - Top net thermal radiation (variable resolution) 230180: - ewssvar - East-West surface stress (variable resolution) 230181: - nsssvar - North-South surface stress (variable resolution) 230182: - evar - Evaporation (variable resolution) 230189: - sundvar - Sunshine duration (variable resolution) 230195: - lgwsvar - Longitudinal component of gravity wave stress (variable resolution) 230196: - mgwsvar - Meridional component of gravity wave stress (variable resolution) 230197: - gwdvar - Gravity wave dissipation (variable resolution) 230198: - srcvar - Skin reservoir content (variable resolution) 230205: - rovar - Runoff (variable resolution) 230208: - tsrcvar - Top net solar radiation, clear sky (variable resolution) 230209: - ttrcvar - Top net thermal radiation, clear sky (variable resolution) 230210: - ssrcvar - Surface net solar radiation, clear sky (variable resolution) 230211: - strcvar - Surface net thermal radiation, clear sky (variable resolution) 230212: - tisrvar - TOA incident solar radiation (variable resolution) 230213: - vimdvar - Vertically integrated moisture divergence (variable resolution) 230216: - fzravar - Accumulated freezing rain (variable resolution) 230228: - tpvar - Total precipitation (variable resolution) 230239: - csfvar - Convective snowfall (variable resolution) 230240: - lsfvar - Large-scale snowfall (variable resolution) 230251: - pevvar - Potential evaporation (variable resolution) 231001: - fzrawe - Accumulated freezing rain water equivalent 231002: - rowe - Runoff water equivalent (surface plus subsurface) 231003: - eswe - Snow evaporation water equivalent 231004: - pevr - Potential evaporation rate 231005: - peva - Potential evaporation 231006: - tifr - Tile fraction 231007: - tipe - Tile percentage 231008: - flsrm - Forecast logarithm of surface roughness length for moisture 231009: - surfror - Surface runoff rate 231010: - surfro - Surface runoff 231011: - ssurfror - Sub-surface runoff rate 231012: - ssurfro - Sub-surface runoff 231013: - rfl04 - Reflectance in 0.4 micron channel 231014: - vdiv - Vertical divergence 231015: - dtc - Drag thermal coefficient 231016: - dec - Drag evaporation coefficient 231017: - pdhs - Pressure departure from hydrostatic state 231018: - snrf - Surface net radiation flux (SW and LW) 231019: - tnrf - Top net radiation flux (SW and LW) 231020: - mtnrf - Time-mean top net radiation flux (SW and LW) 231021: - gits - Global irradiance on tilted surfaces 231022: - eagr - Eady growth rate 231023: - tdtc - Tropical cyclones track density 231024: - cant - Canopy air temperature 231025: - swit - Soil wetness index (total layer) 231026: - swir - Soil wetness index (root zone) 231027: - swil - Soil wetness index (layer) 231028: - ddrf - Distance downward from roof surface 231029: - diws - Distance inward from outer wall surface 231030: - ddrd - Distance downward from road surface 231031: - rpc - Renewable power capacity 231032: - rppr - Renewable power production rate 231033: - rpp - Renewable power production 231034: - wpc - Wind power capacity 231035: - wppr - Wind power production rate 231036: - wpp - Wind power production 231037: - pvpc - Solar photovoltaic (PV) power capacity 231038: - pvppr - Solar photovoltaic (PV) power production rate 231039: - pvpp - Solar photovoltaic (PV) power production 231040: - tgrp - Graupel (snow pellets) precipitation 231041: - litotint - Time-integrated total lightning flash density 231042: - maxtcg - Maximum total column integrated graupel (snow pellets) 231043: - minvis - Minimum visibility 231044: - hthg - Geometric height of theta level above ground 231045: - pcdb - Pressure at cloud base 231046: - hacg - Geometric height of adiabatic condensation level above ground 231047: - hfcg - Geometric height of free convection level above ground 231048: - hnbg - Geometric height of neutral buoyancy level above ground 231049: - deg3l - Geometric height of 3 degrees C atmospheric isothermal level above ground 231050: - rft - Roof temperature 231051: - wlt - Wall temperature 231052: - rdt - Road temperature 231053: - sdrf - Snow depth water equivalent on roof 231054: - sdrd - Snow depth water equivalent on road 231055: - urct - Urban canyon temperature 231056: - urcq - Urban canyon specific humidity 231057: - csfwe - Convective snowfall water equivalent 231058: - lsfwe - Large-scale snowfall water equivalent 231059: - lslt - Lake surface temperature 231060: - sbrn - Surface bulk Richardson number 231061: - mx2r - Time-maximum 2 metre relative humidity 231062: - mn2r - Time-minimum 2 metre relative humidity 231063: - srhe - Surface roughness for heat 231064: - srmo - Surface roughness for moisture 231065: - esrwe - Snow evaporation rate water equivalent 231066: - rare - Radar reflectivity 231067: - pcdc - Pressure at cloud ceiling 231068: - visp - Visibility through precipitation 231069: - h0thg - Geometric height of 0 degrees C theta level above ground 231070: - h1thg - Geometric height of 1 degree C theta level above ground 231071: - h1p5thg - Geometric height of 1.5 degrees C theta level above ground 231072: - vswrz - Volumetric soil moisture (root zone) 231073: - tcvmrpr - Total column vertical-maximum radar precipitation rate 231074: - tcvmrr - Total column vertical-maximum radar reflectivity 231500: - hits - Thunderstorm hazard index 231501: - hiswf - Surface water flooding hazard index 231502: - hipa - Polluted air hazard index 231503: - hiss - Storm surge hazard index 231504: - hicw - Cold wave hazard index 231505: - hihf - Frost (hoar frost) hazard index 231506: - hihw - Heatwave hazard index 231507: - hiw - Wind hazard index 231508: - hiwf - Wildfires hazard index 232000: - fba - Burned area 232001: - bia - Burning area 232002: - baa - Burnable area 232003: - ubaa - Un-burnable area 232004: - fuell - Fuel load 232005: - combc - Combustion completeness 232006: - fuelmc - Fuel moisture content 232007: - llfl - Live leaf fuel load 232008: - lwfl - Live wood fuel load 232009: - dlfl - Dead leaf fuel load 232010: - dwfl - Dead wood fuel load 232011: - lfmc - Live fuel moisture content 232012: - fdlmc - Fine dead leaf moisture content 232013: - ddlmc - Dense dead leaf moisture content 232014: - fdwmc - Fine dead wood moisture content 232015: - ddwmc - Dense dead wood moisture content 232016: - lfmclv - Live fuel moisture content in low vegetation 232017: - lfmchv - Live fuel moisture content in high vegetation 233000: - tvige - Time-integrated total column vertically-integrated eastward geopotential flux 233001: - tvign - Time-integrated total column vertically-integrated northward geopotential flux 233002: - tviwgd - Time-integrated total column vertically-integrated divergence of water geopotential flux 233003: - tvigd - Time-integrated total column vertically-integrated divergence of geopotential flux 233004: - tviee - Time-integrated total column vertically-integrated eastward enthalpy flux 233005: - tvien - Time-integrated total column vertically-integrated northward enthalpy flux 233006: - tvikee - Time-integrated total column vertically-integrated eastward kinetic energy flux 233007: - tviken - Time-integrated total column vertically-integrated northward kinetic energy flux 233008: - tvitee - Time-integrated total column vertically-integrated eastward total energy flux 233009: - tviten - Time-integrated total column vertically-integrated northward total energy flux 233010: - tvied - Time-integrated total column vertically-integrated divergence of enthalpy flux 233011: - tviked - Time-integrated total column vertically-integrated divergence of kinetic energy flux 233012: - tvited - Time-integrated total column vertically-integrated divergence of total energy flux 233013: - tviwed - Time-integrated total column vertically-integrated divergence of water enthalpy flux 233014: - tvimad - Time-integrated total column vertically-integrated divergence of mass flux 233015: - tvimae - Time-integrated total column vertically-integrated eastward mass flux 233016: - tviman - Time-integrated total column vertically-integrated northward mass flux 233017: - tviwvd - Time-integrated total column vertically-integrated divergence of water vapour flux 233018: - tviclwd - Time-integrated total column vertically-integrated divergence of cloud liquid water flux 233019: - tviciwd - Time-integrated total column vertically-integrated divergence of cloud ice water flux 233020: - tvird - Time-integrated total column vertically-integrated divergence of rain flux 233021: - tvisd - Time-integrated total column vertically-integrated divergence of snow flux 233022: - tviwve - Time-integrated total column vertically-integrated eastward water vapour flux 233023: - tviwvn - Time-integrated total column vertically-integrated northward water vapour flux 233024: - tviclwe - Time-integrated total column vertically-integrated eastward cloud liquid water flux 233025: - tviclwn - Time-integrated total column vertically-integrated northward cloud liquid water flux 233026: - tviciwe - Time-integrated total column vertically-integrated eastward cloud ice water flux 233027: - tviciwn - Time-integrated total column vertically-integrated northward cloud ice water flux 233028: - tvire - Time-integrated total column vertically-integrated eastward rain flux 233029: - tvirn - Time-integrated total column vertically-integrated northward rain flux 233030: - tvise - Time-integrated total column vertically-integrated eastward snow flux 233031: - tvisn - Time-integrated total column vertically-integrated northward snow flux 233032: - tvioze - Time-integrated total column vertically-integrated eastward ozone flux 233033: - tviozn - Time-integrated total column vertically-integrated northward ozone flux 233034: - tviozd - Time-integrated total column vertically-integrated divergence of ozone flux 233035: - tvions - Time-integrated total column vertically-integrated net source of ozone 234139: - sts - Surface temperature significance 234151: - msls - Mean sea level pressure significance 234167: - 2ts - 2 metre temperature significance 234228: - tps - Total precipitation significance 235001: - avg_ttswr - Time-mean temperature tendency due to short-wave radiation 235002: - avg_ttlwr - Time-mean temperature tendency due to long-wave radiation 235003: - avg_ttswrcs - Time-mean temperature tendency due to short wave radiation, clear sky 235004: - avg_ttlwrcs - Time-mean temperature tendency due to long-wave radiation, clear sky 235005: - avg_ttpm - Time-mean temperature tendency due to parametrisations 235006: - avg_qtpm - Time-mean specific humidity tendency due to parametrisations 235007: - avg_utpm - Time-mean eastward wind tendency due to parametrisations 235008: - avg_vtpm - Time-mean northward wind tendency due to parametrisations 235009: - avg_umf - Time-mean updraught mass flux 235010: - avg_dmf - Time-mean downdraught mass flux 235011: - avg_udr - Time-mean updraught detrainment rate 235012: - avg_ddr - Time-mean downdraught detrainment rate 235013: - avg_tpf - Time-mean total precipitation flux 235014: - avg_tdch - Time-mean turbulent diffusion coefficient for heat 235015: - tirf - Time integral of rain flux 235017: - tisemf - Time integral of surface eastward momentum flux 235018: - tisnmf - Time integral of surface northward momentum flux 235019: - tislhef - Time integral of surface latent heat evaporation flux 235020: - avg_surfror - Time-mean surface runoff rate 235021: - avg_ssurfror - Time-mean sub-surface runoff rate 235022: - avg_parcsf - Time-mean surface photosynthetically active radiation flux, clear sky 235023: - avg_esrwe - Time-mean snow evaporation rate water equivalent 235024: - avg_smr - Time-mean snow melt rate 235025: - avg_imagss - Time-mean magnitude of turbulent surface stress 235026: - avg_ilspf - Time-mean large-scale precipitation fraction 235027: - avg_sduvrf - Time-mean surface downward UV radiation flux 235028: - avg_sparf - Time-mean surface photosynthetically active radiation flux 235029: - avg_lsprate - Time-mean large-scale precipitation rate 235030: - avg_cpr - Time-mean convective precipitation rate 235031: - avg_tsrwe - Time-mean total snowfall rate water equivalent 235032: - avg_ibld - Time-mean boundary layer dissipation 235033: - avg_ishf - Time-mean surface sensible heat flux 235034: - avg_slhtf - Time-mean surface latent heat flux 235035: - avg_sdswrf - Time-mean surface downward short-wave radiation flux 235036: - avg_sdlwrf - Time-mean surface downward long-wave radiation flux 235037: - avg_snswrf - Time-mean surface net short-wave radiation flux 235038: - avg_snlwrf - Time-mean surface net long-wave radiation flux 235039: - avg_tnswrf - Time-mean top net short-wave radiation flux 235040: - avg_tnlwrf - Time-mean top net long-wave radiation flux 235041: - avg_iews - Time-mean eastward turbulent surface stress 235042: - avg_inss - Time-mean northward turbulent surface stress 235043: - avg_ie - Time-mean moisture flux 235044: - avg_sdf - Time-mean sunshine duration fraction 235045: - avg_iegwss - Time-mean eastward gravity wave surface stress 235046: - avg_ingwss - Time-mean northward gravity wave surface stress 235047: - avg_igwd - Time-mean gravity wave dissipation 235048: - avg_rorwe - Time-mean runoff rate water equivalent (surface plus subsurface) 235049: - avg_tnswrfcs - Time-mean top net short-wave radiation flux, clear sky 235050: - avg_tnlwrfcs - Time-mean top net long-wave radiation flux, clear sky 235051: - avg_snswrfcs - Time-mean surface net short-wave radiation flux, clear sky 235052: - avg_snlwrfcs - Time-mean surface net long-wave radiation flux, clear sky 235053: - avg_tdswrf - Time mean top downward short-wave radiation flux 235054: - avg_vimdf - Time-mean total column vertically-integrated moisture divergence flux 235055: - avg_tprate - Time-mean total precipitation rate 235056: - avg_csfr - Time-mean convective snowfall rate water equivalent 235057: - avg_lssfr - Time-mean large scale snowfall rate water equivalent 235058: - avg_sdirswrf - Time-mean surface direct short-wave radiation flux 235059: - avg_sdirswrfcs - Time-mean surface direct short-wave radiation flux, clear sky 235060: - msdfswrf - Mean surface diffuse short-wave radiation flux 235061: - avg_sdifswrfcs - Time-mean surface diffuse short-wave radiation flux, clear sky 235062: - avg_fco2nee - Time-mean carbon dioxide net ecosystem exchange flux 235063: - avg_fco2gpp - Time-mean carbon dioxide gross primary production flux 235064: - avg_fco2rec - Time-mean carbon dioxide ecosystem respiration flux 235065: - mrr - Mean rain rate 235066: - mcrr - Mean convective rain rate 235067: - mlsrr - Mean large-scale rain rate 235068: - avg_sdswrfcs - Time-mean surface downward short-wave radiation flux, clear sky 235069: - avg_sdlwrfcs - Time-mean surface downward long-wave radiation flux, clear sky 235070: - avg_pevr - Time-mean potential evaporation rate 235071: - tislhsf - Time integral of surface latent heat sublimation flux 235072: - tisef - Time integral of snow evaporation flux 235073: - tietrf - Time integral of evapotranspiration flux 235074: - avg_etr - Time-mean evapotranspiration rate 235075: - tipet - Time integral of potential evapotranspiration rate 235076: - avg_petr - Time-mean potential evapotranspiration rate 235077: - avg_vsw - Time-mean volumetric soil moisture 235078: - avg_sd - Time-mean snow depth water equivalent 235079: - avg_skt - Time-mean skin temperature 235080: - avg_rsn - Time-mean snow density 235081: - avg_cvl - Time-mean low vegetation cover 235082: - avg_cvh - Time-mean high vegetation cover 235083: - avg_ci - Time-mean sea ice area fraction 235084: - avg_sst - Time-mean sea surface temperature 235085: - avg_lai_lv - Time-mean leaf area index, low vegetation 235086: - avg_lai_hv - Time-mean leaf area index, high vegetation 235087: - avg_tclw - Time-mean total column liquid water 235088: - avg_tciw - Time-mean total column cloud ice water 235089: - avg_2sh - Time-mean 2 metre specific humidity 235090: - avg_lmlt - Time-mean lake mix-layer temperature 235091: - avg_lmld - Time-mean lake mix-layer depth 235092: - avg_2r - Time-mean 2 metre relative humidity 235093: - avg_fscov - Time-mean fraction of snow cover 235094: - avg_sot - Time-mean soil temperature 235095: - avg_sde - Time-mean snow depth 235096: - avg_snowc - Time-mean snow cover 235097: - avg_ws - Time-mean wind speed 235098: - avg_pres - Time-mean pressure 235099: - avg_chnk - Time-mean charnock 235100: - avg_pv - Time-mean potential vorticity 235101: - avg_crwc - Time-mean specific rain water content 235102: - avg_cswc - Time-mean specific snow water content 235103: - avg_etadot - Time-mean eta-coordinate vertical velocity 235105: - avg_vis - Time-mean visibility 235106: - avg_wdir - Time-mean wind direction 235107: - avg_tcsw - Time-mean total column snow water 235108: - avg_lcc - Time-mean low cloud cover 235109: - avg_mcc - Time-mean medium cloud cover 235110: - avg_hcc - Time-mean high cloud cover 235111: - avg_tcrw - Time-mean total column rain water 235113: - avg_sm20 - Time-mean soil moisture top 20 cm 235114: - avg_sm100 - Time-mean soil moisture top 100 cm 235115: - avg_st20 - Time-mean soil temperature top 20 cm 235116: - avg_st100 - Time-mean soil temperature top 100 cm 235117: - avg_mucape - Time-mean most-unstable CAPE 235118: - avg_vsw20 - Time-mean volumetric soil moisture top 20 cm 235119: - avg_vsw100 - Time-mean volumetric soil moisture top 100 cm 235120: - avg_tcolg - Time-mean total column vertically-integrated graupel (snow pellets) 235129: - avg_z - Time-mean geopotential 235130: - avg_t - Time-mean temperature 235131: - avg_u - Time-mean U component of wind 235132: - avg_v - Time-mean V component of wind 235133: - avg_q - Time-mean specific humidity 235134: - avg_sp - Time-mean surface pressure 235135: - avg_w - Time-mean vertical velocity 235136: - avg_tcw - Time-mean total column water 235137: - avg_tcwv - Time-mean total column vertically-integrated water vapour 235138: - avg_vo - Time-mean vorticity (relative) 235141: - avg_sd_m - Time-mean snow depth 235149: - avg_snrf - Time-mean surface net radiation flux (SW and LW) 235150: - avg_tnrf - Time-mean top net radiation flux (SW and LW) 235151: - avg_msl - Time-mean mean sea level pressure 235152: - avg_lnsp - Time-mean logarithm of surface pressure 235155: - avg_d - Time-mean divergence 235157: - avg_r - Time-mean relative humidity 235159: - avg_blh - Time-mean boundary layer height 235165: - avg_10u - Time-mean 10 metre U wind component 235166: - avg_10v - Time-mean 10 metre V wind component 235168: - avg_2d - Time-mean 2 metre dewpoint temperature 235186: - avg_lcc_frac - Time-mean low cloud cover 235187: - avg_mcc_frac - Time-mean medium cloud cover 235188: - avg_hcc_frac - Time-mean high cloud cover 235189: - avg_suns - Time-mean sunshine 235203: - avg_o3 - Time-mean ozone mass mixing ratio 235238: - avg_tsn - Time-mean temperature of snow layer 235243: - avg_fal_frac - Time-mean forecast albedo 235244: - avg_fsr - Time-mean forecast surface roughness 235245: - avg_flsr - Time-mean forecast logarithm of surface roughness for heat 235246: - avg_clwc - Time-mean specific cloud liquid water content 235247: - avg_ciwc - Time-mean specific cloud ice water content 235248: - avg_cc - Time-mean fraction of cloud cover 235257: - avg_kx - Time-mean K index 235258: - avg_totalx - Time-mean total totals index 235261: - avg_10wdir - Time-mean 10 metre wind direction 235262: - avg_cat - Time-mean clear air turbulence (CAT) 235263: - avg_al - Time-mean forecast albedo 235269: - avg_pt - Time-mean potential temperature 235270: - avg_dis - Time-mean discharge from rivers or streams 235271: - avg_swit - Time-mean soil wetness index (total layer) 235272: - avg_swir - Time-mean soil wetness index (root zone) 235273: - avg_swil - Time-mean soil wetness index(layer) 235274: - avg_flddep - Time-mean floodplain depth 235275: - avg_fldffr - Time-mean floodplain flooded fraction 235276: - avg_fldfar - Time-mean floodplain flooded area 235277: - avg_rivfr - Time-mean river fraction 235278: - avg_rivar - Time-mean river area 235279: - avg_rivcffr - Time-mean fraction of river coverage plus river related flooding 235280: - avg_rivcfar - Time-mean area of river coverage plus river related flooding 235281: - avg_aluvp_p - Time-mean UV visible albedo for direct radiation (climatological) 235282: - avg_aluvd_p - Time-mean UV visible albedo for diffuse radiation (climatological) 235283: - avg_alnip_p - Time-mean near IR albedo for direct radiation (climatological) 235284: - avg_alnid_p - Time-mean near IR albedo for diffuse radiation (climatological) 235285: - avg_asn - Time-mean snow albedo 235286: - avg_mont - Time-mean montgomery potential 235287: - avg_cape - Time-mean convective available potential energy 235288: - avg_tcc - Time-mean total cloud cover 235289: - avg_srcon - Time-mean skin reservoir content 235290: - avg_tcioz - Time-mean total column integrated ozone 235291: - avg_vike - Time-mean total column vertically-integrated kinetic energy 235292: - avg_vithe - Time-mean total column vertically-integrated enthalpy 235293: - avg_vipie - Time-mean total column vertically-integrated potential + internal energy 235294: - avg_vipile - Time-mean total column vertically-integrated potential+internal+latent energy 235295: - avg_vitoe - Time-mean total column vertically-integrated total energy 235296: - avg_viwe - Time-mean total column vertically-integrated water enthalpy 235297: - avg_aluvpi_p - Time-mean UV visible albedo for direct radiation, isotropic component (climatological) 235298: - avg_aluvpv_p - Time-mean UV visible albedo for direct radiation, volumetric component (climatological) 235299: - avg_aluvpg_p - Time-mean UV visible albedo for direct radiation, geometric component (climatological) 235300: - avg_alnipi_p - Time-mean near IR albedo for direct radiation, isotropic component (climatological) 235301: - avg_alnipv_p - Time-mean near IR albedo for direct radiation, volumetric component (climatological) 235302: - avg_alnipg_p - Time-mean near IR albedo for direct radiation, geometric component (climatological) 235303: - avg_cin - Time-mean convective inhibition 235304: - avg_zust - Time-mean friction velocity 235305: - avg_lblt - Time-mean lake bottom temperature 235306: - avg_ltlt - Time-mean lake total layer temperature 235307: - avg_lshf - Time-mean lake shape factor 235308: - avg_lict - Time-mean lake ice surface temperature 235309: - avg_licd - Time-mean lake ice total depth 235310: - avg_dndzn - Time-mean minimum vertical gradient of refractivity inside trapping layer 235311: - avg_dndza - Time-mean mean vertical gradient of refractivity inside trapping layer 235312: - avg_dctb - Time-mean duct base height 235313: - avg_tplb - Time-mean trapping layer base height 235314: - avg_tplt - Time-mean trapping layer top height 235315: - avg_degm10l - Time-mean geometric height of -10 degrees C atmospheric isothermal level above ground 235316: - avg_cbh - Time-mean cloud base height 235317: - avg_deg0l - Time-mean geometric height of 0 degrees C atmospheric isothermal level above ground 235318: - avg_i10fg - Time-mean 10 metre wind gust 235319: - avg_rhw - Time-mean relative humidity with respect to water 235320: - avg_2rhw - Time-mean 2 metre relative humidity with respect to water 235321: - avg_capes - Time-mean convective available potential energy shear 235322: - avg_trpp - Time-mean tropopause pressure 235323: - avg_hcct - Time-mean height of convective cloud top 235324: - avg_hwbt0 - Time-mean height of zero-degree wet-bulb temperature 235325: - avg_hwbt1 - Time-mean height of one-degree wet-bulb temperature 235326: - avg_litoti - Time-mean total lightning flash density 235327: - avg_tcslw - Time-mean total column supercooled liquid water 235328: - avg_u10n - Time-mean 10 metre U-component of neutral wind 235329: - avg_v10n - Time-mean 10 metre V-component of neutral wind 235330: - avg_crr - Time-mean convective rain rate 235331: - avg_lsrr - Time-mean large scale rain rate 235332: - avg_mlcape50 - Time-mean mixed-layer CAPE in the lowest 50 hPa 235333: - avg_mlcin50 - Time-mean mixed-layer CIN in the lowest 50 hPa 235334: - avg_mlcape100 - Time-mean mixed-layer CAPE in the lowest 100 hPa 235335: - avg_mlcin100 - Time-mean mixed-layer CIN in the lowest 100 hPa 235336: - avg_mudlp - Time-mean departure level of the most unstable parcel expressed as Pressure 235337: - avg_ceil - Time-mean ceiling 235338: - avg_tcsqw - Time-mean total column integrated saturation specific humidity with respect to water 235339: - avg_sdirnswrf - Time-mean surface direct normal short-wave radiation flux 235340: - avg_cossza - Time-mean cosine of solar zenith angle 235341: - avg_fprate - Time-mean freezing rain precipitation rate 235342: - avg_vige - Time-mean total column vertically-integrated eastward geopotential flux 235343: - avg_vign - Time-mean total column vertically-integrated northward geopotential flux 235344: - avg_viwgd - Time-mean total column vertically-integrated divergence of water geopotential flux 235345: - avg_vigd - Time-mean total column vertically-integrated divergence of geopotential flux 235346: - avg_viee - Time-mean total column vertically-integrated eastward enthalpy flux 235347: - avg_vien - Time-mean total column vertically-integrated northward enthalpy flux 235348: - avg_vikee - Time-mean total column vertically-integrated eastward kinetic energy flux 235349: - avg_viken - Time-mean total column vertically-integrated northward kinetic energy flux 235350: - avg_vitee - Time-mean total column vertically-integrated eastward total energy flux 235351: - avg_viten - Time-mean total column vertically-integrated northward total energy flux 235352: - avg_vied - Time-mean total column vertically-integrated divergence of enthalpy flux 235353: - avg_viked - Time-mean total column vertically-integrated divergence of kinetic energy flux 235354: - avg_vited - Time-mean total column vertically-integrated divergence of total energy flux 235355: - avg_viwed - Time-mean total column vertically-integrated divergence of water enthalpy flux 235356: - avg_vimad - Time-mean total column vertically-integrated divergence of mass flux 235357: - avg_vimae - Time-mean total column vertically-integrated eastward mass flux 235358: - avg_viman - Time-mean total column vertically-integrated northward mass flux 235359: - avg_viwvd - Time-mean total column vertically-integrated divergence of water vapour flux 235360: - avg_viclwd - Time-mean total column vertically-integrated divergence of cloud liquid water flux 235361: - avg_viciwd - Time-mean total column vertically-integrated divergence of cloud ice water flux 235362: - avg_vird - Time-mean total column vertically-integrated divergence of rain flux 235363: - avg_visd - Time-mean total column vertically-integrated divergence of snow flux 235364: - avg_viwve - Time-mean total column vertically-integrated eastward water vapour flux 235365: - avg_viwvn - Time-mean total column vertically-integrated northward water vapour flux 235366: - avg_viclwe - Time-mean total column vertically-integrated eastward cloud liquid water flux 235367: - avg_viclwn - Time-mean total column vertically-integrated northward cloud liquid water flux 235368: - avg_viciwe - Time-mean total column vertically-integrated eastward cloud ice water flux 235369: - avg_viciwn - Time-mean total column vertically-integrated northward cloud ice water flux 235370: - avg_vire - Time-mean total column vertically-integrated eastward rain flux 235371: - avg_virn - Time-mean total column vertically-integrated northward rain flux 235372: - avg_vise - Time-mean total column vertically-integrated eastward snow flux 235373: - avg_visn - Time-mean total column vertically-integrated northward snow flux 235374: - avg_vioze - Time-mean total column vertically-integrated eastward ozone flux 235375: - avg_viozn - Time-mean total column vertically-integrated northward ozone flux 235376: - avg_viozd - Time-mean total column vertically-integrated divergence of ozone flux 235377: - avg_vions - Time-mean total column vertically-integrated net source of ozone 235378: - avg_ietssofd - Time-mean eastward turbulent surface stress due to orographic form drag 235379: - avg_intssofd - Time-mean northward turbulent surface stress due to orographic form drag 235380: - avg_ietsssr - Time-mean eastward turbulent surface stress due to surface roughness 235381: - avg_intsssr - Time-mean northward turbulent surface stress due to surface roughness 235382: - avg_visp - Time-mean visibility through precipitation 235383: - avg_licgi - Time-mean cloud-to-ground lightning flash density 235386: - avg_lshnf - Time-mean land surface heat net flux 235387: - avg_sdirnswrfcs - Time-mean surface direct normal short-wave radiation flux, clear sky 235388: - avg_10cogu - Time-mean 10 metre convective gust 235389: - avg_10tugu - Time-mean 10 metre turbulent gust 235390: - avg_h0thg - Time-mean geometric height of 0 degrees C theta level above ground 235391: - avg_h1thg - Time-mean geometric height of 1 degree C theta level above ground 235392: - avg_h1p5thg - Time-mean geometric height of 1.5 degrees C theta level above ground 235393: - avg_veg - Time-mean vegetation 235394: - avg_lai - Time-mean leaf area index 235395: - avg_srhe - Time-mean surface roughness for heat 235396: - avg_rol - Time-mean Reciprocal Obukhov length 235397: - avg_deg3l - Time-mean geometric height of 3 degrees C atmospheric isothermal level above ground 235398: - avg_rivsto - Time-mean river storage of water 235399: - avg_fldsto - Time-mean floodplain storage of water 235400: - avg_rivdph - Time-mean river depth 235401: - avg_rivout - Time-mean river outflow of water 235402: - avg_fldout - Time-mean floodplain outflow of water 235403: - avg_pthflw - Time-mean floodpath outflow of water 235404: - avg_sdifswrfcs - Time-mean surface diffuse short-wave radiation flux, clear sky 235405: - avg_vswrz - Time-mean volumetric soil moisture (root zone) 235406: - avg_lwcs - Time-mean liquid water content in snow pack 235407: - avg_mucin - Time-mean most-unstable CIN 235408: - avg_crwc_conv - Time-mean specific rain water content (convective) 235409: - avg_cswc_conv - Time-mean specific snow water content (convective) 235410: - avg_mld - Time-mean mixed layer depth 235411: - avg_cur - Time-mean urban cover 235412: - avg_wse - Time-mean water surface elevation 235413: - avg_grfr - Time-mean groundwater return flow rate 235414: - avg_rivfldsto - Time-mean river and floodplain storage 235415: - avg_darv - Time-mean depth-averaged river velocity 236386: - acc_lshnf - Time-integrated land surface heat net flux 236387: - acc_sdirnswrfcs - Time-integrated surface direct normal short-wave radiation flux, clear sky 237001: - max_ttswr - Time-maximum temperature tendency due to short-wave radiation 237002: - max_ttlwr - Time-maximum temperature tendency due to long-wave radiation 237003: - max_ttswrcs - Time-maximum temperature tendency due to short wave radiation, clear sky 237004: - max_ttlwrcs - Time-maximum temperature tendency due to long-wave radiation, clear sky 237005: - max_ttpm - Time-maximum temperature tendency due to parametrisations 237006: - max_qtpm - Time-maximum specific humidity tendency due to parametrisations 237007: - max_utpm - Time-maximum eastward wind tendency due to parametrisations 237008: - max_vtpm - Time-maximum northward wind tendency due to parametrisations 237009: - max_umf - Time-maximum updraught mass flux 237010: - max_dmf - Time-maximum downdraught mass flux 237011: - max_udr - Time-maximum updraught detrainment rate 237012: - max_ddr - Time-maximum downdraught detrainment rate 237013: - max_tpf - Time-maximum total precipitation flux 237014: - max_tdch - Time-maximum turbulent diffusion coefficient for heat 237022: - max_parcsf - Time-maximum surface photosynthetically active radiation flux, clear sky 237023: - max_esrwe - Time-maximum snow evaporation rate water equivalent 237024: - max_smr - Time-maximum snow melt rate 237025: - max_imagss - Time-maximum magnitude of turbulent surface stress 237026: - max_ilspf - Time-maximum large-scale precipitation fraction 237027: - max_sduvrf - Time-maximum surface downward UV radiation flux 237028: - max_sparf - Time-maximum surface photosynthetically active radiation flux 237029: - max_lsprate - Time-maximum large-scale precipitation rate 237030: - max_cpr - Time-maximum convective precipitation rate 237031: - max_tsrwe - Time-maximum total snowfall rate water equivalent 237032: - max_ibld - Time-maximum boundary layer dissipation 237033: - max_ishf - Time-maximum surface sensible heat flux 237034: - max_slhtf - Time-maximum surface latent heat flux 237035: - max_sdswrf - Time-maximum surface downward short-wave radiation flux 237036: - max_sdlwrf - Time-maximum surface downward long-wave radiation flux 237037: - max_snswrf - Time-maximum surface net short-wave radiation flux 237038: - max_snlwrf - Time-maximum surface net long-wave radiation flux 237039: - max_tnswrf - Time-maximum top net short-wave radiation flux 237040: - max_tnlwrf - Time-maximum top net long-wave radiation flux 237041: - max_iews - Time-maximum eastward turbulent surface stress 237042: - max_inss - Time-maximum northward turbulent surface stress 237043: - max_ie - Time-maximum moisture flux 237045: - max_iegwss - Time-maximum eastward gravity wave surface stress 237046: - max_ingwss - Time-maximum northward gravity wave surface stress 237047: - max_igwd - Time-maximum gravity wave dissipation 237048: - max_rorwe - Time-maximum runoff rate water equivalent (surface plus subsurface) 237049: - max_tnswrfcs - Time-maximum top net short-wave radiation flux, clear sky 237050: - max_tnlwrfcs - Time-maximum top net long-wave radiation flux, clear sky 237051: - max_snswrfcs - Time-maximum surface net short-wave radiation flux, clear sky 237052: - max_snlwrfcs - Time-maximum surface net long-wave radiation flux, clear sky 237053: - max_tdswrf - Time mean top downward short-wave radiation flux 237054: - max_vimdf - Time-maximum total column vertically-integrated moisture divergence flux 237055: - max_tprate - Time-maximum total precipitation rate 237056: - max_csfr - Time-maximum convective snowfall rate water equivalent 237057: - max_lssfr - Time-maximum large scale snowfall rate water equivalent 237058: - max_sdirswrf - Time-maximum surface direct short-wave radiation flux 237059: - max_sdirswrfcs - Time-maximum surface direct short-wave radiation flux, clear sky 237062: - max_fco2nee - Time-maximum carbon dioxide net ecosystem exchange flux 237063: - max_fco2gpp - Time-maximum carbon dioxide gross primary production flux 237064: - max_fco2rec - Time-maximum carbon dioxide ecosystem respiration flux 237068: - max_sdswrfcs - Time-maximum surface downward short-wave radiation flux, clear sky 237069: - max_sdlwrfcs - Time-maximum surface downward long-wave radiation flux, clear sky 237070: - max_pevr - Time-maximum potential evaporation rate 237077: - max_vsw - Time-maximum volumetric soil moisture 237078: - max_sd - Time-maximum snow depth water equivalent 237079: - max_skt - Time-maximum skin temperature 237080: - max_rsn - Time-maximum snow density 237083: - max_ci - Time-maximum sea ice area fraction 237084: - max_sst - Time-maximum sea surface temperature 237085: - max_lai_lv - Time-maximum leaf area index, low vegetation 237086: - max_lai_hv - Time-maximum leaf area index, high vegetation 237087: - max_tclw - Time-maximum total column cloud liquid water 237088: - max_tciw - Time-maximum total column cloud ice water 237089: - max_2sh - Time-maximum 2 metre specific humidity 237090: - max_lmlt - Time-maximum lake mix-layer temperature 237091: - max_lmld - Time-maximum lake mix-layer depth 237092: - max_2r - Time-maximum 2 metre relative humidity 237093: - max_fscov - Time-maximum fraction of snow cover 237094: - max_sot - Time-maximum soil temperature 237097: - max_ws - Time-maximum wind speed 237098: - max_pres - Time-maximum pressure 237099: - max_chnk - Time-maximum charnock 237100: - max_pv - Time-maximum potential vorticity 237101: - max_crwc - Time-maximum specific rain water content 237102: - max_cswc - Time-maximum specific snow water content 237103: - max_etadot - Time-maximum eta-coordinate vertical velocity 237105: - max_vis - Time-maximum visibility 237106: - max_wdir - Time-maximum wind direction 237107: - max_tcsw - Time-maximum total column snow water 237108: - max_lcc - Time-maximum low cloud cover 237109: - max_mcc - Time-maximum medium cloud cover 237110: - max_hcc - Time-maximum high cloud cover 237111: - max_tcrw - Time-maximum total column rain water 237117: - max_mucape - Time-maximum most-unstable CAPE 237120: - max_tcolg - Time-maximum total column vertically-integrated graupel (snow pellets) 237129: - max_z - Time-maximum geopotential 237130: - max_t - Time-maximum temperature 237131: - max_u - Time-maximum u component of wind 237132: - max_v - Time-maximum v component of wind 237133: - max_q - Time-maximum specific humidity 237134: - max_sp - Time-maximum surface pressure 237135: - max_w - Time-maximum vertical velocity 237136: - max_tcw - Time-maximum total column water 237137: - max_tcwv - Time-maximum total column vertically-integrated water vapour 237138: - max_vo - Time-maximum vorticity (relative) 237151: - max_msl - Time-maximum mean sea level pressure 237152: - max_lnsp - Time-maximum logarithm of surface pressure 237155: - max_d - Time-maximum divergence 237157: - max_r - Time-maximum relative humidity 237159: - max_blh - Time-maximum boundary layer height 237165: - max_10u - Time-maximum 10 metre U wind component 237166: - max_10v - Time-maximum 10 metre V wind component 237167: - max_2t - Time-maximum 2 metre temperature 237168: - max_2d - Time-maximum 2 metre dewpoint temperature 237189: - max_suns - Time-maximum sunshine 237203: - max_o3 - Time-maximum ozone mass mixing ratio 237207: - max_10si - Time-maximum 10 metre wind speed 237238: - max_tsn - Time-maximum temperature of snow layer 237244: - max_fsr - Time-maximum forecast surface roughness 237245: - max_flsr - Time-maximum forecast logarithm of surface roughness for heat 237246: - max_clwc - Time-maximum specific cloud liquid water content 237247: - max_ciwc - Time-maximum specific cloud ice water content 237248: - max_cc - Time-maximum fraction of cloud cover 237257: - max_kx - Time-maximum k index 237258: - max_totalx - Time-maximum total totals index 237261: - max_10wdir - Time-maximum 10 metre wind direction 237262: - max_cat - Time-maximum clear air turbulence (CAT) 237263: - max_al - Time-maximum forecast albedo 237269: - max_pt - Time-maximum potential temperature 237281: - max_aluvp_p - Time-maximum UV visible albedo for direct radiation (climatological) 237282: - max_aluvd_p - Time-maximum UV visible albedo for diffuse radiation (climatological) 237283: - max_alnip_p - Time-maximum near IR albedo for direct radiation (climatological) 237284: - max_alnid_p - Time-maximum near IR albedo for diffuse radiation (climatological) 237285: - max_asn - Time-maximum snow albedo 237286: - max_mont - Time-maximum montgomery potential 237287: - max_cape - Time-maximum convective available potential energy 237288: - max_tcc - Time-maximum total cloud cover 237289: - max_srcon - Time-maximum skin reservoir content 237290: - max_tcioz - Time-maximum total column integrated ozone 237291: - max_vike - Time-maximum total column vertically-integrated kinetic energy 237292: - max_vithe - Time-maximum total column vertically-integrated enthalpy 237293: - max_vipie - Time-maximum total column vertically-integrated potential + internal energy 237294: - max_vipile - Time-maximum total column vertically-integrated potential+internal+latent energy 237295: - max_vitoe - Time-maximum total column vertically-integrated total energy 237296: - max_viwe - Time-maximum total column vertically-integrated water enthalpy 237297: - max_aluvpi_p - Time-maximum UV visible albedo for direct radiation, isotropic component (climatological) 237298: - max_aluvpv_p - Time-maximum UV visible albedo for direct radiation, volumetric component (climatological) 237299: - max_aluvpg_p - Time-maximum UV visible albedo for direct radiation, geometric component (climatological) 237300: - max_alnipi_p - Time-maximum near IR albedo for direct radiation, isotropic component (climatological) 237301: - max_alnipv_p - Time-maximum near IR albedo for direct radiation, volumetric component (climatological) 237302: - max_alnipg_p - Time-maximum near IR albedo for direct radiation, geometric component (climatological) 237303: - max_cin - Time-maximum convective inhibition 237304: - max_zust - Time-maximum friction velocity 237305: - max_lblt - Time-maximum lake bottom temperature 237306: - max_ltlt - Time-maximum lake total layer temperature 237307: - max_lshf - Time-maximum lake shape factor 237308: - max_lict - Time-maximum lake ice surface temperature 237309: - max_licd - Time-maximum lake ice total depth 237310: - max_dndzn - Time-maximum minimum vertical gradient of refractivity inside trapping layer 237311: - max_dndza - Time-maximum mean vertical gradient of refractivity inside trapping layer 237312: - max_dctb - Time-maximum duct base height 237313: - max_tplb - Time-maximum trapping layer base height 237314: - max_tplt - Time-maximum trapping layer top height 237315: - max_degm10l - Time-maximum geometric height of -10 degrees C atmospheric isothermal level above ground 237316: - max_cbh - Time-maximum cloud base height 237317: - max_deg0l - Time-maximum geometric height of 0 degrees C atmospheric isothermal level above ground 237318: - max_i10fg - Time-maximum 10 metre wind gust 237319: - max_rhw - Time-maximum relative humidity with respect to water 237320: - max_2rhw - Time-maximum 2 metre relative humidity with respect to water 237321: - max_capes - Time-maximum convective available potential energy shear 237322: - max_trpp - Time-maximum tropopause pressure 237323: - max_hcct - Time-maximum height of convective cloud top 237324: - max_hwbt0 - Time-maximum height of zero-degree wet-bulb temperature 237325: - max_hwbt1 - Time-maximum height of one-degree wet-bulb temperature 237326: - max_litoti - Time-maximum total lightning flash density 237327: - max_tcslw - Time-maximum total column supercooled liquid water 237328: - max_u10n - Time-maximum 10 metre U-component of neutral wind 237329: - max_v10n - Time-maximum 10 metre V-component of neutral wind 237330: - max_crr - Time-maximum convective rain rate 237331: - max_lsrr - Time-maximum large scale rain rate 237332: - max_mlcape50 - Time-maximum mixed-layer CAPE in the lowest 50 hPa 237333: - max_mlcin50 - Time-maximum mixed-layer CIN in the lowest 50 hPa 237334: - max_mlcape100 - Time-maximum mixed-layer CAPE in the lowest 100 hPa 237335: - max_mlcin100 - Time-maximum mixed-layer CIN in the lowest 100 hPa 237336: - max_mudlp - Time-maximum departure level of the most unstable parcel expressed as Pressure 237337: - max_ceil - Time-maximum ceiling 237338: - max_tcsqw - Time-maximum total column integrated saturation specific humidity with respect to water 237339: - max_sdirnswrf - Time-maximum surface direct normal short-wave radiation flux 237340: - max_cossza - Time-maximum cosine of solar zenith angle 237341: - max_fprate - Time-maximum freezing rain precipitation rate 237342: - max_vige - Time-maximum total column vertically-integrated eastward geopotential flux 237343: - max_vign - Time-maximum total column vertically-integrated northward geopotential flux 237344: - max_viwgd - Time-maximum total column vertically-integrated divergence of water geopotential flux 237345: - max_vigd - Time-maximum total column vertically-integrated divergence of geopotential flux 237346: - max_viee - Time-maximum total column vertically-integrated eastward enthalpy flux 237347: - max_vien - Time-maximum total column vertically-integrated northward enthalpy flux 237348: - max_vikee - Time-maximum total column vertically-integrated eastward kinetic energy flux 237349: - max_viken - Time-maximum total column vertically-integrated northward kinetic energy flux 237350: - max_vitee - Time-maximum total column vertically-integrated eastward total energy flux 237351: - max_viten - Time-maximum total column vertically-integrated northward total energy flux 237352: - max_vied - Time-maximum total column vertically-integrated divergence of enthalpy flux 237353: - max_viked - Time-maximum total column vertically-integrated divergence of kinetic energy flux 237354: - max_vited - Time-maximum total column vertically-integrated divergence of total energy flux 237355: - max_viwed - Time-maximum total column vertically-integrated divergence of water enthalpy flux 237356: - max_vimad - Time-maximum total column vertically-integrated divergence of mass flux 237357: - max_vimae - Time-maximum total column vertically-integrated eastward mass flux 237358: - max_viman - Time-maximum total column vertically-integrated northward mass flux 237359: - max_viwvd - Time-maximum total column vertically-integrated divergence of water vapour flux 237360: - max_viclwd - Time-maximum total column vertically-integrated divergence of cloud liquid water flux 237361: - max_viciwd - Time-maximum total column vertically-integrated divergence of cloud ice water flux 237362: - max_vird - Time-maximum total column vertically-integrated divergence of rain flux 237363: - max_visd - Time-maximum total column vertically-integrated divergence of snow flux 237364: - max_viwve - Time-maximum total column vertically-integrated eastward water vapour flux 237365: - max_viwvn - Time-maximum total column vertically-integrated northward water vapour flux 237366: - max_viclwe - Time-maximum total column vertically-integrated eastward cloud liquid water flux 237367: - max_viclwn - Time-maximum total column vertically-integrated northward cloud liquid water flux 237368: - max_viciwe - Time-maximum total column vertically-integrated eastward cloud ice water flux 237369: - max_viciwn - Time-maximum total column vertically-integrated northward cloud ice water flux 237370: - max_vire - Time-maximum total column vertically-integrated eastward rain flux 237371: - max_virn - Time-maximum total column vertically-integrated northward rain flux 237372: - max_vise - Time-maximum total column vertically-integrated eastward snow flux 237373: - max_visn - Time-maximum total column vertically-integrated northward snow flux 237374: - max_vioze - Time-maximum total column vertically-integrated eastward ozone flux 237375: - max_viozn - Time-maximum total column vertically-integrated northward ozone flux 237376: - max_viozd - Time-maximum total column vertically-integrated divergence of ozone flux 237377: - max_vions - Time-maximum total column vertically-integrated net source of ozone 237378: - max_ietssofd - Time-maximum eastward turbulent surface stress due to orographic form drag 237379: - max_intssofd - Time-maximum northward turbulent surface stress due to orographic form drag 237380: - max_ietsssr - Time-maximum eastward turbulent surface stress due to surface roughness 237381: - max_intsssr - Time-maximum northward turbulent surface stress due to surface roughness 237382: - max_visp - Time-maximum visibility through precipitation 237383: - max_licgi - Time-maximum cloud-to-ground lightning flash density 237384: - max_10efg - Time-maximum 10 metre eastward wind gust 237385: - max_10nfg - Time-maximum 10 metre northward wind gust 237386: - max_lshnf - Time-maximum land surface heat net flux 237387: - max_sdirnswrfcs - Time-maximum surface direct normal short-wave radiation flux, clear sky 237388: - max_10cogu - Time-maximum 10 metre convective gust 237389: - max_10tugu - Time-maximum 10 metre turbulent gust 237390: - max_h0thg - Time-maximum geometric height of 0 degrees C theta level above ground 237391: - max_h1thg - Time-maximum geometric height of 1 degree C theta level above ground 237392: - max_h1p5thg - Time-maximum geometric height of 1.5 degrees C theta level above ground 237393: - max_veg - Time-maximum vegetation 237394: - max_lai - Time-maximum leaf area index 237395: - max_srhe - Time-maximum surface roughness for heat 237396: - max_rol - Time-maximum Reciprocal Obukhov length 237397: - max_deg3l - Time-maximum geometric height of 3 degrees C atmospheric isothermal level above ground 237404: - max_sdifswrfcs - Time-maximum surface diffuse short-wave radiation flux, clear sky 237405: - max_vswrz - Time-maximum volumetric soil moisture (root zone) 237406: - max_lwcs - Time-maximum liquid water content in snow pack 237407: - max_mucin - Time-maximum most-unstable CIN 237408: - max_crwc_conv - Time-maximum specific rain water content (convective) 237409: - max_cswc_conv - Time-maximum specific snow water content (convective) 237410: - max_mld - Time-maximum mixed layer depth 237411: - max_cur - Time-maximum urban cover 238001: - min_ttswr - Time-minimum temperature tendency due to short-wave radiation 238002: - min_ttlwr - Time-minimum temperature tendency due to long-wave radiation 238003: - min_ttswrcs - Time-minimum temperature tendency due to short wave radiation, clear sky 238004: - min_ttlwrcs - Time-minimum temperature tendency due to long-wave radiation, clear sky 238005: - min_ttpm - Time-minimum temperature tendency due to parametrisations 238006: - min_qtpm - Time-minimum specific humidity tendency due to parametrisations 238007: - min_utpm - Time-minimum eastward wind tendency due to parametrisations 238008: - min_vtpm - Time-minimum northward wind tendency due to parametrisations 238009: - min_umf - Time-minimum updraught mass flux 238010: - min_dmf - Time-minimum downdraught mass flux 238011: - min_udr - Time-minimum updraught detrainment rate 238012: - min_ddr - Time-minimum downdraught detrainment rate 238013: - min_tpf - Time-minimum total precipitation flux 238014: - min_tdch - Time-minimum turbulent diffusion coefficient for heat 238022: - min_parcsf - Time-minimum surface photosynthetically active radiation flux, clear sky 238023: - min_esrwe - Time-minimum snow evaporation rate 238024: - min_smr - Time-minimum snowmelt rate 238025: - min_imagss - Time-minimum magnitude of turbulent surface stress 238026: - min_ilspf - Time-minimum large-scale precipitation fraction 238027: - min_sduvrf - Time-minimum surface downward UV radiation flux 238028: - min_sparf - Time-minimum surface photosynthetically active radiation flux 238029: - min_lsprate - Time-minimum large-scale precipitation rate 238030: - min_cpr - Time-minimum convective precipitation rate 238031: - min_tsrwe - Time-minimum snowfall rate 238032: - min_ibld - Time-minimum boundary layer dissipation 238033: - min_ishf - Time-minimum surface sensible heat flux 238034: - min_slhtf - Time-minimum surface latent heat flux 238035: - min_sdswrf - Time-minimum surface downward short-wave radiation flux 238036: - min_sdlwrf - Time-minimum surface downward long-wave radiation flux 238037: - min_snswrf - Time-minimum surface net short-wave radiation flux 238038: - min_snlwrf - Time-minimum surface net long-wave radiation flux 238039: - min_tnswrf - Time-minimum top net short-wave radiation flux 238040: - min_tnlwrf - Time-minimum top net long-wave radiation flux 238041: - min_iews - Time-minimum eastward turbulent surface stress 238042: - min_inss - Time-minimum northward turbulent surface stress 238043: - min_ie - Time-minimum evaporation rate 238045: - min_iegwss - Time-minimum eastward gravity wave surface stress 238046: - min_ingwss - Time-minimum northward gravity wave surface stress 238047: - min_igwd - Time-minimum gravity wave dissipation 238048: - min_rorwe - Time-minimum runoff rate 238049: - min_tnswrfcs - Time-minimum top net short-wave radiation flux, clear sky 238050: - min_tnlwrfcs - Time-minimum top net long-wave radiation flux, clear sky 238051: - min_snswrfcs - Time-minimum surface net short-wave radiation flux, clear sky 238052: - min_snlwrfcs - Time-minimum surface net long-wave radiation flux, clear sky 238053: - min_tdswrf - Time-minimum top downward short-wave radiation flux 238054: - min_vimdf - Time-minimum vertically integrated moisture divergence 238055: - min_tprate - Time-minimum total precipitation rate 238056: - min_csfr - Time-minimum convective snowfall rate water equivalent 238057: - min_lssfr - Time-minimum large scale snowfall rate water equivalent 238058: - min_sdirswrf - Time-minimum surface direct short-wave radiation flux 238059: - min_sdirswrfcs - Time-minimum surface direct short-wave radiation flux, clear sky 238062: - min_fco2nee - Time-minimum carbon dioxide net ecosystem exchange flux 238063: - min_fco2gpp - Time-minimum carbon dioxide gross primary production flux 238064: - min_fco2rec - Time-minimum carbon dioxide ecosystem respiration flux 238068: - min_sdswrfcs - Time-minimum surface downward short-wave radiation flux, clear sky 238069: - min_sdlwrfcs - Time-minimum surface downward long-wave radiation flux, clear sky 238070: - min_pevr - Time-minimum potential evaporation rate 238077: - min_vsw - Time-minimum volumetric soil moisture 238078: - min_sd - Time-minimum snow depth water equivalent 238079: - min_skt - Time-minimum skin temperature 238080: - min_rsn - Time-minimum snow density 238083: - min_ci - Time-minimum sea ice area fraction 238084: - min_sst - Time-minimum sea surface temperature 238085: - min_lai_lv - Time-minimum leaf area index, low vegetation 238086: - min_lai_hv - Time-minimum leaf area index, high vegetation 238087: - min_tclw - Time-minimum total column cloud liquid water 238088: - min_tciw - Time-minimum total column cloud ice water 238089: - min_2sh - Time-minimum 2 metre specific humidity 238090: - min_lmlt - Time-minimum lake mix-layer temperature 238091: - min_lmld - Time-minimum lake mix-layer depth 238092: - min_2r - Time-minimum 2 metre relative humidity 238093: - min_fscov - Time-minimum fraction of snow cover 238094: - min_sot - Time-minimum soil temperature 238097: - min_ws - Time-minimum wind speed 238098: - min_pres - Time-minimum pressure 238099: - min_chnk - Time-minimum charnock 238100: - min_pv - Time-minimum potential vorticity 238101: - min_crwc - Time-minimum specific rain water content 238102: - min_cswc - Time-minimum specific snow water content 238103: - min_etadot - Time-minimum eta-coordinate vertical velocity 238105: - min_vis - Time-minimum visibility 238106: - min_wdir - Time-minimum wind direction 238107: - min_tcsw - Time-minimum total column snow water 238108: - min_lcc - Time-minimum low cloud cover 238109: - min_mcc - Time-minimum medium cloud cover 238110: - min_hcc - Time-minimum high cloud cover 238111: - min_tcrw - Time-minimum total column rain water 238117: - min_mucape - Time-minimum most-unstable CAPE 238120: - min_tcolg - Time-minimum total column vertically-integrated graupel (snow pellets) 238129: - min_z - Time-minimum geopotential 238130: - min_t - Time-minimum temperature 238131: - min_u - Time-minimum u component of wind 238132: - min_v - Time-minimum v component of wind 238133: - min_q - Time-minimum specific humidity 238134: - min_sp - Time-minimum surface pressure 238135: - min_w - Time-minimum vertical velocity 238136: - min_tcw - Time-minimum total column water 238137: - min_tcwv - Time-minimum total column vertically-integrated water vapour 238138: - min_vo - Time-minimum vorticity (relative) 238151: - min_msl - Time-minimum mean sea level pressure 238152: - min_lnsp - Time-minimum logarithm of surface pressure 238155: - min_d - Time-minimum divergence 238157: - min_r - Time-minimum relative humidity 238159: - min_blh - Time-minimum boundary layer height 238165: - min_10u - Time-minimum 10 metre U wind component 238166: - min_10v - Time-minimum 10 metre V wind component 238167: - min_2t - Time-minimum 2 metre temperature 238168: - min_2d - Time-minimum 2 metre dewpoint temperature 238189: - min_suns - Time-minimum sunshine 238203: - min_o3 - Time-minimum ozone mass mixing ratio 238207: - min_10si - Time-minimum 10 metre wind speed 238238: - min_tsn - Time-minimum temperature of snow layer 238244: - min_fsr - Time-minimum forecast surface roughness 238245: - min_flsr - Time-minimum forecast logarithm of surface roughness for heat 238246: - min_clwc - Time-minimum specific cloud liquid water content 238247: - min_ciwc - Time-minimum specific cloud ice water content 238248: - min_cc - Time-minimum fraction of cloud cover 238257: - min_kx - Time-minimum k index 238258: - min_totalx - Time-minimum total totals index 238261: - min_10wdir - Time-minimum 10 metre wind direction 238262: - min_cat - Time-minimum clear air turbulence (CAT) 238263: - min_al - Time-minimum forecast albedo 238269: - min_pt - Time-minimum potential temperature 238281: - min_aluvp_p - Time-minimum UV visible albedo for direct radiation (climatological) 238282: - min_aluvd_p - Time-minimum UV visible albedo for diffuse radiation (climatological) 238283: - min_alnip_p - Time-minimum near IR albedo for direct radiation (climatological) 238284: - min_alnid_p - Time-minimum near IR albedo for diffuse radiation (climatological) 238285: - min_asn - Time-minimum snow albedo 238286: - min_mont - Time-minimum montgomery potential 238287: - min_cape - Time-minimum convective available potential energy 238288: - min_tcc - Time-minimum total cloud cover 238289: - min_srcon - Time-minimum skin reservoir content 238290: - min_tcioz - Time-minimum total column integrated ozone 238291: - min_vike - Time-minimum total column vertically-integrated kinetic energy 238292: - min_vithe - Time-minimum total column vertically-integrated enthalpy 238293: - min_vipie - Time-minimum total column vertically-integrated potential + internal energy 238294: - min_vipile - Time-minimum vertical integral of potential+internal+latent energy 238295: - min_vitoe - Time-minimum total column vertically-integrated total energy 238296: - min_viwe - Time-minimum total column vertically-integrated water enthalpy 238297: - min_aluvpi_p - Time-minimum UV visible albedo for direct radiation, isotropic component (climatological) 238298: - min_aluvpv_p - Time-minimum UV visible albedo for direct radiation, volumetric component (climatological) 238299: - min_aluvpg_p - Time-minimum UV visible albedo for direct radiation, geometric component (climatological) 238300: - min_alnipi_p - Time-minimum near IR albedo for direct radiation, isotropic component (climatological) 238301: - min_alnipv_p - Time-minimum near IR albedo for direct radiation, volumetric component (climatological) 238302: - min_alnipg_p - Time-minimum near IR albedo for direct radiation, geometric component (climatological) 238303: - min_cin - Time-minimum convective inhibition 238304: - min_zust - Time-minimum friction velocity 238305: - min_lblt - Time-minimum lake bottom temperature 238306: - min_ltlt - Time-minimum lake total layer temperature 238307: - min_lshf - Time-minimum lake shape factor 238308: - min_lict - Time-minimum lake ice surface temperature 238309: - min_licd - Time-minimum lake ice total depth 238310: - min_dndzn - Time-minimum minimum vertical gradient of refractivity inside trapping layer 238311: - min_dndza - Time-minimum mean vertical gradient of refractivity inside trapping layer 238312: - min_dctb - Time-minimum duct base height 238313: - min_tplb - Time-minimum trapping layer base height 238314: - min_tplt - Time-minimum trapping layer top height 238315: - min_degm10l - Time-minimum geometric height of -10 degrees C atmospheric isothermal level above ground 238316: - min_cbh - Time-minimum cloud base height 238317: - min_deg0l - Time-minimum geometric height of 0 degrees C atmospheric isothermal level above ground 238318: - min_i10fg - Time-minimum 10 metre wind gust 238319: - min_rhw - Time-minimum relative humidity with respect to water 238320: - min_2rhw - Time-minimum 2 metre relative humidity with respect to water 238321: - min_capes - Time-minimum convective available potential energy shear 238322: - min_trpp - Time-minimum tropopause pressure 238323: - min_hcct - Time-minimum height of convective cloud top 238324: - min_hwbt0 - Time-minimum height of zero-degree wet-bulb temperature 238325: - min_hwbt1 - Time-minimum height of one-degree wet-bulb temperature 238326: - min_litoti - Time-minimum total lightning flash density 238327: - min_tcslw - Time-minimum total column supercooled liquid water 238328: - min_u10n - Time-minimum 10 metre U-component of neutral wind 238329: - min_v10n - Time-minimum 10 metre V-component of neutral wind 238330: - min_crr - Time-minimum convective rain rate 238331: - min_lsrr - Time-minimum large scale rain rate 238332: - min_mlcape50 - Time-minimum mixed-layer CAPE in the lowest 50 hPa 238333: - min_mlcin50 - Time-minimum mixed-layer CIN in the lowest 50 hPa 238334: - min_mlcape100 - Time-minimum mixed-layer CAPE in the lowest 100 hPa 238335: - min_mlcin100 - Time-minimum mixed-layer CIN in the lowest 100 hPa 238336: - min_mudlp - Time-minimum departure level of the most unstable parcel expressed as Pressure 238337: - min_ceil - Time-minimum ceiling 238338: - min_tcsqw - Time-minimum total column integrated saturation specific humidity with respect to water 238339: - min_sdirnswrf - Time-minimum direct solar radiation flux 238340: - min_cossza - Time-minimum cosine of solar zenith angle 238341: - min_fprate - Time-minimum freezing rain rate water equivalent 238342: - min_vige - Time-minimum total column vertically-integrated eastward geopotential flux 238343: - min_vign - Time-minimum total column vertically-integrated northward geopotential flux 238344: - min_viwgd - Time-minimum total column vertically-integrated divergence of water geopotential flux 238345: - min_vigd - Time-minimum total column vertically-integrated divergence of geopotential flux 238346: - min_viee - Time-minimum total column vertically-integrated eastward enthalpy flux 238347: - min_vien - Time-minimum total column vertically-integrated northward enthalpy flux 238348: - min_vikee - Time-minimum total column vertically-integrated eastward kinetic energy flux 238349: - min_viken - Time-minimum total column vertically-integrated northward kinetic energy flux 238350: - min_vitee - Time-minimum total column vertically-integrated eastward total energy flux 238351: - min_viten - Time-minimum total column vertically-integrated northward total energy flux 238352: - min_vied - Time-minimum total column vertically-integrated divergence of enthalpy flux 238353: - min_viked - Time-minimum total column vertically-integrated divergence of kinetic energy flux 238354: - min_vited - Time-minimum total column vertically-integrated divergence of total energy flux 238355: - min_viwed - Time-minimum total column vertically-integrated divergence of water enthalpy flux 238356: - min_vimad - Time-minimum vertically integrated divergence of mass flux 238357: - min_vimae - Time-minimum vertically integrated eastward mass flux 238358: - min_viman - Time-minimum vertically integrated northward mass flux 238359: - min_viwvd - Time-minimum vertically integrated divergence of water vapour flux 238360: - min_viclwd - Time-minimum vertically integrated divergence of cloud liquid water flux 238361: - min_viciwd - Time-minimum vertically integrated divergence of cloud ice water flux 238362: - min_vird - Time-minimum vertically integrated divergence of rain flux 238363: - min_visd - Time-minimum vertically integrated divergence of snow flux 238364: - min_viwve - Time-minimum vertically integrated eastward water vapour flux 238365: - min_viwvn - Time-minimum vertically integrated northward water vapour flux 238366: - min_viclwe - Time-minimum vertically integrated eastward cloud liquid water flux 238367: - min_viclwn - Time-minimum vertically integrated northward cloud liquid water flux 238368: - min_viciwe - Time-minimum vertically integrated eastward cloud ice water flux 238369: - min_viciwn - Time-minimum vertically integrated northward cloud ice water flux 238370: - min_vire - Time-minimum vertically integrated eastward rain flux 238371: - min_virn - Time-minimum vertically integrated northward rain flux 238372: - min_vise - Time-minimum vertically integrated eastward snow flux 238373: - min_visn - Time-minimum vertically integrated northward snow flux 238374: - min_vioze - Time-minimum vertically integrated eastward ozone flux 238375: - min_viozn - Time-minimum vertically integrated northward ozone flux 238376: - min_viozd - Time-minimum vertically integrated divergence of ozone flux 238377: - min_vions - Time-minimum vertically integrated net source of ozone 238378: - min_ietssofd - Time-minimum eastward turbulent surface stress due to orographic form drag 238379: - min_intssofd - Time-minimum northward turbulent surface stress due to orographic form drag 238380: - min_ietsssr - Time-minimum eastward turbulent surface stress due to surface roughness 238381: - min_intsssr - Time-minimum northward turbulent surface stress due to surface roughness 238382: - min_visp - Time-minimum visibility through precipitation 238383: - min_licgi - Time-minimum cloud-to-ground lightning flash density 238386: - min_lshnf - Time-minimum land surface heat net flux 238387: - min_sdirnswrfcs - Time-minimum surface direct normal short-wave radiation flux, clear sky 238388: - min_10cogu - Time-minimum 10 metre convective gust 238389: - min_10tugu - Time-minimum 10 metre turbulent gust 238390: - min_h0thg - Time-minimum geometric height of 0 degrees C theta level above ground 238391: - min_h1thg - Time-minimum geometric height of 1 degree C theta level above ground 238392: - min_h1p5thg - Time-minimum geometric height of 1.5 degrees C theta level above ground 238393: - min_veg - Time-minimum vegetation 238394: - min_lai - Time-minimum leaf area index 238395: - min_srhe - Time-minimum surface roughness for heat 238396: - min_rol - Time-minimum Reciprocal Obukhov length 238397: - min_deg3l - Time-minimum geometric height of 3 degrees C atmospheric isothermal level above ground 238404: - min_sdifswrfcs - Time-minimum surface diffuse short-wave radiation flux, clear sky 238405: - min_vswrz - Time-minimum volumetric soil moisture (root zone) 238406: - min_lwcs - Time-minimum liquid water content in snow pack 238407: - min_mucin - Time-minimum most-unstable CIN 238408: - min_crwc_conv - Time-minimum specific rain water content (convective) 238409: - min_cswc_conv - Time-minimum specific snow water content (convective) 238410: - min_mld - Time-minimum mixed layer depth 238411: - min_cur - Time-minimum urban cover 239001: - std_ttswr - Time-standard-deviation temperature tendency due to short-wave radiation 239002: - std_ttlwr - Time-standard-deviation temperature tendency due to long-wave radiation 239003: - std_ttswrcs - Time-standard-deviation temperature tendency due to short wave radiation, clear sky 239004: - std_ttlwrcs - Time-standard-deviation temperature tendency due to long-wave radiation, clear sky 239005: - std_ttpm - Time-standard-deviation temperature tendency due to parametrisations 239006: - std_qtpm - Time-standard-deviation specific humidity tendency due to parametrisations 239007: - std_utpm - Time-standard-deviation eastward wind tendency due to parametrisations 239008: - std_vtpm - Time-standard-deviation northward wind tendency due to parametrisations 239009: - std_umf - Time-standard-deviation updraught mass flux 239010: - std_dmf - Time-standard-deviation downdraught mass flux 239011: - std_udr - Time-standard-deviation updraught detrainment rate 239012: - std_ddr - Time-standard-deviation downdraught detrainment rate 239013: - std_tpf - Time-standard-deviation total precipitation flux 239014: - std_tdch - Time-standard-deviation turbulent diffusion coefficient for heat 239022: - std_parcsf - Time-standard-deviation surface photosynthetically active radiation flux, clear sky 239023: - std_esrwe - Time-standard-deviation snow evaporation rate 239024: - std_smr - Time-standard-deviation snowmelt rate 239025: - std_imagss - Time-standard-deviation magnitude of turbulent surface stress 239026: - std_ilspf - Time-standard-deviation large-scale precipitation fraction 239027: - std_sduvrf - Time-standard-deviation surface downward UV radiation flux 239028: - std_sparf - Time-standard-deviation surface photosynthetically active radiation flux 239029: - std_lsprate - Time-standard-deviation large-scale precipitation rate 239030: - std_cpr - Time-standard-deviation convective precipitation rate 239031: - std_tsrwe - Time-standard-deviation snowfall rate 239032: - std_ibld - Time-standard-deviation boundary layer dissipation 239033: - std_ishf - Time-standard-deviation surface sensible heat flux 239034: - std_slhtf - Time-standard-deviation surface latent heat flux 239035: - std_sdswrf - Time-standard-deviation surface downward short-wave radiation flux 239036: - std_sdlwrf - Time-standard-deviation surface downward long-wave radiation flux 239037: - std_snswrf - Time-standard-deviation surface net short-wave radiation flux 239038: - std_snlwrf - Time-standard-deviation surface net long-wave radiation flux 239039: - std_tnswrf - Time-standard-deviation top net short-wave radiation flux 239040: - std_tnlwrf - Time-standard-deviation top net long-wave radiation flux 239041: - std_iews - Time-standard-deviation eastward turbulent surface stress 239042: - std_inss - Time-standard-deviation northward turbulent surface stress 239043: - std_ie - Time-standard-deviation evaporation rate 239045: - std_iegwss - Time-standard-deviation eastward gravity wave surface stress 239046: - std_ingwss - Time-standard-deviation northward gravity wave surface stress 239047: - std_igwd - Time-standard-deviation gravity wave dissipation 239048: - std_rorwe - Time-standard-deviation runoff rate 239049: - std_tnswrfcs - Time-standard-deviation top net short-wave radiation flux, clear sky 239050: - std_tnlwrfcs - Time-standard-deviation top net long-wave radiation flux, clear sky 239051: - std_snswrfcs - Time-standard-deviation surface net short-wave radiation flux, clear sky 239052: - std_snlwrfcs - Time-standard-deviation surface net long-wave radiation flux, clear sky 239053: - std_tdswrf - Time-standard-deviation top downward short-wave radiation flux 239054: - std_vimdf - Time-standard-deviation vertically integrated moisture divergence 239055: - std_tprate - Time-standard-deviation total precipitation rate 239056: - std_csfr - Time-standard-deviation convective snowfall rate water equivalent 239057: - std_lssfr - Time-standard-deviation large scale snowfall rate water equivalent 239058: - std_sdirswrf - Time-standard-deviation surface direct short-wave radiation flux 239059: - std_sdirswrfcs - Time-standard-deviation surface direct short-wave radiation flux, clear sky 239062: - std_fco2nee - Time-standard-deviation carbon dioxide net ecosystem exchange flux 239063: - std_fco2gpp - Time-standard-deviation carbon dioxide gross primary production flux 239064: - std_fco2rec - Time-standard-deviation carbon dioxide ecosystem respiration flux 239068: - std_sdswrfcs - Time-standard-deviation surface downward short-wave radiation flux, clear sky 239069: - std_sdlwrfcs - Time-standard-deviation surface downward long-wave radiation flux, clear sky 239070: - std_pevr - Time-standard-deviation potential evaporation rate 239077: - std_vsw - Time-standard-deviation volumetric soil moisture 239078: - std_sd - Time-standard-deviation snow depth water equivalent 239079: - std_skt - Time-standard-deviation skin temperature 239080: - std_rsn - Time-standard-deviation snow density 239083: - std_ci - Time-standard-deviation sea ice area fraction 239084: - std_sst - Time-standard-deviation sea surface temperature 239085: - std_lai_lv - Time-standard-deviation leaf area index, low vegetation 239086: - std_lai_hv - Time-standard-deviation leaf area index, high vegetation 239087: - std_tclw - Time-standard-deviation total column cloud liquid water 239088: - std_tciw - Time-standard-deviation total column cloud ice water 239089: - std_2sh - Time-standard-deviation 2 metre specific humidity 239090: - std_lmlt - Time-standard-deviation lake mix-layer temperature 239091: - std_lmld - Time-standard-deviation lake mix-layer depth 239093: - std_fscov - Time-standard-deviation fraction of snow cover 239094: - std_sot - Time-standard-deviation soil temperature 239097: - std_ws - Time-standard-deviation wind speed 239098: - std_pres - Time-standard-deviation pressure 239099: - std_chnk - Time-standard-deviation charnock 239100: - std_pv - Time-standard-deviation potential vorticity 239101: - std_crwc - Time-standard-deviation specific rain water content 239102: - std_cswc - Time-standard-deviation specific snow water content 239103: - std_etadot - Time-standard-deviation eta-coordinate vertical velocity 239105: - std_vis - Time-standard-deviation visibility 239106: - std_wdir - Time-standard-deviation wind direction 239107: - std_tcsw - Time-standard-deviation total column snow water 239108: - std_lcc - Time-standard-deviation low cloud cover 239109: - std_mcc - Time-standard-deviation medium cloud cover 239110: - std_hcc - Time-standard-deviation high cloud cover 239111: - std_tcrw - Time-standard-deviation total column rain water 239117: - std_mucape - Time-standard-deviation most-unstable CAPE 239120: - std_tcolg - Time-standard-deviation total column vertically-integrated graupel (snow pellets) 239129: - std_z - Time-standard-deviation geopotential 239130: - std_t - Time-standard-deviation temperature 239131: - std_u - Time-standard-deviation u component of wind 239132: - std_v - Time-standard-deviation v component of wind 239133: - std_q - Time-standard-deviation specific humidity 239134: - std_sp - Time-standard-deviation surface pressure 239135: - std_w - Time-standard-deviation vertical velocity 239136: - std_tcw - Time-standard-deviation total column water 239137: - std_tcwv - Time-standard-deviation total column vertically-integrated water vapour 239138: - std_vo - Time-standard-deviation vorticity (relative) 239151: - std_msl - Time-standard-deviation mean sea level pressure 239152: - std_lnsp - Time-standard-deviation logarithm of surface pressure 239155: - std_d - Time-standard-deviation divergence 239157: - std_r - Time-standard-deviation relative humidity 239159: - std_blh - Time-standard-deviation boundary layer height 239165: - std_10u - Time-standard-deviation 10 metre U wind component 239166: - std_10v - Time-standard-deviation 10 metre V wind component 239167: - std_2t - Time-standard-deviation 2 metre temperature 239168: - std_2d - Time-standard-deviation 2 metre dewpoint temperature 239189: - std_suns - Time-standard-deviation sunshine 239203: - std_o3 - Time-standard-deviation ozone mass mixing ratio 239207: - std_10si - Time-standard-deviation 10 metre wind speed 239238: - std_tsn - Time-standard-deviation temperature of snow layer 239244: - std_fsr - Time-standard-deviation forecast surface roughness 239245: - std_flsr - Time-standard-deviation forecast logarithm of surface roughness for heat 239246: - std_clwc - Time-standard-deviation specific cloud liquid water content 239247: - std_ciwc - Time-standard-deviation specific cloud ice water content 239248: - std_cc - Time-standard-deviation fraction of cloud cover 239257: - std_kx - Time-standard-deviation k index 239258: - std_totalx - Time-standard-deviation total totals index 239261: - std_10wdir - Time-standard-deviation 10 metre wind direction 239262: - std_cat - Time-standard-deviation clear air turbulence (CAT) 239263: - std_al - Time-standard-deviation forecast albedo 239269: - std_pt - Time-standard-deviation potential temperature 239281: - std_aluvp_p - Time-standard-deviation UV visible albedo for direct radiation (climatological) 239282: - std_aluvd_p - Time-standard-deviation UV visible albedo for diffuse radiation (climatological) 239283: - std_alnip_p - Time-standard-deviation near IR albedo for direct radiation (climatological) 239284: - std_alnid_p - Time-standard-deviation near IR albedo for diffuse radiation (climatological) 239285: - std_asn - Time-standard-deviation snow albedo 239286: - std_mont - Time-standard-deviation montgomery potential 239287: - std_cape - Time-standard-deviation convective available potential energy 239288: - std_tcc - Time-standard-deviation total cloud cover 239289: - std_srcon - Time-standard-deviation skin reservoir content 239290: - std_tcioz - Time-standard-deviation total column integrated ozone 239291: - std_vike - Time-standard-deviation total column vertically-integrated kinetic energy 239292: - std_vithe - Time-standard-deviation total column vertically-integrated enthalpy 239293: - std_vipie - Time-standard-deviation total column vertically-integrated potential + internal energy 239294: - std_vipile - Time-standard-deviation vertical integral of potential+internal+latent energy 239295: - std_vitoe - Time-standard-deviation total column vertically-integrated total energy 239296: - std_viwe - Time-standard-deviation total column vertically-integrated water enthalpy 239297: - std_aluvpi_p - Time-standard-deviation UV visible albedo for direct radiation, isotropic component (climatological) 239298: - std_aluvpv_p - Time-standard-deviation UV visible albedo for direct radiation, volumetric component (climatological) 239299: - std_aluvpg_p - Time-standard-deviation UV visible albedo for direct radiation, geometric component (climatological) 239300: - std_alnipi_p - Time-standard-deviation near IR albedo for direct radiation, isotropic component (climatological) 239301: - std_alnipv_p - Time-standard-deviation near IR albedo for direct radiation, volumetric component (climatological) 239302: - std_alnipg_p - Time-standard-deviation near IR albedo for direct radiation, geometric component (climatological) 239303: - std_cin - Time-standard-deviation convective inhibition 239304: - std_zust - Time-standard-deviation friction velocity 239305: - std_lblt - Time-standard-deviation lake bottom temperature 239306: - std_ltlt - Time-standard-deviation lake total layer temperature 239307: - std_lshf - Time-standard-deviation lake shape factor 239308: - std_lict - Time-standard-deviation lake ice surface temperature 239309: - std_licd - Time-standard-deviation lake ice total depth 239310: - std_dndzn - Time-standard-deviation minimum vertical gradient of refractivity inside trapping layer 239311: - std_dndza - Time-standard-deviation mean vertical gradient of refractivity inside trapping layer 239312: - std_dctb - Time-standard-deviation duct base height 239313: - std_tplb - Time-standard-deviation trapping layer base height 239314: - std_tplt - Time-standard-deviation trapping layer top height 239315: - std_degm10l - Time-standard-deviation geometric height of -10 degrees C atmospheric isothermal level above ground 239316: - std_cbh - Time-standard-deviation cloud base height 239317: - std_deg0l - Time-standard-deviation geometric height of 0 degrees C atmospheric isothermal level above ground 239318: - std_i10fg - Time-standard-deviation 10 metre wind gust 239319: - std_rhw - Time-standard-deviation relative humidity with respect to water 239320: - std_2rhw - Time-standard-deviation 2 metre relative humidity with respect to water 239321: - std_capes - Time-standard-deviation convective available potential energy shear 239322: - std_trpp - Time-standard-deviation tropopause pressure 239323: - std_hcct - Time-standard-deviation height of convective cloud top 239324: - std_hwbt0 - Time-standard-deviation height of zero-degree wet-bulb temperature 239325: - std_hwbt1 - Time-standard-deviation height of one-degree wet-bulb temperature 239326: - std_litoti - Time-standard-deviation total lightning flash density 239327: - std_tcslw - Time-standard-deviation total column supercooled liquid water 239328: - std_u10n - Time-standard-deviation 10 metre U-component of neutral wind 239329: - std_v10n - Time-standard-deviation 10 metre V-component of neutral wind 239330: - std_crr - Time-standard-deviation convective rain rate 239331: - std_lsrr - Time-standard-deviation large scale rain rate 239332: - std_mlcape50 - Time-standard-deviation mixed-layer CAPE in the lowest 50 hPa 239333: - std_mlcin50 - Time-standard-deviation mixed-layer CIN in the lowest 50 hPa 239334: - std_mlcape100 - Time-standard-deviation mixed-layer CAPE in the lowest 100 hPa 239335: - std_mlcin100 - Time-standard-deviation mixed-layer CIN in the lowest 100 hPa 239336: - std_mudlp - Time-standard-deviation departure level of the most unstable parcel expressed as Pressure 239337: - std_ceil - Time-standard-deviation ceiling 239338: - std_tcsqw - Time-standard-deviation total column integrated saturation specific humidity with respect to water 239339: - std_sdirnswrf - Time-standard-deviation direct solar radiation flux 239340: - std_cossza - Time-standard-deviation cosine of solar zenith angle 239341: - std_fprate - Time-standard-deviation freezing rain rate water equivalent 239342: - std_vige - Time-standard-deviation total column vertically-integrated eastward geopotential flux 239343: - std_vign - Time-standard-deviation total column vertically-integrated northward geopotential flux 239344: - std_viwgd - Time-standard-deviation total column vertically-integrated divergence of water geopotential flux 239345: - std_vigd - Time-standard-deviation total column vertically-integrated divergence of geopotential flux 239346: - std_viee - Time-standard-deviation total column vertically-integrated eastward enthalpy flux 239347: - std_vien - Time-standard-deviation total column vertically-integrated northward enthalpy flux 239348: - std_vikee - Time-standard-deviation total column vertically-integrated eastward kinetic energy flux 239349: - std_viken - Time-standard-deviation total column vertically-integrated northward kinetic energy flux 239350: - std_vitee - Time-standard-deviation total column vertically-integrated eastward total energy flux 239351: - std_viten - Time-standard-deviation total column vertically-integrated northward total energy flux 239352: - std_vied - Time-standard-deviation total column vertically-integrated divergence of enthalpy flux 239353: - std_viked - Time-standard-deviation total column vertically-integrated divergence of kinetic energy flux 239354: - std_vited - Time-standard-deviation total column vertically-integrated divergence of total energy flux 239355: - std_viwed - Time-standard-deviation total column vertically-integrated divergence of water enthalpy flux 239356: - std_vimad - Time-standard-deviation vertically integrated divergence of mass flux 239357: - std_vimae - Time-standard-deviation vertically integrated eastward mass flux 239358: - std_viman - Time-standard-deviation vertically integrated northward mass flux 239359: - std_viwvd - Time-standard-deviation vertically integrated divergence of water vapour flux 239360: - std_viclwd - Time-standard-deviation vertically integrated divergence of cloud liquid water flux 239361: - std_viciwd - Time-standard-deviation vertically integrated divergence of cloud ice water flux 239362: - std_vird - Time-standard-deviation vertically integrated divergence of rain flux 239363: - std_visd - Time-standard-deviation vertically integrated divergence of snow flux 239364: - std_viwve - Time-standard-deviation vertically integrated eastward water vapour flux 239365: - std_viwvn - Time-standard-deviation vertically integrated northward water vapour flux 239366: - std_viclwe - Time-standard-deviation vertically integrated eastward cloud liquid water flux 239367: - std_viclwn - Time-standard-deviation vertically integrated northward cloud liquid water flux 239368: - std_viciwe - Time-standard-deviation vertically integrated eastward cloud ice water flux 239369: - std_viciwn - Time-standard-deviation vertically integrated northward cloud ice water flux 239370: - std_vire - Time-standard-deviation vertically integrated eastward rain flux 239371: - std_virn - Time-standard-deviation vertically integrated northward rain flux 239372: - std_vise - Time-standard-deviation vertically integrated eastward snow flux 239373: - std_visn - Time-standard-deviation vertically integrated northward snow flux 239374: - std_vioze - Time-standard-deviation vertically integrated eastward ozone flux 239375: - std_viozn - Time-standard-deviation vertically integrated northward ozone flux 239376: - std_viozd - Time-standard-deviation vertically integrated divergence of ozone flux 239377: - std_vions - Time-standard-deviation vertically integrated net source of ozone 239378: - std_ietssofd - Time-standard-deviation eastward turbulent surface stress due to orographic form drag 239379: - std_intssofd - Time-standard-deviation northward turbulent surface stress due to orographic form drag 239380: - std_ietsssr - Time-standard-deviation eastward turbulent surface stress due to surface roughness 239381: - std_intsssr - Time-standard-deviation northward turbulent surface stress due to surface roughness 239383: - std_licgi - Time-standard-deviation cloud-to-ground lightning flash density 239386: - std_lshnf - Time-standard-deviation land surface heat net flux 239387: - std_sdirnswrfcs - Time-standard-deviation surface direct normal short-wave radiation flux, clear sky 239388: - std_10cogu - Time-standard-deviation 10 metre convective gust 239389: - std_10tugu - Time-standard-deviation 10 metre turbulent gust 239390: - std_h0thg - Time-standard-deviation geometric height of 0 degrees C theta level above ground 239391: - std_h1thg - Time-standard-deviation geometric height of 1 degree C theta level above ground 239392: - std_h1p5thg - Time-standard-deviation geometric height of 1.5 degrees C theta level above ground 239393: - std_veg - Time-standard-deviation vegetation 239394: - std_lai - Time-standard-deviation leaf area index 239395: - std_srhe - Time-standard-deviation surface roughness for heat 239396: - std_rol - Time-standard-deviation Reciprocal Obukhov length 239397: - std_deg3l - Time-standard-deviation geometric height of 3 degrees C atmospheric isothermal level above ground 239404: - std_sdifswrfcs - Time-standard-deviation surface diffuse short-wave radiation flux, clear sky 239405: - std_vswrz - Time-standard-deviation volumetric soil moisture (root zone) 239406: - std_lwcs - Time-standard-deviation liquid water content in snow pack 239407: - std_mucin - Time-standard-deviation most-unstable CIN 239408: - std_crwc_conv - Time-standard-deviation specific rain water content (convective) 239409: - std_cswc_conv - Time-standard-deviation specific snow water content (convective) 239410: - std_mld - Time-standard-deviation mixed layer depth 239411: - std_cur - Time-standard-deviation urban cover 240011: - chcross - Cross sectional area of flow in channel 240012: - chside - Side flow into river channel 240013: - dis - Discharge from rivers or streams 240014: - rivsto - River storage of water 240015: - fldsto - Floodplain storage of water 240016: - fldfrc - Water fraction 240017: - dslr - Days since last observation 240018: - frost - Frost index 240020: - woss - Depth of water on soil surface 240021: - tpups - Upstream accumulated precipitation 240022: - smups - Upstream accumulated snow melt 240023: - dis06 - Mean discharge in the last 6 hours 240024: - dis24 - Mean discharge in the last 24 hours 240026: - sd_elev - Snow depth at elevation bands 240028: - gwus - Groundwater upper storage 240029: - gwls - Groundwater lower storage 240030: - lakdph - Lake depth 240031: - rivdph - River depth 240032: - rivout - River outflow of water 240033: - fldout - Floodplain outflow of water 240034: - pthflw - Floodpath outflow of water 240035: - flddep - Floodplain depth 240036: - fldffr - Floodplain flooded fraction 240037: - fldfar - Floodplain flooded area 240038: - rivfr - River fraction 240039: - rivar - River area 240040: - rivcffr - Fraction of river coverage plus river related flooding 240041: - rivcfar - Area of river coverage plus river related flooding 240042: - wse - Water surface elevation 240043: - grfr - Groundwater return flow rate 240044: - rivfldsto - River and floodplain storage 240045: - darv - Depth-averaged river velocity 250001: - lat - Latitude 250002: - lon - Longitude 250003: - tlat - Latitude on T grid 250004: - tlon - Longitude on T grid 250005: - ulat - Latitude on U grid 250006: - ulon - Longitude on U grid 250007: - vlat - Latitude on V grid 250008: - vlon - Longitude on V grid 250009: - wlat - Latitude on W grid 250010: - wlon - Longitude on W grid 250011: - flat - Latitude on F grid 250012: - flon - Longitude on F grid 254001: - covar_t2m_swvl1 - Covariance between 2-metre temperature and volumetric soil water layer 1 254002: - covar_rh2m_swvl1 - Covariance between 2-metre relative humidity and volumetric soil water layer 1 254003: - covar_ssm_swvl1 - Covariance between surface soil moisture and volumetric soil water layer 1 254004: - covar_t2m_swvl2 - Covariance between 2-metre temperature and volumetric soil water layer 2 254005: - covar_rh2m_swvl2 - Covariance between 2-metre relative humidity and volumetric soil water layer 2 254006: - covar_ssm_swvl2 - Covariance between surface soil moisture and volumetric soil water layer 2 254007: - covar_t2m_swvl3 - Covariance between 2-metre temperature and volumetric soil water layer 3 254008: - covar_rh2m_swvl3 - Covariance between 2-metre relative humidity and volumetric soil water layer 3 254009: - covar_ssm_swvl3 - Covariance between surface soil moisture and volumetric soil water layer 3 254010: - covar_t2m_stl1 - Covariance between 2-metre temperature and soil temperature layer 1 254011: - covar_rh2m_stl1 - Covariance between 2-metre relative humidity and soil temperature layer 1 254012: - covar_t2m_stl2 - Covariance between 2-metre temperature and soil temperature layer 2 254013: - covar_rh2m_stl2 - Covariance between 2-metre relative humidity and soil temperature layer 2 254014: - covar_t2m_stl3 - Covariance between 2-metre temperature and soil temperature layer 3 254015: - covar_rh2m_stl3 - Covariance between 2-metre relative humidity and soil temperature layer 3 254016: - covar_t2m_tsn1 - Covariance between 2-metre temperature and temperature of snow layer 1 254017: - covar_rh2m_tsn1 - Covariance between 2-metre relative humidity and temperature of snow layer 1 254018: - covar_t2m_tsn2 - Covariance between 2-metre temperature and temperature of snow layer 2 254019: - covar_rh2m_tsn2 - Covariance between 2-metre relative humidity and temperature of snow layer 2 254020: - covar_t2m_tsn3 - Covariance between 2-metre temperature and temperature of snow layer 3 254021: - covar_rh2m_tsn3 - Covariance between 2-metre relative humidity and temperature of snow layer 3 254022: - covar_skt_stl1 - Covariance between skin temperature and soil temperature layer 1 254023: - covar_skt_stl2 - Covariance between skin temperature and soil temperature layer 2 254024: - covar_skt_stl3 - Covariance between skin temperature and soil temperature layer 3 254025: - covar_skt_swvl1 - Covariance between skin temperature and volumetric soil water layer 1 254026: - covar_skt_swvl2 - Covariance between skin temperature and volumetric soil water layer 2 254027: - covar_skt_swvl3 - Covariance between skin temperature and volumetric soil water layer 3 254028: - covar_skt_tsn1 - Covariance between skin temperature and temperature of snow layer 1 254029: - covar_skt_tsn2 - Covariance between skin temperature and temperature of snow layer 2 254030: - covar_skt_tsn3 - Covariance between skin temperature and temperature of snow layer 3 254500: - curvmsl - CURV diagnostic mean sea level pressure 254501: - curvz - CURV diagnostic geopotential 260001: - tcolg - Total column vertically-integrated graupel (snow pellets) 260002: - slhtf - Surface latent heat net flux 260004: - heatx - Heat index 260005: - wcf - Wind chill factor 260006: - mindpd - Minimum dew point depression 260007: - snohf - Snow phase change heat flux 260008: - vapp - Vapor pressure 260009: - ncpcp - Large scale precipitation (non-convective) 260010: - srweq - Snowfall rate water equivalent 260011: - snoc - Convective snow 260012: - snol - Large scale snow 260013: - snoag - Snow age 260014: - absh - Absolute humidity 260015: - ptype - Precipitation type 260016: - iliqw - Integrated liquid water 260017: - tcond - Condensate 260018: - clwmr - Cloud mixing ratio 260019: - icmr - Ice water mixing ratio 260020: - rwmr - Rain mixing ratio 260021: - snmr - Snow mixing ratio 260022: - mconv - Horizontal moisture convergence 260023: - maxrh - Maximum relative humidity 260024: - maxah - Maximum absolute humidity 260025: - asnow - Total snowfall 260026: - pwcat - Precipitable water category 260027: - hail 260028: - grle - Graupel (snow pellets) 260029: - crain - Categorical rain 260030: - cfrzr - Categorical freezing rain 260031: - cicep - Categorical ice pellets 260032: - csnow - Categorical snow 260033: - cpr - Convective precipitation rate 260034: - mdiv - Horizontal moisture divergence 260035: - cpofp - Percent frozen precipitation 260036: - pevap - Potential evaporation 260038: - snowc - Snow cover 260039: - frain - Rain fraction of total cloud water 260040: - rime - Rime factor 260041: - tcolr - Total column integrated rain 260042: - tcols - Total column integrated snow 260043: - lswp - Large scale water precipitation (non-convective) 260044: - cwp - Convective water precipitation 260045: - twatp - Total water precipitation 260046: - tsnowp - Total snow precipitation 260047: - tcwat - Total column water (Vertically integrated total water (vapour + cloud water/ice)) 260048: - tprate - Total precipitation rate 260049: - tsrwe - Total snowfall rate water equivalent 260050: - lsprate - Large scale precipitation rate 260053: - tsrate - Total snowfall rate 260054: - csrate - Convective snowfall rate 260055: - lssrate - Large scale snowfall rate 260056: - sdwe - Water equivalent of accumulated snow depth (deprecated) 260057: - tciwv - Total column integrated water vapour 260058: - rprate - Rain precipitation rate 260059: - sprate - Snow precipitation rate 260060: - fprate - Freezing rain precipitation rate 260061: - iprate - Ice pellets precipitation rate 260062: - uflx - Momentum flux, u component 260063: - vflx - Momentum flux, v component 260064: - maxgust - Maximum wind speed 260065: - gust - Wind speed (gust) 260066: - ugust - u-component of wind (gust) 260067: - vgust - v-component of wind (gust) 260068: - vwsh - Vertical speed shear 260069: - mflx - Horizontal momentum flux 260070: - ustm - U-component storm motion 260071: - vstm - V-component storm motion 260072: - cd - Drag coefficient 260073: - fricv - Frictional velocity 260074: - prmsl - Pressure reduced to MSL 260076: - alts - Altimeter setting 260077: - thick - Thickness 260078: - presalt - Pressure altitude 260079: - denalt - Density altitude 260080: - 5wavh - 5-wave geopotential height 260081: - iegwss - Instantaneous eastward gravity wave surface stress 260082: - ingwss - Instantaneous northward gravity wave surface stress 260084: - 5wava - 5-wave geopotential height anomaly 260085: - sdsgso - Standard deviation of sub-grid scale orography 260086: - nswrt - Net short-wave radiation flux (top of atmosphere) 260087: - sdswrf - Surface downward short-wave radiation flux 260088: - suswrf - Surface upward short-wave radiation flux 260089: - snswrf - Surface net short-wave radiation flux 260090: - sparf - Surface photosynthetically active radiation flux 260091: - snswrfcs - Surface net short-wave radiation flux, clear sky 260092: - sduvrf - Surface downward UV radiation flux 260093: - uviucs - UV index (under clear sky) 260094: - uvi - UV index 260095: - nlwrs - Net long wave radiation flux (surface) 260096: - nlwrt - Net long wave radiation flux (top of atmosphere) 260097: - sdlwrf - Surface downward long-wave radiation flux 260098: - sulwrf - Surface upward long-wave radiation flux 260099: - snlwrf - Surface net long-wave radiation flux 260100: - snlwrfcs - Surface net long-wave radiation flux, clear sky 260101: - cice - Cloud Ice 260102: - cwat - Cloud water 260103: - cdca - Cloud amount 260104: - cdct - Cloud type 260105: - tmaxt - Thunderstorm maximum tops 260106: - thunc - Thunderstorm coverage 260107: - cdcb - Cloud base 260108: - cdct - Cloud top 260109: - ceil - Ceiling 260110: - cdlyr - Non-convective cloud cover 260111: - cwork - Cloud work function 260112: - cuefi - Convective cloud efficiency 260113: - tcond - Total condensate 260114: - tcolw - Total column-integrated cloud water 260115: - tcoli - Total column-integrated cloud ice 260116: - tcolc - Total column-integrated condensate 260117: - fice - Ice fraction of total condensate 260118: - cdcimr - Cloud ice mixing ratio 260119: - suns - Sunshine 260120: - _param_260120 - Horizontal extent of cumulonimbus (CB) 260121: - kx - K index 260122: - kox - KO index 260123: - totalx - Total totals index 260124: - sx - Sweat index 260125: - hlcy - Storm relative helicity 260126: - ehlx - Energy helicity index 260127: - lftx - Surface lifted index 260128: - 4lftx - Best (4-layer) lifted index 260129: - aerot - Aerosol type 260130: - tozne - Total ozone 260131: - o3mr - Ozone mixing ratio 260132: - tcioz - Total column integrated ozone 260133: - bswid - Base spectrum width 260134: - bref - Base reflectivity 260135: - brvel - Base radial velocity 260136: - veril - Vertically-integrated liquid 260137: - lmaxbr - Layer-maximum base reflectivity 260138: - rdp - Precipitation from radar 260139: - acces - Air concentration of Caesium 137 260140: - aciod - Air concentration of Iodine 131 260141: - acradp - Air concentration of radioactive pollutant 260142: - gdces - Ground deposition of Caesium 137 260143: - gdiod - Ground deposition of Iodine 131 260144: - gdradp - Ground deposition of radioactive pollutant 260145: - tiaccp - Time-integrated air concentration of caesium pollutant 260146: - tiacip - Time-integrated air concentration of iodine pollutant 260147: - tiacrp - Time-integrated air concentration of radioactive pollutant 260148: - volash - Volcanic ash 260149: - icit - Icing top 260150: - icib - Icing base 260151: - ici - Icing 260152: - turbt - Turbulence top 260153: - turbb - Turbulence base 260154: - turb - Turbulence 260155: - tke - Turbulent kinetic energy 260156: - pblreg - Planetary boundary layer regime 260157: - conti - Contrail intensity 260158: - contet - Contrail engine type 260159: - contt - Contrail top 260160: - contb - Contrail base 260161: - mxsalb - Maximum snow albedo 260162: - snfalb - Snow free albedo 260163: - _param_260163 - Icing 260164: - _param_260164 - In-cloud turbulence 260165: - rcat - Relative clear air turbulence (RCAT) 260166: - sldp - Supercooled large droplet probability 260167: - var190m0 - Arbitrary text string 260168: - tsec - Seconds prior to initial reference time (defined in Section 1) 260169: - ffldg - Flash flood guidance 260170: - ffldro - Flash flood runoff (Encoded as an accumulation over a floating subinterval of time) 260171: - rssc - Remotely sensed snow cover 260172: - esct - Elevation of snow covered terrain 260173: - swepon - Snow water equivalent percent of normal 260174: - bgrun - Baseflow-groundwater runoff 260175: - ssrun - Storm surface runoff 260176: - cppop - Conditional percent precipitation amount fractile for an overall period (Encoded as an accumulation) 260177: - pposp - Percent precipitation in a sub-period of an overall period 260178: - pop - Probability of 0.01 inch of precipitation (POP) 260180: - veg - Vegetation 260181: - watr - Water runoff 260182: - evapt - Evapotranspiration 260183: - mterh - Model terrain height 260184: - landu - Land use 260185: - soilw - Volumetric soil moisture content 260186: - gflux - Ground heat flux 260187: - mstav - Moisture availability 260188: - sfexc - Exchange coefficient 260189: - cnwat - Plant canopy surface water 260190: - bmixl - Blackadar mixing length scale 260191: - ccond - Canopy conductance 260192: - rsmin - Minimal stomatal resistance 260193: - rcs - Solar parameter in canopy conductance 260194: - rct - Temperature parameter in canopy conductance 260195: - rcsol - Soil moisture parameter in canopy conductance 260196: - rcq - Humidity parameter in canopy conductance 260197: - cisoilw - Column-integrated soil water 260198: - hflux - Heat flux 260199: - vsw - Volumetric soil moisture 260200: - vwiltm - Volumetric wilting point 260201: - uplst - Upper layer soil temperature 260202: - uplsm - Upper layer soil moisture 260203: - lowlsm - Lower layer soil moisture 260204: - botlst - Bottom layer soil temperature 260205: - soill - Liquid volumetric soil moisture (non-frozen) 260206: - rlyrs - Number of soil layers in root zone 260207: - smref - Transpiration stress-onset (soil moisture) 260208: - smdry - Direct evaporation cease (soil moisture) 260209: - poros - Soil porosity 260210: - liqvsm - Liquid volumetric soil moisture (non-frozen) 260211: - voltso - Volumetric transpiration stress-onset (soil moisture) 260212: - transo - Transpiration stress-onset (soil moisture) 260213: - voldec - Volumetric direct evaporation cease (soil moisture) 260214: - direc - Direct evaporation cease (soil moisture) 260215: - soilp - Soil porosity 260216: - vsosm - Volumetric saturation of soil moisture 260217: - satosm - Saturation of soil moisture 260218: - estp - Estimated precipitation 260219: - irrate - Instantaneous rain rate 260220: - ctoph - Cloud top height 260221: - ctophqi - Cloud top height quality indicator 260222: - estu - Estimated u component of wind 260223: - estv - Estimated v component of wind 260224: - npixu - Number of pixels used 260225: - solza - Solar zenith angle 260226: - raza - Relative azimuth angle 260227: - rfl06 - Reflectance in 0.6 micron channel 260228: - rfl08 - Reflectance in 0.8 micron channel 260229: - rfl16 - Reflectance in 1.6 micron channel 260230: - rfl39 - Reflectance in 3.9 micron channel 260231: - atmdiv - Atmospheric divergence 260232: - wvdir - Direction of wind waves 260233: - dirpw - Primary wave direction 260234: - perpw - Primary wave mean period 260235: - persw - Secondary wave mean period 260236: - dirc - Current direction 260237: - spc - Current speed 260238: - wz - Geometric vertical velocity 260241: - tsec - Seconds prior to initial reference time (defined in Section 1) 260242: - 2r - 2 metre relative humidity 260243: - ttrad - Temperature tendency by all radiation 260244: - rev - Relative Error Variance 260245: - lrghr - Large Scale Condensate Heating rate 260246: - cnvhr - Deep Convective Heating rate 260247: - thflx - Total Downward Heat Flux at Surface 260248: - ttdia - Temperature Tendency By All Physics 260249: - ttphy - Temperature Tendency By Non-radiation Physics 260250: - tsd1d - Standard Dev. of IR Temp. over 1x1 deg. area 260251: - shahr - Shallow Convective Heating rate 260252: - vdfhr - Vertical Diffusion Heating rate 260253: - thz0 - Potential temperature at top of viscous sublayer 260254: - tchp - Tropical Cyclone Heat Potential 260255: - aptmp - Apparent temperature 260256: - hindex - Haines Index 260257: - ccl - Cloud cover 260259: - eva - Evaporation 260260: - 10wdir - 10 metre wind direction 260261: - minrh - Minimum Relative Humidity 260262: - sdirswrf - Surface direct short-wave radiation flux 260263: - sdifswrf - Surface diffuse short-wave radiation flux 260264: - tidirswrf - Time-integrated surface direct short wave radiation flux 260265: - eva06 - Evaporation in the last 6 hours 260266: - eva24 - Evaporation in the last 24 hours 260267: - tp06 - Total precipitation in the last 6 hours 260268: - tp24 - Total precipitation in the last 24 hours 260269: - tipd - Total Icing Potential Diagnostic 260270: - ncip - Number concentration for ice particles 260271: - snot - Snow temperature 260272: - tclsw - Total column-integrated supercooled liquid water 260273: - tcolm - Total column-integrated melting ice 260274: - emnp - Evaporation - Precipitation 260275: - sbsno - Sublimation (evaporation from snow) 260276: - cnvmr - Deep Convective Moistening Rate 260277: - shamr - Shallow Convective Moistening Rate 260278: - vdfmr - Vertical Diffusion Moistening Rate 260279: - condp - Condensation Pressure of Parcali Lifted From Indicate Surface 260280: - lrgmr - Large scale moistening rate 260281: - qz0 - Specific humidity at top of viscous sublayer 260282: - qmax - Maximum specific humidity at 2m 260283: - qmin - Minimum specific humidity at 2m 260284: - arain - Liquid precipitation (rainfall) 260285: - snowt - Snow temperature, depth-avg 260286: - apcpn - Total precipitation (nearest grid point) 260287: - acpcpn - Convective precipitation (nearest grid point) 260288: - frzr - Freezing Rain 260289: - fscov - Fraction of snow cover 260290: - cat - Clear air turbulence (CAT) 260291: - mwt - Mountain wave turbulence (eddy dissipation rate) 260292: - crwc_conv - Specific rain water content (convective) 260293: - cswc_conv - Specific snow water content (convective) 260294: - glm - Glacier mask 260295: - lauv - Latitude of U Wind Component of Velocity 260296: - louv - Longitude of U Wind Component of Velocity 260297: - lavv - Latitude of V Wind Component of Velocity 260298: - lovv - Longitude of V Wind Component of Velocity 260299: - lapp - Latitude of Pressure Point 260300: - lopp - Longitude of Pressure Point 260301: - vedh - Vertical Eddy Diffusivity Heat exchange 260302: - covmz - Covariance between Meridional and Zonal Components of the wind. 260303: - covtz - Covariance between Temperature and Zonal Components of the wind. 260304: - covtm - Covariance between Temperature and Meridional Components of the wind. 260305: - vdfua - Vertical Diffusion Zonal Acceleration 260306: - vdfva - Vertical Diffusion Meridional Acceleration 260307: - gwdu - Gravity wave drag zonal acceleration 260308: - gwdv - Gravity wave drag meridional acceleration 260309: - cnvu - Convective zonal momentum mixing acceleration 260310: - cnvv - Convective meridional momentum mixing acceleration 260311: - wtend - Tendency of vertical velocity 260312: - omgalf - Omega (Dp/Dt) divide by density 260313: - cngwdu - Convective Gravity wave drag zonal acceleration 260314: - cngwdv - Convective Gravity wave drag meridional acceleration 260315: - lmv - Velocity Point Model Surface 260316: - pvmww - Potential Vorticity (Mass-Weighted) 260317: - mslet - MSLP (Eta model reduction) 260318: - ptype_sev1h - Precipitation type (most severe) in the last 1 hour 260319: - ptype_sev3h - Precipitation type (most severe) in the last 3 hours 260320: - ptype_freq1h - Precipitation type (most frequent) in the last 1 hour 260321: - ptype_freq3h - Precipitation type (most frequent) in the last 3 hours 260323: - mslma - MSLP (MAPS System Reduction) 260324: - tslsa - 3-hr pressure tendency (Std. Atmos. Reduction) 260325: - plpl - Pressure of level from which parcel was lifted 260326: - lpsx - X-gradient of Log Pressure 260327: - lpsy - Y-gradient of Log Pressure 260328: - hgtx - X-gradient of Height 260329: - hgty - Y-gradient of Height 260330: - layth - Layer Thickness 260331: - nlgsp - Natural Log of Surface Pressure 260332: - cnvumf - Convective updraft mass flux 260333: - cnvdmf - Convective downdraft mass flux 260334: - cnvdemf - Convective detrainment mass flux 260335: - lmh - Mass Point Model Surface 260336: - hgtn - Geopotential Height (nearest grid point) 260337: - presn - Pressure (nearest grid point) 260338: - ptype_sev6h - Precipitation type (most severe) in the last 6 hours 260339: - ptype_freq6h - Precipitation type (most frequent) in the last 6 hours 260340: - duvb - UV-B downward solar flux 260341: - cduvb - Clear sky UV-B downward solar flux 260342: - csdsf - Clear Sky Downward Solar Flux 260343: - swhr - Solar Radiative Heating Rate 260344: - csusf - Clear Sky Upward Solar Flux 260345: - cfnsf - Cloud Forcing Net Solar Flux 260346: - vbdsf - Visible Beam Downward Solar Flux 260347: - vddsf - Visible Diffuse Downward Solar Flux 260348: - nbdsf - Near IR Beam Downward Solar Flux 260349: - nddsf - Near IR Diffuse Downward Solar Flux 260350: - dtrf - Downward Total radiation Flux 260351: - utrf - Upward Total radiation Flux 260354: - lwhr - Long-Wave Radiative Heating Rate 260355: - csulf - Clear Sky Upward Long Wave Flux 260356: - csdlf - Clear Sky Downward Long Wave Flux 260357: - cfnlf - Cloud Forcing Net Long Wave Flux 260360: - sot - Soil temperature 260361: - sdswrfcs - Surface downward short-wave radiation flux, clear sky 260362: - suswrfcs - Surface upward short-wave radiation flux, clear sky 260363: - sdlwrfcs - Surface downward long-wave radiation flux, clear sky 260364: - sohf - Soil heat flux 260365: - percr - Percolation rate 260366: - mflux - Convective Cloud Mass Flux 260367: - sod - Soil depth 260368: - som - Soil moisture 260369: - ri - Richardson Number 260370: - cwdi - Convective Weather Detection Index 260372: - uphl - Updraft Helicity 260373: - lai - Leaf Area Index 260374: - pmtc - Particulate matter (coarse) 260375: - pmtf - Particulate matter (fine) 260376: - lpmtf - Particulate matter (fine) 260377: - lipmf - Integrated column particulate matter (fine) 260379: - ozcon - Ozone Concentration (PPB) 260380: - ozcat - Categorical Ozone Concentration 260381: - vdfoz - Ozone vertical diffusion 260382: - poz - Ozone production 260383: - toz - Ozone tendency 260384: - pozt - Ozone production from temperature term 260385: - pozo - Ozone production from col ozone term 260386: - refzr - Derived radar reflectivity backscatter from rain 260387: - refzi - Derived radar reflectivity backscatter from ice 260388: - refzc - Derived radar reflectivity backscatter from parameterized convection 260389: - refd - Derived radar reflectivity 260390: - refc - Maximum/Composite radar reflectivity 260391: - ltng - Lightning 260394: - srcono - Slight risk convective outlook 260395: - mrcono - Moderate risk convective outlook 260396: - hrcono - High risk convective outlook 260397: - torprob - Tornado probability 260398: - hailprob - Hail probability 260399: - windprob - Wind probability 260400: - storprob - Significant Tornado probability 260401: - shailpro - Significant Hail probability 260402: - swindpro - Significant Wind probability 260403: - tstmc - Categorical Thunderstorm (1-yes, 0-no) 260404: - mixly - Number of mixed layers next to surface 260405: - flght - Flight Category 260406: - cicel - Confidence - Ceiling 260407: - civis - Confidence - Visibility 260408: - ciflt - Confidence - Flight Category 260409: - lavni - Low-Level aviation interest 260410: - havni - High-Level aviation interest 260411: - sbsalb - Visible, Black Sky Albedo 260412: - swsalb - Visible, White Sky Albedo 260413: - nbsalb - Near IR, Black Sky Albedo 260414: - nwsalb - Near IR, White Sky Albedo 260415: - prsvr - Total Probability of Severe Thunderstorms (Days 2,3) 260416: - prsigsvr - Total Probability of Extreme Severe Thunderstorms (Days 2,3) 260417: - sipd - Supercooled Large Droplet (SLD) Potential 260418: - epsr - Radiative emissivity 260419: - tpfi - Turbulence Potential Forecast Index 260420: - vaftd - Volcanic Ash Forecast Transport and Dispersion 260421: - nlat - Latitude (-90 to +90) 260422: - elon - East Longitude (0 - 360) 260423: - adswrf_cs - Accumulated surface downward short-wave radiation flux, clear sky 260424: - mlyno - Model Layer number (From bottom up) 260425: - nlatn - Latitude (nearest neighbor) (-90 to +90) 260426: - elonn - East Longitude (nearest neighbor) (0 - 360) 260427: - auswrf_cs - Accumulated surface upward short-wave radiation flux, clear sky 260428: - adlwrf_cs - Accumulated surface downward long-wave radiation flux, clear sky 260429: - cpozp - Probability of Freezing Precipitation 260430: - perc - Percolation 260431: - ppffg - Probability of precipitation exceeding flash flood guidance values 260432: - cwr - Probability of Wetting Rain, exceeding in 0.10 in a given time period 260433: - etr - Evapotranspiration rate 260434: - acc_et24 - Time-integrated evapotranspiration rate in the last 24h 260435: - avg_et24 - Time-mean evapotranspiration rate in the last 24h 260436: - petr - Potential evapotranspiration rate 260437: - acc_pet24 - Time-integrated potential evapotranspiration rate in the last 24h 260438: - avg_pet24 - Time-mean potential evapotranspiration rate in the last 24h 260439: - vgtyp - Vegetation Type 260440: - avg_swv24 - Time-mean volumetric soil moisture 260441: - avg_tp24 - Time-mean total precipitation rate 260442: - wilt - Wilting Point 260443: - rorwe - Runoff rate water equivalent (surface plus subsurface) 260444: - acc_rod24 - Time-integrated water runoff and drainage rate in the last 24h 260445: - avg_rod24 - Time-mean water runoff and drainage rate in the last 24h 260447: - rdrip - Rate of water dropping from canopy to ground 260448: - icwat - Ice-free water surface 260449: - akhs - Surface exchange coefficients for T and Q divided by delta z 260450: - akms - Surface exchange coefficients for U and V divided by delta z 260451: - vegt - Vegetation canopy temperature 260452: - sstor - Surface water storage 260453: - lsoil - Liquid soil moisture content (non-frozen) 260454: - ewatr - Open water evaporation (standing water) 260455: - gwrec - Groundwater recharge 260456: - qrec - Flood plain recharge 260457: - sfcrh - Roughness length for heat 260458: - ndvi - Normalized Difference Vegetation Index 260459: - landn - Land-sea coverage (nearest neighbor) [land=1,sea=0] 260460: - amixl - Asymptotic mixing length scale 260461: - wvinc - Water vapor added by precip assimilation 260462: - wcinc - Water condensate added by precip assimilation 260463: - wvconv - Water Vapor Flux Convergence (Vertical Int) 260464: - wcconv - Water Condensate Flux Convergence (Vertical Int) 260465: - wvuflx - Water Vapor Zonal Flux (Vertical Int) 260466: - wvvflx - Water Vapor Meridional Flux (Vertical Int) 260467: - wcuflx - Water Condensate Zonal Flux (Vertical Int) 260468: - wcvflx - Water Condensate Meridional Flux (Vertical Int) 260469: - acond - Aerodynamic conductance 260470: - evcw - Canopy water evaporation 260471: - trans - Transpiration 260472: - avg_sd24 - Time-mean snow depth water equivalent 260473: - avg_skt24 - Time-mean skin temperature 260474: - sltyp - Surface Slope Type 260475: - smr - Snow melt rate 260476: - acc_smr24 - Time-integrated snow melt rate in the last 24h 260477: - avg_smr24 - Time-mean snow melt rate in the last 24h 260478: - evbs - Direct evaporation from bare soil 260479: - lspa - Land Surface Precipitation Accumulation 260480: - baret - Bare soil surface skin temperature 260481: - avsft - Average surface skin temperature 260482: - radt - Effective radiative skin temperature 260483: - fldcp - Field Capacity 260484: - usct - Scatterometer Estimated U Wind Component 260485: - vsct - Scatterometer Estimated V Wind Component 260486: - wstp - Wave Steepness 260487: - omlu - Ocean Mixed Layer U Velocity 260488: - omlv - Ocean Mixed Layer V Velocity 260489: - ubaro - Barotropic U velocity 260490: - vbaro - Barotropic V velocity 260491: - surge - Storm Surge 260492: - etsrg - Extra Tropical Storm Surge 260493: - elevhtml - Ocean Surface Elevation Relative to Geoid 260494: - sshg - Sea Surface Height Relative to Geoid 260495: - p2omlt - Ocean Mixed Layer Potential Density (Reference 2000m)
= (3, 11))')" ] ; then if [ "3.10" != "$PYVERSION" ] ; then PYMETKIT_IFACE="-DENABLE_PYTHON_METKIT_INTERFACE=ON" else >&2 echo "Not enabling pymetkit interface because python version is $PYVERSION" PYMETKIT_IFACE="" fi CMAKE_PARAMS="-Deckit_ROOT=/tmp/metkit/prereqs/eckitlib -Deccodes_ROOT=/tmp/metkit/prereqs/eccodeslib -DENABLE_GRIB=1 $PYMETKIT_IFACE" PYPROJECT_DIR="python/metkitlib" DEPENDENCIES='["eckitlib", "eccodeslib"]' metkit-1.20.2/python/metkitlib/setup.py0000664000175000017500000000006215246725757020316 0ustar alastairalastairfrom setup_utils import plain_setup plain_setup() metkit-1.20.2/.vscode/0000775000175000017500000000000015246725757014642 5ustar alastairalastairmetkit-1.20.2/.vscode/launch.json0000664000175000017500000001317715246725757017020 0ustar alastairalastair{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "${file}", "console": "integratedTerminal", "justMyCode": true }, { "name": "make params MARS yaml (deprecated method)", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-params-yaml.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1" // "${env:ECFLOW_DIR}" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make paramIds yaml - 0001", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-paramids-yaml.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1" // "${env:ECFLOW_DIR}" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make paramIds yaml - 0078", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-paramids-yaml-esuite.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1" // "${env:ECFLOW_DIR}" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make params FDB yaml - 0001", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-params-yaml-from-fields-catalogue.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1", // "${env:ECFLOW_DIR}" "PRODUCT_CATALOGUE_HOST": "bol-db-products-prod-01", "PRODUCT_CATALOGUE_DB": "products", "PRODUCT_CATALOGUE_USER": "ecmwf_ro", "PRODUCT_CATALOGUE_PASSWORD": "ecmwf_ro", "PRODUCT_CATALOGUE_PORT": "5432", "VALIDATION_SERVICE_HOST": "https://products.ecmwf.int/requirements/" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make params FDB yaml - 0078 - preprod", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-params-yaml-from-fields-catalogue.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1", // "${env:ECFLOW_DIR}" "PRODUCT_CATALOGUE_HOST": "k8s-bol-webapps-test-worker-016", "PRODUCT_CATALOGUE_DB": "products", "PRODUCT_CATALOGUE_USER": "products", "PRODUCT_CATALOGUE_PASSWORD": "products", "PRODUCT_CATALOGUE_PORT": "30544", "VALIDATION_SERVICE_HOST": "https://products-test.ecmwf.int/esuite/requirements/" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make params FDB yaml - 0078 - prod", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/make-params-yaml-from-fields-catalogue.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1", // "${env:ECFLOW_DIR}" "PRODUCT_CATALOGUE_HOST": "k8s-bol-webapps-prod-worker-012", "PRODUCT_CATALOGUE_DB": "products", "PRODUCT_CATALOGUE_USER": "products", "PRODUCT_CATALOGUE_PASSWORD": "products", "PRODUCT_CATALOGUE_PORT": "30545", "VALIDATION_SERVICE_HOST": "https://products.ecmwf.int/esuite/requirements/" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make fields DB yaml - 0001", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/fieldsdb.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1", // "${env:ECFLOW_DIR}" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, { "name": "make fields DB yaml - 0078", "type": "python", "request": "launch", "program": "${env:HOME}/git/metkit/share/metkit/fieldsdb-esuite.py", "console": "integratedTerminal", "justMyCode": false, "env": { "ECFLOW_DIR": "/usr/local/apps/ecflow/5.8.1", // "${env:ECFLOW_DIR}" }, "args": [""], "cwd":"${env:HOME}/git/metkit/share/metkit/" }, ] } metkit-1.20.2/README.md0000664000175000017500000000430615246725757014563 0ustar alastairalastair [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ecmwf/metkit/blob/develop/LICENSE) [![Static Badge](https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity/graduated_badge.svg)](https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity#graduated) > \[!IMPORTANT\] > This software is **Graduated** and subject to ECMWF's guidelines on [Software Maturity](https://github.com/ecmwf/codex/raw/refs/heads/main/Project%20Maturity). # metkit ## Requirements Runtime dependencies: - eckit -- http://github.com/ecmwf/eckit Build dependencies: - CMake --- For use and installation see http://www.cmake.org/ - ecbuild --- ECMWF library of CMake macros () ## Installation metkit employs an out-of-source build/install based on CMake. Make sure ecbuild is installed and the ecbuild executable script is found ( `which ecbuild` ). Now proceed with installation as follows: ```bash # Environment --- Edit as needed srcdir=$(pwd) builddir=build installdir=$HOME/local # 1. Create the build directory: mkdir $builddir cd $builddir # 2. Run CMake ecbuild --prefix=$installdir -- -DECKIT_PATH= $srcdir # 3. Compile / Install make -j10 make install ``` ## Release Config files should be updated before releasing a new version. Please refer to the documentation available on [Confluence](https://confluence.ecmwf.int/display/METK/MetKit+configuration) ## License Copyright 2021 European Centre for Medium-Range Weather Forecasts (ECMWF) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at > http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 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.metkit-1.20.2/metkit-import.cmake.in0000664000175000017500000000065015246725757017516 0ustar alastairalastairset( metkit_HAVE_GRIB @metkit_HAVE_GRIB@ ) set( metkit_HAVE_ODB @metkit_HAVE_ODB@ ) include( CMakeFindDependencyMacro ) find_dependency( eckit HINTS ${CMAKE_CURRENT_LIST_DIR}/../eckit @eckit_DIR@ ) if( metkit_HAVE_GRIB ) find_dependency( eccodes HINTS ${CMAKE_CURRENT_LIST_DIR}/../eccodes @metkit_DIR@ ) endif() if( metkit_HAVE_ODB ) find_dependency( odc HINTS ${CMAKE_CURRENT_LIST_DIR}/../odc @odc_DIR@ ) endif() metkit-1.20.2/.clang-format0000664000175000017500000000544015246725757015657 0ustar alastairalastairLanguage: Cpp AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortEnumsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakAfterReturnType: Automatic BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: AfterColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false EmptyLineBeforeAccessModifier: Always EmptyLineAfterAccessModifier: Always FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeCategories: - Regex: '^<.*\.h>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true IndentWidth: 4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never metkit-1.20.2/docs/0000775000175000017500000000000015246725757014231 5ustar alastairalastairmetkit-1.20.2/docs/requirements.txt0000664000175000017500000000013715246725757017516 0ustar alastairalastairSphinx~=8.1 breathe~=4.35 pydata-sphinx-theme myst-parser sphinxcontrib-mermaid sphinx-autoapi metkit-1.20.2/docs/conf.py0000664000175000017500000000672215246725757015537 0ustar alastairalastair# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html import datetime import os # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "Metkit" copyright = f"{datetime.datetime.today().year}, ECMWF" author = "ECMWF" version = "local-dev" release = "local-dev" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ "sphinx.ext.autosectionlabel", "sphinxcontrib.mermaid", "autoapi.extension", "sphinx.ext.viewcode", "sphinx.ext.napoleon", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.inheritance_diagram", "breathe", "myst_parser", ] templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "_internal"] autoapi_dirs = ["../src/pymetkit"] autoapi_type = "python" autoapi_generate_api_docs = True autoapi_add_toctree_entry = False autoapi_python_class_content = "both" autoapi_ignore = [ "*/_internal/*", ] add_module_names = False autoapi_keep_files = False # -- Napoleon settings napoleon_google = True # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "pydata_sphinx_theme" html_context = {"default_mode": "auto"} html_theme_options = { "switcher": { "json_url": "https://sites.ecmwf.int/docs/metkit/versions.json", "version_match": version, }, "show_toc_level": 2, "icon_links": [ { "name": "GitHub", "url": "https://github.com/ecmwf/metkit", "icon": "fa-brands fa-github", }, ], "navbar_align": "left", "navbar_start": ["navbar-logo"], "navbar_center": ["navbar-nav"], "navbar_end": ["navbar-icon-links", "theme-switcher", "version-switcher"], "navbar_persistent": ["search-button"], "secondary_sidebar_items": ["page-toc", "edit-this-page", "sourcelink"], # On local builds no version.json is present "check_switcher": False, "content_footer_items": [], "footer_start": ["copyright"], "footer_center": ["sphinx-version"], "footer_end": ["theme-version"], } html_sidebars = {"**": ["sidebar-nav-bs"]} html_static_path = ["_static"] # -- Breathe configuration --------------------------------------------------- breathe_projects = {"Metkit": os.environ.get("DOXYGEN_XML_DIR", "doxygen/xml")} breathe_default_project = "Metkit" # -- Doxygen HTML ------------------------------------------------------------ # The full Doxygen-generated HTML (indexes, tables, class/file lists) is copied # verbatim into the output under ``doxygen/`` so it can be surfaced under the # "Doxygen" section. ``DOXYGEN_HTML_EXTRA_DIR`` must point at a directory that # contains a ``doxygen/`` subfolder with the generated HTML (see build_docs.sh # and docs/CMakeLists.txt). _doxygen_html_extra_dir = os.environ.get("DOXYGEN_HTML_EXTRA_DIR") html_extra_path = [] if _doxygen_html_extra_dir and os.path.isdir(_doxygen_html_extra_dir): html_extra_path.append(_doxygen_html_extra_dir) # -- autosectionlabel configuration ------------------------------------------ autosectionlabel_prefix_document = True metkit-1.20.2/docs/_static/0000775000175000017500000000000015246725757015657 5ustar alastairalastairmetkit-1.20.2/docs/_static/.keep0000664000175000017500000000000015246725757016572 0ustar alastairalastairmetkit-1.20.2/docs/Doxyfile.in0000664000175000017500000040022515246725757016347 0ustar alastairalastair# Doxyfile 1.13.2 # This file describes the settings to be used by the documentation system # Doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). # # Note: # # Use Doxygen to compare the used configuration file with the template # configuration file: # doxygen -x [configFile] # Use Doxygen to compare the used configuration file with the template # configuration file without replacing the environment variables or CMake type # replacement variables: # doxygen -x_noenv [configFile] #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "Metkit" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = @METKIT_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewers a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # With the PROJECT_ICON tag one can specify an icon that is included in the tabs # when the HTML document is shown. Doxygen will copy the logo to the output # directory. PROJECT_ICON = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where Doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIR@ # If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format # and will distribute the generated files over these directories. Enabling this # option can be useful when feeding Doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise cause # performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to # control the number of sub-directories. # The default value is: NO. CREATE_SUBDIRS = NO # Controls the number of sub-directories that will be created when # CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every # level increment doubles the number of directories, resulting in 4096 # directories at level 8 which is the default and also the maximum value. The # sub-directories are organized in 2 levels, the first level always has a fixed # number of 16 directories. # Minimum value: 0, maximum value: 8, default value: 8. # This tag requires that the tag CREATE_SUBDIRS is set to YES. CREATE_SUBDIRS_LEVEL = 8 # If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by Doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, # Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English # (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, # Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with # English messages), Korean, Korean-en (Korean with English messages), Latvian, # Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, # Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, # Swedish, Turkish, Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which Doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where Doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but # less readable) file names. This can be useful if your file system doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the # first line (until the first dot, question mark or exclamation mark) of a # Javadoc-style comment as the brief description. If set to NO, the Javadoc- # style will behave just like regular Qt-style comments (thus requiring an # explicit @brief command for a brief description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by Doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first # line (until the first dot, question mark or exclamation mark) of a Qt-style # comment as the brief description. If set to NO, the Qt-style will behave just # like regular Qt-style comments (thus requiring an explicit \brief command for # a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # By default Python docstrings are displayed as preformatted text and Doxygen's # special commands cannot be used. By setting PYTHON_DOCSTRING to NO the # Doxygen's special commands can be used and the contents of the docstring # documentation blocks is shown as Doxygen documentation. # The default value is: YES. PYTHON_DOCSTRING = YES # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". Note that you cannot put \n's in the value part of an alias # to insert newlines (in the resulting output). You can put ^^ in the value part # of an alias to insert a newline as if a physical newline was in the original # file. When you need a literal { or } or , in the value part of an alias you # have to escape them by means of a backslash (\), this can lead to conflicts # with the commands \{ and \} for these it is advised to use the version @{ and # @} or use a double escape (\\{ and \\}) ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make Doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by Doxygen. When specifying no_extension you should add # * to the FILE_PATTERNS. # # Note see also the list of default file extension mappings. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by Doxygen, so you can # mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 6. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 6 # The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to # generate identifiers for the Markdown headings. Note: Every identifier is # unique. # Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a # sequence number starting at 0 and GITHUB use the lower case version of title # with any whitespace replaced by '-' and punctuation characters removed. # The default value is: DOXYGEN. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. MARKDOWN_ID_STYLE = DOXYGEN # When enabled Doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. Words listed in the # AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. # The default value is: YES. AUTOLINK_SUPPORT = YES # This tag specifies a list of words that, when matching the start of a word in # the documentation, will suppress auto links generation, if it is enabled via # AUTOLINK_SUPPORT. This list does not affect affect links explicitly created # using \# or the \link or commands. # This tag requires that the tag AUTOLINK_SUPPORT is set to YES. AUTOLINK_IGNORE_WORDS = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let Doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also makes the inheritance and # collaboration diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software) sources only. Doxygen will parse # them like normal C++ but will assume all classes use public instead of private # inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # Doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then Doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small # Doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run Doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 # The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use # during processing. When set to 0 Doxygen will based this on the number of # cores available in the system. You can set it explicitly to a value larger # than 0 to get more control over the balance between CPU load and processing # speed. At this moment only the input processing can be done using multiple # threads. Since this is still an experimental feature the default is set to 1, # which effectively disables parallel processing. Please report any issues you # encounter. Generating dot graphs in parallel is controlled by the # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. NUM_PROC_THREADS = 0 # If the TIMESTAMP tag is set different from NO then each generated page will # contain the date or date and time when the page was generated. Setting this to # NO can help when comparing the output of multiple runs. # Possible values are: YES, NO, DATETIME and DATE. # The default value is: NO. TIMESTAMP = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If this flag is set to YES, the name of an unnamed parameter in a declaration # will be determined by the corresponding definition. By default unnamed # parameters remain unnamed in the output. # The default value is: YES. RESOLVE_UNNAMED_PARAMS = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # will also hide undocumented C++ concepts if enabled. This option has no effect # if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all # undocumented namespaces that are normally visible in the namespace hierarchy. # If set to NO, these namespaces will be included in the various overviews. This # option has no effect if EXTRACT_ALL is enabled. # The default value is: YES. HIDE_UNDOC_NAMESPACES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # With the correct setting of option CASE_SENSE_NAMES Doxygen will better be # able to match the capabilities of the underlying filesystem. In case the # filesystem is case sensitive (i.e. it supports files in the same directory # whose names only differ in casing), the option must be set to YES to properly # deal with such files in case they appear in the input. For filesystems that # are not case sensitive the option should be set to NO to properly deal with # output files written for symbols that only differ in casing, such as for two # classes, one named CLASS and the other named Class, and to also support # references to files without having to specify the exact matching casing. On # Windows (including Cygwin) and macOS, users should typically set this option # to NO, whereas on Linux or other Unix flavors it should typically be set to # YES. # Possible values are: SYSTEM, NO and YES. # The default value is: SYSTEM. CASE_SENSE_NAMES = SYSTEM # If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_HEADERFILE tag is set to YES then the documentation for a class # will show which file needs to be included to use the class. # The default value is: YES. SHOW_HEADERFILE = YES # If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # Doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by Doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by Doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents Doxygen's defaults, run Doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. See also section "Changing the # layout of pages" for information. # # Note that if you run Doxygen from a directory containing a file called # DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = # The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH # environment variable) so that external tools such as latex and gs can be # found. # Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the # path already specified by the PATH variable, and are added in the order # specified. # Note: This option is particularly useful for macOS version 14 (Sonoma) and # higher, when running Doxygen from Doxywizard, because in this case any user- # defined changes to the PATH are ignored. A typical example on macOS is to set # EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin # together with the standard path, the full search path used by doxygen when # launching external tools will then become # PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin EXTERNAL_TOOL_PATH = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by Doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for # potential errors in the documentation, such as documenting some parameters in # a documented function twice, or documenting parameters that don't exist or # using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete # function parameter documentation. If set to NO, Doxygen will accept that some # parameters have no documentation without warning. # The default value is: YES. WARN_IF_INCOMPLETE_DOC = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, Doxygen will only warn about wrong parameter # documentation, but not about the absence of documentation. If EXTRACT_ALL is # set to YES then this flag will automatically be disabled. See also # WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about # undocumented enumeration values. If set to NO, Doxygen will accept # undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: NO. WARN_IF_UNDOC_ENUM_VAL = NO # If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found # while parsing the user defined layout file, such as missing or wrong elements. # See also LAYOUT_FILE for details. If set to NO, problems with the layout file # will be suppressed. # The default value is: YES. WARN_LAYOUT_FILE = YES # If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when # a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS # then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but # at the end of the Doxygen process Doxygen will return with a non-zero status. # If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves # like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not # write the warning messages in between other messages but write them at the end # of a run, in case a WARN_LOGFILE is defined the warning messages will be # besides being in the defined file also be shown at the end of a run, unless # the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case # the behavior will remain as with the setting FAIL_ON_WARNINGS. # Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that Doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # See also: WARN_LINE_FORMAT # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # In the $text part of the WARN_FORMAT command it is possible that a reference # to a more specific place is given. To make it easier to jump to this place # (outside of Doxygen) the user can define a custom "cut" / "paste" string. # Example: # WARN_LINE_FORMAT = "'vi $file +$line'" # See also: WARN_FORMAT # The default value is: at line $line of file $file. WARN_LINE_FORMAT = "at line $line of file $file" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). In case the file specified cannot be opened for writing the # warning and error messages are written to standard error. When as file - is # specified the warning and error messages are written to standard output # (stdout). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @DOXYGEN_INPUT_DIR@ # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: # https://www.gnu.org/software/libiconv/) for the list of possible encodings. # See also: INPUT_FILE_ENCODING # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # This tag can be used to specify the character encoding of the source files # that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify # character encoding on a per file pattern basis. Doxygen will compare the file # name with each pattern and apply the encoding instead of the default # INPUT_ENCODING if there is a match. The character encodings are a list of the # form: pattern=encoding (like *.php=ISO-8859-1). # See also: INPUT_ENCODING for further information on supported encodings. INPUT_FILE_ENCODING = # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by Doxygen. # # Note the list of default checked file patterns might differ from the list of # default file extension mappings. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, # *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, # *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, # *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to # be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cxxm \ *.cpp \ *.cppm \ *.ccm \ *.c++ \ *.c++m \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.ixx \ *.l \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f95 \ *.f03 \ *.f08 \ *.f18 \ *.f \ *.for \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ *.ice # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which Doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # ANamespace::AClass, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that Doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that Doxygen will use the data processed and written to standard output # for further processing, therefore nothing else, like debug statements or used # commands (so in case of a Windows batch file always use @echo OFF), should be # written to standard output. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by Doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the Doxygen output. USE_MDFILE_AS_MAINPAGE = # If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- # directories of the project's root, is used as the documentation for that sub- # directory, except when the README.md starts with a \dir, \page or \mainpage # command. If set to NO, the README.md file needs to start with an explicit \dir # command in order to be used as directory documentation. # The default value is: YES. IMPLICIT_DIR_DOCS = YES # The Fortran standard specifies that for fixed formatted Fortran code all # characters from position 72 are to be considered as comment. A common # extension is to allow longer lines before the automatic comment starts. The # setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can # be processed before the automatic comment starts. # Minimum value: 7, maximum value: 10000, default value: 72. FORTRAN_COMMENT_AFTER = 72 #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # multi-line macros, enums or list initialized variables directly into the # documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of Doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by Doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) # that should be ignored while generating the index headers. The IGNORE_PREFIX # tag works for classes, function and member names. The entity will be placed in # the alphabetical list under the first letter of the entity name that remains # after removing the prefix. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank Doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that Doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that Doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of Doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank Doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that Doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank Doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that Doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by Doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). # Note: Since the styling of scrollbars can currently not be overruled in # Webkit/Chromium, the styling will be left out of the default doxygen.css if # one or more extra stylesheets have been specified. So if scrollbar # customization is desired it has to be added explicitly. For an example see the # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE tag can be used to specify if the generated HTML output # should be rendered with a dark or light theme. # Possible values are: LIGHT always generates light mode output, DARK always # generates dark mode output, AUTO_LIGHT automatically sets the mode according # to the user preference, uses light mode if no preference is set (the default), # AUTO_DARK automatically sets the mode according to the user preference, uses # dark mode if no preference is set and TOGGLE allows a user to switch between # light and dark mode via a button. # The default value is: AUTO_LIGHT. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE = AUTO_LIGHT # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be # dynamically folded and expanded in the generated HTML source code. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_CODE_FOLDING = YES # If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in # the top right corner of code and text fragments that allows the user to copy # its content to the clipboard. Note this only works if supported by the browser # and the web page is served via a secure context (see: # https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: # protocol. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COPY_CLIPBOARD = YES # Doxygen stores a couple of settings persistently in the browser (via e.g. # cookies). By default these settings apply to all HTML pages generated by # Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store # the settings under a project specific key, such that the user preferences will # be stored separately. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_PROJECT_COOKIE = # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: # https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To # create a documentation set, Doxygen will generate a Makefile in the HTML # output directory. Running make will produce the docset in that directory and # running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag determines the URL of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDURL = # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # on Windows. In the beginning of 2021 Microsoft took the original page, with # a.o. the download links, offline (the HTML help workshop was already many # years in maintenance mode). You can download the HTML help workshop from the # web archives at Installation executable (see: # http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo # ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # Doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # The SITEMAP_URL tag is used to specify the full URL of the place where the # generated documentation will be placed on the server by the user during the # deployment of the documentation. The generated sitemap is called sitemap.xml # and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL # is specified no sitemap is generated. For information about the sitemap # protocol see https://www.sitemaps.org # This tag requires that the tag GENERATE_HTML is set to YES. SITEMAP_URL = # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location (absolute path # including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to # run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = YES # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine tune the look of the index (see "Fine-tuning the output"). As an # example, the default style sheet generated by Doxygen has an example that # shows how to put an image at the root of the tree instead of the PROJECT_NAME. # Since the tree basically has the same information as the tab index, you could # consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES # When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the # FULL_SIDEBAR option determines if the side bar is limited to only the treeview # area (value NO) or if it should extend to the full height of the window (value # YES). Setting this to YES gives a layout similar to # https://docs.readthedocs.io with more room for contents, but less room for the # project logo, title, and description. If either GENERATE_TREEVIEW or # DISABLE_INDEX is set to NO, this option has no effect. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. FULL_SIDEBAR = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # Doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # When the SHOW_ENUM_VALUES tag is set doxygen will show the specified # enumeration values besides the enumeration mnemonics. # The default value is: NO. SHOW_ENUM_VALUES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email # addresses. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. OBFUSCATE_EMAILS = YES # If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. # Possible values are: png (the default) and svg (looks nicer but requires the # pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FORMULA_FORMAT = png # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # Doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # With MATHJAX_VERSION it is possible to specify the MathJax version to be used. # Note that the different versions of MathJax have different requirements with # regards to the different settings, so it is possible that also other MathJax # settings have to be changed when switching between the different MathJax # versions. # Possible values are: MathJax_2 and MathJax_3. # The default value is: MathJax_2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_VERSION = MathJax_2 # When MathJax is enabled you can set the default output format to be used for # the MathJax output. For more details about the output format see MathJax # version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 # (see: # http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best # compatibility. This is the name for Mathjax version 2, for MathJax version 3 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported # for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This # is the name for Mathjax version 3, for MathJax version 2 this will be # translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. The default value is: # - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 # - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # for MathJax version 2 (see # https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # For example for MathJax version 3 (see # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): # MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled Doxygen will generate a search box for # the HTML output. The underlying search engine uses JavaScript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the JavaScript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /