metkit-1.18.2/0000775000175000017500000000000015203070342013260 5ustar alastairalastairmetkit-1.18.2/VERSION0000664000175000017500000000000715203070342014325 0ustar alastairalastair1.18.2 metkit-1.18.2/NOTICE0000664000175000017500000000063515203070342014170 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.18.2/tests/0000775000175000017500000000000015203070342014422 5ustar alastairalastairmetkit-1.18.2/tests/test_odbsplitter.cc0000664000175000017500000000752215203070342020331 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.18.2/tests/netcdf/0000775000175000017500000000000015203070342015665 5ustar alastairalastairmetkit-1.18.2/tests/netcdf/signature_merge_1.sh0000775000175000017500000000214715203070342021630 0ustar alastairalastair#!/bin/bash set -eaux rm -f *.nc ncgen -b - <C–C–€C–C–metkit-1.18.2/tests/netcdf/simple_three_way_merge.sh0000775000175000017500000000155315203070342022747 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.18.2/tests/test_expand.cc0000664000175000017500000017475415203070342017271 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.18.2/tests/test_integer_range.cc0000664000175000017500000001224115203070342020601 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.18.2/tests/test_mars_language_strict.cc0000664000175000017500000000142115203070342022163 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.18.2/tests/regressions/0000775000175000017500000000000015203070342016765 5ustar alastairalastairmetkit-1.18.2/tests/regressions/METK-89/0000775000175000017500000000000015203070342017763 5ustar alastairalastairmetkit-1.18.2/tests/regressions/METK-89/METK-89.sh.in0000775000175000017500000000375115203070342021733 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.18.2/tests/regressions/METK-89/CMakeLists.txt0000664000175000017500000000027215203070342022524 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.18.2/tests/regressions/METK-103/0000775000175000017500000000000015203070342020026 5ustar alastairalastairmetkit-1.18.2/tests/regressions/METK-103/ok0000664000175000017500000107734015203070342020376 0ustar alastairalastairBUFRb€Ë 4Ë~hd!^ jL!ØbÀFV%R Õ.ÿÿdE¤F ÕÀÌF  €(!I·¾.@@ @@€€ €@€`ÀÀ À@À`À!Aa¡Á@!@A@a@@¡@Á@áD€!€A€a€€¡€Á€á!„a„À!ÀAÀaÀÀ¡ÀÁÀáÁÁ!ÁAÁaÄ!ÄAÄaÄ‚Bb‚¢Ââ"Bb‚¢Ââ"Bb‚@B@b@‚@¢@Â@âAA"ABAbA‚A¢AÂAâBCbC‚C¢CÂCâDD"DBDbD‚D¢€b€‚€¢€Â€â"Bb‚¢Ââ‚‚"‚B‚b‚‚‚¢‚‚⃃"ƒBƒbƒ‚ƒ¢ƒÂƒâ„„"„B„b„‚„¢ÀbÀ‚À¢ÀÂÀâÁÁ"ÁBÁbÁ‚Á¢ÁÂÁâÂÂ"ÂBÂb‚¢ÂÂÂâÃÃ"ÃBÃbÂâÃÂÃâÄÄ"ÄBÄbĂģcƒ£Ãã#Ccƒ£Ãã#Ccƒ£Ãã#Ccƒ£Ãã#Ccƒ£@c@ƒ@£@Ã@ãAA#ACAcAƒA£AÃAãBB#BCBcBƒB£BÃBãCC#CCCcCƒC£CÃCãDD#DCDcDƒD£DÀc€ƒ€£€Ã€ã#Ccƒ£Ãã‚‚#‚C‚c‚ƒ‚£‚Âッ#ƒCƒcƒƒƒ£ƒÃƒã„„#„C„c„ƒ„£„ÃÀcÀƒÀ£ÀÃÀãÁÁ#ÁCÁcÁƒÁ£ÁÃÁãÂÂ#ÂCÂcģÂÃÂãÃÃ#ÃCÃcÃãÃÃÃãÄÄ#ÄCÄcăģÄÄd„¤Ää$Dd„¤Ää$Dd„¤Ää$Dd„¤Ää$Dd„¤Ää@d@„@¤@Ä@äAA$ADAdA„A¤AÄAäBB$BDBdB„B¤BÄBäCC$CDCdC„C¤CÄCäDD$DDDdD„D¤DÄDä€d€„€¤€Ä€ä$Dd„¤Ää‚‚$‚D‚d‚„‚¤‚Ă䃃$ƒDƒdƒ„ƒ¤ƒÄƒä„„$„D„d„„„¤„Ä„ä…ÀDÀdÀ„À¤ÀÄÀäÁÁ$ÁDÁdÁ„Á¤ÁÄÁäÂÂ$ÂDÂd„¤ÂÄÂäÃÃ$ÃDÃdÄäÃÄÃäÄÄ$ÄDÄdĄĤÄÄÄäÅEe…¥Åå%Ee…¥Åå%Ee…¥Åå%Ee…¥Åå%Ee…¥Åå%@e@…@¥@Å@åAA%AEAeA…A¥AÅAåBB%BEBeB…B¥BÅBåCC%CECeC…C¥CÅCåDD%DEDeD…D¥DÅDåEE%EEeEeU€…€¥€Å€å%Ee…¥Åå‚‚%‚E‚e‚…‚¥‚ł僃%ƒEƒeƒ…ƒ¥ƒÅƒå„„%„E„e„…„¥„ń充%…E…U¥eÀ¥ÀÅÀåÁÁ%ÁEÁeÁ…Á¥ÁÅÁåÂÂ%ÂEÂeÂ…Â¥ÂÅÂåÃÃ%ÃEÃeÃ…Ã¥ÃÅÃåÄÄ%ÄEÄeÄ…Ä¥ÄÅÄåÅÅ%ÅEÅeÅuåuå†¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ff†–%–%¦@Æ@æAA&AFAfA†A¦AÆAæBB&BFBfB†B¦BÆBæCC&CFCfC†C¦CÆCæDD&DFDfD†D¦DÆDæEE&EFEfE†E¦e¶€æ&Ff†¦Ææ‚‚&‚F‚f‚†‚¦‚Ƃ惃&ƒFƒfƒ†ƒ¦ƒÆƒæ„„&„F„f„†„¦„Ƅ慅&…F…f…†…¦…Æ¥ÖÁÁ&ÁFÁfÁ†Á¦ÁÆÁæÂÂ&ÂFÂfÂ†Â¦ÂÆÂæÃÃ&ÃFÃfÃ†Ã¦ÃÆÃæÄÄ&ÄFÄfĆĦįĿÅÅ&ÅFÅfņŦůÅç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç%W%gA'AGAgA‡A§AÇAçBB'BGBgB‡B§BÇBçCC'CGCgC‡C§CÇCçDD'DGDgD‡D§DÇD×DçD÷EEE'E7EGEWEgEwE‡E—E§EÇEçE÷FFF'd—d§d·dÇd×dçd÷eee'e7eGeWegewe‡e—e§e·e×eçe÷fff'f7fGGg‡§Çç‚‚'‚G‚g‚‡‚§‚ǂ烃'ƒGƒgƒ‡ƒ§ƒÇƒç„„'„G„W„g„w„‡„—„§„·„DŽׄç„÷………'…7…G…W…g…w…‡…—…§…·…Ç…×…ç…÷†††'†7†G†W£ç£÷¤¤¤'¤7¤G¤W¤g¤w¤‡¤—¤§¤·¤Ç¤×¤ç¤÷¥¥¥'¥7¥G¥W¥g¥w¥‡¥—¥§¥·¥Ç¥×¥ç¥÷¦¦¦'¦7¦G¦W¦gÁgÁ‡Á§ÁÇÁçÂÂ'ÂGÂg‡§ÂÇÂçÃÃ'ÃGÃgÇçÃÇÃ×ÃçÃ÷ÄÄÄ'Ä7ÄGÄWÄgÄwćėħķÄÇÄ×ÄçÄ÷ÅÅÅ'Å7ÅGÅWÅgÅwŇŗŧŷÅÇÅ×ÅçÅ÷ÆÆÆ'Æ7ÆGÆWÆgÆwãÇã×ãçã÷äää'ä7äGäWägäwä‡ä—ä§ä·äÇä×äçä÷ååå'å7åGåWågåwå‡å—å§å·åÇå×åçå÷æææ'æ7æGæWægæwæˆhˆ¨Èè(Hhˆ¨Èè(Hhˆ¨¸ÈØèø(8HXhxˆ˜¨¸ÈØèø(8HXhxˆ˜¨¸ÈØèø(8HXhxˆ˜¨#˜#¨#¸#È#Ø#è#ø$$$($8$H$X$h$x$ˆ$˜$¨$¸$È$Ø$è$ø%%%(%8%H%X%h%x%ˆ%˜%¨%¸%È%Ø%è%ø&&&(&8&H&X&h&x&ˆ&˜&¨&¸AˆA¨AÈAèBB(BHBhBˆB¨BÈBèCC(CHChCxCˆC˜C¨C¸CÈCØCèCøDDD(D8DHDXDhDxDˆD˜D¨D¸DÈDØDèDøEEE(E8EHEXEhExEˆE˜E¨E¸EÈEØEèEøFFF(F8FHFXFhFxFˆF˜F¨F¸FÈchcxcˆc˜c¨c¸cÈcØcècøddd(d8dHdXdhdxdˆd˜d¨d¸dÈdØdèdøeee(e8eHeXehexeˆe˜e¨e¸eÈeØeèeøfff(f8fHfXfhfxfˆf˜f¨f¸fÈf؈¨Èè‚‚(‚H‚h‚ˆ‚¨‚Ȃ胃(ƒHƒXƒhƒxƒˆƒ˜ƒ¨ƒ¸ƒÈƒØƒèƒø„„„(„8„H„X„h„x„ˆ„˜„¨„¸„È„Ø„è„ø………(…8…H…X…h…x…ˆ…˜…¨…¸…È…Ø…è…ø†††(†8†H†X†h†x†ˆ†˜†¨†¸†È†Ø†è†ø£8£H£X£h£x£ˆ£˜£¨£¸£È£Ø£è£ø¤¤¤(¤8¤H¤X¤h¤x¤ˆ¤˜¤¨¤¸¤È¤Ø¤è¤ø¥¥¥(¥8¥H¥X¥h¥x¥ˆ¥˜¥¨¥¸¥È¥Ø¥è¥ø¦¦¦(¦8¦H¦X¦h¦x¦ˆ¦˜¦¨¦¸¦È¦Ø¦è¦ø§Á¨ÁÈÁèÂÂ(ÂHÂhˆ¨ÂÈÂèÃÃ(Ã8ÃHÃXÃhÃxÈØèøÃÈÃØÃèÃøÄÄÄ(Ä8ÄHÄXÄhÄxĈĘĨĸÄÈÄØÄèÄøÅÅÅ(Å8ÅHÅXÅhÅxňŘŨŸÅÈÅØÅèÅøÆÆÆ(Æ8ÆHÆXÆhÆxƈƘƨƸÆÈÆØÆèÆøÇÇÇ(âØâèâøããã(ã8ãHãXãhãxãˆã˜ã¨ã¸ãÈãØãèãøäää(ä8äHäXähäxäˆä˜ä¨ä¸äÈäØäèäøååå(å8åHåXåhåxåˆå˜å¨å¸åÈåØåèåøæææ(æ8æHæXæhæxæˆæ˜æ¨æ¸æÈæØæèæøççç(ç9©Éé )Ii‰©¹ÉÙéù )9IYiy‰™©¹ÉÙéù )9IYiy‰™©¹ÉÙéù )9IYiy‰™©¹ÉÙéù )9"™"©"¹"É"Ù"é"ù# ##)#9#I#Y#i#y#‰#™#©#¹#É#Ù#é#ù$ $$)$9$I$Y$i$y$‰$™$©$¹$É$Ù$é$ù% %%)%9%I%Y%i%y%‰%™%©AÉAéB B)BIBiByB‰B™B©B¹BÉBÙBéBùC CC)C9CICYCiCyC‰C™C©C¹CÉCÙCéCùD DD)D9DIDYDiDyD‰D™D©D¹DÉDÙDéDùE EE)E9EIEYEibibyb‰b™b©b¹bÉbÙbébùc cc)c9cIcYcicyc‰c™c©c¹cÉcÙcécùd dd)d9dIdYdidyd‰d™d©d¹dÉdÙdédùe ee)e9Éé‚ ‚)‚I‚i‚y‚‰‚™‚©‚¹‚ɂقé‚ùƒ ƒƒ)ƒ9ƒIƒYƒiƒyƒ‰ƒ™ƒ©ƒ¹ƒÉƒÙƒéƒù„ „„)„9„I„Y„i„y„‰„™„©„¹„Ʉلé„ù… …¢i¢y¢‰¢™¢©¢¹¢É¢Ù¢é¢ù£ ££)£9£I£Y£i£y£‰£™£©£¹£É£Ù£é£ù¤ ¤¤)¤9¤I¤Y¤i¤y¤‰¤™¤©¤¹¤É¤Ù¤é¤ùÂ)ÂIÂYÂiÂy‰™©¹ÂÉÂÙÂéÂùà ÃÃ)Ã9ÃIÃYÃiÃyÉÙéùÃÉÃÙÃéÃùÄ ÄÄ)Ä9ÄIÄYÄiÄyĉęĩĹÄÉÄÙÄéâYâiâyâ‰â™â©â¹âÉâÙâéâùã ãã)ã9ãIãYãiãyã‰ã™ã©ã¹ãÉãÙãéãùä ää)ä9äIäYäiäyä‰ä™ä©ä¹äÉäÚzŠšªºÊÚêú *:JZjzŠšªºÊÚêú *:JZjzŠšªºÊ"š"ª"º"Ê"Ú"ê"ú# ##*#:#J#Z#j#z#Š#š#ª#º#Ê#Ú#ê#ú$ $$*$:$J$Z$j$z$Š$š$ª$º$ÊBºBÊBÚBêBúC CC*C:CJCZCjCzCŠCšCªCºCÊCÚCêCúD DD*D:DJDZDjDzDŠDšDªDºbÚbêbúc cc*c:cJcZcjczcŠcšcªcºcÊcÚcêcúd dd*d:dJdZdjdzdŠdšdª‚ê‚úƒ ƒƒ*ƒ:ƒJƒZƒjƒzƒŠƒšƒªƒºƒÊƒÚƒêƒú„ „„*„:„J„Z„j„z„Š„š„ª£ ££*£:£J£Z£j£z£Š£š£ª£º£Ê£Ú£ê£ú¤ ¤¤*¤:¤J¤Z¤j¤z¤Š¤š¤ªÃÃ*Ã:ÃJÃZÃjÃzÊÚêúÃÊÃÚÃêÃúÄ ÄÄ*Ä:ÄJÄZÄjÄzĊĚĪã:ãJãZãjãzãŠãšãªãºãÊãÚãêãúä ää*ä:äJäZäjäzäŠä›K[k{‹›«»ËÛëû +;K[k{‹›#[#k#{#‹#›#«#»#Ë#Û#ë#û$ $$+$;$K$[$k${$‹$›CkC{C‹C›C«C»CËCÛCëCûD DD+D;DKD[DkD{D‹D›c{c‹c›c«c»cËcÛcëcûd dd+d;dKd[dkd{d‹d›ƒ›ƒ«ƒ»ƒËƒÛƒëƒû„ „„+„;„K„[„k„{„‹„›£«£»£Ë£Û£ë£û¤ ¤¤+¤;¤K¤[¤k¤{¤‹¤›Ã»ÃËÃÛÃëÃûÄ ÄÄ+Ä;ÄKÄ[ÄkÄ{ċěãËãÛãëãûä ää+ä;äKä[äkä{ä‹äœÜìü ,<L\l|Œœ¬#ì#ü$ $$,$<$L$\$l$|$Œ$œ$¬CüD DD,DmB* X°hXƒ¯À é"n`7‘óJ—ç…,8¨Ð](A²ÂDÓ¬+¤hé ¡ï>A‚(Xö°¥H…–Ü0¤@z"Ü8&gAiT u”œ$1¡<³ ¿\Æ|—Ñþ_h†\äMô7ð0‰ºÆˆ‰ä»¸)Be: ÈfçÃmŠ‘Ï"ŽääzˆÃïS Qq U€Â pZ/ƒ>RAê €O ‚®†&ÆÇ†¢Ô8~ßÒ…Y†~ä5§ñº­Aœuq£Æš ¨ÿ&È/PC(2&Ô‚š1ÀÜ´À`DÆB\~–P²E…ÿ(3^¶‰à{DÞ")Q݉c N}ÂQбQÅÀÚ/·¡‹7 Åìi”#g®çkÇqª=;÷^'Œ„ž?ýÄP,쬡P‚„„'tVÆ `cDƒP>4Pï5‡æDB–â/ÄWP™}Å:)Ä1[¨ IØ]³ƒèϰƳ 7MÑÇ莫xxÀƒáà)ĈdpªÀRõƒD†à÷]”àKp‚‘l=À¿†i6¬áÐ[\°Ch#Í¡, Ì4QÉ‚©@#0·ÜÅõ21[ñ˜f /Èlßãóèî ‡¦f>ârš‘– —Û™˜3–AÒÌHÀÙë`*Âáq= aˆiÕÃ…,Ø üXOXEß‚J (° 6’+fñhÊ‹²ˆ`ù"îïØÖ;Æè*8ò!ÕT| CûV ³á b@Ý®ŸÐR‘ƒˆqö®Ž K8B2п)†e€6ÁϬVq„B #¿‘+‰ÉQ­b¨xð·ªÅó¢1LQ—õ+àlÀ£+âˆíׇ¥>ׂBެ  |¹`,¡›T‹pî„| 'E!U6 ‚àbàƒMPÀîaßÑpÀ ¼L_‘£ ¼É>Æe4¦Íl¸m#v;päKÇ=„:ÃÁÜçˆzž£âˆ€Øÿkˆ‚ABLаˆ7äOR"çñ¤Hð¬H^’I¾ îøQ*"–Ë"ð¬oÅ~r,ÌÁm/‹Ÿd^§CÍ‚8Çv†VÚ3Žq£= Oll-ÃnèãØâ}G/:Q±ÙW y·cÛNExýA¢–³d‡M„H1"®!ÕÈáÔGébF’g¸ àsëvø*A‰ò þÐ}p„WT&!K½ 5ˆ`uÃ9–{ðéY‡¶œAb#ô–jDÎ'IÑA Š>¨S¡bª…ÀÀ±jŦJ.aw ‹îLa$ù!˜Ìe~N4Ê­ Ÿn«C‚Ôƒ8çrVè;Qã4Oì|.£ïäÙ‘È/´BYò™‰Ë\#M× ÞI("P ‰I¼Ký"m|×°¢Å+¢*6AX{ŠùÄYzBÙe6ø½†Ø0÷ñŽp ©Ìfý£E€˜˜Ø#FÜ@7¸ÄŠZ8t„#±´ú8ó0G´¨>~qú° 4‚3!^1bÈŽ/©‘„ Vüdg#0Ìò0ÒöF²t6kAº#Žhqí£œçSÐíöÇŠÜ=0ð=¸8t$ 4 ·É #dpCü²&¯kÄÄvI$©$b >6JÎ]9ˆmÐE â6šØ”c„½Þ&È!<þ üøƒL„5&"‚aÉ hJb^\@ž@… *)@ñP¸ »¨W¢Ê¼P¹:Åäü/ÿ†ÅŒle5ù˜ÔA†½26ÂÁ¼Ór“C¢}8ïN‡•š=„qòíÍ´€äz á1 nHn˜DMò)R@¼d{v$HI%³‰HôK Ò_Ð5” 'Àd2ù0&†j u{¨$Á;Ò ¶0\nƒ\z áK?Ü;±AêëÃð~&²"Á&ˆï€I("VÔ#8œxDþ„(ËÁM'ŠŸ$V¥B½¸·r…Öˆ/qƒ NÈd(£.¿âÒs®¾6L¹)ÿ4q¬šóCh퀆Â=Aï7°2„' ¥Ñ“H_ÀCÚR%œcŒÎät $y#ÞÉ9êJª\3Hšq„á8 nHnבzeñ؇Å?Ô¢ 8ÌȉËi$"'Ù‰tPMQÂx,ؤÅE@ì*ß]Á‹$XZÒãñ‹X¿¿†"1 ±“¶ ÓühO#P ìøÚÆFðô8`ÑÉÃŽ„huÒƒ¼'MÐõÓGÉŽ?%¡ÿö58ƒ\$(t!±Éó¢¾Eð²6héŒþÄ•¢%9,>‰}LÃ’lÿžD ¾pä^À)³ƒ€ Ë@{ÔJp%®áHk ø_”Ã2Žj€Ú‡p9ß¡ÜOˆ}á Ê!'숵°GY¢H`®˜ÕáÎ'æEá dðTÙâ´I P³É¹n.¤¡{â ”bW 2n(ÎÏÆ‘¤5c1±ÖÅoÚcŒfÏéÜÇi¨<&qçðuÐ}`ãø 0¡éÈBtBìÒ0(”Š÷ed#—¹ "‰žI¿²TÇÜ„˜œÄÒ’');tI÷,ê°®¦|.à„“  gáÃYЧ°ãîTôËb(l ÎYJhAÂ>É`œû…(*abL ~ø_P¦ƒ@Òã†Ì 8aÎÛèz=Ãí >±¿{|EŽB9Ó;0•1ÄÄ´&ýA>šŠ*¼SB¥¼šx°,œT-»Ñt›‹Ú``‚C¥ùÀË,†t&4zaª ŠÌnÃ}ÀZ˜æ-L\;<à;8{Œ#éô»q@H%(AÿP…@„_ˆ$¨a2Ö ¸Sr‚¶VˆÀ»†2"ažt _pn`CÄD0ðë¼Ü?™B +½ð‰GÄed$1&Ó lM"uûp¤B<Ò*¾1\» ÀZŠ"áäzð¿qȽ|4 ˆào;Qx)[8–>„̶';Á@› ;$Sˆb©¤¸ð±,E¤ˆ-ûáv‚!PbÀ°©ðP2ÚÁ±üeðyàL!Ö M@M¼‰@¯¢…³`/G!‡Ì ©hh£ƒ`BØ åq‡aØ<ºáójXƒ‘D7L"“‘M ´J1Â_dž…E)OQ5 ¿ôW®âÊØÂ*Umn D hÕƒ|0“àùç;ÐE6ÂDxüðž˜*D+e… —0`C´¶ÐÔZ†Ù 8u!Ñ<ôèzÿƒó" o! Bˆ‡˜Eèâ<ÚQØ•ó„Ê'*‘@Š6ØSf¨‘°X°çyî §ké•”XÁ'l@FÆÂPø]С¸CD+Éak¬ Éøa¨(@×z†ñX9=!×J%X|ƒÿ> Ó! IˆŸÐF°âCƒ—}DÖÞ'C% OtT*⮸…ˆ7„Öæ`ƒ3„O $(A.£ á°Re­ðH°¸öý41•ÁšA =mMC…,ÿpîÅ«H? ¢ÆšÈˆ.„\6#ºá$‡‰ZˆL€bq–ø€£E3Ö*vPr)Æhå¡ ŽÏxIà‚iÆ$@§Ò…tà-S!wú ,hdµC@ÐÜÝ®"ô:ÃÁ㱈œÄ !–qdÑlH7‚OOå€šŠ„ïH(PQI?ƒßh §aœ xKduâˆ@ªæ|.~! ] fECLì@àµ;,;‹Á鿹,Ä$Z!øáwˆêHübUvðœû–Þ€…½„c $ÊÁ3· ÀSªÂ¸–лg„28AŸ# e°nƒ"O ñ)‡¿˜?¨â ÚÁ؉gf^$ &ø‰nMÂv_GÀÂXÈœP£¬Rä,CAo” ç¨b¢/ÒT ÙU‡ø9³áÛD˜}}ª!1¯ G$‚F· À˜jÄÝæ{’ˆ„Ê,'ýMM ×ZBëD3PÈKwÜ5k¸ë1˜tñƒÂNè€þ ˆ%ðBÛ¢$pŽˆÙ™Š%¢a3Ü„¬à'!Eú œhX;‚Üž¼Ä•Zô4ƒÁ± ÷só¨sPúd¤AñBS(þв%.À˜£û)„!Y‚ 8¨]ƒ®ó€Îe†¨°6ñ¡Å “@wþÃÚ¸«Ñ(VÄDbB0¥ð0’ÙÂnÚLà©…-¤azk ?ðeWÃE€0ÞÚ,T;Áæ ›P€2ļ!ºaœˆÚhHa=b W¨V‚Ën1€ÀU†803m¡¨Î ³pêC¢xéÐõÿå|@ÞBS  ‹ÑÄyP7¨…y-x¡y? 5eCC(ïPÞ+&Ü:éAä«`á„Î!§¡íˆÔð*ßdY `_ÓC\ ðÓĆӨ8IaÏÞéøz¨ðf YA zˆXE½!] T]øC „*0ж\7_ÈY¯`xÓCáŽâĈc¨DÏ¡bû ‚à_{à‹Ó†Î08¡Î€ßzPƒíF Caˈ{à-$Avƒ  dQC=LÃÜÍì:Ž¡áÖy¸2„V!xÀ·ñà1;!—: &hl‹ƒÏí(‡Ÿ>¥‚¿‡`Béæ(`Çó†u 5U!¸#, t¹CÀŒÝý"lBÅÁzè CØewFz àÞÿ‡.H;Aæmžp€KÄ  ÐÇ8oD5&A¶“ taýÐÄüáˆ2‰¡­ y8o"“Òt òn‡Èø?óášA =m@Äúýàª€?b” l¸n¾²[ ñ¦‡ÂT?¾Á³Zhs“ƒ·^‘ûEÀ:‚!á¤x(s)C´ vpúdì=«å¾‚š…@è€*b/ ®€´xR€(<8À)À…Üqì D`WË­(Ó@¡¸@@8ï¾| ¨àk!FHœ@9»ðDÀ£`‚à —€ø œp€Öti¡ ÷à'/A5. ‡K ‚OÌ3`\i°›@§ø'g‚ãР´x‘È,Á\ ºàTЂˆ¡@¢ä`¼€èÐ!7óð K gÏ‚h3˜  Ðcóƒ s`Á\úx/Du¢ а[)‚ÐP=À—E¥Ø$˜Á²Ö€Eo!P¹@ƒYÁË’:0p׃~$¯€Û=ÊH5©ú +àh?ƒ9ȃ@ÉÛ=¨1WA†n ð_7ðX:à·§¨,šÁ_ ØUŒ¢œ࣬  '¸Á8ä ž€K»ƒõHkÀù8º =GæT°w{ƒ²àU`èl209AÃô ý@nʃlô"àÖØ£84ŠŸ  Õðew!ôÄ ÃÍ /´xô ¢ [ÌÕYà°§ D³‚!æìІl„*Ì!á™%(@ŸÂäã0~ƒèdø õ´›¨Ëñ¨µÐ¬´…^8*¶aS¦ ŽXSƽ`¤ý…¸(¿ACí ÈOôÂ{¾ºœÖÝ´&¯!3: ˆ K»‚Y^¨”„—È$va!tøàG=‚5<…ð‹„O„"4ArÍ ‡[¿‚Úx²ð´ªžH,°¡cª èWð‚»j¼¬ó_*¹S¦ Sã›0· ¤¦ü( BÚ ØO—xlà›Ï„ÖH&gA1 wpK%‚U„ Å!ˆ0Ô„¬ P`=‚þÔ½¦åÄ.ï¡u‰ p\hBߌ۵Ⅶà,øež PXZ¾îÓ€­•…d*ØÁT  “T‚œ*½`¤ä…Ä(©aC> øhkC?ráÎih3–¤ ¦HdÁÃ"&õÆ®†-¤11Á‡š -ø`ìƒ|ü ¾Òï$/4aw¯ ¬H\ØÂâ¬ó¶‘…«,-Áf˜ "ÈX“Àá­íg4*ôậ È mÊCjÎ;àØåÀ 5ÈÁ¬„ T€j3ƒMæQ€Ñž†…$3ðá“ Üøfq/n]ÀÉÛGl1ö¡Ú ] biÃW@Á§/àA|Ü Öx^*BíjG`¹AÁp:@Ð[tÈs5Ö*–ÀãÕ<8¡ÂK€o«ƒyد€Ü›Ýl6¬á³¥ ŒÀkõƒ[ĽPÔýŸ´4¿!£ì Àgôƒ;¼¹ðÌÕ†]°2¨Á“R ‰`cÎ$±àĈ†t<ó!æ "8x¡CÁ†ñ î¬nX;=¡Ø+±¸uC¥˜ €ç‹5P9lÉž>qsÇèpà†øÄ7aº0 Ám‹Ãh!P×÷†·ˆ5w©È =ØiqÃGB`ÏÐw5Ba©ê8~çƒóêƒ0ûEÓ„>gñ]|Ø{|ƒØ’¦àôV›¨<¡áãM Xwåûª¿ í`¬:ÆáÔ\’xtÃÅ å3!d8Ì¡Ä&p C|bÂ@Ý$†àð6ÏAµgØ„½D"Ê ûÁý1DAUÿ9è^ÄÖ "‘'ú0?œaûÊ0}çCë„<àùÀ`=Ç¡ì{Spz+Í>L@ñh‚¨;Ùáܩըv#íhJpér‡C`9ß¡Í"a@v#îø"¤ÁQˆœLD°b#ÚÀˆÄ=¾!Óa ºfÈCB`¤ð„·"f õË/´A?"3¨ DL °ÿk‡óð?aù¹}DÃæp ÷´¨=iáé[<yoƒÇ”0 ÷¨¸ôGÿB>jæÐŽÒ„sB#'ˆÒüFbÂ1m}X‹€„Xä"«wœ°D°b#Ú0ˆD<ö!Èq od Báâ4š„_„ ×ÑÑ&T@ô"{€}Ääþ3ê,@Â"UÁ¡ˆ”¨D¢"$ø&ø‰0¼ISâI;ø‘u„ˆ($&± mˆüdGªâ;•Ï`ŽÄlÐ#JarÄ$Eèâ-l[ÀŠm„O„"\á‡üD‚ᆘD1 !lA LœB/ÂÒ\@š~ÐÐ&o2°‰DLH"`˜÷x—^·ž%£ñ,>‰[PJ¨‚S›Ž”ÄœÜ$ÊÁ%h‰$h‰íO9Bx:µP@Dæâ'8(‰º M£kVMhšD̶&I‘1k‰…Kí‚]ªà–İ–%h‘*c‰KLJ""OiH’Ó„’‚${"÷‰´HMb@Ûù•+„§š)K°ŠWDRˆ"’ʉУꅘ(ËÁE– &pPþb†c&˜ ÐÅ4'ý?‰ò|O^ÂyM»x„è@'$Q8A‰»hM£k$KšÎÜ&ja2ɉ’ÈLz"b×è˜?À%ò/,‰uàK’âZîÊð–D¯j%mA+‰áLNë"v_¬¸'DçF'-±8ð‰ÃœNâo p›R„Ø &³Ñ5G ¦´M‚h3 ™dÄÉd&@11‘ ‰hL/"`±uÀ«PEW”*¥1Tn œÌT·‚¤,¨¨O…?\)ááN:Šk”S*¢—ű¨¥#%Æ)¡GÕ 7 Q„Š}F ¡ö…$(aB›Š¼Pn¢‚”` 4„ÿ°'ñ? ôpOŠ‚{AÓž_Äñ<'x±;U ׈N "t9šÀœªDã'q8 ½\MÕmÇhÀ› Å4)’1L! ]èRЕŸ§€¤ý…&\)$ÑH©ŠB,QõBްo@£;…(´QE%Š%DQ"‡4¨¡sE ¦(=‘A| \P#¢€nühŸ·„ûú'ÓQ>6‰î0O[¢yüÊhž!DïH,Ia‹ X ‚¾Õêð®ùÅtà+Œq[¨ ×V‰"²ÒŠ«ì…\*ÆUh ¥Tö¦¶/p©I…HŠ*7ÑQN †ìTb úP§šE:Þ)ÌMïŠk”SC¢™#Â¥Þ…-)XaJ_ OR\b’ˆ@¤ ÅŒ(äÁF© 2ŒQxB‹Q¢Z…v(}¡C•Š(P³b„Ó ÐÅg+-!XøŠÄ¤V ¯™uÀ«uÅZ*ÁQU¦ŠªU1b¨Ü?Ø©ÀELr*UR/ ŽXTS‚¡»˜§þE>0)åNžŠqpSr‚šͦ/Å/¼)rñK3ŠV|Rž”›¤ŸÅ# )HŠ>DQÖÛïÓÀ¶F…¯F-dQjg MÀZ?"ЂxX³e—Ö,§Adr ´X»¢Äf°°Q…€Ê+øA_jŠ÷ÐW¨¢¼dÜ®®Ås´+’±\% ݤVÔ"µ§¦ø­Åf:+&áXÆŠÂLUùb®Ño€«JX\*´ÑUBЦ,UB§É6x©{…J*A1Q² ŠpT7b¡% §åE=h)àQNžŠqÔSxšþÒx¦[…›ð,Ñqf4 .Y[É÷Ix²Å ,mqbî‹TXŒÂÃlè°wö,1_© ú(Wµ"¼¯à®Á…tJ+•Ñ\1ŠÞV×BµÀ¦ø­Åf:+%QX­ŠÁ„Uó"®Ño€«PEXò*¼¡UtŠ©LU.B¨ª@ ©ÌÅM*[ÁR†‹Ç ^Bïm¨»ÅÕÀ.–‘t‹šP\£¢ã¿@¸4E¾‚-Ü¡n‹mÈ[R"ÙûÈжů-l!k ‹TdZBÓ‰–д„…¢0-‘gÄ ;Y¿Íbx²Û…•,œQdr pXçbÆZ,±&‡n,.ñ`ú‹PX ‚¿RôP¯jEy^+¾q]vŠè”W.¸•¿ø­Ç…m+ZqZˆŠÑ$Vm²ÒP¬VÅa&.€±s® šP\¼¢å!à¸ãEÅX.ÑpŽ ´[ô¢ÞÄð¨·M¹ -¸±ma‹hP[#BØk¼PµªE«ô-Pj(‹MÀZNÂÑdžh³ûžH,â¡f½‹2hYtÊØO¸²?Ej,s±c ‹¬X™BÄ4˜°¯Eƒ¸,a`? þØWàâ¾?íH¯2xd0•ƒý p`§ÃàÀIÿZ/äñ~_‹í _=Bøs·à½[é¬/?Qy¯‹Ê\^6Âñø»äÝ^.àvœ ±\]uêÇPÀºMÅÑ.|so‹˜\\¦Â䇸¸±EÄ,.Áp6‹~0[ÕbÝãè·E¶²-§lØ cȹÐÅÌö.[1rŽ‹‘T\wââ÷¸fEÂ.±oÒ‹{t[ÈâÝð`Ç]†9\1Á¨ j c>C*ÃØÅìÆ.1cÁŠÓ Sb…ãN”øÄuÆ"1‡Ì ;¤aÁ YdˆÂø†40£‘„Å "¤`ÿC3PÁh† P0?‘Œ ¤`1§ÿÀ¿¿…üž/Öá~F‹ï_YbúÉØ¾Eï$/kzÛ‹Ó¼^~¢ó_•€¼zâ¤/¡wú ¼°]ÒÂíÎiÀ»×´.±!u> ¦Ð]#Ã6¨«ÍFe€3!˜ º˜e¬#,[hÊ©FSì2’á”L Ÿ@dçC&r.É>ÆHÊ29Ñ‘w ˆ˜d2 ÈÇÔF=1Ý¡Ž‰ qŒcpCìÐXÆW1(1|Á‹ŽŒXðb±£¬ŸèÄÍF$¨1Qˆb @Ta情mèÃ0Æ(0³1…) &ŒaCú8ÈÁŽ 0Hñ㌠˜`@£=Άo¨3pÁ›; Ö¸f£4PÐ̼†d¸3A˜ ÀØeðã.¿qHËXFYd2¾¡•Œ©Ìe;£(üC0ÉáFMz2bq’£ ’Àdyã# øÈjFA2¨ŒyÀc¸#àáˆÆÔ51‘Œ| a$bé㇮ÀÅ=Æ(^4Ë¡¥Ç )$icG¦2@ÑfFŠ4C±¡Ò thHãAHÐF3ì1ŸŒõ”g™ã<ۈΪFt&3“!œN ßPfçÃ6v­pÍ?Æhn3:™` Ȩf)#0š~ËÈÆ\¶2Ù1–r ° em£*ŒNèÊ?Pš2xQ“R —Ôd¢ƒ$e Ô€¢Ô5 !¨=tiØãMÿkHÓ(F˜H4µÁ¥c 'øi-H @PÑІ&4\±¢š´hzãCÐf‚4ŸÅŒû gƒ=LäèÎû†v3§qœË ãœgã7p´xÍ~Fj03E™ÐŒËfB#d*ðØnFÀ¶5ña®èt$kŽc[Ýب֦³ 5“¡¬9 _Ôjå£V—®xÕNF¨â5=±©‰Iôj6£Q‚¸ÓðFò4䡦Á 3°i„ƒKŽV0Ò†’Ð4ˆq£ø@hÏCE™(ц‡4*± ñ4h ƒ?½LHÚ6†Ðˆ6yQ³˜Ül·Cdò"àØåÆ.6$ñ°ÜƒÄl c_¬ø°×“†»p5Ï®- nHk_ƒZ4Ì(Ö/F°N5uñ«dW jªCTŠŸ ÔÄÆ¤ú5Á¨ƒ @øiîÃNÇoøÓTïd7haºø Õn•csü›0ܧ†äB7‘¸aÀ´mì£nèqÛb†Ù„6ÄQµ¾«œmCãi‰FÚ ÆÎø6na³ •¼l”ãdHجÆÃÖ6Q°Fkåã^gíÀ×<¸´5·‘­qhp1ãPßíþ<7æñ¾ìô¨o’ƒ{åÚxÞ¨ô7”!¼bßônìãvŸ±ÝV†éˆ7?Á¹³ ÊxnAqY†ÛþÆÞ˜6èA¶÷ ´˜m’kÈYÚš†Óv61´.ðlÜÃŒ\(ⵇ€8™ÄŠ!0pùã‡3ˆávÇ X8IaÁç àpQ#ó Xà+†ÿÌ7õ¿D ÷Èo¥C|­ß(ÞÓÆõ7Ÿ¼”âLnùcw² Ýo†éì7F¹¿ŽZœr¿•b¦`åG'9-É!FLr£N}ÀãÂGæ8ڱƗ1˜qz‹!TXâpÇZ8‰qÃçŽäpÎ#…Û)`áœ80aÁ8 p"C€Jý èLÇA9þáÏŸŽ{s¿C}å¨ç6à9­¡Í"fTsã˜P½Ð弇,ê9ZÑÊ‹ŽQ1ð=ø{íÞ¹ñà÷cG¹î=Äíål {PÃÙ×Êö*ǯø=vaë[X„z®Cí‘h ûמ>²õQ§Ð}.ãèá@ÈùçÍÚ>gòà”¬|ƒãÿ¸ø®‡Ãä>ÁüYàØ~ðã÷ ³ ýqGê?I‘úÍ´~^ò(XüEGàÌ>ý÷º}¾£í_ 9Ö¶@"Â×€cÿíùðÿ°Gü$?ÙQþsñ¤t#û$ÔpþwÇò’@é2 6D m ]ñÐH$@ŸÂ¦"ÜD‹ 8q—È Ä@Q¢ ódàƒ„% ½AĈ,ÆA^b ›Rè‚dŽ —Á’H#˜AB‘’”„~Ä#y!Q“HCI¥K4“óŸ4$õ¹'‚ TÞJ ²Zî; ˆ°)¶©Ú@p€;  ¤„È@Æ@d5 4lêw€Èf€5  p¼^À»€_€2ˆ Œ¶Ûô€Q@(èà Ü”€Ô€p 9а°Ø–ƒÏšQ@) à hÜ ¥€Øp€;`Ødƒ΀í`x< `÷ŠXÀ¯]À1pH H†Óí€|PaP28È E¶ƒk¾€ás :fLT,¢€R +ÀL þ@R¬€áp€:`Ô>NF4À¢€T`+À¨ ¸ ” Î h€5@X5ƒµ@}DÀ"hÀ ®¼jÀ»€d2à Hþ îA`ˆpGØ%€ˆ (F¼j€»€_ð0p| ~eEA° às Äõ&@–Q@*°ä ¸ ˜ÀÔ€n°:°@h˜~X8€¢€SØ+ ‚ iÜî~ Ä`e84€F&ÝÀq`>€ ˜0 `òŠ^³@]À1P Hÿ¶ô€‰€Gà%L„ 9¯Á^²ÀZ .  €x]AÁ@ë°à €@„Ô€ú‰€Hà%€P |xì|€Èj@8@° 4J‚=+Àœ0P)0 £xÔyÇpi(6°ZÇK¯_€28Ð&+ÀšQ@+À @RÂëà{ðAx"`ø ]Ó‚uE£ T`,$œ ¦ªzÍ`î À x¼Ê€p€=`˜0 0ÏŠ^º ^ð2LÖ‚Á€H¨%„ˆ ,‚¼y`Ê€j7èèØ£}K' à ð@ ÂõÀ}@DÀ%€\ (xîwÀÈj70Œç&€“àM(„f —?ƒLµ€ás ;t¸þ,–W€+À €è–Op( à pÜ ¥Õp€;`@L]R‚K:`©0[H0È´ HÖƒA΀ð  ð@Ìô,ŸW€,° (è–Q@(ðà ‚ç ©Ô€p€:È4°KylP ´^à1œx ¿~Ï€À @è@,¯W€2Jè&#€–Q@)8à ¬Ü ¥€Ô€p€9Hà·Ñ‚Ÿb€¹Ð`¨0¬ 詃|AÓ À hàèX.€¯Y@2HÐè&#@–Q@)èà °' Ÿ€Ô€p€:¸@ †'‚ºÁi »€]À/ÀÒ ä°‚‚0`î൰ ôÿ–P`+À €„Ý€ú‰€E°%€P (xÈg`5  F ‚‚AQ€¯Z -ܨ ·19Á° á`‹˜NXè€}K&È F.Âó}A0"hÀ š¼t@»€d5  ØvpE@¨ÀW€-P¨ Tâ ž Úp [ô̱K2`¸Ä°¥gÇ@d@<€¨ ÔL@,¢€W€.àl ж÷`–Nh(° ð…‚ÍqÀÁÀd(4,P± Uƒ¡Ãt@ð Ø@èôú”ÀM@+À ”¸€ú‰€I€%ÐP ðÜ ¶Àï }D8$0$ ÷£Á^¯Z / Z ¬‰hùA€ `L´ïÜ€}BÀ%€à øåÈì@}DÀ%hP Jxú§@èÀ}AÈ"”€ Ä–ÁQ€¯Z@.° |`ƒPÁ·á& ‹(ˆZ‰Ð€d>`@ð ,ò ¶€á|àA`$ P Dxî¤âyA "`ø WÌrAE¨ÀW˜.`² E<±à s  Ò,˜€d2   `¼ŽÈp€B°%€L `€î“Üàtp=H Hö¦~X8€¢ÐWh-P– @8©Ùàp€8@¶ ‹d:%€À `@$ô–W€2H ˜±`»€d5Àlç‚ ÁÀÀK2<Š uÁ¬ÀËp=ì+ø¯Ù„ˆA·Í€h`5ðà¿•ƒ¬B Á}p½HÉ85†ª”ìÂ"ÃAH‰ÈJ$à‘Œ Cl¡W€s`4ÌÔ…\v-€þ0„¼Ë•€K2   `x&Ë€ú@‰€K( à 8Ü ©Õp€;0lR+ úǪcöA‘à¸\]¨3¦ ÷™B“áX`‹¬Ä](¹\áÄp´èQà#d~Ô»ˆ¡E ¡D”$Œ¬‚èaA P·è€`_NA8jÈØew’)À¢(dŽ7è0VHå뀺øXH>~#]àMƒŠØ}„8fØ …Z‚æáz¼b<&NÁG2À `@ª[€Èq >€"`D `—^»€°P·b0D^)s¯ŽŽˆCäpÒ)!{È9PY“‡ÆsxÒ-À×ÀùþR<«•nÄx¡ÁQZ¡”|œi¶Kd™bÍÁe‘”¢€<ŒC 0†yÄ Õ°Ú°NÐ/0CY 0F˜ÑÂðø${>’!îUkâ®áñáý„·|)ÜCMÄûâ&Q‚WÃz¤_0àY!LM(¢µROíf¢:‚\Çûâlñ& „«ÛR ëÅFáŒËhTÒqæ(É€7¡1 šÈLŸ !.(‡„J¨1ÁÌ„˜Jsb°åHƒìy"yK‰Ìöã1ÒøSúö1§{©GÆ£Y2>iEDƒVA7Õ—‚Dã+¦Ï*§fóÛîËHˆ¶â÷¹ÀàRÝé}žJá¦RåçúûªÉÜ’óëuÕÁmŸ”¯FÌ”²"oCV›M=(Krd#Ûµæ4Ïd†£ÇÜê;Q-ú…<Ãè€í™|­LŠd=TI%;uõ,ŽM-!¬”×IØÁPqˆ6ú)—å@§à‹àg؈ ºr_õp×C­^/­Düó¾`=$&ŽfÃÁÝ¡ÎrL³Ä=ß6æ÷} ÅŽ£ÅåiÏŒEÚ œÕ¼3"£0‹ó:9/˜A? £Woé×#«¢)w2¦‡-TM§c†AhT‚?„8°O’¥sRK±°Ò0a¦2%€Àð  `þ¼^¨@Wà+à``ª¨@l2Ñ/xqÔqèò7‹¥¥ûAŸŠ¿Í5¦·r‚x©ÔV Æ*àLw9¸ œX/´È{’„)KÇ n»óŸOðH]ók5ÐZ÷ðz¨JÖ¥É·Ø ‰åÖßÀrÎ?k&.ßø LoÍêÒç\m$žÇ±†|Ø£òwý f\_.•—,ÇŸsyh Ô –›ÃÞIý$Ó'* - €Ö2HÖ¡pÓ ð–›´0)ïí˜ÇNŸ¢&P¯–[Ž)»œäçovº PÎèðìβ¸)ßn»FŸ¨ÍìÆë˹™ñ[1©ŸÞÁ5a2.ÀsKn…°(톉AÀ›0,h<+iÀPµ0-·ŽÖÖ-ìb4¦rrÌú’n½Nõ¨(e(ÀàØØ4úÀP@+À"x#xŠ Tœ»@rp[ûáÒ*Á£e¹6tZ‡ çvƒ’õA†2Þ¹~|C_¢4s˜ØÅ®ô5m¶ßÜ”ר!ü·¦HË2°­Ë¬œµ-«ço£¹Ý¨2àI}ÊÉ!g÷Pº×º‘9ÁÞ×7i=¹ßÄ/™¦b£8b aèDtª`«¸Z 9Ú]ñ~;¹Ã]Cn?7û¤Ñ°>ÏÆe©²iÛÏA(¶ô-¦5'ÿ†É<ò 1 Vö ¾rë+œJÎk&Bð·˜÷ëÇᨬåzp‰´íÛ¯,ˆ€Ü¸Ò" ê³dK€2@  `Æ ‰ê…&!±ˆ´¸sÌZŽCû1mÞ}ŽP·ŒëqËžãÜxÄ:ÙZÊÍ)&ñCëÒ*7R‚þGàßûÇJãÇ•¹Êâ@ªAr¦hq„8+Ëë–ôç¥h‚75Úº­¿‹´°T Z ŽxGvâèa;(œ8r¾ü)›ˆAK¤°wçÛÛ½A÷Tøe^˜­;¦3ƒÕ ó’„À¿Û¹ .~åÖ~WZ;«'[@ïV6u*1¾²1™%•1Gz$Å!1CñaÚâ1l"²½Ùö Q4"ûlæ’j 3DgTtPÊfõ„.ËKS¥<Éb Ñ9¸ŒØF@m‰<ÀDÝïΡKý¼ë#|>^{ Ÿ^5Φ]Y0®åSëèw“8ûÔIþ 8åžÇÅÓàÞÈRÓíä–ØÀd(¶/›@måg€›˜A¡'g‡ªTR<éÄ“WÜì¥2dþH„´@Ÿkï3§Ì\Ú"­„ó.†W ¦cI”}BNžƒÕ§VÑ ØGóá¥å\ðþtù8Ù]ŠnŠ˜ ¼Èü¸ÌèS¨ª;–WË ™w|½ÂwÿFuœíDoŸ;oG=cƒQ´ŒÔËqЏš¯ï&ÆXj³@"¥QÛÌ‚gs‘tu* Ù xâº6[j DæX[v!`Ë=j$-ŒUkéå”Ã<ú^ƒù>ê ÖHøáïǽãÁÍ ØncP±4–Õ ¦…„Z¥å@š&«”Õ¤k²²Êi0<[ž7—tø¶ú˜V o†R²½HÚ ´S-V&²i(îÔQ:hý§qæµBÙ ô%Ñ’åY8©Ò]P•ãÉ­çõ£ÒMÖ¦á0jb1ãÔ*ε§âÕáf̸–®ÄàO>'hû¥e¨¾¸~Tc)=’‘jZ…pZßžo##ƒÒAÌ]2ì*%~‚„y!¾ŠÑAL#s“þKîúÃÌ¡ú#÷¶^©ÒI¦4@r-€÷H•)`@Odæ%1Ç×ËFéj*†2Fþ¡KŽ®çÔõ¦Äê*;+–x÷®‘M&dCIÌ„ÎäŸÕ‡¹º$X䊙½ÙµÔÆŒacA©<£]Ums4M©”ÔHaì?䙡Vª DbÕ©¼Hôúk„-{¢É IÅjb½yPd–øV "Œçˆ…±Ø˜öÀ“¬Bé/$ … 8 <(µC‘òO`zC–,ßf÷4É ¶e+œ¹%¹-±p‹¶Vb ”¬¤e()ÑVw¤Ob’¤“D™%1/‰ÈQLfé‘V!‰*A²‘8D°'0, v eÀxéàçP<1ôhŠäœ'\"a$é¾RÞÀ×cÀÙF†2Ñ“ ¸hCcpñH ER”¬ÌEi3„“I"ot¼°UÛF".qe ðW‚Ë(ÃF~7±à<ŒäÇ)hÌ:j˜ NZ£²ÃЏàP ‰’§L{‹$RÂsà•„º'1J‹ _C;(õÈfIbྦ|8qêšÄÚ©éiL%hk€–É^r4Û`”ÄL ü‡èA"&x …~0Q¨Žœ€äl&áU ¨fyžAêM:›Ö†Â&†8 á0‰Üš§R%± ‡œ8!ª jC€Q 0Q¢×8àÄDÂ_à·¦J7àPXŽЩµhl%h«„3ªJ&y‚4pm`ªH,aƒ pxÄ@&AXŒl#Å!‰)ÊHZ³›Tï('FúhÔâ´¶4•Ä3‚l]e^!€æ4+1 B © 7ý ¥Ô4‘Ø|’Å- ŒÍ wt£•5j~Zãšxä'©AÆ4 ¨\nrƒ0 ÙøKÄàðˆ°O¢×ÀèH$HÒˆtƆÓ;ò ‘à›µI-ÍŒM\s+ß!AAœ+¡`1JàH‚Ô Pbš!¡7 $e£™ i"Ê Z›pñHBH2uZ¹FC6Ò/¬†¸€CF0ˆƒh ‹@ô fü'!yƒÄÊ,!”Ž\ÄŽ(ÁjÌŽnÃΡe$ òV‚î™dÛWc¿š"ƒ`j¦°h€``@Ü 0_ƒÌ%Áp È‚DÂ,!•p‚Ę))n̶pCÜ!Ý(JWÒúYÆÞ×À¤T# °V ÐLÄ €BðÀÊ \°%!k ¸‚DÈ,q™˜„dª)ÁtLêr#í¢u-ªEYcÚ@âç¥AÐHAÎ XF¤  60à.À`Z¨%m ÐƒÄØ-! ܆äÀ*‘{Í,tt£%3ª|[;ÚÈçwÌC" Põ€aT ðP¼ @1@Tð/ í `]†!±= ŒjcÎ"‰7Jæa3_ÍhþN¢®RÊfØ;!ýЇ…¸jŒ0Wà À/à@0`|º MBþŠ4_#mQ Y£´ôÈiIÒ…Uø¿y8ä0M‡L "<`µ„D ˆÀ 6àL @#„à¡<¦ò$ èGžà-€ÐÀ0 œÀÎpUƒN !2 š Q8D¸'JJÇYúîØpËÆ¡7-Ë~…œT£í)餚,܇A<úPʌ즑BŠˆWêÝ×èÇF}6Án›~!óÙ!K®s4L 8=^€&°à P€H2L! ˆhƒö%Ñd èvä=&aEÊÊ[S™tÖ§;qóPb‰t¥­; JUòÍ×dÃY´á¸MvëÜ  X¢G¢Qó=Ÿm(F<5¡Ìdƒd`%A<Š|XÂïÈÑ&â9ñçOú†$d¤Í3êT¾Vä¾ö7³Å¯Nt“Èßs(yFNGáœ}iò€t‚¤ M€@(@€Ð `}x:Â^À舸R¢Êφö;¡ýüÄÎ(ÉYËn`£/šÜân>j Q,ôµ'aI ¾Y¢ëØ\Êöš6ñÉ.Ø{l!?èñJ*fÓå¤ÅTú+!u kãñHÌJÂ{ ²Eã1¹£MÌtР ÈÍIªh”"¨E}-Ñ~ pg›^\é'‘¾Þ Pe½8ªSֵአ€9R€)€àà ÀÀMH"a[ h„l&N HacFðˆDrF“X¤%n-é„LÌkãŒÔúÈ7DâARàå'+ g ´a›-pÛ÷%»uíð… OãÃ(¹šÏ–“5P°u³ÃQ PQ½ Ë†Ô:ô¬Ž¸( SK8^ã š\ÞK=RÐÞdŸ¦µCjŒX ßðdž}6ÁN—yCï «ÊHâ\3¢=ëaf뙆&éx @¼@¸@B"|°¡œ1á¹(„Ć'QT‹|cRxóÈ EBMŒ¥Å|.a‡Läl£’žü(BE:Døž¥-«9h«Àaã/š„ܧ*»‘îð…LQãÓ)ŸO²”W°¥µ¯6`®5Ö Œ„È)af g£zÀþtGòc”D«…«/á” Np­å(~G"S“r¢•M¬EqLd#AÛágR<ÙùPb‡ôgä‡.ÉÌÑ. 5»³ÍÏ0€ÄØ`¼€€¬`  ~€:‚Ш…à4ʰ‰¨(a]‹ÈeCeøˆFFbV“Ô¨Ž.ñŒ mÛPþhUEÊH“Ÿµ6+jëÐbs3Z¢Ý—2»Ññ…ÌU£ñ)é¤Oî•õc±¸ïS‡,¡ƒ (RÜP߇¨BâFÀ«Â1‘© @Y  ýØNºðÒ‡0> hžÅX-ñ‰,pƒ¾ÙÈøLƒ•@³eè1Á¢¾ssÈŸµ ˆ®H LÂ:{ƒ `Ð80x  _àì0HB€Á†œ9¡õø’„ð*qm 4hc{¨üˆ^GY“à§å‰.©‰ ìlƒàúc¨ŒÃÀ°°… &ap@æpƒlÚ;ú0–$,ሠXtî!ñ% ÜT¢×Hφê:Ñô˜ŒÄ§'YLŠô\C ŒÖ§ ˜^À ›„œ#A(?ô~„"a% àUBäÙX?‚#hžL-Q„ ìn#¨ˆºIâp”­¥¸0!”„´ °ó‡ 5a ÈX¤ ›¬$aàGBFàž<,Á èoC¾ 1 HO‚¨¸ÁÆt6ñÓxƒDW$±6 :V2Õ—ØDbqCX0À¼,!T XQ‚¢,+g àdÃ\°ö8FBZ«…¶0¡ž ¼tÃÝ ¹É$M"Š•h´BèxC φ80!v pZB΀¶…Ü0• PpƒºÁüL‚Œ°¸† 4¹Žœ{Ä"‰#ÉœQªh@è˜pb@͆P2 ˜fCH â‡h>bp“Ää)‘`‹°cGÈêÇÂA‚)’<™¥ *ˆˆ„`ò‡\9A ànÃväPì@yÃÔðüˆBÂ*–Äò)‘]‹€a2èãz?‘p’¤Ò¸‘j"ˆdBÂЈDX#¡%‰€O“ˆ´Eê1±¡¸sCÊÙ ÈÊIÒk‰¼L"Vh’Œ$&‰TL"p¦b,ÁuŒ4f#Wë²@’À•àè£Å 'á= äO‚‚`¦EP+Ái °a#(PÛÇ,<öh‰„%¡;€+qY ¸Uâ³б¦.1| 2” ÄgÃN݇:Áå°‚8##IvNÁ±xkC[øÙ†à7ÑÇŽˆw#ÓÈbEr?¤šÅøæ‡09‘ÏŽ˜vCÀùÇüA’‘\D &I= D=‘ìl|#걈0BÒ"‘„¡&); *T lƒä%!H˜F;@•„Ì'qDŠtVB0„Œ¤m#¹!‰,J²apŸE)éX‹JrT’¸–äÃ&‰9 ðQ••$­eŽ-©¨E¤”Ú£UŠÕ`bŒ´”RÂ_Lé½;¨¤Ôò«²7ùbDZQêFIÉY/¦t䟔Z›™óö•Õ»o/rAHM"TZQòGJ)y6'Õ © ¼A²fcÏjMU¬=¹½‡€õCúrCÈ#ôœ˜S²ŒTËo¯öBÑ[Sw/ŠÈË$æñ¤UNÂÛƒlpYÖ< øcü‚¢"Fi.'U «VjïbLÝ®¸W\ö`,2‹ò:_NúAT¬)Œ2æˆ×›£ˆrΑØ<¤û d0‹’*`Oªq^®ÊÚ›|tÏIÿÂØ¹#%äí¤L±–ÒòaÌ© 5&¼Ù›“‚sÙì@]¤ÔØ£zÓ_L…¥7WBó_Ü*‹R.^NêEUIÙD)åt¶—ÓcŒ‰•3fØœ¨zG©u@*E”¼XÓDnu忨U¤d¾ž4”Š‘²JLÊ!V,åÀ¹2ì_LA—5&øëŸ$‹ÒŠuSk u±V‚ÛœãÊP®.Èù…=‡ˆõ¤ Œ²‚TÊ©S*e`°–âþe YÆLÊA]®v*Ñ£¤zÐ"ÆùI6Eš¢PK ÁL,Æ Öc˜töBˆá2)eœÃc’{¢=LÊE_.¶4Ò[Ó«{ÐF"Ç™^#Ä }!à=%Å(²ceaÊ=Èi&(%|À#Ž{Pª>M*Ua¯BÔ\µ}r(HYvÃHb áx0†pÞ„0‘‚˜Y ¨:ˆ'el¿#|ÊCMêqe/¦TÖÜkÀ~¦/I9•-¦ˆZ !(#„p’¸a aØA‰@,Æ@á„dœ•²þiNaõCé);ªeªÁY³gsO:CxÛ(åôÇ€ìƒ d AÐB aX1%Hƒ茓²¼a YÓ?¨¡*¨%b¸X“CnÎ¥îÁ(‘¥¤Ð—ÓdƒPÁ!`¤ƒ°ŽC(x˜` ÑøGJe1†Ðî „h˜”‚À^ …©¸gtûá$Z’"qÍ)Ú €H p: A€> Á€;ÑN1$Å(¸s€z²CN*h0fÙœËÍ€Pâ7JY¹9Àà $ ,81‘&+†`ì!ÄÄ«—ãLsÏÒ'JŠ Y.6(ћ˭|P^&ÈIw<çð@0   Á)†¡'EÐÙDp –smÏ GI¡K¬uðÊZãxæFY74èX€€ÀxàÜ%ðî$E`ÊD,—•BôhŽa÷DIA?*ÕºÃÚ ttïrÄxû-ge$}   €À`ƒ@ŒÈŠƒoòBQ Q‘7'#¤Î¥–2öaì¡ µ¦æã];Á{oÖB¸dÄÏt‘ÓõF®Öšë`lqš´æÎà\Ó´yϦÁès#|¦˜3v|ÑZcSkP€$°"Á86 p9‘B/À÷"ä䮘#Rtâ'GÉQ5¨%2¬Bà_ E•46¸Ý\{¨xOqûÁYã}“2ÒhNºHê\¯&ŒÛœ³À}JF©-0§…©•òÖ]̳v ÚœvïAõÀX?¢¬p‘’žaÍùõE©•O­v 0"ÀÈ!Àô'† ð#…(½Љ"¨^ Yž6'ìžÔ†jCK)ÁC©åj³"übŒµ£5öòäcÅ{ïæ Âè“#ü›–ÓJvКKPªõz²ÅÐÙ“XoΙç?ˆE#Ĥšú•U‹m¯&ÈÙãUmÎл·¨û D#‡±b9ÈéU1§ ý£4Ö¨ÖÛi( À|‚ÀnÂÈn‚dWŒQº=ˆ‘0*„¹3,j ÙϨa¼QrPK©­áÇ©T‹zJÍ(Ê–mL`¦ÜÊ e&r¡W°é»vp»ýž_o^—Æsî}è‹$F)#vúÉ#4¦Â_E6žpQ)rU5*ÙeŒkpq¿@ãs1:kœ.ÿç˜{Ø)ñØûÇBd¡q刑dZú8M"”[Kñ&èSñ*8e=¯9_ú´7\1/$ØlMFFÃ3A¡lÔ‡l"¶û›ïî/§3#¦áÙàðy™½ß$Ç哸Šu:„`C¢†­©„§’d:b¡uRê*ÕÓë0µ»"îé€Ä;d0³Ú EVÁ;ou¿ãs=:v¤O›kÙØïªxX<®Á¤§ýÊúŽI¥§a ù…"£IZè² [H®ÅïL<Ö@s0é –ÔCl¶ûÛòÎ2Ç5c¨MÚºð•yÜXN0'%¡×ð’{a?i ¬Ú(²ô}âQÍ2^žQm©íÕ˯ÂêU~8Ã’cô³ š E–ÂpY¿žãnszº˜]¶oW Ù«èÕZk"6*‰Ng:#¹AìPþ¸ƒåDT#b’R)z傇Mœ«ÎX[­dWIkï6멘nÐojB6‹ÎCaÕŽî x²*Ì}¦?C&1™°Ñrk¹7œÑNôgÅä ŠÕPþK&&ê b•Z’Áj¸º0_x0ãLÇF…«Se±¼ÜÅpE¹œùN´7uzxºŠ\Ö.œ—¤ÌÆ838q¬ˆßdtŠ<ÞÆP’h¢¤~Ua5È fRá*Ä«U•ÒŠü…‡ªÈdff4GZ©m•Fê+„EÉŠè^uì;ÍË¥mò®)VÜ­(Xt-˜§ÌX¦v³d Ç€îþ}?A‹¢?ÑÚ©JäÓz€mKT«X0-dWRë÷Ö!ã#iš¬Ñ«jï6|Û¼N‡+;£ýÙSž(o“ÀIÊê€ùJŒ¬Z /v 9&úã­Ñïe ˆ9G'%“IÊõ.R­Yaz¶]‹0Ø™ì—vo»IQ­Ú¡oM8›\ÄN›·iÁN<øKY$T‘ňÛ$|¢Oq4”¢¦V'-Ϙ]Œóæà£9ë­Ô‡ÒG¥“NjÅ1š¯Ebž¶œ]â°/˜±Œ£–uëL}®²Ûro²8ÎÜÞ¨’„Á,ÄBÜ .ʇîd’! Üš\RÏ,q—ØŒÁ¦ÌãIé­r‡ÑG-¥-Ó^ Õ6²²Qd&·l^M°gØÎì²}{P°„ÜYp'9 œû.¶òB‘ €{°:9ÿ Ö £¯á÷y¼“¦P9+x€ ¢æÄc›qéÕàˆ=Gu¥Yvª•>Z¶f ¸m^Ò0ª˜ðL¦…³TQ²œÝbpª¹HŽÆGY³œÚ9ò„ _ô"ûØÖu"@–#“™Š¹…ÔC&±Pçf{A¢EÑúéfôæJ‹áQ⮵Z .hWÚ¬=†Eƒ5Y£ªÖ2m6·Ÿ\M.cWNó®¡Øh7l‹Âv¡D€¯œaæ7;‘o‰°…Wêy”¼Ùìtˆ=ÿ ÏQI‰ô½òxHªªHX-h—_ì&)s'íJÓk¸6ç›ôî9G:ëíÙ†ë‘v«ŸÝ¢ƒÑ@0?("?–‹m£ãÑÂ*6x«Ì^"3‹¼‡×4&/U%n™VOâ)}}kE¹zò-ƒ`Æ e³´-ŒVç{ƒ½É èŽv;{™.èu@š>x³ “†"CªBAÑ`€ÑèzfEê'L•Ë ô¦}óÞèþš†«Fߥ&gŠå> ¶Áfθê_°ÓYìÏ–ŒcWÑ´jÞOq$9ˆ:®Ö—pp<Äb4ÅBi᪑ذØj°>Œ#Ñ ‹(cQ ȸô–Dr£ÿÒÙiÖ‚§-_dµX]e°X¡ìŸuL®üÛ·oä8íœï޲wu»½|A@öÆ(¡Ð×H’\Z7¬ „’’ i¥Àƒ$©³péâ|³B¢ßÒO‰“Ôý—ñX ±Í[²/2˜>ìo6]ÓAi©¦Ù"n¥¸S¦ndsÀ0J–Y°Ñ € øxDOÒ1 Ÿ' ¸…k2û©Ÿ à6x?Ý¡ÌQÊÉSôÞê‰1Pò®dZ®gWÜŒ?æG[6™¤hÖ›mj7»\îk—Sc¸âìÛ´ná„Ã`ö B #Ð!½¢‹9ÆÌÃüBG±H µ c]5Öîrh1ÄSE¡0pž·RªÅVkhEßk™ŒRÈ’e{³S™óÍ6 ëY5°àÚ‹nK·©ÜÎ)×$[¡U×òí¾w¾¼LÞ\KÒuþ A‰šÇBdÕ³™ËÍ ¦—CTq®~Ù`m¼7`[ñîwÓ•ÒBêóv^;¡ž7Õ B2Û (ãBHÁ!§XeÎ<"‡“ƒJÙÅõã=©¿Hï`^C]£vR—ɵu*ŸÁ[¬°¼YÊ-–×#+»õòóɆøÅõd3²²™¤,ööcO™¬*Ø ¢)R™ªUf áõ‡ÚÎñlð¹?]÷¯ŸX!,9¦0“!Í• Í)g¸´kZzm`Ái¸²Þ_p+¸’\†Îb?Ó§5×2í]w¼6Q/CG®»Ýµòú–AU!9HÓt…2Oé. š‰Nà(6Ô}ªp%Oš³±_”²“Z«.×WëÕõÿS ±‰ÒÇWdÛ3™Ì ¶–ÛSÙ®:ÙIm®7Z›íÇk”ÅÎèâuZ;]Èÿ‡ƒÍqêö¢|¾r_kÿa[¢ÜPt*çŒÈÇ1Ä "A!?Œ¯p_Ž3¨›¾ŽÑ£CïÕ ˆƒEþ#ØRX©aÊêr?X¢ÃRä*/uÊé‹*нmâ¹’^¯²˜)Ì<Ö2"m•ìÍMgÌ´wmbæÂƒi™¸ÒÞpp6¸—œ‰b·@ §a×Bíbw¼6QÏCG®Ãݵòú•~^%GÌdò}ÀŠÍG$gÉ‚”ÚÊyõC&¤”SȪ ­Kå˜×pÚ»^×0ØVlRæ<{'ŘΔhg4Áš¦­u–Ë›n»ßp¼8ØÜ©®r÷Gó«ÙîLx¼mÞl¯P·µSàùóžûY~rŽhF ‚XÂÐeî6Ôö}‰äúb·ÉyœË´mC8w&Og½sû2 y °‰ÃF™$ Òz©qTÒ*ui@Ú£{S<*X‰ ñÕJÒ™nº¹ý^M¯Ê˜4ÌBv4ƒ#¹–~Í“gê4ƒšˆ-f–ÃûjI¹2Þ“pF8Ÿ\Žd·A;§Ù×€í{w™¼9žR/Dw¯KÝéòúž~¿jäæãeAÀôçÀwo=¡AÈW$Fò0Õ ’ÙK&MSŠ)ö…z•P¨«:W ,8skdÕÇÂîq|NÀ a1c˜þ,¤¦dã;m¢0ÓKj»5ä[4­»Fí»~±ÃDãr·¹Ò$.¯7e›¹©à2ñÎyº½JžØO…wÏí©ùàþvö›Ç]Œ!ÁXäÐw„?¢"GJEs"ðÑ” ÑPl?7áœÒÙǤÃîBÝ P‡ÿE¯#§’= QTÂm‘<è¡oR3©×UGŠÐÕ~ÊÊyj²¸]M¯K—õL"†$Ë ’¦Ë£fø4 šMMI&µ›cµ¢ÜÓoh¸1ÜVŽIç3³¡5Ô<ëâvÓ»×Þ!O+¢ËØ1ï8ù*}Y¿_·ï󸔔¨ÜW¢-u—Œ4æPãDi¯ØÞìrâ;+pÏ¥( r"ÙH…¥ÒìI§tìJ‚5G¦qT¦«ÕÞ ¥¢úÜ1sX¼E_`0Ou,a&Cs+ šÏNhÂ4ëÚ¹­~–ÏÃoù»úßípð¸ò´Îw§J{¬eÙ¸î™x&¼|žsoTw·+áÑôûŒ~‡¿£ pÈ9 ‡îA`  ˆ5$FbAñ3èŸTRi*­Ëneêc)”ÜÑ(kð7¤ªÎÀç•£å¢E®†¨Dò#IÒ i8´ÚfÍ9pŸ´QR)dU ´upbÃ)g ¶-\b.Õ»ì¦K¹ÉÞf3œZM-Ö§³\5²@Û'n•7ÈÜ"î/ç'šÙÑL'`”H wåfâʉpྀb[2Äš3 €æôs”©×Tò<|h?Ó »Äh•Ôdj>é%ª•ïL{¦ÿÞ*µ&RžTâ­AWþ,ª–« Ô"ô)~üÁïb,±®™!ŒµÆl»?E¤Ô+k"“;ÉCD…"=!8”$K\&y“¬Ê.bª)_,´è]90 ˜Ã ¦’ãba½ØåBuÊ<„ íÈ)d.b#¹.2I %US ©µtò*„±H¦èTØ« æËe¤zÜ™sb¼;_Z0Jqì^†Aó*)™ M!'á %.B§I\²¾[Ë/AKŒ~¦mcN1³ߘrÖ:úžB…çô„2B‹¯Ge¤rÒ› |äÖRv¹A0£tS*ByJè%‰ZÏ9lʸõ]¾¯|X ,,¦)IW¥‚P™'¶ÕÊ ¥â’yN̪~W,|–Æ‹«åþC!”$ÏjjŒ6°NgçcÃÊñþš‚XB´"Qo甋zQÁ.¼šLNš([ªZµC‚¬‘[²°†Y“-l— ¬uêþ¼§:TM* ™Šúå•RØñt¾@aS1èæ¶ÓqyÃØçªv«<É!ï&ô+*!YªŽ@Hœ¥RáÉžtæZ~aDÂ¥"Sôª¦U¨ªþµ”JÔyoFº+Ÿ•ª ×oÒ¼1a²–Z{-ò—X ã¦Ñ•”ÏJj6FÉŽ9§H3¹éèXùÀ»AQ¡`Ñ (´ÄqzD='à–ÈLЧÓçjµ&ÑT:¬ÅW·,€V‘‹p²Ò)iе”[`.R Ïeýã YŒ„Ê$g4šßÍ»gÓ–AÕLðz©>· Ð_h\Dr-™t‘Iî%§Ó. Âä÷†ÁHÀ§TÜ«UÞkõ ixܽF_B0˜KÌN¦=+aœTÑâjø6‡›ÓŽ1ç>ã²Ñã|öä~@[ ÛÐèt\²9m">“âKK¦VÉì ª}M|©]Uý«¤V"†'SáÊ.fN3ÜPM^ÆÌÓv Ã(åúu5;ÁžwÏŒGí¤ ‚ @‰EEõ£§Ò(É@µ:eM8ž¡P¨(úÑj‘å]j¸Ýazfì3ç: Cæ¸ãh¡»$árz:=¥ÏG±3ë1ÿ-x„¸C¡¢rQŒèïDŒŠP½-ž™fN§§”(J<Õ3£­VÞ­ò5Ç›*M½&ôó†ùÉðè¢v<žŠŠÇèäÒ ‘ *‡úE7£=‘í餤\I32œ)O\(QTyÊd•F:­4ÞŽpb8ÔœÁŽ’gc¿©ç÷h}¸?ê €P†Èh„FŠ,Ù,I:%;’ìéžtâò{‘B¸£ÙS(ª2Ufg@³ª¡ÚØðdyÖ=ÃWç(tB¥"‰ÉF #–Ò‰0ô«ª_I4Žœ±O¨bÔ€*fEF¢¬Äïfxï=(žñϬ‡ñ4Ò Õ L†¨D\¢·‘ ¨ó¤‹òO,˜iM]§DÓîj!šMQÜ«I>?ŸvOꨄz HˆDÿ#ÂID’ÚRI-†™M¨'fü*"¥#Ú›9RB€@­ ´ÐŠˆ`=¢%áªBGž$OkIV„¼Êg 7ðž2P1(§œ rJò Ñ ¢†3Cã"VQ`èÌttÚAé$Ì”dK2&ÓRIËör„F8¥VSÀªlÚHƒL¢,iTŽPH¤st©X”»²eµ6è{OÄ(i”wJ]µ@¨d‹€Fu£—Qþ d™ÂSñ-‚˜œM<§ÓÌ*õò‘­M¨‹UO#±’iD™*S,¼˜Lê&çÓ®éõU ŒÕJ^§&T•*Ì@©5„¦zY/°™{M§6Òj¥Ú}L§éTîªô’¥)gô¿"e}5Àœ|O 'ñÔ0ª4e(’›…QŒª¡VC&“3 ¯$ã2w¡>ê¡QV©TÂj}%M2­ÅZ’›VNM'ì õÒ¡K§)Tpª£•ŠêáÅyY?6 íQ)(ïT§ªkõBª§ÍW4­HW|,0(j(Õ¢”ÕM4¨TÈ*ÄÕ•êäµRÆqHL¥iSYª•,ꬥbZ·E^¦°ëYD* •/*¬Åa¶-]â°sXü,ßÕ` Ã…l:»Y``±œY†-$VÂ*îÕbÅáeª´7ZѭÖ1K,õ ŠÕ©mz¸#Z®­¨ýË”E¿ä‘tÖ»³]Î/8—Á  ­2šRB –‰\y(”E;¼¬}.BÈR§Ê©Á_¡-6%ĨÀÍ>\; ×1@á)’e_ô±•–á"ñ*`¬PÐi;&(ðENt¯µ­òì`-ÌWÉ”ÇCR(sàšâfVÊ‹;s"/Î&%\É×™ØãMþ†!ÐÙ*Cÿäï¤h"ÆÎ[ûËß¡‡t2NÆs¤Ód3q²„ÂP°‚ûCvˆy5b¢2i˜PÌŠ‰Ù^ë-‰åäd­S8i M‚¡¹¥8J)Üé ´v„ÇвŠcCˆh{ÝÈ"@ÔLÒŠ)IâÑKD+<Šغë.s¾fÇ 2!°¬7MGPæ?ã¶ìx—ÏFÉîç>Çç4ÿa 6¤j‚ïP|R/BÓ(g-f!û$D؉A;â)ÍE$ÒT¢FCbÇ\ŠÌÉŒ3D¦—°Ø|›³³‰°suޱÝâ<¡®¼øöŸ{ƒú,€ˆ6š :Aé¨L‘ e!£”:̈Ñj&Eˆ¬©"øÒW´O>Jna^+-¿…ôˆÅ¹JælPMáÁÆS9öga¨ð8~#ÝŒ}JÏØâ @¶*u!1D-ø†§ð!3D‡èœeä"Ĥ]nŒ@Ñ™A!Ú&ƒ¥h¬‘"òâb7Ì»¥63Æô¤ãô“µÎxÒÏXyò*?§û53 ™„â„»¸º CÕHˆ ˜"„$V‹mQê2™Fš¨ÛéhRT$OfJ†Ácr.¦æ<ÊÜšFcÀo–MaÔ0;·‡š ÷9Ÿ^ù€±PDZ ÒBV(\á ±!ôôEȉ’Kª,qEæÈÇ}(£j$q¤Ž»Qç1 Š#ÎÄÍà¤?•Íàn`¥LžY£¦6G¦þDåàm3Á²z}Ï’ùÞ@ˆƒ¡$ä.††ñPÿ¢#ÜDî(«=ø£$g,IJ:âG èü z§ò!ÂIêÉó¡TÛ-JútÈ©š,£e@p=Žo¹Ù¾<·ûJŸåD ò‚ûÐŽª3C„H‚éo"‹´X”‹æ•R5…Ghêí‰#ôÔ‚ÐÕ’)ÒFö+D„ŒÖ“(b–\XÁKÌaŽx44FÌ$áT£ºPz’ûH@jˆ*i›!qÔ9*ˆ]0 *E½ˆÅÅT£zôtòCQüRAËH~©½#é$š–˜5pÇt‰ö$†ÄþH­!—7óòhM›1Ä‹:t'ƒüöqt¤J‚;‚RöC‰™"¼_”Œá‘·ª:G¡¨þÁ!$ITt’3ÒU’LIÆé?e(¬Š‰&6C§1‚&;еãgÓ= lù6a×&<±‡È8þ‚ jtN…­ê‚#]E¨¶Qò#^Ôs6ÑÿB±H¨©Å$ñ$¿›Ô“SJ’)WØH,Šg1€p7>G执(…‡XÀS™ÅShüraNÛÙêã?H…Å¡cd;ˆ‰ Sj/£Fˆâ?#ÿÔ†`‘‚’EòK&I®)=í(·¥4$©è•¤ÀZY{KYIp8E–Š<¡Ä8ŽÈ9ñ.;*…ã Ìº›S“™¦x/O‹yÿlAièVE"]X쌌Qºê;ÙHiÑ#u¤Ÿ„™L“ÊR‹RSJ¯ ],s¥§Ô·æ—T’ô²_ÕL©ˆ-2>¦O±à(† é."¦% €·!™TkètM:AùTAQý`"xt]òVQ×Â?âH„)ù%±$çô¢p”îÒ°bXK?énñ.±¥í4Àz˜aÓ2céL É˜ 3‚¦w4ÔÒêDÙHÂÀa¤£è¯j˜õCn8uºO~jeBXH}UÓ£!´r¬ÞR%²I,I¦©B¥)ã%i´²–ÔÒêj_0LI‰u1ì&Qt̪™ÚDi}MOI­U6&ÏtÚ¨›ûSÐT¡ ð‚NXb1rŒ¾axÍÆqß2?dè@¹ ¢Ë„m$§’*‚JÙIò)Ní+’%£º—ÕÓ âcjL¤Išá4¦–ÔÕ@šëSeÒmœMÏ©½98' D㜎S–js%N· ×¼ ¶…ÞaB1µà¡t¸7hǬÁ­"4bH;12MEJb)_=-ô%õtÄB™.“7ªhÍMT °™6½¦ë”ߢœ9Ž‚rœNm‰Ðq:f'U´ì ¦S¸Âw¡OIâ5=´f…xÌa¹P8§þ¼£‘ôŽê”bÒÆâ^†Ldišù4×&ÂÄܸœ“Žs%N‹ÉÕ­;4'tDïüž/ÓÊÂyçONÉë¥=±'¼øFŸ"ÓçB}FO³ ÷•?¨pl‚0¨Û 9ªè¤¡%F%®É<šÎ“}ZsN²IÞY<¦§ªÔ÷jŸ-Óìj~BOÚéý?ô¨ÅT D” ¢›P; %A (&Å0 ¯”RƒPi ¥AǨ;ºuQ·ª/íBW›êA‘Fó¨Ë•2¢äZÊ‹QbJ+Eh(©å¢ŸTRêŠê…¡Pwªµ?ð§â”øŠž¤SÈ*waN½©Ñå9€§ಛ¾SmŠl©Mq)ªe4Ȧ‰ÔÏŠ™¯S/ªeLŠ©å1N¦Õ”ò±{V мýW*ÔµX´ªÜÕU©ÌU!Š¡YSÎ*oÅL°©lÕ(ò¤ŠT~ÊáQw*'Cø¨a‚ ­TªIOº)ðe=^§”Tïú·S­JtNp©ÉE8°§ÞÊ›Skjl©MvÉAXÕÑ &_£²›o'M1õ•1 õþf¼(W'RÛªZ€+6Éd%,;Õ€¯•Ôú¹V÷êÙ‘Z˜«?…ež¬p•…ª¯ÎUÛŠ·ÙVªÄEWª³•Pr©iUê USÀ*nL¤©rU)Ò¤¼TˆjQ¦*-åDʨ ¢¡,T*‚P)ýÕ? §ÏT÷Zž©SÌJxiNï)ÙÅ:¾§G–*ÃDX/Šþé_«ÉåvZ®tÕÃú·VVà ÓýYþ«.µd¬D•Z¯[UЊ·V‚*ÃåW2ªº•Qú©«U Ê¡ÉSôªu¥M ©–.¢¥`T ‘½R*9åFv¨·•2¢.T:*†%P˜ª õA:¨ÕJŸÁSïñ°Þ!*A·Èê›× U§glƒy‡4¯ûuçZºp× *ÚaZ‡«Figƒ¬Ìµ• ²!–4Ä´X`K-_è+äõyö®ëÒº¹4WjÛåZø«Nõgú¬ÄU‘R±ZVj¿ W~ªä5[0«8ÕaÊ«ŸU_J©™Tî*”…Qˆª=ê§DTت™aRúªXJR©0U#z¤ Tw*‰Q…*,%Dਈղܨ[:ë\!j7­uŸ*³]–ZÚÉ}XóK¹b&,+e‚Œ¯öÕôн7W€Šë™\Þ«‹eo\­«®Z´ÓV€j̵Y(«ea–¬U{2®°UÂêµÉVfªÃåWTªÅ•T’ªU/ê¤ETL*ƒOz©×Õ7Ц”TÆ*—QR¾*QåI¤©!lmv‰jPŽÁü¼¹·BâßšâCBeÌjí†0•뎻A—J*åŽ\JË|¥n-—õ­Îµ"–‘¢ÐY«0qe,¬‰uޱZ–Ò•X& ÿ­_S«ØExήÐÕÒ:¹:W jÝ…[=+Yõiœ¬ÿU™ê²}V8êÄIX.ªûå^2«ž•o*­KU—ª°ÍUÚª³ÅU†ª”Nâ©zU!Š¢yT#*}…O©ÈÕ6‚¦nTÄ+í-{\¯'Eݺµ—;:ä$\&kyMmÒ­“%­¢µ(“ÚЇYÒk3EeŒ¬—%ä±›(bçXLk©_ø«í5{`¯*ÝbºªW8jãE[ú«qõlˆ­bU¦³ýVjjÊaXô+•a>¬U{Š®×UË*·)V«*Í…Xʪû[«UTJ¨ýT誗ER"ª.UBäQu†0™òÿ¼W9:ß1[ ;I³fºŒ” ‹f°¬F ¬ÀÞz÷^o‹¿v .…Ë̸¾×:Ý©[g‹cÁke­Ke¥d´7ÖxbÍeYwË(Ídd,u5‹ü±+ÖÚÂBX"‹_w«ÞÕyÔ®üÕØ’º&W*ªáý[×+nålF­Z¥ò´VkÊÊåY«µa̬•~ʯPUÚê¹eVòªÖÅZ«$UaB«ºUkª«ÕUKª£FsbÌVKC"Hcp U‡É0£V jÀ$—âjø/^ËÃavÍ.¥ÕÎä¹'WÊß|[¤ k©l]-kµ©Ž´¾V‰âÏ…Y½Ë1ey¬˜5h±¹V,²ÄmXh‹É`,Å~*¯„Õé’¼FWlŠêQ\Ü+%pd­Ûµâ¶ VªJÒÍZ«7Eeʬ”UŽj±DVªÁWç*õ]Ä«šUp ­U¥J²õV/Å`ØsÁº_ÏûØ‘vúNd¹¾Ö6RV´0ÓôZ1£=Wf§lµñ“A1ýf3|Äþv ¶`£ì~`¯åꆼxušë¬]ë˜qÅ.彤·+Ô’Ø“ZÖ«S]i•­•ŸÐ³˜–g:ËYE #¥cÒ¬gÅŠ¨± ÖòÂX!+u_•«äEzƯ"ÝâºæWD å\P+nvßt›ûfÆk‡ I‘¤š´ VqŠÌOYSS#Ùc½ `á‰u°ßfÔÁ?˜ªý _/ ×ùyk®ûUÙʺ„;ºå\Tkåo$-Ä•´–¶VµRÔøZk‹Gh"¬ì¥šì³–VbÉ“Y õc ,Puˆ °¿V4A„Ç&!ö<å§c<èÿœ¶ Š4oÆ­Ë™´+µâÆ©¸ÒøÚ;;ìf˜ì·¥“Õ²æ8JźX# Wa ìµ€Ž/×óâ½®Wð¼]¼ë­=t]®eõÈ&¸zVþ¢ÝÏ[~KhQl1-j¥ª2´ßVjИYæk7¥fM,¶e”ZïÝx[¢1r·î'…¿A7=†ÓBØZ¸3N‚hÓŒûùœ³ÖVTÉSØþ+šb¸ D†q°‹ö ÄÀM×îBúu^ëËÑ¡xÌ.î•Ø¸º{×<šån\g«„åo¥-×%·4¶|Ö ֪Z¥KN¥i"Dj¨^WwßÛ£àÎxÈnäÝÖh:§)¦âcÛö{t®mZ­‡¬‹5 †’2Ðs™×S4‹eÊ좵‘—±âö2”Å3˜ƒs `aLu€±¯âõò½ü—©Bòi]ü+µMuƒ®‹µÍB¹ Wšà¥[Ù‹tim®­œwž¢ð®]´ËªÁsãîP Ĺ·ñêÆÛ[;Zéj_ .¢63ÜVnàÌ]_&wd/¬rŒ1>†žÂü˜B‹Ò`1Kù]}°¯ˆë°¼ÊW…ºîE]ë§esÜ®[UÇ‚¸{ @Ãïô|u/^9åÍ»úwhBê6Gq%ýa» ¶ÒfÉX×,Z§cMÇhÙ­a3Dæ]€Êe$sìckì\•‰ˆ°ô¶( Ø&kµ_Õ+ï|…/fçȼ]×yŠìë][k£õt½õ7§8ò Ýè±àtè.ráÉY¸Vÿ>Ý®qóeðkÈí[§å´‘–…ŠÏ0Ù¸C1•e,ý‘s±ç4ŽÅŒØ“;Áai¬å‚k°eýì¿ Ìöõ^’‹ÈÉwþ=²÷ 8ñNÝÓ+¯çt¸.o1É8FÿäÝÓxûgEk÷­b¨Ý4´¦ŠÏÍY̳4>eå ©}’Ù2Æ:ZÆOØ«sÓaÊl,Yƒï0MvØ¿Ì×ãúùü^ïï39á6;ƒW\(é\ÖÓ‘›qÎM»ð6ÿ¶Ð®ØGÑËS’i¨mÙ ¸3¿vm˜Ìm™hC(d„Ì€ ޱ…–)fÄOØo³ É`ù ]/ö¥ùÎïö±3¬stch=È‹¸‰×´Þ …ÛiulOn¹ªª4ñ¶’ŠÐâÙò9f†l¾•2köElDzXØkrbÌC†Ò0©&zÁ:¡c«tBdÍÈe8‰'.Þ>Œ‹jwlwMtM«uµ6–RÑgÚK;^fÐŒÇõ–À²”ÆJrÈ\˜í[&bÕŒMéˆ&°ÔÇeêc]C˜ƒr î" Àd7–fä¤ÚßÛ&“^ƒk-JA¦~4y–„äÏc™ÅÓ4Ceû ®ý“ݲ>F@\Ç/ØÊS b]n¡ Ð9§ @âSyõn_±™²õµü†³¶Õ Úu›IfhŽ,ýñu3fædRËÙPÃ&KdT¬|…á1‰NVç̺ÛÈq޽/7;vÚVÙ¿[ [Kj³A}¥”´cöƒÏ:YÄ4Tf ±M”I2OvCljòÓ–ÜqòÎ!…À¾·¬¶è¬Û„›?£bk­\¨ê´Í¶øÐÕöÃ:fÊlÉ•—<2­&NZçkܲóTq ޽½œ·OÝÆÚ; ]ÃkÍMm§64š†Š8Ð*™ã[8]f‹,Âa–v¹ÏW+ìãÆC àou­Õå·Î¶ž†È|×¼ZÎ+TÑiý-)£d4'&|†ÎŽ™²k2—sŒV Çn8‡PÞý°kpMmX•°µ¯–¬@Ô[Zf«HdhƒíEž3ƒg7¢åHuÛˆqpJñq»Q7†×Ù’ k\2jìKI§"´Ÿ¦‹€Ðl™îƒ”ØqÓî!ÅÁB·ÌVîLÜk[bÛg:lDuA¬VµAŸjÒÜ;CIr‘n9¹ÄF¸,úPÝç›’;m m­Œ­¯6µfªÊÔCÚg‹HÇr‰.9¡ÄF¸/6úêÞ[—3mÛmÍu¯Ã5°F­<Ô—šr+“÷qÈN"uÁ•7ÝvñÜàt[i¼l¡-‚ ®µ|§Däôqcˆ¾pl-øe¼7@–ތڣ[/ƒak¤c½ªk99§,â/œk~wo/Òu·õ6µ†ÍŒØœZñcZrm8-Äg8n$ÅÂD·ÿÖ÷"ÝÈ—[n¼q*NíÀ·¾–ï<ÜÛÜ'“€—oˆ-àIÂŒ8 †ùRÞ"Ü«|üp,@:ù­_;+æ-|š¯ õón¾W×ÈJú;_¾‘×Ïzù—_'ëã|G¯ƒuï¾½á×¹zö×^ÑkØÍzó¯Xõꂽ:W¤êôO^€kÎÕy´¯1µåž¼ ×’ ñó^6+Žx–¯µáR¼€ ü+_zkíí}‘¯¬µô澆×Îùk_"kâí|1¯€µïf½Ö׸ö·^ËëØMzݯV5ê*½2W£Šô#^zëÎ%yž¯/µå^¼˜WúñÓ^2+Å=x†¯ µáWã ü _v+íe}{¯©õô޾{×̺ù?_ëâ=|¯}õï½Î×·ö‹^Ç«×zǯTéê½'W¢zô^v«Í¥y¯,õå¼Wúñ±^-ëĵxu¯ •àÌWáªûÝ_p«ìµ}j¯§ÕôJ¾sW˪ù_ká½| ¯{õîνÃ×µºöi^ÂkÖíz¶¯Ré’½סzóá^r«Íy}¯*õ伇׎šñ‘^)ëÄ5xe¯•àŽ¼WÚüa_+îÅ}¬¯°õR¾”WÏÊù¡_)kãÍ|M¯„5ïÖ½ä×¹Úöã^Ò«Øõzó¯Y•ꂽ=W¥:ôO^€kÎÕy´¯1µå¶¼£W’ ñÿ^6+Žx–¯µáR¼ׂ:ð;_ˆ+ï¥}ȯ³•õª¾ŸWÑ*ùÍ_.«ä}|c¯†õð.½ï×»:÷^Ø+Ù¥{¯\UêÚ½HW¦šô{^…ëÏ…yʯ4uåö¼«×“jò^;«ÆEx§¯Õá–¼"W‚:ð%^kÀm~6¯ÁU÷z¾ÙWØjúµ_K«è|Ö¯•Uñæ¾)WÂj÷õ^ó«Ý{v¯iUì’½|W­*õM^ŸëÒz-¯@Õ炼Ü×™:òÛ^Q«É-xÿ¯Õâö¼KW‡Zð©^ ëÀÅx¯µßò¿WßJû‘_g+ë…}D¯£ó²¾`WÉJøÑ_+à…{ä¯wî2½³W³ªö^º+Õåz•¯M5é½WŸjóŸ^jkÌy\¯&ÕäV¼wWŒÚñY^!«Ã-xD¯à¼WàúûÇ_mëì]}_¯¥Õô ¾kWʪøý_«á5{ú¯yÕ¾Wµ öI^¿«Ö•z«¯Oõéf½W jóÁ^nkÌyr¯)•ä–¼‚Wêñ{^'+ÃÝxZ¯5àb»û×*ûó_skí }u¯©5ôv¾x×ÌZù5_«â|¯}Uîú½ÉW¶jö^Å+×EzÁ¯Rµé¾½$W¡Êóí^sëÍMyƒ¯,Uä×Jñ›^++Ä]xj¯ 5ࢼ×~Êü+_xëí½}‹¯«õôξƒ×ͺù__!+âÅ|,¯€ïR½ÔW¸ö«^Êk×õzׯUuê½/W£*ô^ykÍýy˜¯.UåF¼•×JñÇ^0«Äåx{¯ Uàæ¼ WÊï¹^%«Ä5}ÿ¯ºuöž¾½×Ôúú;_¼ßךšó1^`ëË•y\¯)•äבZò ^=+Çxͯµâ²¼NWˆºð÷^«ÂÕxI¯5ࢼ W€zïù]û+¾åwË®øÞ»Ï×Þêû…_e«ëU}>¯¢Uóš¾]WÈúøÇ_ ëà]{߯vu׳Jö^¸«Öez¶¯TêB½=W¦Jô§^kÑ=z¯@5ç¼íWœªói^hëÌmy}¯,õå^¼£W“ òA^CëÇÍxé¯ã"¼[׊zñ-^!«Ã­xe¯ •á¼ׂ:ð%^«¿•wç®úÕß»Ú×zªï5]ãë×õzí¯Zõë½Uשºõ ^›ëÒõzH¯FU芽WŸjóË^sëÍýy©¯3æ"¼¹W–*ò™^NëÉ]y¯ •ãÒ¼q×:ñ…^++Äåx‹¯u᪼-W„šðs^ +Àíx ®ÿ•ß²»îW}ï]ì+½-w”®ð•÷b¾ÖWØ ú©_J+çå|Я”µñæ¾&×Â÷ë^òkÝ{v¯iUìÒ½W°êõñ^¸«Öez¶¯TÕêB½=W¦šô§^kÑmz¯@Õç¼íWœªói^hëÌy}¯-µå^¼£W“jòA^CëÇýxî¯ã"¼[׊zñ-^!«Ã­xe¯ •á¼ׂ:ð%^뿽wç®úÕß»Ý×zªï?]ãë»õ{U¯gõì¾½Œ×°:õÛ^µëÖ=z±¯Suê½:W¥êô‘^Ž+Ñz¯?uç–¼ê×›úó]^f+ÌEyx¯,UåF¼ ×’ºò5^B«ÇÍxã¯uã ¼YWŠñ#^ +Ã…x_¯ õàú¼WÚð]ÿ+¿•wá®ú5ß»Ú×zZï)]âk»Í}ô¯¹ör¾¸WÓêú%_9«åÕ|¯Œuðò¾W¾J÷q^ã+Ûµ{`¯jìê½’W°êõñ^¸«Ö•z¼¯TÕêB½?צšô§^ëÑmz¯@Õç¼ðWœªós^hëÌyƒ¯-µår¼£W“jòK^EkÇýxî¯Õã6¼^׊Êñ-^!«Ã­xe¯ •á¼ׂŠð1^뿽wç®û•ß.»Ý×{ ï?]ãë¼%ws®íî¶½Ë׸öã^ÖëÚ-{/¯c5ì½y×­Úõ^¬kÕzНN•é’½'W£ŠôO^„kÏÝyë¯:µç¼×W™êó^^+Ë=yQ¯(5ä¼Wªñó^:kÆÅx¯U↼H׈ ðá^ëÂ}x>¯Õàv¼ W€*ïå]øk¾µwÆ®öµÞ–»ÍWxšîý]Û«»}Þ¯¶Uö¾­WÒêú_5«åU|~¯ŠUðš½ýW¼ê÷q^è«Ü•{|¯lÕíB½W²Jö^¿«×EzÒ¯W•ꮽJ×§úôß^–kÒz2¯C•è2¼ûWž óŸ^nkÍMy˜¯0uåʼ®W”Êòw^IkÈ­y¯•ãz¼f׋ÚñY^'+Ä]x{¯ Uáj¼$׃šðQ^+À=wü®ý•ßr»å×| ïa]çë¼¥w„®ï5Ý¢»®×tÚø_ kàÝ|¯}õïf½ä×»:÷;^áëÛ{[¯hµì¾½W°šõç^·kÖez¶¯Tê*½=W¦Jô^kÑ=z¯@5ç¼íWœJói^g«Ìmy}¯,õå^¼£W“ òA^CëÇýxé¯ã"¼[׊zñ-^!«Ã­xe¯ •á¼ׂ:ð%^«¿•wç®úÕß»Ý×zªï5]ãë»õws®ìuÝb»£×ßJû‘_g+ë…}D¯£ó²¾`WÉJøý_+â•|<¯…•ðZ¾W¾ª÷©^ï«ÝE{’¯o•íš½¨W³úöS^Å+×õzè¯Zõë½UשZõ ^›ëÒõzH¯FU芽WŸºóË^sëÍýy®¯3æ"¼¹W–*ò£^NëÉ]y¯ •ãÒ¼q×:ñ…^,«Å x‹¯u᪼-W„úð}_-kå-|¯5ñ޾&×ÃzøC_ëß­{߯y5îνÎ׸zöã^Ø+ÚU{4¯cÕì&½|W®*õ™^­«Õ5z¯OU馽)×£Úô[^…ëÐ5yð¯;Uç*¼Üך:ó'^_kËmy\¯(ÕäÚ¼’×úñÿ^;«Æõ}㯶õö.¾¯×Ó:ú_óˆ\«À ˜è&—3€ªŒE«âêþȱmš*ø‰ùÿâ\øè6”!å{ˆÄåó)þL #‚j„€”uØÊ'ž0â ajb±Ì£¢&‹O‰’B&0(Õð§<š!µ¼./. ‚£ê ¡¼¸&½¼ G]‚9*CB‘ÀÅÐ.Ð àb›ÞÀmÌ%Í»Zb X{Õ&ú†Ýžèaœl6¢@Ãïx.´8 #¹‚¨5`¢h`&Îè FJ‚7Ž@‡¸H x´3Œ×ó˜«>(B tlâ8X…IþB£‡VYá¸ôˆgŒÐYtº]!Y" QR<-„†ûÚ€@Â2S‰¡b Üz@4Zv o?ôb³èÀ*Д 3fn ”BX#ZØo·…@{Óq 1×Î ¸–bÀp¥”&žŸ‰U"ˆ@~f,ù†ê†ÁžwèaüÁÂVÚ¡A1ÐK…^Æ„0@üÝ€;²(AVÊN@/í€ \–‚²!à£ó8&õ† C‚3ˆ@†-pï`›HÏ|cmÑèÎr@0n>‹UX‚¥©žL$Ò[”vÿn wZ¿xÍ‚µ ZWU„òÁ(¸(Ež¾YQƒØ@ç¾06•< àøƒ `€·Ý+|> K‚pk “ì€#äR¿ùÝ`xƒæÇíG•#ZNȺÖ.Ôg æÖ¢ˆ[`–p<"ᜈ—Áߨàoe@Û±úeðØS]Îp ‰îáÆ`?•Êè?ƒ—ÒI1t ¤x‚½ª€¥là'’ ;-æ@„ ;"c~¿íàj&!ô‘Žé C~·ÈÐÏZ0Š#‹@±BšSè™ßþ#ƒˆ0®Á㼈o¥ž;…÷„¡aȈRzgmÅ!2˜=›d_ýƒ\sàÉ]è/0¬ ™0 œp˜$Ð6©¦‚ ~€{.0¦ÙÝ`a³ Üq­ ?#¨(pÙÿ2~ §õ"®ûЂ$3‘ˆMaáç}Ðoá´²ìé’!\ÁP{¨˜b„Nc`ÿiÐ;X È·6]€¿¤Ð,¾ tØ‚r ’¦X"]¦(ä#€q¹È° G.y§ÀX¶ÐÞöçFqº#×÷èäŠ~4Èø c‚Æ•¢4ä$ÿjˆl:aë? p ž“ÏجAV•ÐN£–!)¤@õ°¸8Çn #}ƒ ·à´ê@*à È‚GÀ‡üð»ài gªØ9D«œSL OSˆŽºU\^¤Wé§”<6ÜÔ£+¢ð¸¯Ö)Þ‰x·‚#ù°{ÑFY&p‰SâÖG]#àÿ¿¨:£Ê w-ï ¶Ü*Bö Ää‚CÀ†-p:¹­° cØ`<dÍAT J¡èY¾ï@À7ti&Ÿ¸Dâýõ#©ÈÕbò09¬ Ù(ÂnŠp‹l²5ÆýÖ‘íØZpÔc”>Á ž<Õú ܃*¹W8*i– ¾t‚<¦àƒÔ [øÿ·T _Z@ ^h,h@EUÈùÖ¯€Øt@1ÁH f>œh°JDå˜ñ%ÖD\šXƒˆ×àË$Ð-@ÿŠ{Â8mX}،ݚ†1/`é`Nת4ƒøJäþ`3»® »‚©@›H#EdÖ1k}P—® âI` Koè;²ñ•€æfà4oð ï’´€œèÀ#EhéšWÞèY$êÓé$”&Ch€#^Tˆ¾8 ) ô gûXn¹–F$…VÚ¡-zøBÚ¤Þ—ƒQ0¾*¡` •¯‚)› |¹2\`îq`S{ØèÚEE÷LÀ7§è ’¦Ò ¡3`#ñò0¿d€a]@ýqBYTƒ•&Øðá"PÌAnîŽz‹ƒ'$Юl%>·‡ú€¡¶W^e¢oa„r™øùà6™ˆ w‚©Œ —(!˜<wá«_h,6»I K@<° K0ò=¥ÂÀ$i8íæ¶Ï€]W@”ˆøÍþ€') p©•0D x%±ÙW ¬OSŠ‘ì/ãòÛˆÙdž-öP‰›Ú¢? k£ò¯…ÏðAìp8~x 5O‚¤ý`“R +t:‰zàV;¨Ód’€å€1Òp ¾àKñ}‡I j*ó6ü@ µ¼;€8(–4I‹;DŠy±úñu6nW<“åƒäcÇPï'1[Y‰þJ‚©h¬XejH`ëÕP1xR p‚7Ž@x¡àªXx+?LØ i:Ï瀕!€¤ðj2Ø€8Üà Ûp}ò#hD¦&päHð&Äz¦q?¤FŠß‚‡ÄXÚ±W„CäÏífT‚Fa‰dÀ&O¾ÔóG#hcx¨ÏÀ X„;ê!8á0>$¸ |ˆJ@„‘x(J·#Ž`;Fà Þh^.u{ ¹ØÖÄ™lø€Iœ9-Ä~#•tF}ÿvºÄUq! DKÏôéD6…± lBš]‘!Ä!U1Ô@êi¥™”%¾…i>ûR?.Ë } âÀ[îÆÈæ$e™ Zzž¼v€3æ˜ Ž,©ï€CÜ /À‰UkXćA±ÌFJ\úÄJè1 dC[{´‰Ä%ŸÑ‡LAzÓFÄ iQ ¬@ëô}Ãø0ý"Œ? \$Óò†áÑï´O-ý`¡ú30M„ vÁ‚&€Y„ÐÍnXЀSô¶$JƒþüDdì‘e´C %©ÏD1,ü@luö£CõápûË<>‹ÏŽ‘Cßö¡l=p‘MCÐTpóit<¦ÝüÃÅð©¤<ø]C¥j'tÜqÌ•èiÇ•02¡Øgp.´8<‡s  |Fí“ï‹Ä‡ðþ¾Œ>`åR|CÇzï…Ô;SÕ·ýè]péÜ: csSCš¯æ"Œ9fOUIC“úðäº9%ïFCÑLPä0¼9å?ÓÃ&ðã–<8áE6+ÃŒ¼è%Ñâ uö‚ÜÊ íê0æü*GW #¿BÜ„pº6¼/=£ ì`ÚÐÁÊ„0ÕU H¥CðÇ8ü2 •äÃ*p˃œ3 Õ1C8„PÎÝì3æ¯ žCD–0ÑÀ 4Ÿ9 0cCO 0ÔL\55m WCVÓpÖ ¬5œk lxC[ì×.„3ˆE çn£Iii%Rh¨²º28B/e_Cø×›ý¤Á©ð\ÎL‚¶!BMPÁ”%!× §òB~èp¤')ú µ=¹¥ð±\Ì,ñ³ aùÂáºY/Ù ÜHÂýÆÐÀë\0j %=C ¹Pë,1k LïÃ¥ðÆü1Ÿ pWÃ;0Çä¤23 OC&G0Ê=t2¤Ñ ®‘Ã,ûÐ˃œ- XQÂÚ_·eÄ.Ñ •yBèQ°ºó”.ô¯ ÇæBõ1¾\/­7 öCupÀ¦´0L ¨CÆÃ30ï CGÃ)PÅ|1Z_ ^CÝ铊†Kˆ°CW1ì0òŸ«¸M2öZ>¾Øm/êhäű¨;@þÐÐP«<³˜½ÁÌEÐ{.” ›!Œ´Â9pP“0 %ï× ¾zB~p¢¾”)ï ^VB|0¨ÇŒ*ƒm ¶PB²h­¬¬+¸k jBÄð²¤,Ë @·BÔ3еì-®‡ xBáá°¹5<.x; ©ÛÂìàð»ãä/O Ð{BöˆÐ¾+Œ/­7 òÐÂþÙpÀ\0)­ŽÚB.,pŽD$/ )XÂR¦Ð–Š\&#U ¤ºÂp'ðÙt'Ù ñ‰簣Ñ<)R¹ iŸ0© 4*”— ¶PB²#p­›„+§? ùçÂÂð±L,›Ý 4êBÐ-дÙt-aG dÂÛ¶p·™D.; öÂåç°º<.§o ³ƒ©Hpªùi²ù:JÏ¥·º«ŽŸ#ÅáVöžè¾•£¯ÐÜ“ü5ÌñÀ‡þp3¢Lô+ÁhÃPc¥Dù-3<ÁÛpyèl;9þB ;p…,T!î) Â1dpŽmD$ Y )XÂQOp–4„& ™ÿÂm4œ×ü'”i ü´B…°¢‹(ø› QvB™1§d*)M ›}«*«Ý,+/ Úʹ갯YÔ, õ JBÆÊ²^L,ÆÉ “ àÏB{°pŸíœ(31 Ñ¿¦&Ïó {ÿ/Gè@1[Ðê´°kd@üð-w µ€ô@ôŸA·4‘Ÿ¥ÙÁ:\PRz¼‚!›ŠÁsÂ`9ÌØ%d+Á¤Ø0kÓ„œAçÁÍ0uÑD;£ãÁñŽ0~Ud Í#B!ð…ÆÔ!áI’B+9ŒjD#peò¡BAÀð‘ÇŒ$¿# D+BUš–Š\%ë‹ Œ BgMðšÃÔ&õŸ ËZ+ðê«h0"ùKtÆåû³ê-lC}OJå•£:°dîð~«Êõhؽô´ï²ÓîeúüYŠ¿ERïÛ¹{ùrþôà?ßHpŒéUñl@VèÐ;Tñ›À¶ª2Ã$ ò§ÅžApE"¬J)ÑÖÁBñTJ4ñ·®ÚÁxÛ0aômÓÁ¥êÐl¤ÕÂAÌÏ0ujLãç›NÁîšð}˜ŒÞM‹B `°„ÅT!˜U~ÂB%RpŠÎD# g×ÎÂ; P <$SÙ & ÂN\0”©´%sa lðB_†°˜Òãø¤ùæú>¯¨/¸tñ üðÑ?eÇoâÕúüìÿG|¿ðüPÁ¬³@^ô°è|ÞQŽ{@¸F2å| êÀAA:ÐDT¬¿™Á=ØðS •Áqáð_ItŠçM£Áš0j ëêHAÃõPstQýt®Áäó{.”CÏìØÂ/p‚¬ ðõRÅB—ð‡ýL"Ys«ÑÂ0 Il#›Qû6ÂCP‘û $¿# @ó-mÊKÕ› ìÊè»´—Y-•ÑÃ~ŒÜå÷:m›nÚ*[ÃÕÆóÅY=‰¯Ïs–{àÍŒùF¾};/¬.³îü2ê¿6MÏ×]³øþ’+¿ÅˆùCq¿À6¹ p ÷4À’ý°)é4 «3´ÀÞå°;á¼þ7;¢Á2Kt1ðÁS×pWú\¿µÚ×A‚>pc ÄeÅ#Á©¬0lÃܾ•ÕAÌŠutÅÛAëP|…ä„-úËBÞ‚±,! µX"Bªˆ0Ì"]½ª¿B/Èpì#ŽqôÆÂAÀîуëºûØðP8`Co£TÃë¶û‚÷?+oÊäËôï®ý½½?¾oêò3üv”ÿŠ¿ûr0Ôªs»ÀV_pŽ,áA<@¢Œ0,º4 <éN‡@âbph ¯¥ëëÈ€ûŒž¿¢ʱKô·äý­¥?ˆêoégcü JÿiÑ¿óªð¯ÓÛöÀKéÎ\,ßÀ–5°)Ø s7@ÕÇ09!ëPâ¸ÖÕ6W‹­½.Cx¦>àg¸ DnHR™£úè0øºwˆ¶Ó˳µRîM<;çÌo TÇñ2ó@=ýPÉC×ÌþöÉÓ=æ}¯…ƒäNzùÀZ¾™©¯°SîYü"Ó?)÷/Òx“ö~Êþ¤¿ @OîÄ«ý@Hÿ¯ŽÀ°0MüÝó @UÖ0©S-ì.D]ëÛ¹c ö¼Çö3NÇ-'rKT'’×|u6z%mÃ{ãyÅÆàŽ-8£|NGë™#<çÿž:g´Î²Dƒ²Ujíïå;Ìp¯ŽSÆ"ò›<åÍÏHÎ{Õ¸ÖöJ&½Å;O}¥câ6ù/ûÏ–Acè |ú–î>Égﺙkð¶6ü§Ü¿HØèƒ÷ë’þd ?±¯oòc£ýýÅà32©ÒlÑXó=äjÑÕ#5 Jíg{óbÅÊÚÞF7<›-ﳃ~Þâ³~9†®bËžÒé; º¨ŽÀ%㵄î}„;é#Ï ¦óÆü”ò»F<é¯Hx£Õk˜ö&¿=¸äÏyÒëáøï$>bhÏ¡ýsê¾lû6Ê>î°OÃQò´èýá?c¯ß$óù`îËÛ4³‰„­S³JÕzÔëÚ5È -“xëm.ÔÝE 7ËL®v›‹#Îäu9…˜Žy‰ó¤3¦êq:ð;Ïù¸´2ïM§¼ÏoãÉbFó?==™oN×sÖå@öw6½Ë!ï}”;áõ¨ù¾lUO£ÞëÖûJ>ò,ïÃÚ[òÊ\ýó³Êö;kN *Õ¦ˆµíÝÍ›– n¹¦Ý—¦7ÚÛN¾C‹}ìä|й‚îx!s£²æêAç:ázŽÌ{·”ªîþB¼žïCÇàòáå¼îOHÎ{Õ@®ö=°”/vðËàD8ø¯×>Q>/KÓéo°úàõ¾×ϽjcHß^ÔD{µ’"„„ãhÓÜ17y9mú3s…cÎâö9 ¾®`BS¿hèÉR:…¿.µHû²Àí¥Ý»®!Žû±kÂårñ¢-¼¡hO5å£Ð±bôñÁ½j’¯eùÃÜ(Ê÷­F¾z¯ðÃå»Bùõÿ¾ž}¯f«Sç2Öå.63šªgÃq¹ÖÞ2$·÷I®ÖãŒ%LäŽÈ9mNuÈ£¢ÆÞéð]:Ê$®Ä׃µ¯¸îtî»Ý›/ÁÓÅXò1ñ¼Âf=4»ÒZ>õP+½€‘/j«cÝ7(÷ç6>ŸOŸkæQv×{b6Rs °×³s/4Þt©¸²îR ŒŒJä—]9(®té{¢q éÒRº¿ónÁõc´ÌDî7Ç;Í>¯f ĺñçê<®H/8ÐúVôúV½j oe+ÃÛÆ÷>Im€)g¶Ûsg·H#Íë•;xáËÀ8ÏyNJ!{—åªçH'º!*›U³«q˜ëòF;@)àû»Ö¶ïäî¼1σɫ:ó2]<üïJ{£ÕIBõûÔ½¦ì/sbûhßèÛØA·[sï›3ô áð:¸ÕNKÓ—î>çA·º$™Ê۪߮ë˦»4å.ܶ«»l率¼ íï‚cÈœÜòèU¼é¯E…[ÔDõ«]½’E ªëpÍÎݽ37΄® ¡+ˆWão?¹/:Ž`BSœèÔèfºaDΩóã®”쨪;gR蕽»¨ðG£‹ª„ë‰!» >×7 ¹lºï:W¼± î›Æˆ¶òcL<Åâ¯<«ks|rÞO·ìJn»C‰v¦ã¡¬¹7FŽ`©SœÓ`èPºXj뭣Èì_µ»RïŽâÐ˼(@ïÛF¼)>¦3ÈüòØ=·Lnmé¬dá;ü8 Dn;ƒC“¹æ=9Ìh„«‹¥NäêU7:ÒþŽÃø[´”zîG»¶¶ÎúžÃÁÁ ñ1†¼z?M×Í;{Y.à#ö8Y¹Ž*]Fäþ\¹‡y.s++¡"éD³º‘GγW+°}Xí·;w®®ë;½æ’ð@ <>ømÜm³|Z¬àXˆ¸d¸Î,[»»°å a¹Š'îs óé; ºŒ¸Ž±ÿÓ° Äìä¿;nKn蕽C~ðë7Ô¯îž+†û€âè¹æNQùë˜ÄÒç9"ºΓIèrë ˆ:ûîÌZ#¶_¬îaž»ÊKMøÜ#‚þáæ’¸ÁËnAó;”Ðæ=9Ï[Nƒv‹¤°êüº¾œ½T벫>ív¨» müXîâj8ÊC c•®æG¼¹Ðm³¤’ê :º μòHŠí[Ö8#®ßˆÜ(ãG9#nU‡»™N&çI:º Ú.‘òk§ýˆêÛS:ë«ÎǨƒ€ËîáL¸–á.6j£‘±Ìåi¦9—ÕÎtØS ÙXéÜ:}nŽ¬Ö ®m|ìe¸žÎ£cŠ Úã…Ç9 ¾®W­ ™¬çSõ: Ú.‘zC§¸Þê¿n:ãn Žƒ†”‚â¨Ã¸èônI©S–/ìæx9×"ŽƒÌ[¤Q°éçǺ¬è. ¤#‡;àâ˸ð»ÎK"û–xâæ…ö¹ÙNƒî³¤Mféâj¸}ª®.ų{PäÉÊ9kØÎi ¶ÒèB#:D‘®!åËŒ? ãù¦¹8Y.\šqúçs:›n‘CŒÄäž9=¶n],«š²Xç{§:in4ðûÅÂåt9yBkužˆäK0¹Hq_…{›äg¹NW®`d£›DBåO湆fŽV3`ÎZµ¦¥k%ê­&<¥æÅu%F%»€å£EÜ¥å%Ô¥©¦¦Æ‹¦}æef,†&K(†ÄkyfMƱFd& ÆfÆ¦Öæ¨Ê´ ¯†ªf(f&Y¥÷¦Yf`¦ƒæPf£&d *7ŠQj>ä…Ù%ÜF0†]&f!&afå§浉ɒéÖ‰Þ)óêêF^æ%§f5%Óf2&ZFžfÃæ¼§'æáÇ¥¨Q),Iˆ·HçÉYiɃIµ ÷Š!*IÆ Ý彆0fHæ•'IÆi&—F—'.ÇB'’ÈZ§ñȇ¨RÛˆC¨{H›èÝÈûˆ•ȹ ˆÿé‰Yi]‰e©¯* êAÊcåÈeó¥þ†X†{fXß&Ó‹æïFß§Cç·F¬Çqg·è`è çûçéhbÉhÌHŸˆ½è¸¨ý)eÉ„)Œ ¶içJ 5ÅʆNeöÆS¦fÙF_çg ff“e»Æ¤…æF¯Ç g‘ç©§¶çš§îèÁi ëÈ¢HeH¸)G))‰i™©¡‰· ó&©e›†ªeìf–fÂJ E´†¿æ¯ÅÁ'o†üÆ['!§…‡«GC‡U…Gæ¨~ˆ‹èšˆÂèÀ(äÉi .éb‰€i™‰Õ Å›ævEîfÆó…­ØfðæDÇ-e»†‡£>'mÇbG@ç3§bG¨Çžçw(hM(Ih¸é©'‰*ˆäèÆiiC‰v©¿)õ¸¦¾FOÇëg[&2J‡ªÆf°t‡iÇKǘÇ^GS§‹G¼GÕG‹gœ¨ (YÈkˆ{hà)?ɨ¶(ªèЩé’©ò…F…¢fÝ¦ðæ›Æ6Gßþ%®ç²&ÄfòFãÛ&à‡X§?]Œ‡æGîçù‡¦Góh9(Rh¼©+©2þHÒȾæI\éÊ)éì ˆdÓFI…9d˜Ç6Æ2tF ¦dƹfÍg^‡'‡ FøG!‡Y§tGØg ‡Äg®Ç¡¨ÈDè©(öI .‰1i ©)@é~‰‹ÿÿê¶æD§Õ¥yFÔg¦V†¬&e¦Ü¦Ó‡¦ñƇfÇ'+§8çk‹ç‚¹çÀÇ GÒH Gãè7À¨ðÉ É:)I iFÉd‰^ ªH‰€ˆŽ…NEè'ž†{Æ]f’FÖ¦æ‡g ú¦åç;G§‹‡¤çˆÇʇ÷‡Â( Göè"‡(„ȹÉHóèðÉ+éAÉFéË'DHDÇ„ú%1ªf†kÆäFÆgØÆÁ&ÄFíÆ²F†&âGJç%ç‡BgyçˆGùGº'ôˆ (Haˆ4HGȳhÛÈîÉ Èÿ‰‰Ô«PÆVeÆ2'ù††JÆô…†pðf’Ưf¥Ç Æ©F²gÆáLJDžç®ÇÇGvgÓè%((BÈCÈIH_(pH¹‰èúˆüé±+¬¯ÌLÍç6%E Åç¦Ú…hE‚f†Æ‘&k&þg&çÀ†»æÁç2ÇgX§/‡z§ŠçÕgí§¿‡òHè gæè$HnhZH‡èò) ‰wÊm Èlˆ¬¼ˆYåèÆE%ª¦˜†'r¦$Æ2†´æ€fà&ÃÆïÒ'†Òg'C‡Z'ÆÇÅgà‡éÇõGßè(ÈÈp¨o(€HÉÈÏÈÁÉ ‰¦jžÌìïÌ9ͧ†’fRÆ©fž¦öÅÞwÆWF”Æ‚­ÀæŠæ‘ƯFùFægg4Ç’‡œ‡zgãHÈH è(ÈoèkȃˆÌH¿HŒ·i" ­ÊºL^ ; ‰ ]çh¦b%ò¦Ƥ&Îç3EŽ&ÁNFüæífŽF|ÈÆÂfÚfÞg/ÇY'ǧ©‡æ(gë§õlH„“ÈϨÌÈ®HÔ)I: ¾ ,À,í¦±†u[† ¦’f.æÀç?Df'†„&äF~Á¨F´î¦î¦ðüg€§|Ç©¤‡Ñˆ 'õˆ2¨J¨yãêÈÏ )Q‰` *:Ë,挊æð!æ£Æ—F‡¦ÞF(F½æyfåÆœËÆÒfµ f÷'@.'4'dG]'ÒGùèè(8Èeh„hò© èÞñÉ6éoé¦Ê ë@«êgìæÍÆÚærf*†o¦˜f ÇHf;ÆJ'1fÃ&éFÐ&ã†Þ†õ'<íüî¤nŽŠî›Ž_mìM¯-ÀMÊm­­›ºííÎmímÒÍË ÖMå­ñ-ûNÎî3.@Î?n2Î*N0îCÎV®]nUC,nn-ú-üŽ &nDÎaÎxN‡ŽŽÎ’N“®•ΜN¤Î¯Ž¸î½Î»J†jñ‹Ãl{Í9Ž?éîº._®bjníºm¦-Ô-èmÒÍÒ.1./N­üŽ.NN#ÎO®ˆÎ­.¬N….VŽ<îCÎ`nzînrNRî0®NÎ n®6NTŽiÎtnsîmne®bŽgtnˆŽŸîµîÅÉN¿%ž¥üæ±×CFû¦ÜFT'lç:'£)½Œm|*Ž...ÎxNÅÅî^Nn ë­¼mÈN.&n)Ž6.\NrNc®JŽFNSîVŽJ.C.ZN“ÒŽö.鎶Ž~]ÎbNzŽŽ”‚.cnCî-Î*î<®\Π.®îªî™Î…ntNnÎx.Nª®Ë.çø.÷ÎåîÂ)zI¥)Ê"j•ë NMÑ%O®QnZ®—®¨î™Qî mä­êMùíöíýN#®O.eNlîpNnnjon~އ~nl®f®zn¤Ñ.ìNæîÇ¡.‹ˆ®’ÎNžn”.‚mîb®e®z›îÀîÝ.çîàîÏŽ»N®Î¯¿Ùüï¯;/IDÏ*õ&‘&.'…¼F8æƒÇÄ©rj/jÂËn̬L,m Ñ-í­K¯‚®ÜNFîŸn±îªÎ·î’Ž:M÷î.P.[;®:Ž[Žr.dŽEn8®PŽ|.›.œ‡®qngÎlz.î¨n¸½N¸®°.§®¢Ž¡Î¥.©ŽªN¦î¢¢N­Á*“«gKöŒÄ 6ín-àîÍÑ­®Í•,Û¬Ï N‘αÎñîµn®nËŽµNhÎ;nnŽº®·Îy.Z®k®xN[N/Ž)®Xî—î¶Žª.‡Nh®TNFŽ?®HÎdŽ®³ŽÉîËN½ÅÛ¥¾Ær&¦b'óhæªB AÍnwí´Ž9NPŽXn¦k-Äíš­kLx¬PmhÄíénun}n].NÎŒN€Ž‡Ž¼®îŽÚN›®wÎîŠNvn[.a®’ÂîÏÎ³Ž‡®bG.2Î'n0ÎU «Ì1ígNt’.FNXŽ¢N¯£î…."-Ømß-‘ Äl,œ ÁvåmåN"ŽG®;NdN§NÈÕîÚ.ÁΔÎvÎz®nš®š£n»NÎÎÈΨ®.]ŽHæï匦Xñ»éò F ¹=¢®Á.ªNtŽvŽÊ®ò.šî<îENSîMŠí¬ˆLPmmø. Ž#h.H1N‘Žõ®ú®ÐNµN¢NZnX.zΣN´.±Ž«î¨®¡Ž‹Ë4,ð®&nžNÓŽ³n¬Âî’.¥¯ ¯.c®!ŽˆÎiM³íP-L` œÍꎊN.ÎÎÈÎl®vNô>ïnÌŽ«NŽyŽI=eîœî²Î¡îˆÖHÝJ Œo-©N6ŽqNÛŽûn¾.ÙnÓN¹òo<®ß®®Nt-ÿíM­,,Ô Tí® ÎPNšîÙN”cN³*¯EON»Î .œ†.^Pnt®­®È+¶M ŽY®2®kNì®Öîª.î…ξNÂŽµ®inN ®Mƒ,ülî,¯,½mx-Æ-æ8.M׎-.¯OîÂNniŽ{î“.î‡Î¢. ®KÎÎަ®ÎNwNí±.-.yn Ž.ÎUî à­¸ %  ¬›¬× z çÍ÷ÍÙ H iÎ8ÓnôŽÈ΀.7îŽLî›îÁ°Íº®~.ý®¦N&mN Aî<î1 ÎN>.„®-»í›ÍŒu¬–m?ÍúŽmòkM­Ê¶¯Îùν.nî­ý®B®­­¯É.ÈŽƒ-øm´nz®-÷n†ÎšÎÍÜmÂÍ#Œ·Í -¸îݬ :¬©M;΋/AïHï®ÎG5N-ùN4m¾Íxn/nHMü®=N¤.ŒŽ&έò­€Íp Ò®A.DíØm î ïmx.ë/NO(îén·®› È­í£-—-Í ñ­ÕîWÎxŽgÎ;NE.9®Un“®º.¨n Œ«U, ÇN¯î®¢.jîN¬c m•ºMŠæ®D)îÍ…Mö0NuŽÐN Mÿ k [í° ÿ–nànÍ‘íYMomªzCM™ÍX JM_MË­r­A 3 Ímˆ­> 5­²m\-èâ!.§Ke”èk².†³nªk1VÊtÒ=¬ÅZ©“Ä ¬ÅZ)RÈ“ñÐØ@ µ˜«E"U{8š %±Øðz>b,ÔI41äÜe.•б Ôl7GL5Š…$„;› E¢‘,ŒA…âñxÀb2£qÂÁ@AF¹<ŽAŽ£p°V* EB¡P¨T+ EÂñ€Èf5X'Ñè¡ ¼T&àÀX)‰D¢A J% „âH¨V- +ú=r3ÊD’ìj0‰Ä‚€>ˆF% Å"¡`¹n¡GŸÎ&RÑ@Ž? …‚q|: †Ãa°Øp:Ä"!”N)‹Wêt’âd,ˆ£Ñ°¼R$†Ãp°X, †Ðl:„‚a@©Ž¯L¡F2¹4†;‹Dâ è`* ‚A ˜L( Ã!°à|B#Š u(ìe+“C‘ˆ¨H …‚ax8ƒÂP* †Ã”NÙ_©Ò'ÃId˜A "0èd(€°X, ƒ‚  X0ˆBW\˜A‹¤ÒÜ^'‡á0x0 Àà€@( Ba`Àl< ‰¡nÆzÙ>‰:˜Š$1À¸L …‚ ÐP  P0  aPÀl `€0€À`@( „a`Àl"‰®ÖzÕ<‰:˜ÊD1°¨@  P€@  ‚ÁÁ \4„‚FÒùL>‹$¡Ø¼F ƒ `‚A€ðP.ˆbQ •ÌÉW¦P‡#D„5‡‚ÁP€À€X4  aá”L%µ—j4qìÒY%â@ØL€ €@@0( „BpÐt@# ÙKÒäb)±\ À €@@0( a`Àl Äâ¡X´^0ÂL( †! àt>  ¢P°Z/Œ†‹å:Dük-“HAP„4€àP €àX0„! ˜P* †CA°èx>! ÂqH¨X.ŒFcA° ‚  `4BA0 T, †ƒaÀè|@"‰DÂH¬X. FcA°án FLå‚Xøh*¡`$€àp8  ƒ àx@$ BÁpÀd4CˆH%ŠB±h¸`2FÃÈ ‚A@°`4„‚a@¨X. †ƒaÀèx@!Ä¢a8¤T,‹Æ!˜Ôn8ÙK$â ìf+’‡ãQ`: Á€°P( Á`Àh8„‚a@¨X. †ƒaÀè|@!Ä‚a8 T+ …ãÐj7އƒÑø4 €Àh4BA@¨X. †ƒaÀèx> ˆ„bA(œP) ¢ñ€Äf4 Ç#¡àô~@c+Ó(c©”®J…Â`øh, Bðx8ÁáˆH& …‚á€Èh6‡ƒâˆF$‰Å‘P¬Z.ŒFC1¨Øp9Ãò . ÂA ˆD B! ˜P* A Øp:ˆ"!L' EB±`¸^0ŒÆƒQ¸àr;‡ä¸ÃV%Ї3X”A Å0ðp4 ÂÁP L& Âa@¨X. †ƒaÀèx> ˆDbA(˜N(Š…bÁp¼`1 Æ£a¸ät;ˆŒGˆ„!ðèp4 Âá`¨T* …‚á€Èh6‡ƒâˆF$‰„âH¨V- ÆÌh6Ž#±àô~@!È„b9%²ÀT¥GXšDŒÅ‚¡8”F!áÀØh2 …ÂápÀd4CÁðø€B"‰¢a8 T+ EÂñ€Äd3 FãÈèv=È$"ŒH$’‰„Rðr5 Å‚¡@˜J#ˆáÐàl6 ƒA Øp:ˆ"!J&Š"¡X°Z.ŒC1 Ôl7GC±àô~@ È„R9 ’K&ž Uê&8™ %R,ŽCÇQ¼Z+ŠÂA„@C¡Ààt:ˆ"!ŒH% Å‘P¬X-Œ#!˜Ðj6Ž#£1„¸W)I$Røt8ŒÆñh¬R' b!€@ˆŒF$‰„âH¨V,‹…ãˆÉÚÓ]©’h#ÙÐÜh1—‹%By,ŽD ‡c°Ðd0 B‘8˜J$ˆÄB!ŒH%‰„âH¤T+ EÂôZúv8ÌÆÙ\¤N%È„ððt7ŒÆ"ñh°T) ÄÂa0˜L&Š‘P¨V,‹¡¶’íN•F¡ç“™¸Òc/J¥i(ŽD!Ç£±ÈØh2‹ÅÂÁX¨T)ŠE"¡P¬V, Eʺ=‚>Ž&³1„¸X*IIJA†AC°Ðd1 ÅÂÑh´Z-‹EÂá{Iz³U(S)$Zþy9›2ùl®T(“‰d’1„?ŽÇ#°Ôf3 F#€À`0¯ÖªÅq*D ‡c¬Ìb/ËR‘<˜J#‘Hdøôv: ÆÃa¨Ôh4 êå2}0’E¡Oç“™¸Ôf1Ëe‚±L M%’ÄB~>ÇC¡Èàp7XªTI´ª9‚>Ž&ÓI˜Ä_.JÅB‰8˜J$ˆ¤2~>GƒÅb•<˜H¢Ð§óÑÔâm5Ìféh®U) ÄÂQ$ŽF"‘d" ¥D›J#‘#éäêq7ŒæC x¶X+ Eq4–J$’êÅ*y.‘E¡'ÓÉÔän5š †ùp´X+ E8šLT(SIDz) >žNÇ#y¬Ðe1 ÅÂÑ`®U*Š*µ"y0“G"P¨ñèìs7› &c!ˆÀ^.Ë%‚ºBœK¤‘È”*üz;œÎÓQ Ìd1KååZ•@›K$‘Ȥ2üz;&ãa¨Ðf2˜Õê•"}4–I#‘Hdøöx:œÎãa¬Ò³VªiôÒY$ŽE!Góáäîu9œMëu’µP£O&’Éj) ƒ@Og“¹Õl±V)ÔIäÒU"ŽE!Ј#ùôöºZ¬jeu2•H£‘h„2¾\­ê¥*…:™J¤‘èÄR!„½\,ÕÊ•*…:™K$Ò(ö3x·Y+U*U u4˜J¤ÙLVíl±VªTª$úq4Îd±륲ÅZªS(Ô-6k!†¿]-–JåZ¡¤Íd0×Ë¥²Í`Öh³ì5úípµk4YŒ†#zÚj´Y¬–3i¬Òg7Ûmv£ºæ@˜ @   @ A €@ H  ‚@€@H$ @ € ‚C€@$ €@ € €!‘ˆà2 †C!€À$ @!ˆ@21 F#€ÀB€@ €B D"`$€ €D †FˆÀ`0D"„B!”J,‚ D „!ÈÀ`1"€B!D„H$‰€Ð@„!€D  Œ"„@!ˆD€@ ‰D‚A H, B€@"„ÈÀ@!ˆ"„@ D¢A H%‰‚Àè@" B€@ €B!" J%‰D‚Q(H$‡!€D0@H B ˆ"„J$ D‚A ”J$ D¤,BH"„B! D‚A(H%‰D‚Q€B!D €è\(€ €J% D¢A(”J%ˆ„B E@¨T"@ P ˆ¢A(”H% D‚A€@!ˆD"„@ ˆE!@¨P @„J% ¢A ”H%ˆ„@!D€@! €@T*!Pè0%‰D¢A(”@ €B „@ ˆD"H *CШT  D‚„@!ˆ"„@!ˆD„@ D"H R ŽA Ð T( „B ˆ€B ˆ€@ "€B)ŠAÀ(„B!ˆ @ ˆ„@!ˆ"€@!ˆD„B  A €(€B!€DP!D€B!ˆD"„@ ˆ€@ ˆE€@ €@€D0!ˆD„@!ˆD"„@!ˆD„@ ˆD"@$@"ˆ"„@!D"„@!ˆ"€B!"@$B!@ @„G„B!ˆ"€@!"€@!ˆD„B!ˆD"€@(D"„@ D"„B!ˆD"„@  €@@D00ˆD"€@!D"€B!ˆD„B „B!„B!ˆH¤ €@!ˆD„B!„B!ˆ€B!ˆ¤R @€@ Ž#„@!€@ D"€B!„B!„B!€@ H¤!ˆD"„B!ˆD"„B „B!"„@ €@ ŠE €H $b1 D€B!ˆD"„B ˆD"„B!ˆD"„B!ˆD"€@!ˆD€@ "‘H¤!€@!ˆD„B!"„@ D"„@ „@  ‚ €H0 ‚"ÀÀ`0D„@ €B!€@ D"€@ ˆD"€@ "$D€@ "€B!ˆ„@ D"€@ "„@   @ @Àäb!"€B!ˆ€@!ˆD„B!ˆ„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 „BH¤P!ˆD„@ ˆD"„B!ˆ"€@ H$ ‚A„ˆ@ ‚E‘„@ ˆD„B "„B‚A H  @€@@ €@"@ P(€@!ˆD„BH$€@ 4 D"„B!€D  "€B!ˆ€@ ‚€H$€ @ B!€D"„@ @$R)"€@!‚A€H€@ P 0@ !ˆD"€ ‘H @!ˆDH$ @ €À(@ B!€‚A„B A€H €@h4€@„B!ˆD €@ P( A@ €@Ðh€@„B!ˆD €@$( @€@(€@@ B!ˆD€@ @$ €AÀ @B€D €@ $ ‚A @(€@B€D @ €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 ƒ7777BUFRU¬b€Ë 4Ë~hd!]ò}L!Øb¿°V@ܼ.ÿÿdE¤F ¼ÀÌFUL €(!I¸¾@@@ @ €€€ € €@€@À À À@À@À`Àa!!AAaa¡¡@!@!@A@A@a@a@@@¡@¡@Á@Á€!€!€A€A€a€a€€€¡€¡€Á€Á€á€áÀ!À!ÀAÀAÀaÀaÀÀÀ¡À¡ÀÁÀÁÀáÀáÁÁÁ!Á!ÄaÄaÄÄ‚BBbb‚‚¢¢ÂÂââ""BBbb""BBbb‚‚@B@B@b@b@‚@‚@¢@¢@Â@Â@â@âAAA"A"ABABAbAbA‚A‚A¢A¢CâCâDDD"D"DBDBDbDbD‚D‚D¢D¢€b€b€‚€‚€¢€¢€Â€Â€â€â""BBbb‚‚¢¢ÂÂââ‚‚ƒbƒbƒ‚ƒ‚ƒ¢ƒ¢ƒÂƒÂƒâƒâ„„„"„"„B„B„b„b„‚„‚„¢„¢ÀbÀbÀ‚À‚À¢À¢ÀÂÀÂÀâÀâÁÁÁ"Á"ÁBÁBÁbÁbÁ‚Á‚Á¢Á¢ÁÂÁÂÁâÁâÂÂÂ"Â"ÂBÂBÂbÂb‚‚¢¢ÂÂÂÂÂâÂâÃÃÃ"Ã"ÃBÃBÃbÃbÂÂââÃÂÃÂÃâÃâÄÄÄ"Ä"ÄBÄBÄbÄbĂĂĢģccƒƒ££ÃÃãã##CCccƒƒ££ÃÃãã##CCccƒƒ££ÃÃãã##CCccƒƒ££ÃÃãã##CCccƒƒ££@c@c@ƒ@ƒ@£@£@Ã@Ã@ã@ãAAA#A#ACACAcAcAƒAƒA£A£AÃAÃAãAãBBB#B#BCBCBcBcBƒBƒB£B£BÃBÃBãBãCCC#C#CCCCCcCcCƒCƒC£C£CÃCÃCãCãDDD#D#DCDCDcDcDƒDƒD£D£DÃDÀƒ€ƒ€£€£€Ã€Ã€ã€ã##CCccƒƒ££ÃÃãã‚‚‚#‚#‚C‚C‚c‚c‚ƒ‚ƒ‚£‚£‚ÂÂã‚ッƒ#ƒ#ƒCƒCƒcƒcƒƒƒƒƒ£ƒ£ƒÃƒÃƒãƒã„„„#„#„C„C„c„c„ƒ„ƒ„£„£„ÄÃÀƒÀƒÀ£À£ÀÃÀÃÀãÀãÁÁÁ#Á#ÁCÁCÁcÁcÁƒÁƒÁ£Á£ÁÃÁÃÁãÁãÂÂÂ#Â#ÂCÂCÂcÂcƒƒ££ÂÃÂÃÂãÂãÃÃÃ#Ã#ÃCÃCÃcÃcÃÃããÃÃÃÃÃãÃãÄÄÄ#Ä#ÄCÄCÄcÄcăăģģÄÃÄÄ„„¤¤ÄÄää$$DDdd„„¤¤ÄÄää$$DDdd„„¤¤ÄÄää$$DDdd„„¤¤ÄÄää$$DDdd„„¤¤ÄÄää@d@d@„@„@¤@¤@Ä@Ä@ä@äAAA$A$ADADAdAdA„A„A¤A¤AÄAÄAäAäBBB$B$BDBDBdBdB„B„B¤B¤BÄBÄBäBäCCC$C$CDCDCdCdC„C„C¤C¤CÄCÄCäCäDDD$D$DDDDDdDdD„D„D¤D¤DÄDÄDäDä€d€d€„€„€¤€¤€Ä€Ä€ä€ä$$DDdd„„¤¤ÄÄää‚‚‚$‚$‚D‚D‚d‚d‚„‚„‚¤‚¤‚ĂĂä‚䃃ƒ$ƒ$ƒDƒDƒdƒdƒ„ƒ„ƒ¤ƒ¤ƒÄƒÄƒäƒä„„„$„$„D„D„d„d„„„„„¤„¤„ĄĄä„ä……ÀdÀdÀ„À„À¤À¤ÀÄÀÄÀäÀäÁÁÁ$Á$ÁDÁDÁdÁdÁ„Á„Á¤Á¤ÁÄÁÄÁäÁäÂÂÂ$Â$ÂDÂDÂdÂd„„¤¤ÂÄÂÄÂäÂäÃÃÃ$Ã$ÃDÃDÃdÃdÄÄääÃÄÃÄÃäÃäÄÄÄ$Ä$ÄDÄDÄdÄdĄĄĤĤÄÄÄÄÄäÄäÅÅEEee……¥¥ÅÅåå%%EEee……¥¥ÅÅåå%%EEee……¥¥ÅÅåå%%EEee……¥¥ÅÅåå%%EEee……¥¥ÅÅåå%%@E@E@e@e@…@…@¥@¥@Å@Å@å@åAAA%A%AEAEAeAeA…A…A¥A¥AÅAÅAåAåBBB%B%BEBEBeBeB…B…B¥B¥BÅBÅBåBåCCC%C%CECECeCeC…C…C¥C¥CÅCÅCåCåDDD%D%DEDEDeDeD…D…D¥D¥DÅDÅDåDåEEE%E%eEeE€e€e€…€…€¥€¥€Å€Å€å€å%%EEee……¥¥ÅÅåå‚‚‚%‚%‚E‚E‚e‚e‚…‚…‚¥‚¥‚łłå‚僃ƒ%ƒ%ƒEƒEƒeƒeƒ…ƒ…ƒ¥ƒ¥ƒÅƒÅƒåƒå„„„%„%„E„E„e„e„…„…„¥„¥„ńńå„å………%…%…E…E…U…U¥E¥E¥U¥UÀ…À…À¥À¥ÀÅÀÅÀåÀåÁÁÁ%Á%ÁEÁEÁeÁeÁ…Á…Á¥Á¥ÁÅÁÅÁåÁåÂÂÂ%Â%ÂEÂEÂeÂe……¥¥ÂÅÂÅÂåÂåÃÃÃ%Ã%ÃEÃEÃeÃeÅÅååÃÅÃÅÃåÃåÄÄÄ%Ä%ÄEÄEÄeÄeąąĥĥÄÅÄÅÄåÄåÅÅÅ%Å%ÅEÅEÅUÅUÅeÅeåeåeåuåv¦¦ÆÆææ&&FFff††¦¦ÆÆææ&&FFff††¦¦ÆÆææ&&FFff††¦¦ÆÆææ&&FFff††¦¦ÆÆææ&&FFffvv††%v%v%†%†@Æ@Æ@æ@æAAA&A&AFAFAfAfA†A†A¦A¦AÆAÆAæAæBBB&B&BFBFBfBfB†B†B¦B¦BÆBÆBæBæCCC&C&CFCFCfCfC†C†C¦C¦CÆCÆCæCæDDD&D&DFDFDfDfD†D†D¦D¦DÆDÆDæDæEEE&E&EFEFEfEfE†E†E–E–e–e–e¦e¦€Æ€Æ€æ€æ&&FFff††¦¦ÆÆææ‚‚‚&‚&‚F‚F‚f‚f‚†‚†‚¦‚¦‚Æ‚Æ‚æ‚æƒƒƒ&ƒ&ƒFƒFƒfƒfƒ†ƒ†ƒ¦ƒ¦ƒÆƒÆƒæƒæ„„„&„&„F„F„f„f„†„†„¦„¦„Æ„Æ„æ„æ………&…&…F…F…f…f…†…†…¦…¦…¶…¶¥¶¥¶¥Æ¥ÆÀæÀæÁÁÁ&Á&ÁFÁFÁfÁfÁ†Á†Á¦Á¦ÁÆÁÆÁæÁæÂÂÂ&Â&ÂFÂFÂfÂfÂ†Â†Â¦Â¦ÂÆÂÆÂæÂæÃÃÃ&Ã&ÃFÃFÃfÃfÃ†Ã†Ã¦Ã¦ÃÆÃÆÃæÃæÄÄÄ&Ä&ÄFÄFÄfÄfĆĆĦĦįįĿĿÅÅÅ&Å&ÅFÅFÅfÅfņņŦŦůůÅÖÅÖåÖåÖåæåç''GGgg‡‡§§ÇÇçç''GGgg‡‡§§ÇÇçç''GGgg‡‡§§ÇÇçç''GGgg‡‡§§ÇÇçç''GGgg‡‡§§ÇÇçç÷÷&&A'A'AGAGAgAgA‡A‡A§A§AÇAÇAçAçBBB'B'BGBGBgBgB‡B‡B§B§BÇBÇBçBçCCC'C'CGCGCgCgC‡C‡C§C§CÇCÇCçCçDDD'D'DGDGDgDgD‡D‡D§D§DÇDÇDçDçEEE'E'EGEGEgEgE‡E‡E§E§EÇEÇEçEçFFeWeWegeg''GGgg‡‡§§ÇÇçç‚‚‚'‚'‚G‚G‚g‚g‚‡‚‡‚§‚§‚ǂǂç‚烃ƒ'ƒ'ƒGƒGƒgƒgƒ‡ƒ‡ƒ§ƒ§ƒÇƒÇƒçƒç„„„'„'„G„G„g„g„‡„‡„§„§„DŽDŽׄׄç„ç„÷„÷……………'…'…7…7…G…G…W…W…g…g…w…w…‡…‡…—…—…§…§…Ç…Ç…ç…ç…÷…÷†††††'†'†7†7¤—¤—¤§¤§¤·¤·¤Ç¤Ç¤×¤×¤ç¤ç¤÷¤÷¥¥¥¥¥'¥'¥7¥7¥G¥G¥W¥W¥g¥g¥w¥w¥‡¥‡¥—¥—¥§¥§¥·¥·¥×¥×¥ç¥ç¥÷¥÷¦¦¦¦¦'¦'¦7¦7¦G¦GÁGÁGÁgÁgÁ‡Á‡Á§Á§ÁÇÁÇÁçÁçÂÂÂ'Â'ÂGÂGÂgÂg‡‡§§ÂÇÂÇÂçÂçÃÃÃ'Ã'ÃGÃGÃgÃgÇÇççÃÇÃÇÃçÃçÄÄÄ'Ä'ÄGÄGÄWÄWÄgÄgÄwÄwććėėħħķķÄÇÄÇÄ×Ä×ÄçÄçÄ÷Ä÷ÅÅÅÅÅ'Å'Å7Å7ÅGÅGÅWÅWÅgÅgÅwÅwŇŇŗŗŧŧŷŷÅÇÅÇÅ×Å×ÅçÅçÅ÷Å÷ÆÆÆÆÆ'Æ'Æ7Æ7ÆGÆGÆWÆWãçãçã÷ã÷äääää'ä'ä7ä7äGäGäWäWägägäwäwä‡ä‡ä—ä—ä§ä§ä·ä·äÇäÇä×ä×äçäçä÷ä÷ååååå'å'å7å7åGåGåWåWågågåwåwå‡å‡å—å—å§å§å·å·åÇåÇå×å×åçåçå÷å÷æææææ'æ'æ7æ7æGæGæWæWægæhhhˆˆ¨¨ÈÈèè((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&ˆ&ˆ&˜&˜AhAhAˆAˆA¨A¨AÈAÈAèAèBBB(B(BHBHBhBhBˆBˆB¨B¨BÈBÈBèBèCCC(C(CHCHChChCˆCˆC¨C¨C¸C¸CÈCÈCØCØCèCèCøCøDDDDD(D(D8D8DHDHDXDXDhDhDxDxDˆDˆD˜D˜D¨D¨D¸D¸DÈDÈDØDØDèDèDøDøEEEEE(E(E8E8EHEHEXEXEhEhExExEˆEˆE˜E˜E¨E¨E¸E¸EÈEÈEØEØEèEèEøEøFFFFF(F(F8F8FHFHFXFXFhFhFxFxFˆFˆF˜F˜F¨F¨c˜c˜c¨c¨c¸c¸cÈcÈcØcØcècècøcøddddd(d(d8d8dHdHdXdXdhdhdxdxdˆdˆd˜d˜d¨d¨d¸d¸dÈdÈdØdØdèdèdødøeeeee(e(e8e8eHeHeXeXehehexexeˆeˆe˜e˜e¨e¨e¸e¸eÈeÈeØeØeèeèeøeøfffff(f(f8f8fHfHfXfXfhfhfxfxfˆfˆf˜f˜f¨f¨f¸f¸ˆˆ¨¨ÈÈèè‚‚‚(‚(‚H‚H‚h‚h‚ˆ‚ˆ‚¨‚¨‚ȂȂè‚胃ƒ(ƒ(ƒHƒHƒhƒhƒxƒxƒˆƒˆƒ˜ƒ˜ƒ¨ƒ¨ƒ¸ƒ¸ƒÈƒÈƒØƒØƒèƒèƒøƒø„„„„„(„(„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†ˆ†ˆ†˜†˜†¨†¨†¸†¸†È†È†Ø†Ø£h£h£x£x£ˆ£ˆ£˜£˜£¨£¨£¸£¸£È£È£Ø£Ø£è£è£ø£ø¤¤¤¤¤(¤(¤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¦ˆ¦ˆ¦˜¦˜¦¨¦¨¦¸¦¸¦È¦È¦Ø¦Ø¦è¦èÁ¨Á¨ÁÈÁÈÁèÁèÂÂÂ(Â(ÂHÂHÂhÂhˆˆ¨¨ÂÈÂÈÂèÂèÃÃÃ(Ã(Ã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ňňŘŘŨŨŸŸÅÈÅÈÅØÅØÅèÅèÅøÅøÆÆÆÆÆ(Æ(Æ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äˆäˆä˜ä˜ä¨ä¨ä¸ä¸äÈäÈäØäØäèäèäøäøååååå(å(å8å8åHåHåXåXåhåhåxåxåˆåˆå˜å˜å¨å¨å¸å¸åÈåÈåØåØåèåèåøåøæææææ(æ(æ8æ8æHæHæXæXæhæhæxæxæˆæˆæ˜æ˜æ¨æ¨æ¸æ¸æÈæÈæØæØæèæèæøæøçççç©©ÉÉéé  ))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'IAÉAÉAéAéB B B)B)BIBIBiBiB‰B‰B©B©B¹B¹BÉBÉBÙBÙBéBéBùBùC C CCC)C)C9C9CICICYCYCiCiCyCyC‰C‰C™C™C©C©C¹C¹CÉCÉCÙCÙCéCéCùCùD D DDD)D)D9D9DIDIDYDYDiDiDyDyD‰D‰D™D™D©D©D¹D¹DÉDÉDÙDÙDéDéDùDùE E EEE)E)E9E9EIEIEYEYEiEiEyEyE‰E‰E™E™E©E©E¹E¹EÉEÉEÙEÙEéEéEùEùF F FFF)F)b™b™b©b©b¹b¹bÉbÉbÙbÙbébébùbùc c ccc)c)c9c9cIcIcYcYcicicycyc‰c‰c™c™c©c©c¹c¹cÉcÉcÙcÙcécécùcùd d ddd)d)d9d9dIdIdYdYdididydyd‰d‰d™d™d©d©d¹d¹dÉdÉdÙdÙdédédùdùe e eee)e)e9e9eIeIeYeYeieieyeye‰e‰e™e™e©e©ÉÉéé‚ ‚ ‚)‚)‚I‚I‚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¢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ÂiÂiÂyÂy‰‰™™©©¹¹ÂÉÂÉÂÙÂÙÂéÂéÂùÂùà à ÃÃÃ)Ã)Ã9Ã9ÃIÃIÃYÃYÃiÃiÃyÃyÉÉÙÙééùùÃÉÃÉÃÙÃÙÃéÃéÃùÃùÄ Ä ÄÄÄ)Ä)Ä9Ä9ÄIÄIÄYÄYÄ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ä‰ä‰ä™ä™ä©ä©ä¹ä¹äÉäÉäÙäÙäéäéäùäùå å JJZZjjzzŠŠššªªººÊÊÚÚêêúú  **::JJZZjjzzŠŠššªªººÊÊÚÚêêúú  **::JJZZjjzzŠŠššªªººÊÊÚÚêê"j"j"z"z"Š"Š"š"š"ª"ª"º"º"Ê"Ê"Ú"Ú"ê"ê"ú"ú# # ###*#*#:#:#J#J#Z#Z#j#j#z#z#Š#Š#š#š#ª#ª#º#º#Ê#Ê#Ú#Ú#ê#ê#ú#ú$ $ $$$*$*$:$:$J$J$Z$Z$j$j$z$z$Š$Š$š$š$ª$ª$º$º$Ê$Ê$Ú$ÚBŠBŠBšBšBªBªBºBºBÊBÊBÚBÚBêBêBúBúC C CCC*C*C:C:CJCJCZCZCjCjCzCzCŠCŠCšCšCªCªCºCºCÊCÊCÚCÚCêCêCúCúD D DDD*D*D:D:DJDJDZDZDjDjDzDzDŠDŠDšDšDªDªDºDºDÊDÊDÚDÚbªbªbºbºbÊbÊbÚbÚbêbêbúbúc c ccc*c*c:c:cJcJcZcZcjcjczczcŠcŠcšcšcªcªcºcºcÊcÊcÚcÚcêcêcúcúd d ddd*d*d:d:dJdJdZdZdjdjdzdzdŠdŠdšdšdªdªdºdºdÊdʂʂʂڂڂê‚ê‚ú‚úƒ ƒ ƒƒƒ*ƒ*ƒ:ƒ:ƒJƒJƒZƒZƒjƒjƒzƒzƒŠƒŠƒšƒšƒªƒªƒºƒºƒÊƒÊƒÚƒÚƒêƒêƒúƒú„ „ „„„*„*„:„:„J„J„Z„Z„j„j„z„z„Š„Š„š„š„ª„ª„º„º¢ê¢ê¢ú¢ú£ £ £££*£*£:£:£J£J£Z£Z£j£j£z£z£Š£Š£š£š£ª£ª£º£º£Ê£Ê£Ú£Ú£ê£ê£ú£ú¤ ¤ ¤¤¤*¤*¤:¤:¤J¤J¤Z¤Z¤j¤j¤z¤z¤Š¤Š¤š¤š¤ª¤ª¤º¤ºÂúÂúà à ÃÃÃ*Ã*Ã:Ã:ÃJÃJÃZÃZÃjÃjÃzÃzÊÊÚÚêêúúÃÊÃÊÃÚÃÚÃêÃêÃúÃúÄ Ä ÄÄÄ*Ä*Ä:Ä:ÄJÄJÄZÄZÄjÄjÄzÄzĊĊĚĚĪĪĺĺããã*ã*ã:ã:ãJãJãZãZãjãjãzãzãŠãŠãšãšãªãªãºãºãÊãÊãÚãÚãêãêãúãúä ä äää*ä*ä:ä:äJäJäZäZäjäjäzäzäŠäŠäšäšäªä«++;;KK[[kk{{‹‹››««»»ËËÛÛëëûû  ++;;KK[[kk{{‹‹››««#;#;#K#K#[#[#k#k#{#{#‹#‹#›#›#«#«#»#»#Ë#Ë#Û#Û#ë#ë#û#û$ $ $$$+$+$;$;$K$K$[$[$k$k${${$‹$‹$›$›$«$«C[C[CkCkC{C{C‹C‹C›C›C«C«C»C»CËCËCÛCÛCëCëCûCûD D DDD+D+D;D;DKDKD[D[DkDkD{D{D‹D‹D›D›D«D«ckckc{c{c‹c‹c›c›c«c«c»c»cËcËcÛcÛcëcëcûcûd d ddd+d+d;d;dKdKd[d[dkdkd{d{d‹d‹d›d›d«d«ƒ{ƒ{ƒ‹ƒ‹ƒ›ƒ›ƒ«ƒ«ƒ»ƒ»ƒËƒËƒÛƒÛƒëƒëƒûƒû„ „ „„„+„+„;„;„K„K„[„[„k„k„{„{„‹„‹„›„›„«„«£‹£‹£›£›£«£«£»£»£Ë£Ë£Û£Û£ë£ë£û£û¤ ¤ ¤¤¤+¤+¤;¤;¤K¤K¤[¤[¤k¤k¤{¤{¤‹¤‹¤›¤›¤«¤«Ã›Ã›Ã«Ã«Ã»Ã»ÃËÃËÃÛÃÛÃëÃëÃûÃûÄ Ä ÄÄÄ+Ä+Ä;Ä;ÄKÄKÄ[Ä[ÄkÄkÄ{Ä{ċċěěīīã«ã«ã»ã»ãËãËãÛãÛãëãëãûãûä ä äää+ä+ä;ä;äKäKä[ä[äkäkä{ä{ä‹ä‹ä›ä›ä«ä¬¼¼ÌÌÜÜììüü  ,,<<LL\\ll||ŒŒœœ¬¬#Ì#Ì#Ü#Ü#ì#ì#ü#ü$ $ $$$,$,$<$<$L$L$\$\$l$l$|$|$Œ$Œ$œ$œ$¬$¬CÜCÜCìCìCüCüD D DDD,D,DmAójPЊ†ƒBÄ ëð_€AÒ&à‘7ö´'µ Âì`7‘¼ˆ˜0|Á„R¤"•!/Ï ~xR– TZ Aüàë¥](A²Â –  ‘ôÀ'¦XV °)ÒAN’ *aPƒw€¼ùÈ0E (Xö°—µ…*D)R!e· -¸`7CºD¢ =n‹pà´ª¥P4 ²€u”œ Ìá! hO,Âyf «ùÌ`.cŽ> qðj_CRú?Ïñþ_h‚ûD2ç!—9}›è oàSD€Ýcë>"Añ<€‰ä»¸%ÝÁJ P YN‚ÊtÀ<†n|3sá¶Å ¶(tnC£r;“‘ÜœQzˆÃïSz™‹ˆ\BUBª„ˆ $[<"Ùà-Áh¾ øPgƒªHRAê/PHDB Ã`ž] *èari “HccËPÔZ‡Ð8~ßøÐ~ô€!Va ³ ýÈgîCZÓøÝV†ê´9qÈ3Ž®4uq£Æš4Ðø]GÂê?ɱþM^ ‚õ2ƒ!”jH›R hS@ƒ81ÀÜ´å >ð1‰Œ„¸ü%ÇáIe K(Y"ÂÉü ¿åkÀ3^¶ °°tO¢xÄö$ˆ¼@íâ"•¨nÄ{v%Œ,d ϸN}Âx ªŽ…Tt,Tqb£‹´\ ¢ûzÛÐÅ›†,Ü3±˜½2„i”#g®=pਇD9ÚñÎ׎ãTw£Ó°€û¯GÝz@ž2ñ“À„ž?ý!ÿè&"1 ;YØ;XÚÀêP¨AN v0„î€'tVÆ ¶0\{ãØÑ Ɖ |5áÃE(wšÃ¼Ö™üȈRÜB–â/Ä~ ’º„•Ô&_q2ûŠtP#¢œCâ­Ôn -'ai; ¶p]³ƒD€ÈçFG:3ì1Ÿag@k:tݦèãôG¢:­áÕoxÀƒáˆÿGø Aq" ‰Èà†GЬU`)zÁKÖ `ˆƒp܆à÷]ºèD§%8Ü –á"Ø)ÁcÜ à_ÆÂþ6¤Í †Õœ6¬áÐ[‚ØzàC× l`†ÐD6‚<ÚæÐ–°'0Ñ9†Š90QÉ‚©@J±…ˆÌ-÷1o¹‹êd_S#¿­øÌ3a˜4¿!¥ù ›ülßãó˜ç@:;‚aÜLÌzfcî'q9MhBD ýœ‡ìà—Û¾Ø,ÌÁff åg,ƒ¥˜,ÁŒ$`Gì‚?d­€lX\*Âáq= ‰èc CbupÓ«‡ X8RÁÝŠìP~,ñ`!=a ë»ðEß‚J PH™E„Ê,(Á@ m$Si"¶o³x´eE£*.Ê!vQ  `ù"îpÏ~Æ{ö5Žñ¬wÐTn‚£"yê‹ÇT^°9õêÒVq„ Œ"Q‚ˆïäG"W¸øœ‘DäŠ(Ö±FµŠ¡àUÞð·ªÅ½V/|èŒSb˜£/ëXÒ¾•ð6`Q³Ž¬p%cœQâˆíׇn¼=(AéBµà}¯… D)êÈGVÁð>‚\°倲„” 3j›T‹pt[„t {¡øNŠBtRS`ª›Áp. ‹‚ \iªMPÀàÎs;˜A÷Ͼx„½D%ê"á± ‰$ƒ=èÖ Æ°N73A¹š9hqËC©NJpñ#G‰=øïÀê8QÄ´ ­¡ 6ˆa´.)t¸  ʼnNJp)Hè û0_Ùƒkd[ õÿ¯øD<Â!æà –`(ëbL `_\‚ú䉀ÌLÎø6wÁÎËvXz|CÓâ NQrˆ€,Db;1Ùˆ•‰D¬J'!8™ 1`Q‹§~;ð°©…H-ÛnØ‹ã\_ãäŸ ˶]°4Ÿá¤ÿ ”l¤ƒ€1ˆæÕÇ6®;fAÛ2F(z1Cì°eõˆ¬B.2qÈž@`JPÜöà gÐK>‚ÆŒ4`ÌÉfH9ûÁÏÞ/À~xˆ#ÄA9H Ê@U‚¨Üö ·±*„1T!¥• ,¨p.Ãv¾íð‡ÛT>Ú¢ûØ‹HDZB$‚á$ PLj‚~aó¦UE2ª+G±Z=‹> Yñê®UpÁnÆ v2pŒðg€ãW¸ Üކät8Öư¡´u £Ã“˜÷¡Ç½?šÑüÖS°‚„/à! Ån(# )µä ¯ ˆŸDø(ñAGŠ î°_uƒgà?õ¨ðD‚ $³€•œx(ËÁak X_$Bù"{pËۆʬ6UaÍÑnˆz=ÃÑî >±õˆ{àCß:Ðð•Qªˆ'8 -xQk¦k3X°dEƒ"-ÉánO ÞH^òC¸•ÀËqF[Š4ޱ¤uDl‚#øðæ‘4ˆ;V¡ÚµB@zë\é°È †B’ô€7x»À)6I°Àˆ†ö/´ê<Qà•µ­¨,0a„ ¾€eôœ8áÁ4 GJ:P†pœ3…Ml*kan tbiÃNI°ÒM†ÿ7ø!Úç×8}}ëè!t°8EÂG8˜DÄ‚'ÓQ>šŠaSB³h›@³Eœê.™±tÍŒ `•œüàΰ†u„5\ñªçÃÔn£Œ`ØéÐGN‚<$áá'uÐ{®ƒøšÄÑð'€Bîbs Øh ™@lÊ(¬±ü à‡¾=ð(¹EÈ Ý€^ìcøÀô$¡ CÌBb¥p•+…,(©a`q ˆ^åÂ÷.h°ËE†ÆÄ66!Ì¥e(yùÏÈ -lwøC¿Â8òÇ•¨0&òQ7’Š(dQC"¥X*À°€˜-µ‘m¬‹Ùü^Ï㌌`Ë,†Yd4za£Ó ‹”l\£}ÙîÈæ?Ç1þ;BQÚ=,yécêŠTQl `B bkÓl†›` /ix @Pª7°!½z Ñy®Íp%5)¨ ÿ Wý+¸]Àæ-1h@HBBÄpŽ#„Ý´&í¡Ra “[aÂÛ‡°Ä=†Ž„4t!¾® õpvhƒ³DL€úd>ðA÷‚*âW‘u„‹¬&Á0~ ðˆO„B—/¹x¬dx,Óf˜  ô]£cÇ¢F=3™aœË R(j‘CoÉ~Hâ»ÇÞ:aQÓ ìx'cÜaãýáÇïA*Ò V·L…ºd;M!Úi ¬ˆMdCDR"€iH 5„¬  ð¹à5ÏØ€¡êP/7y¼ ÐlƒÌDb!7q¸JJÂRVE¢(…}Ü+îázk ÓXe^*ð àØO.H9rAæ¸5À€kX!Í!ißFøÂRì—`›xÛÀ(aDƒ LT‚b¿ ù¶…´(/RzŒAbã+TZ Ñž†Œô6á°· ñìoc—¡½ì¾Geò<áñç¥}(¤k #YäH?"BÒB’ê܇Vä3–!œ±D‚ w@º hS@]Žìp-ñh = Ií»œÜàÊ Ph9@AÊp€„ll#ca6A ²TMB¢j۶-†<0éá¢\ ào`ƒ{‡`ì;Îp>s‚t€Šm„Sl$J¡"U ~ÜKöâzÄÖ ¥tE+¢+ QXb‹/HYzBæø7ÀÀF 1Ò±Ž•ŒààgS,™`ÛšÆÜÖ8•ñĝޒt£¿«ýXö´Gµ¢?b‘ûE<‚)ä,u!c©‚tDx"#ÁËœ†\ä@z"Ð "€i÷à'¿ª T€…±-ˆ(5ÁA® ¿Ð]þƒ[ÄÞ ò¸CBƒ”… ”(d¡^K òX^OÂò~DÀÊ&½5èÊfS0yVƒÊ´ @mÐCn‚6h³@”c„£&È!6A  Põ¢µ¨¯p…{„-‹al[ Ï ^xcéwHÊ}†Sì4N¡¢u Ükþã{Øè劇,T;ب2‘ðƒL„d"…, 0Hi‚^@òžY„òÌ)G1J9нœUìâÊtS ¹`EË0 q€SŒnÐcvƒ6¨µ@ÔmF£j6ͱ¶mŽ q#£pï|h=•¡ì­Ò~Ø ~Á ¿ˆUüDbB#NÜŠväL–"d±ŠH´R5à¯:t Ó i‘Lˆ!-Á n ú°OÕ‚ëDZ ÕÅ®(<:ÁáÖ½ð…ï„›L$ÚaAù ÈWA‚º ~»ð†K¸2]Á® p rB’?Àñþüˆ?äBаB„j%( ¬Md‚†11ˆ¨IEBJ+Ã!^ ]`Zëòe“(Ã\†ä2†a”3 Ìhnc^™ôÈÞi†óL9I¡ÊM¿uøÊÿWøù‰GÌJ@B²qăŽ$7°!½Â¦F ²0M‘e ‹)fÛ#6ØÀ€¶¸-ÀÙÔΠ‘›ŒØ+#ÁY {PcÚƒŠ¤U ýìèF0Â1†;p™Û…:¬)ÕaiÑ Nˆa7ƒ ¼ûÏ܆ë7XÁÕº­Ð|8áÀ ½Aê› DÙAîp—8„¹Ä'~ñ;÷ŠKŒR\b­ðo€²?E‘ú.?qø‹ûø_ßÃ$Ñ ÍLFjb5ᨠ¬ÈmfC†?1øèYGBÊ;ȱÞE^Äzö#ò×–¹‹È^B•Rª‘ìˆd_¡"ý SHÚšG:b9Ó5Ä ® heC( âÁ çðO?‚åÌ.`Ô™¤È;ãAߨ…@„•Ô$®¡@› ØVã·h0»A†EÜ2.ᬠdèqäC"(PñB‡õä?¯"†Ä0ŒëgX$ê'P ¦´M5¢„ˆ$@§ë…?\+«±]]‹V¼Zµâð¼…àÂòF’2kÑ“^(h9C\ðç€ÝÿFïú90¡É…¸ÀuÆÉ$I ù‡È”?ú!ÿÑk ƒY6!°9Qˆ¢ŒFÈR6B=ôyï£Ýè€ËÀ~^¥P-*Õ$© =è*ØÁVÆ epc+ƒ…,)`ü?áøEÙB.Ê%™,…54)©¡hA B`ÙÃÎå°Ï-†å<7)áÔC¢{ÍÃÞn ¥Ñ.ˆ•DDª"@E(–ÎD¶r'eñ;/ŠDèR'B¬Gb8±Õލ.&q0‹õT_ª£bÃÌâg4éQ§JoØk~Ãi‰LHÝÀÆî8IaÂKH¤rE#Ÿ¼ýàëSÇZž;¬‘Ýd!py ƒÕ½­èøà‡Ç?QøŠú Õ ; aÙ äÈO&D-"!iA”Š ¤]ø"ïÁâÈ×G‘â<ŒÜcÔö Ë†Xœ3žœðhèCTÕ¦¨Ø“ÆÄž6þ!·ñ õÔo®£‹X@æ ‡1:aQÓ ŽÍØvnÃÁ Hó§8=Áaî §l};c÷n»q@H A*Ò V·L…ºd;f!Û1>H‘òEkâ+_‘DŒŠ$qä#!ÝÈþîHÒ2F‘0L ‚`g99È ‹AZ ÒN‚àT Ó;™Ø;‹ÁÜ^’0„‘„\$‚á? øXV³þRPº’†@d2!« Xhq€CŒPðz‡ï¤?}"¸xŒ€Äd$Ïq&{‰ M‚‚ßø§E< +‘!\‰ O´Z}¢á€ »ÄÅÞ&/ÈÑ~FŒ(€aD¼É>ÆIö3(á™G „h#M›lØÖˆ¶\6Œ´`ØìnÇcƒÎpäKÇ"^9ì!Ïa°ðu‡ƒ¹ÏÎxñ؇ŽÄ=OQêzŠ |Qð€Øÿk‡û\@´ cPƒ„&™!4É hXDò ß‘=H‰êD\þ"çñ¤HÕ"G…b<+¤½$“|$›àžï„÷|(•D¨Š[,RÙb¤^"ð¬oÅc~+ó‘_œ‹30Y™‚Ú_Òø¹öEϲ/S¡z 4`Y£G‚8Çv†;´2¶Ñ•¶ŒãœgãFz3ÐÔöƧ¶6á°· » mÝ|{ãØâ}Gê9xËÄ”lt£c²®•pð €P<Û±æÝm8{iÃè¯Exýìè@@rFh‚3D- ùi 6HY²C¦Â6 ĉ$UÄ"®!ÕÈÆ®G¢8uúXÒÄŒ)$aI&{‰3Üp,{Ϭ}`Žßvø*AT Ob|ƒ´ý úá×EuB+ª ˜d….ô)w¡f± 5ˆ`uḭ̂Îe†Þü6÷áÒ³•˜{iÃÛN ‰±MˆŽƒô–jD³R&p¡3… ÒtN“¢‚¸£ê…T)бN…ŠªUP¢¸ÀÀ±jÅ‹V-2Qi’‹‚\ÂîpȾäÅ÷&0’a„“ Zäb×#$3!˜Ìec(3òqŸ“2„i”#Z4Ñ ÙñÆÏŽ7U¡º­ PpZƒgƒ8çr;:·AÕºãTw£Æh3@ôþǧö>Q𺼠}áü›äÙ‘ÈŽA}¢ í–|„³ä32!™‘1HŠDå‚',p‹€„i°#M×î¸HNòBw’J’PD %©+v‰[´%&ñ)7‰¤Ký"m|kàž½„õì(…qD+ŠWDRº"£d ¬=Åaî+ç_8‹/HYzBÙeË(¹·Å;/G!z9 °`=ƒ{øÇ89À2§1•9Œß´fý£E€,ÔÄÆ¦&6ѰF¸€mÄ{ÜâE(9háËG„t„#±´ ïÑÇ~Ž<Ìæ`iP{Jƒçç?8ýXGêÂ@0Ò†B€‚3 ñ™ ñˆWŒC˜²Å‘xˆãÄTÊ"¦Q—HĺFÿ7øö °d‹H$ZA&= 1èJhrSC’Ðh–ƒ@JµU¨j@ËR ð@g‚o x`¶·µ¸4jÁ£VÉðvO„L òa"‡ 8O^Âzö†ð¬7…Íð.oŽp s€jYRÈE€â,}0;éú@Ò…M*h#öìG·bKNZp–+ı^&b‘3‰ÎŒNtb6 °£¬`)Ä1N!ЦU4‚·¸ð±,E‰b-$Ai" ~ø[÷Âíhø¾¦Eõ20‚Á„ Vüb·ã#9ÈÌ3a˜3äaŸ# /di{#Y:ÉÐÙ­Íh7Dqº#Žhp;Cm{hç9Ç9Î:§¡Õ=ßlvûcÅn+pôÀG¦>±ð=¸8}ÁÃû¡Ý MhAo’ |’0„‘„28!‘ÁòÈ–DÕâ&¯kÄ‹^$h¶#E±’Hì’H?RAú’F ’1Ÿ$øÙ+8 YÀK§2]9ˆmÐCn‚)H8¦„m4$?±!ý‰F8J1Â^ï÷x› „Ù'ŸÁ<þ að¾tEó¢0s!ƒ™ R°b•ƒ"?øËô†_¤3ÔÁž¦ +|i[ãX@ÂÙn†Ët74ѹ¦Ž€pŽss˜æûG7Ú:˜ÔÀÛ„vÜ#Ä$hô{‡£Ü=øïÀ´P}¢ƒúŽÔqHBA[B Ú€„l1%!‰)®}pDIJ&%‘gx‹;ÄgŠ#H­J…jT,+Ña^‹@ÜZâÝÊîPºÑÖˆ/q|kŒà`DzÈQFBŠ2ëñ—_ŒñHgŠCIÌN`Õׯ®¾6L²` ÉLnJcÍþhãXÀ9¯1ÍyŽ¡´u £¶°ðØG†Â=Aèzy¸{ÍÃì`þeó(@rr“Rè‚—D"M!i ø_ÀCÚRÒ‘,à‰gXä"Ç!ÈÌîG@¢:<9ä{${Ù'=I9êJªUP’ᘗ ÄÆ&0‘4ã §N‚pœ·$5¹!»\ Úàr/C‘zeë(ŽÄÌIöb]è š£„Õ'€< lP³b“˜¨¨…@ì*ßVø î WpbÉH°µ¤­ .?qø‹Å¬^-bþþ÷ðÃ$F"1 ±Œ°díƒ4ÿ§øÐžF„ò5Á¨ v|k³ãkXÈÞ†ðô8`ÑÃŽNrpã¡Ðë¥](;ÂqÞ&èy7C×Mºhù1ÇÉŽ?%¡ù-ÿ´ý¤ N jq¸H5ÂB‡B:Øä†Ç$CÌ"aWÈ¢¾Eð²/…‘³@šzc#Ó!ý‰ìIZ"JÑœ”lä°ú%‡Ñ/£I}LÃ’f“gø›?Äç‘'<‰=I êH_*øÃ€‹Ø^À)³M˜ `àrЖ€÷¨½@D§%8×p–»…!¬) ac_ ø_”Âü¦”pÌ£†š 4Õ´; ¡Øpgƒ8ïÐç~‡r@;’éñOˆ}áï NQrˆIàBOÙþÈ‹[ZØ#¬Ñf‰!€I UÁ®˜Õƨ'q8s‰ù„OÌ"‹Â^¦O2x*lñSgŠÑ$V‰"Áª P³ÉžH-Ëqn[‹©(]IB÷ľ ÁIF J1+‰\ €ÈdC-Ån(ÎÏÆv~4!¤i XÌjÆcc­hÜPâ€7í1¿iŽ1˜qŒÃ™àÏéÜÇNæ;MAÚj œxLãÏá÷]ºè>°qõƒâ$ 0¡éÈ'NB¢»4…Ù¤<`!ã‰H”JE{‚+Ü•Œ¬„r÷#—¹ "‰HäòG'’oì“d©Ž%Lq-ÈInBLNbbsJHšRDà%');tIÛ¢O¹b}Ë:¬)Õa\ à`ZgÂÓ>p»€L0’a‘ô  gáÃ?΀Ötéì7OaÇÜ>àuOCªz-ñhÀÄ>!ýÄî ‚ÉDJ!‹ \˜dDÃ"3¬`CD‚$ê'P pK€"i{KØÃ„î(HBD HlRCbŸ•ü¨«=…Yì+§]8 YbÕd« ¸·…ż/x8‹÷¬_½c ~[ðÆ>1ð2h±“EŒÏ°f}ƒAf 0Ó¾Fò5ÈÁ®F ¨m@Ãw™¼Èá>‡ ô9'AÉ:€tã­šlÐîËGvZ<ŠqäSXèzÇCã›ØüK‡â\?êÿT0ì‡dÎ Îq ØHNÂCOBz äˆW$PL"‚a~»ðF¸²5Å‘å@*†ã$7%#É)J""Q¿œ•üä½z%ëÑ2ɉ–LM‹’l\“™øœÏÄô*'¡Q@oJzPø"‡ÁŠˆ T@}–ì°&"1 6PY²ƒ9dË éMJhAÂð0„²X%’Á9÷ ϸQâ‚N°ªu…‰0,Ioß ~ø_Pú€­0Åi†`Ð3¥Ç .8lÊfP °à]‡8p9ÃÛ}Ûèz=ÃÑîh ûE¬@}b~‹ð‡·Ä=¾"Ç!9çLG:bGf;0•1Ä©Ž&%¡1- ¿PMú‚}5騢«Å^)„!L! –ðT·‚³Ošx°,`,â¡g nô[w¢é7I¸½¦í00A!‚ F”b4£8ùÀË,†Yd3¡1 ˜hôÃU ©Ø¬ÆÅf7a¸# ÷o¸‹SZ˜æ-1h:báÓÏvx#Á; Øó³‡œ=Æî0§Ð}>ƒ÷n»q@H A)B J*!P!â¿IPÂJ†-`™kÜ(áMÊ nPVʶVˆÀ´FØ|.ÃჀ dDÃ"&ç@Ï:¯¸5}Á¹ Ìq¸ƒÄD0ê!‡‡X<:Áï7y¸2ƒù” ¢±ˆ^øB÷Â%(øŒ¬„ed$1 ‰6˜I´Â[Ø8š DÑ'_±:ýŠ8Pq‘ˆ@§šE<Ò*¾1UñŠåØW.ÂÆð7€µE¨¢.Apò ½x]ëÂüñçˆÂšÆÖ1|Á‹æ •|d«ã2Ù–ÈЀØ4Þa¦ó mäko#hÚFÐÝ•ì¨8>qÁóŽEèr/CŸ øhÀ¨Žó°7‚(¼Eà¤ñ'ˆ/ùAÊ ­mlƒ è@õÍ®h@Ë‚\ ˆ`„xˆ#ÄA+ƒ \NE‚r,gp£;…P(*AaR [¹BÝÊÄ`¾#'d1;!—: ¹Ði3CIš"àÙþ<7ñáÍ"iv Ãµ`ô ÕÜ>®â P„Ä Ö!ßáÿ­|Ekâ8ÀÆ‘”ÄŒ¦%;A)Ú …L(ânÁvŸ„øD(™ÁDÎ \¼Råâ¤$€¬dx+ùÑ_΋4\Y¢âÚªÕPº ÐH/XQzÂŒ ``c’„ljF¢ÞðƒÜDâ!Í!i¨ÌEFb7ƾ0‘IÄŠN%+¡)] €ÐL‚m•l¨žÃÄö(Š!DQ XpR£}è¬=Åaî+è¡_E 0Y€‚ÙeË(¹¾Íð/G!z9 `@£{ø1JÁŠV +ài_€|àí€l>ÄÁö&‡@„:WT"º¡#h @L2Ba’â Ÿ.ô)w¡Y ÈðY¬BÍb?¹ü†001Ží whg 9žÔð†Ý6èAÄÕ&¨tƒ¤lüï䇴¨=¥Aú‹ÔX‚„×t&»¡Cp €T4B¡¢ä°¯%…®°-uy? Éøa¨ @@ʆ†P42®õ w¨oƒx¬žäô‡](:éAä«%X|ƒä|ùðÿψ4ÈA¦B“Ô˜‰ýOè#XqÉ H`"Pbƒ—}D»ê&¶ñ5·‰ã@O†J2P¤÷E'º*qP«ŠºàU×¼2á7„¼$·0u¹ƒä Ág38Dò' ¡ºŒ%Ôa<6 á°Re“.o€«|’,,‘aqì `_ÓBþšÊàÆWi3H!§¢ =mMCjj)`áK?Ü9þáÝŠìPz´ƒÕ¤†Pü2ˆ@¸ÂYšÈˆ.„At"á± ˆî¸GuÂIHx•¨„­D&@12‰ÆXN2Âø€£Eâ)ž±LõŠ”Tì ñšŒÐ?ËAþZÈà†Gg¼#=á'‚ <M8ÂiÆ$@¡">”)ô¡]8 éÀZ¦BÕ2 »ý40±¡’Õ –¨h@ÐÜÖä†íp7kȽEèu‡ƒ¬<;ñ؇ÄH>"Aþs󘃄 !–q ³ˆ› DÙ4[¢Øoƒx$ôñ'§‰rÀK–j*QPéïH(PQB‚ŠIüROá;)ØD§%8ð„¶@%²:ñ ׈R!‘\Àªæ|,kápÀ †_ˆBüB»@ÅÚdT3"¡¦v 3°mCh àµ;,9ÙaÜ^âðzoÃÓ~rûÌ@–b-‘h‡ã„?"ÎñwˆêGPBGã?•]„ªì&+á1_ ÀàN}ËîX¢ÑEŠ·°…½„c #a&V 2°LíÂgn€ Œ:¬)Õa\ à`Z[BÒÚlà»g„0Œ!‘ ŽgÈÃ>FË`Ö[çø7?ÁÇ‘<ˆu<ƒ©ä%0ñ)‡¿˜=üÁýGê8‚¶„´!ƒ± ˆ–pD³‚3/™x$ $ß&ø‰nKp‚hþGð—Äì¾(=‘Aì„| #àa,d c NqBsŠu€£¬Rä*—!b Ð[åß(ÏP¾z†* 1Q—é ¿HiRƒJ”*°ÙU‡ø8ÁÍŸløvÀ¶‰0ôI‡×Ð>¾‚U¨„:!Ð!æ!1¯ Exb9$É ‘­Än%A* †¬L5bnów˜IDBJ"(°™E„ÿ°'ýMM jhV¸µÀ„´ …Öˆ.´Aƒ5 ¨d%ƒ!,ßpÎû†­`5k¸ë ÇXqŒÃŒf<`éㄜ<$áîˆt@Ãø6 —Á¾[tBÛ¢$p#€ŒtDc¢#öA² 3I˜¢Z&Ñ0™îDÏr³€•œâd'!Eú /ÐTãB§à°w¹<-Éá{É ÞHbJƒTkÐË^†x4ƒÁ± ‹øo¸}ÀÇ0æ9‡gP;:ç59¨}2é "‘ˆ>(AñBè芙DTÊ#ü dH«"Rì—`˜DÀšìŸ`0„)„!Y‚ ÌYÅBÎ*G`º;\0:á8 yÀg2Ã9–¢ÀÕÞ46ñ¡Å )tš¤Ðÿ°ïý‡µp=«ú½Õè‚ ![ ؈ŒHDbB0¥…(„| $¶q%³‰cèKADÎ &pT‹Â¤^ü ¯á´Œ-¤azk ÓXaÿƒüUðʯ†‹4X°S ‚˜om{h±P劇aØ;Áæ 0H|ÚƒæÔ ±eˆ9xA˦Ý0ŠNDRr#i¡M ìHbQŽŒpR½B•ê…`¬+–Ü,¶ás ˜À`*ÃVàÀÇm´3m¡¨Î Fpm˜ClÂ:áÔ‡Dð:'Þôèzÿƒ×ü•ðü¯ˆÈ@ÞBS¢˜ˆmCh"ôq£ˆò G•J PHT@¢æ@¯2¯-x¡y? Éøa¨ @@ʆ†P42®õ w¨oƒx¬›päÛ‡](:éAä«%X|ƒäø`ÿÃ3œAœâzÓЉöÄO¶#SÁž GÁÿŸüøƒ@z ÓÑÔ 6 aÂ}ès÷ƒŸ¼Óðª€=Tø3Á˜e (¯€Ý|!d9 !Õº­ÐxÓCÆš PøJ‡÷Ø?¾ÁÀíhs“ƒœœºðíׇ¤@="öŠ´P€úÄÖAꇆ<4ïx(ƒøÌ `íœ<ìáôȦ@wþÿöÕÀö®Ì|>câï“è¨û@/ÚWÀ ¾¾<ñà-hð ·ÀU¾P°"… P @)H%¹(¦à-7`X ÀU¥­(Ó@¦šF€:4È@Bo¸}Àd€Û$³ 5£$  ‹ñ_ˆ"rÁ–xÀCÆÐ¦€ƒp -låà?/ްu€¡m h'Ë>\ ЀN„jÀV˜qÈ%’Á,– ?0Iù‚FÐ6€P€ b€› `\{Û¤Ý ´ª¥P,¡e àX/¸ÀÀ«à_*bS tS¤‚”Œ¤`nZrÐú@×Ò”`4£šð ׀б…ˆ3œœä ÆPf2ƒ(˜DÀÈ@1|Á‹æ 9°a̓81À¿Oúx/=Áyî ­]h‚áä ŒUb¨"Aúw0C¹‚¤ ‚F0ïÿxÓ >™˜´Å ãîp8~Ãô ý@oêvT² ÛˆÜ@6RA²’ sÀkžSôŸ Ò¾•ð4&¡0 äg 0€ ÉÛNØ+Ì^d Ê€VT©@J§Æ>0)UAJª ,°Qe‚ ·í¸&Ñ6Œ ŽàLwYÌ€ý¶í°>ä÷ ™À|ÎÝØîÀõi«H<ÊæT`x›»ÜÞàìêgP:ªÁÕV†Àt6™|Ëàä 88rà ú oÑu(©@Û$Ù 63±˜ e°k-ƒP €`ÑÄŽ 3ÛžØ Î fq)`KÇÎ>p1]Šì 0Pa‚ƒP€¾ ðP.æAw2 ”\ ‚Û@Ú´_¢ø,uAcª ö@W²Tü"§áKšXDIB"J󇘄4,!¡a X¸B6°n@ƒr” ”¡˜À@B²ã0ƒð˜„ÀùçÏ8=ðAï‚X zĄ́e@ñˆ ;±AÝŠÅ@v*¨TB ç½=è9Sʘ-°qmƒ¨ @Þð6î·t “l˜Zü×àÔg£84ƒÁ¤ øPgƒ4´¥ Ê¼Uà2AúΖp„ªì%Wa(à GI®‚MtL`’cŠ€$T ¬ `G¡‚= É ŽIi°#M_ÂøEˆ,@BÀŠH"@Á®}pChBB¶ …±$Œ!$aý7èA)B J$Ð&„œ àý¶í°>×ö¼‘ð|ƒÚ¸ÕÀô=¡èu ó¨OÂxž§@:á8' Á6s ³˜MBhš%™,„ÀÌ&a.& q0KXŸ0”ù„Ÿ4$ù¡%À .HàBG ¡|Ô#æ¡ÝæèF§‚5<‡€Œ<X€"ÄÈž@Dh‚#Dô ‡¥3!˜ hS@B‚„]‚è„ ä o!: Ð?¸ýÄÈ ~EèÈF@÷ó¿˜Vy‚³Ì «ùW”*¼¡T  PTB£ú ¨8T)¡L! aR…”(‚¤è(¿AD 0PxƒÀý0Ÿé„ö´'µ¡;  ÝNXBrÂs°›„Ô¸&¥Á2ï —xL2Ba’è —E°È%†A* P`IíOhUÀ’®Œt$c¡ Þ ðG›B<ÚÄpŽ#„g¼#=á—¼¸EOÂ*~1‰Œ„C"!qV а[ÞÂÞöڶЮL-rai¸ MÀYêÂÏV]²è ,xaaè @Wö¿¶Þp®ó…oh+{AYæ Ï0Uð¯€\ÀªæN4*q¡Q™ ŒÈSÖ‚ž´Ö`¦³,œ)dáI HQ¶Â¶IÀ¢N Ø(NÁ@ ðOwÂ{¾ºÐå„',!7 ¸HM8ÂiÆ(°™E„Á0& .& q0Jó‚Wœ˜ð”Ç„¤$í!Šº UÐb7þn°Ãu†¤0¥!ƒ à`PC‚÷ð¿¿…õÈ/®A{— ܸ^b‚ózð»×…Öˆ.´As® p\tÂã¦úзօ¶ä-·!k“ \˜ZgÂÓ>w³¼…–,°¡c_ øXNBÂròÀ¯–s´+¡ZÇ Ö8V"±d«$…PŒ*„aQË ŽXSãŸ×𦿅,œ)dáI HQ¶Â¶H0¢A… Ø(NÁ@7 ¸h„CD"ðÐ'†z43Ñ¡œ™ äÈfµÃ5®Ž0Ìq†[¼2Ýá•- ©hdÎC&ràȯ=¨1íAv k°bæÃ6˜àÄÇÌ0öa…¦ -0`ìƒdPÀÒ…þÄ/ö!}‹ ìX^åÂ÷.•€¼¬Ý.ìu% ©(\Ææ0 ¸yºÌ-Öal dhZ“‚Ôœ€ð´…˜,ÀAcà Xm‚Ãlõ௯tà+§[+ ÙXV;±Ømð«o†æh73A·¦ ½0mCkú@Ú†É6Há°… „(k¤C]"Ë`Ö[«Ð5^© HiσN|T Ò¥X4jÁ¡{ ØgáÃ?ÚÀÎÖn|3sᙫ ÍXeíÃ/nYÊȆNØ2vÁ‘ Œ€cç8×`Æ»-@1j‰C JaÇC:Qˆ à0W€« X_¡Bý Ä`¾#èä/G!wú ¿Ð]‚ì ‡ì8a¾à ÷oACz ² Ý•äØ7&Á·t » m`k8ÀÙÆÆÄ66!¯¤ } krC[’º0Õц§ 59§¢ =iqÃKŽ:ÑІ†P42Ÿn ûpgXC:³°Í†d¸3%Á–ï ·xe2C)’+ÀÉ^BX2ÁŽ¢ ucÃÆ¦ðÅ7†!$1 !†U 2¨z|CÓâ„€ô$š<ÐÁäÄ& xÀƒÆð¬‡~\;òáݼíàvþƒ·ô£€íat; ¡Ö›´Øu6C©².Pér‡DŒ:$aÏHz@sUš¨¹ åÉ&90¡ÇÃ>qsËž< ááœ8<á¿Ú þÐoyƒ{̽Ýì†ç”7<¡·¿ ½ømrÃk–8ÀÙÆÆ`63¯Y zÈkLÃZf³ðÕŸ†¤d5#!§% 9(i?ÃIþ0°Ñ…†ƒø4ÁŸ øPjYRÈø`ÿÃ÷Ø?¾Áü4á ~œƒôä‹üXÜ>àáõEª(|àÃç øíÀ`>î$q {ƒØÄ«õ\‡£=¡ç8yPCÊ‚4Ðñ¦‡†,<1aß°ý€wo»x¾íð‡gP;:ØÀu‡ƒ¬<?€éüHt:C¡Ð€€s‡œ8¿`åû(9@AÇÜ>àqsËž:áÔ‡Ô86¡¿¨ ý@om{h¸àÝÇæÌ76a· ¼hmlƒkdK€Ú\8@Á0…M*h!8± ňGˆB¸AóçŸ8||Ããæ€ø ¸=ÄìIbHzÔx„€ô$™´<Í¡äG"8x”ÃĦ`ð#yH;ÊAÜà˜v‡Ã´> ì Xx:ÃÁÔ ˆtƒ¤lè‡9œ9ÌáÍTj sƒ˜´Š€ìT_:øâ*âWŠT„R¤"z‘Ôˆ—œD¼â$>!ðˆ¥DE*"Áv|¨CåBhë@†ö7°!¡a `ìCby³È…4) !/Q zˆDÌB&bŠ|PƒkÄ^ »±݈'äA?"@(Š„ T F4 l@Kb™È£ýËþX‡êô?W¡ø—ĸ}¯íxI`úKʈ>TAð|ƒà{¨CÝBÉ@öJ«H=ZAèÅF(yºƒÍÔPðò‡‡T<‚¡ãØ|àøö÷´‰@H?ø\zà#¼qãˆèG@¢8\Âà³m˜#R1‘ˆÍ„Fl"1ŸŒøŒÄ`"äÑ&ˆ²,E‘b*ÉVHŠHR@"tQ¢ˆ– D°b#ÚЈy„CÌ"±ˆyˆCÌBnãp†¤Ä5&!‹ \[BØ‚é§H„½D%ê!€ˆ<4Aᢠh‚ÉDJ ñˆ,@áb(¸€Ïx ¤ý´?í¡ý«íX~íÃ÷n®ðýwˆô@ç¢*1P•ùįÎ%eq++‰SJ˜âS7™¸”c„£$þQ'ò‰8ðIÇ‚L¬e`’ÀÄ–$•¡$­ `HóEï/x‘Ĉ¾$)Ñ!N‰lHb?øÈ\zà#¼qãˆçLG:b8*ÁPšlÐ#GA:Ë,FYb0ð‡€‹¿]ø"Òˆ¬´Ee¢)kKX‰ÝÄNî"V¡µŽAð¡¥E *(PQB‚Š Phbš Р…'ä?$ òOÂzÄÖ žMòh'u‘;¬‰Ö\N²âsîŸpœ‹äX'8 ¹ØMÎÂl´e š°Õ€&á4 ›ÄLÞ"e(°˜ÎÄÆv&á0— ~xKóÂ]Üîà—„¸4%¥-, c KW¸ø›&ÄÙ6&»¡5Ý ÃV¢¯Nzp«o…[|*ÂáV ªÜUVâ©'I8©åÅO.*c‘SŠ’¤T•"£ȨbEC)þOð y@SÊœÀæ¦Ì6`)˜qLÊ_Rø¢–°à¥#)).1IqŠDèR'B_zø£sÅž(ÀÑFŠ(ÈQFBˆpC€¡±Å Ž(PQB‚Š DPb"O x Eª'õ±?­‰úLOÒb}€ìŸ'Äù>'»á=ß ëOX‚yÊÎPž:DñÒ'~ñ;÷‰Ü8NáÂvF²0YDêÊ'HA:B ËlN[bqKŠXœ„àÔ&ú!7Ñ »MØ"np›LDÚb)H =QèÂŽLr`£a(È¡FE .@QrŠÈV@¢s…œ(ADj 0Q‚†ù7ȡŠþ(SqB›ŠôP‡¢ƒ*P žÅö(¡@Í äP"€ PŸ˜DüÂ'Ø>À ò|O“â{ð߀žÃÄö'¤q=#‰å˜O,ÂxžÄð±©EJ,Ráb— ÜXvâÂ@°2E’+ò_ ö¤Wµ"¼àÈ®¨…uD+’±\•ŠÞlVób¶ °X­…hÄ+-!Yi ÅV(B¯²}«ˆ…\D*Ç‘V<Š«@UZ©'I8©åÅO.*]QRêŠLT‚b¢jP¨0EA‚)üqOãŠ{üSßâžð(§I:H)ÅÁN. mˆSlBšÔ¦nE3r)‹ñL_Š_Rø¢–ý·è¥€Å,)P‘J„ŠQRˆ"“.™p¤“E$š)ÁH¶ B,Rbx{À£¥Å.(ÜñFçŠ3¸QÂŒ&a0¢ÑEŠ(©aEK &ÔQ6¢ˆÔF ¢ EJ(tAC¢ TPÒ¢…´- ¡AE (BAB ÇÄV>"±ˆ€¬ Å`^*öqW³Š¹´UÍ¢­Œl`«7EYº*¾1UñЬlUcbª:QЪPR€*‡T< ž\Tòâ¦4è©uEKª*M±RmŠèTB£2¨ŽDp*áPŸ ØTŸ|ûà§ …=)Û¡NÝ sS˜›ÆÞ0¦¹E5Ê) AM d(S!B˜[ÂØ¥Þ….ô)l±KeŠW¨R½B•"©¥…(´)7‘I¼ŠK(RYB‘éH¤N…"t)!H9 >DQò"Ü…ä(¶Ã…¶-›lØ `ä["Ö¶µRŪ–-=Aiê ItZK¢Ð´… ³ÏEžz,ÚÑfÖ‹0ØY†ÂÊ¿Uø²KÅ’^,yñcÏ‹ØX¾ÂÄM"h°¯E…z,Ñ`–ŠþtWó¢¾£õ¯vÅ{¶+Ñ1^‰ŠðÈW†B»jÛP®¢Eu+š\Ô ã€W·ÿ¿ø­ÍÅnn+ea[+ ÕpV«‚´­¥h¬óg˜+-!Yi È(VAB±ˆ€¬ Å`^*öqW³Š¹´UÍ¢­sk˜«*ÅYV*»UØŠ«@UZ©ïOxª=EQê*AT œÌTæb¦91È©OÅJ~*IRH ޼Tu⣨‡ÅD>*qP«Š‚ TŸÇþ8§Æ>0)ãqOŠv S±œÀæ¦÷Å7¾)³M˜ i…yô+À^ íDWj"º‰ÔH®jsP+‘!\‰ àÄW"·‚¼­¨EmB+_!Zù Ô¨V¥B´b£¬ìÅgf+.±YuŠÈðVG‚±[ŠØ¹yEËÊ.PAr‚ ‘T\Š¢ãt ¸±EÅŠ.Apò „p\#‚àT ·Üžæ-ìAob w([¹BÝè·Ÿ¦-µ‘m¬‹j¨[UBÙÉÎH¶:±Ð-‚l \üZçâÖw³¸µe…«,-IÁjN O´Z}¢Òó—˜´„…¤$-¡h¥ A@Z Ïo{x³©ÅN,ÚÑfÖ‹30Y™‚Ì` ²Â…–,¤!e! % Y)ÈNBp±á… ,kác_ tX»¢Å.)p±…ˆÌ,;qaÛ‹ XXZÂÂ@°WÅ‚¾, q`K‹X"¿ ù¯œE|â+Ú‘^ÔŠóèWŸC ÕN¨ÂÆî0pƒ€ ˆ`´CD" À³Fš0a€« ÿà_ÿþô¿B…ú/ºÁ}Ö èÔ_F¢ø¾Åð½Ë…î\/[qzÛ‹Ð8^Âó_šø¼Ÿ…äü/xô Ä^ âðX‚À»ÝÅÞî.êñwW‹¸]Àíh0»ÅØ®.·au» «]XâéÿOøºAEÒ .…at+ Ô\î¢æ­5h¹yEËÊ.PAr‚ ð\‡‚ã[ظž…Äô.!pÙ ƒD\"à H·Ê¾P-äqo#‹uü[¯âÜ…ä(¶é·H-­Ámn gˆ[âÉHJ@² ‘,}cè‹ÿà_ÿÿIúH¿ Eý/Ý!~é ô(_¡Bü)áH¾Þ…öô/©‘}L‹ç¨_=Bù ÈH¾…ð´/y!{É ÚÄ^Ö"ö°½HEêB/G!z9 Íà^oò°•€¼€Eä/qxƒ‹Á`^ ïw{¸»¥…Ý,.Þqvó‹³´]¢ì>aðº×EÖº.¦1u1‹¦Ð]6‚èºEйöEϲ.qsˆ‹˜\\Ââåh+@¹!ÅÉ.8ÑqÆ‹‹\X¢áä ¸:…ÁÔ.!p | [åÞ.ñp·f»0-Ëqn[‹p„[„"Û@Ú¶ª…µT-œ‘lä‹dÌ[&bØkÃXµèůF-oAkz X°ZłՖ¬°µ-E©j-Añj‹M\Zjãçÿ8Ç¢F=1Ò±Ž•Œo4cy£oÓxÆDF2"1y¡‹Í Xðbǃ¬¥`ÄÍF&j1qˆëŒA€b éwHÃVF²0Ì1†aŒ/ˆa|C LZ`¡0š1„ÑŒ#Ðaƒ,A`ÁßFú0iÁƒN ´`½£%)(Áˆ09QÊŒ Ð`Vƒ(ÀIH0Q€:‹þ´_õ¢þÌö`¿ü/Ò1~‘‹ñÐ_Ž‚û¬Ý`¾¬…õd/ 1}‹ä$_!"øZÂнÞEîò/i{L ×@^ºôï§x½…è/4ay£ É”^L¢ñŒè¼/áx.ü!wá »è]ßBîpÈ»GÅÚ>.Åqv+‹­t]k¢ê|SàºmÓh.ŽÁtv  ô]¢çu;¨¹±…ÍŒ._árÿ • \­ä %¸öǰ.4!q¡ ‰„\L"áËX¸@ÅÂ.Ñp6‹~0[ñ‚Þö÷°Æ‚Æ41•ÁŒ® bPcƒå¿(ÅÇF.:1fá‹7 V4b±£÷§¸Å†(,14ቧ J|bSã×ޏÄJ"P1qˆ#Œ>`aó·u¸Ã‚0Ô†  1|a‹ã °]€Â³Æž0¢… $üa'ãwC¸ÁëÆ^0láƒg |`Ãã%)(ÁFº0:á× Ð`Vƒ(ÀBÆ01€!‹ýì_ïbþô¿t…û¤/Ï~x‹ïÜ_~âûHÚ@¾“…ôœ/–Ñ|¶‹â”_¢÷«½X½²…í”/`!{ Ô ^¡ôr£¼äEç"//±y}‹ÈÌ^FbñŒè¼5Eáª/Ñx‹½Ü]îâî¯ux»†EÜ2.Óvœ ²ˆ]”BëÚ^кÑÖˆ.§Áu> §˜]<Ã7Wº¸Í~Fkò3I±šMŒÌôfg£1ߎøÌ Fa2ò1—‘Œ·e¸£,gc8ÊÂFV2™!”É  lec&½5èÉwK¸2Ta’£ ‘ødÃ#ÏxÈÁÆF2#ñ‘Œ†@d2 È@ÈF@21õ¨ŒzˆcÔCÁîÇD†:$1Ä¡Ž% nlcscÓÖ˜Æ|†3ä1”1Œ¡Œaˆc C³½˜Å´†-¤1`¡‹ Ulb«cz£ÐÄòÆ'–1.¡‰u HìbGcZŠÐÄF ò0ü¡‡å ;¤aÝ#!qÃP€0Åñ†/Œ.Àav ¶U°ÂÆ0’a„“ !a£};èÁ Æ 0Z!‚Ñ Ì`žcùÈÀÌFb0%( X`*ÃŽp¿ñ…ÿŒ/ó˜ ù _Íý í¿B…ú/Ä!~! î°_u‚úä× ¾“…ôœ/˜a|à ãÀ_ø(Á@½ä…ï$/l¡{e ØÐ^Æ‚õl«`Í~Fkò3V1š±ŒÒlf“c4 (ÌÏFfz3'Q™:ŒÇ|f;ã1ˆ¸Ì`Ð2ú—Ð »ÄeÞ#.)qHËdÆ[&2Ë!–Y °peƒƒ+;YØÊ£U2œA”â £ðeƒ(MBhÉáFO 2ma“k —Ôd¾£%F*0ɆHü2<ñ‘猌d`ã"&0È]ÆBî2 aK œcüãø0Ç•Æ<®1ØñŽÇŒrTc’£æß0ÆÁF6 1¥a+ eÔc.£”Ä ÅùF/Ê1n±‹uŒXðbǃ[ªØÅ†(ô1;!‰Ù JàbWð€ÄJ"P1ሠ=üaïãžtðÃiH0ÍÁ†n /ˆa|C LZ`§F:0Q„êŒ$üa'ãwC¸ÁøFÂ0q‘ƒŒŒ `Ðc»-ØÁIF J0EÑ‚.Œ`xãÿøÀšFÒ0€ÐŒ,`!cu¨ÓTš 4¿!¥ù *´iU£IOJxÑüFâ4i1£I8h©ÃCð€ÐÖ††´4)!¡I Œh_òøÏl{`3Ξt ñHgŠC;ŠÜPζÆu¶3¡1 Œåôg/£8µŨÍûFoÚ3sᛟ ÙØfÎÃ5Ç®8Í?Æiþ3Eš( ͼfmã2Ù–ÈÌ~cð3‘˜¤ŒÂf#/¹}Ȉ^2â‘—Œµèe¯C,™dÈÊú†WÔ2²!•‘ ©eH#)’LÊ,FQb2€!” œ„dä#&r3Éj†KT2K’X d€## ÈÆD~2È ‚¼dãçÿ8ÇÁ†> 1âQŒuØc®Ã|ãàÆí7h1­1iŒgÈc>C*ÉPÔÆ ®4ü§à;äiß#NcsÓfÆ›64ÐQ¦‚0ôi‡£K§]8Ò·Æ•¾4¤‘¥$&i0#HëGXÒÆF4wA£º °hÕƒF0°ÑS†Šœ4Iñ¢O\hzãC(@И„À4¡ å ¤h#@S˜Ï܆~ä3ꡟU øPgƒ=3é˜Ï'Fy:3»ÁÞ ì4ga£:,Ñ`Îe†s,3‹QœZŒß´fý£7>¹ðÍ—Flº3Záš× Ó4f™£4 ðÌÏFfz3'Q™:ŒÇf8Ã0å‡(Ì †`l2óÁ—ž ¹Ðe΃-Ån(Ë9YÈ2ÁÁ– ’œl”ãc{ØØ†Äl6 °l ~Lkòc^5ñ¨×a†» 5Ï®x p k…[’ÜÖ²†µ”5£A­ e°k-ƒXÖưÖ†°5w«¼ ZÀjÖV°ÐÕT†ª¤5KÁª^ OÐj~ƒS^šðÔ¥†¥,5 © D|j#ãP‰„HÓðFŸ‚4ò±§•9ŒiÌcM›lØÓ;™Ø4ÂA¦ .8iqÃJ­UhÒ…Æ”.4“a¤› "iãG¿=øÑÄŽ 4d£$ h°DÑ&ˆÐü‡à45¡¡­ ähO#AÊPÐ:FÒ4¡  ýÈgîC>‘ôˆÏx†{Ä3О€ŒñHgŠC;qÛˆÚ{FÓÚ6•q´«¢àq÷ xHãG‚8Ú±ÆÕŽ3ŒqœcŒMbhâÛØ8­aÅk·”u¼£­Ojxë.GYr:À¡Ö­ÐunƒªÅV(ê‹ÇT^:˜ÔÀ£¨uC¨;AØééGOJ:oaÓ{™€tÌ¥˜,ÀéFÇJ6:E1Ò)ŽŽôtw££pèžDð:ÐØ„ht#C „ çï?x9òaÏ“yxsËÃÈî@çFG:29ÅÎ(Žnìswcšóטæ—G4º9™QÌÊŽcüsã˜7Á¸åèG/B;‡Ü8Žßlvûc·,¹`í«Çm^;^qÚóŽÕ¨v­C´¢¥í GhJ;5ÑÙ®ŽËvXã²1‘ˆì`‡c;ÁØvÀôv£¯§}8ë·Ç]¾:æ!×1¶hu³C­h ëXx:»ñÕߎ«Üu^ãªaSêfGS2:1ÔŽ ìuc§¾=ð齇Mì:fÓ0–`t³¥(é‡Ht"Añ…Ô|.£àßøøGÀ’=ù¡ïÍ|{àƒÞUò¨÷v»°=ÑîˆrL{’cÛäß öÓ‡¶œ=©ñíOh${A#ÙZÊÐö1±ˆ=Áëþ]˜zìÃÖж€õˆG¬B=X±êÅS z˜cïx@ûœ‡Üä>ßQöúµ}¨Ãì°e€ûGØ>¸Aõ«T}Z£ê?QøúdÓ >¡ô}¡ô}£çµ=¨ùÎÎp>gó8—Ì|¾cåD* ù+‡É\>?ññÿ¤|m#â¡ø‰ÄH>Áð®ƒ||ãù­ÍhþLò`?†ü4߬~ýc÷Uº¨ý©‡íL?b‘ûÕè~¯Côä§ ý Gèj?;ùÜËÀ~^òs“˜üqãˆ?áø—Â`~ïéHûÔÇÞ¦>êA÷R¸8}ÁÃí_jøû2GÙ’@P€䀇$¼ áÃH@)H„€<$d !3ˆœ@bþ$ñ#þÚöÐÿ—Güº?Úáþ×ô`£üiãHþû÷Ø?²Aý’êœTãùßÎøþRGò’?ŒÁüf@Œ‚dt {¡½Èî@æ06ȶD 5 i©!ˆ @À’-hkD Ý Vé…H*@™‚Ì$ DS B™ïHz@pâ‡D€Ò$â /S ˜@IÒNmƒh¤È ÖA†H42A™Â Îc´ƒ¤p Ãöˆ/´Ar² •Z¸‚ÕÄÿ ¯ù`ˆ+AM2 iQX‚ŠÄŽ œqÄH&"A'² =G0‚9„6 ‰±!È!Bh@˜ „Àd%m!+i 5ÈI®BE¢-ެ„ud#.!qŸÈDþB’ô„è„'D ¤!! È@NA÷‚¼{ìƒßdL òasÈ;žAÐrƒr(ƒ‘D1Ô!Ž¡ IHbJC `ÀH†D/•!|© ¿È]þBå(·L…ºd-$!i! )ÈYNB¿‚ü­ì…od*å!W) ˆTlB›’ܣą$(t!C¡CÈzCÉ"I|d;f!Û1­ÈunC£¢æ,‡1d9!ÈqpðC~"ñÝ”†ì¤6¶!µ± ŽHlrCX¢ÅÓІž„4^!¢ñˆ”D|Ò#æ‘xˆãÄF¡"5 ‰ŒHDWR"º‘àˆŸDI""Ió‡˜D1Ò!Ž‘ ˆPäB "QVÈ‚¶D â o¼ˆäSž"œñ»È¥ÞE(²)E‘GpŠ;„Q_"Šù,¡`E¢( >؉öÄO"x9¢ˆDݲ&í‘5x‰«ÄLÈ"fAH˜2Eí’/l‘y ‹Èd]­"íiLº`EÉ¢.Mpt‹ƒ¤[n"Ûq¼HµâE§B-:g‹8¤YH"ÊA&H±2EÂ,¨PB„i—#L¹:ÑÐFˆB4BŸ¸ŒýÄg?#9ù¶ÈͶFbÂ3–¼Œµäe2#)‘ ÈÉF@b2ÓHŽšDtU#¢©éˆçLG4"9¡ÊLŽRdr#±fHã2G¢8uÁ´Ž ¤o×#~¹ÐHÞ‚HÒ@‘þ@ò#ø™ŸHüúGà?õDª$|Ô#æ¡Èø®Gº‚=Ôíh„ŠË$VY"™ÉÎHDè’$ì‘'dˆ¾$Eñ" PHz²CÕ’¸åÄ•¢$­%II*JIJ‚JTOä’$“•$œ©$¹‰%ÌJòP‡’€” Ÿü$ÿá'ÓI>šIíOhyL“Êd«Í%^i*ÍÉVnJ«¢U]¨•D„·!%¹ -© mHK_RZú“ p˜K„Áø&Á3LÉšfñJ wp;¸L&6›«ÀÕàm6€L&y<«ÀÕàp°8X° XK%«ÀÕàl€6@L&ÙìyÀ¼àd°2XÐ è¯WÄÀâ`W°+Ø çóƒÀÁàd°2X¼ ^ßoGÀ£àR ) ŸÏyÀ¼àd2H´ Ú¯WÄÀâ`ñPx¨=,–ÄbGÀ£àS)€ Ïç~@¿ d°2X¤ RápÄÀâ`çðsø9üþÔj¸ÁÜ`Gp#¸ì vÛmGÀ£àU°*Ø çó@Ç fp38¼ ^‰Ç@ã Ûpm¸7p¸ ƒ†ÁÃ`çÐsè:¤RãqÀŠàIà$ðì v …GÀ£àX0,T ªùü’ÀÉ`jð5xÌ fóyÄÀâ`vÀ;`/D¢ 5ƒ"Á‘`ÎÀg`4P( S©ƒfÁ³`Ûpm¸8l6K%ƒ¶Û@?0˜@ W+.À—`L&H| >z½i€´À^p/8, –Y,«ÀÕàjð5xÌ æ\®íö€~P?(! †Á`‚SA) œðNx(Ìf —K‚¾Á_`µðZø/ † 'ƒ‰€ÈdH2Ün ÷{ƒTÁª`ÛPm¨8H$|>ãÀqà?0˜ÐèW+€À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œ ÇcƒR©@ÜÐnh:(Ì€f@8ðxètóùÀŠàI$Èì vP@¨ X0,D ¢çó’ÀÉ`j`50L&w»öÀû`ƒðAø"ŒFú}ZA- ›pM¸(d2 f3¥ÁRà­0V˜+ìö O§‚ívÀÂpa82\. ŽlA¶ æ s ŽÇc€ÊÀe`5€<óù @† Ep"¸ì vårGÀ£àX,d 2çó’ÀÉ`jð5xL&w»öÀû`ƒÀAà"l6 ‚‚Z-@›MÈ'Ìæ 3‚’I@©pT¸+äò o·ƒƒÀÌÀf`5L¦ Ôê›ÍÀ&0 ŒFÇc€Ã€aÀ2°X<½Þ€üÀ~`E`"°Üî»]GÀ£àV+Hœ Nçó’ÀÉ`jð5x f³õ€úÀƒðAø"ŒF ‚S)À—°KØ'<ž áð‚ŒÁF`«U€-Ôê M&ƒ>ÁŸ`Ùlˆ8H$‰DƒÍæÀðø Pc1€˜ÀL`,p8 D¢+•€ß@o 9 ÐÌæ?€ŽÀK°%Ø| >x¼e€²À^p/8, –¯W¼ÀÞ`u@: Œ/‚Áà‹ÀEà$  kµ‚iÁ4à P*\. r¹Á‰àÏgÈ6„B ƒŸÁÏàîÐwhl6ÿÿ€†@C &0 ŒÆÇc€±ÀXà2°X øü‘È€üÀ~`Ep"¸øü»]GÀ£àX0, çó”ÊhÐ4h( ”+•ã€ñÀ~0? ü~›M‚9A ”€J@'  Z-ÏgÀÂ`a03d² S©ƒmÁ¶àá°pØ:-–€`À0`P (üþÿÿ€˜ÀL`&0 ŽÇc€ÊÀe`8ðx¬VóùÀŠàIà$ðì vWÀ«àX0,œ Î;“ÀÉàjð5xL&w»ó@ù €@@ !0˜Õj‚^/@£ QÐ,@ Ùìƒ A Îðgx5L¦ ´Ú†ÁÃ`ëuˆ”Ê7›€c@1 ° Øüþ#€˜ÀL`'  Žñx€ÊÀe`8ðx`°óùÀŠàK°%Ø ŠZ­Xà,pœ ÎK%’ÀÉ`jð5x4šßÀïà ?"d2 Y¬‚‹E€¯W€.  ÁŒ`ËÀeà4”J ŸσˆÁÄ`ìÐvhL¦ètMÀ&à° ØœÎÿÿ€˜ÀL`&0 ŽÇc€ÊÀe`4À`<óù @† Ep"¸ì vÎgGÀ£àWÐ+è çó@Ç d°2X¼ ^ÐhÇÀãà{ =!x¼ J¥‰AD «°UØ-ˆ ¼Þôz@ÂaH2< ú}f³ärTl*6Ói€M&€ð øl6ÿÿ€@€&0 ÄâÇc€Äb2°X<Ç€üÀ~`C@! \®»]9œ€Qð(øü þÎçyÀ¼àbP1(, –¯W¿€ßÀwp;¸ Ìf -–‚}>À© T+Àà _¯‚×Ákà¹p\¸0@ ‹EƒAÁ àÛmˆD¨"T!¨Ô¥AÒ¡e²ÈL¦7›€fÀ3`P (üþ*˜ÀL`,p8 Ž+•€ÊÀe`6 <óù€€@Ep"¸ì v»]GÀ£àX0,¤ Rçó‡@àd°2X¼ ^+•û@ý Ž€G@&À` 3‚¥ÁR௰WØ,x< û}‚×kÀÀ`H28 (”oA·¡ ˆÐ‰¤DÒ- –Šö…{@ 0˜Ünx¼fÀ3`ðø €@ª€U@, P Tª+•€ÜÀn`8ðxètôúÀŠàG@# ì v^¯X0,œ ÎK%«ÀÕàq08˜„“I‚YÁ,àNˆ(Ìf —K‚¾Á_`¯°WØ+ìö kµƒ Á„àÉÀdà5  »Ý…©Ôƒ¸‘ÜH8Ôjs ¹€4À`p ¸t:žÏv;ðø 0˜u:€±X€,H ¼Þ|¾ãÀqà9ˆÌæ'ÀŠàK°%Ø| >ƒÁp€¸@`ð0x´ Z‰àÀð`… BÐ$ tºtÁ:`£0Q˜*\. ²Y¶Á[`³PY¨/ † ,,–@Ôðjx=`°R )‹ÅÁ´ÚHX,âqMÀ&à° Øl6›Í€~€?@!ˆ ŒÆÇc€Ãa€3  |¾ÈäãÀqà?0˜ü~a0.À—`Qð(ø çó €Ð@r9àð.,Á`“IÈ&x< Óé‚FÀ©pT¸*\. û}‚Úm@½@^ 1L¦ ¹\ƒRA© ÷{ˆ}>ŒÏçˆãq€ 0˜ètÀ€e€2À l6½Þ€À?à(0 ”J+•€ãq€3p¸ `°Ç€üÀ~`E@" ì vÌfGÀ£àY°,Ø ˆívÝÀîà~?!0˜çs‚ZÁ-`œðNx( 3‚¥ÁRà®ðWx-L¦ ­ÖƒÁ„`Æ€c@3¨Ô f³‚BA"‘HD0"‚ Á2@ @ À`H¤MÀ&à  Ðl6€¬€V@* ŽŠÊÀe`8àpôz+.À—`L&H| >ƒÁÀÆàkp5¸€Ûí‚ÁàŠ0E#øü ?Ÿ‚\.œðNx(Ìf šMÏÁgà¸\/Š 1ƒ&A“ Òðix6Äb …n‚·@ð@x @ Ói€6p ¸Ün‰Ä€fÀ3`ðø¸\c1€°ÀX`,p8 ¬VÒéÀŠàG #Ð\ ®'“a°€_0/˜8 îwÔê{=È ˆDM&‚(Á`pH8%¬Ö Ôê¥R€¯°WØ-Èä ÃრÁ„àÈdH3¼Þ E¢ƒmÁ¶àÛpm¸0 2À àðxL¦Ói€4À`p ¸är›Í€y@< ðø ŒÆÇc€Ìf8ðxÌæX,1€˜ÀRp)8 çó’Éip4¸¼ ^‰Ý€îÀ|à>p ôz£Q‚AÁ àÃ`a°'°Ø ްAX ¿ _Ð44 W+ƒnA· ò yVŒ+FV«V‚+A€Ž@5|¾ ?ƒ,Á–`Ò0i8\.f³´Ú@å€rÀ;Øì …ß‚ïÁuº€ÇÔcêj 5ÔCê! ð†x-h´H$ ÷Å{â}>€ˆdD2$³Y‰!ÄâL¡&PÈ dP †G£àá0p˜3h´ ‘ȃ‹Åð‚øEü"þ‰ƒâñA€@X¬o7€€@ L¦7›€N@' ° Øüþc1€³@Y 3àð@ óùÀŠàK°%Ø| >C¡`À°`^p/8, –O'«ÀÕàpÐ8h Ðè4‚Aüþ®HW$<ú}ÂÇác1¶ø[|.{=ŒÓFi¡¹Pܨh04F #KB¥¡ˆH«`U°.1†óÃyâÛQm¨ê,u*ŽGûƒ}Ád@² P|(>Y¬„ B¡ðŠxJL%&ì öþB!Ú í_¯á[ ­ÐTD*"Þ oÒér€¹@`Ø0lÎ gP¨¼Þo7€4À` 0˜Ün|¾p€8@%àð Ž4šãÀqà?0˜àpW+.À—`M`&°| >ƒÁ‚«Ui´È`Ì0f!Öë F…#Caˆ0ç„sÂAÆ ã­Ö„Âa¤Ò=‹ÅŽ=ÇâÅ¡bÑ7›Œ3?Ÿ‰Íæ„Uò*øÖ\k.|¦>SxÊL&å r†yC<¡eà²ð“,I–/‚‹·Û‚V1+ÿøüTÜ*nç‰sÆ÷Ã{â6)j(5#Ê‘ï»7ÝœívŠËeŠ(íTv´}ª>Ôðx¿Ã‰aıÌ8æÈäÆc _F¯£‘AÈ¡y@¼ žüO~:o7Ð_¸/ÛQŽ(¦íSv§qS¸¨»]‰îˆ÷DAê õ }ƾâÙql¸§Sˆ6œNWˆ«Ä!‰‚ª U&ƒ“A¾@ß #„ 5ƒØÁìa¨Ôd@²7a°œƒNA¥lB¶!ïÈ÷äuê:õÁàeײêõaÛóŽùØ¢,Q I…$SB)¡(K”%ÓézN ''N“§.¡—PÌǦcÑI˜¤Ëø¹ü]•ƒÊÎg µ@  ë†uÂR_)/›Mͦðcˆ1Ãï9÷!¥ÒÅþ"ÿ óvù»‹íÅöÊVe+-§–ÓÇçcó±—L˦I‚$Áǃ%’Áž ÏWÆ+ãŠ?Ã\q®8q@8 ÓiˆÐ„h@P(,o7€$€@€À|¾ÌæŒÀF`*@ ŽìvÊÀe`8ðx¸\óùÀŠà¥ðRø´ ZÒiHC¤!ià´ðp8 YÓ,é¢ìÑvjý…~Ãø©üUh4J)¥КˆMD(þ»Š]Æ&£TLú&|’Id ²b¶ñ[u¡:ЧòÓùZ­E=bž²öé{t‡¦CÒìËve¼Éždш‰D®öW{Q¯¨×ÐÎ(g+•iδç'|“¾=žÞÏpÆhc4&J%ÜŒîE0¢˜PÀØ`jرlX™õLúžLO&&Fs#8Ý naè0ô² YIó$ú¥R%l¶b1O±'ØÅ bÐC!„P( €AM¦„!r¹Š !ðàxpxò€@ €üþJ%™@L ,p8 ŽÇc€·€[À2°X<Áà‚…BÄÊ"eg¬³Ö‹EŠ—§‹ÓÄUr*¹¿@ß ÝnÈ>¸ß\r²ÙYlΆgC.—¿ÁŸàÎ¥—RË·ÁÛàó’yÉ:ÔjPUØ*ëñ1ø˜ôz>N_'0Y8,œCz!½Bý¡~ÍfÈ/Z·­] „%è’ô9œ„NÒih:4 ‰…Ã;¡Ññ¨øÕF£†‰CTª Ó‰ƒCÏçÎí@v >@_ 3 Ž„†FC#A› Íɤ‰ÍÒFé"W¹+ÜLÔ&j¿È_㿊™…LÂ(N”çÌs䲨á¬pÖ6¡P”k 5…óÂùà˜ Ìe2ƒ(”B¡ Qs€¹À_œ/Î8U\*°åÈrãСèPúx}o7‘^¯ w>;ŸýŽþÎf'3o‰7ò±ÙY C„¡ÆÔ£jO7ƒ›‚íÁvùØ|ìAn ·ÇWã¬pÒ8i/4—šL f/½ÞŒiŠ4ÄþD"8ÊÜen¨÷T|QŠ(Å2M™&ÀaÀ5 Ú†ìêþu?€Ÿ°«ØUå›ÂÍá Ð?D¢B! †ÁððøxVÜ+nGI#¤I2$™¢üÑ~aM0¦˜Ìà°Xôz•J€I€$À° Øüþ&˜ÀL`&0 f3™@L »0]˜©DT¢ÝOé‚ôÁ¸‚Ü£6Q›#²‘ÙN ‡äAÒ ýG~£ÅQ¢¨ÑRh©4Ñöhû5bš±Ió¤ùÑN§ T¦*Râ¼q^7üÛþná§pÓˆ¥ÄRâhq4>¼ß^pð˜xLŽ.GA» ÝÒÛ©mÔJ#½ÍÞæä¡rP¿ªßÕp¶X[+iÑ´èçÔsê=åÞòïOw§¼¬V-ï–÷Çü£þOª‡ÕD &ò’yI7ÙÛìîT7*õMú§/°—Ø;i´Ð „nj74õzˆ9ÀÜàmžöÏy˜ˆÌD=3™™Ý î‘BX¡*v™;L„ZB-‹€ÅÍ"æ‘oÌ·æ @†žg3ÆíCv  p¸r9€ˆ@RÐ)h,€@O'Çœ}>†HÃ$bÉä„çBsµvZ»)DŠ¢© Ô˜/L³¬Ö 1Ö˜ì.¶[žŠOE%"’“Y‰¬è–tKÙˆl¦!S'(““ŒÅÆbÝunº¹#œ‘ÍéFô£¦µÓZÿ©ÔÃ%a’±ÜHî$ÍNf§[»­ÝÀá pÍ»FÝ£³Ù‚üí~v·œÛÎmÒé¸5ÜøT|*LT&*öIû$òù 8„œ:êuápƒm!¶×»kÝ».—˘e̦ R×àkð@›`M°rÈ9c‡‚ßÏoç­5Öšå±bذìv±X‹1r˜¹IGT£ªqi8´|ð>x™ ̉Ù$ì’@ŽšÐMh"íQv§Æ“ãHtºÁ`€MÀ&ఠؘÌ)€˜ÀL`?`°" ìö>ÁŸ`íðvø/$’ ¿„ë‚uÄÛ"m“ú}ý>è¼t^0R˜)L±–XËeá²ðÓßi難•ÃÍ–€ÊóÝyîÂÀa`7˜ÌÉ÷äü4–KJˆ¥NM'&‘„¨ÂT®× ~†¿<÷{›†ÍÃl™¶LäGr#»H]¤0>h4¶B[!$ ’L•æJò)¹f\²åPr¨6™[L®Ù·lÛ¸yÜ<ÓÀià7üþ³ÇÙä²HY?aŸ°ÂÕ!jn§·SÕÇêà–pK5·šÛÍ"&‘È ýÈ~ä>P_(,¿¶_Ø#`°ª UC !t€º@FŽM &…G‚£Ábð±xrP9(]˜.Ì?ŸƒÎkG5£§ Ó„ãšqÍ8¡œPηFÛ_¯ŒÏmg¶²¾™_L”æJs^Õ¯jó¹yÜÅ¡bа±X„h¦4S'¦“ÓCNa§/6÷›|¾^á¬pÖ7šÍô7z r9 Ò†éN'“•9ÊœÆúc} …G©#Ô÷ŒûOõ§úå!r¸.œM5æšóvñ»xòoy7Ãð¡øRݹnÜCÞ!îêZu-À à P¨Ün›Í€fÀ3`ðø ( Ãa‡²Ùu:ˆ£`Q°‚ A¦SÍQf¨æ4sZ5-¡çPó¬Uæ*óÎMç&ä²rYp ¸´ÚŠÅ5@š Ö kP‘0H˜7²Ù‚ƒ¥áÒð±TXª#€À LF&4½^ާS¡PÁñ øÏÚwí<2ý„~Áí`ö°Jø%{×Yë¬ókyµ·[È-â¦ñSÉÕäëÔê>ýŸ~ÏŽ'Çï)÷”óåyòÁ­ ÖŒ‡ÆDªTú;}¸|\>.Sw)»\-®èÇtc»÷ûMØ&ëòíùvå¡rжÛ n'“M¦ÎÎgg3‚5A¡`°¨ìTv`30¹ô\ú&ÖƒkAd²0¼^Ç‚f´ãZuY^¬¯^¤¯RNg±žØÏlžÏ+•‹Cq!¸3ØëÉ9äœþîwïweD²¢G§#Ó’ÅIb¤Dö"{@€ :þ õ¶úÛiA´ Ùlº2ÝmT&ªŠÅú]}.½ÒžéO¼‡ÞCº9ÝßµoÚº1Ž`÷0{dݲnÓ1i˜·(”8'œÜAî!ci±´×é«ôÕ*ŽŠÅ f\³.[Âmá7âûñ}qj¸µPܨnOQç¨ó§ùÓüíòvù=ž‹A `…/6›©ÔŠé¡tй…œÂÏWˆ+ˆáÄpÔj4'ÚíþæÿsÃÑáèø–|K:ý~ÎÃ7a›‰-Ä–×Âká7k›µÍdf²3ááÑW«Qú¨ýVk5“Éz†½C`þp4Ý*n”¹\‹".‘HÁd`³où·üVÒ+iþ€>âßqpÈ c÷Ýûîú}G¾O'FÇ£cM…¦ÂÑhƸ!Üî£wQ»Ìåæs („”>ñŸxÍÖöë{J!¥σgÁ¹z½í)ö•gгÅUõªúÛ*­•W¥‹ÒÅ›æÍóEÊ¢åKÔåêq‡8Üg23™* •Çcã±ò\ù.|†ÞCoÏ‚ç¾ÿßîʇeC‡að×ìkö2§™SÌõvz»eá²ðéÍtæÂ !Õ×êë˜åÌrÓÁià³.— ΉgL…¦BØ,¬VTË*eJò¥yJ ¥Hï$wÿX¬aê0õ6¸›\Mn&·þhÿ4LR&) ‰@. NòWy+~¿NÓÐiè1ɘäÌèVt+«)Õ”÷×{뾇C7÷›ûf]³.Ëme¶ÔµjZµzƽuƺãOV§«PØèls©ˆÔ|†>C&‰Cí¡öÓˆÉÄdôÚzmö‹{A¦àÓpŠ8E(û}ƒ¼|ž>M†vÃ;áðØel2¶j›5MßÖïë¾éßtå·rÛ´šVrë9uQ>¨ŸHš¤MSré¹sQ‰¨Äm6‰(ç”sÆ6c/Þ÷ï|$>%€¾ÁŸ`Ï.— äÉòe€ÎÄ?b°Ûxm¼&6¹À\à.#×ì‹0˜Ñçhó·[ˆ´Z¬òºy]Sõ©úΗgK±›Hͤ"–Jø|<•^J¯ W…+¦eÓ2ã q†:þN…B„z=1ǘ㿀ßÀlÈödz›áMð¨•TJ¬™VL«ƒ‘€I/—‹Îé§tÔaÊ0äæseˆ²¾`Ÿ0O·[MΦà{p=¶{[=­G&£“ŠñÅxâq»I¤Ï!7›*Ñ•h¬ŠVE'î÷ Kå%òyF¼ª©UTΧS‹‰ÅÅVƒ«ñŽxÁDà¢p (“űâØè€t@6½Û^í?VŸ«*É•dÝ/n—±•ØÊì®FW#O½§ÞµØZì*Á•`Éàð`90>NŸPhè4séôN‚§#Š‘ÅCÇáãð8›ÝîÆïèwô9‹ÅVÀ«!ŒÄyb<­“ÖÉë õÐzÀÉ`d© T'éÓôéœÔÎjJí%wVЫhUŽ*ÇÓÊéä±6X›%®’×E£¢ÑÑ·(Û”Œé˜tÌ6ä[r,%vºÚ]m.°qX8«•ŠÊAE ¢m@¶¦S¦“FÕ~º¿\¿_‰&9“Ç`c°1?XŸ¬3n¶ýN~§8ÝnK&¥“½Þ¾é_t®vW;+ŽÅÇb± X†©yT¼®tW:* úƒýóŽù¡aP¯å·òÛÐéètêúu}85ÜíF¶£[ÍŽf¾H_$+$•’KJ5¥Ô}j>³ Y†­êÖõkõˆü´Z æ‡ó?ö_û/8—œK³ÙŒét€5š€Ë‚UÁ*¡µPÚ¤RN¥ ’†JTõ*z¶É[d·Ž[Ç,ä–rTêô‹*E•Kƒ¥Á¶ Û“çIó %„’¯wW»¨1T©d„2)áð‚?A£]Q®©ù”üÊúY}-X ¬J e±ø‰{ÝuÊ]å(ï”wÉ: h)4‡µCÚ¢ÁÑ`ç´³ÚZSA) À'`ÏN§§S †„p’8Hó`y°4XZ,+KÅ¥âe ²¤FÒ!5Pš§Qã¨ñóØùì´Z?ÇO#§ØëÆYã,¹ë\õ¢ƒÑAè Ôj A rÈ9dÆLã)÷Äûâû­}ׇ: l¶6Z¤ÝRn—bK±G £†.Ói„(‚ùA|¥ãñŽ¥'R“[i­´‰\D®*L•&œ³NY†LÃ&p?¸‹ ʼn>ÔŸiip´¸n¹7\ž‰D†²³YYjxµQëOZyÓΜ(áFö7±»ÍÞgS:™dË&K2Y“Lše³-—¨½EÈ.AmKjZòזܶåÍ.i[ ØT2¡”¤¥%(ñGÊ>Râ—(©Dù'É2I’J’T’X’Ä“$™' 8KX“Pš…(ðϬíp‹„4!¡È6AL‚d#!€F’4’p“„Ú&ÕèÏZŠÔ[úߨcÃþGò<±åލuC™ÈåG*9ñÏŽÜvãÙÉD ’„Ñ&Œ8¡ÅÈþLšdÔ]¢íiëOp€á§ #l`цŒ2Á– ˆdC&0ÍÆn5Qªôo£²üàC‚(‘Dä'!QÊŽÀ†3™œÍÍnk°Ý†üwãÃbõ¬É&IF0Ö®µw ¸PƆ4.Áv 0Y‚ºЭ…l,!a d["þðËFZ6q³Ž¬ucö±˜J¢U@¢€,~ ðg£=àÇ›<ÚPrŽ$q&Y2ÊTR¢Ç–<¿5ù³QšÀn®tü'áCR‘÷¼Ê¦UFº5Öö·¢Ô §…<'q;‰tK¢R•Dª&4 Pâ²¹ÅÎ2a“ ¸mþñÈ.G‚<ˆœEU*©t  eS*›Ü‡{;ÚP:Œ¤e&0 BRÃÖ¾uó³-™m»mÛ®püGâCv²ÎfsGú?ŠdS"]è‹DZ Áà?õ¨€!¡ HqˆªÅV.ñwŒäg#hú‡ÔDâ'ì—e1)‰kË^cƒ Ùc;‹„\%É.J7QºÁÖ¾5ñ³)™M¾mó°€ü÷çîr´Ôf£IÊNœDáð€qƒŒ°Õ†x3Á¢ mCjpëˆ@‚:ОDò,` @Z.pä‡$?ñÿ‘ÐŽ„ò'‘^Šô`£Ô‡H:AýÏî‹X%Ñ.ŠDR"Ç–<Àf3០úoÓÁž (E6)²‡”8‡D:pã…ü/áC D‚$°uƒ<àÂ3Áž yx#ÁR cÐæˆXBÂf0¯Ez1ÁŽ pcò‘ˆÔN²u•Ô®¦ 0Q©ÍNtã§ »EÚ`Ó¥u+¬Õf¬ aEÚ.áÇ<ÁæP‚„_âþ!q †ì7am hKBZp{ƒ0€ªP$A"€D¨@Ó˜@œ…Ø.Á¹ È%Q*мUãˆÞÆö>Ñö‘ ë'Y^ ð`óš¼ÕçZ:ÒÐh&m3jrS’ÕVªÄ6!µ¨HrC×Þ¿hCFæ7"ÐnCrP²„€$æ0-Án IÜàf0€ä PJ‚üàñˆJP`³°5ùÈ“˜*T 0aƒxÀûGÚF²5“ÈžE‚,‡ 8kó_´í¨&A2:QÒ›”ܪMRkz[Ó˜ÜÖæ·9ñϘ|Ä0á†*1Qˆ EÀYÂΰƒt «X Á`3P €R€Ì€DÈ@å(GB:Ю†4ó˜’D’*qSŒ4a£}èýèGB:øŸÅ‘,‰‹Ì^mckðèCBBÒäï*õW«©]K'Ù>ÚVÒºá×Ù~ÌBb,1a‰HЀ\àðƒp€¥(€ô -À>ð€ŒÐ.‚«X7Aº€Œd+!¢ |ƒä$±%аUƒ¨áÇ@¥(‰j Pdƒ$ÌÞg©=JГTš¨)AJîWr÷—¼Í¶m·•¼®ýwì Ihs ˆLAêP`ƒ°•ƒˆ@§8Àî@*ì`/x `à` |à*P °m„\"áZ ÐiCJ€ü LJb¶°È†D9qËP‚„›$ÙJŠT\Bâ¤Í'8ÁôO¢‰DJ%•,ª@RÊPÁÆ4y£Î-qkÒÞ—h;DF"0 €L(áF áx3Á@ ‚°€ä HÂF°µ†è7B˜œÄæ-Án 4i£ÈA˜M’l•Ä®&0‘¯|wCº Õ©Hv“´«õ_®Áv ®esoxíWj¿ùÿИ„ÄMbkC2… (IÂN €l\àd @z€D `@"àYÈ Á @Z¸Á0 €^‚ôÐæˆœDâ‹X½Åî6Á¶¨}Dr#‘@ÊYâÏȆó7™ëOZ‡$9%(Š Q–À´ Žp³ÍžmX2Ä Pœ„äNbr€Ü@*ùÈ,Abp;( @(@€$p€Àx@BÀ€ Œ`0€Ð~…(!Ž pyÈ$!!  UÀäG"A" t“¥3)™t‹¤g³=œ¸åÇí?j.p˜äÇ)½Më^ZóX²Ö&±9íÏo }5!©HºDB"²a0‘ƒL`’@º`h@@@@D &0ˆ 5d ЀC€¬ 5˜„Ä,Áf ügã»ÙH‚Mh•¤­& 0i®tw#¹ ÙÉH"w“¼¬Ub®åw,ºeÓr›”îWrÀF1NŠt|£åØÆÈÊFAY È@ `[è@KX P `€ ` ¸ÀOxð  U' 8\‚ä`ãŒDb‡8½Eê6Á¶°}„v#±BJZbÓ8ÉÇ8ïOzˆDB%]*ê7QºÈÖFÁÖ´¤n9qË×¹èGG>9òw“¼¯ezp›ƒ€ŸøÐà€ `€ 0€ð@rÐ.‚(@¿ø?Aú0¡†H2AêP’„”+1YŒdƒ  HKX•$©%ë/Y¥,tæ 9ÈÛFÚmh©•L®%q,ˆdCe›,ëX?iûQˆœ—dº»õß°ù‡Áz ÐGB: ðg‚@]è ÀnÀ0€(@ ¤ H@à PhAØY‚Ì܈PB‚xÀ¹EÊ5¡­d{$b#= èXòÇ˜ÔÆ¦ç79èD€4¡¡ ÏFzNRrštÓ¨aC ‹TZÀV·Å¾/Ý~ìycË?YúØV¸AÂ2y”àǀƊ4RH’D¼eã:Й|Ëæ·5¹ÄŽ$vã·<ùè1AŠ&0€¥µ-©ÛNÚ“Tš¬`,åg+´]£ ˜dËf[4ñ§Än#”œ¤÷g»@6°¨…DR¢•ØöÊVR³@šügâ ±(!@Á!Á€,Ô èÀ°€D @r°-‚à»Ø>AòðŸ†81Áä ‘ˆ*ÁV tc£—¹È6JrS“Äž%>)ñcK]òï˜ÈÆFˆ4A¸ÄsãŸtó§þ?òÐÆŒ¤e$Ñ&‰ M„ ¨5A«å_+s[šüàÇ83Ñž{kÛ\ é7I¼äï¼}ä ­8yÆV2²:‘Ô¹¥Í99Éш£Ïx7‘¼Žäw#ùÉHzHBB4™¥(ÁYJÊ[RÚ˜ Á]2é­Mjqˆ´í§Ì>b Ð^‰DJ#õ©hKBuÓ®¤…$*õW«4Y¢ë×^Âæ2½•í5i«o›|ä—$»eÛ/n{t` (QE(1ä$¢¥3y›Ïš|Ôœ¤àTB¢ð‚ì`ƒ°€Œ`¸@*Ѐ¼àKX€ä 0QƒpðZа݈XBÂ{Ø«Å^.qsŒ€d]èæ‡4=¡ítƒ¤c#*‰TO2y•¨Ž,qx ÀcC(ÑFã7ÏÎ~yÓΟüÿècC2‘”“Dš&4 Pz Õ¯…|-Ån+î_sXÚÏx5Ý®îp£Ýò7‘¾á÷P‚„;aÛøÇÈÖF²ß–üã'CÚÓΞu¯­x™È)L ’ h‰¤M$ kKZvS²¤¥%*ýWë7YºìWbÃ2Å–-5i«o›|ä§%;eÛ/l{d øOÄþ'ñá ¢%39™Ï’|”˜¤ÅT*¡`˘4ÁI H>ô  ]À]è€Àˆ@" X Œ `x1ˆ0…$)!˜ À|à!ñ‰@Jƒ®p.ñwŒ¤e#d èÇF>1ñ”„¤j#Q,‰dOÂ~¨å–,±yËÎcÃHÒFë7YÑÎŽzcÓ ¨iCJ4 “¤&4êPª¢¯Å~-ÙnËó_›˜äÏVzµí¯np+¥(òW’¾é÷PS‚œ;áßøÇÈÚFÒß–üãgCêSÊžU¯­}›lÚ0Ù‡|;â hDz&ñ7ŠŒTbÙÈĆ$4ѦŽ$q#ÈAˆDâ'P’„Ü&áIÊNWRº—¸¦0¹› ØlCbtã§{;Ùö϶ƒÔ¢i HR]’ìž4ñ©IJJÆV2ÏÖ~»¥Ý0ᇠ¸eÃOxÜVâ¹AÊßvûÜ^ãˆBŠQCŠycË(ÉFLÞfóæŸ5&é7U*©X:ÁÖж€h@p£ƒô À#€40 €:Ð `€øÀXÀÀþ Xƒ´¡1 ˆUBªÀ¾”4¡Ð€Cú"Á pK‚€±EŠ/Ñ~ŒØfÃrì`?øȆDx#Á/É~P’„•Tª¥¤-!} èdƒ$|Óæø7ÁÔΦ{#Ù E(uCª7¸”d£&Í6j"Q¥(°•„® pký_ëXúЀ6°®p{§8ò×–¿ øp[‚Ü=áïxËÈöG²è—Då')DZ"ÓêŸUµ­­,és{›ßþÿá& 0R‚”¸`3Â{ƒÜ!Á 0I‚~ð¬…d.‘tŒ„d#\àæG2=ìdƒ$^"ñ) HNÒv”ܦå…,)u ¨bs™ðφÔ6¡ËNZx£Å¤ý(LBb,Qb‘”Œ¦ 0iñOŠ˜”Ä­ek-9iËÈ^CXŠÌfc5-©mÑn‹—\ºîçw=ýïð€¬+á_è§GÆ>2—”¼Ð¦…?)ùRš”Õ]ªí†l3m›lÞŠôXmÃhhÃFà§ ÀÌ`'A:â%(•¨(aC äW"ïxÉÆN6°Žts£ÛÙ8Eâ/ˆ”Dê'QM hXÀ—<¹æ0ùœÌæl“dœˆäG};é÷¸ƒ´¢}éH2[ÒÞÔî©-Ij¾UòÌ–dº¥Õ0•„¬¥e+IÚNÚÆÖ8ÙÆÎÂvÔÞ¦þ‡ôBˆôo£}&0Ì"aµ­(ÙR Wb»•´­Ö®µ‚<ãÃÃΧ58p3”  e(@Ú@`²3œÀv0!- hT Pº†p3Å(|à!Á ,Ib{Ø«…\.!q dc#RãG<¡å(DK"Y#‰MRj”t£¥h+AmKj`S™`ˬ5aÁuÓ®žè÷H@ÒøŽp¥%))³MšˆTB©H¬`«|[âý—ìÇ69³ÉžMwk»›üè—D¼]âïª}T ÈnEê/Rä°eƒ6Ù¶Ðvƒ´Ò¦•aëdk#\à·Ë¾^%±-–Œ´h÷G»ضý÷î!± †ø7Áo xK‚\€|4 «X$$D‚ {ØÀî@:Ì`€&0 Xƒ4 ëXB‚°•…4)¡q ˆf0ð߇¤="¨Ä†'!9 T‚׸ÃF4Q¢øoúÐþGòC¢ô¤Á& B U2©–|³åí/iŒ|iH› Ý@:æÏ6“ü¡i H¾EòIRJ˜äÇ'í?jhSB¶•´´õ§¯xìCb0™„ÔV¢·)¹NSr›¸À À^¼à¤ &Á6 @J8À‹X"AÐF‚xÀµ¨4 Àv,!a* PS˜¸T2¡¼ àyCÊ!ðG‚kX§8,ág `c:ÐÜÆæ:á׬}d*!QØKXØžÅ>)ñb‹]rë˜œÄæy3ɳMšrS’üïçÜ>âÐv‰äO$ IiKJuSª¤5!ªÍVbšÐŒDbpë†01M hFB20ƒÀÞð4  0a‚ä °€*AR PR‚Œ`¤ +\ ð_ƒ\àòCÂЖ…8)Áp €eC*°Ý‡ŒŒDaþðwCºà߈4A‰ H\‚äð¯…<)áA MBj°•„$! HBB@’°%8 ÀRB’à¯Ü.á’ l`è‡Ð>‚È€&‘4ŠTR¢Â¼…ä2A’ \jãhñˆ?ñÿì‡dy#É.IrOÒ~•¨E‰,It‹¤! Ø>ÁØÀoCz@Ò<1á| à[BÚð¯…P*L `QÂŽ@¢(áL `U‚¬0±…Ø.Á‹ XiHà|;âˆÄF$‘$‰ÄN"œà±ÅŽ/QzŒ˜dÃXÀãGÚÐpƒ„à×l3a h`p»…À.l `[Øð·…Ü.á‚ dC"0цè7AÓ˜|à ñˆÄF"U¨Ÿø*AR ¡ð€{Øà÷Ä>!ùÈ!1 ˆD‚:Д„¤&á7 4Q¢®p´…¤/| „d#KXÞð:Ôh{D qȆG²=’Ü–äô' ”Ħ$1!ˆÐF‚*PˆDB!шlCbèˆÄF"±ˆàGG8–„´'8 (QB¦0±EŠ.qsŒ0aƒ3˜×¸8qÃŽØvÃæ10DÒ&’¤½%é>Iò&á7 „L"W¸” $Ñ&‰4I¢P€•Ä®&0 ¸MÂ~ð¤…$*¡U XbáÀ21‘ ip€æ‡4Qò°}ƒö±H AR ¤…$?!ùH²Gr;’X’Ĺ%É7‰¼PbƒĦ%^*ñ HB"”„¤)!I ˆdCÒ‘,‰d]"éHâH¢E”„Ã&8ÉÆPbƒ°¥…U*©`KF’4‘ j#QàG’<’Ä”$¡)ÉNKÒ^“XšÄò'‘D S"™XªÅ}+éi‹LJòW’À–³%™.‰tLBbDš$â'= èP²…”¤…@*XJÂX"Á µÏ.y>IòO¢}üŸå(1DJ"Qâ¼¥åE*)VʶWb»L²e´-¡uË®_Âþ¨¥G*9S ˜URª•€¬p+`KYrË–¼µåÊ.QzKÒ`“˜¸ÅÅ”,¡f 0YÒΖœ´å³-™p‹„]b묽f0)ˆLBd ˆÌEê/Q|Kâ_¢ý$Á&0Á‹LZdC"t˦z3Ѧ4kÓ^˜üÇæG29”Œ¤f0ÈÎF…4)§Ù÷¸ø %(#AІ…„,!Á«EZ0ц’ô—Œ,[»wn%ĸ÷ÖZË_k홳2ö^Í™³=g¬€°vØsbÌy-5¦¶VÊà\ s¯à¾WʦTʱV+5f®ÕÚÂXK-e­E¨žSÊ€P C¨õ¥ÔºŸSêS«b®ÕÞÃY3&%D¨–ÒÚcLi­5§DèŸâ…Pª=G©•2©•5ƒ°vÎÙÞKÉ…pª)E(Å£”rR JIII)%¤´˜ÓmM©á<(U ¥T¬ðžzTÚ›_kí®µ×‚ða,$2Aèa "BŠ‘R1F(é$$„“bXK ™3'tî¢TJžSÐ*CXk#Œ‘’ê]N™ÓF(ÅH©æ¼Ú»WwnéÜ;‡”òžóÞ~OÊ@H-¡$$†Ò"DHµ£¤t’BZKI¹7(E§Tê¸W Yk/eìÅØ»:gMy¯8W ë]kÖzÐBÈY"ĆÒÒZN9ÇE(¥E¨µÞ»Ú«Tl‘¼7‡ã¢wáå<§°ö3æ~ÏÚ@h=¡Ä8‹b?GéU*¦ÔÚ¢B¤T‹ a®EÈÁØ;+emI©7–òèÍy¯áüBXK¢”2ÂXNÃD¨•D¨•Þ»˜ƒdŒ‘3¦¨ÕclpÈ98çC¨wáå<§Äø „PŠ&DÈõ¥t®¢’RJÍY­Uª¾WËcí£·àæœÓÆxÏÉùBA¢t~Ò¾WÍù¿D¨•D¨•⼂Ð] ¡0&@ÈÃ8i1¬5†ÄØ›cloMéÇ8çTêžSÊ~OÊAè¡$Dˆ˜“‚PJ‘R,…»—sbÌùŸ6–Òå£Àx¹÷AØ;"d}²ºWN ÁDèG¨äüŸ”¢”V Ág,åÜ»˜Cc a1æHÉs.h «5fìݜӚxû?hY #4f”ÒšuNªeL«å|¹W+aÌáœ6FÈ䃾wϱöAÈ9bl~ÒÂXN9ÇEè¹#€ÒZOIéO)刱ÒÚ\ q.%̹—’ò`Ì‘2&ˆÑsnsÎyé=("TJ‘Ò:jMJ=G«un·Öû a,Ñš6ÂãÜ{½w¯µöÁØ;â|2ÒZN‰ÑG(àïãÈyð@È"D’¢tSŠqT*…D¨•¢U ¡]+¥¤´—’òcLi¤4†ôÞ£´~OÊCHõ¦$Ä¢TJ±V+mm°f ÌÙ›`l=Ç»ç|ûß|!„1N)Èi .¥ÔîÃ+Eh·âü_ŒqŽ4FˆßãÄxb Gˆñ9'%X«bìf Á¼7‡ˆñ ¤ŒÑš]Kª%D«el¹#b¢7VêéÝ;Ú{P:D(…ã¼­•³>g‡PêƒPc ax/кÂøc a¬5‡`ì D¢\KŠB+Eh¼ƒ8g1Æ>‡Ñ"¤È™"¤] ¡—2æôÞžSʃPj7Fé1© ¯•òí]¬uŽ´æœà³vo™óA¨5â|…²î]Íi­)å<Øap. ¤ÂXGá"„pŽ`S at.†p΃~Â,E‰¡4+`Á#dlAèD0&D¨—RêfŒÑÁ8'¨õ!D(ÑúkMjeL¬…¾Ã(e i­8Çðô~¦Åè½%$¤°–.eˉq4æ˜FÀðPj 1 ̃pnðGáH)à¼ÃX{b8G 1F.…ÐÔƒÄx‘ LI‰T*…会@rAñ>(q$”’“²¥T«Qj0FÍ™³hmmͼ÷Ÿ $9‡1¾7ÊiM0f Ü›’¾W̹– ) Œ‚@Àø"@˜à\ Á¸Ca@(Àƒ‡âxO ‘r5F¨ódPŠrnVÊÙ0&¤Ô#¤Oê(E U*¨E¬Õšã\lQŠ4fŒÞ[ˬu…ðÁh-"d‚Ræ\ÍI©0æß›à´‚ D€Ð€H  $@¨@ˆÐZÐI !l-†ðÞ!Ä8 ð^ ¡´>‡Ñ#… RÊcLiµ6§|ï ¤ѺeLªMI¬5†½£&dÍa¬8·ïÝûô~ªFÁ%ä¼Î™Ó¦tÍ™³=g¨àT €à@H$€ÐÀ˜€ pn@\ Ì9ˆÁ)Å8ărR J AM)¥Ì¹™³6q!ï=èm ¤„Óº¦TËYk0vÎYËlmŽÐ=G©`L?‡ñÖ:ÊÙ[9'% ÂxPÊ@¨ € €€( $@è*@ì„àœ |‚XK !d4Fˆñ$L‰“‚pWJé„0†´Ös®€.EÉq.(µ®•Òë]lqŽ4öžàœ¶vÏ­õÂA⼑³bOÉùF(Å%¤ 8p€€P `ÀŒƒ0fB(Z AÄ8ˆ¡)E(Ãcˆq2I‰1J)EÀ¸ƒ0pN ë=h] ¤Dˆœ“’¡T+Eh°&Í£flÎiÍ<çà9‡1¾7ÊiM7¦õ¢Ïmí¿Wê@¨ €0ÀÀ0P*A0!`¬`„‚xO ‘r5Ƹ÷ä`Œ²vX ‡0æ¸×ƒ°€Ð/Eéq.(¥®UÊê]L 1¶6Í£Mi­™³8æœÓ´v9ç>‡Ñàƒ°âE8§"¤TÁ˜3vnÏ™óEh­1¦56¦©u.«ÕzÇXëyo/uîÃØ{(e  µ¶¶Ý£q®Ó¼‚öÞÛõ~°BB¸W¢4aŒ2bLK9g3æ|ëtƒÒ:GOéý\«€T €à€P, À$€Ì„@ˆ¸m ¢ A‰Á8-…°Ñ#¼wòLI‰T*…ä¼™ó>rAò>Hq#$d’RJ_KéÙ;(Õ©"¼W‹Qj.ÅØÁ#c¬Ù›4öžÚCp.mÍ»WjóÞ{é}0Aè=aÌTŠ‘¾7ȹ)¥4˜SvnÏ™óEh­2¦U:§VªÔfLÉ­5§àSªzQÿ?èQ "¤TVJɱ6(5¦ÔÚ²VK)e.%ľ—ÓbL­•´Fˆ×ZëvnÎAÈ:§TðÞÝ{¯áüA(% alH‰Ž1Çèý&d̵–³FhÎÉÙB(E$¤• Uʹ^kÈ P  $À˜€°VAÈK ax/0æ"DH à\ !¤;Gi !d¨•˜[Ëy0&PÊsNnÍÙÏ9çŒñŸƒðƒPj"DHá$䜙"{OjII*•R°V Ø[ Áx0†ÈY :gMI©6ÆØá\+ŸsîáÜ='¤ûd€Ð†øw¢´qŽ26FʉQ1&$ß›óî}Ñz/LéO©õ²¶X‹im)l-…ø¿ hͶ6Ç$äã¼|’@èm £b‘Ò:]K©Í9¨¥¨U ¸W =g®uο×ûbìÁ˜4†Ø›{onQÊ:ç\ñÞ;á|/íýÁX+ !„K‰qš3H'$丗NiÎùßBè]'¤õ¡Õú¿_+嘳ÒÚ€Pà|‚0F P5¡ !…PªCHâTJбV0ØÄxÂJIIH)d¬–¢Ô^ËÙ1¦|Ï›#dqN)Ù;'´ö …²+Ei %d¬›b‚PJaL+`±Ö:Þ[ËÙ{0æÉ™3?gí]«·ââœS¤tŽõÞ½g¬ü„ P–Ãø¢Ôu޲BHJÉY1æ<ãœsúQº7M©µSªu¾·Ø»j-Eĸ„䜔‚V Áh-优³g á¯5çàSªzAü?ˆA"TJŽQÊOIé•2§´ö¤TЍU ý_­e¬»—s`Ìy3flÓšsglîÀ9§4ìÝ›Ìy™ó@ Ô5†±>'ƨÕ!d, ”ò^My¯<ç¡ôª•TjX«¯önÍÛ‹qu. 2@¸à\ Aˆ7á … ƒwâ$Ä„0†B(GHé&$ÄÀ˜²vQŠ1S*e€°âÜ_ á‹1flÍš³Vo áÍ9§€ð#ä"Bè­£ô~”ÒšhM õ>©%$ªB¾WËUj®ÅØÀ˜cLÉ™4–’Ø[ zoNIÉ:·Vñ#Ý{¯ÙûA! ¡TDˆ‘z/Gˆñ$¤”­•³"dNIÉ?çý£tÞ›Õ:§[«uбVžÓÜ;‡wîýü¿˜g ã\k”²W áP*@¨BÈa !¸7ðþ%D¨¯ã8gñÞAÈ9"ddŒ’@IÉ9/%ä䜔"„SJiY+%”²—"ä` Ž1ÆtΚÓZoMéÎ9Ç€ðâ"BÈ©#Ôz”RŠeL©é=(õ©"ºWKAh.uο—ób¬±–46†ÖÚÛsnn1Æ:GHï]ëÖzϽ÷Àˆ¡<‡‘V*Æøß"¤T¤”’þ_Í©µ=§µ¢4¶–ÔŠ‘Y+%€°rÎ[‹qun­ñ¾8;b¬U‘ò>d̃˜sðBÈY#D|’2FIÉ9,%„Ì™“¢tPŠK)eTª–ÀZËYx/™#$hM °6üß xO ò>H!d,ŠQJgÍY«6ÖÚá\+s®ÙÛ<÷žúD€0n ÃHi¢tgŒò@ÉÉ9-¥´Ñš3¶vЊHé?§õn­×jíd¬•®µ×*å^{Ï€ðÃøóeœ³³¶“Òa¬5† ÔCht‚@ñ(żƒ,e±¶;`þÄH‰‚PL‰‘4&„Ø›¢tOÉùE(¥0¦B¨WŠñh-̹—âübÌY˜3”Ò›ChpŽÒ:GŒñŸ3æP*B襣Äx“ÒzbLIÙ;(¥§Ôú´V‹%d®À½—³ aŒ‘³–rÔZ‹hmÀ9‡0ìƒÉy/…ð¿§õ ¤-…±#ÆÁ£Ô”’‚Ü[Œ1†4Fˆá#Äx2Eˆ±)%$ì”"„Q !H)0¦¢V Á_+圳—â^ËÙ†0ÆDÈ™ãÈ¡„0Š‘Rh !"ˆQ 1F) !%4¦™S*wNê)E)Õ:¬UŠÅX«el®åÜ0Ç㬕’ÅX«elŒ‚1†0̃Hi ¡´8GêCÔzCˆq#d¤”“rnQŠ1T*…ˆ±òÞ^‹Ñ„0†0ÆC(g á£4f¬Õ›ChoMéÆ8Ç@èøyÏ9ó>h ä‡â+Ehé$¤”–’ÒkMiõ>¨å¨U ³Vc`l Ѻ9 ìƒÐzáüBHI"¤„’ò^O áJ)E`¬rÎ] ¡€0,Å™C(gLé¥4¦´Ö›SjoñÇ8ç@裴y!ð>ìý „…Pª#DhŤ€“Òz_Ké½7¨¤TФTƒ¼wqî?Çù!H‰Â8JII5&¥¡ WŠñk-eà¼S dL‰›3f”Òšã\m±¿7ç ä¢vŽÑãjÍY·6ç áœãœvÁãŠQJ4Fˆý¤´–•ÒºdL‰É9(£”rœS…优CcÌy›3fœÓ›3foÍùË9gXëÂzÏYö>È ¤…¢CÈ¥£4f‘òII)U*¦À›SjzOJ!D)U*cŒq™3&Ò`nÍÙÇ8çLéã¼zQô>‡üÿ ”…¢CÈ¡#$dQêHI M)¥ä¼š“RvNÊ A© §”ñ£4f¸×“rq!Ð:pîsÎ|‘þ?È!!4&‡Pê(EÉ#Äx‘Ò:RJIu.¦„Т€P 1F)•2©Õ9Ã8g8瓲yO)ð>ðþ t„’C¨£$dâGHéE(¥Ä¸™Ó:rNIù?(¥¥”²£TjÁX“²xÏï=çèý T „‚Ch™#bâGHéE(¥Ä¸™Ó:qN)õ>¨…¥¢ T ±V+¥t{qù?( ô†Ò&DÈÁ#´v‘’2QJ)q.&dÌœSŠ|OŠC©5&§ÔúªUJÝ[¬5†"Aèi "TJŒ‚;Gi#%¢—âfLÉÅ8§Äø¡Ô:’RJyO*•R­UªÁX+9g!¤4‰Q*0Fí¤dŒ”‚[Ki™3'âŸâ‡PêII)Õ:ªUJ´V‹`,ضÂ1F(í¤dŒ”RŠ\K‰3§âŸâ‡PêII)Õ:ªUJ´VŠý_¬µ–µÖºã\i#¥$¤—RêgLéÉ9'Ôú¢B“RjyO*•R­¢¿Wë-e­e¬¸—î]Éy/&”ҜӚ~OÊ%D©E(§ÔúªUJÕZ¬€²ÖZÖZˉq.ÕÚ¾WÊuNª@(µ¥Tª T ­U«el°V ÌY‹]k®%Ä»—rù_,‚¨Õ¥Ôº¢TJ±V+un°–ÍY«al.5Æ»×zú_L‚±"©"¯Uêå\¬Eˆ³ÖzÙ[+•r¯à¾×Û`ìI‰1Ö:±Ö:ÑZ+qn.uμ—’ý_¬!„16&ÇØû*eLÕš­õ¾ºWJô^‹ý°¦ÅX« d ±–3vnК`L5†±†0ÈÙ.eÌ圴FˆÔƒ&dÌŘ³¶vÑš3RjMu®³ö~ÒZKUj­}¯¶¶ÖÝ›³clm¹·7–òá#}o®!Ä9'$圳¡t.í݈ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ€¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú> –%¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>¡6M€Å€èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èúf ¢¨>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú=ïxF€Žã@Sèú>£¶í½ï{èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£Àà{*Ê€èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú)JP$I>£Ôõ>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>¡Ìs¸nà{èú>£èú<èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>¡šf´#èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£Žãº.‹èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú<¬ë3LÓ¬ë(ª*D‘%‰ahZ£èdð<çúä¹>£èú=ORX–Dêz­«iÂp²¬ª&‰ž€‚ ‹BÐBŠ¢Žc˜…@PŒ#)ê{HÒ€–%ƒÀðað< b[èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£Þ÷¹Žc à ˆ!dA<¡hP È (Š€Z†A< È€ (‚ €¶í­ BX–!(I¸nÇ1TU BÐa@@P>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£4Í'Ü7 È( àx@P€  (F@PP (çø2 €€  (¾/…¡hn@P € (( @P € €2 € (>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú+ÊñhZ ( (@P@ (ˆ"2 €x@PŠ¢“dØa (Â02 …@ àx€àx €@P ¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú+*È2 àx…@€ @<€<àxx(  ("H‚€ @PŠ¢ä8 È (dØx ¾£èú>£èú>£èú>£èú>£èú>£èú>£èú3ìú&‰àx € È „a( 2 …@„á'iÙ†aàx €( àx@P>£èú>£èú>£èú>£èú>£èú>£èúÇ0xA €(  (@P €@P (RÔ¨ª*N“Ÿçø¾/€ ( €P (èú>£èú>£èú>£èú3ìú‡ #H<àx€F€ (P ( È0Œ"@P’İÜ7Àð ( €ãèú>£èú>£èú>£\×%È€  D €@PZ€ (( ˜æà € €@P (ª*€€ @P @Sèú>£èú>¢N“ƒ ÈA@P ( µ-I|_ bX € € € ( ( (¾/Žc˜ € ( ( € ( €@P€  ( ( € ƒ È@PF€ ( €Z…¡hÀð€¡@P € ( #Hø¾àx ( ( €F€ )Fàx@P( € € (( àx2 € €< ( ( (.ȯ«WÕ«µÎÚë¡uдöZ{-|Ö¾kr¹ Ã-a–±ûXý¬Øl ¹e\²²}Y>­ùVü«åUò«e‹2˜½L^§.S—*•ÊŸµOÚ¯)W”­ÜVî(.T*% ’‹E€¤BR!)€ÔÀj€õ@z©aT°›M¦õÓzéÎäçryM<¦ P (‡CŠF#›±MØ©§TÓ¿XŸ¬P˜JX,–íKv¦“Iž´ÏZo™7Ìž9O¨1”Ê9•ÊšiM4©ÄTâ=BÞ¡o~'¿êÉõdýq~¸¤ d†²H $“œIÎ%d² }¾ŠiÅ4âBN¡(T ª:µZœaN0ª«UUºý~ŽðçxsÈaä0õ z…½ýÞþï¬×Ökö9ûŠ”EJ"é‘tÈÑ$h’;Ýî‘@H $ôÒzikĵâgy3¼DN¢(6 I$‚¡©PÔ¬VV+,V ­°öØ{za½0âq9YܬEïÉ×äïwŠ<ŠÞEoR·©[àMð&úó}y¡Pˆ¨R)© Ôˆ9D¢šQM(ФhR?¹Ü“0I˜%ª’ÕI¦Ó y <„¢;Q©•ÔÊê¨TºÅ]b²ìYv-ÆÖãkµÚ‚ýù~üÃaαéXô¬º6]>IŸ$Óhi´5ËZå­­ÆÖãyÕ¼êáõpú¹Yœ¬ÎŒ7F¯ý×þï0w˜<“žIÏU‡ªÃá5ðš}ä>òÏÎçú#ýˆƒjAµ!}P¾¨dG²0årØGì$éÒtézÔ½joa7° P ©ÔŽêÅFâµZŒ±µXÚ­…‹¦õÓzû }„Âüa~1ʘåL´fZ3=žŒÓ3i™µÂÚám¬–ÖKyͼæâq9^œ¯N÷Fû°ÁØ`ïdw²<¡ÞPïYg¬±ÝÌîæwÌ;æ*G£ÑðÈød~À?` l6HP´(Z"ÉdŒ­FV¤5’ÉSd©²fy3<ž.O(µ”ZÊvµ;Z°9X°¬XV-MÖ¦ëœÎøµ|Z‰aD±´Ú °FX#ÔŸjKÒ%é5‰Äo7‰xŒ¼Fÿ#’6I$½^?4ŸšTy*<–u‹:Åù’üÉ–àËpl6 œžŽOGÈ þÿ…÷Bû£ÑŠi)t”ºbå1rž½O^©$T’*£ÅQâ¿_Œµ!Z®•—JËõÅúãÕˆjÈìdv3_Y¯­†CX¹¬\ÚPm(7’›ÉN"·[—¥ËÒéŠtÅ;C¡…RŽ)0¸˜\G@# Ôjb°û}È>Íf Èd-r¹´ZKv%»=J¥o2·™u˜ºÌd 2²ÍYg£QáÈðäJ?¥!{½ˆÈ$dK­%Ö™1L˜§ÒÓéjS%)’«õUú°‰XD­|V¾+²ÅÙcõ€zÅ b2uÙ:ìæös{KA¥ ×kŒ6Îg ó†ùÃŒ]Æ.æÓsiºœNÊÇea&P“(Cª!Õ¦‡ÓCªÕßøoü6æs á†ðÜÎõ”zÊB,!úHý$ãbq±Sx©¼[ç-󘿌_Æ£QÅ€âÀxq<8ŸÞïe2Š45“–IË&}>Še2˜™LL«ðUø,aÖ0ëo%·’ðÝxnÁI`¤±‹XŬ®W=åžòÓâiñ6 ›ÍÄ6â€ýÀ~är 9ô\ú.¶—[K´¥ÚRê$u:û}„b 0ì4v5Îç u:‚÷±{ؼ8^0˜L €ŒQÆ(úh}4E!"“I‡¥9œálL¶&b¤1RŒ F³ŒÙãèñô€@¡Êåß„oÂRu):›M„(Q”(Êtå:r´Z@²¸Y\. W+Ö¥ëS å„òÇ^c¯3Y‚- ¶„ÛTªÙGl£·X[¬.g ³”­ÊVèÝtnº«UŽÇ7c›¬áÖpìûv}Ž8‡C$‘’H³DY¢(çs‰ÃDá¢ya<°©°TØ/KF¥£À1à ð†øK;%”Ê e°âØq‹ØÅìjÊ5ešNM'ŸÏ‚ívˆ]D.£ÏÑçé^ä¯rq8Æ¢¦QS*)•ÊçµsÚÐ}h>¹Š\Å/²—ÙL=&“"©‘TÍaf°´yŸ¡+Еè½T^ªJù%|™sL¹§ùÓüêa¥0Ò°]X.±ÞXï-ÛÖíëÌ¥æSµƒÚÆäcr2ìYv-&‚“S©ˆÙl‡·M[¦®W «”eÊ2èÔtj;]Ž.ãWq«´]Ú.îÙwlŠ[Å-âQ(`00;ƒ˜AÌ!P‹(a0Ž ³YŠi4ƒ,–@ðx E'"““… Â…n· ~¿gã3ñœŽG¿þ<ÿ†ïCw£†‘ÃIO„§Ânu7:¡þPÿ* U*áõpúÏQg¨¹U\ª¯§WÓ¬;V«"‘FÍ_f¯´zš=Me6²›jYµ,Þ°oX8ªUgw3»¨©ÔTíÃvá»àðNæ·s[À™àL#Uƒ*ÀÿÀàÄâR©½ÁÞᄠ‡tCº+ÇF£è!ôD¢På(r–lK6&$ãqª˜ÕLrÏ9gž¨TH#D¢&I$ŸHO¥Ü’îIßäïò‘IH¤ªjU5,–K^…¯Bímv¶À•`J±g˜³Ì§&S“Ôj¼ÈÞdòÒyi]”®Ê_†/ÃâLñ&ö{ Ü0î~€?@!o·È”äJr3=ž7H¤år‰nd·2hµ4Z`N°("”J9Åâš¡MP©¦TÓ+'“Š÷Õ{êÉdŒµZ„-õ–ú˨ÅÔbôz@¿²_Ù0’I Lv&;¹Ž\ÉdÈ2ûY}¬ã¦qÓAù üÒ§iSµ6š›Moæ·ód‘²HÛAm ·S©ô†úC„ýÂ~ã0q˜9Gœ£Îo×7ë£QѨê£uQ»ÝnâÇqc¿½ßÞñ¢xÑ<Ô^j/Ow§»ÚEí"ø/|¾p8 $’µÚ‡þCÿ"ÜQn(îtw:I$€•JΦ7S©Á¤àÒ|ñ>x¢UQ*©\T®*†•CJ­VήMW&¬G#‹>eŸ2Ú¡mP¹[\­¯W€+é%ô“e‚2Ãaα„ ‡VC«+Y•¬Í,f–3Þ™ïMƒO=§žÕùjü¶ Ûm¤6ÒqY¸¬ÞXo,8 $W+½È^æs¹úý›×Më®× íQv¨»Ãá ‡†CÉáäðôz½q^¸¯u׺ëãÉñäúƒ}AŒk5‚ÌÑfh¦TS*(@ŠmE6¢Ï‘gÈǤcÒ8 ¼Þe22™8œUË*å—g‹³ÆV3+´@Ú t¦:SÏ çÀ“àIþ„ÿBƒ:A!®×H£„QÂ6‘H‘H‡¥Ò‹é{½Šk‘5ÈžO(K%ŠCu!ºœÉNdª*U+DÕ¢jþårÊýe~µ†ZÃ. W«­UÖªõÁzà¿û_ý°ŸØOìO†'ÃiŽ´ÉºdÝ3Y‚¬æfs3B¹¡\Òâiq5BZ¡-s&¹“e5²šÛTmª7WÛ«íõöúû…Y¬ãDq¢9JÜ¥npg83£…ÑÂê­uV»ŽNâ÷q{¿½ßÞñ¨xÔ<ÔÞjoO§§ÓÚEí"ø+|¾oŸ7Ï´·Ú[óEù¢sž9ÏÔÎêg¯3×™ú@ý ‚A !g³ˆôHz2 éGô¤Ðh h´ fÝ3nœáNp§ÿSÿª0E"˜!L¨ýT~ªû•}Êìuv:Æ9c´UZ*­Âá ›UͪñQx¨¾ç_s°YX,¬>V+-Œ–È´dZ2Äb Ök>}Ÿ>ÑÜhî5‚ c¦±Óaa°°Úom7·[­ç&ó“½ÀÞâ^q/9‰Žc'1“ Ð éÓtéºçsŽÕçjó¼iÞ4ðÐxh< žPOBÇ¡c×9ëœ÷e{²¾>_/¨gÔ3ðUø*ýš~Í šFÍ#-!–Ãxa¼0ÇcŒ£ÆQã`!°ëuŠ@ HÁHà¤ßro¹Tتl\˜.LÌ€æ¶ã[q¼lÞ6rÈ9d›͇ ÓÐiöxû<@¡&“H€”@J-ñøŽÓGi¤ŠÒEiW«Šb•1J›ÀMà'¶SÛ*Åb“½IÞ§ÝSYŠÚem2Áå`ò³c P (;”Ê¥RŠ…EB£xѼi$‰’Q™(Ì—™K̦®ÓWiÝtAÆ£ÜQî)µÚŠ›õMú²Y@¯pW¸,Œ–FKN¥§RÞio4ºB]!/4—šKõú‹]ƒ®ÄJb%1­ÖŒ‘H‹-–ÎÍ­fÖ³ûý"V‘+QU¨ªÖuk:¶#[­ªÕ r¹NÞ¢oQ8&œN(çs‘ÅÈâæLs&: Ž P¯ ׄí~v¿;Ì]æ/—‡KÊQå(ôMz&½yÞ¼ïw÷»ûä5òú™}L¿„Ú—íKüþG˜€L÷‡ûÃÀ±àXæs8‡CŽI$ƒ´ÁÚ`ûH}¤Cw!»’<ÉdñRx©V€«@\s.9˜ÝŒnÆo#7‘ªÕ n7œcN1§QC¨¡âLñ&|> àÏðh/d²E ¢‰³DÙ£=ÑžéT‚*MÝ&î–«KU¦t: ÎDç"•?Ê¢âQq)w”»ÊŒ%F®aW0®lW6,MV&«?UŸªÚ‘mH¹B\¡.ö×{kåµòÛ}¾ÃUaª±qX¸¬¦@Ó)½”ÞÌÁf`³ÀÙàmFŠ#MŦâÕ–jË5êZõ-›ÖÍëo ·†ÝÅnâ·ðø ‹Ž}Ç>åzr½9Õ\ê®’·I[«ÍÕæì»v]»]ίG#Ç9ãœózy½=L^¦/l7¶áUðªùß|ï¾ÛŸmÏÏ'ç“ùÍüæÿëõÀX`,%02˜X¤¬RYe,²—,K–&ãq´ÆÚfç3sš“MI¦Ü“nIÄÐâht§:SžO§·³ÛÙûŒýÆ‚EA"¡gP³¨ŽÔGj0ápmG¶¤ªÒUi\Ô®jc¡1ЛýMþ§ÁÓàê E"“ÙIì§ÖSë*«•UÊØEl"Á `†²ýY~­l¶ …5šëÍuæ½s^¹¯ýþŒ&f3Q‰¨Ç5cš²c™1̽Ö^ë8œFÐ^h/4£ZQ­K¥ƒ[E­¢Øãlq¶»›]ÍÏç‹{•½ÊàÝpn¸±ÜXîJ·%[š ÍèZt-:Š]E.¾ç_s¶½Û^ïgw³¼CÞ!ï+Ç•ãÑ¥èÒö {=ç^ó¯“GÉ£êíõvüA~ ¿p߸oô'úâq%Àä`r$R’)I „Ž@« UÈ dâ ñ”‡ÊGr#¹üI~$èrt9DÀ¢`T*½ÊÞå rÐ9t°ºX`a00˜êÌufp8©XÔ¬m«6ÕœBÎ!gFC£!ÞÜïn{=‰›ÏÍèd ²! ˆGD#¢ã‘qÈëÄuâG½#Þ• J…¦ S)³ÙŠxÅR+íöŽ*G$R) Fd£2]Å.âšzM='\Ó®j‚QF¨¦0S*A н•^ʺa]0±LX¦,ÿ–ËiÅ´âäÍrf»¾]ß/ׯì Ök q†8ŃbÁ±öû ¢ÆQc1µ˜ÚΫgU´9Z­0Ö˜kTµªZN*'ÅÉbäbH1 X,H`$0u :„¯’WÉ1´˜ÚN 'þIÿ% B!Q(¨”V·+[–RË)e¿òßù{,½–aµ0Ú™,Ì–f|#>«XÕ¬mû6ýœHN$'DC¢!Þïz¦=Sxϼh”J‚‡cC±¢£‘QÈÚ”mJBé!t“ÓI饵Ú‰´ÎÚs-9–Ÿ·OÛ¨§SŠWµ+Ú¡PŒ«U«uUº«µ„ZÌáfpµÓZé®— K¯׊õ½zÞ¿æ_ó0”ØJlK%‹-ŽÉ[d­²é™tÄÐh 7Ø›ìO'”'Êå#‘áP¨HV"+Ë å«òÕùuˆºÄ_ý/þ˜­ÌVæXƒ,A¡¼ÐÞkc5±›˜ÍÌgc‹1Ñüèþw;Æž¬ÏVg܃îB‘È„B!ÇÐãè¢ÔQj51˜MH&¤×k‰eT²ªeM2¦œ:N'É“äÊ e2“MI¦§¢SÑ*šUM*ÒUi*¿E_¢²yY<­E¢‹z•½JèÕtj¼®^W/È—äL ƒчèSÊ)åYÊ,äÿb±>hŸ4PO('”A … B!M ¦T´*ZšŠÍEƒrÁ¹i´€\m.6—·‹ÛÆà a„ÇÂf3?šRM)&Á#`‘»ÈÝäqá8ð5Κç}S¾©ëHõ¤}Ú>í 8H?ŠÙ ì‰ÿDÿ£@‘ Hÿ”ÊKñ%ø•ëJõ¦6Ói¼tÞ:zq=8¡uPº©”ˆÊpõ8z§SެV?«ËÕåëEŽ¢Ñ•hʶö[{.`0 ¿õßúùÉ|äS¯)וЇÅPâ¨qXX¬,Wh+´2‹E§‚ÓÁq¸Ž^J/%Ì æ-#‘”¼Ê^g¸3ÜÍGæÏcg±¾Èßdr‰9DUΪ烃ÁÁì”öJ~?  AÐ è?´Ú© Ô‰èDô#4ÑšhüÄ~bJÉ%d•£JѦS©¶DÛ"xÍ­V’ŒIF%Y¬‰ƒôÁúlA6 ÛNí¨'Ôê6e2˜uL:¨ÉTdªÜÕnjáõpúÂÙal³DY¢-qÖ¸åªâÕqk˜µÌ[]-®—Ë‚eвèYxˆ¼D_j/µ7ŒÆ'Ãá‘\È®fE3"š F¨ÃTa³dÙ²o-7–œk5‡BáaÛXí¬yo<·Ÿ …Çíóöú½^„oB7¡ÎÐçh¡P‚3…¶GÛ$ RP)TTª*`Y0,šÓMi§gÓ³ê¥ÒŒ5F¥½RÞª• J°ÅXb¶¡[P°8X,²VY%ëBõ¡|ܾn`0˜;ŒÆ!“ÉpƸdà2pœLÎ&ƒ“Aɨ´ÔZl16›˜ÌG #„‘Ë”åÊuE:¢ðø¤“ÒIóùÈ?¿ ŒPF(M¤&ÒA ŠLE&#C‘¡Èý~‚JI%$•EJ¢¦ «ÔÕêuÁ:à &P(´”ZJW+Š MP&ª³UY«S©Šý~ŠÉad°c 1„˜õ z†M&‰—¼ËÞgB3¡-M¦¥CR¡°\Ø.mø6üüÍþg!S©Ðüè~v;@ž4G´£ÚQöäûr€G@# ´ZHV´+Z 1ЬEV#W‘«É$€Jé%t•_J¯¦Ói«´ÕÚue:² P(¨”TJTU**ŸiO´ªjU5+=Õžêø|žlÏ6h4Nšo7ƱsX¹²Ùn)7›öMû's¹Î˜çLu´:ÚöŽûG¡ƒÐÁñ|ø¾~¼?^ I$ˆ9”Êu :ˆœDN"ÍfˆÛômúAe ²’õIz¥d’²I‚„ÁBk55š^N¯(”J(år”‰JD§®S×*ŒÕFjËue¹±pظm†6õÚÇ3‚™Çtãºs¡9ÐWŽ«Çucº±å0ò˜{l=¶f³Gý³þÚm6„„BB!¸Ü•J‚/E¢ŽFG#$3R©5$š’W+À˜dL2&¼Ó^iØ$ì€5@¢—QK©D”¢Jy<ЍYT,¬‘VHœN'SŠ)Ê,åt:dβgtsº9ä òzè=t8œï÷ŠI$ƒ=Až¡a°È|„>B(•JŒF?£¹QÜ©4ŠšO)'”–@K &/S©³ÙŠvÙ;l .P(ª”UJR%)žeO2ªU«•GNÓ§iØøì|w¨;ÔNÏ'g­ÃÖáòŒùF~q?8  ˆ&¤R †žCO"3Q¨±$X’5A ¤GÒ$~Ò?iE´¢ÚZÕ-j™(L”&ã“qÉßÄïâ•@Ê¢ÚQm)OT§ªzÅ=b¨%Tx‡G$‰+ä•òT*—XK¬&iS4©¿´ßÚya<° ³PY¨ÆTc*WE+¢Ÿ5Ošª(UÏ‚çÖëû4ýš€Q@( z=H9D¢• ʇ8Cœ"JQ%(²Y 4­VEG¢¤YR,©9dœ²W +„˜ L¦‘“HÉÈ´äZ{A= ¡P‹(Ù”lÊ[u-º PªXU, 8H$´Z€…]B®¡ÁP਋´EÚ*E"ŒsF9£˜ÑÌi´‚ÚIÅ$â”yJ<¥©Ô‰ŒtÆ:kå5ò.N—'×ëŠ% ’QG¨¦S *U ª©…TÂa°…®B×!ÌÐæh‹¤EÒ)i´ŒF£vÑ»hû}ŠFM#&“ˆIÄ%`R°)xä¼rf3F›¼MÞ'u“ºÊ5ˆqD8¤TR*)ž”ÏJеEZ«¥UÒ‡¹CÜ¢CÑ!訔TJ01¥FÒ£ÕÑêéôˆzK}%¾”ºJ]%¨’ÔI‰Ä‚jI5$œ“NI§¦“ÓJ Uª‹Eˆ¤âRq)ÁàŠ’5I­ V†‹ E…#ÑŒèÜänr=Q¨áHp¤¢Q D4¢Wý+þ—ÔKê&j5 ¹„ÜÂv ;Ÿ}O¾¨^/ 7õú–=K§˜SÌ*jU5*¼^9A ¨GÔ$EÒ"é(Ä”bPq(8•±JإْìÉ‘tȺke5²œªNU' ÓÐj•J‰]D®¤MR&©TÈ*„B © T„¬JV%$c’1É-t–ºQ (†•°JØ%ÎRç)$Æ’i±4ØœN'tÓºiú4ý…µBÚ£MQ¦©KT¥ªpÕ8j£õQúªîUw+9Uœ©KT¥ªX,—[K­¦3‰¤4ÒoY7¬iN´§ÄÓâj •JŠ E¤KR%©…TªE?¢§)S”«ªUÕ+d•²I}$¾’dm26šjM5&õz‰Ôäêr{M=¦ fP3(~Ô?j:•J•YJ¬§S‡ª7UªªEU"±áXð®HW$&¹\‰ÁäàòuÝ:îžÑOh¨ µ Ú F„¤ÞRo)”ÎÊ‚…AB§=Sž«“UÉ«UªŠñex²w•;ÊŸ!O¨” Je2Œ¡FP¤¢RQ)†ÔÃjzµ=Z¥ R†ªãUq«#U‘ªã…q¿ñ_ø ÔPj(…BŠ55š’­IV¦S)ÛÔíêGŠ©µTÚ¬V+f•³Jó5yšÃ…a¤ñRx©ŒÔÆjw¥;Ò£aQ°ª5Uªë•uÊÒÕijº­]V°:‡ÇQB"D˜H“ ³"vdSЊzbR,JEÒº@Ÿ]“ë²›€Sp äI\‰-`¬¼h— aƒŒ•¹’·3Õfz¬ÔÿšŸói2m&Mô¾ƒ8û`è<“± v$Aà(<æGœÈöçÜãçâ|üOÍ1ù¦?á'ü% @䎂1Ði: 'B'hDí Œ!Q„1’†2Páê=Cïh}íF"(ÄJî‰]ÑAâ(Â`ûvnÃù²6PbL@í¨µ Ãd!N„)ЦZËChbÍ4¡Æ”?ƇøÑÚ#DÄ蘉"‘$W¸Š÷sR.jFHÉœ#3„jþ_Éma-¬'ÜDûˆ§í”ý²ÀÌX‹ƒipm/ùEÿ(Ç.åÃ7îfýÍF¹¨×6œ&Ó„àã”Âr˜NŸ™Óó;š§sTòƒžPsÙ@{(!ó¤?EGè© t ¢tPWê ýAü(?… .¡EÔ1.†%ÐäzDh€í×¢:ôMÚ‰»QOò)þEœˆ³‘΢ùÔdVŒŠÑ¤ê4FÝèÛ½£‘ôvhŽÍ ­i5­)e ,¬õ•ž²ÖXZË Þ9{Ç1pF.Í-™¥³Pdj ¨‘µ8Ghåî½Ã«øuúßB<úçŸ\÷áü#î}Àíyý¯@|H‰Ì ™„¾ƒ·Ðž¢ÔC aM!ɤA<ˆ'‘"z$OD÷žáx"¯\‹Ñ†â0ÜFrÎA#aäq Ž!QÖ2:ÆGž(óÅ€#ðV~JÏÉ÷>â*JEIH²”–R’îj]ÍLBшZ3 æa\Ó¨šujDmHŽ Áá9¯§5ô샃Äx€OWÉêù>cgÌlýWªä€`P>ÚÛA·ˆ6ñ Œ¡1”0&†Ðå‚°Dhƒ­t"N„QŠ#‘_ê+ýEçh¼í##ÄjBѽú7¿GG¨èõI£É4}’²RJ@éH]  ›óbhšMJL I+Áåx<¸ã—s a ̰–4Éæ™<ÚŸSã…ÒpºN{aÏl;XkòƒžPsܺ{—O·aöì?ÔÇú™å \´`ƒ ‘Â8Bóh^mR¡ÊTBzˆOQ,%‚E/H¥é"ÒaŒ"‘º3·FÖHÚɸ£—x4‘ô>‚HiM!¤04Š‘CR7ÚFûI)#d}s®r"ZDKI?¹'÷'ÜäûœªÈYÖºZ×L‘€2fÆLØÒšRj¾mWÎ ¹Ä:.EÀîÅØ£Ðøz‘Éò9?d'ì…‰ 1$Ђš‡úÿBàÈ\B¡ÈTCˆcÑ1º&7ENH©É/¢åôcþŒѪÂ5XGHáé­#µ¤|(…b@¬Ha) %"¥$T¤ŽÒ‘ÚRJêI]Ii‰-1&‡$ÐáÒÊ:YH¡ô#-de¬™*%B–|RÏ‹‘cr/NeéÌÇî˜ýÓGhâ«yµo8°çéë=sÁ¦x4Ïaéì=>ÓGÚhÿçŸüô$‚|"„BÉhY-)¡Å4CÚˆ{Q7&âEpˆ®Ù¢û4g6ŒæÑ¹Š71GNhéÍÅ#ؤ€´’òBþH¯ií#æ$|Ä“ú’R_ÚKûI¼‰7‘'Û¤ût¢Ò”ZR˜ZS ¯cUìvøŽß(B y#O'Úäû\¬õ•ž²çV\êÌ^!‹Ä44憜ڷ›VóŽFqÈε‘Ö²»G×i— ä<‚ÇžúßCthn8¢'QHŠ)pb. FbèÌ]¾£wÔw8Žçúj?MHUi ­"á$\$‘:’'RYÚK;Iµ©6µ'ô¤þ”¤”€ÒŸâSüJº©WU+É%y$²P–JÕÊZ¹K„‰p‘.ĥؑ!J$)E/ ¥ôÝû mw ®áô„>ˆßií'ÍÄù¸¯üÿ‚ÿR_êL⡜T6®¦ÕÔ宜µÓ¼wƒp©î?~GïÉß {ô$„€P×¢ôDWÈŠùÖ"šÄ_ˆ‹ñ¨â5G@è(#å„zR.šEÓI" $A%ý¤¿´v“®Ò‰:Q'JoÉMù*Æ¥XÔ¯•áÒË*YeKbél]._%Ëä¼r—ŽRýr_®L i„ 1#¦$tËÄ™x„VøŠßN‚‰Ð_@ è½Â7¸H‰Q%Hd© ¨Æ•Òí,]¥ŒÃ9˜g6­FÕ¨çâ#ÆxÀÏ£yôo@aˆ 1´ ö„4†ƒQ*#E[H«iÙ#$o2æQçZ<ëH/©õ"á$\$“’af’LÒIý)?¥)T%*„ªj•MR»âW|K4éf-Ê¥¹TºÐ—Zø¢_L ‚Á1&"dÇ ˜ä&zdÏLÂi˜M3˜¦sМš“F’hÒMXÉ«5¢&´AÐ Úîèݹb÷,r ŽA2ìC݉ë‘=r-A¨ É[™+sjDmHŽÑã=&'¤ÄþÂäš‚óPÄêDiˆ12¢ÆTh>ÑÕ‚:°HÉÙ"Ã$Xd“Â’xRn¢MÔJ)ÉE9*=¥G´®~•ÏÒÎjYÍK„‰p‘/¥ã4ÀX˜ zaïLsiŽm2…¦P´Ìö™žÓ>ZgËM!é¤=5&£$ÖžšÓÓb’lRMª‰µQ6ã¦ÜtÜt›Ž“…êp½N$ÉĘ<àœ.®%ÕÅæ ¼Ôm#­¤i "¯¢UôLTÙŠ›6œ&Ó„ëflÓâ¦|TÐ0ÂBÊ(YEÄ¢8”\œ‹“‘¹7 GÓ¨úu"{¤Ot”¾’—ÒwŠNñJbÉLY+M¥i´³š–sRåj\­Kä©|•0¾&Äǘà+JeiLä œ4P¦ŠÔ8š‡[k`M”‰²‘6Û¦Ûtݨ›µ~ŠoÑNÉÃ98Ô§”å œ¡Sš‚sPNviÎÍ:§@´ì˜“´Êv™ “!²dFðˆÞvÈ.Ù³ˆöq'`¤ì¾ÍÙ£hÚmNðqÞ?‰Gñ) k¡-tGâˆüQˆò1GnÈíÙ!ì¤=”•š’³R„BPˆJ¬éU,Ö%šÄºV—JÓB`HLe Œ¡2Å&X¤Ït™î“Mri®Mmi­­6€¦ÐÜ⛜S~oÀN‰ÃÑ9§ tæ(œÅŸÚsûN›‰Óq:Ù'[$ìúŸS¹Âw8Ný©ßµ,§Å€rBÎÀYØöáþÜ\U Š¢&B„Ë.¡eÔ6j¦ÍTô\‹„vƒÑ!r$.F¤èÔ ×¤ô–Ä’Ø’–¢RÔKicm.â¥ÜTÃô˜~“+úeM )¡e5¦°4Ú†›PÓy:o'NIÃé9B§(T羜÷Ó¨òuNÇiØí;§'täðtž“ÈZy O;‰çq=F'¨ÄövžÎÓÞR{ÊOމñÑ>„§Ð”ûŸaÓð‚~OÑ)ú%?~§ïÔþhŸÍêÝPAÊ8¢‚TP{‚pC7øfÿqâÎ<™N)ÃbØl[Ý©ûµD•ˆ’±W£Êô˜p“·ÂVøKÕ‰z±2I¦I4Ò¬šU“hbm NiÀM9K')dé¢4Sµ¢v´OIàé<Ò§šTõÆž¸Óߊ{ñO©óµ>ä§Ü”ýŸ¡ùú?Oû ÿa@3¨uø ? JiPýGܨû•h£í}r®QöŠ>ÑGÚ(ûE%⤼T–Ú’Û£T”j’ƒÞÐ{ÙÅÁ8¸$#„`tÚn›M‹ß1{åÇØ¸ûdJ̉Wßêûý[4«f•`ú¬U`b¬ UŠ£SªbµH"©UJ¢IT ‚áO·)öå<Χ™ÔëzoS“*reM᩼56H¦ÉÓúšS<ªg•L°©–1ܦ;”Ę€Sª`5Kשzõ.ð¥Þº:—GRäÊ\™Kz©oU-œ¥³”µB–¨RÐ*ZK*©eU,h¥°š–R¾JWÉJî)]Å+‚¥pT­*•¥R¯*UåJ©©U5*p¥N©:•'R¢*TEJƒ)Pe)à¥<ãšœsS„ pM驽57:¦çTÚª›USaÊl9Mn)­Å54¦¦”Òòš^SFJhÉLø)Ÿ3r¦nTÌj™S+ŠeqL©‘õ2¦AÆÂ˜ØS*bÅLK)‰e0¨¦Ár˜.Sª`5Kû©u/¬¥õ”½Ò—ºRóê^}KÁ©x9Ç 8áeòÔ¾ZÂÑøZ nÁ-×-úå¿Yþû?ßaÜL;‰xܯ•¾R·ÊV“‚ÒpY"«$U`ý,¥x¯U¿Ò·úVŒ ÑX¬«•]¢«´Ud‚¬USŠªqTu*Ž¥Nô©Þ•0Ú¦T›Š“qQäª<•E¤¨´•¢¢TT=Ї±P¹ª5B¨B•’ ²T J)Oö©þÕ?@§èúŸBSܪ{•OQ©ê5<¢§”TïòþS¸Jw N·©Öõ:\§K”犜ñS–ªrÕN4)Æ…8b§ Tßú›ÿSyÊo9MÑ)º%6â¦ÜTÚR›JSc*leMv©®Õ5˜¦³Õš SPŠjM,)¥…4z¦TкšU{j¯mUˆ*±Tžª“ÕM© Uª£•T *„On©íÕ8º§TÒŠšQS*`¥Jö©^Õ(B¥T‘ê’=RªAG\¨ë•Ú£;TZJ‹IQ=*'¥D$¨„• ª¡µT,J…‰P‚ªUAZ¨+U* eTj€MOà©ü>þ§ßÔùºŸ7SÞ {ÁO\©ë•<ú§ŸTòZžKSŠxQNî©ÝÕ;X§këªuS©Êu9NŒ)Ñ…9â§UG¨à•R£*T[ ‹aQ=*'¥DD¨ˆ•2¡æT3*†eP®ªÕB*¨EUš ÓTŠ‚qP-ªµ@R¨ TÿjŸíS÷j~íOÂ)øE>š§ÓTù Ÿ!SÝJ{©]+¢qØ®;½š·³VÓjÚmZÁ«X5i"­$Uœ¢³”VU Ê¡Xæ+ÅaÖ¬:Õ*°%Uíj½­WRªêU\ «•jÚ­[U•в±V ªÁuVúªßUW"ªäUL ©TèªRšªSUFÒ¨ÚU j¡mSô*~…O©à8 §TÓ*šeS*cÅKœ©s•+‚¥pT¢j”MR]ªKµHè©! ¤!T{JiQÌ*9…F–¨ÒÕJ£ TXJ‹ QBª(UD’¨’U ¢T: ‡APÊ*EBº¨We ¡ázïB$–D’È{ï^PÓÊqÎn9ͯWµêöuΡØüCˆaf,,Å}ï¸uÝþ»¿×)2å&[ ‹tk+­eu¡P´*^ÊËÙXõK©b,@E„¤°”–²À¶Wê*ýE^ó«ÞuyD¯(•Úò»^WEjè­\‰«‘5p®¸r·VÂJØIZ+Rhˆ­›³cVSªÊuXî«Õb.¬EÕƒj°mU÷ª¾õW†ªðÕ\è«nz­ÏU§J´éVXªËX:«U\Ê«™Ud ¬UKª©uTFªˆÕMZ©«U&J¤ÉTdªŒ•P¶ªÕ?r§îTójžmSŸ*såMÀ©¸4ʦ™TÈ*™Rú*_EKD©h•*Ò¥ZT¢j”MRf*LÅI©"•"B¤HTj-Qâ*©œp3ŽLtÉŽ˜·sîaCÌ(y¯/õåì^½‹×’òB]Ì«¹•u€®°Ïf¹ì×$ºä—\9«‡5oŒ­ñ•¹L·)–ÑÒÚ:[ë`=j÷­^õ§Ê´ùV’ÒZë@ýgO,éå™î³=Ö[âË|Y@Ë(dN,‰ÅŽŽ±ÑÖ/òÅþX™ë=aÖ,:Å„ü°Ÿ– âÁ|X ‹‘_¼+÷…|ö¯žÕìz½W–jòÍ]뫽uv®ÀUÑÚº;W.ÊåÙ\`+Œp ®Uº‚·PVÕjÚ­[«`j¸­W¦´ÃV…*Ð¥YÊ+9Ef¬ÂÕ”:²‡VAÊÈ9XÈ+b:¬GU…"°¤VjÀíWç*üå^À«Øx¯UÒŠºQWªãÕ[´«v•l­‚U¤Š´‘Vi*Í& ¸Á7£_tKî‰{Â/xEçX¼ë€êð]–k²ÍtÓ®šuÍX¹« ãá\*‹…Qon-íŹ,·%–ÒÚÚ[[ ËaYk+­eu¨þµÖ•âÒ¼Z!KD)g¼¬÷•›Ð³zcòÌ~YaË,9d߬›õö²Ö9ºÇ7XÃ+ebu¬Nµ‡°°öâÂÜX9Ë9`q,%ʯùUø"¿WÅÊø¹^¦«ÔÕy¯#UÞ »ÁW^ÊëÙ]#+¤es ®a•Ʋ¸ÖW*àÅ[Á+x%mÄ­¸•²r¶NVµêÖ½Z˜+Si8­'¡´#VtªÎ•Y™+3%ef¬¬Õ’²@V:JÇIX²+Eb¬@U„r°ŽV ÀÁWä*ü…^à«Üxj¯ U×jºî/‚EðH~…Рß\ë~ÉÏÙ9è7=çX¸ë]‹£màm¼ «I5i&ƒšÐsYÓ0:d*Ì…YŠd1L†2ÃÆXOà ø`¤Œ‘€\0 …øü¿—IJø–^žËÓÙxƯÕÜ´»–—ZÊëY]K¢)rè.]Æš¸ÓWŠàñ[ÖËzÙnE­Èµ´ì¶–ÄÂØ˜ZÖ«ZÕj~­OÕ¦–´Ò֪ѵZë@ýg_¬ëõšÊ³YV`*ÌYY‹+1dÄ,˜…¨²9âÇÖvîØO; ç`®,Å€˜°ú´¿V—ÌrùŽ^ÅëØ½yk/-eß »ôfúìß]D˨™sÉ.y%ÊL¹I—ÒâÚ\‹‚QoB-èE¹ ·!VÕ:Ú§[KcikŒ-q…ªìµ]–ŸÓàZL«I•hu-¥Ÿ³ãÖqÚÎ;Yœ+3…eÙ¬»5”þ²ŸÖK:ÉgY‹!c˜¬sŒh±)jÅ-XˆËa­,5¥„ܰ›– ºÁ—XËÙ_õ«þµ~V¯ÊÕòú¾_W¶ öÁ^z«ÏUxt¯•ܪ»•W\Êë™](«¥sz®oUÉJ¹)Wêâ}\ +eo$­ä•¸r·VÒªÚU[ «aukH­i©Zµ+V—ªÒõZ%+D¥gάù՛ʳyVbÊÌYY\«+˜Ž³ÖaIœ)3°#uóÕ¾z·˜ñ³=K§©qo®-õ¹7#ÍvÙ®Úà³\jCHq¤q4Ž&€æÐÙÃ;8gf$ÌÄ™•²£6G¼È÷˜îãÜc Ìa¹‰™13&ÜÛ˜O; ç`·Ìù€Ý0¥ûþ¿×ÓÒúz^å‹Ü±z¯@õâ2¼FWrªîU]tË®™tš.“EÍŽ¹±×$Šä‘\Lˉ™p+®u¼Ò·šVäÜ€[Xkk lƒ-e®ÔµÚ–®ÊÕÙZŠkQMiw-.壴`ÖÒÐ:YÝk;­fÞ¬ÛÕ™³#–ZÚË[YIË)9dš¬“UP² 9ºÇ7XÇëýb©¬U5ˆÚ±VZËXWk í`ï¬õ‚2°FVšÀSWòjþM_o+íç`âÌø+nnÍÑ´I¶‰6¼Î×™Ú§Tài€0¡ë´=vxÏ™§k4íeÃL¸i“ù2&DÈ‚Øä;‡bìì]‰Z1+F8ÇOk í`¼Œ‘0"åý&¿¤×Ú²ûV_kàMz–¯RÕ䄼—|šï“]¢Ë´YuO.©åмº—0êæ\«5q. EÀL¸ –òBÞH[‘kr-mo-­å²z¶OV½º×·ZÆ+XÅjh­M¦Î´ÙÖÂÒZ+CegÍ,ù¥œä³œ–j"ÍDY…‹0±e’,²E”.²…ÖHÒɉ³6b¯Ã5ø.}ÏŸÐÓú{qáßÈ»ùc~ìoÝ"[¤Krïn]íÅ•8²¦ÿ(ß奛t³mGM¨é°N¶ Ö®°ÕÖw£NôhÛ aŸ¸³÷p¢ÎY޳1Öes ®a’ô2^†@ôȘÛ{obÒÌZY‰±!öÀÃxOk í`ÄÌ™E°(µþp¿Îà*ü_!Kä){¯aõæà¼Ü†:ðÇ]ËK¹iuÿ.¿åÓ†ºp×<êç\± –!qÐ.:Ãb¸lVÿ¢ßô[ÆKxÉnK-Éeµè¶½Ë¢ÙtZÿË_ùkD­h•ªtµN–ŸÓàZTËJ™h´-‡“ÐòzäS¼Šuð޾Ñmº-·-Œå±œXû‹pnͺڷ[V׬Úõ›cbŒk8­g¨Ï5æ“|ÒoškBgsLîiš¢³TV^fËÌÙKë)}d„ ±ñ¶4ºÆ—X¯»÷b5ŒF±†Ú0ÛFÎÂyØ2ë]`]Ì ¹à¯üùj¿-WÎRùÊ^ákÜ-z3¯Fuãà¼|{êï}]© µ!uƒ®°uÑîº=×7Êæù\¢«”Uq™.3%ø`þßÃ[Ä+x…nM­Éµ¶ ¶ÄÍ*Ù¥‡nÐíÚVÂ*ØC(_q î!zrÏNYã:âÇÜ뀽nÔíÚ¶«¶ÕvÈÔٚ߃[ðj~OѦ64ÆÆŠ¤ÑT™õã>¼g ìá]™@3(Y¢Ë4Y=S'ªdSLŠiŽþ±ßÖ2ìÆ]˜ªcLb'ÌDù†Æ°ØÖðÂ~4Ë™`kŒ q€0¥ú¾¿W×ÔRúŠ^ý‹ß±z©¯U5åׄÚð›]Î ¹Áv .ÄÔŒº‘—BjèM\ÍK™©rV®JÕÅʸ¹W áC{Šïq]çÙ¼û7†ððÞ»û·uw®®õÐ9º7+\åkœXÓ‹p&®Õ»×7zæÝ@Û¨0[f k»Íwy«µcVÈÓ¹@cH h0Í®³µÖk:ÍgY€K0 e] «¡’ô2^†B¬ÈU˜çSêc bÁŠ\1K†!îÄ=Øk aa@l( ƒZ°kVRÀêXø_¿+÷å}/³¥ñd¾,—±ªö5^}ËϹxÔ/…Þð»Þkrín]më­½tÆ.˜ÅÏr¹î_ä»ü—~ ÏÁYò#¾Dw°œöžbSÌJxϹڧ»T÷TLꉜûcŸlr£îT}Å8±§FàhÛÄ+x…mú¿Q³Ñ6z&¿X×ë«XUj$­D•¥D´¨–ˆRÑ Yñ>"g ¬áµ™Š³1V[ÜË{™I+)%d ‘á²Ö7ÐÆúÀ[ b‡LPéˆZ± V€ÃPP› `Þ,Åû0?fXÀKó’þr_|‹ï‘|³¯–uî ½Ä¥úô¿^SëÊ}x=/§×~úïßkà z”oR䯼•÷|L[³ku.£ÏK9ég)xå/YS‹*pB.E¼³7–fáØÜ;H“il/-…å­5£†¦ªÔÕZf«LÕhÕ± b´ VvXÎË®û5ßfÌÃ9– 2ÁfNüÉß™Ë#9cáL|)‘1²&.ÂÅØXž»×b A¡†˜0Óƒ˜9C(`‰Ì9€Ê0Eý࿼ä"ü„_B+èE{è¯}¸Œ÷ž‹Ñ`xÏ/åÝõ»¾·cFìhÝ<›§“s·îvýÊ'9DçŒâÑœ£‚toBÍèY¹· öÔ¶Ú–Ûûbÿk€Ípªµ5V¦èÓ½HCIhhm žâ3ÜFqÎ"#3¤eÚ¬»U•7²¦öL(É…[" cÄlx6±¦Ö-°Å¶›ûb,@ņ0Ѧ<‡˜:£T`’ A€þ0ÅþÞ¿Û×è’ýyÉÏ99â¼@×s¬îu»¯÷tÌ®™•ιÏ÷(åÜYƒ‹0pZî ]½e·¬¶åöܾÛ\£k”l‘m’-®ÞµÛÖ®ÕÒˆ£Qie ,¡¢Ä´X–€xÐØ+;fÇŒØñ˜Ê3FZ<ËG™G#(ädš “A‚2F:ÇRÍë½bËìY}‰Ž11Æ ÄXh# aGL(都°w¶ ÄÁ8ž ÁÁvÿßá×CºèwJÆéXÜâSœJruîN½Å¥¸´·”àÒ›Úû{_n~mÏͶi¶Í6˾ÙwÚû#_dk ­d©‡50æšdÓLš>SGÊhPM ž»³×vqFÎ(Ù¡;4'eö,¾Å•Ʋ¸ÖOÉá“#²d,€ŽC±Èv2HÆI¯»÷bZ¬KU‡ô°þ–ÃB]²‹¶Qu©µ!Ò+:Eg7–æòÜ››“sqjÎ-YÁ˸9vøßÛ¦stÎm¾-·Å³”¶r–Á´Ø6šÕ‹Z±jšÍSY§˜´ó“²ÒvZ%kD­g÷ þá‹3±flüÍŸ™‘s2.eÀŒ¸•2¢†LŠÉ‘Yƒ"°cä |î±½Ö14Æ&˜¬K‰bQ J!ÚU;J§X*ë]££tsg®lõÉž93ÇÎâÙÜ#ƒäo•Íò¹ºÝ·[¶ÝÐÛºCëh}lIM‰)®$5Ć­0Õ¦ˆ{Qiy­/5£f´lÖƒ¼Ðw™ê‹=QgLã)šM³I¶`øÌd{,e £!’Ÿ2SæCrÈnXó[kcfllÍŒ±‚wUÂê¸]û¢¿sY.k%É}9/§úâß\"c„Lo§ ôá»@7hßÈÛùM3i¦lsÍŽy®ìµÝ–°hÖ –‹RÑi·m6í¤X4‹ˆÑÙû[?kgb ìA›e³l¶ešÌ³Yw‹.ñef¬¬Õ“Í2y¦HVÉ Ù ãu<ާ‘Ðüº—4 æ”—K’éqq®.5ÂJ8IFû¢ßt[¹[w+nÃqµb6¬FÉæÙ<ÚùC_(k: gAª?5GæžüÓßšU#J¤h¾-Å º´Vz`ÏLdz8öf lÔ ˜–3Æ[Ë`YP *dÐLš Ôt:އBªèU\ÏÙørTîJÅ×8ºç Âá8[òƒ~Pnþ ßÁ¸Þ·Ö×–ÚòÛ0³flm‚­šµ³V¬>Õ‡Ú‰SQ*iŒí1£ç´|ö†èÐÝù#?$gd,ì…›Ž³qÖfÎÌÙÙ}[/«e‡Œ°ñÐX: 3,æeœ”‹’‘qwî.ý™¸S6ýœß³›Å[x«nS-Êe¶l6͆ΈÙѳaÖk”­r•«Ð5z¥ŒÔ±šq#N$i3­&u¢£´Tv‚FÐHÙèS= g&LäÉš»³Wvc¬Ìuc ,s'dáÉ5¹&·¨âõ,[…‹oè-ý¼¥7”¦æþÜßÛmûm¿m ¡q±Š¶1V¼ׂšÈƒYjŠíQ]§â´üV–´ÒÖš8£Gh^Í ÙŸ|3ï†vXÎË»Û7{só~aÌp9Ž$Àä˜`Œp¶îÝ¿à7üóÈÞy¢+tEmÖ-ºÅ´º6—FÈpÙúK_IkP jªì5]†¢¢ÔTZg³Löi-#E¢\4K†¬Ð5™è{=sã.|eÌO9‰ç$ˆä‘`‹ŒpÁ.%À¸–ôòÞž[§{tïmôM¾‰µ66¦ÆÊÂÙX[`‚kzoQ«›µsv¥¸Ô·tcNŒiM­)µ£:´gV…У\õ‹ž±s ®aµÉ9"çXã 2ã†\p7±†ëjÝm[ƒóp~mk ­a³76fæÃjØmZèC]kbqª µD :ÔZ_ËKùií ]Ï'9äç06æÜ‘s’.qŠn1MÃQ¸j7àB[Üã{œnÌ­Ù•¸¤·–ؔۛ;Ûg{lZm‹M¯&5䯳ÖÖzÚ­‹U±j/­Eõ¦Å4ا;ìç}œÀ+˜rCHaÆ78Æç ÀḠ“ro…M𩻇·pöäÜiƒm0mm¢ ±ø¶?¾ô×ÞšÙS[*jà \©y5/';çcœ¾—ÃrBnHMÆG¸È÷XáË{Ïo–òÑ»×7zæåžÜ³Ûpcn m-í¥½²v¶NÖÁ$Ø$šã\ckaΦ9ÔÇ/.ååÜó’q–2Áï8uçdàŒ›ék}-o -áe¹È79ÝÄÛ¸›S jal¾­—Õ°Ú¶V»×cè+rî]ÁÉ9 ç˜ã3=+‡¥pSN i¾Ø7ÛñÚÞ;[¢StJmõ ¾¡µ¡6´&ÍäÙ¼›Ãb¸s—®rõ˧¹t÷$ä‚g“ŒòpüîÁ|8/†üRߊ[Ëãy|nœÓ‘¸9·6Ø0Û?KgéråÎ\¹É ¹!7ãBAKˆ)pj® U¿K·évôÞ‚Û¬{un%ÍŶw6ÎæÑ”Ú2œ¸ó—rCÎHyÆ—¸Ò÷À⛃³oâÍüY½G7¨æìfÝŒÛKqém¸­·É9!ç¶ãVÜDëˆp€®¿Ä·ø–öFÞÈÛ¶kvÍnTʑƻ8×g¼â7œ#„boüÍÿ™½Ö7ºÆîØÝÛlãœq"®$UÂZ¸KW¾àÛàû|o-àÄ®¸•× øá?C€Èo“ÍòyÄÊ8™G ´áVœ ƒ0q8'ÂиZ æá|ÀuóB¾hWÎjùÍ_.«åÕ|ü¯Ÿ•óB¾hWÊJùI_9«ç5|¿¯—õòR¾JWÐú_5kæ­|ª¯•Uñú¾?WÅ*ø¥_Eëè½|ë¯uòþ¾_×ÉJù)_kãm|A¯ˆ5ô¶¾–×Ðú_5kæ­|ª¯•Uò¾A×Åzø¯_ ëá]}C¯¨uô^¾‹×κù×_1kæ-|™¯“5ñ¶¾6×Äøƒ_kà­{ï¯}õàμ×*ð%_Lëé}¯ õón¾m×Êúù__ ëä|W¯Šõðƾ×ÀZø ^ökÞÍ{­¯uµáê¼=W…šð³^«Áõx¯µõ&¾¤×ÑÚú;_µç>¼çךŠóQ^b+ÌEya¯,5å¼ W‘ªò5^>kÇÍxدâÆ¼X׉ ñ!^kíxT¯ •àμר û_U+ê¥}(¯¥óò¾~WÍ ù¡_)+å%|x¯ñ2¾&W øA^ý+ߥ{ίyÕÑW·jöí^Ò«ÚU{#¯duëâ½|W­õ£^ªëÕ]z¯Oõéb½,W£*ôe^‚ëÐ]yå¯<µæú¼ßW™Šó1^\«Ë•yQ¯*5䪼•Wšò^:+ÇEx¯U↼P׈ ñ^+Ã%xC¯uöZ¾ËWÖªúÕ_O«éõ}¯¢Uóš¾sW˪ùu_#«äu|b¯ŒUðÚ¾WÁ ø!^ù+ß%{¸¯wî2½ÆW¶ öÁ^ÎkÙÍ{¯aÕëž½s׫ºõw^¥kÔ­zn¯MÕé ½!W¡Êô9^}kÏ­yϯ9õ溼×W˜Šó^XkË y@¯(äj¼Wšñó^4«Æ•x±¯5âB¼HW†úðß^+Â¥x3¯u÷v¾î×Ûûc_akì-}Y¯«5ô¢¾”WÏÊùù_4+æ…|ª¯•Uñú¾?WÅ*ø¥_ «á5{ú¯Uï:½çWº*÷E^ÞëÛÝ{O¯iõ쎽‘ׯÚõû^´kÖz«¯Uuéþ½?×¥Šô±^Œ«Ñ•z ¯Auç–¼ò×›êó}^f+ÌÅyr¯.UåF¼¨×’ªòU^BkÈMxè¯ã ¼aWŠñC^ +Äx_¯ õá¼!×ß:ûç_qëî=}›¯³uõ¾¾·×Ô:ú‡_Eëè½|ë¯uòþ¾_×ÉJù)_+ãE|<¯‡•ðB¾W¾J÷É^î+ÝÅ{‘¯r5í–½²×³šös^ÄëØzç¯\õë½`שZõ+^›«ÓuzH¯I膽ןºó÷^u+Î¥y®¯5Õæ"¼ÄW–òÃ^P+Êy¯#Uãæ¼|×*ñ¥^,kÅx¯á¾¼7ׄêð^ ëÁ~ ¯Á5÷v¾î×Ûûc_akì-}Y¯«5ô¶¾–×Ðú_5kæ­|ª¯•Uñú¾?WÅ*ø¥_ «á5{ú¯UïN½é׺z÷O^ÞëÛÝ{O¯iõ즽”ׯÚõû^µëÖ½z«¯Uuê½BW¥Šô±^Œ«Ñ•z ¯Auç–¼ò×›êó}^f+ÌÅyw¯.õåF¼¨×’ªòU^BkÈMxè¯ã ¼aWŠñC^ +Äx_¯ õàú¼!_‰kñ-}ù¯¿5÷6¾æ×ÚûC_]kë­}I¯©5ôv¾Ž×κù×_/ëåý|”¯’•ñ¢¾4WÄøƒ_kà­{é¯}5îö½Þ×¹÷#^ÙkÛ-{?¯gõìN½‰×®ÚõÛ^°kÖ zš¯SU麽7W¤Šô‘^ˆkÑ yû¯?uçR¼êWšêó]^b+ÌEya¯,5å¼ W‘ªò5^>kÇÍxÒ¯UâÆ¼X׉ ñ!^ëÃ}xN¯ Õà¶¼€`M_ŽëñÝ~¯Áõ÷޾ñ×Ûzûo_bëì]}_¯«õôξ™×Ðzú_6ëæÝ|¯¯•õò¾A×Åzø¯_ ëá]{ÿ¯õïN½é׺z÷O^àkÜ {U¯jµì¦½”×°:ö^µëÖ½z°¯Vê½BW¥êô½^ëѽz¯B5ç–¼ò×›êó}^gkÌíyw¯.õåF¼¨×“ òa^BkÈMxè¯ã ¼aWŠñC^ +Äx_¯ õàú¼W‚ŠðP€µ~*¯ÅU÷æ¾ü×ÜÚû›_hkí }u¯®µõ&¾¤×ÑÚú;_¯Õàμׂ*ðE^ +ÁEx¯õøª¿Wßêûý_t«î•}¦¯´Õõê¾½WÔêú_H«é|ö¯žÕó*¾eWÉúù?_ëã|G¯ˆõðn¾ ×¾ú÷ß^ðëÞ{—¯rõí½¸W´Jö‰^ÆkØÍzò¯^Uë½cWª õA^+Ó¥zM¯Iµèž½×  ô^v«ÎÕy³¯6uæ6¼Æ×–zòÏ^P+Êy¯#õãæ¼|׊ñ±^-ëŽx–¯Õá¾¼7×…Jð©^ ëÁx"¯UàJ¼ W‚ŠðQ^ëÀ½~¯ÃU÷º¾÷WÛÊûy_d+ì…}d¯¬•ô⾜WÐÊú_8+ç|´¯–•ò&¾D×ÅÚø»_ ká|¯€µïf½ì×»*÷e^á«Ü5{Z¯kU캽—W°Šö^·+Öåz¶¯VÕê*½EW¦:ôÇ^kÑíz¯B5窼õWœJó‰^gkÌíy}¯/µåZ¼«W“ òa^CëÈ}xè¯ã¼c׊jñM^!kÄ-xd¯ •á¼!ׂ*ðE^«À•x¯µà2¼Wã üa_+ð%}د»ö²¾ÖWØ û_U+ê¥}(¯¥óò¾~W̪ù•_'ëäý|s¯Žuñ¾#× øA^ý+ߥ{ȯyîr½ÎW· öá^Ñ+Ú%{¯cÕëʽyW¬Êõ™^¨+Õzy¯O5éN½)×¢zôO^€+ÐyÚ¯;UæÎ¼ÙטÚó^YëË=yF¯(Õä~¼×êñý^6+ÆÅx·¯õâZ¼KW‡Zðë^+Â¥x3¯uàJ¼ W€ð^+Àewü®ÿ•øj¿ WÞêûÝ_p«î}–¯²Õõª¾µWÓêú}_D«è•|毜Õòê¾]WÈêù_«ã|6¯†Õð*¾W½ê÷½^ì«Ý•{†¯pÕ킽°W³Jöi^Â+ØEzâ¯\UêÚ½[W¨úõ^˜ëÓz=¯GµèZ½ WŸ óá^rkÎMy£¯4uåö¼¾×•jò­^MkÉ­y¯!õ㺼wWŒzñ^)«Å5x…¯µá’¼2W„:ð‡^«Áx¯õßÞ»û×Êïù]ük¿~ ¯Á5÷v¾î×Ûûc_`+ì}T¯ª•ô¢¾”WÏÊùù_4+æ…|¤¯”•ñâ¾kÇÍxè¯ãb¼lWŒzñ^-ëŽx¦¯ÕâZ¼KWˆZñ ^kíxd¯ •áR¼*Wƒ:ðg^«Áx¯Uà¼×ïã]ù«¿5wÖ®úÕß»â×ßšûó_skîm}¡¯´5õÖ¾º×Ô:ú‡_Eëè½|ë¯uòþ¾_×ÉJù)_+ãE|<¯‡•ðB¾W¾š÷Ó^ïkÝí{‘¯r5í–½²×³úö^ÄëØzç¯\õë½`שZõ+^›«ÓuzH¯Ièr½WŸZóë^ykÏ-yϯ9õæú¼ßWšŠóQ^f+ÌÅyˆ¯1åʼ¹W–òÃ^RëÊ]y;¯'u䪼•W‘ªò5^A+È%xó¯u㎼q×*ñ¥^0«Æx¬¯•ân¼M׈ºñ^«ÃÕxj¯ Uáf¼,ׄšð“^+ÁÅx(¯àv¼×€Êð]ÿ+¿åwì®ý•ßn»í×} ï¡]ð+¾zy¯O5鎽1×¥:ô§^kÑíz'¯DõèZ½ W  ô^|+Ï…yÚ¯;Uç&¼ä×›:óg^hëÍy¯1µåö¼¾×–ÊòÙ^U«ÊµyF¯(ÕäÖ¼š×‘úò?^CëÈ}xþ¯Õ㺼wWŠñ±^1ëÆ=x·¯õâš¼SW‰jñ-^!kÄ-xu¯µá’¼2W…Jð©^ëÂx3¯uàŠ¼W*ð%^ëÀ=w÷®þõßš»óW}jï­]òë¾]wº®÷U÷޾ñ×Ûzûo_bëì]}_¯«õôξ™×Ðzú_6ëæÝ|¯¯•õò¾A×Åzø¯_ ëá]{ÿ¯õïN½é׺z÷O^ÞëÛÝ{U¯jµì¦½”×°:ö^µëÖ½z«¯Uuêj½MW¨Jõ ^›«Óuz^¯KÕé"½$W£zôo^ˆkÑ z ¯Auçî¼ýמZóË^u+Î¥y¾¯7Õæº¼×W™Šó1^b+ÌEyw¯.õ冼°×•ò£^NëÉÝy*¯%Uäj¼Wšò^<ëÇxã¯uãJ¼iWŒ*ñ…^,kÅx¡¯5âB¼HWˆ ñ^ëÃ}x_¯ õá:¼'Wƒêð}^ kÁmx¯µà2¼WÊïù]ük¿wá®ü5ßB»èW| ï]ík½­w¤®ô•êî½]תZõK^£ëÔ}zy¯O5馽4×¥:ô§^«Òz,¯E•èZ½ W jô ^|+Ï…y߯;õç&¼ä×›šós^hëÍy’¯2Uåö¼¾×–ÊòÙ^W+ÊåyF¯(ÕäÖ¼š×’ZòK^CëÈ}xþ¯Õ㺼wWÚñ»^3kÆmx¼¯•âš¼SW‰jñ-^!kÄ-xu¯µá’¼2W…Jð©^ëÂx3¯uࢼWzð/^ëÀ=w÷®þõßš»óW}ºï·]òë¾]wº®÷UÞ¾»××yúï?_~kïÍ}ͯ¹µö†¾Ð××Zúë_RkêM}¯£µóƾx×ÌZù‹_&käÍ|m¯µñ¾ ×ÁZø+^úkßM{ïxuî^½Ë×¶ºö×^ÏëÙý{¯c5ë¶½v×­zõ¯^±«Ö5z°¯Vêj½MW¨ªõ^+Ó¥z^¯KÕé6½&×£zôo^ˆkÑ z¯B5çî¼ýמªóÕ^u+Î¥yį8•溼×W™Úó;^b+ÌEyw¯.õ冼°×•ò£^P+Êy*¯%Uäj¼Wšò^>kÇÍxè¯ãJ¼iWŒ*ñ…^,kÅx¡¯5âB¼HWˆ ñ^ëÃ}x_¯ õá:¼'Wƒêð}^ kÁmx¯µàJ¼ W€ð]ük¿wá®ü5ßB»èW|Zï‹]ík½­w¤®ô•Þf»Ì×°êö^¾+×Åzâ¯\UëF½h׫ºõw^©kÕ-z•¯Rµéþ½?צšôÓ^–+ÒÅzB¯HUè²½W¡Êô9^«Ð5yõ¯>µç~¼ïלúóŸ^nkÍÍy¨¯5æJ¼ÉW˜*ó^[+Ëey\¯+•å.¼¥×“Zòk^IkÉ-y¯"•ãú¼WŽêñÝ^7kÆíxͯµâò¼^WŠÊñY^&ëÄÝx‹¯uáê¼=W†ªðÕ^kÂÍxI¯ 5àâ¼W‚ŠðQ^ëÀ½x¯õßò»þW~ÊïÙ]öë¾ÝwË®ùuß»àWzúï_]çë¼ýw”®ò•÷¢¾ôWÛÊûy_d+ì…}d¯¬•ô⾜WÐÊú_8+ç|´¯–•ò&¾D×ÅÚø»_ ká|¯€µïf½ì׺Ú÷[^àkÜ {Z¯kUí½¢W²êö]^ÆkØÍ{¯`uëʽyW­Êõ¹^±«Ö5z°¯VꂽPW¨ªõ^+Ó¥zc¯Lué6½&×£zôo^‰ëÑ=z¯B5è½WžªóÕ^u+Î¥yį8•æÎ¼Ù×™Úó;^ckÌmyw¯.õåž¼³×•jò­^P+Êy0¯&ä~¼×šò^>kÇÍxè¯ãb¼lWŒzñ^,kÅx¡¯5âB¼HWˆ ñ^ëÃ}x_¯ õá:¼'Wƒêð}^ ëÁx"¯UàJ¼ W€ð]ük¿wá®ü5ßZ»ëW|Zï‹]ík½­wª®õUÞf»Ì×xêï]ß«»õ{e¯lµí>½§×³šös^ÈëÙ{¯buëö½~×®zõÏ^´kÖz»¯WuꮽUשZõ+^ŸëÓýzn¯MÕéb½,W¤*ô…^Œ«Ñ•z¯C•è.½ןZóë^ykÏ-yϯ9õæú¼ßWšŠóQ^f+ÌÅy‚¯0Uåʼ¹W•Êò¹^RëÊ]y;¯'u䪼•W‘Jò)^A+È%xó¯u㎼q׌Úñ›^/+Ååx¬¯•ân¼M׈ºñ^«ÃÕxj¯ Uáf¼,ׄšð“^+ÁÅx(¯àv¼×€Êð]ÿ+¿åwì®ý•ßn»í×} ï¡]ð+¾w¯®õõÞ’»ÒWyJï)]àë¼wx®ï÷J¾éWÚjûM_^«ëÕ}N¯©Õôо‘WÏjùí_2«æU|Ÿ¯“õñξ9×Äzø_ëàÝ{ï¯}õï"½äW¹Ê÷9^á«Ü5{p¯níj½­W´Jö‰^Ë«Ùu{¯cÕì"½„W¯*õå^·+Öåz̯Y•êÚ½[Wª õA^¢«ÔUzy¯O5鎽1פÚô›^kÑíz'¯DõèZ½ W  ô^|+Ï…yÚ¯;Uç¼âW›:óg^hëÍy¯1µåö¼¾×–zòÏ^U«ÊµyF¯(Õ优W‘úò?^CëÈ}xþ¯Õ㢼tWŠñ±^1ëÆ=x·¯õâš¼SW‰jñ-^!kÄ-xu¯µá’¼2W…Jð©^ëÂx3¯uàŠ¼W*ð%^ëÀ=w÷®þõßš»óW}jï­]ñk¾-wº®÷UÞª»ÕWyúï?]㫼uw~®ïÕÝλ¹×»Ú÷{^éëÝ={‘¯r5íî½½×¶ZöË^ÓëÚ}{?¯gõ즽”×±:ö'^¿k×ízç¯\õë^½k׬õƒ^ªëÕ]z•¯Rµê½BW¦êôÝ^–+ÒÅzH¯IèʽW¢ôC^‚ëÐ]yû¯?uç–¼ò×Jó©^o«Íõy®¯5Õæb¼ÌW˜Šó^\«Ë•ya¯,5åF¼¨×“ºòw^J«ÉUy¯#Uä&¼„×:ñç^8ëÇxÒ¯Uã ¼aW‹ñc^(kÅ x¯â¼@W†úðß^ëÂýxN¯ Õàú¼W‚Úð[^kÀíx ¯•ßò»þWïã]øk¿ wЮúß»àW{Zïk]é+½%w™®ó5Þ&»Ä×wŠîñ]Ûk»mw]®ëµ÷¢¾ôWÛÊûy_d+ì…}d¯¬•ô⾜WÐÊú_8+ç|´¯–•ò&¾Dׯ*øÅ_ «áµ| ¯Uïz½ïW¼Š÷‘^ì«Ý•{œ¯s•î½ÃW· öá^Ø+Û{J¯iUìÒ½šW±êö=^Â+ØEzò¯^U늽qW¬Êõ™^­«Õµz¥¯Tµê>½G×§šôó^škÓMzS¯Juèö½×¢ÊôY^…«Ðµz¯@Õç¼øWªóµ^rkÎMy¹¯75掼Ñ×™*ó%^_kËíyl¯-•år¼®W”jò^MkÉ­y%¯$µä>¼‡×êñý^;«Çuxݯµã6¼f׋Êñy^)«Å5x–¯Õâ¼BׇZðë^+Ã%xT¯ •á¼!׃:ðg^ +ÁEx¯õà¼×zïï]ù«¿5wÛ®ûuß.»å×{ªïu]ëë½}wŸ®óõÞ:»ÇWx:ï]Üë»wh®íÝ^»«×Åzø¯_kâ |+¯…uðV¾ ׿ú÷ÿ^úkßM{Ó¯zuîö½Þ׺z÷O^äkÜ{¯p5í®½µ×µZö«^ÏëÙý{)¯e5ìN½‰×°:ö^»k×mzׯZõë½`׫ õa^¦«ÔÕz„¯P•éÒ½:W¥êô½^’+ÒEz7¯Fõ膽סô#^~ëÏÝyê¯=UçR¼êWœJó‰^k«Íuy¯3µæ"¼ÄW—zòï^YëË=yQ¯*5å¼ W“ òa^F«ÈÕy ¯!5ãæ¼|׎ŠñÑ^4«Æ•x¯UâÆ¼X׊ñC^$+Ä…x€¯á¾¼7×…úð¿^«Âux>¯Õà¶¼×Úð;^«À•x¯5߯»ø×~ïÃ]õ«¾µwÅ®øµÞÖ»Ú×zªïU]æk¼Íw‰®ñ5Ýú»¿WvÚîÛ]Ø«»wR®êUÝ»£×ÝŠû±_k+íe}€¯°õR¾ªWÒŠúQ_?+çå|Яšò’¾RWÈêù_+ãÅ|b¯ŒUñ2¾&WÃjøm_+á| ¯UïÒ½úW½ê÷½^ò+ÞE{¸¯wÑW¸Ê÷^ݫ۵{`¯lí*½¥W³Jöi^ÈëÙ{¯aÕëâ½|W®*õÅ^´kÖz»¯Wuê–½Rרúõ^ŸëÓýzi¯M5éN½)פ*ô…^‹+Ñez¯C•è½WŸZóë^wëÎýyϯ9õææ¼ÜךŠóQ^d«Ì•y‚¯0Uå²¼¶W•Êò¹^RëÊ]y5¯&µä–¼’בJò)^A+È%xó¯uãv¼n׌Úñ›^/+Ååx¬¯•ân¼M׈ºñ^«ÃÕxj¯ Uáf¼,×ÌZù‹_+ëå}|™¯“5ò¾A×ÆÚøÛ_ëâ½|A¯ˆ5ð®¾×ÁZø+^ÿëßý{é¯}5ïf½ì×¼:÷‡^ëkÝm{—¯rõî½À×¶ºö×^ÕkÚ­{D¯h•캽—W±šö3^ÀëØzí¯]µër½nW¬jõ^¬+Õ…z ¯Tê*½EW§Jôé^˜ëÓzM¯IµèÞ½×¢zôO^„kÐz¯@窼õWªóµ^q+Î%y³¯6uæv¼ÎטÚó^]ë˽yg¯,õåZ¼«W” ò^L+É…y¯#õä>¼‡×šñó^:+ÇE}Õ÷ ¾áWÙjû-_Z«ëU}>¯§ÕôJ¾‰WÏÊùù_9«ç5|Яšòê¾]WÊJùI_#«äu|x¯ñо1WÄÊø™_+áå|&¯„ÕðB¾W¿ª÷õ^ù+ß%{ίyÕîâ½ÜWº*÷E^ã+Üe{v¯nÕí–½²×´úöŸ^ÎkÙÍ{#¯duì:½‡W¯Úõû^¹ë×=zѯZ5êî½]תºõW^¥kÔ­z¯Oõ麽7W¥Šô±^«Òz2¯FUèr½W ºô^}kÏ­yå¯<µç>¼ç×›êó}^j+ÍEy˜¯3æ ¼ÁW—*òå^XkË yK¯)uäî¼×ÑÚú;_Aëè=|ñ¯ž5ón¾m×̪ù•_-+å¥|Ÿ¯“õò&¾D×Ç:øç_kâí|G¯ˆõðƾ×Áºø7_kà-{ï¯}õïf½ì×¼:÷‡^ëkÝm{—¯rõî½ÃW· öá^Ö«ÚÕ{D¯h•캽—W±êö=^Â+ØEzò¯^Uër½nW¬jõ^­«Õµz ¯Tê*½EW§šôó^˜ëÓzS¯Juèö½×¢zôO^…«Ðµz¯@ç¼øWªóµ^rkÎMy³¯6u掼ÑטÚó^_kËí}ݯ»µöƾØ×ØZû _VkêÍ}-¯¥µô^¾‹×Ðú_:ëç]|Õ¯šµòþ¾_×ʪùU_%+ä¥|~¯Õñ¢¾4WÅ*ø¥_+áå|&¯„ÕðB¾W¿ª÷õ^ù+ß%{ίyÕîö½Þ׺z÷O^äkÜ{{¯ouí–½²×´úöŸ^ÎkÙÍ{)¯e5ìN½‰×¯Úõû^¹ë×=zׯZõë½`תºõW^¦«ÔÕz„¯P•麽7W¥êô½^’+ÒEz2¯FU膽× ºô^~ëÏÝyå¯<µçR¼êW›êó}^k«Íu}8¯§ôо‘WÐÊú_=«çµ|௜ó*¾eWËJùi_'ëäý|‰¯‘5ñξ9×ÅÚø»_ëâ=|1¯†5ðn¾ ×ÀZø ^ý+ߥ{Þ¯{Õï"½äW»*÷e^ç+Üå{†¯pÕí½¸Wµªöµ^Ò«ÚU{4¯f•ìz½W°Šö^¾+×Åzâ¯\Uë2½fW«jõm^©kÕ-z¯Qõéæ½<צšôÓ^”ëÒz=¯Gµè²½W¡jô-^«Ð5yð¯>ç~¼ïלšó“_[ëë}}Y¯«5õ¾¡×ÒÚú[_Eëè½}¯ 5ó®¾u×ÍZù«_/ëåý|ª¯•UòR¾JWÇêøý_+ãE|R¯ŠUñ¾ ׺øW_kà­{ÿ¯õ尿ô×½:÷§^ïkÝí{§¯tõîF½È×·ºö÷^ÚëÛ]{U¯jµìþ½Ÿ×²šöS^ÄëØ{¯`uë¶½v×­zõ¯^±«Ö5z°¯Vêj½MW¨Jõ ^+Ó¥z^¯KÕé6½&×£zôo^ˆkÑ z¯B5çî¼ý×Ôêú_N+éÅ}"¯¤Uô2¾†WÏjùí_8+ç|ʯ™UòÒ¾ZWÊJùI_#«äu|x¯ñо1WÄÊø™_ «áµ| ¯„ð*¾W¿J÷é^÷«Þõ{ȯyîʽÙWº*÷E^ã+Üe{v¯nÕ킽°W´ªö•^Í+Ù¥{#¯duì:½‡W¯Šõñ^¸«×zѯZ5êî½]תZõK^¥kÔ­z¯Oõ馽4×¥Šô±^«Òz,¯E•èr½WÒÚú[_Gkèí}¯ õóƾx×ͺù·_1kæ-|¯¯•õòf¾L×È:ù_kãm|W¯Šõñ¾ ׺øW_kà­{ÿ¯õ尿ô×½:÷§^ðëÞ{­¯uµî^½Ë׸÷^ÚëÛ]{U¯jµí½¢W²êö]^ÆkØÍ{¯`uëʽyW­Êõ¹^±«Ö5z°¯VꂽPW¨ªõ^+Ó¥zc¯Lué6½&×£Úô{^‰ëÑ=}¯¢Uóò¾~WÎjùÍ_4+æ…|º¯—Uò’¾RWÈêù_+ãÅ|b¯ŒUñ2¾&WÃjøm_+á| ¯Uïê½ýW¾J÷É^ó«Þu{¸¯wÑW¸Ê÷^ݫ۵{e¯lµí>½§×³šös^ÈëÙ{¯aÕëö½~×®zõÏ^´kÖzÁ¯X5ꮽUשZõ+^¡+Ô%zn¯MÕéb½,W¤Šô‘_½§×³úö^ÊkÙM{¯buëö½~×®ÚõÛ^µëÖ½zÁ¯X5ê½XW©ªõ5^¡+Ô%zt¯N•ó*¾eWËJùi_'ëäý|‰¯‘5ñξ9×ÅÚø»_ëâ=|1¯†5ðn¾ ×ÀZø ^ûëß}{Þ¯{Õï"½äW»*÷e^ç+Üå{†¯pÕí½¸W¶ öÁ^Ò«ÚU{4¯f•ìz½W°êö^¾+×Åzâ¯\Uë2½fW«ºõw^©kÕ-z¯Qõéþ½?×ʪùU_%+ä¥|~¯Õñ¢¾4WÅ*ø¥_+áå|&¯„ÕðV¾ ׿ú÷ÿ^úkßM{Ó¯zuîö½Þ׺z÷O^äkÜ{¯p5í®½µ×µZö«^ÏëÙý{)¯e5ìf½Œ×°:ö^»k×mzܯ[•ë½cW« õa^¨+Õ|Ÿ¯“õò&¾D×Ç:øç_«ã|L¯‰•ðÚ¾W øA_«àU{ô¯~•ïz½ïW¼Š÷‘^ì«Ý•{¢¯tUî2½ÆW·jöí^Ø+Û{J¯iUìæ½œ×²:öG^ëØuzø¯_ëž½s×­õ£^®ëÕÝz«¯Uuòf¾L×È:ù_kãm|W¯Šõñ¾ ׺øW_kà­{ÿ¯õ尿ô×½š÷³^ðëÞ{­¯uµî^½Ë׸÷^ÚëÛ]{Z¯kUí½¢W²êö]^ÆkØÍ{¯aëʽyW­Êõ¹^³+Öez¶¯VÕñâ¾½§×³šös^ÈëÙ{¯buëö½~×Äøƒ_ ëá]|¯‚µïþ½ÿ×¾ú÷ß^ökÞÍ{ïxuî¶½Ö×¹z÷/^á«Ü5{p¯níj½­W´Jö‰^Í+Ù¥{¯cÕì"½„WÃjøm_ «á5|¯‚ïê½ýW¾J÷É^ó«Þu{¸¯wîž½Ó×¹÷#^ÞëÛÝ{e¯lµíV½ª×³úö^ÊkÙM{¯buðƾ×Áºø7_kà-{ï¯}õïf½ì×¼Š÷‘^ì«Ý•{œ¯s•î½ÃW·jöí^Ø+Û{J¯iUìÒ½šW²:öG_ «á5|¯‚ïþ½ÿ×¾š÷Ó^ôëÞ{½¯wµîž½Ó×¹z÷/^àkÜ {k¯muíV½ª×´Jö‰^Ë«Ùu| ¯„ð*¾W¿J÷é^÷«Þõ{ȯyîâ½ÜWº*÷E^ã+Üe{v¯nÕí–½²×´úöŸ^ÎkÙÍ{ÿ¯õ尿ô×½š÷³^ðëÞ{­¯uµî^½Ë׸j÷ ^Ü+Û…{Z¯kUí½¢W³Jöi^þ«ßÕ{ä¯|•ï:½çW»Š÷q^éëÝ={‘¯r5íî½½×¶ZöË^ÕkÚ­{?¯gõïf½ì×¼Š÷‘^ì«Ý•{œ¯s•î½ÃW·jöí^Ø+Û{J¯iUîö½Þ׺z÷O^åëܽ{¯p5í®½µ×µZö«^ì«Ý•{¢¯tUî2½ÆW·jöí^ÙkÛ-{O¯iõî^½Ë׸j÷ ^Ü+Û…{Z¯kUîF½È×·ºö÷^ÙkÛ-{†¯pÕíÚ½»W¶ºöÖ4,«Þ *÷‚Ù˜¶cæ+ÛºŠöî¢é–Pºe”,ÈÖ 25‚¯ëh«úÚ-Æ q‚B¾xx¯ž*ˆŠ„""íœH»g,àp‹8"¯ @«Ã)0 DLs縜ùî0 ÂÞÊ·²¤+äOŠùâžž§§¤'ÿš ÿæ‚b3à˜Œø1C¿ŒPïâñ*¼J„,îc ;˜Â­pH«\(͘Š3f"mˆð›b<$öÕ‰=µc* ÊŸÈ0T~Œ¢àÞ°¸7¬+×oŠõÛâ›À¦Ä0'›Ó æôÂYÀð–p<#²Óì´Â臀z ̆ó›o &ÛÈ1¶Œ m£ô·à½-ø,ñ› ˜'O¦ Wu‚UÝ`Ž#…Äu‚E €Ð( 4 «`êØ tÏp3Ü7¹îCãGChÑÐÚ1>b O˜‚á½à¸ox+d ÅÙƒ p È%v‚ ] ‚.£‹¨Æ ƒnˆ Û¡äh0y 4‚ ¤Ö°i5¬‹á†"øanÊÈ[²²oÍ[óAAxPCÞÌõ³=AG¨òƒª< ÜáH78R †A¡€Ä½x1/^ œöç=€¯Àx+ð eÓ™tÀ”p'e UPUT¸#cndü‚? Šâ€‘¡äh@rÿð¿üÔ͵3@gÞ`÷˜6º £nƒ1hÌDZ/­ ëHÆ„h±¡)E Qp‚dó°™<ì#…ÃápÂÚXƒ¶–†â‡¡¸¡Å\HqWLX†“!†Ýpa·\»R…®Ô¡R²0T¬Œ»#îÈÁ&µ0I­L9„NcaÓÐ@tô¬ƒÆ« ⶸ8­® MUƒSU`ÈÈ2ò ÆÎñ³€±\p,W sÆœñ€êH'z’ S*‚TÊ Œâ#8„SÒô€~X„t©Ý*@pëÈ:ò°R¬ ec8XÎ_€ÃŽyØãžv5*š J¦ƒ…ÅáF-ä‹y⧉¨©âj':1 ÎŒBCl˜Û&!eψYsáìýx{?^nq‡œa¤^ˆi¢I\†W!gÀXYð×s…5Üá5ºhMnšüÑ4A 8CŽž£ƒç¨àé&°:I¬ žßƒg·àËê@2ú ð¥‚ü)`³NH,Ó’ ƒÝ‚ ÷`ž.ð'‹¼ L»S.ÀŒ#>]—@|c0ÌRTÔ•n’ø¤¾…h¡Z b’@¤ÐtÀWÆ€ñ ;”åCxâXÞ8–3‚Ð à´ùz°¾^¬+Ø‚Šö ¢„«@¡*Ð$öÕ‰=µbh臚:¹‡ÅîaÈqèrz2˜†Œ¦!nø`[¾0ë…Œ:áG˜Qä&èÚº6ÐFt5ê z€ðíð<;| ü6ƒ  Ðh`4 #ƒÄ µ@-P –¥†Àžbp'˜œ E8QNŠð@"¼%° lz=àx-ÙËv`kÓ(ôÊV3•Œà_7ðÍü˜Mf@T8°,ò<€@JL“B껺®ãêyÈúžr:J½’¯C^™8צN1•IŒeRbÖ¾ µ¯¨){g ^ÙÂ]‚@—`"t-ˆ aö}‡¦‡'i¡¡ÁhpDÚÚ…ö¶¡](WJ=A&+àIŠøï†;á€ù¶¸>m®j¸ƒš® Õ^¨5Wª [ïƒûà·v-ݤ ¦2‚©Œ ž„À'¡0 9kNZÀ‰˜ð"f<’‚¤ wåùDÊÀ2€h½ˆ/bV‡U [˜øæ>ZV„ Oÿ8ÿήi+š`EðH|„jâñ¸¼B+Šð£×³@õìÐ8´Ž-c>òÈϼ²/M£‹Óhâ®”«¥&ÔD‰µ"1bèŒXº²8ìŽʆr¡„ö„}¡xƒà^ øg7…YÍá7½`MïXÉQ„rTa` Aêeº™@Û 6À( š)ƒ&Š`¹­.kD ¸p®žs˜'œæ +x‚JÞ ‡ëÈ!úòßó÷üÀuð@¼Ê²„ dü8?Þ w‚`W,ËDG€KM˜Ófa*J€AXGŘ€ñf 7ì û$>xžyŽÞc€1"} HŸB°¨@¬*%ˆ¿ b/ ‘8‚äN‹i"ÚAÙ°cölóÄ|ñ5dMY$ à„H8 óz@<Þ Úcö€ÂÐ0ƒ´ Ýý·@œ+ð' üÍ‚3C ~Í0³L)ÊcÀg™°ælÙ¿voÀT°Ø,6ÊN2“ E3xLÞé€ú@ 8Sä-?ËOÀ-™ f@ŒP€£ $Fè º×€5À:ø¾ˆ­b+@0€L vG€ §ðiüZœÑ§4EarÑX\´P²`,˜§M¡)ÓhClÛ#c¾|ÈïŸ24Rô ½Òì(´» &«‰ªß≄b@\4‡ !…þHa’P…D!$ÔˆI5"ùÕƒþu`ßåÀ7ùp SZÖ€®$x+‰ ¢‚h£àˆþp"?œ›IæÒ@l¡((Jô‚=V(‚ ͆3a DTPÍóFÀ5é˜ zfûå¾ù@)ú ~‚M€“@à p\»nÆ€‰PâT<€€O :8ŽÐ$4 ÛpvÜp§€)àJ¨ª0H )S JTÃàÄ x%È¥r)`WsÝÜ÷EÍéE3zIèM’zdWiÚ8 Î(3ƒ/HÀ Ò(&: Ž‚NȆ²þÝÿ·Ax•^%BêXú–N8CÓŽŠè¢ºÈ¡2(F ØŸ‚¶'à–Ψ%³ª7í û@rÌp³Cöý€WÆ€ñ ÊN2“ Bý¿@¢1èŒ@2H £Ò¹`®X%H` RüŒ#Уt^Õ€Wµ`‘ˆdbÞ€7…  1HŒRmo€[à°(ì à8(–4J%a±D˜lQ#ŸìHçû(ÔDŠ5!ilHZ[½ŠDob‘×$FµÈ^æ`—¹˜%“EAdÑPQöb”}˜¤:ù#ξ>𼦝#?7xÏÍÞ*{ ‡žÂ6è‡Íºi»†Únáe"àYH¸Z(„–Š ô7= Æ Å1E«ÿ(*ÿÊ sGÀ{¦`阑5¤M`Ys¨\êÄñ1<`@“$Àjf€Ú™ -Ì€ s ^.—‹€ÇFŒøc>r0܌櫀9ªà ì˜{&`€#à6€¡˜hĦ1&_¼I—ï9þÄŽ± ñDH~à ¹nFGâ€ø eûÄ™~ñ"ÀÄH°1ögD}™ÑaüGœGÄgñ'EÉÁPDTÑÜdD·©DCªQ ìCÂ{ÕÌD5s ¯œBëç¢LÄ(“1¼LB/÷DýÑ*@ŠÄÇf°AY¬Vfr…Ü…ìt9{S¼7ï Ä>Ù°æ2õŒ…=b0r˜Œ¦°[†,áQðGT| ==ƒOO`Ÿþh'ÿš¢Ëè²à]#ÈHòqBP€5`@ Xs£€œèà9xÎ^ÎE3€ cHXÒ"U€•q&¤dI©ÎĆs±Ú$G6‰ŽUDc•QyÜE^w%0ÄIL1¤lCéÐnÄ4± ÐtB´ŽüÄ#¿1$AÃÉW2DÌ‘ù|@þ_+5D ÍQ9´@Nmÿ8ÃÿÎ0þàÜ?¸7ÛÐÃöô0ü»Œ?.ã¼³Ãï,ðûAä>ÐyT¡ÃÕ(pôôL==pÃ0À†Áá°A8d®ë…S€ATà<|î;‚<ÚP6”_µWí@×Px5ÔØÛ‚66à^G˜‘æÞE€÷‘`'¸ Á®[€Vç`úÈþ³»DŸîÑ™ôGæ}Œ0Dc ÜdD·ÞaD7˜Q tB€gJDÒ‘T@àUPDÔþ­d?«Y˸Ãòî0û”>Çå˜9CæPø=l>[q™ÃÜfpõõÄ=}qQiÃÔZpô7t= Ý7ªCÍêòÎô<³½ ÃÈðñUT45 h-ÃZ pÍ0Ä3L1 Ù|C6_ÎC:3Ψ¹¤*.iZ_Å–ªy°*žl ]9‹WNbñØåxðÙOƒöSá!HÀR0 ALSv9ÀÝŽpI±ül…Áa@°db…è"Áº°w(œÊ'íëAûzÐ…‚,!`‹¶ŒÂ-£0l$ [ TCBUЙŽÔ&cµ ÖÂu…0 ÌÌ(33 (±ÂŠ,p¤Ä)1 Z –‚ð§ <)ÂO †¡‚0©É *rC ¯à«ø0¬3+ Á Ö€Bµ ®+§A ùç¾yð°ÂT,0• OÂÇSð²´$,­ 4/Ï üxÂÿ0†-Ô!‹uŽÚB#¶‹yô"Þ}Ý,B7K<¼$/ '3ÂIÌð”Sä%ù g“BYäИH¬&+ ¡‚Âh`°›ç¤&ùé Ø:BvŸ¤'Çé ¹Â‚îp¢$(‰ :îÂŽ»°¥<)A c³Â˜ìð§’Œ)ä£ ‹Â£bð©üŒ*# ²¬p¬U\+W ÔZµ°®XT+– ö°B½¬°[T,Õ §ÂÄéð²*Ì,г .zBËž³µ¤,íi G'BÑÉе/L-KÓ ^ÁÂ×°p¶—Ä-¥ñ t7BÝ Ð·Ýì-÷{ ˆ™Ââ&p¹ì.D» šÖÂæµ°ºGì.‘ú£cq(ØÜJÍ( ³JºQ0.”L ±Ôã,u8Û h¶Ã.rƒœ† ïq»ÜCïR1ÃÔŒpù7¾MÁ¯Âdzð±ìþqû¿œ~ð}ßA¢rÀhœ°,í´ ;mÉé@òzPJô†=ZÁV†0_Ž$㉃HÁ Ò0oÙ„öa9¬ÁÎk0vãô¸ýŸ™AçæP}<CÏþAÿ€Ð‚  ¨S×Âõð‡§|!éß Â(0Œ6Ä# ±åÁÂ9ppl$ [ %BIC”B´%­ b5ÂXp—Є%ô! ˜íBf;P›*Ô&ʵ ÊGÂr‘ðž/L'‹Ó û¡Â~èp¡D(@ &ŒB‰££®ì(ë» N>“°¦*)Š… tÞB7¨q¼*o ˜FB¦ª¨4*ª ¸u®p¬w´+í Õmµ[p®6+ ñR¼T°¯Ñü+ô ÿÂÂð±\Ô,W5 !šBÈf²ÅL,±S 7BÍÃд-Ì- s L„ÂÓ!0µsô-\ýðLÁ|0bùœ¾goùA›þPj¯´«íãØA¸öqdTYIÄÁÒq0w±ôì}¨.Áê °}SäTùþAÿ€Ð‚J4 ’MhBZ†ûÔ!¾õ“$Â$É0‹Wœ"ÕçÔ–Â5%°;D#ÎÑ ÑBD´P’ëd$ºÙ K®BRë–VÜ%•· ~Â_†°™}¬&_k ®bÂk˜°œN¤'© Ü„Âw!0Ÿ¤'Çé IÂRp¡«ô(jý .‹ƒ°¤Ä)1 R‰B”¢P¦ì)†; q¦Bœi¨ ¼*¯ ŽžB£§©ëd*zÙ «•ªåp«˜„*æ! ÆhB±š­4„+M! à(B¸ ®®,+«‹ ÷½ð°°¬,ª¥Ý„)wa Ö"µ„È·Wp­ÕÜ,6¦³ ©¬ÐN›´¦íÀwkpÚåk˹ZòîÈ»+².ÊñàhÁ™O°iÐŒt#ÏuÁ³Ýpp/T Õ3<ÁÌÏ0vÌ3\AãW{bØ…ÜÀÁ÷00€X\ 'ÛB öЄ´,!- nªBªˆÜ|"7­÷B+}ÐŒÀ#0çæÂ9ù°_$Å ÃÂH0ð“— $åà S0ÂTÌ0–¬´%«- ‚eÂ`™p™ &h ¯uBkÝPœN¤'© ÙLÂvS0ž¸¤'®) ýÇBqСD(@  ‡Âp£D(ÀÑ @LB¥<)A ^V—•°¦Õ¼)µo {NBžÓ¨‚ä* ¹ •B¥Cªä*‡¹ ®Í«³p(£ (Åö@½£P5qÌ \s´sÀíð@“\$×\åA9PJ¢L¨“óA<ÆPSžŒç£y5Á^Mp[ª|êŸõªA}jbƱ‡e>Á™O°iXdVÇóA±üÐor„Ü¡$7ÁÉ ðtò$<‰wçAÝùÐyù”~eÇKÁñÒð~š ¦ƒB†Ðƒ ÁÁOBãP‡ ü!Ã?ŽÚB#¶Š«ô"ªýÇ·B1íÐŽ(œ#Š'üIB?P‘`”$X% -£ÂKhð”e %C ]ëBWzЗ6%Í ‡ÂÂað°™ÂT&p• ®bÂk˜°œ ü' ÑÊBtrž@|' ôB}РTœ(' aÂ…Xp¢5D(Q 3lBŒÛ¤Ä)1 OQB“ÔP¥Ã)pÄ­ðö«|=«[ÈjÖò¿/ÁÇ ”SË%òÓƒ]4à×MËÿûrÿþäw-¹Ënª8“ªŽ$»âo/6ÄËͱ2ö‡N½¡Ó¯õCä=Pû&²¾É¬¯ÎƒÃó ðþ`Ñ?˜4Oú´Cþ­8ã@8Ð 4œ ';sÀNÜðKÒÁ! ÀˆC0(´< -ìåÀ»9p4pL ¢6Àè°?*äʹAA@HŽ$#‰ΟA3§ÐQE¼QoQƒAT`ÐXê¬:«LJÁqáð_ãôøý3äAŒùfe™E–˜Á¥¦0lK´íð¸A¼.qº$n‰CTÁÐÕ0v°t¬”Aä%{?¼Ïï×cÁõØð›ŒæãèBzƒ¡„ èaX"°‡@|!ÐÿÂ$?ðŠ«ô"ªýÅ‘Â1dpõ#}GöìB=»ù”$>e &!BIˆP“¨<$ê OøÂSþ0–#\%ˆ× v˜B]¦˜|,& ›ÂfİšÃÔ&°õ ½gÂoYðœé$':I Ý—ÂweðžÚô'¶½ û¡Â~èp ªt(* ™Â†&oâKËø’òþÅ«?±jÏõ½óýo|ÿçY?ùÖP£Ô¨õèÖÀ:5°—äeùÉÀrD°"ÞÄ·±‘³À¤lð.¬ «@”ÀÐ%09UlU[àpÀø0B¸´®-p5A PK_×ÇöQA=”PS<ÅOmhÁ[Z0Z0ÔŒ5زAv,`ÔL5;fÁŽÙ°f̳—«A¥êÐl:„¡î’Á»¤°qAüP<äÁÏ90v'‰Ç„ÆÁá1°z‚ì »È^Aò~ˆä¢9½Â/p‚[\ –×B­B«P…úT!~•yeBYP‰Ct"Pݯ Â+ÂpŒY#GÝ,B7Kì#Æ; ÂB°‘¶d$m™ /ÈÂKò0” d% UUÂUUp–y4%žM yÐB^t˜¯¬&+ë šBf€šÃÔ&°õ º/Ân‹ðœ¤|')´ÎX­3–+Äþñ?†Ä¬H±+,Ö ›5‚¦ÕÉðµr|-Öëuƒºãçi¸ùÚn•]ë¥WzíÕ»uD¯ScÆØôŒç½#9ïqq+Ü\Jù9+¾NJï°¾ì/€ûÔ>õ OÉ{òc^ýF¦?Q©ÞàK÷¸þŒÎ?£3ñ·ûümþÿ¤Ô?é5%´‰m¼À'o0Ãì0ûx›@^&Ðù¤~i<ñÀÁ‹é°búlLS#ÓÐj€´ -xP«^*Ý÷"·}È®9Ö#Žuˆèo3:ÌÎȇ«²!êðæ<y/ÛËÇöõoH½[Ò/hÛëÚ6ú÷“‡=äáψq+âJùh`¾Z/£ïCèûÐû]¾Áo»Îkîóšüi¢¿h¯ÐºCô.ý£ý¿hÿoã*óøÊ¼þ¼?¯Ïó©Óüêtÿ²Æ¿ì±°H ’Ý@$7PŽìã»Xk@VÐ\,×  ª@ƒ*&'ì ‰û±ã@¬xÐ/¾¬ ï«GÀÑÁ08©Ä*qÎ3ÀóŒð@ÆÜ1·J¨AªHZ¤©¼aÁ/pO1”Ìe'«ÁIêðUddŠ`Ab˜[vüÝ¿å’Ayd`öœ=§9AÁŽPpf<ƒÏ†€Á¡ 0jã4¸ÍÑ›A´fÐo-ÔËuEAÅ‘PsgLÙÓV¤ÁÕ©0w\×’¹Áä®pzû¾ÅÈ^Aò~3 ŒÃüðÁÿ<0H´ R-,% p„*Ô! µY5BMP†ê¤!º©„Â!ð‰vô"]½«ÑÂ*ôp‹àô"ø=Ñ_B4WÐŽ9Ä#ŽqõÙB=vNÀŒã°#8íÁ»pp®öBû½¾ðÜļ71/$1ËÉ róŠW<â•ÏKŽKÒã’õÓ=tÄmž³Ûg¬÷Ή=ó¢OŠý{â¿^ù‡}¾aßo¤¬é+û º¾Ân¯»VCîÕü]Õ?uOÏtóÝý‹P?bÔáJKøR’þ–v?¥ñƒüG`ÿ„¤?á)ÿeãÿÙy^p@œ 4œ ' ¡@H(P¬ëÒº@t®"DD‘s©@œêP+Û öø@Áî4´ô -=ŒÁÀã0p<ã<8Ï HÁ’0DT¬+zïÁ»ðK+œÊççLA9ÓQy<^OJÁR€0W_Ü×÷£ AhÃP\ð¤<)øâA~8b+œŠçHFÁ’°fÝ<·O“`Á¤Ø0kJ4ÒÖøAµ¾o”Ôå5jAÆs‰¤âiTAÕÐwDÁ‘ŒIÁãpz`”˜%¿ÉAïòP}‡daÙñ#AüHЀz´ ­3BŒÐƒ:„ ΡJ0BŒ…×ü!uÿrõB½PˆdL"™”Â&e0н"¯G¼üB/? –0"匿X¦/Ö)Œ[ªcê˜ÌF~³Ÿ­,#ëKúØãß¶8÷íç(KyÊão?¸ÛÏî[)³–ÊlçË 9òÜ4Û§ 6ë”ï:å;ÎÓíë´ûzîÕ~;µ_WÛÁöñ¤SÆ0»½CîïPüM½¿ooÍúsó~œýfÖ?YµÞ4£÷(þ_¾¿—ï¯í[ûA–ÿ?ú?Ïþúo›þ›ç æ@9£Ô¨õÆÀ1 pÖ”u¥pÀ\pÔõ —@‚åÐ%Yì V{šHÀ¦’0-ï4 {ÍRÀÇÔ°5Ǥ q陡Àæhp=¼E¯ [AÖÐCþÜÿ·rZA–JLt“ÕA5Cˈé:â:N½Àó¯p<ì5 ³ B¬É½2DoLî·»;­îÔž›5'¦Íu«]_êÚ06€ Èar@ÞóC·¼Ðî¿Ã„ïðãaM8ØSNW“•ĤçW-9ÕËN’ž¤§†êÑ«º´jîÆÉ[±²Víáò»x|®ô•Ó½%tð—¼$%ïÀSÇ0òê{<ºžÏ?ÁÏðDôûi½>Úo^ïS×»ÔöÐC=´Ïz\CÞ—ømR¾T¯’³“ä¬äùàŠ¾x"¯¨~“ê¤û'Å>ÉñO»ß“î÷äüNÐ?´Í_óóWüýYö?V}Ý3+÷LÊþI6¿’M¯ëŒ³úã,ÿ#¿ÈÀ¯ø þ(ÿé~?ú_~„¡žÏÀ'³ð[lÖÛEÀQFðHRÞ‡Àw¡ð"fœ™§m9@›NP*ÙŒ ¶cï À»Â°2±ü ¬g4ÀÙÍ-,¾kK/šÕm¬5[k õ›_ýfÚܶŸ÷-Î#s…ß-37ËLÎ9k…NZãe—¸ÙeîUv“•]¤ç-U¹ËUnކ󣡼ê„l:¡Á;°EŽí|;_Žíœ‹»g"𓼤ï´kÅ-òkἚøo7’ËÍä²ôv`=˜VHãÕ’8öB¤=©q¤«Üi*÷áÙ=øvO‰ûûâ~þùQؾTv/ŸØ#çöú^>§W³[{ìÖÞûÆŽ¾ñ£¯Äþ+ñ?ŠüÒÇ?4±ÏÔó»õ<îýÇe?qÙOã^sø×œþ¥{¿©^ïð¶ƒü- ÿn¿Û‡/ü·;ÿ-Ï'¸@ îǤñéÑ<@4O ‚…k»@ZîÐ~„ß¡ü’@$‹Èòâò<¸Áx™°^&liòË|²ËÛ4²öÍ-–#DeˆÓ°l´ì-b–ÓX¥´Ø6'D ¯^ kׂÝ:O7N“Íö-ƒ}‹`áo{¸[Þî6À{°åD9QNpk[œÖè±·º,mî¤@Ë©2ë¿ÙºïönÒQë´”zîq¶»œm®û(¾ÊðÙŒ¼6c/nûÇÛ¾òú“<¾¤Ï?“Ïãdô᩽8jo\@«×*ö–S½¥”ïv£Ý„hø¶>­ŒÞ#ã7ˆùvS>]”Ï¡…KèaRú³æ>¬ù´:£í¨ûÐ6¾ô ¯Å ƒñH üÒÇ?4±ÏÔŒ»õ#.ý¼ª¿o*¯âKøŸÒþ“>?¤ÏïNûÓ€ÿV‚?Õ û=“þÏe ®@kø,~ ¯ú@+þŒùOû>SþÒ]´„×mGÕ‹QõbÖÙa5¶XM’ÍKd³RÛeu6Ù]MØV“v¤ßš¢7樎鋆:bãn-8Û‹NS„Ôá0æé¾9ºoŽˆ[«¢êêå:¹N·{­ßÞìÑo»4[îá›Ë¸fòïOÍ;ÓóOƒÁÁ ñˆnFº[Ñ®–õI»½RnïaÀKØpöã“=¸äÏyõCÞ}PøR€> ¯ãëÆùŸ>gÆ£wèÝÆúÉ[>²Vϵ*ûíJ¾ûÙÞ¾öw¯Åv[ñ]–üÒÇ?4±ÏÔ6ãõ ¸ý³¿lÀ¯áäËøy2þ„9?¡Oî]³û—lÿA ¿ÐC/ù²Ãþl°ÉjɲZ²l½n£/[¨Î8P3Ž gÛBYöÒô«´½*íT ÃUp×­5ßëM›/ fËÂÛϬ¶óë-Ýo3w[ÌßÐG7ôÎÛc†¶Øã{ 8ÞÃNS ›”Ã&æÍÙ9³vN…ñ³¡|léÑ@:tP³›Ó¬æôì‡h;!Ú܃+· ÊîöÀ»½°/ScÀTØñ!n¼H[¯"Q#È”Hóe¼ÅYo?ô“Ïý$ôÙ½6E/ZµÛÖ­vöm޽›c¯r·SÜ­Ô÷ÚV½ö•¯ˆq+âJù%Û¾IvçjúTi¾•o­þ3ëŒûeÿ>ÙϾI“ï’dü^è?ºÍ,sóKýD€¿Q /ÚÚ[ö¶–þ·?…mÌÏDÓ3Ñ4ÏCu³ÐÝmJÃF°Ó¾_´ï—í_pWÜØ¼6ï ¢å+h¹JÜ0;· íâ«xƒêàƸñ®;àモ8àèŽRêC”ºæº‰9®¢Nƒ‡» áîé¥C:iPί볫úììG»Â.×Àcµðî¤#»©îûÓþôððÈb<2Y[ÇVò²±<¬¬O9„›Îa&ômʽr¯SðÔüö2=€Lk¾ Úï‚÷lç=Û9Ïf³àY¬ø·Z>-Ö•.»åK®ù⯾x«ï§ëéÁ:úöj¾½š¯·r›íܦûïS¾ûÔïÆwÓñôü׿5Ämp@ÜÒ^w´—íH^ÛR¶Ö›'5¦ÉÍŠ8bŽÚžù¶§¾mÈê[r:–Þk·šÀn/»€Ëîáï(8{Ê9LËŽS2å*Y¹J–njÉkš²ZèÎ:óŽ—Ù ¥öBêÊ(º²Š.ÀÑ“°4dí8n»N®å¡Ã¹hpïlÄ»Û1/(ÛÁÊ6ñh>Âø¸–sî%œûýF>ÿQ2é³LºlÕ4Ï5M3ÍsŸ\çÀÙ*¯6J«Í°èãl:8Üç²79ìêØkz¶àjŸ±n õ{ˆ=^ã¤ä¸é9.RÙ”¶Fæ¡Ü9¨w€>“ ¤éYºVE®©â»ªx®ëÕNºõS®Ïçó³ùüî—»…åîò«¼†ªð%N< SZÄV†ñþr<œ-È‹Ër"ó¯ä<ëùGª«Ñêªõ8‘=N$O_£×Ähö¡=¨Ct†ÓÝ!´÷é[½úVïˆSâÔùÄ>Eq™ñƒæ|`ú&G>‰‘Ϫ_;ê—ÎÕÝ@µwP-|¬s_+Ù§#¶iÈí·XÓmÖ4Ý?¬7Oë ïã{Äxà—Õ¸%õn"³Óˆ¬ôã¹G8îQÎRêC”ºæ”ü9¥?+ëŸÊúé7Ô:Mõ§xéÞ0ë¢áºè¸nÌ£³#híØJ»v®íòc»|˜ïÛF»öѯ ¹£Ã.hñ¬è,:“è“äú$ØPã¶8í¡ÓhctÛÜŒ6÷# جcv+ß/X7ËÖ Óƒ tâM’8“dŽ<•ó%|å59MEhèÚ:0ç⦹ø©®‘òs¤|œêSº”Än·¡Ë­èrì”H;%ÙöÛ¶}¶î¡þ»¨®ùŒ¾cð€€< PcÅ”ò;™¼Žæo0™ƒÌ&`óʶ¼ò­¯H¬+Ò+ õ<Û½O6ï^»Ó×®ôö¾½£o¯r٫ܶj÷Åô=ñ}…ácÀØÕí65{M¨‡j!ÆÜ:ö7½Ý‘ƒwd`ßl·Ûîu#ƒÝHâoç8›ùÎ=¨›j&å8L9Nh×›š5æçÐi¹ônxˤ2ê5:AεS­Aìe;DÎ־㵯¸îeé»™znõ¨{½jð=û<~Ïô›Ä}&ñïm<{ÛO+´cÊíó~Š<ߢC“‹Ðäâôè½:oYo³Ö[ìö8ü½Ž?/m|[Û_÷lç=Û9ͯ^ kׂÜË7$2Íâ«xƒêߪº7ꮎ4ó„<âŒÞ¸£7®>ÿë¿úå@á9P8NhÆkš1šçů9ñkÎCË£Ðòê׺…5î²ð+¬¼ ì;;;ÎÎÓ˜´æî4;#ÎñÖ¼u€ðæ<y ©óÃj|ñ­û¿“ϯäôœÿ½'?ïTh;Õõçr½yÜ­µšƒmf Üç²79ìæžóy§¼ßßL7÷ÓôÃ…=0â®!8«ˆN@#ÃðåNÔ9SµhµCš-Pç½¹ïFnŽË£ƒ²êu:€N°¹³¬.lì®;kŽÐés´:\íÿý;ÿNîž »§‚ïÇö»ñý¯ ÆSÂq”ñm›¼[fï# óÈÃ|òîż»±o:AsÎ\ôPÓ=4ÏO¶›Óí¦Û»J6îÒÓ‚›tà¦Þ«a7ªØN¤ë€i:áy#¸^Hî,Óã‹4øä±9ìNUek•YZæy¹£^n{j›žÚ¦èÔ º5.žZ+§–Šêôº½.¿¯À„ìç÷;9ýÎÝQ+·TJ;®!Žùó¾Düðe­¼koÓÃÄ´ðñò¥<|©O*¡»Ê¨nóa’<Ød@±cÐ,X݈¡7b(MîÞc{·˜àMÎ8sŽR †”‚âín8»[ŽBÒc´˜åa9XDNhùëš>zç°9¹ìnŒa££héÕŠºub®­»«`nëÒºô…®Ì7˳ òíª(»jŠ.è¦Cº)ï`÷»Ø=ïÞ3À·Œðö„¼=¡/F³ÆÑ¬òlô<›=1ðÓÌ|4ÝÑ•·temòä[|¹à„…¸!!nÞ[‡7–ã S8ÂTÎDL ‘åo¹[Àîi šBÆç¯'9ëÉ΋Ç#¢ñÈéÃMºpÓn«å»ªùnë¶1ºíŒnÊS²€T탈»`â.åæs¹yœï0°;Ì,ÿÈ“¿ò$ðÀß¼07ï¸ãÅî8ò1ò<Œ|áÊûxr¾ßm’·Ûd® LÓ‚Ó4áò`8|˜2ÜÛŒ·6äY#9HÎWÏc•óØæ–¹¥ƒ®z¾óž¯¼è°¥:,)N›U³¦Õlê« ºªÂ£®dèìå»ùnÖµ€„î6´»­.ð:¼€ïÍT;óUÖÂ5€ñFû¼Q¾íæ&Ëy‰²ß§‚7éàŽb{ƒ˜žâ7¸‡ î4›+&Êäm…¹anXÐã–48柶¹§í®z‹sž¢Üè«Gº*Ñîšv‹¦¢ê˜Îº¦3®¸Ë®2ìe;DÎÓË“´òäî'»„IîíÐ »tï D»è/Á@Þ£Þ·¨÷­ýôÃ}0á¸Eàn$>£‰¨ãfª¸Ùª®H@Û’6å˜Û9f6ÎjQCš”P稷9ê-Ί€û¢ >é››:fæÎ¨iªFënOºÛ“îÄ’Û±$¶í®»G«®Þ¹«·®jî·s»­Üî÷f˽ٲÞä>7¹Ž“»€dîáC~¸Pß®&†K‰¡’モ8àèŽIºƒ’n å¨ó9j<ÎjÉkš²ZçªÜ¹ê·.Š++¢ŠÊé‘ó:d|Χ‰ë©âzëWǺÕñîà °Âí¤»@).Üû[·>Öî‘æ»¤y­ò3|LàIƒ¸`î…Æ„âŒÞ¸£7®:+óŽŠüä±9,GN[n[–Û–æµ+¹­Jîzòsž¼œèŸzº'Þ®˜s‹¦âêi™ºšfn´X«­*ìø»~.Îs³ŸÜí¯…»kámõú}~€àM¸ SnÈ»†r.â°F¸¬®;ÇóŽñüäÇ¥91éN\^³—¬æ¾Ó¹¯´î{ËžÅ2èœBº'®—ûc¥þØê^ß:—·Î³hS¬Ú샻 îÌô£³=(ß–W·å•î ;«‚ÎêáÅP¸qT.-:ã‹N¸ãÜ®¸÷+®MÑ£“thåÖ¹u€®lv“›¤ç³q¹ìÜn‰³¢lÀéyF:^QŽ¥BK©P’ë%ZºÉV®¿$k¯ÉßÏ47óÍQKƒ”RáñM¸|Sn/qc‹ÜXãön¸ý›®OË“Åòåå9yBm›DDç¶©¹íªn‰³¢lÀésé:\úN¤–›©%¦ëºÆcnM“€dáI¸En!¡+ˆhJãX¸Æ.A%;INåß9AwÎ_R—Ô€æÛ˹¶òî|ŸFèš:&‡N— ¥ÂÄê?Â:ðŽ›…fâ@²8,Ž3ªÛŒê¶ä,¹ îQµC”mPæJ¹’®ny‹›žbçÄœ9ñ'‰Ä+¢q émy:[^N£•#¨åHámV¸[U®&Rˉ”²ãYʸÖr®D‹‘âå/¶¹Kí®a2«˜Lªæóf9¼ÙŽ|Ÿ›Ÿ'æèU:'UN–’㥤¸â‹Ì8¢ó7}SßTäa¸¹n.TRÕ°æ#B9ˆÐŽo¿³›ïìçÑ|9ô_‰æƒ¢y ã˜¸æ.G¦[‘é–åX{¹VîcWû˜Õþçµ¹Á­n}m›Ÿ[fã¶8íƒÎISƒ’TàålÞ9[7Žd%ó™ |畹Äån}Ô›Ÿu&䪭9*«NX6c– ˜æRw9”Îqåœy@äÄm91NYI –RBæa|¹˜_.MâË“x²åª¹jn[Ä3–ñ ‚$øùú2 ý’,ðBFØï²@é*PÈÜòWÈÛâ@HÛ2BPùz[PìŠC€ú2CÁ‚=’Œ˜ê²<ØçÊ> ‚D1+’M99sHøò|¹J*Hè¢R@øb8¹*`™-gIš]‘ªy! HP®LñÒQÙ’[©*G-2Y’]AÊÉ⎃`ì{kéúFir; Hý*Uù"^!mª|}9‚–ÁÒjIãF(å3CñQxñ³\0ò’X@ðê^É1zB!RUiêo¡Ú_A z`Q RY‘j}9$¢Š¹ ò–±òzùCVPàÓQøÜ»9î“7P÷LøóÛ_`óci(öúoá2wÉ BÑZe¡*Úq‘ŠI1âkxú}é ’zqAú“2JsB¡ãíèâsˆûÛ9£"Ñ»6óDpò;1 ò“i+%0ÿ I óÓghï[wØò…iÚ^1šWÙ"~Éi$B|ù øòÚli "sP÷¢m ýj‘9$’±ê˜XëªàHÜzæ˜úôèùZÚ Þªï˜×Ú×Q:ÚÙí8õ‚þHëcÀí"íYÚé‰ [!s(A»=Ñ“7€ý A³9 S"yCFˆøcbÀôƒq¨øÚY¹ bpé- E±úZɺnÒx1j„rzXú iqš~xà‰O±7º‡É ªÂxð’éR¦ñ¶xèªË æZ½ ýRÊèÿŠÖ) zÙ ýû˜ð+ªèúôxüó™ó1aû8ùË*¹;'© 4IëA9“JÉ“V9òGa ªi‘5*j +ZQyêa¹.eázt9êtðöb€¸õšpàônÁ 6©>*nj‚i"¢dQ1Êki'ª…áÒ© b·ÂÊàøÎÙ ÒÀ! "ò ûhúCˆñc¸ô»Iá 35¹S4Û3Ñ›: 6‹21C?©[yD8êx $JOI@:‹ $’o‰Úh™¢øç:/¹"²™ððŠy±ÂA7Ú¬Xàš¡ùÂnQ/Ú”Áò·¨óâ½Húrº@üêµ°øR¶€ûJ¿Ñ:àXÿ‚í±k)KpÿsÉó™S#Y›AÃÁ[') 3,‰#+ Ë9‰#Q™j«yL ’J±²Ž%6ÁNÂ|á R†XóòYB«ÈêÊu‰$²`Á(ʿٺ°Á"¦©ʨA ²µþz˜A ¢µ¹B¾xõò«ðþZ«ªÓ9jÚÉ’ÞùË¡ÛXøË@ÿa kI «Ñ [á Ë Ë'Q6q»C‰²¬¹ºU‘FªâÚú†¡2’\'Š•¯ñšzéb€¡‘ Žy'šŸÉ:®0÷j”Ù:|‘ ±Q–éªQZ±) ± ʬqrËq$šâi¢ç9 *è±zè! ¢í[© QÃ É ñ»áãA#“*Ñ"Ë>é£AiâZ ¡/QFy!,J A%bjRJ†Y<’¾iÒ¢±2[yß±I*Úp„¢iby¿¡-Q’˜ ‚=éeºv+¢€ašX‰3 k‰'Òw!v™&ò}™&|Ñ"€9buÁ w #2œqÚ”RžY ¢˜qÄY"©‰/Ò±Ñ0‚¶¡'š·‘BÓ1)òã¹4Êèi)²×‘7 ãA"ÿa0êÿÉ'Êþy-› Á0“ 0k5c5É=»Š‘D3ðYK’lISá¢m;Z²azYa.r¥q’ka!u90rx±+Òri;²w962|Ù>ªn©DZ|1?š›ARŒÙ *ŽY)²Œá)r™á0B­A#z®$òÆ™²Ê‘/BÆa>Ú°™8r·Ñ(j»á-‚Ïé3èù%Úãq-:âÁ>òð8úëù3²ñ:› i=ëñ9ó ©3».Ù1³„É9êÙFŒyM[Ø);d¡Dzš¡Ú)F1©WZtqN’\ 2‚C‰)Â.Ù$BÁ%q!4ªm2Jƒ9,ê†I0RA.Ê¡'Òx‘#š‰Ú†™-Ú‘á4B§q-b¯Ñ"r½'"¬é0´1?r»1;²±*êÄi7²Ò9>âá3âÒ‘6²×Crõ!=úù¡8¢ôI>ãñD›ÁCëù7›‘%{]!!‹»)1;êÉ;t!Dö)@´"‘Fk))"k™!Êl1P +yJŠbé3"`‘9z€ëâZ‰4Šb™<êx,p982R€ùŠ‹A(â„Y:ªˆi7ZÑ&R‰™,j‘a5 ®y/ZÄ)-"­é9Rºi<²À‰7r¶™9’¿H*åERàyFr×MÚìFõA=úöáB*ýéH2úqQâô VsyGÃ8™4“|¡4³ÒAü!GŒ/AJ“ßYH ÉK’ qh MaA"cQ4r|éH²u3"/Oʹ±¢f‰!p™'âá3Š‹ar{yrkAšyÁ6:wñ<âŽ+ÊA-…?*›a@â·@*«IAZ­É=±aMâ¯ÉJB¨CšÑ)A2à!ER߉Mºõ‰GRñáCúéÁL*óYMòóQPrí1]’ýÑ`#$¹OûSùBƒ•CSðII\!IKäéIÄ1¡KRÔ LR_)P‚@™/bE)Jjm‰/RpÙ V¹$‚ѲFÑ7ŠŒéÂd©;š)2Ú|± º{é)Êh:Z„AŠÙ@Їù9jw±F: ù;ê¬É;ZÑ,ú®ÁA¹‰NR³‰M¢ÀC"ÝÙF*á9RŠûáG‹ ÙB ÷¡JÊë Q²ç!RJæyRÂðùS+P3"ÑN[S)O£¥ÑNô yM,;ÁMÜ‘Td:1U2„A.€9‚n)#úUyAâW™(Šb¡‚–$»˜ìºe1)‚HA=ZbA.Òy(‚€A<ê~A*byñ5Š”ÉCB‡i ™¡<²´á8ª¼D²³)BBʹ<úÖPŠáyNrîDZ÷qD‹F»©RIX“ÉQS ÉKà R£Ù^3RAaû©!^D)]L*_+ì‰el yhb†á9Jlé-²ù,Rfa úgé4Ú~q0‚„É/Bš #úR%‘@²r±.:uñ;ÊzQA¢|É.R~!9‚‚)F*}±Kr–qG˜)@‚ ¡HrªÁG‚ ¹A*ƹ=Ä‘HʾYCš½>JËéDÖ)G¢ß)NÃ1NsQO+QV+TÃqN›‰NKyRã2éWã`A\³—¹c“Í kãåncÇÙqbbñ+ŠuáDê[I@’~*zl?:hÙ7ª!4J™¹Òg±"â@y1Ê£Yêva2‚¡7ú‘¹7âŒA6ÚŽQ>Ê”y?R†iKr :Ê”Q?ª­EB  NâµVR¿QTâ·ÁUêÈ©L¢àqNÂÚ)XÂð9Z«Ñ[«"^«4!Y{BñS«;‘Wc4‰[#AÙX»[áX«uq`‹lS°As v]‹lÑaRqr{Y1Ê1Kò{ÙAjkYAšV7âuaF"s @"•Ù‚k!Y\©JjQ>2Y=º¢1;š‚!8Œ"ªŽI7šžq;•iJz¯±5ʤ 2Ò§K’µ‰\¢½U»áQÔùSsÁU«-1^‹E‘^#ly_yIcSƒIf‹‘)ec¢Ù_[¸áVÛËÉPKÕ‘NóÕ)RûÍqYËÁy_˶áb«°Áaó®_k° ]SºI_ÃÊ™ikÔ¡n3à¡q›íIs+ûYrÌñpÓŒIWË QU›´UkÇ)XsÒÑ]“ØéaCßé`KéÑZSø¡QÔ 9J´9HŒñK´R$¡XtQ[Ô9[+ú¡XköYUëõÙU“ûy]Ä d, ±j,Én¬Éq4)¡qÌ2pä9éo*}a$¢‹)Úg9 ‚y©BJc‘92‹‘6Ò„‘L y>ª“é?B8zZ_!6"paCÚŠÉ?‚‘)FbŽA>ª›QAÊ™!>R’ÑMò‡ÁT²£QKZ½AIzܱ^K aYË‘_ƒKPÓmKK‹aM+¦ÙQ»ÅÉR#ãÑMƒûH\ G4)Kt$IRä'1Y(9Z<)V4.1O45qI ; F¬>ÑHÜ@YMì@qRÄ>ÙTÜáaÜIh4QálœYYnÜ]Qo|_inü_‰n1éRÛSÉQsr1O+ƒYTk‰a]Ó˜ù_ë¹aUãØùHKñYB|ELñH¼,ÑFtAá@ÄN‘=ÄT™@¤U)H$RéPOaT¼L‘ULaR,NYNüOÙMœOñNÔOÁQÔP™T,S T$UÙPôW!KŒVFTB´R±C4TÁGlZ™NddAV4oÙ]ŒyùctYg¤ƒijŒilT{ÑmlténiÁ"zcÉ J !B‚l‰Cªk©/Z‹!:ÒŽ9JÊ‘Kâ“1SòuÙQºlù:2=±SJqÑF¢aJŠ‘IG¢¥yIÂi=jšQY °9QƒE U‹†±M3­)H3ÇqHßÁHÓô‰K±QD ÉW¼ ¡V¬4ÑLÔB B$Ey>,HA?ÔRÁ@Ä`™?$i‘>j)@œdF,[!L$QùP¤KñSJaT”LñVQaX TyZ,U¡[œV[LW™XÜZQT<\¹N<]¡H\]ACü]yBL`¹C¬hÑGtuyLŒ… QÜ“©VüžÙ[Œ£A` ¡d<˜©hDŒ¹k䀡n¤vypc•9T{ã¹U, Lô(±Hl5±Kt?©OlEINÌJéLTSáK”]ÙJÜbÉG^B4Uy>üRQ=$WY<<]á>^¹C´XáJüNùOÜDIPô;‘Pl7!QT8áTä@‰ZtJy_¼Rñb„XaaŒ[)]l\Wt\ÁQD\KäZÁH Z±EÔ^IEdg‰F,vÁG¬Š‰I„žÉK´°N\ºIR4¼YW$¶],©cÌ™Ái¼Š9n„}ñqJ§©@Š~Ñ8b[)*‚”¹ª™¡(’QéDy:l!B¢˜™Câa1P¦é?úOÁRªiSŠ€^ÂqÙ]Ò•!HZ°!NòÙU«C1\ŸáIëîÑG$&QQDBÉUÜOQRZQQLc VÔb9X¬[YP4WIDŒX¡?„YùA TñBÌKALBA<Ü@8BÙ7ÔD>lAùIt=ÑRt9!TD4!P´.ùLì,‰M,/ñRT9aYlEi^œQ1_äYq\¬]ÙV´^ÑPt]AK¬Z™HÔXÉGdZ‘F´biF$qEd…‰DdœÑC¬²ñC´Ã¹E„̱I\ÌQOtÃÑW,¶_L¥ùf¼–YlŒ‰™oÛpñV«•A]ûÜSŒ"YC4Mñ@¼[JœVaQìU1SL`V´g\aYYtTAK,K¹=H‘8,EY:ô?y=”8©;Ü4Ñ6œ4Q2¬3Ù5t2)?Ü1aLÄ3TÌ79TÄ8¡OÄ6Kl3QK\3ÙOL9©SÔBñVDMaU V P´[YL]QH”\ÑG [‰FÄ[áF¬`yEìk D,{A¼¡?,¦Á=dºá=TÈÙ?|Ï¡D\ÎKTÇ S„»q\®‘cl¢)hü—ùl¤YbwÑ.jfq!Bd zsùÊxñ)R’ajbÉ.Âx±6ÂkéDú¼I3"Š¡YJ‹‘5ú©EŠ‘W£HkQ;#½ÁBkñQMä ™T¹QL;9P¬<9OLAAL´HQIOaFTTùEÉI´B1MìH‰SLN¡X¼Qù]dO™`LIQ`dBá]?1Vl?QNlBGãËÁC$ Y>´2¹Cô^9CÜ€1=ä|á@Dr‘DLxÙ?ìnQ?ÔJG|/‘J´5iDtP;ìb™<„`ÁI¬VÉXlR]ŒR[ôQ^TMyf”M¹jÌVYdtbYXÌgéR4cùTl\ÁZ\¹[df™UìsM´xÙIlqùMtaùW¬PùbFif¬E™bäKYY¬QéPLU©K„UÑLìUySlWñ[¬^±b,i)d\taa¼|ù[¤¹TŒ€‘O4{YMlsOTkqTnù9\ká9iY;üVA?¤CéABQC,KáFÄYñF¼e±EŒgùKbUl[a\lX)_lWùb´YQe´]‰c¤gY[TsRtyN„u‘PDnAS¬l¡S¼s¹NÜ}ÙGä)DTx¹GTg¡O„VXMé[ÜO™Z WÉU4`QœdiRdÑVôdÉ^Œg‰edohÌ{QgL†Ùa¤iYÌ‘¡R”9NŒ‚éNÄvSiÙYä`Yaœ[™hd\Ál´cmdmikyéf¼†ÁaT‘)\„—QYœ˜9YD“Ù[£0éI3T!U[>Ña³RYë•QTã·9R“ê‰=t:¹,ôù%$¹é)@,nÉGÔ\™M´YTìa!PÄi N„c™T„Ué\¤NÉ`ÌT _l^QZ”fYU”miSvR })P$})M$vùJ¬qAI|qAHœuéGÔx‘H sáJ´háNô]¡RôX!TäZ¡TôbÉTÜkIV|pÙZÌs9`Ütáf”y‘j,ùiüŒÑeÜ–ñ^üœÙWœIPÜ•MìˆùNô{yStoqYüg`ücÁfÜfiÔm±iÄyf¤…ya¤‘Ñ[Ì›‘Vœ¡ St¡S<œ!UÔ“YZÊDÁ+2a1.¢“Ù<Ê7QZBLÙ+¡ ÒÑù$ó$ÉHëNéQS~éJã±yK»ß¹\;ûùlsöqh±\$%yPÜ$iFŒ‰D4GÉ8¨É¬´™$ÄwiA”s@üyÑEÜkQMTn!B4ñ8Œ~ÁA”aáM„VyF¼l¹8Œwy>DbñStR^\Y!Z,déU¬^©Y„L‘`´FYb„SQ[´dqR$k)OLkTtn Y|uQVtyM tÑDäkùCteÉHf1NºK±Jêºa&[ä`iT;¡T´H‰7¬aY+_ÑA”léU”qÙVôKQYt'!RŒùC¤ QÛá©`³ýYI,1YBìEÉNŒn9C<†±>ÜwG qyF\~ùGŒ‹Q¼…9\ y Zü‚©M\’aB¬†ÁF\hQQ$V™X\‘[´hi`hùeüdyiôk±f”~ZL‰aMì‚ÑKÌs‰SlZ™@79Eä79D$4ÁG[úYh+ÜÑ€”%9[ p©7<€±C,†YO”1OeùUüW‰^Œhù[4†ÉPT–ÑKÔ”™P\’±Tü˜9R<”©L ~Müf9YL_ábYetain ‹h¬3AÜM±zLPÑf4OiF¼+Ñ0„/ (4Vñ.”_ÑJl_ÁZToQRÄsHlZa>DPá5¬Ué9Ì];ØyƒÛß©zT¹]$5áW”:Ù]|DRdPR 2YpD0Qq|uÙN±!B¤±ÑY‹ñoŒ_‘pôC™cì>Ù_¤M¡h¼(!•Œ;ái´>1?l4,3º6ÚAËÜ4Áz¼‰{ÿ™ŽTi‡Ì&Y‰Ü3á’¼T9ŒŒw©}4…9uL…ÑlÿÿûÏÿúHærãš±êƒÐ)¤)á}D Q•ÄÉŽ\ ì0ŒD£Äi©–ŒŒ9{d—Ém—ÿù·ÿù&•î©‚ƒ¬°ÓÔ¡—|—Ü ¡—|I‚Ô‚Ü+Ñ ”J9¬ŒwI’t˜¹uŸÿøÿø]INÛqÈÓÁ³ôQ–”!Á‚Ìù|äa<%9¦dP)¡4y…7ÿø$yёêãû©§„6ÁzŒ8ÙsÔ9tq’,)Á—ŒXqФñ}vLh·”1ÏÃõÆ<8ù€”VñjDÙv´"é‚왃Ü6ù¼\q{´!ùÄÔF‘Ü^Ñp `ÉuœIɼ1Ùyœ0¹p„BÑrd¹~´`¹‚ü[1Œ L|Aim¼A!hìTá” Qé „PA™K~„JùjÌM nüDɰlD9™ÔFy}$RQq>Y®49É•\E)~´<±¢¼7!Žü@Qˆh7ñè¡ @l€Húj#Úèº ?|€I"$bÈO A€Iâ$b(æ ?<€HúÊ$(L A¬€IR$Dˆa DF€HÿB$(  D€IÊ$/(‘’ D\€I‚$kèG A:€I ’$]hê Fö€IÒ$/¨Þ J €I ú#ðhF Bä€I¢$VHÔ @â€I *$^èu BT€IÂ$ˆ‘¦ ?f€HûÊ$èŽè @Ü€Hùj#à( >ì€Hü’$ˆ] Bv€I j$^h\ D”€I’$ ( ?Œ€IÒ$ ¨< ?€Ib#›ÈŽ· Cò€IŠ#ÕÔ @¢€Hý²#ìÈÐ ?&€Høª#ÙhR Eì€I‚#ùè# Bl€I’#Óˆ† @@€Höê$ (ŽÆ >€Ir$NH‘3 ;†€Húâ$B(¦ @~€Hÿ²$Èü Aœ€HúÚ$Hq ?F€Hö:#äˆ ¸€Hö#ªèŠ B–€IR$&ˆŽÚ =´€IZ$-ˆ< ?:€Hüâ$.ÈŸ ?ø€Iz$%(¨ <€Hú#òn =Œ€I$ 8 >6€Hÿ¢#Çh, @š€Iò$H ?X€HþÂ$H5 :Ú€Hîº#ÉèŽð >¶€I:$(Ý >²€I $!( AN€IJ#ÜH; ?2€IJ#õ¨ð A2€IÚ#èH ?d€Hóê#öˆY B¬€Iº$QÈ’€ ED€Iê$ HG =@€Iª#Ûh ? €Hû‚#éhÊ @j€Iš$ è1 >΀Hô"#ÜHà ?΀HýÒ$HJ 9è€Há’$¹ @"€Hô#ó¨a ?f€I$kH] Z€I$ (ö ?L€Hüò#ôHŽI 7&€Håª$/(v BH€I *$%¨y 6Š€HéÊ#ûŽˆ C€Hõú#Ûh, @Š€Iº#ýh` >Þ€HüÒ#åh– @€I¢#üHÄ @t€Iª$è ?n€H÷:#ɨ‘. Mh€I)B$"ˆ’­ H&€Hü$6¨  >€Hè2#·hÚ >Ž€Hú’#ØÈŽÜ ?h€I"$)(C ?è€I"$ˆ: @ €Hú$H  >¤€Hüb$¨K B €I*#ᨎ栀Iâ% (”< IÆ€I#R#ÚÈe B”€I $M >Ú€HýÒ$8 ;Ž€H÷*$H‘  FŠ€I "$&ȸ AÔ€IÒ#öh7 A0€Hûâ#ñ(b C4€I ²$#¨â <Ø€HðŠ%Xè’½ C€Hë"$8È7 A˜€Hþb$O(‘¶ >ê€I:$"h' B^€Hîz#ÊŽú ? €I2$Fh‘" ?Ž€Hý‚$(Û >Ü€IZ$*ˆä >€IÚ$%¨k AH€IÊ$ˆÑ /‚€IQš#éh> ?*€Hýâ$ˆ‘( B€Ir$©‘£ ? €Hÿj#åˆÞ ?0€H÷#Û(¸ AN€Iº$ ¨  <~€Hû#òH~ @ˆ€Iò$%$ A´€I²#þ(ô A€Ij$Hü %8€HåB$ÿ¨Q >d€Hü"$xhë AØ€HùŠ#ñh‘© F¨€Iú$ÈÒ ?ø€Hô2#ïè  B€I *$© ?Æ€IÚ$"ˆx Bx€I J$èW BÚ€I J$èH B€I "$6¨– D€HJ$,‘B CF€IJ#ȨÈ >h€Hýr#å@ Cb€I#ühŽþ ?0€I$Ä A €I‚#ëh_ ?f€I$&£ D¸€I*$ˆ A„€I º$¨q Cf€I $Q(‘k F€IR$d‘• 7Ä€I3Š$…È’ O4€I š#’HT CŠ€Hú²#ÅHŽ­ ?€Hèj#º¨ CH€Iò#óˆô =Ô€HõZ#ø¨ @f€Hü $¨¦ B\€Iš$ˆ† A*€Ij$"hâ CÖ€IÚ$Jˆ‘/ D¦€I ‚$$H“2 E‚€I&:$[h‘^ P0€IÂ$~‘> >@€Hîr#Ú¨· >À€I$èÀ Bþ€Hÿª#ÏHŽ ?d€Hÿê#þ( ?X€Hûj$ ¨¦ BV€I b$&ˆ A&€I2$GèÇ A”€I‚$¨ ?L€Hü#ÅH1 : €HÜB#âˆT <´€I š$J¨S <â€Iâ$èŠ =Ü€HûZ$èæ Aæ€Ij$h¿ A€IÊ$ h4 À€H×ê#´Ž­ <ö€IÒ#áèŽÈ ?‚€I$*ˆ˜ Cú€IÂ$L(‘ CB€I’#îH Bì€Ir$h5 ?P€HùZ#éHР?Þ€Hü2#ˈŽÌ ;Z€Hí#¿h‘æ Dh€Ib#ñh§ @€Iò#·HŒ @p€I "#Îèr @°€Hÿr#æ` ?&€IÒ$:H‘ F€I$1H‘ D8€Iê$\¨ì C€I2$1ÈÚ =ò€Hû"#ùˆÍ =h€Hïb#Çhc >€Hý$\È A$€I â$vè" =Þ€HüÒ#ÈB ?Ö€I ’#õHl B8€Hö2#â¨3 C¦€Hùò$H0 A(€I ò$Eè‘8 EÞ€Iº$JH‘T D6€Iò$ µ CN€I¢#åH+ =4€Hýª$ Èa 9à€I #š(Žæ Br€I²$H‘Ò D^€Iš$'ˆ €Hôj#ÄHL >€Hû2#öHé ?€I"#þˆÞ ?€HúZ#áHs =Æ€H÷r#àˆ“ >²€HüZ#ó¨Á >|€H÷’#ÖHG =2€HõÊ#Ûè >€H÷ú#Û(N HP€Hÿr$?¨î :`€Hçú$]È‘‚ A\€I ò$B¨­ Dì€IZ$ ˆ‘ Bj€Hý$G ;ˆ€Hüê$¨{ ?†€Iú$0hÛ Bž€I$ˆÒ >Ø€HùÊ#àÈx > €HùÂ#íÈÈ ?D€Hý#ñ(¯ >T€Hø#ÝÈ| >&€Hù¢#é¥ >v€Hø¢#Û(E t€I*$¨œ BR€I:$ ˆ ?Ü€Hþ’#öèÏ ?(€Hüò#öˆá ?Œ€Hýò#õhÇ >Ø€Hú¢#ê´ ?"€Hýê#û(ï ?š€Hý*#í¨’ =¨€Hô #ÆH4 H€Ib$?(Ë >8€Ir$ah¿ C†€I$:$EˆÓ B2€I*$R @ò€Iª$hï >B€Høb#íhâ ?œ€Hüê#ðÒ @€Iú$h5 @”€Iê$¨/ A€IÚ$ˆa A€IJ#ù¨à>Ô€Hûj#ðhР?~€Hÿ*$ @r€Iâ$È @:€Ir#ÿˆñ ?x€HüÒ#îhm AŒ€I²#û(É ?j€IR$. @€HÿÚ$h6 @Ü€IJ$¨ @À€Iz$&ˆä D@€I"$-hG ?t€HùZ#ßÈ‘ >ò€HþÊ$h2 A€I $ è, @œ€IÚ$H^ Aò€I Z$*(¬ B €Hñ*#Ó¨’á EP€IÒ#ìH AL€Hùê#íÈ‘q D²€Ir$È: @2€I *$èv AÈ€Iº$È\ @€€HûÂ#Þ(” ?¾€IZ$( @®€Iò$$¨€ Av€I$ˆ] B€I r$Mˆ‘o E\€I$Hä >D€H÷’#äèÈ ?þ€IÂ$(G @ê€IR$H @¢€I:$#hÌ D€I$Rh‘M ?€Iš$'(Z @¾€Ib$ (· ì€Hü²#ùÈ @F€I$í ?^€Hüº#õHô @¶€IR$/è‘ Dø€I*$\ˆ >\€Iª#ÿï ?‚€Hùš#ø(" @¤€IÒ$Ÿ >Ø€I $TÈ‘· Dì€Iš$¨ AÜ€HÿÊ#û ?r€HòÚ#£(Ž <€Hù#ï(© >Ì€Hÿb$ˆp BÀ€I $V¨‘I C~€I $¨W B€I z$!èb @ü€I¢$ˆ @*€Hÿ#óh§ >€HöR#ÔHR =€Høú#òˆ A~€I *$7¨‘ D˜€I$ƒ¨’E H€I$È5 Aü€I2#ëÈÞ @€HúJ#¾hŽŸ ;t€H÷â#þè ?¸€Hý:#÷hù @΀I ’$OH‘ D´€IÒ#ëHU = €HúÊ#ý! A€I¢$hq Al€IŠ#ø <ü€Hð*#¶ŽÆ ; €Hî*#ÃF >(€Hüâ$(J B€I ’$2èâ >*€HôZ#žÈg ;0€Hß"$hó A\€Hó2$(Ž“ Fè€I!ª$ˆÈ’ G¶€Iš$%5 AÆ€IZ#÷Õ @È€I¢#ó(f =`€Hú:$(U AЀI2$ˆG @Ž€Iâ$!Èó D€Iú#鈎ô :Ü€HîJ#ÏÈ“ ?T€Iâ$hv B>€IJ$¨õ >8€Hòz#´ÈŽ— 9Ø€Hæê#Ÿ(Ž ;:€HðZ#Ð耠?€Hÿª$ hj 9p€I$Yè‘í G΀Iê$Dˆ¢ AŽ€Iš$,Èe @p€Iz$+è¡ A¢€I$ˆi Ab€IÂ$*Hè Cä€I ê$ˆ @®€Iâ$èà =€Hî*#¸ =|€Høâ#ìhÝ @x€IR$'¯ BD€Iš#øèx ü€Hø"#Ýè— ? €HüÚ#ïÈ· ?F€I:$¾ C´€Iª$-èc ?ö€Hù¢#ÎèŽí :Ì€HèŠ#œˆŽo :€Hê"#´H =8€HùR#÷(% @L€Hö*#ÿh‘ Dì€I2$HŽÚ 9¬€HùÒ$_h’¥ J€IŠ$"¨Ø ?¢€Ir$3H¢ A®€I $=‘^ G$€I$e(å A„€I2$舠B2€I²$ H  ?ü€I*#ýÈå ?¸€I²$èZ @ЀHÿª#öhï @Ü€I z$<¨‘) D¼€I’$/èh @2€Hû"#Ø( ;b€Hêj#¢ˆŽ‚ :D€Hë"#¸H =l€HùÚ#÷ˆ Fr€Hïò#®¨A I€IJ#MˆŽ„ =€Iª$(ü ?¬€I$\è‘ DP€IÒ#¦(Ž* >”€Ir$£è’ž H.€I*$ˆ4 AÀ€I²$è Af€I R$Uè‘¶ F¢€I"$!(3 @Ò€IÒ$(k Að€IR$È_ @º€Hÿª#ùè Aˆ€I $(hh @‚€HÿÊ$(` C€I"$OH‘9 7Þ€Hæ #«È# ?N€I’#ûH Bº€I ‚$>(‘) ?ö€Hær#ž( E°€I $Œˆ‘õ E\€I:$%è` @¸€Hÿ²$Q BÄ€Iê$I÷ Aú€I$(_ B^€I B$1¨Ü C–€I â$"È) ?@€Hû:#ùèG BD€I b$T¨‘  DØ€Ir$L¨”  Jâ€Iª#Ôh ;.€HîB#ÇHŒ ?2€Høâ#âHç AÊ€Ib$Eˆ$ =Ì€HþÚ$"g B,€I*$Hèû Cª€I Ú$ Èì ?”€I*$ȃ B €I J$ˆB @†€I$-H‘ D €I²$>HΠB^€IÊ#ûH˜ Or€I7Š$€¨‘  A€€Høb#Ý( <ž€HòŠ#ßHv =¬€Hþš$'¨‘ DZ€I$N(÷ >Ò€Hé*#ÉH" A8€I:$&q @‚€Hÿ#ÿ¨A A„€I"$n BŽ€I 2$#š Ct€I$Kó BR€I¢$Qè× ?€IZ$Õh“F I(€I’$e³ A€I $èŽü ð€Ir$ ˆ Fì€I%R$i(Ù 8¸€HçJ#ùH6 ?Ê€I:$3È‘  CZ€I#ù¨È ?€HþZ$‘7 GF€Iò$DN ?°€Hè*#üH‘ E€I $ˆ‘ KX€I*B$]hî B6€Ir#û(S ;€HðB#õH ?¤€I ²$r¨‘» D€HÿR#ÍhÏ B^€Iú$œ Db€IZ$BHN >à€Høò#먠Bž€I2$rÈ‘„ CR€I¢$ˆ] >Þ€I"Ò$þ“  Eü€I ’$7H‘ A¤€Hõ¢#ºÈn ?æ€HýÚ$¨Ö D €I $)(s @\€I$.h­ B¬€I *$G(‘W D €Ib#êÈ‘ ?Z€IÚ$*H‘ ?>€Hí #›èè Jh€I9Ò$©H‘€ D €Iz$NH• ?8€Hø2#õè" @(€I’$¯ >¸€I$ ¨ @R€I $;(ù CŒ€Iò$Qhô @Þ€HúŠ#èÈÿ AP€I#‡¨ŽC ?®€I "$¨(’ E4€I Â$+(³ Aø€Hý#Ú(ñ Bb€Iâ$ ¨k :Ô€Hò2$¨ß =d€HùÒ$¨ë Cä€I¢$Dˆ‘ B®€Hÿr#ߨ’ @€Hê*$h’ IÖ€I Š$Rhw @,€IR#ÿÈŽý <€I :$k¨‘? @0€HíJ#ÈŽÜ >€Hô:#Æh¥ A&€I $*ˆØ CÜ€I :$¨€ <¬€IÚ$«H’û K€I R$2èA A*€Hõ²#œ¨« GÌ€I)’$S  9ø€Håš#ÆÈ“ >&€Hþ:$d @|€I:$,¨¹ @ì€Hù‚#ÄÈ‘á Jú€I4$Û(’• E€Ij$è ;†€I$¯¨’g BЀHô’#¯È- @8€Iâ$4H‘y F2€I B#ÿH: C€I Ò$hw GR€I*r$ºˆ‘ï Dž€Iò$HŽÔ >€IÂ$«h‘Ý AÀ€HùÂ#æè^ Dö€Iª$™H’ø IÖ€Iº$ˆÖ E(€IB$=¨‘ï H€I*$=m <’€Hî²$’: Jn€Iº$1(B A¨€IÂ$|ˆ’ MN€I9j$©(‘e Dì€Iò$‚¨‘E Iø€I,’$Šˆ‘7 ?~€HöÚ$*H’7 Jø€I%b$gˆþ Bp€I B$S‘í J`€I4z$ÌH’3 F8€I ú$±¨”° Tò€I? $v¨Î G”€I5ò$Ìè’ E¬€I $;(ˆ B€Iú$\¨’ J2€I% $iH‘¯ Jˆ€IlZ%³h”ƒ Ir€I+š%!H”m I€I j$$Hæ B¢€Ib$è‹ CÜ€Iº$oH‘ Eœ€Iâ%Åh– Mª€I'Ú%(•R Mb€Iú#ð¨z CD€Iz#ë(é AØ€I ò$;H‘) E€Ib%šˆ• NX€IEJ%s(”Å Eø€H÷z#õˆàBv€HüR#á& BÞ€I Â$"ˆ¼ D4€Iú&T(š gŽ€IwB$ȈÈ <ø€I²$0ˆQ ?€I $1h‘ B€IÂ$(Ÿë –R€J]Â'Ü(–¨ A¶€HíR#éèa A¼€I:$ˆÛ E*€I$ ²€€K2*+8ˆžå KÞ€Hèj#Í(< @Ê€IŠ$4è‘2 E^€Ij$>¨~ Î,€KDj*h˜Ù ?ò€Híb$Hj @0€I *$T‘W Dn€IŠ$H(©¨ ˜€IÁB$Þhû @x€IR$ H= CÒ€I²$-¨– D8€I6$Á¨’” GÜ€I $ÈÛ ?â€I *$8o @L€I’ ‡Œ <\€Iº$:èK €IZ$d(Žý <€Hå²#žh® Dà€HöB#º¨Ž0 ;€I"$‹(Žm 8¬€H÷š$aHõ 9ì€IB#y¨* @ ¥7i[n“³È³j’³h“€³(ƒ-s0‡¯§âÊu)ÆÚtf³zø}¬gB–k¦¦BD]³zø},':’j¦F*:[!eS2‡¯‡ÒÂs¨æ¢^a£Eš RžÄÑÐHÂÙ@.¯gÒÀs(ÆšZ`¢å‚Q$©ÄEÄ ˜<’™.*"±4,/GÒ¼r¨†’V_¢…rO¤‰ºCáˆ9—CYd2¢Q%‘âi"'’b‰*)’⪸r(FŠP_"%iúNDq²Ac¹|6–c)T.”b¹8)Âi b !¢ "â1$bQ&âq"(+G"€h$ÅêUŸDÙÎFÃùŽ9—#IZ/”b¹6(’‚Y#âáàøaÐöAÐôAÐöaØø¡àüáñ b "â1$‘‚Z´r§ÆzJ^!…QîLœ„Q¤>˜cj2•Ù<)‚Y"¢þAÀ쎠æŽA˜äŽA˜äŽa è¨êá¸òAØøáù ‚! #+ÇR~h¤…âTž¤ÁÂD™ãÙ~7#H,"q#Áøü¨äÁpÖA`Ô A`Ô ahÖ pÚá€àA˜èŽÁ¸òaàü" "(†²J_!eIæK›Ä9˜<—ƒYZ/”©*%‘B! âaXÌ 0Ä A0Ä A0ÆŒ@ÊŒÁPÐ!`ÖÁxàa ì!Ðøáúœo%†VdÁºCC¹r5Bé:)"A °èPÊ º‹a¶‹a¶ º ÁÀ !(Æ ¡@Î A`ÚŽèŽáÀö«g2ne¢…‰ìM›¤9’;–ãAP-“""Aàðá`Ð ¶ àØª €Ð¨  Ðª ŠÀà°‹ ø¶ ¾Œ!(ÈPÖáˆæÁ¸ô2hŠÐw¨fš>\ÅÂE#Ál4ÂÑ.'BüŽ€ØŒºŠàȤ аž ‰À¨œ ‰à°  Š À¤ Š€Ø¬ ð¸ áÆ áHÔÁ€äŽÁ»DŽ®§Úœn%fSyš=–ÃIH-¢i ¸â !0 @Ц à˜˜‰@€’ €’ ˆ– ‰€ œ À¦ ŠÀà´‹ÁÄŒÁHÔ Á€äŽÁ»b•0h:¶s&ÆJ&WD©¨?—CaJ-’‚a A¨Þ Á ¾ŠàÀ¢ ‰`€ÀhŠ€X†ˆ``ŠˆÀx‰`˜œ Š Àª ‹ ðº A(ÌAhÞa£è²8)Ó$‰¬çŠ‚i£¥¹øN›¤)‚8Bñ,'¢ð¡XÈ‹€àª À€ˆ€P„ˆ @~à@~H‚ˆ€hŒ‰ ˜ ¸ª ‹ ðº A0Îahàޤ »:JSB‘.‡êšn¥&Rœäi”;–#6(‘ðaPÄ @Ф `pŠ 8~ (x‡€(x‡€0|ˆH„ˆ p‰€   Š Ø²‹ÁÆ áPØ!ˆæ–£I<+"(ì ÁH²‰à°Œà@v p‡nàp‡ t‡` z‡à@†Àp”‰à ¦ àØ¶ŒÊAXÞŽ˜ìŽ¡ ð1ú¼}'&Ò.^Ÿ¸E˜c©V1¢   à” ˆ@`z@ rn‡p‡ t‡` z‡à8„Àh”  ˜¢  Ð²‹ÁÄŒáHØŽ!ˆî!¸ðaÂî‰)ç‚Vh!E¡ÚMD!r7”bá%aàÖ ¡¢ ‰ˆ‚‡ 0t pr‡ t‡` z‡à8„À`’‰   Š€À° ÂŒÁ8ÖŽxêAÀúáÚЃ¨G2>d aÆJ#ñb5b¹#ŽÁÀ΋Až àˆ‚‡ 8v r‡ t‡`x (€ˆ`PŒ @x𠍍 àØ¶ŒÊ APÞ˜ôáØÄ ¡3+gòlo"¦ ìSDq€<•#)**b ä P²‰à¸ˆ@`|€(v` v€ z‡À0‚€P‰`€ž @¨¦ Š À® Ø´‹€ø¼Œ!Æ ¡0Ð APÚ ápêA°úáÑñ À  Š  ¦  ¸¬ ‹Ð´ `躋áÂŒ Ì @Ö ¡`àa€ðA°úÙ¡ñ Bê‹)ǪVk!eÑÚPDIt:”ƒ"(â H´Š ¸” €p€À8z 0|à8€`HŒ‰h” €xœŠ¢ `¨ª ŠÀÀ° ‹ ض  ð¾ !ÆŒ¡(Î!HØÁhâŽaˆì¨öÁÉaé ‘ !‘¢(‚ H„ˆ`PˆÀX‰@p˜‰À€ž ˜¦ Š °¬ È´ `຋àøÀ AÈ Á0ÐaHÚáh䎈î!¨øÁÉaé ‘" !‘Â) #­Hjˆv¤FzZÄÙšCÙD0!A¨Ì ¦  ¨ˆ€p„ˆ@P„@P†`Pˆˆ XŒ h” €xœ ¢ Š`¨ª ะ ‹@и ‹ è¾ !ÄŒÌ0Ô PÜpæ¡îA¨øáÑé‘" !‘Â)"#’bI,%ˆ `Š `Œàh‰ p”‰€€œФ Š€¨¬ า ‹@и ‹Àè¾ !ÆŒÌ 0ÔHÜŽ`ä€ì! ö¡ÁAá â!‚!#"A($’ÂY2&±)Jƒ'§:8eŸÅ‰ÆMd1j9”C "(B ê!`¾ 𢠉 °” ‰€‰x‰x’‰ x”‰`€š‰à  Š@ ¨ ŠÀ°° @ȶ  à¼ŒðÄ aÊ Á ÐA8Ø ÁPÞAhèŽÁˆðA¨øÁÁaá !¢!#BA(%âa2'‚x𠉀 ˜ ‰`– `ˆ˜‰€ˆš Àž Š  ¦ Š °® ‹À´ €Ø¼ àèÂŒAÈŒ¡Î !(Ô@ÜXâŽapêáòa¨úÉé ‘" !‘¢) #’BA*%âa4'‚<)0I*¸‚''22e…‰ÂM™$1j:c&*¢0𠡀ȋ¡°ŠÀè¨ Š@Т ¸ž ਞ ‰à ž  ¢ Š@¨¨  ¸® ȶ €à¼ŒðÂŒaÈŒÁÎ !(Ö@ÜXäŽapêáˆòa¨úáÁaá áù ‘‚"’9&$’¢Y0&“By8(â‘B*‚°¼‹a² 𬊠ب `Ȧ @¸¤ Š`¸¦ Š À¬ Ȳ `غ àðÀŒAÆŒ¡Ì(Ô a8ÚÁPàŽAhè¡€î!˜ö¡°þ!Éé‘!¢!#"A&%ÂY0&“By:(“â™D*”‚±L,/É2°‚¦§2*eŸ‘¼NAj;£),+‘HøA¨Ô áXÈŒA8À‹Á¸‹A² è® Àج ÀЬ àа ‹@ظ  è¾Œ!Čʌá ÐA8Ø ¡HÞ`äŽxìáˆòa úá¹AÙÁñ B !‘Â) #’BA(%Âa2'b:(”™D*”‚¹N,•"ÐìA¨Ü axÐ ÁPÈ A0À Áº‹¶‹@𲋠贋`è¸ ÀøÀ !Æ ¡Ì (Ò a8Ø ÁPÞŽ!`æxìôa¨úáÁaÙÁñ‘B !¢!#"A&$’¢Y.&“By8(‘@*b±J,âÉT-•‚é\/4*Zð“*h*`s¢&YêY„éEÁR4“BÑ'b üAàìa¨à ¡€ÖXÌ @Æ !(À‹Á¼‹¡º Á¾ Ä Ê á(Ð A8Ö ¡HÜŽ`âŽapêÁˆðA øÁ¸þ!É¡á ù ‘‚"â)"#’bI*%âa2'by:(“â™B*”b±L,ÑT.‚é^/–# X5ƒ:.â±"(ba#BüaàðŽ¡°äáˆÚahÒŒáHÊ 0ÄŒA  AÄŒa ÊŒÁ(Ð !8Ö HÜ á`âŽAp莡€î!˜ö°üÉaÙ áñ B !Â!"’"9&$¢Q,%“i4'“‚<)¡D+‚¹L,•"ÙV.¢ñ^0Ch1–Ã+6¥®i:œ‚¥§:fž¥¡¾O™¤iŠBÃál:ÃaN2”ù4,’¢¡ '‘bY#b ¡èôá¸èŽAÞ ¡pÖ!XÐ áHÌ Á8ÌŒá8Ð !@ÖPÜá`àŽAp掀ì˜òa¨úáÁAÙÁé‘"!¢"1$#’bAÂP›$Á¢G™$1‚?#±d7•cAH1Âá2+’¢™ '‘‚Y#¢! áðø!Àì ä€ÜhÖ aXÖaXØ¡XÜ áhâAx掀ìŽá˜òA¨øÁÀþ!ѡ醹²êbà”)¨:\u"&‚ b%ÁÞVœ¥ºM¤‰˜D˜¤z<–£‘\5•#)F0ÂÙ4+’¢™"'‘ÂY$â! !øüaÐòÁ°êa⎀àŽxàŽ!xäŽa€èŽ¡ˆìá˜òA¨ö¡¸üÑêBo"f’c…ÑæXE1ÂN$™ E™‚=—C©f7£IP2‚ù>-“b¹.)’by &¢I" ðüaÈô°îŽÁ êŽ¡˜êŽÁ˜îŽá ðA¨ö¸úÁÉ!Û€º2Êrà•©èZbv¤g.j!fB^žå‘ÜTÄùºK¤q˜C£ù|<‘f6•ÃAR1”¢ùB-“¢¹2)’Â$&‘âI#‘! !aùáàúÈöa¸öa¸öÀø¡ÈüáÑAâ„~¦gzLq#F² e ÅùøZžeYÔQ$ɲH$I’Acáz;‰f6ã9V1”ÂùF-“âÁ6*â‰('"Y$b1" aù!éáá!áaé.irÄ*hrˆ¦ÇŠTr£æÂ*f¡f[ŸqÜR¤ÙºIš¤aœB™$†=ér8ƒa`3•ƒP0‚á@,¢©2)Ây&&"Q$‚1"‘"!â  Â Â ÂÊ*¨‚€§g¢^s¤†Ú4h"* ]…æSDùÂK›D¨EÄ!’?ƒÉ~:—Cl6CAZ2CL.”BÉ>+‚¡2(’ây(&’BY"$’A#‘¢1#‚)"‘b*²‰)ˆ~{&‡JRo#æ’*d!eêYžåQÞQäÙÀJDq¨Dä”?£É€:ƒn6cA^2ƒ P/¢ÙD,±:)“b‰2'’âq,&’¢a(%’bQ$%"J ƒ(‡Êrv%ÇHj£&R` ¥¹øW…1ØOœÄÁÀIDiªCš–?ÃÉ„:—£r6–£Id3ÃV/•"éL-bÁB+â¡<)“‚‘6(“B2'©GÚ|w¦gRl#Æj(b!EÒY%QäQ„éÌKœ$‘¶FÄ9¢Aƒñ<˜c©~8—cqp5£9d2ãX/•BéP-”ÂÉH,b¹D+(g’ps%¦ÚFh£&: _ å²WŸ%AæQ¤áÎKD‰¸Fä9¤AÃñ’=£±‚9Ãyv6Il3c!b1ãZ/•béT.)ªzt¦FòPj£Æb*a¡¥Ú ZåiòSže ÜM±ÆH¤Y°Cš„ž?cÙ;˜ƒ¡‚8—ãqx5—CIp3–Ã)h2C„v¦çZl¤†‚6d"†\ Å’VE1èPÄÑÒJœd¼E›D9ªAš#ùœ>cÉ;£™†8˜#q~6£ZŽx'§:fn¥FªDf£F*&_!¥º X YôRž¤ùÞMD©ÊHa¸D!ª@š#ñž=™ƒÁ”;¡Š9)‡ª„u§'`m%’Be#F&^!¥²X %QöR¤ùàMd©ÌHœDi¾Ed1°AšÄ¦?#Ùž<©‚~s¦æú^k¥‚Bd£F&]¡¥ªW %IöQžÄùâM¤±ÒI¤yÄFäAºCD°@)ǪŒv'Ç*ln¥æªNg$&:4`"eÒZ årTŸ…!ðOždÙàL„¡ÒHœÄqÈEœ$Bª})‡’Šu'§jm¥ÆšNf$&*4_¢…ÊY¡rTŸÅ)ôPžÄéæM¹ÚIdЏª‡º–w¨‡Bxp&¦Ê\h¤æR@b#Eò(\!ÅšW ¥RS¥ôOžäáèL¬h*´~ªGª”v¨g*vo&†²Zh$ÆJ@a£Eê*\"¢X!b T %)üQ-HRÂ+'Ò¢y)'R‚q§FÚfj%¦rLd$&6_"åÊ&Z¡å’W!%Zàˆ-:¾ªçºžw©B€p'&Òfi¥¦jNd$F:_£%Ú,[¢e¢îŠ­ÈbÊ‚+§âªz)ÇjŽr¨òrl&f’Zf¥&JHb$& :^¯¨Ò舭ˆJÈ€«‡Ê¨y)§RŒr(òtl&†š`g%fRPc0ˆúö‹®HzÔƒ¬gú¶{ªg‚št¨Ç€n§fÊnj&fƒ’°HâòŠ.(bÒ‚,Gâ´zª‡zšt©†o§æÓ‘0Òðˆ®RÐ,'Ú¶z*§ržt©g*Œp² ;¯èºî‡®BÒ€¬GÒºzªç‚¦u±é+ޝèªî†®:Ô€¬§Ú¾{1É ¯è¢ð†®H:Ú€²©K°èÓ‰¯hjê„2É;1(ÓŠ3És.“²) Lš4 sRš L €@$@@ €@ @@  @@ @@$@ A€@À 0@  @€@$@@€0FÀä0 CÀd0 @$@€@  BÀdp9 €Ä`0 „@„@ „@! €D C‘Àà`1 €À` „@!„@!€@ D‚Q ”X,A€ B€D  G€À`0„@!„@!€@ D H$ ‚A X,A€€ €D B€D0 €€@!D€@!D„@!€H% D‚A H$ ‚A`° €@ B€D À`` „@ D„@!€@! D‚A H$ ‚Q ”H$ ‚À`4:€D0 B€D €P(€@!D„@ €H$ ‚Q ”H% D‚A ”H% ‚A P)‡Â¡@€@ BÀ`   €@!„@  H$ D‚A H$ ‚Q H$ D€@! Aøx(€@ A€„@ €@!‚Q ”H$ D‚A H$ D‚Q H%D„@!„P(Bá@ P € €@ D ”H% D‚A ”H$ D‚Q H%D€@!„@ @¤ P T(€D@@ „H% ‚Q H$ D‚A H% €@ „@!„@ „P(РP(€@ @ P) D‚A H$ D‚A H$ ‚A„@ „@!€@ „@ €@  @ BP B P8 €Q`°H$ D‚Q H% D€@!€@!€@ €@ D€@!D@ P(E@ ( €P 8P T‚Á H$€@ D„@!D€@ „@ „@!„@ ‘@¤ A€à4 €@ ( B@D„@ €@ €@!„@ €@ €@!€@  A‘@¤ŽG@€P €D @ @ €@!„@ „@ „@ „@ „@!„P)E€@  @€P€D E‘€@ €@!€@!ˆD"„@ D€@!„@ €@!D@  `0B€D  D„@ „@!„B!ˆ„@ „@ D„@ „@!E€@  €P  C€„@ D€@!€@!D€@!„@ €@ €@ D€P(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€@ „@!@@@€@  CÀàp)D„@!D€@!D€@ D€@ „@ €@!D€@ „@!D@@€À  €P € BÀ`0E€@ €@ €@ „@ „@!€@ D€@ „@!@@€@ A@$ A@ €À 0 @€D B€@0E‘@ @ €@ €@ „@ „@!€@!D€@ @@ €@  A@$€@ @$ A@$0 €À`0@€@ @@$ B€@ B€D0 E@ @!D€@!D€@ D„@ „@!„@!@€@ €@ €@  @€P € €D € @¤@!„@!D€@!D€@ D„@! A@$@ €P €@`0€@ €@ @ @ „@ „@!€@!D@ €@$ €@ @B€@ B€@ @@  ‘€@ „@ €@ @$€@$@ @€@`4 @ €D B€D@@   D€@!D€@ @¤@@€@`0 @€@ B€@ @E‘@ @ D„@ @$ A€@   €P € €@ €@$  „@!E€@ A€@ A€Ð`0@€PB€D 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€@€@ B€D  €@ €@ @€@B€D  C€@ €@ €@€@0 A@$ @€@ €Àd @ÀP €P À 0AàÐ8@€@ @ 0 ‚Áàð8€@ €@$ ƒ@àP €@ A`x4@€@ X$ @€P @€Px4 @€@ @AàÐ`4 €@ €@  @€@ @@$ €@ A€Ð`0A€Ð@  €Ð`0 €À`4 7777BUFR4b€Ë4Ë~hd!]ò}L!Øb¿°V@Ü à.ÿÿdE¤F àÀÌFÔ €(!I¹¾@ € €@À À@À`À!Aa¡@!@A@a@@¡@Á€!€A€a€€¡€Á€áÀAÀaÀÀ¡ÀÁÀáÁÁ!ÄaÄ‚Bb‚¢Ââ"Bb"Bb‚¢@B@b@‚@¢@Â@âAA"ABAbA‚A¢CâDD"DBDbD‚D¢€b€‚€¢€Â€â"Bb‚¢Â₃‚ƒ¢ƒÂƒâ„„"„B„b„‚„¢ÀbÀ‚À¢ÀÂÀâÁÁ"ÁBÁbÁ‚Á¢ÁÂÁâÂÂ"ÂBÂb‚¢ÂÂÂâÃÃ"ÃBÃbÂâÃÂÃâÄÄ"ÄBÄbĂģcƒ£Ãã#Ccƒ£Ãã#Ccƒ£Ãã#Ccƒ£Ãã#Ccƒ£Ã@ƒ@£@Ã@ãAA#ACAcAƒA£AÃAãBB#BCBcBƒB£BÃBãCC#CCCcCƒC£CÃCãDD#DCDcDƒD£DÀƒ€£€Ã€ã#Ccƒ£Ãã‚‚#‚C‚c‚ƒ‚£‚Âッ#ƒCƒcƒƒƒ£ƒÃƒã„„#„C„c„ƒ„£„ÃÀƒÀ£ÀÃÀãÁÁ#ÁCÁcÁƒÁ£ÁÃÁãÂÂ#ÂCÂcģÂÃÂãÃÃ#ÃCÃcÃãÃÃÃãÄÄ#ÄCÄcăģÄÃÄ䄤Ää$Dd„¤Ää$Dd„¤Ää$Dd„¤Ää$Dd„¤Ää@„@¤@Ä@äAA$ADAdA„A¤AÄAäBB$BDBdB„B¤BÄBäCC$CDCdC„C¤CÄCäDD$DDDdD„D¤DÄDä€d€„€¤€Ä€ä$Dd„¤Ää‚‚$‚D‚d‚„‚¤‚Ă䃃$ƒDƒdƒ„ƒ¤ƒÄƒä„„$„D„d„„„¤„Ä„ä…ÀdÀ„À¤ÀÄÀäÁÁ$ÁDÁdÁ„Á¤ÁÄÁäÂÂ$ÂDÂd„¤ÂÄÂäÃÃ$ÃDÃdÄäÃÄÃäÄÄ$ÄDÄdĄĤÄÄÄäÅEe…¥Åå%Ee…¥Åå%Ee…¥Åå%Ee…¥Åå%Ee…¥Åå%@E@e@…@¥@Å@åAA%AEAeA…A¥AÅAåBB%BEBeB…B¥BÅBåCC%CECeC…C¥CÅCåDD%DEDeD…D¥DÅDåEE%EEeE€e€…€¥€Å€å%Ee…¥Åå‚‚%‚E‚e‚…‚¥‚ł僃%ƒEƒeƒ…ƒ¥ƒÅƒå„„%„E„e„…„¥„ń充%…E…U¥E¥U¥eÀ…À¥ÀÅÀåÁÁ%ÁEÁeÁ…Á¥ÁÅÁåÂÂ%ÂEÂeÂ…Â¥ÂÅÂåÃÃ%ÃEÃeÃ…Ã¥ÃÅÃåÄÄ%ÄEÄeÄ…Ä¥ÄÅÄåÅÅ%ÅEÅUÅeÅuåeåv¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ff†¦Ææ&Ffv†%v%†%–@Æ@æAA&AFAfA†A¦AÆAæBB&BFBfB†B¦BÆBæCC&CFCfC†C¦CÆCæDD&DFDfD†D¦DÆDæEE&EFEfE†E–E¦e–e¦e¶€æ&Ff†¦Ææ‚‚&‚F‚f‚†‚¦‚Ƃ惃&ƒFƒfƒ†ƒ¦ƒÆƒæ„„&„F„f„†„¦„Ƅ慅&…F…f…†…¦…¶¥¶¥ÆÁÁ&ÁFÁfÁ†Á¦ÁÆÁæÂÂ&ÂFÂfÂ†Â¦ÂÆÂæÃÃ&ÃFÃfÃ†Ã¦ÃÆÃæÄÄ&ÄFÄfĆĦįĿÅÅ&ÅFÅfņŦůÅÖåÖåç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç'Gg‡§Çç÷&A'AGAgA‡A§AÇAçBB'BGBgB‡B§BÇBçCC'CGCgC‡C§CÇCçDD'DGDgD‡D§DÇDçEE'EGEgE‡E§EÇEçFeWegGg‡§Çç‚‚'‚G‚g‚‡‚§‚ǂ烃'ƒGƒgƒ‡ƒ§ƒÇƒç„„'„G„g„‡„§„DŽׄç„÷………'…7…G…W…g…w…‡…—…§…Ç…ç…÷†††'†7†G¤—¤§¤·¤Ç¤×¤ç¤÷¥¥¥'¥7¥G¥W¥g¥w¥‡¥—¥§¥·¥×¥ç¥÷¦¦¦'¦7¦G¦WÁGÁgÁ‡Á§ÁÇÁçÂÂ'ÂGÂg‡§ÂÇÂçÃÃ'ÃGÃgÇçÃÇÃçÄÄ'ÄGÄWÄgÄwćėħķÄÇÄ×ÄçÄ÷ÅÅÅ'Å7ÅGÅWÅgÅwŇŗŧŷÅÇÅ×ÅçÅ÷ÆÆÆ'Æ7ÆGÆWÆgãçã÷äää'ä7äGäWägäwä‡ä—ä§ä·äÇä×äçä÷ååå'å7åGåWågåwå‡å—å§å·åÇå×åçå÷æææ'æ7æGæWægæxhˆ¨Èè(Hhˆ¨Èè(Hhˆ¨ÈØèø(8HXhxˆ˜¨¸ÈØèø(8HXhxˆ˜¨¸ÈØèø(8HXhxˆ#È#Ø#è#ø$$$($8$H$X$h$x$ˆ$˜$¨$¸$È$Ø$è$ø%%%(%8%H%X%h%x%ˆ%˜%¨%¸%È%Ø%è%ø&&&(&8&H&X&h&x&ˆ&˜AˆA¨AÈAèBB(BHBhBˆB¨BÈBèCC(CHChCˆC¨C¸CÈCØCèCøDDD(D8DHDXDhDxDˆD˜D¨D¸DÈDØDèDøEEE(E8EHEXEhExEˆE˜E¨E¸EÈEØEèEøFFF(F8FHFXFhFxFˆF˜F¨F¸c˜c¨c¸cÈcØcècøddd(d8dHdXdhdxdˆd˜d¨d¸dÈdØdèdøeee(e8eHeXehexeˆe˜e¨e¸eÈeØeèeøfff(f8fHfXfhfxfˆf˜f¨f¸fȈ¨Èè‚‚(‚H‚h‚ˆ‚¨‚Ȃ胃(ƒHƒhƒxƒˆƒ˜ƒ¨ƒ¸ƒÈƒØƒèƒø„„„(„8„H„X„h„x„ˆ„˜„¨„¸„È„Ø„è„ø………(…8…H…X…h…x…ˆ…˜…¨…¸…È…Ø…è…ø†††(†8†H†X†h†x†ˆ†˜†¨†¸†È†Ø£h£x£ˆ£˜£¨£¸£È£Ø£è£ø¤¤¤(¤8¤H¤X¤h¤x¤ˆ¤˜¤¨¤¸¤È¤Ø¤è¤ø¥¥¥(¥8¥H¥X¥h¥x¥ˆ¥˜¥¨¥¸¥È¥Ø¥è¥ø¦¦¦(¦8¦H¦X¦h¦x¦ˆ¦˜¦¨¦¸¦È¦Ø¦è¦øÁ¨ÁÈÁèÂÂ(ÂHÂhˆ¨ÂÈÂèÃÃ(ÃHÃXÃhÃxÈØèøÃÈÃØÃèÃøÄÄÄ(Ä8ÄHÄXÄhÄxĈĘĨĸÄÈÄØÄèÄøÅÅÅ(Å8ÅHÅXÅhÅxňŘŨŸÅÈÅØÅèÅøÆÆÆ(Æ8ÆHÆXÆhÆxƈƘƨƸÆÈÆØÆèÆøÇã8ãHãXãhãxãˆã˜ã¨ã¸ãÈãØãèãøäää(ä8äHäXähäxäˆä˜ä¨ä¸äÈäØäèäøååå(å8åHåXåhåxåˆå˜å¨å¸åÈåØåèåøæææ(æ8æHæXæhæxæˆæ˜æ¨æ¸æÈæØæèæøçç©Éé )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'IAÉAéB B)BIBiB‰B©B¹BÉBÙBéBùC CC)C9CICYCiCyC‰C™C©C¹CÉCÙCéCùD DD)D9DIDYDiDyD‰D™D©D¹DÉDÙDéDùE EE)E9EIEYEiEyE‰E™E©E¹EÉEÙEéEùF Fb™b©b¹bÉbÙbébùc cc)c9cIcYcicyc‰c™c©c¹cÉcÙcécùd dd)d9dIdYdidyd‰d™d©d¹dÉdÙdédùe ee)e9eIeYeieye‰e™e©Éé‚ ‚)‚I‚i‚y‚‰‚™‚©‚¹‚ɂقé‚ùƒ ƒƒ)ƒ9ƒIƒYƒiƒyƒ‰ƒ™ƒ©ƒ¹ƒÉƒÙƒéƒù„ „„)„9„I„Y„i„y„‰„™„©„¹„Ʉلé„ù… ……)…9…I…Y…i¢i¢y¢‰¢™¢©¢¹¢É¢Ù¢é¢ù£ ££)£9£I£Y£i£y£‰£™£©£¹£É£Ù£é£ù¤ ¤¤)¤9¤I¤Y¤i¤y¤‰¤™¤©¤¹¤É¤Ù¤é¤ù¥ ¥¥)¥9¥IÁé Â)ÂIÂiÂy‰™©¹ÂÉÂÙÂéÂùà ÃÃ)Ã9ÃIÃYÃiÃyÉÙéùÃÉÃÙÃéÃùÄ ÄÄ)Ä9ÄIÄYÄiÄyĉęĩĹÄÉÄÙÄéÄùÅ ÅÅ)âiâyâ‰â™â©â¹âÉâÙâéâùã ãã)ã9ãIãYãiãyã‰ã™ã©ã¹ãÉãÙãéãùä ää)ä9äIäYäiäyä‰ä™ä©ä¹äÉäÙäéäùå JZjzŠšªºÊÚêú *:JZjzŠšªºÊÚêú *:JZjzŠšªºÊÚêú"z"Š"š"ª"º"Ê"Ú"ê"ú# ##*#:#J#Z#j#z#Š#š#ª#º#Ê#Ú#ê#ú$ $$*$:$J$Z$j$z$Š$š$ª$º$Ê$Ú$êBšBªBºBÊBÚBêBúC CC*C:CJCZCjCzCŠCšCªCºCÊCÚCêCúD DD*D:DJDZDjDzDŠDšDªDºDÊDÚbºbÊbÚbêbúc cc*c:cJcZcjczcŠcšcªcºcÊcÚcêcúd dd*d:dJdZdjdzdŠdšdªdºdʂڂê‚úƒ ƒƒ*ƒ:ƒJƒZƒjƒzƒŠƒšƒªƒºƒÊƒÚƒêƒú„ „„*„:„J„Z„j„z„Š„š„ª„º„Ê¢ê¢ú£ ££*£:£J£Z£j£z£Š£š£ª£º£Ê£Ú£ê£ú¤ ¤¤*¤:¤J¤Z¤j¤z¤Š¤š¤ª¤ºÃ ÃÃ*Ã:ÃJÃZÃjÃzÊÚêúÃÊÃÚÃêÃúÄ ÄÄ*Ä:ÄJÄZÄjÄzĊĚĪĺãã*ã:ãJãZãjãzãŠãšãªãºãÊãÚãêãúä ää*ä:äJäZäjäzäŠäšäªä»;K[k{‹›«»ËÛëû +;K[k{‹›«#K#[#k#{#‹#›#«#»#Ë#Û#ë#û$ $$+$;$K$[$k${$‹$›$«C[CkC{C‹C›C«C»CËCÛCëCûD DD+D;DKD[DkD{D‹D›D«ckc{c‹c›c«c»cËcÛcëcûd dd+d;dKd[dkd{d‹d›d«ƒ{ƒ‹ƒ›ƒ«ƒ»ƒËƒÛƒëƒû„ „„+„;„K„[„k„{„‹„›„«£›£«£»£Ë£Û£ë£û¤ ¤¤+¤;¤K¤[¤k¤{¤‹¤›¤«Ã«Ã»ÃËÃÛÃëÃûÄ ÄÄ+Ä;ÄKÄ[ÄkÄ{ċěīã»ãËãÛãëãûä ää+ä;äKä[äkä{ä‹ä›ä¬ÌÜìü ,<L\l|Œœ¬#Ü#ì#ü$ $$,$<$L$\$l$|$Œ$œ$¬CìCüD DD,DáGŠ ¨èX¨âàmÛÐÅ›†bö4Ê³× ˆsµã¸Õ€û¯HÆBOþ‚b(vÎÖP¨ANÂ@º«cǰ1¢A¨(wšÃó"!Kqâ +¨L¾ââ­Ô¤ì.ÙÁ„H ŽtgØcYЦèãôGU¼<`Aðˆð‚âD28"j()zÁ‚" Ãp{®„Jp%¸AH¶ à_ÆÃ4‚Vpè-‡®@Ø!´æÐ–æ(äÁT  ˜[îbú™­øÌ3—ä6oñÀùŽtwÃÓ3q9MH‚CöpKí‚ÌÌË éf$`Gì‚u°ap¸ž†0Ä4êá–ìP~,'¬"ïÁ%‰”XP‚›I³x´eEÙD0|‘w ÷ìkãtyê‹ÇŠª>!ý«Yð†1 ‰ý)0·jàˆê³„)ác- ò˜fXi úÀõiD ¢;ù¸øœ‘EÖ*‡aÏ z¬_:#ÅXÒ¾Ì 8±Î(ŽÝxzPƒíx D)êÈ}pÁðK–ʵ@è·èGÀÂtRS`¸..4ÕÁœæ}óÄ%ê"á±$‡‰‘8OèB𫍴&Å×P0láú ôkãs4rÐêS‡‰=øýGVІ@n(Jp)f ­zÿ„CÌ%†AGX `_\ƒ10;àçe‡§Ä@œ¢ Ùˆ•‰Dâd(ÅS¿ [¶"ø×Ÿ ˶“ü6RAÀŽm\ṽъeõˆEÆO 0%íÀÏ ±£fH9ûÂøâ œ¤Q¸-ìAŠ¡ ,¨p.÷ÂmQýˆ´„IÂcTó¦UEhö,øuW ìd#<¸ ÜއÀ:†ÑáÉz5¤ì! Ťt×DO‚Ý`Ùø¨ðD‚Vpeà°µ…òD2öᲫnˆz=ÄÖ!ï UN‹^3X°dE¹±û·‚U¤-Ó!Û1>H­|FE8òýÜ‘¤dš¶`˜ÎAZ Ò\ ƒLìÅá # ¸O³þRPÈ †¬48À!àõßH…ÃÄd$Ïq4 |SÀ¢¹Ÿh¸`Þ&/ÈÑ… ^dŸc2ŽÓfƶ\6Œ»Ž8r%ãžÂaàîsÇŽÄ=OQñDÀlµÄ A Æ¡ ¦HhXDò'©søR$xV$/I$ß B'»áD¨Š[,T‹Â±¿ùȳ3´¾.}‘z 4bãÚ[hÎ9ÆŒô5=±°· » oc‰õ¼@éFÇe\<æÝm8}ãöt 9fˆ>ZBÍ’6 ÄŠ¸„cW#‡Q¥‰RIžâSÀcÜ}`ŽßP(1>A¿Ú®Šê„Ã$)w¡f± ¸g2Ão~+0öÓˆ"lDqâ>ƒÍH™Â„é:(!qGÕ t,UP¢¸-X´ÉEÁ .á‘}ÉŒ$˜b×#$3Œ ÏÉÆ™B5£A³ãÕhpZƒgî@êÝqªC3" b‘9`‹€„i°#ºá!;É%JRVí I¼Ký"m|×°¢Å+¢*6AX{ŠùÄYzBÙe6ø½†Ø0÷ñŽp ©Ìfý£E€˜˜Ø#FÜ@7¸ÄŠZ8t„#±´ú8ó0G´¨>~qú° 4‚3!^1bÈŽ/©‘„ Vüdg#0Ìò0ÒöF²t6kAº#Žhqí£œçSÐíöÇŠÜ=0ð=¸8t$ 4 ·É #dpCü²&¯kÄÄvI$©$b >6JÎ]9“ Cn‚)´ÐþÄ£%îñ6A çðPñâ”ðP«ÿEzî,°¡l5‹—”^o t(Æù†Rò3o1¢C Hdkòcm&Õâ Ç+(:2qØPŽøÐyxãÙZ6 ýÈô@øÒªÌ‡ äEò"¹RˆÝºGÈ’E_„”®„³&™3ž¯2(ÐFÀ‚¡pjÞDÉÀE ^@¡@²„ÿð3^µä†Àzÿƒò¾ o! )ˆ‡˜Eèâ<ÚQØ•ó„Ê'-±@ 6ØSi"¨Ã± °íÅ¢b-ìAv çD`æC¬øËô†z˜4­ñ¬ –èni£€às˜æûGS;nâFG¸{ð#íÔqH+hB6’úà‰‰dYÞ#!÷„'(„ž?²"ÖÁf‰!€J¸"cT‡8Ÿ˜E„)“ÁSgŠÑ$X5BÏ$帺’…ïˆ0RQ‰\ €Èe¸£;?FÕŒÆÇZ7¿iŽ1˜s<§s¦ ð™ÇŸÂ=×Aõƒâ€Â„§! Ñ ³HxÀD¢R+Ü•Ž^䀊$ry&þÉSKrbsJHœ¤íÑ'ܱBJBVÀÀ´Ï…Ü0’a‘ô ü8k:tö}Àꞇ‹@>!ýÄY(†.L2#:Á †‰:€K€"i{¸p¡"$6)ùQV{ éÀYbÕdð¼EûÖ0·áŒ| š,f}ƒAfwÈ×#Ô 7y‘Â}IÐtã­šÙhò)Ǭt>9±ø—ú ‡dÎ!; =…rEÂ.ü®,*†ã$¤y(ˆ‰_ÎK×¢e“bäœÏÄô*( éCà€ÅDÙ`LEÍ”Ë éM xGÀÂY,Ÿp£ÅS¬,Ioß êb´Ã0h\pÙ”ì9ÃÛ}G¸}¢„Ö!áoˆ±ÈG:bGf¦8˜–„ߨ'ÓQEWŠaT·‚³O€³Š…·z.“q{L Hb4£8e΄ÆL5R±YÁo¸‹SŠ鋇g‚<±çgq„}>ƒ÷n (B B/ÄT0™kÜ)¹A[+ D`]‡Ã0Ï:¯¸70!Æâ¢xuƒÞnÌ¡ˆ^øD£â2²˜“i„¶&ˆ:ýŠ8R!ži_®]…à-Epò ½x_ž# k¾`ÉWÆe²4Á¦ó mämCvT8ä^‡>Dp7‚(¼ž ¿åÖÈ:ëš2àˆ`„xˆ%pa9 3¸U‚¤îP¾#'d2çA¤Í ‘poãÚD¨0ô ÕÜ@aBkïðŠ×Äq€$e1)Ú …MØ"|"Là¥ËÅI +#Á_΋4\[UBè$¬(ÀÆ!$1âQ•ÏŒähÒcT  4îÂ2ç ~„%¸"à!$I ŽàOÏB™ £³ÉÔ”0PÁ ëÐj¶ÃpÜ\ð鱃 =Äû·Iˆ…¤„HJ#aŒ‰1èKA¢gn¨ žÅ¸)ÕaUtŠáŒX¸‚Ó>ˆ»’Å÷¼0“ñ‹\Œ‘0f8Ã?Y8îÁ-^ f2ƒgàèôÓ CñÂ:P¨œô*[Á`q n^ÙCŒbpÑÝÄl7ÕÁËöÌHyºƒèú ḈsHEFb7Æ)8”®„Àh&ÙQ=‡Š"ˆR£}‡¸¯¢…˜-–Qs| ÑÈ`@£ç1JÁ¥|ðvÀìL!Õ @L2B|T»Ð¬šÄ.!Œ whg SÂt âj‡HØ;ù!í*ÔX‚„*þ"1!9ˆøIlâXú2Øœïl(ì‘N!ЦVãÂı’ ·ï…Ú>/©Q‹¤Á@ËkÇð9—Áç€0‡ZpT%56ð $Ty¾ŠÍ€½†02¥¡¢Ž o`ƒ•Ƈ`òë‡Í¨@bEÝ0ŠNDm4$BÑ(Ç | MBz”+§j™ ¿ÐacC%ª€Öä†íp9¡Öˆ|Dƒüæ Àa ³ˆ› F‹bA¼zx—,ÔT'zAB‚ŠIüTA;”àH Â[ ¯¤BW0,kápÀ ñbí2*g`Ú‡¨9ÙaÜ^Mø}È f!"Ñdz¼GPBGã«°˜¯„àp'ܱE¢„c $ÊÁ3· ÀSªÂ¸–лg„28AŸ# e°nƒ"O ñ)‡¿˜?¨â ÚÁ؉gf^$ &ø‰nMÂv_ÈGÀÂXÈœP£¬Rä,CAo” ç¨b¢/ÒT ÙU‡ø9³áÛD˜}}ª!1¯ G$‚F· À˜jÄÝæ'ÆÐ™E„ÿ°)©¡Zà B]hƒj `Îû†­`7aÆ3ž0xIÃÝÁ±¾[tDŽ1Ñû “1D´L&{œL…è*q¡`î rx^òCTkÐÒÅü7ÜÌsΠyÍCé "‘ÅttEL¢7ø*È”»Àšì¦…f,â¡tv ¸cÎ9–¢ÀÛÆ‡€:Mßûjà~¯D ![‰”GÀÂKgÇÐQ3‚¤^ü ¶‘…é¬0ÿÁ•_ lÃ{h±Pì;˜$>mBËrð†é„Rr#i¡!ý‰F8)^¡XV -¸\ÆVàÀͶ†£86Ì!é‰àw§C×ü•ñyQLD6‚/Gå@’‚B¼È¼P¼Ÿ†€2€!¡” w¨oƒ“nt òU‡È0?ð çÓЉöÄjx$,ð²,…ǰ/題® ÐxiâCiÔ$°çïtü=Tø3, „½D@¬"Þ‘¥ª.ü!…B •h ƒ[.¯€ä,‡W°ÜÖ͆d¸2Ýá”É •àd%ƒDÆ0Å7†!$0Ê¡éñB@y¡ƒÉˆ0 ð¬‡~\;·ÛúÑÀvC­6Mér‡DŒ9éÍT\raC†\ðááœ7ûA½æ ÞÈnyCo~\°ÙÆÆ`5ë!­3 YøjFCNJOðÑ…†ƒø3áAœä K áƒûìÃ@ý9âÀ>àáõEœ|vƒà0â@ö1ªä=¡ç*xÓCÃûîÞo„;:ذðtþ¤:ç/Ø9@AÇÜ.xpêCƒjú€ÞÚî876a· ­m.„`ßùˆTÐBqbâƒÄœ ²Q¥ @»â6Ø€&Cý®ÏÀýåà>ðõŸ8||Ãà0â@ö$‡¨ð= æm"8x”ÃÀŒå î ‡h|;AÖ ˆtƒ Ræpæª1h9o!ب¾8Џ„R¤"z‘óˆøDR¢ ìùP‡Z7°!¡a ˆYäBšõ‰˜ƒâ„^ »±üˆ @ÅBhØ€&CýËý^‡â\>×ô–•|ÝBÉ@õi£<ÝAå ¨xÆÃàøö÷D @Gà=pÞ8ŽDp¸#lÁ‘ˆÍ„F3â0rh‹"ÄU’"’¢ˆ– D{B!æX‡˜„8Ü!©1 \[B"õˆƒÃD ²Qˆ,@¢â< @ÛCûV»pýw‡çÔ@ç¢**H••Ä©Ž%3q(Ç ?”IÇ‚L¬X’V„0$^ñ"/‰ tHb?뀎ñÄs¦#‚¡4ÑÐFYb0ðwà‹HDVZ"–±»ˆ•¨Dqâ!‚üp‡Z7~!žA øX¸BŠbxŒ¸‡„::&Áá5G £øLíÂeÞ à˜£Áø%öÁ.á‰pKKXȸ0•Q§%‘( 9TI’bJÑG°‘Íв$6Q шþ¼G·b;ùÏ`ŽDl#A'ÁÌEÖ",ïX ŠZÄPâ#KñûŠ ÔP<¢€UöðŸS„÷|'¢á‚X±(•„¤¬%±'C‰2°IWHÝ9 ‘\„‡.$Q "ˆý,IZ"™n¾(¥E)|)2áHÛŠ@œQÒâŒîZð¢mE|(iQB‚Š P3B€ ò@Ÿ!„õˆ'“A;¬‰Ö\N}Âr,À›„Ùh&¬4 ›ÄL¢Âc; p—ç„»¸%Á¡-, c Jãâl›]ЫӅ[|*ÂáU[ФœTòâ¦9%H¨ÆEC)þO( sSf™‡¾(¥‡)).1H =|Q¹âŒ Q¢ Ž(PQAˆŠ

'»á=b ç(O"wï¸p‘„êÊ'HA9m‰Å,N Bo¢v›„„Úb&ÅH =QÉ‚„dP¢ä(œáDj 0Pß"…ÿ)¸¡ET('±@Í äPB~aìŸ'Ä÷à'°ñ=#‰å˜OÂwï5(±K…‡n,$`dŠü€Wµ"¼Õ®JÅo6+`±Z1 ËHV(B¯²q«EU *’qSËŠ—TT‚b¢j§ñÅ=þ)àQN’ qpSlBšÍȦ/Å/Š)oÑKŠT$Rˆ"“.’h¤[!(÷GKŠ7*qPT Sãž7ì@§07¾)³M'‹-¸YWâÉÞH°² E ,jQbâ ðX€BÃ:°^’+þ_w ø˜W¨¢¼KÜ®¢EsP+Ž[ó ÜVÇ¢µ*¢H¬àEdÜ+ÑXV ¿,Uã‚®Tl`«7EWø*´ÑU6 ¦ôU¢¨;ð©§EKÜ/&Qxƒ‹¾@]ÆBì»Zèºy…ÐÞ.orÀ‹`\L"àÑúзr…¸¦-¬1l™‹aHZî"Ö¯µL…¨¢-7iT GZ#Ð7z°³£…›(,ÌÁf ,(YKbÉzD±î|,]ÑbŠ‹lXgBÂr À°%Ål+ëÁ^íŠóèW€»QÔH®jr$+ƒ[Á Ú„V¾Bµ*£¬ìÅeÖ+#ÁX­ŠÃ\¼¢å"¨¸ÝÅŠ.ApŽ P[îbÞÄîP·@…¸¦-µ‘mU g$[Ø ¹øµÅ«,-IÁiö‹KÌZBBÑJ‚€³ÛÅN,ÚÑff 0YaBÊBJ@²… ,kábXŒÂ÷°°‚¾, q`Šü€WÎ"½©çЯ †ª0‡qƒ€ ˆ`ˆƒÍ °¿þý/С}Ö èÔ_Â÷.­¸½…æ¾/'áxô Ä^ ïwux»€Ú .Åqu» «]?âéB°¹ÝEÍZ.^Qr‚ ð\kbâz ¸4EÀ-òo#‹uü[¢Û¤Öà¶x…²-ƒ‘kŸ‹YÜZ²ÂÔœž ´¶…¤$-1hA ?°YábÎul ³?…˜l,µQe_‹'ÜY)È€>ˆ¿þþ’/è~é ô(_…"ûzÔȾz…ò/…¡{É ÚÄ^ÀBõ!£¼Þå`/ xƒ‹Á`]îâî–o8»;EØ|.µÑu1‹¦Ð]BçÙ8ˆ¹…ÅÊÐ.HqqÆ‹‹\<‚àê·Ê¼\-Ùn[‹p„[hÚªÎH¶LŰÖ-z1kz X°Z²ÂÔµ ø´ÕÅ¥P1þqDŒt¬cy£oÈˆÅæ†,x1JÁ‰šŒG\b éjÈÃ0ÆÄ0´Á…B &Œaƒ,;èÁ§ Ú0RQ‚" T`Vƒ ÀEÿZ/ìÁ ôŒ_Ž‚û¬Õ¾€Åò/…¡{¼‹Ú`^ºôï p¼Ñ…äÊ/Ñx^ ¿]ßBîhø»ÅÖº.§ÁtÚ £°]¢çu60¹…ÊÐ.Jqì \L"á˸E¿-ïao h,c+ƒ”¿(ÅÇF,Ü1XÑŠ{ŒPXbißޏÄJ Ž0ù‡[Œ8 a¨ _]€Â³Æ@0“ñ„;Œ¼`ÙÃ)(ÁF\0+AŒ,`bÿ{ô¿t…ùâ/¿q}¤ é8_-¢ø¥½X½²…ì/Pz9 ÎD^_bò3Œè¼5Eà.÷qwW‹¸d]§ì¢^кÑÔø.ža›«Œ×äf“c3=ŽøÌ F^F2ÜQ–3Œ¬$e2C(5èÉwJŒ2Gá‘猌dGã!@ÈF>¢1ê!ŽàŒtHc‰C›Ö˜Æ|†2†1†!‹ÙŒ[HbÁC[£ÐÄòÆ%Ô1#±ˆ­ AäaùCéqÃP¾0»…[ (a$ÃE;èÁ Æ D0O1üŒ Ä`JVp¿ñ…þ`/æ~Ð ô(_ˆBû¬× ¾“…ó /| ÞH^ÙBö4«`½5†kò3V1šMŒÐfg£2uŽøÌEÆ`Ð2ú—xŒ¸¤e²c,²`àÊÎÆU2œA”~ ¡4dð£&Ö/¨ÉQ†Hü2<ñ‘ƒŒˆ˜d.ã –ÿ8ÇÁ†<®1ØñŽJŒo˜c`£V˨Æ%/Ê1n±‹ UlbC²•ÀÄ|"P1ᇿŒ:xa´ƒ Ü_ÂÓ:0Q„ŸŒ!Ü`ü#4ÁnÆ J0EÑ㌠ü`M#¡XÀFš 4¿!¥V% 1âQŽ» qðcvƒÓÏÔÆŸ‚4ï‘§16li £L=]8Ò·Æ”’4˜¤u ŒhîƒF¬0°ÑS†‰>4=q¡” €h9C@é˜Ï܆}T3áAž™Œòtgwƒ; Ñ`Îe†qj3~Ñ›Ÿ ÙtfµÃ4Í ðÌÏFdê3a˜rŒÀØeçƒ.tn(Ë9X86Jq±½ˆØl_“ñ¨×a†¹à5Â­É k(kFƒYlưÖ†®ð5k« UHj—ƒSôšðÔ¥†¤5ñ¨D?iåcNclØÓ;˜H4¸á¥V(\i&ÃH‡=øÑÄŒ4X¢hÀhkCByPÐ:F€t3÷!ŸHŒ÷ˆg #£‰FE€áúr8gÂíŽLp£ƒµøà•ÇP8À% ý¤oÚc~ è‹GBü:ÐŽ~ðsÞƒž^î@çFG99»±Í’ŽiØs<™ÄØåôÇ.z9gQÊïŽT\r“C“Ò™àä#¼9aÈ@>àqá#ŽAmXã8Çš8¶ÁÅkµ= ‘ì­czÿƒ×f¶€õˆG«=L1÷9·Ô}¨Ãì°`úáÕª>£ñôÈ£è}£çµ9ÀùœËæ>TAòWü|m#â¡ øWÁ¾?šÑü˜á ~ýc÷Uµ0ýŠGêô?NAúÎà~^òsŽ üK‡á0>þ‘÷©º}ÁÃí_fI@Hr@;† @€<$d q‡ÿ?í¡ÿ.ö¸£üiß`þÉõN?ñü¤ã0‚dt w¹˜Hd@ÓRC0$kD Ý P©f@…2Þ8€Ò$â *a'H5A¡’ Îc´ƒÚ ¹Y«ˆ+þAX" iQX‚qÄ “Ùs"lAr•´„šä$Z!YˈDþB’‚„äx ÷Ù“;žAÐrEÄ”†$0!|© ¿È\ BÝ2’²œ…+{!W) ˆSrB:òH‡|d;f!Õ¹ŽˆsCâ<߈†ì¤6¶!±É bˆièCEâ#æ‘xˆÔ$F$"+©>ˆ’D<Â!Ž‘ ˆADA["ñ È¥ÞE(²(îE|ŠP"}±ÁÈDݲ&¯3 ‰ƒ$Kœ"òkHº`EÉ¢.‘m¸‹^$Zt"Î)R±2EÂ+¶‘¦\h„#?qÏÈͶFbÂ2ב”ÈŒdd#¹4ˆèªG:b9¡ÊLŽBÄq™#‡QHß®Fô?ÈüLϤ~#ê‰5ø®Gº‚=¢)œ‘:$‰;$Eñ" VHrâC?’Vˆ’¤¤”¨$ŸÉ$åI%ÌI("P0ð“餞Ð$ò™*óIVnJ«¢UÚ–¾¤µ‘&Á0^Éšfñr w 9ˆ,tUÁ€áÀbp20¨ †êx€ÈÀfð5P8 ì€ñ‚€ÈÀj 6Ð8 :~Íx€È€f€5P8 F_F€¥`Wà.PÐ Œ¤nÃÏ@ç vx;x„r[5€£@U`+ðˆ pbUÀãÛ n¸8X,xR·€Š@I%° 4~è}ÈÀj 5p¸g!œàÐðjp6Xd#µ@}ÀD°"Ø ˆºh@¼@d`2 ¨ Tî«ëÀüƒàEà% ” .I½k@»àa1 ²yS¶àÐs¸(Üï)@–ÀQ *hø Äþ#‘€Ôn :ØRY9@¢àT+Ø´  ßø„ÆÀe¸4œFjárà>à øH lø_€´@^ 0( ø¹õ@‰àH%˜” E¸Áa`µ ZÐ.LÐ IaQA¹èPÈ ´µ÷€’€K`(Ðð üÌñ‘€Ô pà;Xtžz‚Y6@¡@S*Hœ ö¥ìA„@É h¸6°ºÂaÉ€i`8 pÀ¤¶pF€¯`Y@/ T¹õà‰`H%”h î‘AR@¯@[P0dŽ N烚Aâ %àð &Åî}ÀE#Ø 4dÑx€ÈÀj 8„¢:O?Á) —`NP'p\ «¸žÀØÀph:b#/—€X@,À0 à*î@–ÀQ +˜D ÄFU»€éà|AP!èn ³h?`© [h1(è œ žÝ`ÐP¼^a°€dÀ7`¸¤y[F€¯ÀX/, Å“â@ûÀƒ D°#€† Ã*‚ÎA„ Í@jp6È,”_€4 Ð x¼ÂaÉ€q@:`pH 2¶V€¯À^ 1°( T¹òÿà„pF€%Ôj ê@Ôàmx8XXà™b2À ð xÚÂvÉ€q@=0pH lÀXÀ± ^ 20 Tû—Þ€ý ‰@J (œÖ ~ƒ–àÒlÐ8xÎq€L€2À Ð x¼ÂaÉ€hà8 pÀ¤¶dF€¯Wà/à ŒªeÆ€õ …J((€l ?Û‚óA„€È g¨6LvÈ ‹3€%à  °èþ^6°€a`2`P(Ü/)-€›àQ +Ð$ Ä #ª€ß w Ap$ â u‚Ök@¹ `X2 ¬’ˆhÁÒeAÚ°8d–Ð~€D %à L&®€â€}À?Ð"Ø l¿i¼@a€20¨Lèq‚jÁEÀ© W°,dì Y"A¤`Û€ˆ¨‰™(ê€3€&@0 °è{R€±@Z2`p(jï)À–ÀQ +˜ø ÄFUÀ÷ ˆàK'0\ ’{½^À¶`a2\† ¶ OŽáÆvá³08d˜Ì€tÀ?@$p€ üˆj»€â€q€>à!H l¿o@Á@j€8pøª ·‚sEÀ© Uh+X` ¾ƒ+A©@õ0²hưRD7@&@` °Xê /°€`à2ÐÐ î–ÀQ +ðˆ ú¹‚ AÀ“@LÈ'8b ’I½l ¼ðbp2Јl ƒò„pàP àD¶–Ü~€OÀ20dSÅû€‰àK`&8h ,?tÜ€û`„pG%˜” ý¤]@´à]@0xÆ å°„ p«èž+¡€L€2 ` ´6‡€É€q ;€!0Ø ˆ¿Œ€Ö@s°>° è<ùZÁ9@¢àT¨,èö À%¥@ÚÀp°VÔþ‹g4À&@ Xô)/¯€X@2`hHêÒ‘_À½Àh7H,h$' @–`Nx*<ì m߀Πj6Èd ý€"€À àpÈh–ð~€KÀ, X(ÜS`H€¯À^ 2H T¸òA‰àH0ÄÎ ^ýA¡Å m  F–(€€   ÐpÈh–ý—€Y3X¸¤¶O€¯À^ 20( T¾‚r¡ûÁ\hóÔ~ 1ÞÜ‹†i¹Ð8Q*Pi‹‹yBÛÔ0ª$7Ò? U ”pOœ'èÂc„zFtà]˜-%X§ O‡î…é$ ñБ54ŒÊW"m =#x£pãM”.Z €…mÜ"*12á,ŒÐL7²~‡ÀÃz¡W ­¨T0-²C ‘† BY!F0¤¸ÀÔjg3€&@p è d„/Åû€†`E %°D 4~S…¥ÂH¨Äs¸AÁ#W ÈüƒØaDZT›‚3:—‘V†²GÊŽðÃ䪺#g ÛŠ½ˆ°ãßJÂY¬ÊZm­(]Œ‰EAÞ€ß(h 3î~†¾jÁs ¹K”<¨$c1;ÇÚ±éHøÐ†ü8ö€Š¤»Çð2Û¥ù {‘ˆjEñß(X±ypÂLª4'1¢sTm‰9Ôª†ÌéLYÀ‰œDŽÂ  @ÀO’( ÑwÀ`8¨œ ´€h@Aà+@0 é €ÀKpA >81Àà ,‰1…àíq*ðÿä©®K™7»£aMDeH§Ýtí–£°£êÎþãïPâåóÈ ‹þ5x‡—’ÓÞ/³=MÐÀEÖçH<£_!l¦üm.EY!‘/T¡ÉPÞøè¸ 0¯žÄ ƒuÃAZü÷Úuå?½–ÞëÂ~)—(F¦3i6é9ÒéɘÌRQüSœ0Tÿ'[…%£i”Í÷“! ‹ù·Ìœ^Ì¥´ÉùÄËœI}Œ”Ìðe|È×8–Ξ‹BÀ`P4æZ》€Sà, 8 ˜©÷KBÐ0vhtp-2,ç"ë•úoâXÓ¢†G :Š[F%h™RGë >+Z´i÷­Å<%í2Gf3̆"G+¸¨ÕPÌÞU•¦é;»ÙÚà ŒëÅ/a btÏä°ÈÄ7C0ñš°Ì“HA+8† Iì1c!‹NÂ@e3Ñ hq,A:<3‰“óPh¬¸VÂ,È%)g …@`pøê‹ý‘@L,  ²&ÅoB$ÉÒÏ2,@½2ER7å·bß[Ò²‰œä¸þêTvõ¼ã]i°U‡â;Ð òiŽˆlо&ÇZjì—È:íÄÁ¡1ñZ‹äJ “0cyñ;Nß“¹ eZH’ÝJô“3È_C~Å2âtŒÇ4@ö3 § 4åór¦x„À Ò—6Ëòîr»ÐôçAžHËð$yÆøuK²Á͆é8xX»Ï\Ó/›x®äw*;ñ9““y»)á'Ѵ׃s‚Ùó§³~8G ååóGÞdiuü~ã&uEEfIßʆÄêêtª,Û‘øÇçð6™ðÜ­®I"G.Œ) 0à !Ë~€D %àð Â`× “û'2ô™¥FbŽÅpYºBߨQT© Ñâj»'ÉŠnEIVíþçp«ˆ‘ÄÆúHá¨6Ô¶šû½¹É8þ¦…°6›Üùïy'§”«ò[Õí}QÀ¡Q­ö')óõ:_Ví¡€eÆå^¡noæÏQ˜tz\goŠ'hPŠ¥Àæio¹y™7g ²@ZàFÐ!è 6OŠ8÷™!Ð=¨N&®¢E= ʘ*gU´Æ!uI˜¤|ÄÂtAÔ…IÄ›\e×$Kz=ÓPÿ¤†HGoæiÕ»h n½Ùxüèo77FÝÁ„ùŠ\ý~ï 3uÑ»‚Ûhm{geœ2Ò H×Û4AÉØwü¥¦‘lDuú±b⥊§Î³rúv5@„šËEØ À XÂ$/üA¾pg°;hˆ z Ô“k_ÓN‹v +,Q²æÓÚW ´žL+¶òÛòjAEœ—Æ`,Ð×—ž»fͶBäBvŽ@øe±r@y’”ƒö\¨åKm0;eÃ-;¶ÿ[ìÞHO?\…§½¹\~ ¶ËHuÈýÃ|ž4”¡†@º4 #˲oÁ—5{¦õÇ*âtk5ðYí ¯VJK^Áçi€……Ɖ$ô0ÓG›T©ÃNÞcwŸBá¹³ƒyÊtÆæ7ŸxÍ4þ£Ž VšËvÝäÕ½–ëDÓûθ‡+¢éÒò w]» !§[H$&>`ú…sº¹‰›ÀmÇ"{Èaõt-¹"ÖîJ–ƒ£K‘·ÔHy€L€-P&€5”HŽ7­>Ž–EŠ7±‰e/3Iœƒà¿h¡ˆ=„©ë¢ófo7‰^N<÷ß‹ãu÷ŠóàƒW¾0àD¯£gc”É® èÂwëÁ6–ÎYÆÂ³ƒµ¦ÄÎbbÚ‚4"£csŸ0ÚÆGÔ[i5e*ôüá3Ñ╈Ãoàg$—÷KÆñÎäÚãt6@êžðNò‡c3¹ÁîTúöغN[îŽo6Á·R=áwŽ5BÙºlÚ0@`Ö51á°v1"s|XíÆXíAÂXÓÂgàRP¸hL" / o‰Ÿ¼ªÞ*oËG“dꇵ}.¼Þ\ð.²7HSòéÒbÚ[p ¶]݇H[DyÃRÔ7,#‰‰K^w‚+x%1Qíû]—1gû&«\Ôßž :ðƒ{é=Pî^O˺ÝÁfÚw.B…á 57-;„åÛZñ¼lŒ:µ:سúýË0ß§cMlzù ÝfHÎÐn½ÇƒæQ¼¥|n™%ÃéÛçvs<›dnU†³K…Ã÷Ž:ÁÕŽAgëΉÄÂáij¶¸ö°Ç>Ux&ε^ÃÙÜÍ/>¸füf‹me?KËâ'·_›}Y´–Øþtc5O\O¥‡º+ï Ýß°t_¹‚›&M2ÆäÛÍõî_¡¨ÞØ ,ÊöðKñUqV¡¯=B=NÝç¢0BGšÛ©Ó8æx€8ŒÚ‚í ¦ÿKýñ"ëñv8‘š÷­Ú–² Ãé+Vý£òØl—w¨¥76\")‘€M¾¢µð—Ü4ûäúŠ|›½Úk‡¨Éæi #}à·ZšPl÷ç/gv«ál¥½)Ù½(¸²ôèÃtgT Š’åHl ¹O¿§e‡M¯ÎÊ™g©6\Ý9‚7êØѧx̳pýÑ+© Û*=JÞ”7#·‡øFfÖs[8ÿ L>$7µw7èšylrt«ï¤úy½6d™m È:Æ•u²ž£C^˜ŠÇÇ"D0ûÄ<ôÆ-ã–„OB`OÖÃͰÀÙ¥nü;í\¶MZ¢U"V›5ŸÊ8£Þ¨HTöòÜ|–Àf"Û(LBæíqÀ,f4x›ˆ åMOÂt±¯„P>ÞJ L£Œ×êèK€ŒÌpŸ¿ÌÈ¥7š¢AdÀ±«`Ëħt4ayËäEòúð&s§8›= F×»ˆ1ÚBòeW«!SöjK•ʪ’p+8³ èG; ¡Ð ׋;¡íµäÓðeV·J˜ÊL­öŸR×MVûL§NgÑ”é,ó[h®--кˆÙô&Ôdùâ´zdÇMàÏ2()ˆ 9a™ÅîëîgA›Rd—]6‹˜º ¨/D¼;[U®d¤4ãcífbræRO ´®n´=šÉ‹S$Ôò²A]D§vK$`.Èh<¢ õH’ð[Ë.õ*IŒgb€s4YÚ¦¤ §«<¹ÂMänsuÜÊÆE2!“‹Ú\‚Ø–œµ…¿+‘QJNQ‚‹ˆ§e`&i*I&HÂI˜dîÀ[ÈÄCb ,D!‰ÉL:U.Ó—ýËyGž:±Ë=òQ£–uê™jq‘‹ÈY¢¸ˆ®Eš.áHrcÝ!# àUâé™LÛ‡o@R,Ä¡Õq®Õ’m|s“Ý¡ IiÐB«¢ß0žÄº$ñ' xO"¢ ÄF¬:Aýd˜%2-Q‹Mrtã÷"a)j V¢ê×g8=Ɐ— êê&aˆ<>á𸃄`%ñK‹`d#si É4PÒĘ<ÓÇ6>Â!RxŸåf.‘‘ xs»àá5|PüFaù`kC8°Õ‡$>b' ªEì4±Ó4dð+~ rSå¡é&©ûVR陨7C>V1ޘȂƒ”`·…T)AQ Pd‚‘ Zc*PüˆÆM¯¤ÏÇ>"ÒjŸÕh®É“Í–tëì!£ 9¡°4Ál ðG  ŠØ,a™¨†DÈ+q‰ Ð{äR&iTËÀgƒŒŸ‰â&’Ä£e‹0ŸMþx” bÆ#@çÜ%Àò&@ö€5$@á¸TÃ.!Š4]ãYYÉ–U’øDç‡òEš_Ô®³¦´¡ÆOJƒˆ°Ä†p½¬@µXÀ¬/ô Õ`RƒÁ 0^]‘É®Vƒšœê¨Fšiþ¶†-µuÍŠ…°¹„¼ÃÄ@±’à!T žpA‚ŠÐíˆÈR÷0÷ˆÀN’¿p؇vAš?Sž«Î2U³Ž±~œAa&(1h'àó  @¶¸O€&˜`£<<9ÐÀÆì?:”³F;79ç¼’Å+¥{LØo¿`K çÜÁF‚- ÀU‚ àCl€(ÀdÀ7‚hÁ `eÃÔ$aU Xqd $‰HKxf#ˆŸ1 aQòÊX>Ò=EüЈÜa\»p’@Jp €tLˆ'¨«†?BXðˇLBrY¼Æ:–™õ@­½-stKë ïÁæ"¡  è:Lð"€œ€ pÀ`ð;˜Á (mD&ñl0yM&Ù] ,l3½ ñ!iäVZïÙ|Ü×tÀRÑM‚T9£ †ƒ€TxÀPl€Uø,AØ༇0EŒÈÜÞGr…œÉ¦þ=Ò R¨£50Y£n0z” ¢A Ø®„ìà¡PÀ@À € ØÁ 2!è8«Æ@9’’¨¨á4YÐŒÔѪo vl¦_…¸ÎDâ;at°2`TÀ @NP6p ðkÄ&Ál 0yDQ' _ BmÅ!9#éüW*ö™ºß‡Àö/R“(Tð ;$à @€LÀ±`Lú1 \`à # B\4pûh¥KÒ—’ÄF§¹™ñÁ2ß×ø@q‚€ @AÀ `Œe˜2‚ ÑìK¢ÈØî|LÒ³(×q>‚ Ñ2$·'uIêÄYâí˜nË–Ÿ·!Üð…|S£å)™£ÏÜÆF‰7Ï:ä1£)&©¢P’¡ô·Eø1ÍžÍ}pKÊ_ˆ|FfGÒ眵újX €>€ð €È {h:X戬R"ÿ¸þ L²‹¶†2Ù­vÃæ ½ˆýK:u”Ž«Åš.½…̰i«oœ”íÇ·À1B,£¦s>šN cÃYÀö4EòS“ħ¥Œ.éŒMnžžlÿˆ`F:LS< å@«Ýn ícc<îàG¼‰öðR‡|d$i-éÅÐþž¡k 3$0  @Ð @†€Jƒ4!U („À)AeŒ gÃzÈþÈ~HRf”d¬Å¶0I—jq¶5è•GêZª¤…]¬ÍuÌ,ekL›vägl=®Pœ‰äx%39ñRVªKð¬ÅÔ21¯Žt{Ä #)+ ìT2È—ŒÆ¦Œ7)Ð@€2£)&‰ P‚ Õò·%÷±¹ž xp£Þ@ûx(C¶1Ò2–Üâ¨yOÕ…ˆ!àË „ ;È Ál ›†;"¸šE:-1† pc ÉMb´·Æ31¯N0wSê ÙiKzwš¬œ.͆,²i³oœ˜í··@ñ=¢fi>:JÕBÁvÌ ðrCì"A+ WÂõxÚ‡P>’‘À–å*ig ÚdK›¼égª@RѪ“ôÕ(eQk[ÚþìÏ–¿¸%ÒÏ&}äáå)Ë‚q´?§¥k¬Ûs0Þ|" ÐàÀ¨ð€Ä@P¨)ºð¡…¤2º0…†'aU „c#Sˆô"ERM“¥Å|.Y‡Læl£’žüBEBDøž•-+5h«¾aã/Z‚܇*;îð…2 •Ä÷* c˼cB›tæç”?šQt’$ƧíMŠßZ²ô˜ÌÖ¨·iÌ®ô|<¡oXûÊvi“û¥]XìCn;ÓáÏ(€¬@9B p¥…¼2áÀ`†D'¡V‹ŒcCT€óˆEK“x¤ås.„ÌÌkÊÀùè/Dš>Äœå*½d«ž`Ë&4ÚºÕèï݃¬D#_%Y~ζ‹µ®e¢n›ŒrfØEÇ ˆJÜ àLð ]ÈÁ €b„ ! ZC€ñ-²•œ×¨%LJ½XÖß¾ÄNVt_°5œ Àh°#L oð#¡? bCb±ÐLb•@¿†l6ñÖ¨…Dm%‘>J„XòÐFØ9ãÔ„´W$U/ÉéR²ր»†2Ŧºr³ÞÀÿHGDª9rm˜¼ñhïRºóÚ’í0ÇþtµQ¸q‡`0¡@86Al  H{À@êÐRƒ ûØOÀPÖT?â,Ø£|/1”ˆs£Ú Ñ >NR—並3A¯N(vÓå ¥ˆñJÊq`ªŠ.-€¬‚haÜ `\°@™˜#ÀK¬€ßèFÂxðÁ†¤:!û8•E+atŒ|jã‘iÈ–Hòi”h¬¥°/ù” Fo“©¹hmFzMS> Å=ŠôIáñ WÂP~¨@Ì-Á^ S˜`¸h9‚🅀0©€~DH%? À\Cpᇂ?ò˜… *™g‹Ôc³F›ˆç'’?z‘bPž”" ÿ€8® àcƒ@Äh7Aä “,¡‡ XtCò"!' ìU‚݈цþ;‘úÌŽd¶'ÙP‹]“™àÙg";Ñõ…°&Ñ 0Áj ˆNÂN`ƒƒìè8Âpy #!2 p[ê‡ìD‚Nت…´0Ñ¡ àv#ì!QIVNòš•ô¹31® Î-!R LD`†„ !(CB4À¡l.á– ÀvÄ"(‰ôUBÚXÐê:ÑóŒŒ$¡'!JJâ[³™TG‚tƒh À…´+aL øLÂV`’”%a5 U‚Ú`ч <¢ x•Å*Ñn 0gãsPøÈb‘`’„Ò(˜…DðT9Ç qC’?âp’„Ô(±W‹X_ã)ÀáÇr>Òp’ÄÓ(˜ŽÄN!a‡ø>áòˆ|Ãò !ˆ|FbI  E@,1t (5¤”Ú£UŠßcÔ¢–úgN á@(¥(¨+­¥ºØ¢TZQòFIÉ]/¦„è |â¢þ·Ú ¥ Bˆu"äl’BSL ÁA)hQãü­›t*Ö»9sÎÁá=gÔÿ t…ò(F‰ )¦tú¥UzÌ]Œ1—µ6òè^Sôƒñ.¸1cœ£št›V‹6cŒÁ¥6&ð✳¢vì?H-£TžšÔB©YK¹Š3¶ÈãËå‚Q8J£@éµi,…¼¾£.hI®6Fäáœó¾|: Gie<)¥|¸ØS2kŽ!Ú>!ã| šTœ²ŠT‹ r0$Æ™(fÍ1µ8Çlù ÔT’SZŽVki2Æ®à݃ßðþ8J)«B)á#„ ›”’¸Z‹qo.%Ø¿˜³8l9à?¨q¦ªÖšýe QÁ;¿" s”Óf†Žñê?ˆa&E ©•žTJÁc.fÎS¢{Ђ3JéùS,õôÉš£‚v/Š ÄXñ*çΣ@kàAá7&d¸—2zRÊér1f˜âTŠÒ’yTk9}2vªá]«æƒ.?K9Î,…x®BäbAÐ?Èq !D8‹’BlRË}3fìî N“Ó£YËí•5—îßlŠr^ A6%ˆ‘¢tT q’6Gä#ÈHO ±s2fÔìŸäL“ÓÊ¥ZL—µ÷ ðŸ¬)‹²Jc‰1ƒÀuÁèAÁ4)Ř»c4m±þE‰M-¦8Ùsþ‰ÒŠ}UKaƒ³vÐæ^d†‘¦OMI€`c Áp.ÐCàˆbtT‹a7™ %%$µÓdvP+JêX® Ð[“¥{2 Çy[2$Ì´™áx)„°Œ(I d1†àò!„¨¦cHv‚NR Y6Çy#4¾£UÚëcm1¿»'Í℆”2òlJ©Šè6 ÀƒPvpY áØD Áf3GI%,·™SzyŠ=MJac¯†RÖÜsÂ~Ю0Éyg3¥ ¼šàÐ@@$`̰¶—#4u²ZTK¹ž8獔³g-­Ï½8£¤«˜Ó†ZÍ Ø Ø D €> A¨D ¡¤@ ‘h4‡‰&ÅlÁ£¨ÑZYPêÙs±†–ߓ惑JDKù·2§0à(€€@È%a +Á 'ÅàÛ„x¡–“ nO"G‰©L¬•ôËû’xïòÆy=5§”០\p€àd`’ƒ¨c$t¢\TËÑ¢9‡Ù%ü«–óhMÕÔ½Ø%cü¶Ô*‹À€( ^ k‚x]¡ôGJ g1¦Øð Äp™”ªÄ^¬¬¸×€ýa\a“B„<×Ѐ8`0< AðQ ÁøK‹!ž;È2*åôÒÓðˆÒ‚~U«u{0æNÐËsq®™à=§ë!\E’ÎgκHéý[Qj]-%ÔÀØã4iÍÀ9§hóŸDƒÐâ*H©K0fìù¢´Æ¦Ö.ÀX @< ^˜hÂXWŒ±ÐAÉAK.Xßt"‰ÑêTMJL+¸Ãe- ­·GêÜ~ÐFÄhÃdÄ´™ó®ƒÒ:W+Á4fÜåžè‚ú5Ii…<(õL¯–²î`Ìy›µÒàÜë¶zªAøuc„‹”óoOª-Lªuk°àl 0&Àð(Á-ñ&ÅÌÄ ‘2Î^ Yž6'ìžä†jCK)ÁC©åj³"übŒµ£5öðä[Å{ïæ Âè“#ü›–ÓFvКKPjíz²ÅŒº˜s4kÔ=ñ¢dx”“BRª±a­µæÂÙD !T*…ÀÃØ#tR$EÈñ%%„ÔŸ’¨X az1\ÓcŒuÏIûAÈ$t¶œ*šV n/&ÈÃPlîÌ;’øŸä…ñ&1ÇÙ/,¦lè t~Õjåc­UƼxcœ­œ†ðð dX’2NJɉ8'å ¨•ÂÎ\ËùŒ3F¤Üƒ²zâDÍ#%¤Ü ”º¬XKaw0V8ÌÚSboNEÕ<7¸ý`t'ˆ1n:ÈùS1&üú¢ÔƦ֋ h-íÙ¾¸Gd¼Ï¡Chk ¡Ì?Á>-Æ(ԃ؄‘âhM ±9'Å ¥"¶Y qz0¦HÏšÓxrÎÙì?¨9#4Œ–SbRª©_­UÖÀXÃ.h­uº8ÇHï^«î€ð‚E8Ú,…ðËC¤}Ò@M D(å,¨bºYKiw0&0ÌSdpyß=Çý"f‘’ÊlP QT+Õ¤ºóem ¬6÷çÝÀ†‚D‰!>)Å€ºÃHpñþD 4(e4©•‚¸X‹Ao.æÅC@jÍÁÆ:‡Œù $$‰ªGK)±@)5N¯Šæ_ŒE”3æª*E€¹C4maêA™$&¥©²ÄZ is/FÅ3:iÍ™¿9‡dôd …‘27I)m6¨&©ÕÚÐ\«é‡²Q>'…¨bèa ±ª8Ghû!$\““‚ŽU‹ j.%à¿Se é¦6ìã3À{Oâ CH©¤´º›Ô ”U íh.Eò/†(Ì£xtRCÈé,'e(¬bà^Œ ˆ1æ\Ïs`nNÏ;Gœú $ ‡Ñr>Ié8¨5,ªUâÐ#e ®7‡0ñ¤$‹2`OÊq\-EØÀ“"fM ¨6&èáœÃ®xqûAY#D†”ÓuPÊaU«á¶8‡PñDˆÂRMŠ!R,%¸½˜c"fª6FìáœÓ¬xYö@h=¢´r’ÂhO*)MªÑè?!#˜Ò”V )r00ËšVlÍáÄ9§Xð£èÐZDˆÉ $ä¼›SêŽSä<Œ"ZN !Q+Ŭ¼˜S$gMI²7‡çsÀz™þAQ"Är‘ÒªdNj I’ÂlPJiY,¥È¿˜Ã0i yº8‡8ìÔ|ÏêBHu#D‚“’âkO*!L–U‹ m/FÉ™óVmÎÌ:ç€õ3úЊDÈɤ¤®™’€Q倵—ccŒÑ¦6FøåC¾zO™ý@èE ¢T`ŽÒ:RKé±=(…,]L‹2æˆ×£ŒtŽéç>Gñ!4‰Qz:H©A.&„ê ”‚ž2¦€Õ›s†sŽÑå>í¡4‰z9H‰=-&T䟔Z˜5FÔàîxÏqù@ˆ= ¢D\ŽNK)‘8'µ¥ ¿9GTðžÃî€ÐrDh¹$4š–nOJH©åVuNùë>È!tD‹QÂCI©a1¦Ôò¡‚›U ÔõT‚в!EˆÝ!$Ô°˜“jxPj9M*ed¯ZD(±¤$š–mO G)•J¬UâÉD(±¤$˜•ÓmO G)•H¬ÚÇZhÝ!$Ô°˜ÓjxPj=LªE`®Ö:Ò[É9,¦DÜžT"SJ•X+µŽ´–êéL©Á=(U ¦Õ2±W‹i-ÕÒ½Sâ‡R*qS«%z²šÞ]KÕ€0Õ(§•J´Wë%j-õÖ½˜ c ­[,–µ— ì^ì ‡1–J°ÖrØ\k¹|°FÆÙ31\ËÅ}°f"ÇC3gÌ%Š1öTÍZLj쉖³††ÓšËehÍE®6vä×ÛSupǹ§<ê]¸+èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú> 2%¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èM€ X>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èa€ª>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£Þ€8>£¶÷¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£€ ¨¾£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>¢”$@Ôú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èúÀ¸¾£èð>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú­#èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£Žè¾£èú>£èú>£èú>£èú>£èú>£èú>£èú<³4ë(©XZ¡<ë“èú=Iaz­§FÊ¢g€‚-BŠ9á@PÊ{Hx `ðXú>£èú>£èú>£èú>£èú>£èú>£è÷¹Œ2d@ðZ Šhd x €+¶´%ˆI¸sBЀ>£èú>£èú>£èú>£èú>£èÍ#p2 à( @ àÈP€ /…¡¸ @€€ € èú>£èú>£èú>£èú>£è¯€(€P@( @ xPª)6b€Œ …(€P€xï£èú>£èú>£èú>£èú>ŠÈ2á@ À<‡€(€" (¤8È€M‡€ èú>£èú>£èú>£èú3蘀2(F ÈP8IÙ†€ (@xú>£èú>£èú>£ès (€P P€ JŠ“Ÿâø €@ £èú>£èú3èpÒ€   #K À( ãèú>£èú5ÅÈ ¤ Z€€9à (   P P ú>£è“ƒ @P€ Ô—À%€   €(( 㘠€(  (€P (  €PF €hZð¡@(€Hø€(€ F‘€à ((  € < (€ Šîyoš²ÚYü«mUÚ«¥ª¢ RöªÄV)+…”d*@Å*"šÝPðª|NÙ'ò õz’µMЩ^L`&pSb)Ìõ²ƒ±FÔ¦}?,’^©ÿ QP›hŒ„gBE+Jš3Om¨Ü•JËÅŠb×utž¾±a±ÏYkÌövšÓ\ÁµÎÞ‹q 9cÝηzãÉàÜn„7ËœWt'hÎõñö…ÄE £¼u)‡ôêÒˆýN.«ÞXQ­S×<ëæÖ£‰–²Ïvi¸5Ô›cì§—qÒxì‰wÞÙŒïF…Na¯üÝvr :å|˨0ä?:3¡#ð—N(OÔÉŠ´5jÓåsf¾Naf±ËYnÌùÖ+^i¶Öß#qa94îÏ—‰ƒ€Àø`ä1™LÚ&¡3oAÈpív{˜@W¡wQfit¯blýA ¥ÀUo+ûVš‹š…ò» ¹ŽÊ˸g÷µšÿͽVüÃŒÑÍtê)v¾–  3˜RÓyp”½Éút‰ØG»¥CÓT*å+zªÝ_¾´í\详˜gÌ{–`ëA¨ôØ}n.8œyuV[è¨âRÕ)!§ €…erËYu Ãjf£5ñœ\NåGÐb#5d”†M"§ùԭꬅ€‚Ô©tP¾ýaز™•m¨ÃdiºàÂr59ÿl¥!¢vÑ1¸–RJï%êw …IrÈùx¨ÇRi{7½fOyˆ ¤Ar9(¨š:Oþ©hUcË¥­:ê­ÄKdp³U4 \–ΛváÂþå(tZ”»I¾$œò4QT†ØC¹"£þ‰x%B¬lÀ†g™6þ!bH¤?‚8ý(Øš|P/)„Õu‹5²¢í™€–ÅdÕ3‡ÚMíi¶Õz%Ä åútÂIUDX2 HøhxR;¯XÃHTsrd‘Iܱ¸_y3FŽ}¬” ª ]”ßM„(?ÚªÆõÜ­xŽÁ8bü²§YáM5¶¼+n=¾îã:µµeʸ6^«0‘X଺–€ãQ­±$ܘp<¹ûŽš4£¡ÔJ@Ö ÷âΑ_ ´`4x HðÄí²²™wœÊølï:.ãPTȘDÒZÉ:£T|+§Öƒ«–µôC ånÌÍhŸ5d6Û' ƒ•)Î:èéuV9êÝ.C}‘ˆ¬°M†$K Ÿ…Y£ º8ú°ŒÆNO+‹ ÁFÛcªiò¡°ŠËHÅ&ÓÌJG%RºÀ!kºÏ_ò12Y1¬á–”#[µÄÞâq\9›]® G83£UÕ0/¢/†Ãµa½ è˜ƒðMÎ.ë|HÉÄêR·±|€Îno;_ŸŒ¬ˆÆ„™BfÝ@ ¦Uú,gâ«ÅF ûù•ìÏ}iï¶[†.W³ž‘ÒÜë1t":‚ÝyâyýXcì)"Ú Ž‡G„–ÂÏÑ­xù¸ŽbOÚ,8˜2‡“Äò}¬ŽQJ‹¦ç;ê~Ån͉q–½ûa±ö&ª»f»$á~r¡º9UΘ÷Z ¬.˜ÏÇHl¡¹ñ$ ¶ mÖ@$‰”{ËT¦1#Z!Íõæ‚fDÑ$$’é Û•ª§é_~µN]Q¯òX˜Œ˜¦qJU­ÆÛo„8¸Ó®£Ç`«ÙL"Ø ƒçÀÚ@4àNèL$9J&`ùŽZˆXľ¢¦1uÔ˼n:ÿh¢Ã˜¢fÁ@*¦>V,vë+Ê& Ãa–žÏßj!¶ ’®ç"ë -×0íWw‡z€žÀäŽ@ {FtCÑ‚2é=¨°š`á4šœQ#ˆ T?ò<%+ŽœXQR*+UÐkBÌú݇xÈfš4lš¿í¢¶ñÓˆuËÂé†v…µ¹›±ŽX%ä æ  F÷„®ïÂÑ|•’S¬.+™1MžGNÓäÊN’L\'ɪj´u‡êÚwªÀña³1vL©fg3<­¢ÌÓ’jܵó<í¿ï{™Ç‚çxt¢ºÂ™.ç÷«Çuoˆºi^ƒ/äBlHf7‹%I—NÍðh4—Z‘ j¶ÅókQ¹®ÞÒpc¹%Í®ƒ÷P{¯EÛï@xx<¥DlÁ­ ›p: Æ 8ˆ¸ÅycM‘ï1´Ÿ~Xe0lCÎ!‡”²Q0•QM•J±åp*ÃegH¶X\®éWÇl óµÊjfY3½Ú%í5ö¬+^³nÛ¿nß7í\4n9+c Ò"êÚvL»•žÏ皊Ná.šIN¿(&u*k5Mj²U^β:Z{-ëWJkÎ…ûÃÉˆÚÆÐd—²àY¹ †‘óQ‘­ Ø¡mU7,›Õ® çC‘±ÌŠètôºêݬ®ñ׆CÉÑè&$J®BÁ!x á ‹lU22ܳ‘‰–SìY–àÛbuq>|!Ñkèì„‘zV1–›ùO(ŽÔ© „YB¸%a®³Ÿ[.®@—s âöƒ Ñ‹@Èe-³)ÛíöšCUµ¯ÙŸmÒ7i[óŽw›•AÎJèóu^;Ç.þ‡ŒƒÌÍ骉ÜF¨$.’‚ uÔ:vÕA¨£àSlªs•êøe’‚ÔYorºZ^{¯äØBlH67K$Ñ–üÍÂh4ŒMhÄÓj™¹HÞ¦pI¸žÜ‹cg@C§Q×*íRw‚<-žK¯?׬¨œÈCîÐ.*©·Q”èéd„ÊJ”)e»L^Æ­s”Yè5®„îD:¢øÑèé*¯zd¡8°ŸrQB©b•ʶåqêÄg´¶–\˜.òWÌ̦«ñBÊtf]³»$m4Æ«»^a³NÛ©nÒ7æ\1Ž6÷*KœuÑÒê­v:;ŠÝû¯W™#Óì²÷é>4 Ptq¤Tª7Õ"²” Kò&ÃÅjå"ZITz­Wñ¬«¬‹ÕJôÕ|Â8bQ1ÂY,Œ»†o“@Y¤ŒÔ}kL¶,VíËFõk‚mÅä]sº:¹j{¼ á\òVyù=bžã(®ÐP&c~ŠnÇ#@áÕñð“*QT,˜9M3ŸAÞ|öx~ÛA0!y*¨ÌT€êMÙ-\™ßN(bÊaUH"¯•]x±ŠZ-¸×0«Áeõ }‡ Åëd&²¨™ìò†Š£N «D×­lÞ¶ð›·­ú÷ Ûåʰç(txº¬]Ž®ã'Æ%æJôÃ{,½ùß.FóC—aÚDôd}»@£¡/Ѻ$x I!+˜´Më'¸Ô=ŠN…>òªù[°YYˆ­q +°%ì3-„îÄãc¢²g|,â&‚ãJ%©ZÖÇlf6´ÛšÍì‡ÛŠ‘ÈöæLt :xsîÕ—xsÂíäÂóúzÉ=ÈŸ£·ÝÈÕXbÌ/6‹îWƒraåq Ä“‚Pé,éÌᦫ³s9È8ëdyF>c w¨sTTŠ7¹"r”tKʦ­¹Š eJšùS<¬wW ,€Ö•«uuÏŠñÝ~ Áwað1L®FiC=‘£,Ó¶jéµù<¾vï]Ã|ã¢r¼¹Ò!®¬·d+¸åßìñžyš=3žÌÏWËÛìøþbæ3FšˆM¸‡S§iáØø oAr!‘4ÈÏ´šM­-$™´Nc'ñX*[%DÊ­½\j°ôYÕ-•׫·ïëÕ…ÆÅFcв{†,æö…K!©ÈÖýl„6Á› mï§ ‹ÉHæ{t!º€]w.×7yÃyäöôzÔ=ÍŸO¤×ÞSõ}–:²ë œFÔ†ÜXÿ”ŠJK(– ææ+2 §ÚˆpÊ:%ógçë4šá‚ŒG¤$ ’µ‰ŒDÞZ{5Cœ¤¸SÉ*›§k•¢Õaoìº}^‚¯àX>ÌDö5K#ý–ŽÍgß4zía–Áh¥¸NÞp¸{\yÎZg;‹¤ùÕúì­w-¼ž85·§{Ùõðù•}Œ¿%Á÷Òx±H¨ª²X™-öÑ VæaƒL‰³äàÔsØ; ªOÁˆt% ‚ŽHÓ¥1’üI®ôGš¦ªTÃ+•á+£RÜ=s*¼_I0AmL\–@ë)™NÎÛh4ÉÚ§ítÖÊ«m5ºß8p8¾\š®jwCƒ¨Õ×àí›w¥¼?^TOCw®‹ÝiñÈúi}õ¿XßÚP"áÄ}„> n;H…¤†¢W)5Ô zS+ –K ŠÅùS˜ÈÓlà8áÎÜ'£ãì:i8‡dEJ£r’i@Ô¸Êhy:0  Q{©uÕʶåq¢Ã‘g ¶ \Z®ÎW¶LkŽzÉ‹eæ3‚ZM$¶£3YÕ±Ú‚n?7›œ ä‡"S™2pžQ‹*#ÂË>ÅσÌÞi¢6tNl‡jÃÏQô½Ðƒ¦Cl¢‚‘¥‰ÔšÚY2ÆœfO§(‹T Ê~U"µq`&²µZ©-ø×L Í¥úsåˆ&ÆldX²¼Ù¦,õöŒ3ˆ“(G>#~ˆã¤²Ka+œ™hN¡(f”ÈJ´%„×éw`ÁcÌ3l}M¢§3›YÚ@ó’|î@ ÒΨ™4e*?%Š•ÎLa¦ìSÓ*Õ"jœ™SȬW¨,|kqeÌÊðY} Àèa£±gXýd¦bY©0ä›4O(p”± žsÍùq ½âaµ2G™ÞMNÖÃùÎ íTy¼>kŸýbaÔIò0ý ’LJ¦¦ ÓdÉà•ÂŽ±Lì©7Uô+¤Ö&K<Ųêãiv¤½Å`0¢ÊJdûRp5€›œN_'ž 5…4B§¡[±ô[W.ؘ XfXAá¬HÛðpç9ðµÏ;§Î#ÿr É öˆüEú£¾R=)M´¾Jj…:ú RQŽ©vª´•o’Âf8µª\®©¡…RI„¦T|*ßÔ‹ å¯çÉ}<Ã2d3EšMM|æë3‹ÉÑHîtz#>‰ ^^ÔFº/En‘ªJB¥ÛSGéÐÄþ’Š}J¬¨ U[+Wÿ (E¨:Þ©òTÈ*f•Eʲ¥eòº‘b ³Ž[Q.Š—¼L"¦2ã+±ŸXÔªlä7Ã‹Žžç{3Ô!õxƒBý¢6ÑwH餋šQ=.@™îNY§ßFªNU<’©Y¶¯mXù- eÂË™i8¶”\v.èÕŒ#F/3'©œLÒ k–7œÎc‡ZÃÃì8û¬€“A­!‹ˆ½tÂEq(\–ìLܧSå*µ$ZœÁSœ¬aWz¬^Ë•¥Õ¢ña|ðÀÌaÐ1¥™?LÛÆŒóWy´@Þ¦q©: ¥Ï%G»ÃóB-z†uDš"ýQÖ‰t¡J[y3$œHOz¨e”‡jmÅKº¯ý]a&1ÃL‰&ZS9y£tÕZl”7T1_SÓ½Qè<ù*@Õ¡Üh™aŠ;¡#”5Kr&`Ó†IìÅ ŠM4©(UÙ™tLÛFÃK±¬ÙBnm8&’ÎŒ‡iCƉìˆûîA5¡9ï(Ÿäd¢<µ#Ž”[K¦cÓ†iì ¢LШîUºšbÍSæ½³jºèàØr:zNÿ‡¡Ãâ‘ú%Òƒ?BÓ¢‘R(ÑD|ªH)F—0LÞ§Ú Åb™IQšn[7Çœ=ŽN'AC®)Þ`ó{…>ÍŸîÐ?HCD4R#y|§H ¤¢Ò Éw„ÏâqÅ=Ö¡cQí©‘•‡ #™ùÒ`ì4w«<±ÈÏ¡Gð4r ‡…DÞ#QËi t˜bUñ/¾šJNX§ÉT0ê?e2’¢ðí´x/<՞Ϝçê„ ±Ò†|DH¢®èò¤‹BNí,˜VMX'BSíÊe ú™äôÄ{¿>™Ÿ¯P (!ä21v‰»Eæ£y’é#T£ÂZ½1à›2N»'ðÔBjF5‰÷Œþ|€¢A óP¯t$HÒ, àŽÿH€$ɨIvÔÌúo!;ö :Q;©,Tß(¤ ] "‡&Dh"¤‹ˆãDHQ(6–'L&”“Ièõ"‹¡J.§RB]¡€ÐíèŽtS¢0mH±¤Ð¥ r4Èúl:nŸBP®(ÞÔ³ {TJâ*Ùb²G¨$@RV‰Gô²b`Õ44œO¨”BjA•1: í>ŽGµ¤ùG*€¥‚ж’C vcúLÔi¤³5͈5û"‡äUâ‹PŠ!N$+te£ºÇ˜cff@i­ø6ô‘Z";MŠHÑ]Z.F Ťªô–=²ã_¨ÌZ —44T†µ¨Û͜Ӕjt©Ôz$C´hƒ­¢oäS†‹wÊ1aFu…H§·årÙè^¼ŒC•;4,³˜ÛÕœs–uÎâaä =fÇÇ üßÄà‚$PjÒËB½ègqÌ"tHʉßS’-'È`Òú"E’°êh%Èåͨ×¶cøT…Q524™Gî‰ í'»%V„´Ú—³“Be•M)«µ6”¦òtáÔœ¡“ŸÚuPNЉÞY',„éb£ÓÁyzOWÉïm>z'Þý`ŸàTŠ€áP. iAW(3%l¡ Ô%„ÙPñ ЫÆÁ:¨DLéôôDˆˆü](¥¸TÇNštuúr‘Nµ àí=>'ÆTûìŸØú¬PU*B¨O¥ (¡ˆÔ5Ї%Pó ÙD,(‹µ2¢\TNŠQLJ)ùF–(ÔЉQXv¤-Øèw%'ú&MTàjá“â"ÙPJÊmC (vÅÜ¢Y”Q²ŠæQmJ/ÍF9(ÌõN£an ŽQÇê9­GN(ì5Þ£Äy²hQñÊ>©Gè(ÿÅ ¤°jkÕJ ¨_”O5_âªgå@j§XÜš›S9Je‰Lt©‰Å0h¥ü”¾2—ŠRêj\…Kˆ©m¥-r¥ª´:–[RȪX½K©_å+°¥p”­b•‹R² VJ½)WU*ê¥]TÆB˜²¢ sÄ‚Sÿ¾°¦tî½²–Î É™WôŠã‰Yñ+Z¢ªÀHb§UT¾ª“IQâª-åCî¨SUŸþSíê{ÙOA©àU;D§ZÔéÊS›ŠrÕNG©Æ58n§”ß‚›ÏSvjnaM¾)´µ6H¦ÂTךÝSZJjñMS*'µDR¨u• Ò¡MT ʃ=PFªµ@>§õÔýŸuSçÊ|Os)ë¥=§œ”ñZžS¼Šw9NÓ©ØÅ:Ö§UTéüíÌ™œòU–z»¿Æ´rÈSýºê°[Ž Hye,¬D~ü®çÃ"µªVi ÄåWµªÞUXêªÉ•PJ©Uª =SË*r•MŒ©—U/Ò¥¨T¨ª“µRP*D•Gì¨î•£ T[ÊŠ‰Q*ª!…CΨl "¡MT$êƒÕPdª …@î¨Õz Sý Y&+UaT«ýUyª®ŠU¿jµEVd*ÕWJªËUjª/U8*¥9Ts*ˆ…P4©íÕ;*§TÕJ™uSª[5JØ©I&º¤¦TŒŠÍQû*;õG¨ÔUZ¢ôTYªŠ‰Q8*$©sÕ#¨¢€[þ{uj­£-±£Æ2¼Ø8zÝ“ZÂ+?ec¬fÅ…l¯åæÒºmW'êàá[ +d•jÜ­$•ž ³VHÊÆqXv+Å_f«ÆUsú®U®J³¹V@*À%WªÈUUjªKU= ¥ýT“ªPäªU>š§†TéJ›õS]ªhEL„©‚Õ.J¥˜T¬*”‘R|*LƯçÕòÚ½2WêìI]+‘5pd­ÒÕ´µºVêÑYÊ«.dŒ¬kˆj°„Uýê½™Wvªæ•[ì«bhR¬œU‡Ê¯-U´*°µUZª™•P¢©ÇU/ê¤ÉTxªŠ•PΪ Õ?§²Tî œæEE¾èC8ø7ÝÛpÌi;ì²Ù‹a°^¥ñ»€×*êÞN[ ‹Mgm¬°•’‚±åV0úĤXj«`a+ùõ}¯a仆WTŠæù\v«‚un¬­«¯ºµBV”JÏáY¯+,õdv¬l•‰š°ªVоÍW*ìÅ\¼«zÕlš­6U› ²-V*¾WªÑÕWÒª­UMЍ©Tô*šRÒµ–’zÐ[YÏ 3eŠ,˜5±¨Ö*úÄ X[‹)`P«ú¥}P¯m•æz»îWdjéI\ëup.­ÙÕ¶¶V¯*Ó©Z(+²,–>ºÆðXÃëebD¬=5†2°—– rÀþX JÿA_›+êÕ|<¯f•芼˜W‚Šî…]‘««Ut„®vUË2¹WÑNò;ÁlWëÖõ>ݫۘªoӭ˳ß5Öf§¨ÒÃZ{;!f|̵U“ˆ²Ö7þŘ’»a; í-¯îÅ÷h¾*®Bó^k·áuÙ.—eή¹WW"á–[ùëx¡nF-°³ ¶–¶2ÕqZ‡ Kýhæ- 5Ÿ,³ Öl2ÌŠYs«*±dâ¬E&±Ò3êÅ·rüŽ-¿Â7DfÓ×ûšµÓNEhÄlû5›Ü36V`É^ÙKJbÐÌHÁ‡°¢¦ èÀÅWýâü»_7KÛÙz1/Uß»L—X’èø\áë”…q¬®¿ê·™æÂÛh[? byk­-a©Æ´ëÖ”ŠÑ„Z+=ª,‰ÃùHПN3̾wØÎÄmÒ¹wÇ2à­+k¶lEMeù¨Š´–Æ„ ÎÖ¦ë/e,l…±¨F,<Ä‚˜q f`ß ©€L/Ùuõ¤¾W¬²ó$^ º¡vA.¨•Ñ ¹²'ÒãS\4ë€moM­Ò·Œ¶™–É×Ùz×Ï%)Þºô'EîåÅ_3 nßíµµ²/µ¿f§¨ÓÚ)»>¬g Éñ–y2|vEºÇ”Ï+Æb;Ì8á…W0vV†ÀP—ójû¹_%kÚ¥z1/!ßî»z_Êê] šrl®45ÃX¸ö FÑ…ÑS‰ýO>—¥Øñ’]ͳ­¦t=î[Æ8¦ð|ÛÃÛ2Ë^!jË<-¤4vw@Íqƒ“+6dÎL‡)޽1•*üÄs˜r£ `ù a€ö¯óåù¾ˆ×½²õ€^gëÄíw˜®Ô5Öêºj?²æO}o}ç̼8Go6ë Ý £–oqŠŽ Y¼¥·–Ð,Ø šÅ›Q¤i]IŸS3Žfg>ËžN%3dÌsŒt1Sv#zÖXY»+`«Œ 1ù¯Öö¾.³êô]^JkÁ©wCÀ†ø^ü ß!“Ø(yÚÜ:­w@¼å‹\d+ƒ/oI­Ç‘´Û¶"öµšÕk«GAh, J³VY®ÊÙ [ c„ bŠz±&ôÂÛ˜DsÄ`d M¯¼õóH½Û׫Sëª|ïSÁäÓ»íghên\ýË•üq–Îi½—·2†Ö¼ØûæëV6iû'=¢³éfrÊÍ"Ù~›+rdäLŒ}¡1¹Æ0:Å3ØŒ»›aul#ƒ0;Vd¿¸WäÓܺz9/‘Þc»)·QNçÃܮیâp‹ ð™º#6ËæË0×µšÁ«Ri†-E š3–ntÌ®qã*dÃL‰!N±²æ/”Å'XŒ‹±a{Œ$áƒM0C7ž4ñ4ÝÖó±1tõîz-ÊɸÑG Âß:Ûªón(læ‚M­µBfœÌÒ>Z >·g=ÌÕq˜ƒ2ÍvQÞÉGY ë cV ^!Š;±ÆŽÃÞ˹2uþNšùϹW×¶áSÛíãvvmë¢é±µÁƬ4Ô$ZYKEýh!ÌñÙ›ò3:F_ Êæ>“$Bd¬v)1uv(ÂÄg·ÂuÖŽ˜MÎÍ9V'øátÛó“wsn¨Q±åµÜ–°Ô¤jH3hn¬úñ+3avd<ˉ™SÛ&ÕdoŒ€¥Ž€±Ÿlºëh]*›7r¨.6Ã&7óvð’Ün[#eAkî­gEª9´õ6”Ñhë<¸glÑí˜O2Ð6RÆÉãšvŸ®³ÒZ9Ìç*lãy\5£âo$­Ë-¶Q6kÆÂÖßZ²cQ%i‹½¡œ3íÖu”͵٘+/Zez¬¡ñ’Ÿ:½—H*ç$œ©Ž på®­½7<ÖÛ¶Ùþ›;]jÿÍL%§ 4šæŠšÐ?Yç£9f¨ÌÆÙ—:°‡G,ç Ü©޼W*ús_Ckç |µ¯‘5ñv¾×Àº÷¿^ìëÜe{`¯gUì:¼ ×’ ñÿ^7«Åíxœ¯uáj¼WÚúŸ_Hëç½|˯“õñæ¾&×Â÷ë^òkÝ{v¯iUì’½|W­*õW^¡kÒÍz2¯A•ç–¼ß×™êòñ^UëÉ…y¯Õã"¼S׈ºðÕ^kÁE}e¯§5ô6¾sW˪ù_ëá½| ¯{õîâ½ÆW¶ öu^ë×Ez¼¯Rµé¾½$W¢*ó÷^ukÍuyˆ¯,õå¼Wúñ±^-ëĵxu¯ Uàæ¼ WÕJúQ_?+æ…|¥¯5ñ6¾׿ª÷^è«Ûµ{P¯d•ëú½iWªºõ ^–kÑ•z ¯<µæþ¼ÏW—Šò£^L+È}xé¯âž¼CW†Zð“^ +ëU}>¯¢Uóš¾]WÈúøÇ_ ëà…{ä¯wî2½³W³ªö)^º+Ö z•¯Mõé"½WŸºó«^k«ÌEyg¯(5䂼|׊ño^%«Ã­xT¯uö¾ªWÒŠùù_4+å-|~¯ŠUðš½ýW¼ê÷E^ß+Ú…{)¯_Õë^½UרZô½^Œ«Ðeyå¯7õæz¼¼W•òa^B«ÇMxȯõâ¼2ׄJðG_g+ë…}D¯£ó²¾`WÉJøÑ_+à…{ä¯wîJ½³W³ªö)^º+Ö z›¯Mõé"½WŸºó«^k«ÌEyg¯(5䂼|׊ño^%«Ã­xT¯uöʾÃWÕªú]_@«æµ|ª¯ÕñJ¾W¿ª÷©^ê+Ûå{P¯e5ëú½kתºõ ^—«ÑÅz ¯<µç¼ÏW—Šò£^L+ÈUxé¯âž¼CW†Zð‰_~kîE}œ¯®õ¾ŒWÎÊù_%+ãE|B¯‚Õ諾ßW¹*ö×^ÏëØ¥zí¯X5ên½:W¤ŠôE^~ëέy®¯1åž¼ ×’ ñó^6+Žx–¯µáR¼×àúûÇ_mëì]}_¯¦uô¾m×Êúù _+áe|¯z•À×µZöS^ÀëÖ½z±¯Pµéz½W¡ó×^q+Ìõyx¯*5ä¼…WŽšñ‘^)ëÄ5xe¯€F¿×ßšû›_hkë­}I¯£µóƾb×ɪøÝ_«àµ{ê¯xuî^½µ×³úö3^»kÖ=z›¯N•é:½WŸºó«^k«Ìmyg¯(5䂼|׊ño^%«Ã­xT¯`°WáZûÓ_okì}_¯¦uô¾m×Êúù _+áe|¯{5î¶½À×µZö_^ÀëÖíz±¯QUé’½W¡ó×^q+Ìõyx¯*õ伇׎šñ‘^)ëÄ5xe¯•àŽ¼jüW_ëî}§¯¯uõ>¾‘×Ïzù—_'ëã|G¯ƒuï¾½á×¹zöã^ÑkØÍzó¯Xõꂽ:W¤êôO^€kÎÕy´¯1µåž¼ ×’ ñó^6+Žx–¯µáR¼ׂ:ðs^ +Àè¯Âµ÷¦¾Þ×ÙúË_Nkèm|ᯖµò&¾.×Ãø ^ökÝm{‡¯kuì¾½„×­Úõm^¢«Ó%z=¯B5箼âWš:òû^UëÉ…y¯5ã"¼S׈jðË^+Áx#¯Uຼ×ã ü _v+íe}€¯ª•ô޾{×̺ù?_ëâ=|¯~µï&½Î×·ö‹^Ç«×z̯Téê½)×¢zô^v«Í¥y¯-µå¼Wúñ±^-ëĵxu¯ •àμ×*ð]^kÀm~¯½Õ÷ ¾ËWÖªú}_D«ç5|º¯‘õñ޾×Àº÷¿^ìëÜ={`¯f•ì&½qW«jõ!^™+Ñíz¯>ç*¼Ñ×—Úò¯^M«È}xî¯âž¼CW†Zð‰^ëÀ•x¯µà6»þWàJû±_k+ì}T¯¥óò¾hWÊZøó_ká {õ¯y5»W´ªö=^¾+Öez¦¯OUéN½× jóÁ^nkÌym¯(Õä–¼×êñ{^%«Ã­xT¯uàb¼×€Úïù_}+îE}œ¯®õ¾ŒWÎjùu_#ëã|7¯‚ï’½ÜW¸ÊöÁ^ΫØuzâ¯W•êB½4פ:ô9^|+ÎUy©¯0uår¼›W‘ZñÝ^3kÅex‹¯ µá&¼W€zïï^«¿•~ ¯»ÕöʾÃWÕªú]_@«æµ|ª¯ÕñJ¾W¿ª÷^è«Ûå{P¯d•ëú½iWªºôÿ^–kÑ•z¯<ææ¼É×—*ò™^IkÈ%xÞ¯µâr¼=×…ªðs^+¿½wì®ü5÷Ò¾äWÙÊúá_Q+èÅ|쯘òR¾4WÃÊø!^ù+Ýõ{’¯lÕì꽊W®Šõƒ^¥kÓ}zH¯C•çÚ¼çךšó^X«ÉÝy¯•ã6¼VWˆºðÕ^kÁEx ®ý•âž¼KWÞ:ûo_bëêý}3¯ õón¾W×ÈJø±_ +à{Ô¯uíò½ª×²šõý^µëÕ]z…¯KÕèʽWžZó^f+Ë•yQ¯'u䮼WJñé^8ëÆx¯Uâr¼EׇºðÕ^«ÁÍx¯µßò»öW}Êï£]ð+Ìy}¯-µå^¼£W“jòA^CëÇýxî¯Õã"¼[׊zñ-^!«Ã­xe¯ •àμ×*ð]ýë¿=wÖ®ùuÞî¾ü×ÜÚûC_]kêM}¯ž5ó¾MWÆêø…_«ßU{¾¯rõí®½Ÿ×±JõÛ^°kÔÝzt¯I芼ýמZó«^oëÍuy˜¯1åâ¼±W•òƒ^JëÈÕy ¯5㎼i׌*ñe^(kÄx€¯á~¼'׃êð]^kÀmwü®þU߆»è×| ïk^ŸëÓMzS¯Huè¶½W jóá^x+ÎUy¹¯4uæN¼Á×–Úò¹^Q«Éµy%¯"µãþ¼wWêñ›^/kÅex–¯ÕáÖ¼2×…Jð‰^kÁEx¯õßÞ»ó×}Êï—]îë½U}㯶õö.¾¯×Ó:ú_6ëå}|„¯‹ð²¾×½š÷[^àkÚ­{/¯`uëv½Xשºõ^+ÒõzN¯G芽ןºó×^ukÎ%y®¯3Õæ"¼¼W–zò£^PkÉ…y¯!Uãæ¼t׊ñ…^,«Å x‘¯á¼/ׄúð}^ «Àíx¯UßÊ»ð×}ï]ík½-z¼¯TÕêV½?צšô³^ëÑmz¯@ÕçÚ¼ðWœúós^jkÌyƒ¯.Uår¼¦W“jòK^EkÈ%xô¯Õã6¼^׊Êñ9^"ëÃÝxj¯ Uá&¼ׂŠð1^뿽wì®û•ß.»Ý×{ ï?_tëí=}{¯©õô޾{×̺ù?_ëâ=|¯}õï½Ë×·ö‹^Æk×zݯXõêÚ½PW¨ªôé^™+Òuz=¯DõèF½×žºóµ^q+Í¥yž¯1µå⼳וzòƒ^L+Éy ¯5㦼lWŒŠño^(kÄx€¯á~¼'׃êðg^ëÀ•x®þU߆»ëW|jïk]꫼Õw‰¯b•ëú½tW­*õƒ^ªëÔ­z¯Mõéf½$W£*ô9^ƒ+ϵyå¯9õæþ¼Ô×™Šó^\«ÊåyL¯'u䮼Š×Jñé^8ëÆx½¯•âr¼EׇºðÕ^«ÂMx9¯àv¼×ÊïÙ]÷+¾wÀ®öÞ–»ÊWxJûÓ_okì}e¯§5ô6¾p×ËZù_ká|¯{õîνÃ×µºö‹^Í+Øõ{ ¯^uëv½fW«jõA^¢«ÓÕzd¯IÕèö½סzô^|+ÎÕyį6•掼Æ×—ÚòÏ^UëÊ5y0¯$ä>¼׎šñ±^2+Žx§¯Õâ¼:׆Zð©^+Áx#¯Uà »û×~zï­]ñ«½Ýwª®óUÞ>»¿W¶jö«^ÏëÙM{¯_Õë¶½k׬õW^¦ëÔ-zo¯KÕé"½W¢*ô^~ëÏ-yÕ¯7õ溼ÌW˜Šòå^X«Êy;¯%uäj¼…WJñÇ^4ëÆx²¯5âF¼=׆ªðµ^kÁõx.¯µà6»þW~ÊïÃ]ôk¾wµ®ôµÞR»Ä×wšîÝ_v+íe}€¯ª•ô¢¾~WÍ ùK_kâm|!¯~µï&½Î׸zöí^Ø+ÚU{4¯cÕì&½|W®*õ™^­«Õ5z¯OU馽)×£Úô[^…ëÐ5yð¯<ç*¼Üך:ó'^_kËmy\¯(ÕäÚ¼’×úñÿ^;«Æõxͯõâž¼KWˆjðë^kÂ¥xD¯uàŽ¼ W€zïï]ùë¾µwË®÷uÞ»Ï×xúï ]Üë»E{¾¯uµî^½À×¶ºö«^ÏëÙM{¯`uë¶½k׬õc^¦ëÔ-zt¯KÕé"½W¢*ô#^~ëÏ]yÕ¯8•溼ÏW˜Šòñ^X«ÊyA¯&äj¼…WšñÓ^4ëÆx²¯5âF¼@W‡ ð¿^ëÁõx.¯µà6¼W*ïÃ]ôk¾5wµ®ôµÞj»Ä×wêîÝ]Øëïu}¯²Õõª¾ŸWÑ*ùÍ_.«ä}|c¯†õð.½ï×¼ê÷q^è«Üe{v¯lí*½W²Jö^¾+×zÒ¯W•êš½HW§úôÓ^–kÒz-¯C•è¼ûWž óŸ^nkÍMy“¯0uå¶¼®W”Êòm^IkÈ­y¯Õãz¼f׋ÚñY^'+Ä]x{¯ Uáj¼$׃šðQ^+À=wü®ý•ßr»å×| ïa]çë¼¥w„®ï5Ý¢»®×tÚø±_«áe|¯€µï¾½ì×¼:÷[^åëÜ {k¯kuí½—×±šö^»kÖízǯV5ꂽEW§JôÉ^“«ÑÅz'¯B5è¼õתó‰^m+Ìõy¯/åž¼«×”òa^H+È}xù¯5ãb¼dW‹zñO^%«Ä5xp¯ õáR¼"Wƒ:ðG^«Àwò®üõßZ»ãW{ZïU]櫼}w~®íÕÝŽ»¬Wtzû±_k+ì}T¯¥óò¾hWÊZù_«ãE|R¯‡•ðš¾W¿ª÷É^ó«ÝÅ{¢¯q•íò½³Wµ öu^É+Øuzþ¯]ëJ½^Wªºõ+^ŸëÓ}zY¯HuèʽW Êóí^ykÎ}y¿¯55æb¼Á×—*òÅ^S+ÉÝy+¯"µä¼zWŽ:ñ§^0«Åexœ¯uáî¼5W̺ùk_'ëäM|s¯‹µñ¾×Áºø ^ûëÞÍ{ïuµîv½Ã×·ö·^ÑkÙ}{¯a5ëνn׬Êõm^¨+ÔUzz¯L•éN½×¢zô/^€kÏ…yÚ¯9UæÒ¼ÑטÚòû^[kʽyF¯&Õ䂼‡×úñÝ_}+îE}œ¯®õ¾ŒWÐ*ùÙ_5«æ|ª¯’•ñú¾4WÅ*øy_ «à…{ú¯|•ï:½ßWºŠ÷%^ß+Û5{P¯gõ즽ŠW¯êõÑ^µëÖ z«¯Suê½7×¥êô‘^Œ«Ñz ¯?uç–¼ê×›úó]^f+ÌEyx¯,UåF¼ ×Ò:ú_=ëç |˯–µò~¾D×Ç:ø»_ëá|¯uïÖ½ï×¼š÷g^çkÜ={q¯lí*½šW±êö^¾+×z̯VÕêš½HW§ªôÓ^”ëÑíz-¯Bõè¼øWž ó•^nkÍy“¯0uå¶¾ïWÛ*û _V«éu}¯ Uó²¾kWÌ ùU_%+ãõ|h¯‹ñ ¾WÁjø^ú«Þ¥{¾¯uîJ½À×¶ºö«^ÏëÙM{¯`uë¶½k׬zõc^¦ëÔ-zt¯KÕé:½W¢*ô#^~ëÏ]yÕ¯8•溼ÏWÓšúG_Ckç½|ᯚòê¾R×Èúøó_ëâm|7¯„5ð.½ú×½ú÷“^ìëÝ{Œ¯nÕ킽¥W³JöI^ë×Åzâ¯ZUêò½SW©Zôÿ^škÒÍzC¯Eµèr½WŸjóÁ^sëë­}_¯©5ôξŽ×Ðzùã_6ëæ-|¯¯“Uò¾7WÅŠø…_ +àµ|¯}Uïf½á׺Ú÷/^àkÛ]{U¯hµì¾½Œ×°:õç^·kÖ=z±¯Tê*½:W¦Jô^kÑ=z¯@5箾ŸWÒŠú%_?+ç5|Я—Uò–¾G×ÇšøÇ_ká½|!¯‚ïê½òW¼ê÷q^è«Üe{v¯lÕíB½W²Jö^¿«×EzÒ¯X5ꮽJרZôß^–kÒEz2¯C•è2¾Ž×Ðzùã_6ëæ-|¯¯“Uò¾7WÅŠø_ kàÝ|¯}õïf½á׺Ú÷/^áëÛ{[¯hµì¾½W°šõç^·kÖez¶¯TêB½=W¦Jô§^kÑm|ü¯œÕóB¾]Wʪù)_«ãE|R¯‡•ðš¾W¿ª÷É^ó«Ýõ{¨¯rUíò½³Wµ ö^ÊkØ¥zþ¯]µë^½`תºõ7^¡kÓ¥z^¯Ió†¾e×ËZù?_"kã|]¯ˆõðÆ¾ ×ÀZ÷ë^÷ëÞM{³¯sµî½¸×¶ ö•^Í+Øõ{¯_늽fW«ÊõM^¤+Óýzi¯JuóZ¾`Wʪù)_«ãE|R¯‡•ðš¾ WÀ ÷Õ^õ+Ýõ{¨¯rUî½µ×µZö^ÊkØÍ{¯]µë^½c׫õ7^¢«Ó¥|µ¯“õò&¾9×ÅÚø›_ ëá | ¯~µï~½ä×»:÷E^ã+Ûµ{`¯iUìê½’W°êõñ^º+Ö•z¼¯UuêV½?×ʪù5_!+ãu|X¯ˆUð²¾ WÀ ÷Õ^ökÞ{­¯rõî½µ×µºö‹^ËëØÍ{ ¯^uëv½c׫jõA^¢«ä}|y¯Œuñ6¾×Âø^þ«ß%{ίwÆW·ÊöÍ^Ô+ÙÕ{)¯b•ëú½tW­zõƒ^ªëä%|n¯‹ñ ¾×Áºø ^ûëÞÍ{ïuµîv½Ã×·ö·^ÑkÙ¥{¯a5ëνqW¬Êõm_kãE|R¯‡•ðš¾W¿ª÷É^ó«Ýõ{¨¯rUíò½³WµZö^ÊkØ¥{¯]µë^½c×Ç:ø»_ëá|¯€µïÖ½ï×¼š÷g^çkÜ={v¯lí*½šW²Jö^¾+×EzÒ¯ñJ¾WÂÊø-_+ßU{Ô¯wÕË׸ö×^ÕkÚ-{/¯c5ì½y×­Úø›_ ëá | ¯~µï~½ä×»Š÷E^ã+Ûµ{f¯jìê½’W±Jõý^º+â|,¯‚Õð½÷×½š÷‡^ëkܽ{‡¯n5ín½¢×³Jö=^Âk×|7¯„5ðB½ýW¾J÷^î+ÝE{’¯o•íš½¨W³úöS^Å+Ø|1¯ƒuð½÷×½š÷“^ìëÜí{‡¯n5킽¥W³Jö=^ëá5|¯U諾êW»ê÷Q^ä«Ü {k¯jµìþ½—×±šø7_ëß­{߯y5îνÑW¸Êöí^Ø+Ú…{:¯d•ì:¾ WÀ ÷Õ^õ+Þ{­¯rõ׵ºö‹^ËëØõ| ¯~µï’½çW»Š÷E^ä«Ûå{f¯jìþ½”×ÀZ÷ß^ökÞ{³¯sµî½¸×¶ ö•^Í+ß%{Ô¯wÕÉW¸ö×^ÕkÙý{ɯvuÆW·jöÁ^Ô+ÙÕ{ïuµî^½À×·ö·^ÑkÝ{’¯o•íš½¨W¹Ú÷^ÜkÛ{¯muíV½»W¶ öâ4-M bÓí ­Æ .C 1¨W¨¢àŽ/]» XK¶Â`¦!'±H #›‚çN¨±œÐ*h‚Š5¢i‚øÇZî/¢e‹^2bµÒ¥9^'Sñ Zñâ9]°Ã³`.¡ú ˆB£¦ zÜ&ƒ‰ 5Â$û0(¼n É/ –#Bþ“P¶'Ò+9¹ ?3bmwÈ“î"Ðr<ÀÁòÁÀ*©ö 9ÖsçÀ–›($$Ž3à'ŒSã"쉘±KF)ïF éW4 o®!__‡à7ÛÒðpbt ;T¯. ¤Z8'X< kØAzàŠfèËe00)”‹cb¯É¢#º&0ýßBšètŠýއ¼©ˆPdC¬£ƒà ¾M€-\æ Ñ”å€Ù%³ª  ‚'1ÀƒÔ Ò7Ø1´f‹¹dbÂ\¦Ò'y‰'rB%É0€åœ<ÛdaªVPd.6Œˆ…‰ÐáMÞ8N¬,Žº„a÷á "ð>¶¢Ø'ƒ„¯ ÕŨ2ÂÆ Š‚ÝPà®zP)Šl âï[: Ä8"H0.EóöÃgs ÍŸö0e© \Û"©âxŸŸþ%T-¹y hzʉD¬ÕáêØ]±döü(/62HôÂ+› #@ôj˜9·Â ¤<ƒ:¨@ûø.^d ÿ?‚œ#€ž¸@%³ªõÓ‚!Ô`âÐé˜ZéÀ»ãTÎðÈB¬.æ¤ õ—BŽdX˜{Ì#…ÃDˆì–xr²®ºÚ;ê!t}èW\\ÄgÁÀCNÅC¶ àßè4´– o?ïI€±¡ *– óZöŽå!å| Ýèn@tpJŠÅÞ,?YÈÂ$D-:Š‚†"pYè¸Ò!ˆ$‡ÄÛÁÍWkÞÖû…ÆoXxP!б]ñáa =8°Z ƒ^àÊ’è/¤Œ 5÷¤/`Ÿ—h%¯`ä© EXNÔ¯‘`f180°9åã‰%c&ŠP»&²+P@Š´‚Näˆ9g=ña¬ bßzßÌ…L :ÁàIÎÖö$ÀìoÐ78R éŽÑ@µs˜*‹ê ÿæ‚Yã@¸!OHÙƒÙ$`o”xQ¶7vù XƒPãB<§ØŽ'ª#HÎ8ÂSx,ưŠI©B\ÀŠsÊ»àFËa«høb3К5S3·hG)’w\ƒÒx`ã 4Öì LêÞ¨@«‡( a‚21ƒÃxåNA)³—@fd¸ ¥-SÖO˸¹¦?ÑߎÝ@pçʹˆ.tꊜBktÈŒó: íOª‰Ða]<?ð…³!+‰(DîÊÝð©n Ø§Ð2, ¡@‚²«@ ˜è%]Ö´a‚ÐyMT²ž‹[ \x ›Ê À.IFŠÈz”GâÛ#cì&ðçà‚5)‡Œ´â¿­xŸ4´#õWˆ£¡ÕQÐj7* 5vÅ¡> H€ä’/Ë:`ÞÁð36¤ Èó¸M@ »@%. š¡þlÀvkhyÔb{ÍXP|BÀ¦ù A¶Ø8¸‡^€ÐñÄÂîFŠBœiY+UHËKp-ÊSŠDLLW¸ƒ‡bi†—ê!zÿUG Aæ„Z¹àü݈9„B è‚þ“`¯%ø(.Ð 63WÀ|Ê0·fŸ(†‡ Z0xÉ‚Ëa=àA-€8qé€ÊÆ€.fø œŒUç<ô„Ä›9rÂA†‰éÛC\Àˆ*šï‰lbJPuÕ2IÌßJêèIö@•fƒº™@×.(0²ê ¯‚• ‘¤à!9ÔŽºKàe `¼FÃ4ý FŠÈ v­þÖO0®  Š~^  /À(~Z(òŽ¥%¹1oÂFq‰Ã•aðË\,€ó‰³¹ÂÞuÌž½§¤è=µ0F=ˆ“éwF`Æ%ð,W ñô=tà¨f“[|ÀUåØmäa*ýx8ÿ8 Ûœâ%¤° $­èÐÇúbù@R؆¸òy&äe½¦A^´RX“öDV€h÷¸6B  »E‚¹p‰\Ö_׆LÐ[®pK^¾j|ƒ–¨ Éøh,†R ×!‚.ù| `¤¾#Æ] N hUtÛÚU0XÐ œŒT†ÑÀxó€?¸€Ar@ ${&qº* „JTÀÎD›èåÞpdTÜS7-“Ѥ=äpëÝä2& Š}²B/Øt›êq'êA( ;î> ä1Èe š~È! ÒSg™ €Ys¨B ì’@3L j]€€¿ã Œ€P€8 S˜º;€8(… I‚y8ìD‰ðQ!XDHg»eDjÓF›lO¢T@.ç‚„UèÚ¢2,iYïcaÌà]JdôÍ`Ó(0,RÒ ]åû¾kŸ¨ݨÞ±d 7§è ¦žgÖ~™À6xí¶éã€(;À ‘ë˜hÄ™:1#ŽÄH3½æODoëñÕ¬Eða^ ÄSÂ1"ŒCÂ{åäD6A9øIvã–Uþ¬€cØ^èÀj<$µc†þAZypD ‡E¸‘à|Õrà/2“ >Ø g¾vÛz `v¨ù·àI“£)æÄ}q?¤Fd?o8DRô1¨äD)yìSÄ4± jôBs½‡zDë1 L@pÁ­Ä~ø9…—DWv’)œi H˜³˜ À¦€ß¡)í"ôU‚=Ilð ÃZ„ÎoÙ Åe²#hDšLñ!zœG%_ˆøDTýÃã_°÷‘Ä=ŸÇT¡ÃÑðpó‹Ì<¸ #GÃÅÞðËü<ùoü6°îs,;ëƒkà ç™›Û°–qˆk«‡£$ºØ@Æ~y›ž@ :üF†•ÇÙÄ º°û¨ä=›}!"C»h°ì£¬:£ãCž,0æ«ä9s/OëÃ’Päl8å‘6,CŒ½âûÄ8º§,„CŠÜpâ¥ì8©{)LC‰…â>ì8»"ÜC‡éáúD6üTƒ ¡„]Wz<„}ìB(ŠžýL*C úúÂϤ·ˆ.x; ÆÓÂù|0À„\0Œa 9 C“PÆT1ù) OC(lpËœ2þñ ÎÁÃ7¶Pλ”3Þ yCCÈPÑÀ4šï 0cÃNÇÔ´5­ M[CTòÐÕ\5q ÄC2©ôÚX˜n²dã®Â: å¶øÿ-½@™²PFœT3•ÖøAç¡°ƒÕ"ü‡ -£ÂcÑpž ü(•ç dÆB¦ß­#\+ô &÷ÂÓªp·!.@q ®&Âó 0¾Mä/Æù þÃpÁR\0 )ˆC °ÃÞ¬1K Q:Ã/PÆü1Ÿ oEC 0È$29‰ ”ÒC&‹ÐÉçœ2“§ ªGBÅsP²€¤,é ILÂÖж¨ô-Ý» †tBå^Pºl.¼å ½+Âñ´ð½;&“P¥ º±”F³¿ú¯'ŒiXKC_ÀÚV¸šç.T󸫜òM×=fH„ƒéôºüVR¿xÒóÌ#ÿCEa¨@7ËÐÜŒ9¿<ñÀ¨·0T z}²NÁö0Dï,[SÞ¶ÁG€ðUæ4aKÑ/ÁùÐc¶l·OžA²†or„µH²AÛÔpy<ÄåeÛ®Aþ÷Ò ð÷Y5BÏð‰!$"³“Ç·B8‘$>e %BNå”Ý4%# tsBa𰙎Ô&¬« ¼UBrÖÈL'®) úB‚ oÚÚ[ùTþòº¿àäp½4?)ÎÀ^kpü "û¦@¿È°5`¤•»ò®A_pHü“ÿùAOŒðW_Ü·!ãlÁ„í0dbÈyŸ.A²ÊÐo-Ôn‰BBAÙjpx¢D©OË–Aû60€®4 ŸmEåB-ð‡ÉÔ"]½­÷B1©°l#Ê… ñBFÙ’ëd% UUÂY —‹Ü&#W šBj…ð›¢ü' ˆ³2[®™ (£,pîÓ–­6Ø"‘{ŸhFì•Z¼¶:†ÙO<øº>ž}¯´Ãûðd¬üÍi¿_WoâKúÚ˜ÿEW¿ñÊP~„êÓ+[Àe©psL V{¬†@ÀÛP5ôsgæàÁ âÐFò,±)è^ÁIðU¡ŒCAÂ*Á|á0b+œ?%z³Á©glÕÏÁjAÏ90vô;¢ÑAðÀP}ÿŒüWûB sp„øÔ! ëÔÂ%—0Šßt# g×ÎÂ;Qø$O %BN”‡d%jÍ jËB^ýo{L›âÌ>ùŸ>ž}¯´KÓð ŽüµÏ?V8ïßX{ú"ÿ ?äašäö5çÄ@T:0T2©e¶À­Ð00Œ¬ KI›ÆÀø0B4‰ DÁ6VpQCx#Aj°]‹$0¬A—*Ph4Œ«íÒ­Á½@°q—Ôó“\ÁßQyÆåe×cÁüHЀЌ ’=PBõð†·$"é—oB+90ŒD#R[æÔB>‰Æ$zy .¶,¼:kÀ*ÿ*Ç c³ž+íZy»q ââí9Ç Î¶9S¼Ìò2=,X]Ë{Û´ì÷쓾5Ï›ZêzûCª>ýµÉô{ôóøýÒ¿—ï¯î*3ý|\ÿÔ @, ÌJ»tPÀsWP"Ut Û…Ä ÀÄP5qÌsgÜ&A!pE‰¬EßÉAÁ?¹°S&d‘âÁpа^À$[³@ÄA™ØðhñdÒÖøA¾˜q¨üïI\ÁÞƒypDÃË–Aùš0à4 ZÁ-8B4°…¤|!¾õ„Â& н"ü‡Ñ_B8çLk«¾Øì»ú;›änÿƒëŶnòÂȼÿRBëØ×÷4 ¾À/óÃç( ú~>ÐaO¿“ò<¾ý(›¿mÓOãÖ›úôXÿ4-?êŒPFŒñéÝ ÀN˜0H¥ :Ì@¡¾P-!4 _?\z@ç¿°=Âd`íQA"8KùœÃÑ$sÁT`ÐX „²ÕןÁ€¢b’œ?%uVA¥êÐk±4yëþªÁǶs½$c't¯Aã›°z”¹Ú›Áü€ }5ÍÂŒ…úT!ËÕ‡WÂ&e0нà*r½ÕV1ÉÌ©.sDC2×E½·=$Ž!m«‘¶æ™Fº)5î¨zC± øíéu;ÙÙï T#ȇhó^Z=ïOXæ[Ú!„÷u|¾Ï’*Cç—žú^>Ò†¾Òãòhýq?hvâKú|.ÿØ?à[þº<=«± ÀB†P2d× '@”™°)¤Œ ˆ«#@ÙCÐ:Dtå;AxH8Tà]çLAEäðTJ<ÆÍœAqð^â|S:TA—³­Õ/Ã'\â!•9HÎf²C¡sÖê,r:ö!®×%㻚¢ðKí¼cü/,NãÏ–$ôñÁ½z!Om|[ÞÊø…ÿ¾QÇ Ë걎ûSÂ>úÂOÈ$ûô2ÚýŽˆ?ƒHè!;û×Êÿ_?òð{„ÕàAÀM@ðzS"@™öð*ê´ Àw/j@Ûi0:yD†;Aî°Gó¤ÂR·dP¯ÒL`‹O*Íf4È*-^³bM Û&(·híÿ“ˆ±>äLC9S.‚0c§gV럻A;Îç“›¿,ºñi¼Ž¡Ï5\SÑ{õYÓ½íÏr «ßºäø³¾[o¡¸ËêþÌû[D¾ü^Oǽûô†ý€•¿~¹/æÛûh6ÿEW¿é¾p‰´†¡¹¢@C°ÜŒ§ÕóüÀŽ÷°'æ< £þ@ÏàÃPºÖž_6J"m¹J£w± à ¾¸çîCö;˜‘TçìNºeJβß²•Êî;Ö¡ï _SÇàòÖ¼öøOM*KÖܪö–S½Ù‚Š‹ã€|ù‘%¾S/­03í¤ÜûöÖ?<¯ÏÉëõàýêÍ?—!¯ìZ³ü®^ÿ‹?ú¤0\aééÀMÊP5\,ß.ÚÖÌÁ›9(Ê—³ï,%^#SPþ×Oe¶l2í¿ÿCxÌÞàK¨¸•E.C;˜tç¿?:WXN®O³±Sìí¬M»½ôψcÅ>Dò[ɼØdE[Ôï$öo½¹)oz9óátêù ¾lUo¥ë•ûv>þÈ/È«óåšýp}¿w¿ïäØúÉlÿ"¿Ý#/üÙ”–Mx/À2)ÌäuSCÓ3øµeœMChÆ(ÜZ7žÎ‹ƒ‰Œä8ó¹}HyΣ¤£:ꤛ»îîÕ¬;ºe¤ïì<9Ïå«ËìóÞ½0£/Y´[Ù Æ÷*b½øvO‰ £äàdùÕϾ›¯‰î&üÈ¿ دÏtõ¨8ýØ¿(oêF‹ü Jÿ_2 ¥ ©¦›3êôÏWØ4nO@uCY/؆ˆ¶ªm Ë™zözव8žíC»—‹Šç~ߺ>«.¦ÞC¯Úí „;‘nnöÌKÂñ‹¦< šO7<óÑP,õ+±=}kycݯRøÛ>/ûÏ•ü»çÜHú†Ö¾Ä ¯¹ÃðBXü†š?>ãÖmc÷H~þ4Ô3„°­P»JC’Ô̽5»µýËl`ØÝw7¿:Î #Š<äC®9vN®uü+£XÈê9R:âHŽÌ|{·áêï¼ ªÏöÈ‹²óõ¼ú¹¯L£Ö(löJ'=¿ÞzÔkáJøò\>aV/¡t#ê‚Xû%Ÿ¾è@oÁµ ò8tüüž´O2m6f[UûZ׋z¶cXí¸7ûuzß6Û8>ç.*k‘âåÒË9Ó¦‹Nû¨(tëU¢»"®Û³»/XïÙ!<5P¯+ʹ˜ó=ãoRBëׄ ö˜y=ÐO~¦ãâJùY>jþ£ˆCêéXû:>ìFmB"kX¥´Ø‰6‚1M¾…›vÒxßy_¸I],(;‘OåÏ“9În‰nS§’@ë#5»ú.×7 ºÐﲯŽz忌×Vè6HË-®Ã“r@ÞI¿·ú=M“‹ÜXäjM¹rnr]3¡§Vé¤0º´jî¿$k´ &î Ê;Â?nÿa“Ãòñ»í¼£ÒO5K#Ðd"ôÏl½`êÏbŽKÛ4,÷hœ½ÿ´-™Æ“mLàÜö··£ß ë† ãÕ¹ýn] [œhèW˜ºbœ.ªÓ¯pìÓ”»t»nìß»¾Û0ðw¼\¾/$1ËÌ*ªóɤ=4Rî“×Y öw6½Ä(­¡8nÊÒÝG.·µ’îC㇠¬ã,»9!n]OœR èH“:[玨òk®‡<ì¥r»iwné@»½órðZóè(cº=˜Ž/`3ˆ0äÇ¥9iø.h+ëmÞè+›º=Ý.1…‹ózäÞ,¹oUni ‹êè0ø¹ºNO\s—5¶æŸ¶¹Û(ŽC9c”5†åߪ¹«jNRC—ÃTæ»›¹NW®`1+•7}d€ÌP»˜À Ý@ÚØÇ@âø´ˆ¿ÀÀظ¸­¸ºP¨øÀp™`ª@ª¸ÆØ£¾PΠ¢˜²¸H³Ð´¸½XˈÈР°´P¸®8©˜ à¢8»¯À¬pªÐÉ0ºÀ©p‘˜‚  °@ `xÁÀ¨˜©¡¸²± ©X˜ø8ƒØ|`d` 0$è$àÐ(8X¾ð·È·®Ð¦“ˆŠ€‘@ƒP‰HtÈOÐY0`Ð!ˆx#x&˜'h&`ÐX(À°h°(¯8«¨Ÿø“X•@‡@|¨€ðrèg¸i`È{ð[àOr O84˜*@A°BH1@$@ø%0È2 *("Øpp €&@¦ˆ´Àœø§¸—ø«X°’Hyh„0†XO@a°i gXbyp‰ès¸oÐO@Fà?¸98;Ð4/000*h%x"È"Ð%è'€$ð¶µH«ˆ ‘˜®•8‚°œx‰0DMNXk`N(_x`ˆ\X``U¨HpG?pGè;€0Ð+p*p--ˆ,*)°,/Í ¯˜™Ð”¨œÐ®è©xˆ@–ˆŸpQøyð|èp`\_èM`QU U0D NX7ð/(&¨0:5Ð1è.+*¨.˜8(>ˆ¢@£Å³„¸Pˆø¤øPYÀÐiørW`eàUÈHPN€U7@;È3`0Ð2¸: E3 $Èð!Ð+Ð8à<`1 _¨ÍªÀ•ø¹ð •P|p0n8cxn8^¸NxT˜PG`GxMÀB°7È*#(h$ø/@ˆ`à Ð"P(à(@°v0X Øc0Œ° øª0q°^°sÈm`u¨`€b@[XIàXhH8E˜JˆBˆ7°20 8¸ Ð;70!È'p&ˆð8h(ßÿøfÙ ˜Éø±`ªèœø‚€thp˜hàqpq€`b€N`N`THKHÀIx21h(Ø.'7ÐEp*à%hø(HˆHçÿøåù%€à¾¯¢ðxŠðuP™°j¨ w(\ÐXXMhS0FO8?¸=ˆ6°>€. 1 +(=ø1`/.Àè`èpˆ.qrp xf¨zpÀ`С‹Ø•ðƒˆtøjøjxeZ°Z¸F ?D8>Fà5 =¸?à<(8@5è@,Ð$1%ø"8#p€xF`²à¨Q5h§°˜ ˆÀa€˜p¨]¨v0‰øz0a f°nàRPWø^(Q¨LDÀJ IðA@<˜?06 *p4h+Ј'Ø'h*ø78#øÀF@ÂAUE€r(ö@ixt¢Ð»8n¨gkˆa°„P„˜nlhoÀnaXTPX@b¨ThX S8GøAh33p-h5(0¸#0#€ €!8 ¸*ГÁ)isù ñVÉŸhÌà¬XVànèz؃€Ð‚ðe¸g8Ègp{p~ ˆuÀ[°`T(U0@ðJ E8FÐ8è=°6X<Ð7&Ø+ø+а `%kæ!1±~ùÅùL9šP”`—@_a¨kÐ^ |`jÈf0hxkÈm‚@u`aÈWðZ¸VpLˆTV RøR€@@DÐ=ø9 >89(:¨6* %`80  .Ð}¹[™«ÙjQ¸èWØ}¸\0eØlOu°d`hw8|PnHw@jØe0p`epW°Q(Q¸Wð_øRðF°;ÈBA@,ð2˜58+'À%°0h2 † %I|QÈÙ5 ‹ÓØ‚‚8xÀvÐz¨}¸y \øèlÐxz˜m8jhh P¸V(bÀ^VP¨9ð:P7à3G AÀ<à;`6p9ø3#(8$À2`•9=)Ž>É„ø}¨tØg`z@mÈh˜|@THZ¸fð†ànÈx`c]xK¸[ bT H`7h:pB =˜@<(<Ð8& 5ÈD`-X`"8, %C ±y;Aqá'1KÐ]@[bX]øjj0t@|xHÐf¸u(yHh¨dø\P]Àd°Q°OèJà>°PÀLF@@`?ØHxCðCM¨FÐ2 *°/À1è: g´À÷APÇLðxàn¸o°d mÐwÐy˜xàixàrðvmXfpa8aØY`T`UHV(RX@UPE`KÈM¨IUèZ@Sø^èn¨jH\PQØVÈjh~ˆ°Ã¸´àg@gèeØt8v(p o`jxUÀ\èhb aXpb e€NNXNøPˆJðMxL˜R tÀ¡@Îðé¨ç°ãÀèà÷©.)AéHÑC‰3y Xù@ë ßÐáâä èÈòÙ)a%¡AU_ù]¡VUq]‰páŠñŸÁ¬®é¦¡—‡Iu¹e‘Z!K¹IH¡KNQT±]él}€là‹¨t¨Uøa(yHxPyxf€< ~€kˆ\(f LxFxY8ZXHRÈu0–À¸ÅöA ? Zñv‘“i­Á Ê©Çy¾ ·I¶±¿1ÍÙÚ¡ã‘æ¡åYá1Üy×!Ï™Ç1½i¶¯)ªÉª1ªa®!³q¼±ÇÒA!É3áD I¹SanQ“yº¡Ô‘âyíÉúÒ Ê R‰øIóqõ9ûQÿiÿ1û™÷¹÷qûò" R ‚ yü)ôÁïëaêÉë±ï‘ôÙû(rà‰Èz€qk(vèx°e8xГXhˆvpiXNPRˆ``Wh‡IX1ŠQ¥ñ¸qÉ‘ÕqÛ±äyöbú'b/ê."(Š%Ú%B!ºúÊ ‚ "’J¢qÿú)ý"’*!j'ú(2#ÂÊ* Ú   ò : r ™sáÆñü"’B!j(j0Â4º3‚5r<ŠCòDŠ=j3R)’ú: ÚÊ Jzb Ú9ýQû2Ê â#"'’&Ê"ª’¢brŠB:ò €~pÈŠðvXh ^¸o¸tiX}ƒ˜{ {pijzˆÅÑ-9„©×Jr6*CzEòBz=">RE*F2;Â.b):+Ò/š.b&Ò* áÿõAñéõ9ü2ª‰ÿ1ú÷øÑÿ²úêRRbJòª*ªŠ z¡ÿé_¡”ÁÜZ¢@ŠH’A²;új/*.š0’-‚-.R) â  Ò ¢Ú1û!ô ïIíéî¹î¹ìAè©æ!æQèáìiïQð‰ðïÁïÑòQ÷qÿ¢ ¢êúzú:z êá1«ÊòF2NÂZªdXCb:B4Ê)¢#R( .z/B+’%â *ºRB: š Z ª ÒÑþYúAøYöAóQïÉíií)íÁîíaë©é±è‰éAìaòQúR’ J‚*úr²2 ¢ aàrh@‚ i˜£`„°ht¸}ƒÀrБ²q‡ÁóJN2s:fJ`"j‚^ 9J#r*ª0r%RJ"(Â,z%Bb)Ú,º%ÚB²ª"ª!šê RÑÿòŠRaþQúéö¹òîÙìÁì‰íáðQóIöÉúÁÿ2Òz J j¢âª‰8 „±æ‚/¢hòn‚R:PBMÚ0 ’š)Ê"ÒÊ#z%ê‚¢z& 7*>²9b0Ê.Ú3ò81º!’2Â: ª"ú Ùüó‰ï‰ñöþÊªÚ ÿ9üqû‘ýªÒ ššš€n˜G¸r€”@fXv8‘Ðw@’ ‹˜¿`âÉ|ËiîRz5J[yB…êz2aêOÂ>:#‚ ú¢ºj+â64ª+J#‚"ª$#Ò&z02?2KÂNòJÚEA ;J/z" š²"*ºj Ñýyóññéø*zÚbꊱù!òqñÉöþê ’bzzÒYÙ6QDAsἡøj;ªn2s¢jRdÂu²ˆz|haS²0 Ú:.‚*Â'ÂâL:aJ\ÂAŠ/B5EªIê>1º5 FZUjRê?*&JÒ!R+r/:, %R’JJÒaTi·IòR!rOZWŠ:ê@B`ª_@5R="GJW’SW‚nbfJGº:ZG"TâWreòÂ…²oÂX:P’S2QrIŠE:IºPRNºBê5J127<š7b'âr r  *:6øƒàyà|°•нÛ 1!‚YÙb0âHŠXb‰*•2mê`²n*Wr.ò"B&Š#Ê+z5‚7*O"`Pº<ú8âJ_Rm‚}ZŽ ‘⃒i"QBI"Qj^cb`jZºVºP:Dê6â+#Rq%éyñ*< ƒÒl‚†ZŸš‰jwyzeâDÒ8r84r-’$ò 1Y WjEêBZF‚RºdZtú∈‚ oX²Qr_ês¢yªoêdòaR_’Ur@Š)¸>zÈ€ø¿ù ý’Ej*"”ú~*€R‘ÊŽÊjŠzÊ`Š^2Tú="5²0BjK‚j²T:IjPjS’ZjB|B€’xZsJtzn¢aÒ]ji x:zêoÂdŠbBbrZIÛb=²gjuÂ…Ò†ú{z"ˆ…Z‚¢*–Jo*dÒj¢Lº0¢3ª)2 *"ú`ri*TšVš\Z[šcRwR†²~riâb2išm¢fŠ_RbbiúiÚ`2W9B)¯ZRWòd"dúu‚{út zÒ„Ê}:|ª˜Ú«B‹šbÂaº_ÚB246Â":ò2º[RYRU2YšYÊ]²mÚƒjŠšx2_rZ²gŠphÂ[ TòVòV:W ^â[:i¢{vÚxRƒ*yŠljx2˜*¡ª€‚_r^X2AZ/Z&jJ J?ROÒPBN²H KŠ_Šw„ò~fRJUrhbsÒkjXšLÂ]òcšz‚ƒª{ú}Jr*]bY*gÚ„¢’‚{ªcJ^jQÚ- * :š7ŠGM PŠDb9’LZlÊ~Âyzc2KbBN²ebrJjzZÊyê~òxúkÒOCF’Utª…’xh"^šBj AïÊ Š;BLjH KLúBòK¢l‚„‚‚jj’Mª<Â>MŠ`‚]Je²iZS’:ª;"B2Tbn¢xzriŠY¢3êª ’BªhJ\ÊDR<‚> PRxª•"•2ZfjQjK²Q"ZZ)âFz[2I‚<òAÚNarjÒiBhZcšSª8B, Q"…ÚRg:9* B.jhªŸR¥}‚o²i’lB/MRDB@2K2bškê_rY’_ª_zWªQ`â‹B¨R–Úa‚$*/ú„ò©º“j{âwZw¢yª0r1ò=B\Bu’hšS2WZcÚfziqj„rœª¢J‡ MÊ aýEz–Jšv:izm’kbšjEsz{ÒbêX*d’m¢t‚z‘ғ"ÊsR8 øÿjSâš}Jdc‘äB²X€R{ši"eškp"‚âr¢Â•²Šâ„‚iê(AêúšCzdB\â_àz$’fB„pzfºeêr”"²"­Z—:‹ƒêbŠñáìR:,Š=ëº6ªt ŠJ† qšdÊhâ~¤¶¢¦Ò²†Ú{ZSÂà™àqðÚ9ý Nâ…*›Z’zwbobyâB©"¦òŽ¢~Êx2hšCŠùñèÉèÂcÚšŠ´zœò|z~‹BšÒ ZŠ:rkêiª\Aš$âbªl*«¹2Šºt:†J“â™2Rpra‚f²j:cbVBEr5*j"¨B—ªbkº‹š‘2‹ xZcšcÂrZ|Š~2vJeêu*«"uzKúpBŠ2Juid²qš†š–’œ*’:ÔJéR€ªSJrzy‚jbbrbªlš —êª ­ó¥3bŠ¡ŠU²a’bÂZ’Z‚d*v‹Ú :®¢­“ó®Rž*B‚KâW X2\JkÒ’’žª¤TúŒuú4HÂXê[‚còyŠŽ*–B• ”‹*ZŠC SÊY*\zoÂŒ› •ZÁ:iò`*VbNb\ºúÚ¡r‹BljFB>êabâ§Bœâ˜j^j.ê8šm›š£ÒDê"*?’{jÂ5º(R‡Ò>Rkò^ú…a8€tK!t¸V­•jÂÙRžJ£VÊ”êIZªS‰9øìpT'SÙÌØe"U)Ä‚ôp4KeQèø€B"*“ˆô!àÜe.•Ê$²0Øn9Äâ=x6˜ËeBi„=†ˆÈf5ŽCÄê=w6‹%Q |9Œ…âÑX¨T* …B¡X¬X- Æ#! ØpŸG ŽÆ³ `žG †ƒh¨P&‰D¢Q(˜L&‰Å¡X°\0 É$ØÖ`+“ˆÃá¸ÄX(ÄB€B!ˆ„B1J' …‚á€É.†;šÌbix4 ‚øt8 ‡Ðèx>DBA0 T,§G£a€ªL!ŽÆbÁ(€: †áp¸`0 ÃaÀèx@" Å"±j~7˜ŠÄ²äb*‡ƒ!p¨P& …P°\0 †ÃáŒL()Òh‘®L Ž‘p. ðx<  ÂP°\2bQC^šCLÅ’ip/ˆAPˆ< €Àh8 Â`Àl: ‰E ‰{5 ä1À¼L „ÁàÀP$A  `4 Á€Øx@#¿[Ì%RUo/”H£‘x˜> @€8 €à€P0…ÁØxB#C]Œõ¢uv2IÁ€œ> A `( `8$ „B`ÈpšËdÁøÌR „Á ( €`0  á  \2D‚QJìf¬Óˆc™„¤D Da° 8€€ @À€P0 Ã!Àø„F$ˆÄÖ ¡"|5ICÑ >‚Àà@€@@0 Aá ¨\4ˆ„bAŒJÐZ§‘CLŠ8‰Pp$ €@À€X4  †AшH%‰ÌUbUn.“ˆ#QP„2À€ €@@0  ‚!0°d6‰¢A2õHŽ= $¡ðÈN A``@  Á ðT.‡ÄBQ0˜NÑ[§Ñ'S!TŽ;‰C¡`€, €  `8" CaáL(²–)¤!ÈÂR"ŽEÂ@àT ` @À€P0 …C°è€F&CLUjYo/”Hcq`ˆ4‚ÁP  `@(„!  T, †ƒÐð€D% "¡`´ ƒáˆH& Ã! Øt< ‰¢a@¤T,‹› õ:Hüm.“ÈCaX„2ƒA@p( €àp@$ AÀð€D$ …‚áÐl:DBA(˜P) ¢áx Àà€@$ ƒ àx@" BÁpÈh6"! ”L(Š…‚ÑpÀbÒ\¨‘dzYpœAŠÄA ¨H àp8 P( ƒáˆH& …ƒÐl:DBA(œP)‹¢á€Äd ‚A H$ Á`Àh8Ba@¨X. †ƒÐð€B" DâP¬X-Œ#1£5j FÍeÒyn,CP˜@ Á`°P, ƒ€Ðh8BA0 T, †ƒaÀè|@!‰¢q@¨V, Æ!˜Ðj6 Aààp4  Ðh8„! L( ÃÐp:ˆB1 ˜N) Å¢áxÄd3 †ã‡35l¡FŸMÅò‰r/ˆCÀX* „Bðx<àð€@" ÂP¨X. †ƒaÀð|@!Ä¢a@¤T,‹†Ðj6ŽAÐØh0 Ba D ˆH$ ¡`¸`2 ‡¡àø€B"‰¢q@¤V,‹Æ˜Ðj7GC±ã© ¹R$PPŽ; Ä¢1|: Cp¨P( ‚A ˜L& …BÁpÀ`2 ‡¡àø€B"‰¢a8¤T+‹…ãÌj6Ž#±àô|- Ä¢1|< †ƒ!€¸X* ¡P¨X, ! Øp:ÄŒH% Å¡X°Z/ FCA¨Øp9ǃÑùïj¯TéDÌÈV$†Ã1x°T'ˆÄ"øt8 Cp¸\. †Èh6CÁñ„D# D¢a8¤T+ EÂñˆÈf4 Ç‘Øðz>$"¹@”E Q¼X* „‚!|<ÃAÈd2 ƒa°àt:ˆ"!ŒH% Å‘P¬X- ƘÐj6GC±àø~@ ˆdHƒÍ°ÀU¥ÐÇSi˜¼X(“ä!ðìp4‹Å‚¡@˜J#áàèp8 ‡Àèt<ˆ"!ŒH% Å‘H¨V,‹…ècñØÜg0JdâI‚= ƃx°T)‰D‚1€>ƒÁàø|> DB!J&Š"‘TÊ(÷n1ÊDÒE;›ÍÙT K#ÈÑÈØh1‹Eb‘@˜J$ˆ„"„B!„B1 J&Š+¥‚•6‘D çIˆ¸W)‰Dbüx9 "ñp°T) „¢Q H$ ‚Q(˜L' fчێ»X©“‰$Jðq5 Å’©@˜I"‡ãÁÈØh2 …¢±P¤P'‰Äâq8œN( E-FBõd§N¤ÑHÉÈØf0— å2y,D ‡ƒ‘¸Ôf1 …¢ÁX¬T* …B¡X­üðqµY+å’¡:”E gCq Æ^-ŠDâQˆAC°Ðd1 ÅÂáh´Z-‹\mfRùf¨O%QˆCéØàj2˜K…‚¡DšJ#‘Cñèèr7c!ˆÄ`0 ì¥úÑRŸK#PÇóÁÈÚh2Ëer¡@šJ#‘HDèìt8†£Q ÐhË_­Jº==N³1ˆ¼Z+ÊÒQŠC ‡£±Ðär80ªµe"ŠB3q¤Êa.–JÅ24”H"È$øøz<0VÊÅ"m(C Ogc‰°Ðd0 %b™@šK$ˆ¤2€·W)“Ét‚)~< æ³9Œ¾\,•Šeq0”H#‘H‹•‚¥BšJ#PèáÜän5Œeòád¬T(“ɤ²Q%f«R'’éZþz:œM¦“1ŒÀ]-Ê¥"=l¯T(SiDr!~<ÃI˜Æ`.–Ëb¢åd«R§“ $b|<ÓQœÈa/ +Õ²½P¢M¥Qè¤*öw9œ ¦£9Ä_`.VjÅ*}2“F¢góÙÜèq7ÍeêÙ`©Q§Éb~=ž§#y±€ºY«TÊÒQŠC çÃÉØæÄ^­Ö ¥u0“G"È3ùðôÈ`®–ŠÕ:…6–I#‘Ht–Ä_-Ö*¥"}4–I#‘h†{!‚»Z+• 4òi,’G£LÆ*ùp±V)”IäÒY&Ög²XKµª½T¦Q'“M¦›1Œ¿\¬ÕÊ¥2‰®Ðd°×«u’¹TÔf±Ø+µºÉ\Øi2جÚݬÏd±í¦£9’Øi3[­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$‰D‚A €@ €@ €@( €`€À`X$ ‚Q(„@ €@ €@ @ P€@`0‚A €@!ˆ€@ €@ €P( ˆ`0€@P €@ €@ €@ €@ P€€@ €@ €@ €P(€@€@ €@ €@ €@ €`P €@ €@ €@ €@ €@ €@ €@  @ €@€@ €@ €@ €@ €@( €@ €@ €@ @ €@ €@ €@ €@ €@ €@ €@(€@ €@ €@ €@ €@  €@ @ €@ €@ €@ €@ €@ €@  €@ €@ €@ €@ €@ €@( 0 €@ €@ €@ €@ €@ €@ €@  €@€@ €@ €@ €@ €@ €@ €@€@ €@ €@ €@ €@ €@  €@ €@ €@ €@ €@ € (€@ €@ €@ €@ €@€@ (€@ €@ €@ €@ €€@€@ P €@ €@ €@ €@ €@ @ P(€@ €@ €@ €@ €@ €@ €@€@ €@ €@  €@ €@` @ P €@ €@ €@ €@   €@ €@ €@  @ @ €@ €@ €À` €@ @ €@ €@ €@   (@  €@ @ 0€ @$€@ @$€@ €@$ ‚€@ H €@  @ €€€€€ €@ €@ €@ €@ € €€@€€@€€ €€@   €@ €P8€€@ À`€`0€`0À`€À`0 7777metkit-1.18.2/tests/regressions/METK-103/METK-103.sh.in0000775000175000017500000000070515203070342022035 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.18.2/tests/regressions/METK-103/corrupted0000664000175000017500000010402215203070342021757 0ustar alastairalastairBUFR¡Òb€Ë!4Ë~hbçDiËf7aîn†ˆA˜Æ´.¡Òd jF ´ÀÌF¡r €(!EÆ,õp @ €`€€ 0(bPÄÀ€ @(€`` €&(P 0@€@ („Q¢0D€‰@ 0`À@€ €€ 0(`øÂ„ €€ $J0˜pà À`€8 pHà Á`ƒ€:8xpøâÄ ˆ€€$J8˜€ `€ @ €H `€ @@€ˆ ` € .@`€È `€ >@€ ` €&$H @@ € €$H(X ÀA ƒ€ €$"HH˜!@B …€ €$2HhØ!ÀC ‡€ € $BHˆ‘"@D ‰€ €(P   @P€ÀÀ  (P, `@ÐÀÀ ($PL  AP‚ÀÀ (4Pl àAЃÀÀ !(DPŒ¡ BP„À À €,X°(``Àà‚ € ,X0°h`àÁà„ €,&XP°¨a`Âà† €,6Xp°èaàÃàˆ €",FX±(b`Äà€ €0 `À8€€ € € 0`8Àx € €0*`XÀ¸€ € €0:`xÀø‚ € €$0J`˜Á8‚€@ €€ €4 hÐ8 €A ‚€ € 4h8Ðx¡B „€ €4*hXи¡€C †€ €4:hxÐø¢D ˆ€ €$4Jh˜Ñ8¢€€ €€8 pà8À€ €€ 8p8àxÁ‚ € €8*pXà¸Á€ƒ € €8:pxàøÂ„ €€$8Jp˜á8„ˆt)è’!ѤC¤H‡J‘™":D:„ˆu)ê’!Õ¤C¬H‡Z‘¹"zD;ˆv)ì’!Ù¤C´H‡j‘Ù"ºD;„ˆw)î’!ݤC¼H‡z‘ù"úD<ˆx)ð’!á¤CÄH‡Š‘":D<„ˆy)hR"Ñ$E£H‹H‘•"-2DZtˆµ jR"Õ$E«H‹X‘µ"-rDZôˆ¶ lR"Ù$E³H‹h‘Õ"-²D[tˆ· nR"Ý$E»H‹x‘õ"-òD[ôˆ¸ pR"á$EÃH‹ˆ‘".2D\tˆ¹ rR#Ð$G¡HD‘"="DzTˆôÉéÒ#Ô$G©HT‘­"=bDzÔˆõÉëÒ#Ø$G±Hd‘Í"=¢D{TˆöÉíÒ#Ü$G¹Ht‘í"=âD{Ôˆ÷ÉïÒ#à$GÁH„‘ ">"D|TˆøÉñÒ#ä$GÉH”‘&}"MDš‰4IhÒ$Ò$I¥H“L‘&"MBDš”‰5IjÒ$Ö$I­H“\‘&½"M‚D›‰6IlÒ$Ú$IµH“l‘&Ý"MÂD›”‰7InÒ$Þ$I½H“|‘&ý"NDœ‰8IpÒ$â$IÅH“Œ‘'"NBDœ”‰9IrÒ%ϤK H—B‘.‰"]DºD‰t©é’%Ó¤K¨H—R‘.©"]ZDºÄ‰u©ë’%פK°H—b‘.É"]šD»D‰v©í’%Û¤K¸H—r‘.é"]ÚD»Ä‰w©ï’%ߤKÀH—‚‘/ "^D¼D‰x©ñ’%ã¤KÈH—’‘/)"^ZD¼Ä‰´ hR&Ñ$M£H›H‘6•"m2DÚt‰µ jR&Õ$M«H›X‘6µ"mrDÚô‰¶ lR&Ù$M³H›h‘6Õ"m²DÛt‰· nR&Ý$M»H›x‘6õ"mòDÛô‰¸ pR&á$MÃH›ˆ‘7"n2DÜt‰¹ rR&å$MËH›˜‘>…"}Dú4‰ô‰éR'Ó$O§HŸP‘>¥"}RDú´‰õ‰ëR'×$O¯HŸ`‘>Å"}’Dû4‰ö‰íR'Û$O·HŸp‘>å"}ÒDû´‰÷‰ïR'ß$O¿HŸ€‘?"~Dü4‰ø‰ñR'ã$OÇHŸ‘?%"~RDü´‰ù‰óR(Ñ$Q£H£H‘F•"2EtŠ5 jR(Õ$Q«H£X‘Fµ"rEôŠ6 lR(Ù$Q³H£h‘FÕ"²EtŠ7 nR(Ý$Q»H£x‘Fõ"òEôŠ8 pR(á$QÃH£ˆ‘G"Ž2EtŠ9 rR(å$QËH£˜‘G5"ŽrE:4Št‰éR)Ó$S§H§P‘N¥"RE:´Šu‰ëR)×$S¯H§`‘NÅ"’E;4Šv‰íR)Û$S·H§p‘Nå"ÒE;´Šw‰ïR)ß$S¿H§€‘O"žE<4Šx‰ñR)ã$SÇH§‘O%"žRE<´Šy‰óR)ç$SÏH«H‘V•"­2EZtе jR*Õ$U«H«X‘Vµ"­rEZôж lR*Ù$U³H«h‘VÕ"­²E[tŠ· nR*Ý$U»H«x‘Võ"­òE[ôЏ pR*á$UÃH«ˆ‘W"®2E\tй rR*å$UËH«˜‘W5"®rE\ôŠº tR+Ò¤W¦H¯N‘^¡"½JEz¤Šõië+Ö¤W®H¯^‘^Á"½ŠE{$Šöií+Ú¤W¶H¯n‘^á"½ÊE{¤Š÷iï+Þ¤W¾H¯~‘_"¾ E|$Šøiñ+â¤WÆH¯Ž‘_!"¾JE|¤Šùió+æ¤WÎH¯ž‘_A"¾ŠE}$‹4ÉiÒ,Ô$Y©H³T‘f­"ÍbEšÔ‹5ÉkÒ,Ø$Y±H³d‘fÍ"Í¢E›T‹6ÉmÒ,Ü$Y¹H³t‘fí"ÍâE›Ô‹7ÉoÒ,à$YÁH³„‘g "Î"EœT‹8ÉqÒ,ä$YÉH³”‘g-"ÎbEœÔ‹9ÉsÒ,è$YÑH³¤‘gK"ΚE<‹ZY´Ò-iäZÔH·L‘n"ÝBEº”‹uIêÒ-Ö$[­H·\‘n½"Ý‚E»‹vIìÒ-Ú$[µH·l‘nÝ"ÝÂE»”‹wIîÒ-Þ$[½H·|‘ný"ÞE¼‹xIðÒ-â$[ÅH·Œ‘o"ÞBE¼”‹yIòÒ-æ$[ÍH·œ‘o="Þ‚E½‹zIô²-é¤[ÓÈ·¨‘oS"ÞªEÍ$‹šY4Ò.iä\ÔH¹©‘sU"æ®EÚt‹µ jR.Õ$]«H»X‘vµ"írEÚô‹¶ lR.Ù$]³H»h‘vÕ"í²EÛt‹· nR.Ý$]»H»x‘võ"íòEÛô‹¸ pR.á$]ÃH»ˆ‘w"î2EÜt‹¹ rR.å$]ËH»˜‘w5"îrEÜô‹º tR.èä]ÒH»¥‘wM"îžEÝD‹º™uR.êä]ÖH½£‘{I"ö–Eí4‹Úyµ/jd^ÕH½«‘{Y"ö¶Eít‹õ êR/Õ$_«H¿X‘~µ"ýrEúô‹ö ìR/Ù$_³H¿h‘~Õ"ý²Eût‹÷ îR/Ý$_»H¿x‘~õ"ýòEûô‹ø ðR/á$_ÃH¿ˆ‘"þ2Eüt‹ù òR/å$_ËH¿˜‘5"þrEüô‹ú ôR/é$_ÓH¿§‘Q"þ¦EýT‹ú¹õ’/ëd_×H¿¯‘a#–F 4Œy50jd`ÕHÁ«‘ƒY#¶F tŒù60lda¨HÃR‘†©# ZFÄŒ5©k’0פa°HÃb‘†É# šFDŒ6©m’0Û¤a¸HÃr‘†é# ÚFÄŒ7©o’0ߤaÀH‘‡ #FDŒ8©q’0ã¤aÈHÃ’‘‡)#ZFÄŒ9©s’0ç¤aÐH⑇I#šF<Œ:‰u20ê¤aÕÈ쑇[#ºF|Œ; v20ì¤aÙÈÅ ‘‹M#žF-DŒZ™µR1jäbÖHÅ­‘‹]#¾F-„Œ[¶R1läbÚHÇR‘Ž©#ZF:ÄŒu©ë’1פc°HÇb‘ŽÉ#šF;DŒv©í’1Û¤c¸HÇr‘Žé#ÚF;ÄŒw©ï’1ߤcÀHÇ‚‘ #FF|DŒø©ñ’3ã¤gÈHÓT‘¦­#MbFšÔ5ÉkÒ4Ø$i±HÓd‘¦Í#M¢F›T6ÉmÒ4Ü$i¹HÓt‘¦í#MâF›Ô7ÉoÒ4à$iÁHÓ„‘§ #N"FœT8ÉêÒ5Ö$k­H×\‘®½#]‚F»vIìÒ5Ú$kµH×l‘®Ý#]ÂF»”wIîÒ5Þ$k½H×|‘®ý#^F¼xIðÒ5â$kÅHÛ^‘¶Á#mŠFÛ$¶im6Ú¤m¶HÛn‘¶á#mÊFÛ¤·io6Þ¤m¾HÛ~‘·#n FÜ$¸iq7Ù$o³Hßh‘¾Õ#}²Fût÷ îR7Ý$o»Hßx‘¾õ#}òFûôø ðR7á$oÃHãh‘ÆÕ#²GtŽ7 nR8Ý$q»Hãx‘Æõ#òGôŽ8 pR8á$qÃHçl‘ÎÝ#ÂG;”ŽwIîÒ9Þ$s½Hç|‘Îý#žG<ŽxImÒ:Ü$u¹Hët‘Öí#­âG[ÔŽ·ÉoÒ:à$uÁHë„‘Þá#½ÊG{¤Ž÷iï;Þ¤w¾Hï~‘ß#¾ G|$7InÒ<Þ$y½Hó|‘æý#ÎGœ8IîÒ=Þ${½H÷|‘îý#ÞG¼xIo>Þ¤}¾Hû~‘÷#î GÜ$÷©ï’?ߤÀHÿ‚‘ÿ $ úH8) p’AःÂI †- ÌÀà!݇¸¨ýè˜BDÜ[~ôXä%Ô`Ù{(B‡BDÜ­€©¡žð@Íx-€?†‚-ëÀ£“}Ü…:DB‡"ÛqÁ‚ê šÐy „)<$W!;# œhZýÃÂ>¾‚[vt†ÊDO9#?p-P›T^ &ZØ%äGŠ ÿ ^ƒ!“!×$\{ü£ø ‹A ‡H†E»`Çg|I! ³ èNÕB§~ŰÂb†t¼6³!Î5ÀÕýFá†8—Ñ ëˆzvìe )™zmžDø@ZŃ7ÔÅ þ—·@KÔ‚xð¼T†DL55áÂÕ¨|ÇÄv"Cám UØMÂB†•ùˆ­óE¡.‘á€Ý i¼fg£Kò$pßPÇ,":êÑãÊ€À"ä³!TÑÓÁS  mÊP!_Á#h ÞUÙLQÎÖØX9ÖAç5üø†Äa®$—10û êHRe«Ê#ø·FÅë<0æÁ“„ þôk #q@Nàè¤Gv¾=AAöq\ƒÌ¤7"@'gkÞ åsHƒûì"ì¡/Ï AÈX(Âò~Õ X;fAóœaÀ‰5z®%_17GЬSõ¸J‡0ºfÆn1®Á™Ý 1¼l¡c}Ù´pëÐǾ> aü±I$…\¤C³ŒØ*A„z ªpyˆ„-ì$|¡Õò {ˆ†ïÀž³`3©Î÷€‹ñÁ0)ÁaR Íàd‰ƒU„2ÀíׇÑ,A–¢%Qïh•œÞ(zP.ŠãäZ2¢ê0Æà†hn4Í1²Ä ùXrÞC¯ÀBàøJ‡ó¾A*Ò±À5À’T0´Á¶z:p†„‘ì'œ¡UO na=Lt çÖ¡ @BgŽˆ’›DÆ'»áJ- ³ØX¯"Þ·ÃÙ†Ph4 ¬ÂÈ„qZã¤âõC‡ÛT@i–àP » †.óø-ºAž¦{Ðú„bD&aIe x^ŽC%ª³ðáí‡qx>™ d0š„®>&û±D+ŠƒhW.ÂÒV8ÀØÆ7þ3KA¦Á ˜oÚc—ˆ ò<‡Ã?¥Â ˆ¯| >`z]•p*¹†  » z28$Ÿ=b ®`[š ÖõPÛ߇Al=âaÏ Œ –8&;>ŠS\U®bÆ(ö ½Ø *2‹ ¦hnSËS!ï5‡ªä>äz ‡ àUsnè!‹<þ ng©ƒžôþ¡‘ ‡ØRYBÞ£Éj†¬ü8{aÜ^¦@ƒYL#æ¡+‰¾ìQ â Ú˰´„…ÕŽ061ÿŒÒÐi°Cfö˜åâ`H¡üïJ´…lDD0À`àŠ€0|åŒ3`ªè6©Áæ"·°‘ò„ñ<*—!m# -øgŠCm&òðó̈8C1TO€˜£ö):±V#‹ð[³ö4wHÉáF€t5¸ÞŽ)dtaûäˆûWÈ (Aî"¿A”}@$;âÐ1ÙÆS@NÏ×¼Ë æ‘÷ØEÙB_žƒ°Q…äü25!ª,°ṽç8 Ãjõ\J¾bn9X§ë…p”-atÍŒÜc]ƒ3ºcxÙBÆû²9háס||Ãùb ’I ¹H‡f€`!Ø  xÿŠ€*bƒä ¢ yJ+ø$m;Ò ¡à[)ƒ ÚÀÛ :È<æ¡ÿŸÁØŒ5Ä’æ&a=I L¸UyBÄèØ½g†Ø2pŸÞadn(‰Üˆî×Ǩ(>Î"Ëy”†ãDOè€@¨ÀÉ€Bt‚uL±`Íõ1h?¥Â-Ðõ £Þ€ü/!‘ Mxp†Cµj1ñ݈øG›BUvp¡¥E>b+|ÑhA ¤x`7Co™èÒü†É7Ô1ËŽº´xòƒà0ȹlÈU4D4ò.€À0 ``cÅ $;R¬ àlïƒÉ$!Va# Ú¨TïÂØ¶LPΗ†Öd9Ãæ¸ù…ï„`´$Ñ0¼‰èTRV"«4 ·'…êB0Þñ“R ýjþ£pÃKÀè…uÄ=;ö2̃½6Ï"|!€xF6?‚@àµ@mP9xü˜k`—(+þx^ †@jLƒƒ8£€ùQ-\Dx"<]§È›_ 0)ëA[´‹@]#VÏ̩Ɩê6A¾cVPuÉ#Çÿ(þ H"ÐB¡Ò!‚näŽänAX€ñ6 ÀT…ƒÔ5 òRxH®BvF8еû†„3™aµ5nˆyŽÃý®!tâ !€L‚y˜تºE‡ -Àzk 5ÈdÄã>ûÀ܇ö:‘Ý2M0}}¶ ìé ”ˆžrF~â@^fŒ …Àl$ð'5j€ ×€rØøh"Ð.¼ 90WÝÂïô@Ôg¨;:ò>VЈ݄wÀ%IQ6ŠlSʶì|@ºF²1šq™ +àlu£|{©€ëyG4=øüC¬…:DB‡"ÛqÉI—Rø’Èü’f$Ÿ˜%_ù.ÀV >b vŽß¨0&ËAB›ŠwLVÍâÏ$>À#†2"3ñ¥J ŒÀo‚㔳k@ñ“Ǿ?}"C¥TˆG„[ #³’s¸†´dNŠ#9ù ‰IÒR­’Çl—ËdÊ©&º8êIßîOÉ6¿‘ç4ɤ’´$øÙ*æÉpLF’ht“u@6äö7(ð*ÀÙ”NNw‚Õ¯å°ï@EŽB\âm°¯¢…Ýô1üá¨QØvUÃã‚ ¢±cˆìÄJ|ÂlP'`§blz,í‘sºŒ¨cÃ1{P¸Ø¹F÷49DñÖ›H{×#÷n ‚© Hȃ°E«ò9Ó4‘‹d˜Â%*Ù,jI| Lª’k{“Ž@ûÄüE(FñEKE”FÔÒ<Úl’Sdžé%[I-úIˆnM n›“¦xžÃÅ…(waFÛÁÀðH¤. Ù­(BDܪ`©ˆ­ 0vAœ ¤0s<Ê´ßa 0»(HìÂ_éÄ(¤HESz,$m‡ Ψa‹ã$ûí€Õ“Þ48|ñÐBå¬z@ãêî È9B‚¬8˜ ¡àm) 'TÁj² Ú rñøh"Ç!.£ 6ØWÑBîúþpÔ(‡ì;$¡ñOȈ¥Duþ%9±6ŠèSªÂ©‹¯³EÎ"/ýñŒ1 ÄÀi<£bÚxåF–@<±‚E¤1ÁÀÎø<…ž&àlL@-iƒ² ààm!ƒ™àU þâX¸EÓG4þ€ž…!Þ*•‘`ú‹j¨^hà â$¸ÏY@ªæ×BÌvx· Íõ0 ?“-ê0£†…}.ï¡ç Bˆp(ƒ²aüˆ‰ŒGY¢Si^˜¡Å9ä+Xág ›_ï`aóÎ8$ÁT "@mÉ$!P!"¹ ÖÀTЂÖô; ÎÒ9šáåAíX…‹„]0$p!/Ï à R‚©'¶—Ált ç sHƒú\"ÖÁ.î 90WÝÂï^þpÔ(‡ì;!ñvOˆ˜Äuš%3q5éŠôS›"µ\–0B¿‚F:µP©­…®°0|œN ¤0s<Ê´Ú± º`HàB_lÀ@¤‚.fõ £Å.ü!K Dp.òJñüˆ‰ŒGY¢S7]¡Bw@?µû†¼3´¸iyVƒûˆ!báL KíH 0^ƒ!,Œàà‡e\>8" ù޳D¦nFà¶:3Ÿ¡µ5j yoƒûì!fe /aú IpZƒ³D1.ˆŠTG_á…B ëÐmfC›¢`ÿ ˆZHEߑ Opyôp'T‹€3Å!¶Hrpy¡ƒý®!pðáKjÔ>]¢ %9¨Õ¨4å³ € ¥€g„U 2}™` ð€hâ¾&/44 ® N t •@µTP2Ô˜˜ Ú g9>x Àä $ 9FÊüÒ&ûˆû ð@¦$B€‰ýQx"’ôªÀE{‚_Ð ˜îÉÈ&gA44 §àMd‚mu@›Üˆ(‡Dœ (QY‹,\€£à,ˆe: .€Y™‚ÎÀ`´F¤ˆ-Cj² Z@Zëæø:ຠÑØ.¡u> ­]‚ìpf »gZ2á—Ð ÄÀfKƒ3$¢€Íxn3ƒœ€ è°gk<„í€Ï…~€4 Ì €heCŒ%ÀÑ`X4wA¤ "€i ƒIÌT Ò¾˜H4ÎÁ¦¨ 8`iÃN|™àå)˜9_ËÄbÐs/ƒšDØ`ç:È9éϬ~ðt¡°àèéHØ:MÒÌ™€tå¨THàê`T:ªÁÕˆ¯`u‡ƒ­hn`ëŒ]ð:õÁ×à¿vƒù0ÏÀþÉ÷?Åþ¾ú î8 YX@/B €¶x 3ÁÐ@†ÂÌ)€X„ Œ _üp@ÑÂÀ9 ât ~Á!@AB´E ‹ä„_ˆ#¡_ĈF*‚1¸PŒ“„eÈ#1a½ÏxF‚4B£ 6j#SÁéØFÆÂ6š¶`³mü#s±ßFÿ8\ÄpŽ0q€#!’åXG19ˆ€˜È„Çp&>¡2' ’L–Bdä(°™RÊô&ZÁ3 ™ÐLÔÂfØ8P™ÏÎÜ&z3Ð ŸHM‚h6C@š&„Ñ4&ŒÁ4˜ ¥ˆM,Bi”N0š~Óð&¢¡5 ©pMK‚•ê°à¥“…-)k!Kr ] Ré—z½`¥÷…0 )„!L! aÐS‚˜¦Å0¦62)¡Lž e¸S-™ ͦ4l)£aM4 i SSBššÖ`¦³5ü)¯áM lÀSfÉHJ@²^…’ô,šÁdÖ 'xY;ÂÉÞP€²„”„,¤!e: )ÐYN‚ÊØVÀ²¶–,°¡e… ,ðYg‚Ë<[p²Û…–Ü,¶áeÐ .€YtË ^²ô…—¤,½!f 0Y€‚üØæÀ¿6ù°/Í~l ó`_›üØæÀ¿6ù°/Í~l ó`_›üØæÀ¿6ù°/Í~l ó`_›üØæÀ¿6ù°/Í~l ó`_›üØæÀ¿6ù°/Í~l ó`_›üØ°Ì †`l3A˜ ¿HeúC/ }Ëè^Ü2öá—ž ¼ðeáC/ xPË©†]L2êa—: ¹Ðe΃.Brˆ\ 2á— ¸@e»Ã-ÞnðËw†[X2ÚÁ–Ö ¶°eµƒ-¬m`ØþÇŒ6Ĺ’%Éq.& pKvÂ[„ږȵÜ%ª1-8‰hüKDÂYôÎØ–jD²¼%”Q@P ŒOôÂõ`Ÿ‹Äû2'Ôá>h‰ðìO~{s×°žªÄôŽ'œ¡<¦‰älOâx:¿xéîN'mÁ;H‰ÙN¿bu—ª`F„éž'F±:‰Ï¸Nw‚sŠšÀœ½å„')9;‰ÉNBbqÈŒ°œ_E4l)!Lž b4S"—z¸°¥¦E+Ô)XaJx R0R…“’™p¤²…$š)HŠD„R‚Y¨£ÞZ(î!GKŠ8hQ¹âj£AÅF(ÅF /lQu"‹EX˜¢¾…^(§ÑE (Q9‰µLà¢g+Ô)]JèŠVàV’‚³Ìšx¬¨dF+AXo Á„UöB¯Ntø«ŽÅ[J*ÐñVUаTUs«4U¸ªŽ…SÞ*•‘TzŠ¢¨U§É;(©Æ…M:*e!SŠ–ðT®"¥ %H©ÅHŠ*>QË TfB£ਚ…Dp*!ñQ ˆT=¢¡Ô Ø«›E\¨*ã±WЏˆUÁ"­ðn¸³Š…šÄ,ÌÁf .YWâÊBL˜²?Eœ,}c‹”XÅÅ’(¨±ň,9áa„ ,XDâÁª h°8…€Ê+þ_ΊüäWÚ¢¾qñ0¯vÅz¼+Ñ1^pŠòXW†B»çݨ®Ú…vp+­A]]ŠêˆWQ"ºpÒ¸®…tJ+ Á\ùŠçhXÞÆ×5(±©E,g1c- XÅÆ.è±qÏN.os –h\—"ä# ¸·…Ä,.‘ph‹P[ô¢ßô·xźþ-Ín)‹oX[hÚÜÒø¶rE²ü-lB `€Z÷‚×?¶Øµ¤¬&-\j¾‹TÈZ–¢Ôj¡À´ûE§D-3ái† KhZX"Ò¨”x´…¤ˆ-!!hü‹G€Z8âÑ®øx·ÃŽì-íÑoU‹zH[Ï"ÞGñp·…E»ø-Þ1nå `$ƒuü ¿¿…ül/ÕQ~_‹ïÜ_l"ú™Ð¾NÅñ|/a{¼‹ÛŒ^Æ‚õ·¨@½"Åçê/5ñy}‹É”^@"ñ„‡p¼(ÅàL.ú‘w¢‹»„]Ì‚înp»Z…Ú .Ê!v8 °”]x"ëvZ ºÊÅÕò.®uc‹ª¸]O‚êJQˆº†Óþ.žató §4_ B÷y»½Ë…î*/oÁ{X‹Ú`^Ïâöf²h½ì6/`!zô‹×@cscºÎÈÆDF0`1x‹P W`b¥#ažXĺ†$¨1Áˆ{ Aaö#s`ÃoFN0Ê¡† .\a`# kP8Â\F0‰„ Œ¼`écÎ2ˆÁ† v0Sá‚yŒh`•v" Àøkò3OñšŒÌôfKƒ1”…˜ËúÆ^2ä!–ÉŒ²Èe€c+;T`ÊqR\2†a“è œ„dÑc%Ü*0É%ÆHf2>‘ÎŒ‹¸dQC!Û ¸È>†@È2Î |f OsàÓm™t4¼¥c 'øi ƒH<; Ñž†‹–4N¡¢ hhXƒAü  Ð!Fz3ôŸ< ÷ˆg¦c<áÎÜFuè3¤QœðŒåmCãi ?ÈÙ¿ÆÌ<6RA²Œ\lIãaU ×ë½d5Þ¡®‘ qkrCZ±ÐÖHF±5|1«–Y”j¹ãU ¤PÔöƦ¼89ÁÁDŽØp cPñèßWøÄ7´ñ½7 æ4ocw²¶ˆÝ|êP7Bá¹³ ÊÜn:Ãq'‚øÛìÝÐ6å!¶Å ´4s8㘴¼@åG+(9HÉ·IÐr2c²|øãµÇì8ÏÁÆ-LqQc‰ÃGâÖ8mQØiŽ¿uÒƒ­cpêãGTô:–qÔOŽž”tÒC¥Ê'HèûÇF€:%ñпƒT!èyòˆunEÃb-lfL‹¤Vð"­YÈê:G:b9$èô¤n`$di"ËÉpH…¢Qò‰´—d@YB"^€p €à €@¬H=2*€p `°Ø¼èÀ €@€ È€K2vÀ=p@ü4&€ € @ÔX.Á€i@>€%€:H8¦Á`‹ @pX,¸€d2À `xÜœ€×q0;ˆ`r”nEA(à–0@°XÈd9À@H `xátÀ>€!ÈÀ 0X¯cÀ± \`/´ ”¤}Ñ@ö`‚ D¸$Ê ¶ý‚‡% `° Õý}IÀ%€Ð ð@zá€ÿ@‰€K`*Àh ¸Ý Ë hð5 œ2lè €“M¨((ˆ ŒF[2 hè,–R`+À8 €Ä€œÀW€.àˆ €‹R¶Àààp€9x°|ö#+A% šPOð(Ü Œ–d2@ X°XW¯Wà1 pÐKI>@°€` 2 ˆ  ˜Ûï z>€h‰l‚OÁ3 žÐQh*0tÈ}@@%€À ÐxïÕpà=à!XH Ê3â€Ó€kð8@˜ÏìÁ@ƒ`D0" ø `á‚E¨ÀW€+ÀXÈ}J`'àà "!Âð@€ÀFP&Pd Êò‘@Ô€o8@° @‰ðFx#ðš ›I ¨ÀTø+À~ K@+À8 €¦ñ€‘ Lp)h ÂX,¦Õ p€8¨°Œ!>+A ‘°J%€ ÑQ€¨ÀT`*Ô ‘ú–S€+À x|**ž V,p DqfÂáq ;`À  H<Á'€—MØ'â 0D«\ ¯W€*0í‚€K+À˜ Œ&,¢V-hŒ 6@Q·@át;`°ŠÐ$`•ðN (DP gF‚¼b@³@Y`,<à ŒÃd@>€$0À ð8NÌÀ•€M@*`ì Ü@@¬ã€sÀ= HfÐé‚€‰pG¨%€ˆ (B‚£] ¯PZ -P& Tª¼^À €èXJ¯d8@p `ý¼wÈgà7è˜ØÐè‚ Àƒ@A !t$¾ƒ‚X8€¢T`+Àâ OªèAw¶ðZ -ª ÌÈi€>€ À è¼ Âú‰€K(xà ¸@Q»@ë€xð>€@h]J‚*A! “@IÀ%€h 8‚¶g@µ@[@.´p ¸²‚ÕjÀ¼ að2Hèô"–W€0@ÐIXE¯]À2˜ÌLNÖô „DÀ#Lø `±nÁ8€ àRH*” gÜïw »€]À-X¨ « ˜€$ÐÀ (x ˜€á| DP%L ðÜ©å`và>pŒh~W‚?%`—N '\P mR½Áj€»€`x0p8 ÜîuÀ¿°cø3 È çK€¯YÀ2pÂ.9¯]1( ”hæ AÀ‰€G°$ÜÜ Á÷‚ŠP@¬0WÀ-P* ÀÆð`à.àp H9œ€ÎÀj@ $@/Âá{`?0"hÀ .x÷Ô`qÐ>€ Ð0·~]; ž Q@)˜ ðªÙÁw¾°`à2 €!‚üÁwÂe˜3Ž H~ƒPd`8@ °Ð6&,¢€W€.à Hý» ÀK%üè (§Á\`±0[H.à 2@%A™ Ìd0t ?nƒKA©× j@5 ¦ÏÂö}@"`œ `»o@Èj@8@°ôÊ~[A8ࢀT`*à@ gÂòAƒ€Åd3XÈ är9ÁÀc3 ˆÖƒkºè@|CÐ%&À–K(  @)QÂí€}D$À è)‚¶Am ½^00(š d<£àÔ€j@5 Ê ¼<ƒœ€Ñðjè6È ŠÅ+B;AÖ㋘:º{–}‘]@‚@DÀ"ˆÀ `Š^³À_P3HÜØ×*5Á+Àž`S+ØÄ …."›ÀÒ°k62 £̓X©Î@d2¦ ä¤kËàô …ˆGÈ&ªF>lÜ<€­V9lüi n¼â8Ìäj»_M5×Õi 0 @°„E­ÀW€.Р îÝ­ô ŽàK'ø" à Aœ€Ïðl6°Þ„¹@ÚÀjH3˜ €_9œ€×ÀrØ?Ð#Zò d q«¥Bø¥íéovOcOÃ4Ãl!ÂH]Æ&eR’^• Qø@ÅÐ `ÛŠU@¯[ð/h H°ó@PK(8 7ÙƒÁ¤Ô€n8t¬t:ÂÀÝðj¨3 WKƒ9œ€ÔðqÀ?x#¬# rô’*£µù¡ò9XN !ÅÉâF ì¸C„dÊ0z›pÇlBÜ4x‘¼O.!¥x-€¢€RP+Àh ¸@Nº€æ w€?°"è: Zô¤g »pb4| ÖÁØÀí€vÀ9Ô ¨y ‹@Êg 3”ú–€BdÁ@1QÒŸüÎlCB!t L¥œ±Ð}„B,$'ô >£â–ãöcR™_ª=  ‹Dà"<ñ˜‡è;%æs‡ €¢€W€,hp <OTÀî }A¸$n Ó£^¶°`à3, ¬*ƒ¼éôpyà;`è Ìš' Í@g 4Ì„* щ0ÂôA[ žHQì Žxë{Èj 7 j @‚;Á&™€SÈ*0  TìƒAœ€Õàp€;Ô. Žèç€ìp€5„Ø žƒRF`Z0.à æ¤„ãA „E¨#ôÀ ²'‚©]àµ@]È0t öÑŽÁá ý }=<¾YÖR»€a2 ê9Æ‚  ÀK&À |s‚Âl`»°`à2È EÖƒ—ÛñPx¨;`¤@E°€ávÀ>h ´0üÆqE@©W€-PP ñƒ œ€Ô€l˜7ÔÀ{]¡€ðà} A "`ü |ãŒ] ²]8.ð– |hƒM©ÚÀoØ8@ètb‚X;@¢€R¨+Lú ¡Á‘Ïpjð7XX ;ࢀT`+|¨ Ń(Á›ÀÒk8<46T`¯Z .àö ärRµ€Þ0p€9Lè >¶ïŒ`Î@h64z,ƒŸAÙເ`¨2Äf H΀çÀvÀ;`j œÖ„ÆÀç@vÀ;är ´„Î`ìÀwè<ð¦L%ƒ¨Áâ óÀ{Ø>€¦ö¢ƒäöá@|>€ Q„€‘ì²€Ûæ¢¼ýÇÍ=¹ÎŽ*oÓwHÍFM1ñÌ{Ÿ ¢—عe¬,±c‹Y»]?yÔ+9MÊ*OÒw“¸žÅ )4¯ç•N;\çää%‰"ÚEÒ+‘p¤…%95ƒ ÈÑÊŽvtίÅËHj@¡ôTyƒÍÐü C‚4„œlThÓz]€úHIÆ6Rs¥¦=|‡Š9a¸`icIÙG:qê,‰$Ž&ÑKË^Ã*›èÁBR6’æ¡ea-݆Œün{ªŸ'8ÁÊ2t´ÈËF .qj‹8Z‚äðȘ7ñÝôˆ¤–'iRËbaãIœ<òhE’TêªE­°y-½tóâa)Hβš£ ˆ¨Å&¡/‰€M‚ƒ³ú3Qºè€ä[%±EË_3›˜í§üD¢M¼©¥°A›í¸tÛã! ÙONüWÂp‰Ä ûüBB0°¡Et/aœŽzd*$A; °\³"› êGäCúH“œ¨U¡°-›Í»u#æ!-[ˆô@ÁÙ ¸h, Õ=BP Å†0‘ªŽ¤€dc&YN‹`bÓYœüú¨sHÒrU´V³¡¹µ}d,#-±%9!˜ €T„ð¦”0᱆D¬)‘q Èqä"A,ŠHYÓš”æÇÎC‚FSš¨•§°ŸMàv{ó!¥\3c ¸Eöð{4%ÁW (nCè#A<‹bãoq IDQbȘTÔ'4>‚F…M-‘†íoû½é†Ð.A;X9Š ðTøá ˜Y4q ‰„UãøðHRI’ˆ<ÂÆ¤9Ùö‘“DöªÉp,YiÛŠ^Eˆœ¸3ÁYð:Á„ CÈ :B. Á‡@¢O½†¨;‚’X¢‘0Ù«NŒ~”K%%A Ñ]ÚÊ凪Á€H¡å ˆRB `U  \ÈÁ Àk'!s ~ˆ)awB@¨¼¦;Â’”¤¥¬1éµNì‚i¦AJË&`9›¬í'ìøZ‚[€d„Ða‚L  OØ @fð*ÈÀÁJBÊù‰ SBøèòHzK‚—ËFõ<ÒR›õE-y‡Í(pëÇ SA;4AL1A$€$È !@ ÀœÐI‚ø!) @kCô$ÁS mÃÝ"¡4в^s?|øèxI¢~U޹öBµÉÍÏr„(‰v‰<àk8 @:P €@L`$Á‚P—…À6âà®68aüÌž¥~0‰ªÎž´Y%ÁGK _[5ìwéC¨;Á} H8AJ0&¸@X:&¼ÇèN‚ü ‰ÄZ#7)‰0QâÕ™Ü'ˆAÂ<Ón¨E®°ý¥Î&yd"¶@¥ð@„À €:ЀÀ €ì i„,)A‘ðŒ…/­,ˆ¤Æ*yxLöqÓä¡ý'ªVÂí™:ÙG¾n1Õ ¨H² À5À(@ð€è @p8Â<ÕèJB°°áòGb}– æ¼;"Ñš™/,Ùƒ oóÀ )-ÎT)áð!€´`,@8R@3‚@ ù ¸_C’!Ñ< hhƒ¶!y+Êr\£1œö(gI*{•~¹ÖAµÝÎÏ€„ÄnåÙBК¨€yx À2 €  €ð Pl„P*Á x‘ÅJ0±¼¸dñ+é… bu”£ 0ÊSY[™ôß7|À"$òQœU )§WéïR¥µÜ HA¶ 8,àp@p  Žð=‚j°ätOÛ(íˆ`K0̧=ª’NŸa®‘ {sÓá¡99zNN†ôݬ]Ž-Õ*٥ϲ’U;¯=¦.Ž@‹TÀqp À@` €\€9°T ðx5ùˆ¬F.8Qý‘äŸÅŒ1°NÒ”j&]L‹>aCœ ðÈDò:2i˜$èè‘O ÍÙ"Þ—¨ŒŒ‰åƒ5ÚÒšªüµ=Ö°Œ ¯PÀ¢¸€zÀ€À5hÁ4 À‚…1¡Üˆ£…ä5Áç‘$™eU/9 ŽL}$C¥B*ã^*›>é÷ÕC<óåê Zû.\*øX{ɶ~¤‡-¹¾Pn—õh°•°ní‚Ütf§M¡³ F¾ ÐB €<˜ @p38Àö  ^ƒŒ!9‹Lgcª! ( L[S%›¬òhEHq(¶Æ(´ýÇOA‚”\¥A3éíQò¤µÍ³Õ˯ʼnì­hs\»®x|™¦: !Sޱ–9·=çp«‘éj[kÜ- à[BRð_‚` bŒÐ1‚Àì H[Cn q/ ðd#  X£äëÈ F`”˜²&3­»î`w Ù_݇80!?@6h `?¼  `0!Ah ’…œ5áý«F7‘ôŒDá+A~M sí¢5)J ÐÃÒ𯄤€ØÐ)A*°C‚HÀñ XZÃ`Á&Š˜`Ãlщ–TòîÀá§´˜„`ÄP%P5A cƒ|!!= ÈnÄ%1TŒmÓ")/Š~\s,‹°Qâ<°nƒàž„Œ"A PFjʇHBâc¨Ã†Þ=b"è§¾ˆ}ÃŒðÀ…¨+X ]( üàOâÍßÇÆDâ`ìB!ìxoCfPà‡h?Â.0ªâ4!ÍìŒDÑ ‹8!qˆ¸Ibr·†F6ááx$혢)!S ^$êîDâVŒ `ˆÊF˜7‘ØÀ‡$‹'(âG 8±ÎÜ{¤"9"‰¬R² x†„R#Ù+‰ÔS¿̘×'ÉI ²Y‚ñp­…†-YvL$eC ˜Èfj4á¶ Þpóœ´üèGðĹæÔÛ[ƒ7g,õ 0V˜scO?-E®¶–êâ\˲äßJI©C*e€³BÄX‹1l.æÊszur E 4(¥\´—à i Åͼgåâdy•\wê?HA$äì¤B|O ñD)Ålµ—Ó&jŽ!à?Èu&ªV‚÷cí%¹¹ç”ý¡$RÂoŒ±–3FÛãØ„‘òDFˆ±#¤˜™”"ªZŒœ6çTú¡dp— §YëÝ´¶ìèž{ý…QfDKb*Å@¨bÀ\ŒQ¦8ÇpïÃÀ{òFÉqC+eÐÈãœ{Њ5KJT-öÉZƒ|u/^Ã8Ç$æ ¡âÈÑ:+…üÒ“à† kR«v±¦Ý3Õ€ÐÚ4I©™?Àà  `ÀÐ!…PÒĸ°ƒ¤ƒ’r’ZÌyµ;h ¥Düª–³f yÆ»ÇÛbt€–S–ŒT°$€(€°&AP: a|:±H/Æàù#ä­—óFrO‚H)©J¬ÚÆšCwtÏVChÑ&¦dÿ¦U˜€`@ ‚plØpÂx\ 1àC‰qO.&TÝŒʃUËiƒ³FÄä^ñ„1JBKiÓG*}i°vr¯W‹#k˜ à`Àè*!(.‡!'…¸Ñ„$“”rÒbÍyÔ?h‰%§E<²—³#j ý×=Ø£„¢š”UJÛaí »º´Â¤Ö:ûeí…˼ XЀ˜ È …0Ì„x¥ƒPw’LQ !…50ø¡„~š’¼] Q ¶÷:ó_ì-‹Ò>`ÏJIêZ®v6Óܹ}¨%¦õFµX;=mî•íÀøˆ 2Àø& ø'ê"„ø´x{òRQ‹!„4§ ö!p—ÔB²[¬!š6' ïŸt‰ÑþXÎ:+R+ {²¶¾äÞ;ý†1–M¤õ¬×;iî Û¾¸?dL¾ ÁX)@¸ƒðšƒˆâÔe ÁÞAÑ2)… ÃcŽzr3KJ U-EþÊÚ»„vO†Dã(¦§JµØ[?nn•íÀø‡ålã¢äÝ1©U޾Ù{crï-ÿÃ8Ó'f§ø<¡#…¼Ä œãDt‚F YA+ÈÆš³–{r2K ùS,õðÈÚ{{u/^ÃXËd¬®˜óà+0¦BØeBK ñ˜;i$ÄÈ¡BÐ_ÌÉ´:GÅ "4j–Sò¥Y‹Õ´–êè‘0l a´9‡±%…PÁ£ô”ŽT ák/†DÔ#º~Ò:KªS,ÅèÇD@†0J áX/† í!D¨¤ÒÔ]Œ“4Æèé´‰"dPê­h AJ*p¸ƒTtò2MJ‰j/Æ4γxsÏ)ýBèÉ(&õ ¬Æ˜ß£ü‰’¢ˆW‹¹Ž4&Èá3Ì~‚)Hi}>©Â0J‰ùV-ÆÏ3ŒvOQýB(™¥dâ£J±k0FdØc¶{ Eõ)¦µ§ÌÁ®8‡h÷ D0‹UMJMc¶|P2Eé,&Õ¦U‰õAH}$„ºœ”:œVk)¤äÆÔb¢Vë5r&´þ¥B½Zk¥©Õb±–Úð`Ìqh®UôÃÙ#:cl½¢5¦ÌÞÝ +èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£ *€Ø£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>ŽØn >£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èÞ‡€ X2f³@£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£è»² ÈÏ­>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£è§€ ÒУèú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èí¡Êópú>£èú>£èú>£èú>£èú>£èú>£èú>Ãfú>£èú>£èú>ˆëèú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>£èú>  ¨…¾••%š] Íjo5u¥}}>MBÕHµPÍZeoàŸòíÕÝ¥"+¥6­C—¦7¹·ÌÿßÌâ,å$êÄò û5½µ"U2Çg?|G‘¥7¹/ÌŒ²$´œ¸üÀ,ÉlÔ„áÔñ­í'Ç#§:/PWe÷{gO¤ÿ¹‚T†<Œ„•„ ä­ô½|Îüâ ö5 # ;FÁ¶Ú~ò¯ o!Ï8×OWe{‡¥ î,æK>h¶…¶¢6½ÆØæó' ÿ&>¯V—mû>s8‹73:{B;Nc^cr‹‰Ó£ûÀË߃ÿô",Dìȟı„Õ¬úUBEe•ˆ}ªÌ5íF V-&Kæj‡n¤>ÀVÛŽöG/)§B7Zc"þJúBû‹» Ã1+I;d[‚{¢ËÅcé,3ìY¼€T¦ôÌüóEý> bu†U©mÌí®Ž.®N>lžŠŽ§–ÃÆßvú'_-ÏF²ÐšÃê¼Ò»b¿ºÉ²Ùí;Ë"#A«d ˆs®CÕ;ýD%ÔNÒK‚^òxŠ–²¹Jß;Û2S^ ŠÛ·ÛåDL?dk„—LÂ,ìm¥=ÅdÍŠí°mÔe÷¾Þ;[Fz¦™>¶®Ó†ïO —$Ï>’†š`J>Š! yüÉö÷Ú²ê.‚MÊqê™êÅò kPïÃß”„=¬m¼›ìÈœôMÝHMp]—E½-áÆ~(I¦iö‰f§ÞÅ–â^þ/G3gM&ñøÍi§±‰Éukán9|©”é¶IÞ¢ ê%gÅ·-Ýž$ÞGhFˆ.§fÅVâ–þ§/4§Ny]a"xë`¼ˆœà“À¤hÉèüY5AqɯÁî¢-òlB©Úæ;!“[ÔË\ 4äg\˜<Çœõ=!åLµuíž=Åêv¶1ÞSÆtn”N³ÐÞí¿ §$ç?X©!Pàh£mÐMR{³°ò±4IvÙ¹Aû<{âºZ÷ƒ3³m³¦ÓݤPWŠÀÉ LñéÒ²ŠUJ”âÓ ÃK+…#½ó´(¤[Äl½lëÕåD=n–µ½Õã¶~+ÞMîo&N®>ÌNéW‡ ÿ;_UOn?z—†÷’ןª—µÿhGtÏ'7™_¥°»i0ÏX¤ŠP‹Ð¦ÐÓá 1EÙ„!â²AÚ€b½Êú“5snó§+ÞÜF„xŒ¨Ô×½1[}„Ŭ-Òµ÷Ö–>†_æ€^Ÿþ¾^ÛæøW/IU÷bo{7‡¿“ߟŸ«µÇÀ×P\ÏiWu‚/ŽOš¥Ç°Ÿ»¿Æ¯Ñ™°õ˜ÚÐÐÚ`öaùPq‡À™ûÒ8t*¯Úêã$Ó]»•«ËÜÔ4tfœ—<Æ$óÍíJÍt%œÂÕè~ ¾/žQ–rž’>±.Îþì#'=ŸWp|ŸˆŸ”O «w¶wÁ¿Ì¿×âW^_j¯wWƒ/š×¦7±‡¼ÏÇ×Ò¿Ýè1lYF*Yá )™Ek1˜iʪ27òoò©á»#Rˆ“¾òœ%„Wœ‡´¶Ôä}õ;ÕeE­´…Ú=þž"DNev…¶¤žÂîàüwï2wL_eŸrG~‰ß•Ï¡7¬§¸ÃÎ'Øßã‡î7ø‡o`myo…gÇœw§ÿ³7¾‡É‡Ô_ß'é¿ôþ–y¹gI`¡eñw’ɶaàÚJAŠtúªZà ûKƒ€‹µèD\K¬{œªT×Õõ.X}€µ§ÕÍ•òF®86Yvy¾™>·nÔþñnÿ ?§(BäAÇñ³ù¨É§Ñ±QÄ ßJú(²TZ‚ê³jåk;K~ °[â,|C|rÄ ŒÍ´ùå$%M=uuœÂ=ç& ®-N¦o z9ÿ™òQìÉïáûjb)ÊJ:o˜:Ãêñ› ÛPƒ€ë± àÌ4>Ìm$šTÆDñ•eDel]“¸­Ý^ `ÂJ²:ê2R1R7ŠD¢X²rú‘ªµÛ£C/c[È›¶CãÄl>DjÜ–ÜÁÜì%m=Íe=‹…±“êƒ:x2s‚uZ}ŠŒZ ª¹º×jùC›l+•ãÀ[ë4\A´l\–¬À¤é¥9¥`M†2´Ò¹2ÂêÒæ‚þó+:ã]C›§›Ï#÷$¤HŒqlš Â4êM7Ý]«“k*+Aû\ã{S›‹½ëáô ,ÄS,yü |Ç$í==8ƒX kû‚ëœó¹3ØCøÄ„=taL…¤ª,δóM;³«kÁËÚkõÔä1ŒQ¬rt”|¶ÔÙLü »é þT40K”h¤†Ü¦\Æ4æuE(D$d8ÜO”h<‚|ܺäØDöm54,q$†Üžœ·lÑ”ìŵ%=BL§¤¼œÒôê¥]í7]R´ÛôïÝ­35K•d½e!]5Jýax½Qd…xÍŽ‘}¤ÕÏXÉL±ç–Òz\z èf-ä…àD¬+rØP]_‹õ‘‡°E¸¨ÀȪxïÑà\²Kã …Ý1â(®=£ Tf:TŠ×ef-öEç¤ÁïÙÃ.$h ‘Hº+òEÔÈÅã£bâ–pU™‹ ild.åFÌÅý_S?:j9ÍŽ¢&HE,ˆ±—£äi¾QÊÙQf+£¢Ðºôc.e)Œ÷9¨¶6DæìáâœÀ$>ôˆÄ3Ê)·E›(¿%O#U$oÀŽ–å¡KÝ+ å’(¸W—Ès&dÑŒñ¨„6MÆïìâÚ3«€wa&Yë¹>H‡â ÿ) =tƒ€Ð•‚"Ckˆ}e"kT2‹WƒB3jFƈ㉸#ÝÔ€\QkÊ—9_í-›¥æDÂô:–jM–‰¼ç8Í=ÄëõÿÏ{®«¹ûË@8H\¡ô-†¼Ðùš#1DÞèªû#ähŠÌϪ<žGä)9!ç$`B}€S3ŠÖ¹h“.ÄF hÈÆSPBlØ ïqÇô:,§itñ[©³ä~9ûb‰AhˆB± á!¨T>ˆÑÑ9ú+EÔÈÈž#„äv\z2BþH«é$àm²‹ÖU6‹ÉrV0f9Í´Sg¼oËNN9Ó¶;¢'—öúY#ùn€ÙMj~Bžèhuv"6„ONŠéQ{22üFËHæ5G#ö4„"‘)R8ºIhIsÉ6´žý”â’¼4[yKêAŒ3H¦ŸÚLœÓ—Œu“ÏÁéA>8æm{ |ôR…ÌÊËDsè -#Ôh(æQ×b>H ‰9#;¤T–¬“eÒ}âQÆJwDŽDšneB¯`Z6‹Ì‰‰´3"枤گœ#óœZvQO¹í>¾ø1Ï É4&&†Dõz#àEè´Å°#V4r("þZBÝH¸I!Ù%‡¤×ÄŸ¤”R ’V Jýä_ •!ÑB "X¶ ©1†®2ô&ÜÛ(As¡¶w-š§ätþ¨ T ‚P^ªéB=†` ùJ&vEÿPðÜ%a|Ïvœ¾cÝì‚kPòê._Ga %&Ü%N4µž—ú“"Zh(Ms‰ºa8¨§:4ë¦ô“ÍR{FO™‰ø™?¸( 5Ž µZ„²P²jeC‰¨|åü¢HÔNŠQ ÑÁ–PC \¡ñÆRìLÕAמ@@ˆ}é0¤6ž–Rü:eSMI ¹ñ9'SDﺞ“ã"~BOÿ*ÑAo¨AÕ ¬¡™;ˆGQ# 'ET¨µå£%ÔiŠºQÇ*:ñGš¨ú5 " sÂd4„/¬È¬ò„CÚùÒ”’_M ¼Y:&§ˆtøNŸÀ” òƒ„P­ŠõD.¨š~¢ôTeò‰QÉ*<GÚ)…!¢¤UTŽš’LRWJLIÀ)=…(`¥"¦¢•'R­*V•Jö€ô C±g>Eëi|}9§Ð5 ¡ä”TšŒÙQÙªA!H¸©&…&È¥ T¥ú•^R¼êYAK^©re/¥õ”Àº˜_SŠc¡LŠ)•3¦lÔÏ*šSFJiaM?©ªµ5˜¦»TØ›#Sh”ÿ™åQ”©PvÊ4Ö·ÁkVëjÅ V窲RƪU6R¥ôT§ê’iRª:åF–¨»UŠ¢oTE ‡ñPêªEBú¨X• ’¡6T$ „PwªEA¦¨2’ ­Tª‚qPKªÅA ¨ UÚ {TjŒ}QŠ*0åFBá'¢ ÊtroÄɶò²Tåþpº5–ÖòÐÃY7ëÕ_í+ÈsB­ÑUªº³šVCêíWÙ*ëU[¾«Ebr«©UcJªUª™¥Rnª5Cº¨TúêžYS¤ªpuM ©§•3j¦6TÁJ—yRç*[…KZ©g,2¥vUª¢ T3*…PjªU@§è™à2§^9BGW—ߟFc$ras+çvj..¥¶µ4V}ËUXõKa`ú«øÕzü®í•Ðz¸šVêªØ‰Z‚+A5fX¬••Œ‚°æV*¾qW†ªèõ\,«fiR¬ÌU*°IUÚ*¸…VĪÐUXÒªùUZjªÉUHª§Tž«˜ÕqŠ®UºŠ¶¡VÁ*ÕeZT«@ÕfꬱU‘R`Ž6áC~góüî÷Ü2‹\g„l ˆÈ0/åï^»—×6:à}[aKZ9i:¬ðe—þ²NÖ6úĹXWK!_¶+ßy:®ÚUÒ ¹3WJÝU[B«[õj­Õ‚³VQªÈ X¿«Åa2¬Õ‚¯ÿUùª¾YW²ªóE^«·u‚³ŸVm Ì™Yu+*…d⬎•ò±ÇV2 ÅyX–«Éy΢œÙ|R®Î¡Å¶~–—ºÍh™ƒÓaëûi{(.ôÒ&¸íVøòÚôZëëPqh˜¬ê5˜x²ˆ–ArÆUX—ë å`ç,U~¢¯Šé2¼/Whjé\Ë+ÅpL­ÜU¶’¶8V¾ Ö¥Z±+Ri¼­'¢¢´V|*ΕY·+3Åf ®ÏUÈ’¸ÆW Êà…[ì«xnt­¼ÕµŠ¶oVŪ׭Z׫W%jmXbB´#M}¯Oµ×8¸ÌߨÕÌšK1‰dbl[å†E°;õø,½^—|šêx\¼ ‡o-­•¯ÆµJV•bÐkYÊ+1µeR¬åŽü±„Ö%úÃ{XJë)`«ôµ}°¯™Uﺽ‰W–q  &7ÂÛ¨âú×PKªËqÏ­Õù¯Å4Íöy"˱Û^a茩€3/¤5ér»õWZòçl\wk‚ o~­Ø%µp¶Ö®šÓ¹Z7K?]g¬È••ú¹%ây5ƒ(Oñ=ï¼-—Pdãé›Íófj—íéœ+2ËVDºÆYXŠ£ )`yKø|q¯BUßà»×D"åL\H ~ny­ª%°ÙIáÔ¬"f(¯¾Yë»×L:ä&çÛk!kim4Y 6³R–V2È‹Ыßa‰,ဈ/Ä%íײì‡]*«™¦;–¨&ÿ_++Îèw&.’È›·ùFÞŠØ#ZšSG;g‹ìʱ”â²ö5<Řp³˜`sËûí}h¯mõæ¨ü€ÞêCÉ v´ÎŒáÈã86å<Ù?šÄcMhUÌäõ˜I2Œ&CÆÀX¨+kaMlµ€´Ò)Z9^fṞ Ï¥¹,7 ¨Þ䇋fìk³Sm¦\´PV{ÊÍÏœôƒ’äpô ô¹}¶˜¦Á:ÖZ‚KHÜh@ŽK¥Ä5·æ¶é¾ÚöÛ‹[/js1É}8VÿÝžÛnÓe­k½-Z5Éw¸˜7 Ýø}gÍl .KÑÄþ8&ò^ÜQN ŠupHMé]¹e8/&öÊ߀` 0+€$V˜€`6D ´+€$V…@ý}د¸à à1$ÀlˆPô¯¿Õ÷º¾î×ÜЛ@¸AE`Tÿ€§_‰kð¥~¯¾u÷޾é(àð˜€`6D(úàl€]~;¯ÅUøj¿×ßšûÑ_vmJà €!Pþz@˜½4àD½€Ðn €°¯Æø~¿×ßêûÝ_w@pxL@ 0"{€ÀÈ6@pôQ p!Èn `j'Àxy H!À6ëñÝ~%¯Â•ø¾ù×Þ02 ÀhWH ”€Æs@ ¸¡1€ز€ðrHÀh÷àD]€  (!p]àÜ€_Œ+ðý~ ¯¿5÷¢¾ì*@žFFhà°p€ `–0 ¬ª€@\Fì€X ðÀR€° Zük_‰kð¥~¯¾u÷ŒW€)›@ˆ;D <ü€0šmÀ Œ.À€¥@FC@¸á ìR€ @üÀG „WãºüU_†«ðM}ù¯½ G'€¸‘€€@€¸ì Xe€ k* ü”€€;°ÀhD€0º@à&À¿Wáúü_«ïu}ÝX  ¼L€(0Δ@Ø%Aàäñ€Ðnh@ `v,(šà=ÀË ”G€àÐ1`.¿×âªü)_+ï}ãZ èR(àä—00C@÷€„k °-`TŸ€&?@`Ö¨M@æ@h7À@Wã ü?_ƒëïõ}î[` W€)™Àˆ;D <ü€0šmÀ Œ.À€¥@<Bá`ÔO€ðòB€l×ãZüK_…+ð%}ó¯¼` ˜h+ H£€À\HÀÀ @Üv¬2Àµ€P~J@˜ýàX`  4!p]àÜ_Œ+ðÕ~ ¯¿5÷ ƒ€/¶±@x™P` ´(€#°JƒÀÈã9 ÜЀÀìX P4#À y À–-@ø  ) XÀ-~5¯ÄµøR¿Wß:ûÆË@¸ü\À @]*@žFFhà°p€ `–0 ¬ª€ðRDÀèçÀU @€èGà„ üU_†«ðM}ù¯½÷`œ2 ½€ØÅUà dA€&ЮŽÀ(? Œæ€pBc °`)@Ѐä7Xµà<<€0¤`µøÖ¿×áJûý_{«îí}Íe  \€€.° ®€ ŽO ˆ&#4pØ8@°ËÖU@ ø)"`ôs€ `€*€ „ Àt#€pB~*¯ÃUø&¾ü×ÞŠû°È€`ö\ W€)›@¸AD <ü€0šmÀ Œ.À€¥@<Bá`ÔO€ðòÀ<€l×ãZüK_…+ïõ}µ÷4‹€0Öµ@ø¤QÀ à1$Àlˆ î;Ö`ZÀ ¨?% L~€p¬0Pš€¸.€pn@€¯ÅUøj¿×ßšûÑ_v+î8 ^À €e+@2 ÀhWH ¬ €Æs@ ¸¡1€ذ hG€@ò ,Z€ ð@@R@° €Zük_‰kð¥}þ¯½Õ÷v¾æ×ÛÀºÀ¨¿U 8<& ˜€ =À`áÀ,`@ XU'उ€ÐØ5€ª@7ÐŽ Àøª¿ Wàšûó_z+îÅ}ǯ¶õö˜{.”­ðƒM  \ €"°*ÀHÍ6à„Æ`ÀR€  ! Üp€ °j)@xy H!À6kñ­~%¯Â•ø¾÷WÝÚû›_p«í}¡]` T_€*&žLFÀ€@¼r `–0 ¬ª€ðRFìÀX  ÀèMà˜ üa_†«ðM}ù¯½÷b¾ã×ÛzûM_hkìÝ}¯´Õön¾ËWذì¸P´SÀ 9%p‚ŠÀ¨ÿ= LÞ€p"]€ O' Œ†€pÂ2ÀØ¥àø4 „ Ø ¯Æµø–¿ WàJûç_xëî•}¯¶5öš¾Ð×Ùºû!_bëì}·¯µ•öš¾Í×Ùjû_akìëZ  üT€)0ú™À`6D(úàl€ à†.h¢à0@€`ÖÀÀM@æ@h7ÀXWã ü?_ƒëïÍ}读÷¾áWÛzûc_k+í }›¯²ö.¾Â×תû_o+í½}¬¯´Õön¾ËWÙ û _`+ëÕ}o\ (Z)àœ€àAE`Tÿ€¦o@ 8‘/`”§€FC@¸á ìR€ðüÀB l×ãºüK_…+ð%}ó¯¼u÷J¾áWÛûY_hkìÝ}¯±uö¾½W×Zúß_Z«íå}·¯µ•öš¾Ð×Ùjû!_bëì}z¯®µõª¾².°ª¯ÀH“O  œ(€#°@‚€ Þ9ÈÎ`àV€ .#v ÀŒ,Љ`Œ&€ÀL€-~0¯Ãõø>¾ÿWÞêû»_skíå}±¯µ•ö†¾Í×Ùjû_akì}u¯­õõª¾¯×Õšûç_p«í½}±¯´Õö†¾Í×Ù û_`+ëÕ}u¯­Uõ’¾¯×Õ̲€ žQ È.$``†€øé:`ôÓ€öY@ x9$`4~€p¢.ÀP”À¸,pb@]~;¯ÅUøj¿Wßêûç_{«ï}Ò^ 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ÀÀ @Üv¬2Àµ€P~J@˜ý€X`  2 ÀhQG`”€Æs@ ¸¡1€ذ hG€h÷ ,1$À`†€ î;Ö`ZÀ ¨D%À`À‚ŠÀ¨ÿ= 4Û€p"^À (O' Œ†€p@‚€ Þ9ÈÎ`àV€ .#Tÿ€¦o@ 8‘/`”§€R‚€ Þ9ÈÎ`àV€ P4= LÞ€p,`@ XU'àŒæ€pBc °`)@`áÀ,`@ XU9 ÜЀÀöY@ `œ0ÀÀÈ€°ì º´,â•‹j‰Á'©ü*.~’ 5(²޸¦6/gc i1¢¾šÈ¨ØV(‡Û‰¹¢¾8'$¼0bq ¢—âËG0«˜&)$‰ÚY‚[Ãè*G@ &†‚o •Í(ÅÐ/# dº·(§/z'ø‰ŒL"Gr‹92 œ‚£¦¡ß&Îè Lº‚:Æ#*)HÂ>.† øB¨5P¢JX&¯Ê 7Eb1§…¤Ëøâ$à°Zð*)6 [‚i- “§Ø#RDtŒnúBøñXµ¢Æ+Kö O‚s縕6ò#mh½Áþ±Py&ì»°†ÏøàË”h0ƒ´ Áõ ¨O(&: ”œ‚IB ‹¾@!WÜözŒÁ £ X¹­ ,* ~;‚}\(—P#Æ"ˆwÂâçÈyI@¯ã†Ç횥pa’úŸoÌaIq@Mü8{kan ðè?À÷DƒDà×êø3L 1æo€°}H)ú ýÁ`S@Öà"wf5Çô€ w9hÑÃø1öë â)bÑꨫ †(xÖ‰ÛbBHȈyb /¿•ëaÉÚhkäRqzáj;xUqô)¤„Ãa ¸DìòηæšX6Ò î냋àºà,R uë{j`–½x#Æ"~¼‚ z“¸êB¤Mc`FX͆60ÀÛ ‹†ÂºaX¤¼è&Îç "Y¢%ÚX.Zå‡!>A¬Ñxd³@«¥…ü!NΈN΂‹‚„]ñáuÀ>ؤ¨t—ÀÐÏX1Q² §°‚À…³'m° S*‚4šà…’ð¢ {ÅÀkIÐe®„ óãO`€È¡/\¨‹'¿‚Ÿlˆ$ô°¨ ‚½@yV c·¤…Açà],ZÏI…aÁ2¤ÀGê°Þ[ƒöi`îPx8 +ª`ºÐà,H ^P‚pô`“R"³z/Wî tŠÀyÔy›†Ì@[ÿð±@6Ã_ íOC Pضñ„*Ù)Š1"V«H‹›æ „“<Áïi-&…µ‰!Tµ(O‡ Í„UåáoÈ<¯\4ƒSU@Ç}@.Á ÷¼‚’{€šð$Fâ„ÿÄx)¸6¨Ÿ(Å€]WHä¾"H3êàH˜ðJÜ8ÌÊ_’c#t¨»ã„+ÒŠ2Sb^a`Œ®Ž œ‡ßaÀ gÑzm”‹aKt8Lúºáþ„(Ö@÷³¸9³v oªƒ!û»&°+¯¾ 3faeàŽ[¸!BhÆ3Ñ`l¡(abëüb @R‹ˆ>®zé œ =õˆúš&;#v äWC?| Á§Î,ò­ŠhÆbgÚø ®Y†æ!»´Xf$R‰P…lÙ!@†(J pôˆÀê~6LH –ñ‚ëˆ ­‰ø(LÚ [À,j¨ûlŸ4À`lèa4/(3¦@G%HvJÊöß²@3]I…n=×xŽ|õc`‹ÈiL.B}Ф Bpã@@ º&{]¡µš8d àæ<…>ûa3·hF—¨8ƒº™ Û›2“ §°‚°A@ž¸@$¡tó² sUÀšª"³j Sj°-„gš ;$ˆ ‚ú²¢ (LØ ¸E(ºFÏãÎ-¨â5ø4.y‹øl‚»¸¨Ÿ[T$&±ˆ2JÁÛŽ@kÆFr!…‹láB«xIWv³qÐ`Þ°À2Ø: £f©H›çH#´ø.Eß”@mÖ !½ÚOÐL“Ào2õàå˜à3æ˜ ªèœh”Üà ì˜5\/“Ii$“cAО@b¶å£kÇgÎ-&- ,±‚Id`ƒKLoT{C°UB¾1΄Ri@ùÙ8i Æ&‚ãÀà§Å°& ~ŸþôÄÀq†H·8ÓOQ'`L&>Øè€Üz@1  á6dž…× æ +JJsCRà Fè{&UoÎy×dÞ°Ù%ÖD”«ÔŒÃ)8Ñ.æ¤ qäâQ€ƒB¸݆l»l-PPÚû¾„#àäþ`36¤ t1‘$ “/° ý¾d‘§Ê^®˜V³Æ j ;$ˆ <×j€¶À!î1}ò€MÈÀIàÙ{b*€„J÷Z.§4EsÒQM; NËIir$>\˜÷ X7[¸ŒD™B°ìè–<¼ ª»¡‹}àV?òß2!S èX°3)Ä Cé‚|8`Œ6höxÒ§€ T}XÚ €ß²@0 5Œ%Qq0€Ì€qBÑ6€## ÂÐ@a~¨Ä’…‘!‹ÄG®±¾œÄeº‘óthpYbv@—Äüeq&þ"BäKŽÜûc>¿H²³Â&"+Ï¡¸7°]µ®ß„Iëx2µä ò_‚]é@ƒ PhO‹`GŒH0"1‰€©„"¼ð²T€=°à ðF( ¨D›èÑ#”Gæ}ÆDe1Qj,DîãD;žQ ŸìB¢ð`–%rD>ÜI kDï#RH»°&Êœ‡õñA¢Ó°Vf’æIƒºàÇl)׬DZØ b’8s¬;Ü¿,d¯£€IÂÀ ªè_èD–Fñ!œG2=zÄUçq0´CÏYÆÆÄ'ŇLAT53ÊÄ+pÿj4?j÷Àþÿ{…úšAh8~R¸“’P;Ãßá€É3'à·raÕøKý†ßË@š\xò,.85€<ô ™¾!€]œ áhu_“ DŒ±ctFÿ;¸Ä>ÖQ lB3][|Ä ›QI\?w×» ÃæÜP÷Öl=Ž›IçCÌNpñ«$<õðÚ÷§FVA’rÞ^[ù•Hèd‚hà;(»[:¹aK¸à&–‚ð§Mä*)M žµÂ¬p¬w¬+b• çªÂ½«ð°[T,Ré !šBË0³‚$- s N©ÂÖpµýD-¥ñ pþÂÞe¸l."e ‹ÑBãÂP¹$,Žÿ ( BË0³"A÷<• k'X;JÍö øá¸p;ÿ"Ox6óïÛXþƽÀ5$zÄ ºÝZ¿ÁEð[T¤í›­ ÁÄÃPvÁœÜÏ;B"‡sü"ªýÝ,BBJP’üŒ%Y¡ x½Âeö°›M$'Bß ë‰Â СšÄ(¸; CƒÂ•´ð¦ä)àY ˆ.B¦pªcŒ*Ìa ¹ˆB¯þ¬U\+/ Ñ"µ䰤k¼)=E VÓ—Pð¦Ld)±# sËžŽéÌD’t2¯~¯•ÉìüªK{·˜é¶<+/Y´[ä¬äûÅ{¿qÙOö̪s¾WÀ©@Ð6¦ÄOÁ¤ÇAJ¸ðYÛsbA­²oûÔ3‡þÁð6ðh £·W 9ðŠ‰œ#<çñŽÂD*ð’ëd%3 g“B`™}¬&¹‰ ÄêBs¤ƒ¤'‡‰ ë‰Â}LpŸÜt(q ñÂ…¡ðœ%w­ h¥Â]ap—òÜ&'¡ ”¢Âg’°š,&Æi »BBq:œ×úŸ¹Ê°Kì.¹Ù qS M*ÝÖò¹ŠlŽÇ¨ƒÅ>Dôïœ=öPð ŒýŽˆ?µúÿeä’1½E@s1ŸT¸.ÂÁ)vpQ#dÂɬÁ†ÍÐfSä§£êHAÉRu®ô>½£Áúh0&\ ì«_¥B 9ðŠt"ü‡Ý,B=ÿ°‘ ¼$v/ )XÂMI“þ %.· W{BXHЖ½Ü%Ö -Â%RŠ"œ"Àq¼üB2»°|ì#Žqð{Â?Pd$O žBJV*¦ÍÄ›*¹?š0˜ŸLÁ…ÃUXFÝ9þ.6S¸ô’ñÜ=1úsSäÂXúèx?ÙOÙ”+úÅ"ÿ¢®ÀcP¦  ½›À»ð8©ÄñWezA1ÇQà<¼ÍA€metkit-1.18.2/tests/regressions/METK-103/CMakeLists.txt0000664000175000017500000000027515203070342022572 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.18.2/tests/regressions/CMakeLists.txt0000664000175000017500000000017715203070342021532 0ustar alastairalastairif (HAVE_BUILD_TOOLS) # test scripts use the metkit tools add_subdirectory(METK-89) add_subdirectory(METK-103) endif() metkit-1.18.2/tests/mars2grib/0000775000175000017500000000000015203070342016312 5ustar alastairalastairmetkit-1.18.2/tests/mars2grib/backend/0000775000175000017500000000000015203070342017701 5ustar alastairalastairmetkit-1.18.2/tests/mars2grib/backend/mars2grib-test-search.cc0000664000175000017500000001767315203070342024336 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.18.2/tests/mars2grib/backend/CMakeLists.txt0000664000175000017500000000024515203070342022442 0ustar alastairalastair ecbuild_add_test( TARGET mars2grib-test-search SOURCES mars2grib-test-search.cc NO_AS_NEEDED LIBS eckit metkit ) metkit-1.18.2/tests/mars2grib/utils/0000775000175000017500000000000015203070342017452 5ustar alastairalastairmetkit-1.18.2/tests/mars2grib/utils/CMakeLists.txt0000664000175000017500000000004615203070342022212 0ustar alastairalastairadd_subdirectory( dictionary_traits ) metkit-1.18.2/tests/mars2grib/utils/dictionary_traits/0000775000175000017500000000000015203070342023205 5ustar alastairalastairmetkit-1.18.2/tests/mars2grib/utils/dictionary_traits/mars2grib-localConfigurationTraits-tests.cc0000664000175000017500000006005115203070342033555 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.18.2/tests/mars2grib/utils/dictionary_traits/mars2grib-codesHandleTraits-tests.cc0000664000175000017500000000564615203070342032155 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.18.2/tests/mars2grib/utils/dictionary_traits/CMakeLists.txt0000664000175000017500000000061115203070342025743 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.18.2/tests/mars2grib/api/0000775000175000017500000000000015203070342017063 5ustar alastairalastairmetkit-1.18.2/tests/mars2grib/api/test_python_api.py0000664000175000017500000000065415203070342022653 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.18.2/tests/mars2grib/api/test_cpp_api.cc0000664000175000017500000000323315203070342022045 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.18.2/tests/mars2grib/api/CMakeLists.txt0000664000175000017500000000110615203070342021621 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.18.2/tests/mars2grib/CMakeLists.txt0000664000175000017500000000015515203070342021053 0ustar alastairalastairif( HAVE_MARS2GRIB ) add_subdirectory( api ) add_subdirectory( utils ) add_subdirectory( backend ) endif() metkit-1.18.2/tests/test_param_axis.cc0000664000175000017500000006556415203070342020134 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.18.2/tests/test_codes_api.cc0000664000175000017500000002541115203070342017721 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.18.2/tests/test_step.cc0000664000175000017500000000705315203070342016750 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.18.2/tests/test_matcher.cc0000664000175000017500000001474615203070342017427 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.18.2/tests/expand/0000775000175000017500000000000015203070342015701 5ustar alastairalastairmetkit-1.18.2/tests/expand/MARSC-263.req0000664000175000017500000000050315203070342017565 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.18.2/tests/expand/MARSC-231.req0000664000175000017500000000036015203070342017561 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.18.2/tests/expand/MARSC-227.expected0000664000175000017500000000100715203070342020577 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.18.2/tests/expand/PGEN-569.expected0000664000175000017500000000055315203070342020501 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.18.2/tests/expand/MARSC-254.re0000664000175000017500000000013315203070342017403 0ustar alastairalastairretrieve, type=ob, date=-3, time=00, range=1439, target=data.reference metkit-1.18.2/tests/expand/MARSC-267.expected0000664000175000017500000000114715203070342020610 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.18.2/tests/expand/MARSC-252.req0000664000175000017500000000041015203070342017560 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.18.2/tests/expand/MARSC-248.req0000664000175000017500000000017215203070342017572 0ustar alastairalastairretrieve, levtype=pl, level=1000/500, date=20250416, param=t, accuracy=LOW, target=data.reference metkit-1.18.2/tests/expand/MARSC-253.expected0000664000175000017500000000044215203070342020600 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.18.2/tests/expand/MARSC-255.req0000664000175000017500000000025415203070342017571 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.18.2/tests/expand/MARSC-228.re0000664000175000017500000000022015203070342017401 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.18.2/tests/expand/MARSC-241.req0000664000175000017500000000024315203070342017562 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.18.2/tests/expand/MARSC-245.expected0000664000175000017500000000062415203070342020603 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.18.2/tests/expand/MARSC-228.expected0000664000175000017500000000052715203070342020606 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.18.2/tests/expand/MARSC-244.expected0000664000175000017500000000060215203070342020576 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.18.2/tests/expand/MARSC-236.req0000664000175000017500000000033315203070342017566 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.18.2/tests/expand/MARSC-258.expected0000664000175000017500000000053515203070342020610 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.18.2/tests/expand/MARSC-241.expected0000664000175000017500000000047115203070342020577 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.18.2/tests/expand/MARSC-227.req0000664000175000017500000000025615203070342017572 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.18.2/tests/expand/MARSC-248.expected0000664000175000017500000000051315203070342020603 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.18.2/tests/expand/MARSC-264.expected0000664000175000017500000000064015203070342020602 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.18.2/tests/expand/MARSC-234.req0000664000175000017500000000033515203070342017566 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.18.2/tests/expand/MARSC-249.req0000664000175000017500000000021415203070342017570 0ustar alastairalastairretrieve, levtype=pl, level=1000/500, date=20250416, param=t, accuracy=REDUCED, grid=5/5, target=data.reference metkit-1.18.2/tests/expand/MARSC-233.expected0000664000175000017500000000073115203070342020577 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.18.2/tests/expand/MARSC-263.expected0000664000175000017500000000074015203070342020602 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.18.2/tests/expand/MARSC-264.req0000664000175000017500000000045315203070342017572 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.18.2/tests/expand/MARSC-255.expected0000664000175000017500000000063715203070342020610 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.18.2/tests/expand/PGEN-569.req0000664000175000017500000000050615203070342017465 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.18.2/tests/expand/MARSC-240.req0000664000175000017500000000036015203070342017561 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.18.2/tests/expand/MARSC-233 copy.expected0000664000175000017500000000070015203070342021526 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.18.2/tests/expand/MARSC-258.req0000664000175000017500000000025715203070342017577 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.18.2/tests/expand/MARSC-233.req0000664000175000017500000000033515203070342017565 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.18.2/tests/expand/MARSC-225.req0000664000175000017500000000027115203070342017565 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.18.2/tests/expand/MARSC-251.expected0000664000175000017500000000056415203070342020603 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.18.2/tests/expand/MARSC-242.expected0000664000175000017500000000037715203070342020605 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.18.2/tests/expand/MARSC-249.expected0000664000175000017500000000057015203070342020607 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.18.2/tests/expand/MARSC-251.req0000664000175000017500000000031415203070342017562 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.18.2/tests/expand/MARSC-236.expected0000664000175000017500000000071415203070342020603 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.18.2/tests/expand/MARSC-231.expected0000664000175000017500000000056715203070342020604 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.18.2/tests/expand/MARSC-242.req0000664000175000017500000000021015203070342017555 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.18.2/tests/expand/MARSC-261.expected0000664000175000017500000000063215203070342020600 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.18.2/tests/expand/MARSC-247.expected0000664000175000017500000000050715203070342020605 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.18.2/tests/expand/MARSC-232.req0000664000175000017500000000035515203070342017566 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.18.2/tests/expand/MARSC-237.expected0000664000175000017500000000100715203070342020600 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.18.2/tests/expand/MARSC-252.expected0000664000175000017500000000043515203070342020601 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.18.2/tests/expand/MARSC-225.expected0000664000175000017500000000137415203070342020604 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.18.2/tests/expand/MARSC-237.re0000664000175000017500000000030715203070342017407 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.18.2/tests/expand/MARSC-261.re0000664000175000017500000000045615203070342017411 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.18.2/tests/expand/MARSC-247.req0000664000175000017500000000012615203070342017570 0ustar alastairalastair retrieve, class=rd, date=20250413, expver=aaaa, target=data.referencemetkit-1.18.2/tests/expand/MARSC-245.req0000664000175000017500000000032215203070342017564 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.18.2/tests/expand/MARSC-254.expected0000664000175000017500000000043715203070342020605 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.18.2/tests/expand/MARSC-267.req0000664000175000017500000000036615203070342017600 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.18.2/tests/expand/MARSC-224.req0000664000175000017500000000027715203070342017572 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.18.2/tests/expand/MARSC-232.expected0000664000175000017500000000067115203070342020601 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.18.2/tests/expand/MARSC-224.expected0000664000175000017500000000126715203070342020604 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.18.2/tests/expand/MARSC-223.expected0000664000175000017500000000101015203070342020565 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.18.2/tests/expand/MARSC-223.req0000664000175000017500000000026615203070342017567 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.18.2/tests/expand/MARSC-244.req0000664000175000017500000000026215203070342017566 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.18.2/tests/expand/MARSC-234.expected0000664000175000017500000000057315203070342020604 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.18.2/tests/expand/MARSC-240.expected0000664000175000017500000000072215203070342020575 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.18.2/tests/expand/MARSC-253.req0000664000175000017500000000031215203070342017562 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.18.2/tests/test_mars_language.cc0000664000175000017500000000377615203070342020612 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, {}); // TODO:(TKR) THIS IS MENTAL 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, {}); // TODO:(TKR) THIS IS MENTAL 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, {}); // TODO:(TKR) THIS IS MENTAL EXPECT(match == "parameter"); match = language.bestMatch("par", {"parameter"}, false, false, true, {}); // TODO:(TKR) THIS IS MENTAL 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.18.2/tests/test_hypercube.cc0000664000175000017500000001611415203070342017761 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.18.2/tests/test_steprange_axis.cc0000664000175000017500000001160115203070342021003 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.18.2/tests/test_obstype.cc0000664000175000017500000000433415203070342017461 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"}); assertTypeExpansion("obstype", {"sd"}, {"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_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.18.2/tests/test_request.cc0000664000175000017500000002171015203070342017461 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.18.2/tests/test_context.cc0000664000175000017500000000272315203070342017460 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.18.2/tests/test_codes_decoder.cc0000664000175000017500000006040415203070342020556 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/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"); } } //---------------------------------------------------------------------------------------------------------------------- } // namespace metkit::codes::test int main(int argc, char** argv) { return eckit::testing::run_tests(argc, argv); } metkit-1.18.2/tests/tools/0000775000175000017500000000000015203070342015562 5ustar alastairalastairmetkit-1.18.2/tests/tools/d1.grib0000664000175000017500000005661015203070342016743 0ustar alastairalastairGRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿeÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿg ÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿg ÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿgÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿgÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿÔbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR‰* €ÿ7777GRIBÿÿìbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê ÿR‰* €ÿ7777GRIBÿÿìbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($:ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê ÿR‰* €ÿ7777GRIBÿÿìbT"ê ÿs0000000. aad3HR‰(ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿZ-…Z-pß^ÿÿÿÿ@$(-2<@HHKQZ`dlxx}‡– ´´´ÀÀÈØØØáðððú   ,,@@@Dhhhhhhww€€•°°°°ÂÂÂàààààæôôô@@@@@@XXXX€€€€€€€ˆˆ££££ÐÐÐÐÐÐÐÐÐÙîîîî      **```````````„„„„„„„„ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÌÌèèèèèèèè88888888888888eeeeeeeeeeeeee€€€€€€€€€°°°°°°°°°°°°°°°°°°¿¿¿¿¿¿¿¿¿¿¿¿¿¿°°°°°°°°°°°°°°°°°°€€€€€€€€€eeeeeeeeeeeeee88888888888888èèèèèèèèÌÌÀÀÀÀÀÀÀÀÀÀÀÀÀÀ„„„„„„„„```````````**      îîîîÙÐÐÐÐÐÐÐÐУ£££ˆˆ€€€€€€€XXXX@@@@@@ôôôæààààà°°°°•€€wwhhhhhhD@@@,,   úðððáØØØÈÀÀ´´´ –‡}xxld`ZQKHH@<2-($:Áÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê ÿR‰* €ÿ7777metkit-1.18.2/tests/tools/grib-to-request.sh.in0000775000175000017500000000167215203070342021565 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.18.2/tests/tools/CMakeLists.txt0000664000175000017500000000050315203070342020320 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.18.2/tests/test_multihandle.cc0000664000175000017500000000356415203070342020306 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.18.2/tests/marsgen/0000775000175000017500000000000015203070342016056 5ustar alastairalastairmetkit-1.18.2/tests/marsgen/metkit_marsgen_legacy.sh.in0000775000175000017500000000166215203070342023364 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.18.2/tests/marsgen/metkit_marsgen_simple.sh.in0000775000175000017500000000165315203070342023411 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.18.2/tests/marsgen/genfile.default0000664000175000017500000000055015203070342021035 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.18.2/tests/marsgen/metkit_marsgen_compare.sh.in0000775000175000017500000000164315203070342023545 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.18.2/tests/marsgen/genfile.legacy0000664000175000017500000000071115203070342020654 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.18.2/tests/marsgen/genfile0000664000175000017500000000067515203070342017422 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.18.2/tests/marsgen/metkit_marsgen_obs.sh.in0000775000175000017500000000117115203070342022676 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.18.2/tests/marsgen/CMakeLists.txt0000664000175000017500000000052315203070342020616 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.18.2/tests/test_type_levelist.cc0000664000175000017500000000616015203070342020663 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.18.2/tests/test_filter.cc0000664000175000017500000001213715203070342017261 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.18.2/tests/test_time.cc0000664000175000017500000001051215203070342016725 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.18.2/tests/test_language.cc0000664000175000017500000003134315203070342017557 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.18.2/tests/test_date.cc0000664000175000017500000000670515203070342016715 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.18.2/tests/test_c_api.c0000664000175000017500000000132315203070342016677 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.18.2/tests/CMakeLists.txt0000664000175000017500000000741015203070342017164 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 ) 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(tools) metkit-1.18.2/tests/test_c_api.cc0000664000175000017500000002014615203070342017046 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.18.2/metkit.code-workspace0000664000175000017500000000005315203070342017403 0ustar alastairalastair{ "folders": [ { "path": "." } ] }metkit-1.18.2/pyproject.toml0000664000175000017500000000165015203070342016176 0ustar alastairalastair# pytest [tool.pytest.ini_options] minversion = "6.0" addopts = "-vv -s" testpaths = [ "pymetkit/tests" ] # pyproject.toml [build-system] requires = ["setuptools", "wheel", "cffi"] build-backend = "setuptools.build_meta" [project] name = "pymetkit" description = "Python interface for metkit" dynamic = ["version"] authors = [ { name = "European Centre for Medium-Range Weather Forecasts (ECMWF)", email = "software.support@ecmwf.int" }, ] license = { text = "Apache License Version 2.0" } requires-python = ">=3.10" dependencies = [ "cffi", "metkitlib", "findlibs" ] [tool.setuptools.dynamic] version = { file = ["VERSION"] } [tool.setuptools] packages = ["pymetkit"] package-dir = { "pymetkit" = "./python/pymetkit/src/pymetkit" } include-package-data = true zip-safe = false [tool.setuptools.package-data] "pymetkit" = [ "VERSION", "metkit_c.h" ] [project.optional-dependencies] tests = ["pytest"] metkit-1.18.2/src/0000775000175000017500000000000015203070342014047 5ustar alastairalastairmetkit-1.18.2/src/experimental/0000775000175000017500000000000015203070342016544 5ustar alastairalastairmetkit-1.18.2/src/experimental/netcdf4-example.cc0000664000175000017500000001112015203070342022026 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.18.2/src/experimental/CMakeLists.txt0000664000175000017500000000026215203070342021304 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.18.2/src/tools/0000775000175000017500000000000015203070342015207 5ustar alastairalastairmetkit-1.18.2/src/tools/mars-archive-script.cc0000664000175000017500000002163515203070342021410 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.18.2/src/tools/message-to-mars.cc0000664000175000017500000000356315203070342020531 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.18.2/src/tools/check-mars2conf.cc0000664000175000017500000001421715203070342020470 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.18.2/src/tools/ncmerge.cc0000664000175000017500000000203115203070342017132 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.18.2/src/tools/grib-to-request.cc0000664000175000017500000001756715203070342020567 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.18.2/src/tools/parse-mars-request.cc0000664000175000017500000001136315203070342021262 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/io/Buffer.h" #include "eckit/io/Offset.h" #include "eckit/log/JSON.h" #include "eckit/option/CmdArgs.h" #include "eckit/option/SimpleOption.h" #include "metkit/mars/MarsExpansion.h" #include "metkit/mars/MarsParser.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 ParseRequest : public MetkitTool { public: ParseRequest(int argc, char** argv) : MetkitTool(argc, argv) { 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; }; //---------------------------------------------------------------------------------------------------------------------- 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 << std::endl; Log::info() << "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); 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); } } } } //---------------------------------------------------------------------------------------------------------------------- int main(int argc, char** argv) { ParseRequest tool(argc, argv); return tool.start(); } metkit-1.18.2/src/tools/grib-blob.cc0000664000175000017500000000442715203070342017364 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.18.2/src/tools/odb-to-request.cc0000664000175000017500000001150015203070342020365 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.18.2/src/tools/compare-mars-requests.py0000775000175000017500000002176115203070342022032 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.18.2/src/tools/bufr-sanity-check.cc0000664000175000017500000003672215203070342021046 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.18.2/src/tools/nccompare.py0000775000175000017500000000747315203070342017546 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.18.2/src/tools/CMakeLists.txt0000664000175000017500000000534415203070342017755 0ustar alastairalastair### provides MARS requests from GRIB files # ecbuild_add_executable( TARGET grib-to-mars-request # CONDITION HAVE_GRIB # SOURCES grib-to-mars-request.cc # INCLUDES # ${ECKIT_INCLUDE_DIRS} # LIBS metkit # ) # 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 bufr-to-mars-request # CONDITION HAVE_BUFR # SOURCES bufr-to-mars-request.cc # INCLUDES ${ECCODES_INCLUDE_DIRS} ${ECKIT_INCLUDE_DIRS} # LIBS metkit eckit_option eckit eccodes # ) ecbuild_add_executable( TARGET parse-mars-request CONDITION HAVE_GRIB AND HAVE_BUILD_TOOLS SOURCES parse-mars-request.cc INCLUDES ${ECKIT_INCLUDE_DIRS} LIBS metkit eckit_option eckit eccodes NO_AS_NEEDED ) 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.18.2/src/metkit/0000775000175000017500000000000015203070342015344 5ustar alastairalastairmetkit-1.18.2/src/metkit/fields/0000775000175000017500000000000015203070342016612 5ustar alastairalastairmetkit-1.18.2/src/metkit/fields/FieldIndexList.cc0000664000175000017500000000315615203070342021775 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.18.2/src/metkit/fields/SimpleFieldIndex.cc0000664000175000017500000000117715203070342022314 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.18.2/src/metkit/fields/FieldIndex.h0000664000175000017500000000277215203070342021006 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.18.2/src/metkit/fields/FieldIndex.cc0000664000175000017500000000741215203070342021140 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.18.2/src/metkit/fields/SimpleFieldIndex.h0000664000175000017500000000167515203070342022161 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.18.2/src/metkit/fields/FieldIndexList.h0000664000175000017500000000161715203070342021637 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.18.2/src/metkit/mars2grib/0000775000175000017500000000000015203070342017234 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/0000775000175000017500000000000015203070342021053 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/make_HeaderLayout.h0000664000175000017500000001320215203070342024605 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 Mars2GribGenericException 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::exceptions::Mars2GribGenericException; 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( Mars2GribGenericException("Critical failure: Unable to resolve GRIB HeaderLayout", 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.18.2/src/metkit/mars2grib/frontend/GribHeaderLayoutData.h0000664000175000017500000001117515203070342025215 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.18.2/src/metkit/mars2grib/frontend/normalization/0000775000175000017500000000000015203070342023741 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/normalization/normalization.h0000664000175000017500000000024115203070342026775 0ustar alastairalastair// Project includes #include "metkit/mars2grib/frontend/normalization/normalizeMarsDict.h" #include "metkit/mars2grib/frontend/normalization/normalizeMiscDict.h"metkit-1.18.2/src/metkit/mars2grib/frontend/normalization/normalizeMiscDict.h0000664000175000017500000000502015203070342027527 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/enableOptions.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::normalizeMiscEnabled; // TODO: Implement sanitization trigger logic based on optDict settings if (normalizeMiscEnabled(optDict)) { // [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::normalizationmetkit-1.18.2/src/metkit/mars2grib/frontend/normalization/per_key/0000775000175000017500000000000015203070342025377 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/0000775000175000017500000000000015203070342026341 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/model.h0000664000175000017500000000106115203070342027610 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/hdate.h0000664000175000017500000000106115203070342027575 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/All.h0000664000175000017500000000533515203070342027230 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/stattype.h0000664000175000017500000000107215203070342030367 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/expver.h0000664000175000017500000000106415203070342030024 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/origin.h0000664000175000017500000000106415203070342030002 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/grid.h0000664000175000017500000000105615203070342027441 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/system.h0000664000175000017500000000106415203070342030037 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/anoffset.h0000664000175000017500000000107215203070342030317 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/activity.h0000664000175000017500000000107215203070342030346 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/htime.h0000664000175000017500000000106115203070342027616 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/channel.h0000664000175000017500000000106715203070342030126 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/ident.h0000664000175000017500000000106115203070342027613 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/timespan.h0000664000175000017500000000107215203070342030332 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/level.h0000664000175000017500000000101315203070342027614 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/wavelength.h0000664000175000017500000000110015203070342030646 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/chem.h0000664000175000017500000000105615203070342027430 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/domain.h0000664000175000017500000000106415203070342027762 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/generation.h0000664000175000017500000000110015203070342030635 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/realization.h0000664000175000017500000000110315203070342031026 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/packing.h0000664000175000017500000000106715203070342030132 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/experiment.h0000664000175000017500000000110015203070342030662 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/instrument.h0000664000175000017500000000110015203070342030712 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/levtype.h0000664000175000017500000000106715203070342030206 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/method.h0000664000175000017500000000106415203070342027773 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/date.h0000664000175000017500000000105615203070342027431 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/stream.h0000664000175000017500000000106415203070342030006 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/truncation.h0000664000175000017500000000110015203070342030670 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/dataset.h0000664000175000017500000000106715203070342030143 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/param.h0000664000175000017500000000106115203070342027610 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/number.h0000664000175000017500000000106415203070342030003 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/frequency.h0000664000175000017500000000107515203070342030516 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/levelist.h0000664000175000017500000000107215203070342030341 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/time.h0000664000175000017500000000105615203070342027452 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/resolution.h0000664000175000017500000000110015203070342030705 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/type.h0000664000175000017500000000105615203070342027475 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/class.h0000664000175000017500000000106115203070342027615 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/direction.h0000664000175000017500000000107515203070342030475 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/mars/step.h0000664000175000017500000000105615203070342027467 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.18.2/src/metkit/mars2grib/frontend/normalization/per_key/SanitizationRegistry.h0000664000175000017500000000227615203070342031764 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.18.2/src/metkit/mars2grib/frontend/normalization/normalizeMarsDict.h0000664000175000017500000000756715203070342027560 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/enableOptions.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::fixMarsGridEnabled; using metkit::mars2grib::utils::normalizeMarsEnabled; using metkit::mars2grib::utils::dict_traits::get_opt; // Track if we have moved data into scratch yet bool modified = false; bool needsFix = fixMarsGridEnabled(opt) && hack::needFixMarsGrid(mars); bool needsSanitize = normalizeMarsEnabled(opt); 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::normalizationmetkit-1.18.2/src/metkit/mars2grib/frontend/resolution/0000775000175000017500000000000015203070342023256 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/0000775000175000017500000000000015203070342026352 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/SectionTemplateSelectors.h0000664000175000017500000000310415203070342033505 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.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/0000775000175000017500000000000015203070342027313 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section0Recipes.h0000664000175000017500000000245215203070342032526 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.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section4Recipes.h0000664000175000017500000002240715203070342032534 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_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_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 >(); inline const Recipe S4_R104 = make_recipe<104, 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_R99, &S4_R100, &S4_R103, &S4_R104, &S4_R142, &S4_R143 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section3Recipes.h0000664000175000017500000000475015203070342032534 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 >(); //------------------------------------------------------------------------------ // Section 3 – Aggregated Recipes //------------------------------------------------------------------------------ inline const Recipes Section3Recipes{ 3, std::vector{ &S3_R0, &S3_R40, &S3_R50, &S3_R101, &S3_R101_ORCA, &S3_R150 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section2Recipes.h0000664000175000017500000000507115203070342032530 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 >(); // Satellite-related products inline const Recipe S2_R24 = make_recipe<24, Select, Select >(); // Analysis-related products inline const Recipe S2_R36 = make_recipe<36, 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_R24, &S2_R36, &S2_R1001, &S2_R1002 } }; // clang-format on } // namespace metkit::mars2grib::frontend::resolution::recipes::impl metkit-1.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section5Recipes.h0000664000175000017500000000315115203070342032530 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.18.2/src/metkit/mars2grib/frontend/resolution/section-recipes/impl/section1Recipes.h0000664000175000017500000000261015203070342032523 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.18.2/src/metkit/mars2grib/frontend/resolution/resolveSectionsLayout.h0000664000175000017500000001231715203070342030020 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 fails to resolve or returns invalid data. /// inline std::array resolve_SectionsLayout_or_throw( const ActiveConceptsData& activeConcepts) { using metkit::mars2grib::backend::concepts_::GeneralRegistry; using metkit::mars2grib::frontend::resolution::recipes::SectionTemplateSelectors; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; 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 (std::size_t 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( Mars2GribGenericException("Critical failure: Unable to resolve GRIB HeaderLayout", 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.18.2/src/metkit/mars2grib/frontend/resolution/resolveActiveConcepts.h0000664000175000017500000001014215203070342027737 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; 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; } } // Return the active concepts return activeConceptsData; } catch (...) { std::throw_with_nested(Mars2GribGenericException("Unable to match ActiveConcepts", Here())); } mars2gribUnreachable(); }; } // namespace metkit::mars2grib::frontend::resolutionmetkit-1.18.2/src/metkit/mars2grib/frontend/header/0000775000175000017500000000000015203070342022303 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/frontend/header/SpecializedEncoder.h0000664000175000017500000004545115203070342026221 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.18.2/src/metkit/mars2grib/frontend/header/EncodingPlan.h0000664000175000017500000002170115203070342025016 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.18.2/src/metkit/mars2grib/backend/0000775000175000017500000000000015203070342020623 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/encodeValues.h0000664000175000017500000001262215203070342023414 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::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}); } } catch (...) { std::throw_with_nested(Mars2GribGenericException("Critical failure in SpecializedEncoder execution", Here())); } } } // namespace metkit::mars2grib::backendmetkit-1.18.2/src/metkit/mars2grib/backend/checks/0000775000175000017500000000000015203070342022063 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/checks/checkDestinELocalSection.h0000664000175000017500000001051615203070342027070 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/checks/checkLocalUseSection.h0000664000175000017500000000655115203070342026275 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { 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.18.2/src/metkit/mars2grib/backend/checks/matchProductDefinitionTemplateNumber.h0000664000175000017500000001107615203070342031554 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/checks/matchLocalDefinitionNumber.h0000664000175000017500000001147615203070342027476 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/checks/checkDerivedProductDefinitionSection.h0000664000175000017500000000741015203070342031515 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { 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.18.2/src/metkit/mars2grib/backend/checks/checkStatisticsProductDefinitionSection.h0000664000175000017500000000736215203070342032273 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { 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.18.2/src/metkit/mars2grib/backend/checks/matchDataset.h0000664000175000017500000001025215203070342024636 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/checks/matchDataRepresentationTemplateNumber.h0000664000175000017500000001103615203070342031713 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/checks/checkEnsembleProductDefinitionSection.h0000664000175000017500000000765515203070342031700 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { 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.18.2/src/metkit/mars2grib/backend/checks/matchGridDefinitionTemplateNumber.h0000664000175000017500000001073315203070342031020 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/enableOptions.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::checksEnabled; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::joinNumbers; using metkit::mars2grib::utils::exceptions::Mars2GribValidationException; try { if (checksEnabled(opt)) { // 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.18.2/src/metkit/mars2grib/backend/concepts/0000775000175000017500000000000015203070342022441 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/nil/0000775000175000017500000000000015203070342023223 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/nil/nilEncoding.h0000664000175000017500000001054715203070342025634 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 nilOp.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.18.2/src/metkit/mars2grib/backend/concepts/nil/nilMatcher.h0000664000175000017500000000070115203070342025460 0ustar alastairalastair#pragma once // System include #include // Utils #include "metkit/mars2grib/backend/concepts/nil/nilEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t nilMatcher(const MarsDict_t& mars, const OptDict_t& opt) { return static_cast(NilType::Default); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/nil/nilEnum.h0000664000175000017500000001026615203070342025010 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 /// `nil.h` / `nilOp` 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.18.2/src/metkit/mars2grib/backend/concepts/nil/nilConceptDescriptor.h0000664000175000017500000001254615203070342027541 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 NilConcept.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.18.2/src/metkit/mars2grib/backend/concepts/AllConcepts.h0000664000175000017500000001706615203070342025033 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. /// #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/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/level/levelConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/longrange/longrangeConceptDescriptor.h" #include "metkit/mars2grib/backend/concepts/mars/marsConceptDescriptor.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/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_::detailmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/GeneralRegistry.h0000664000175000017500000001176215203070342025727 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. /// #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.18.2/src/metkit/mars2grib/backend/concepts/analysis/0000775000175000017500000000000015203070342024264 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/analysis/analysisEnum.h0000664000175000017500000001045515203070342027112 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 /// `analysis.h` / `analysisOp` 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.18.2/src/metkit/mars2grib/backend/concepts/analysis/analysisEncoding.h0000664000175000017500000001532715203070342027737 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 analysisOp.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}); // 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.18.2/src/metkit/mars2grib/backend/concepts/analysis/analysisConceptDescriptor.h0000664000175000017500000001456015203070342031641 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 AnalysisConcept.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.18.2/src/metkit/mars2grib/backend/concepts/analysis/analysisMatcher.h0000664000175000017500000000127115203070342027565 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t analysisMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "anoffset")) { return static_cast(AnalysisType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/statistics/0000775000175000017500000000000015203070342024633 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsMatcher.h0000664000175000017500000001133715203070342030507 0ustar alastairalastair#pragma once // System include #include #include // Utils #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_ { template std::size_t statisticsMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_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); } if (matchAny(param, range(141101, 141105), 141208, 141209, 141215, 141216, 141220, 141229, 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, range(235129, 235138), 235151, 235152, 235155, 235157, 235159, 235165, 235166, 235168, 235189, 235203, 235246, 235263, 235269, 235283, 235287, 235288, 235290, 235305, 235309, 235322, 235326, 235339, 235383, 263024, 263107)) { return static_cast(StatisticsType::Average); } if (matchAny(param, 49, 121, 123, 201, range(143101, 143105), 143208, 143209, 143215, 143216, 143220, 143229, 143232, 143233, 143245, 228026, 228028, 228035, 228036, 228222, 228224, 228226, 237013, 237041, 237042, 237055, 237077, 237078, 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, 237321, 237322, 237326, 265024)) { return static_cast(StatisticsType::Maximum); } if (matchAny(param, 122, 202, range(144101, 144105), 144208, 144209, 144215, 144216, 144220, 144229, 144232, 144233, 144245, 228027, 228223, 228225, 228227, 238013, 238041, 238042, 238055, 238077, 238078, 238080, 238083, 238084, 238087, 238088, 238090, 238091, 238093, 238094, 238097, 238108, 238131, 238132, 238134, 238137, 238151, 238159, range(238165, 238168), 238203, 238207, 238263, 238287, 238288, 238290, 238305, 238309, 238322, 238326, 266024)) { 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, 145232, 145233, 145245, 239041, 239042, 239077, 239078, 239080, 239083, 239084, 239087, 239088, 239090, 239091, 239093, 239094, 239097, 239108, 239131, 239132, 239134, 239137, 239151, 239159, range(239165, 239168), 239203, 239207, 239263, 239287, 239288, 239290, 239305, 239309, 239322, 239326, 267024)) { 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()); // } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsEnum.h0000664000175000017500000001745515203070342030037 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 /// `statistics.h` / `statisticsOp` 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 `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 } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsConceptDescriptor.h0000664000175000017500000000730415203070342032555 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 StatisticsConcept.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 { static constexpr std::string_view entryName() { return statisticsName; } template static constexpr std::string_view variantName() { return statisticsTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &statisticsMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/statistics/statisticsEncoding.h0000664000175000017500000002617215203070342030655 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 statisticsOp.h /// @brief Implementation of the GRIB `statistics` concept operation. /// /// This header defines the applicability rules and execution logic for the /// **statistics concept** within the mars2grib backend. /// /// The `statistics` concept is responsible for encoding GRIB metadata /// related to statistical processing over time, including: /// - type of statistical processing (e.g. mean, accumulation, extremes), /// - time range structure, /// - time increment and span, /// - start and end steps for statistical intervals. /// /// The concept operates exclusively in the *Product Definition Section* /// (Section 4) and is executed across multiple encoding stages /// (`Allocate`, `Preset`, `Runtime`), each contributing a well-defined /// subset of the GRIB keys. /// /// The implementation follows the standard mars2grib concept model: /// - Compile-time applicability via `statisticsApplicable` /// - Stage-dependent encoding logic /// - Centralized deduction of time descriptors from MARS metadata /// - Strict validation of GRIB structural constraints /// - Context-rich error handling /// /// @note /// Support for multiple time ranges is currently **incomplete** and /// explicitly rejected at both preset and runtime stages. /// This limitation is documented and enforced at runtime. /// /// @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/statistics/statisticsEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/forecastTimeInSeconds.h" #include "metkit/mars2grib/backend/deductions/numberOfTimeRanges.h" #include "metkit/mars2grib/backend/deductions/statisticsDescriptor.h" #include "metkit/mars2grib/backend/deductions/timeIncrementInSeconds.h" #include "metkit/mars2grib/backend/deductions/timeSpanInSeconds.h" // checks #include "metkit/mars2grib/backend/checks/checkStatisticsProductDefinitionSection.h" // Tables #include "metkit/mars2grib/backend/tables/timeUnits.h" #include "metkit/mars2grib/backend/tables/typeOfTimeIntervals.h" // Deduction helpers #include "metkit/mars2grib/backend/deductions/detail/timeUtils.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. /// /// This predicate determines whether the `statistics` concept is applicable /// for a given encoding stage, GRIB section, and concept variant. /// /// The concept is applicable for: /// - any encoding stage /// - the *Product Definition Section* (Section 4) /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index /// @tparam Variant Statistics concept variant /// /// @return `true` if the concept is applicable, `false` otherwise. /// template constexpr bool statisticsApplicable() { return (Section == SecProductDefinitionSection); } /// /// @brief Execute the `statistics` concept operation. /// /// This function implements the runtime logic of the GRIB `statistics` concept. /// Depending on the encoding stage, it performs the following actions: /// /// ### StageAllocate /// - Validates that the Product Definition Section supports statistics. /// - Encodes the number of statistical time ranges. /// /// ### StagePreset /// - Encodes the statistical processing type. /// - Encodes time unit metadata for ranges and increments. /// - Handles special cases where the time increment is missing /// (legacy AIFS behavior). /// - Rejects unsupported multi-range configurations. /// /// ### StageRuntime /// - Computes and encodes `startStep` and `endStep`. /// - Resolves time span and forecast step from MARS metadata. /// - Explicitly rejects multiple time ranges. /// /// The concept relies on multiple time-related deductions and helper /// utilities to interpret MARS time semantics consistently. /// /// @tparam Stage Encoding stage (compile-time constant) /// @tparam Section GRIB section index /// @tparam Variant Statistics 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 incompatible with statistics /// - unsupported multi-range configurations are detected /// - any deduction or encoding step fails /// /// @note /// - Time units are currently normalized to **hours** at GRIB level. /// - Time increment handling contains legacy logic and known hacks. /// - Multiple time ranges are not yet supported. /// /// @see statisticsApplicable /// @see deductions::numberOfTimeRanges /// @see deductions::getTimeDescriptorFromMars_orThrow /// template void StatisticsOp(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 (statisticsApplicable()) { try { MARS2GRIB_LOG_CONCEPT(statistics); // Global deduction used in multiple stages long numberOfTimeRangesVal = deductions::numberOfTimeRanges(mars, par); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Stage == StageAllocate) { // 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", numberOfTimeRangesVal); } if constexpr (Stage == StagePreset) { // Deductions std::optional timeIncrementInSecondsOpt = deductions::timeIncrementInSeconds_opt(mars, par); // Encoding set_or_throw(out, "typeOfStatisticalProcessing", typeOfStatisticalProcessing()); set_or_throw(out, "indicatorOfUnitOfTimeRange", static_cast(TimeUnit::Hour)); set_or_throw(out, "indicatorOfUnitForTimeRange", static_cast(TimeUnit::Hour)); // HACK: handle special case for AIFS (MUL-227) if (numberOfTimeRangesVal == 1 && !timeIncrementInSecondsOpt.has_value()) { // Encoding set_or_throw( out, "typeOfTimeIncrement", static_cast(tables::TypeOfTimeIntervals::SameStartTimeForecastIncremented)); set_or_throw(out, "indicatorOfUnitForTimeIncrement", static_cast(TimeUnit::Missing)); set_or_throw(out, "timeIncrement", 0L); } else { // Encoding set_or_throw( out, "typeOfTimeIncrement", static_cast(tables::TypeOfTimeIntervals::SameStartTimeForecastIncremented)); set_or_throw(out, "indicatorOfUnitForTimeIncrement", static_cast(TimeUnit::Second)); set_or_throw(out, "timeIncrement", timeIncrementInSecondsOpt.value()); // Test WIP deductions::StatisticalProcessing statsDesc = deductions::getTimeDescriptorFromMars_orThrow( mars, par, opt, typeOfStatisticalProcessing()); if (numberOfTimeRangesVal > 1) { MARS2GRIB_CONCEPT_THROW( statistics, "`statistics` concept with multiple time ranges not yet supported at preset stage..."); } } } if constexpr (Stage == StageRuntime) { // Deductions long stepInHour = deductions::resolve_ForecastTimeInSeconds_or_throw(mars, par, opt) / 3600; long timeSpanInHour = deductions::resolve_TimeSpanInSeconds_or_throw(mars, par, opt) / 3600; // Get the length of timestep in seconds std::optional timeIncrementInSecondsOpt = deductions::timeIncrementInSeconds_opt(mars, par); long tmp = stepInHour - timeSpanInHour; long startStep = (tmp >= 0) ? tmp : 0; long endStep = stepInHour; // Encoding set_or_throw(out, "startStep", startStep); set_or_throw(out, "endStep", endStep); // Test WIP if (timeIncrementInSecondsOpt.has_value()) { deductions::StatisticalProcessing statsDesc = deductions::getTimeDescriptorFromMars_orThrow( mars, par, opt, typeOfStatisticalProcessing()); }; if (numberOfTimeRangesVal > 1) { MARS2GRIB_CONCEPT_THROW( statistics, "`statistics` concept with multiple time ranges not yet supported at runtime stage..."); } } } 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.18.2/src/metkit/mars2grib/backend/concepts/generating-process/0000775000175000017500000000000015203070342026240 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h0000664000175000017500000001105615203070342032723 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 /// `generatingProcess.h` / `generatingProcessOp` 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.18.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessMatcher.h0000664000175000017500000000077215203070342033405 0ustar alastairalastair#pragma once // System include #include // Utils #include "metkit/mars2grib/backend/concepts/generating-process/generatingProcessEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t generatingProcessMatcher(const MarsDict_t& mars, const OptDict_t& opt) { return static_cast(GeneratingProcessType::Default); } } // namespace metkit::mars2grib::backend::concepts_ ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessConceptDescriptor.hmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessConceptDescr0000664000175000017500000001515615203070342034152 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 GeneratingProcessConcept.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.18.2/src/metkit/mars2grib/backend/concepts/generating-process/generatingProcessEncoding.h0000664000175000017500000002137115203070342033546 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 generatingProcessOp.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.18.2/src/metkit/mars2grib/backend/concepts/param/0000775000175000017500000000000015203070342023541 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/param/paramEncoding.h0000664000175000017500000001274415203070342026471 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 paramOp.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::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (paramApplicable()) { try { MARS2GRIB_LOG_CONCEPT(param); // Deductions long paramId = deductions::resolve_ParamId_or_throw(mars, par, opt); // Encoding set_or_throw(out, "paramId", paramId); } catch (...) { MARS2GRIB_CONCEPT_RETHROW(param, "Unable to set `param` concept..."); } // 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.18.2/src/metkit/mars2grib/backend/concepts/param/paramMatcher.h0000664000175000017500000000071115203070342026315 0ustar alastairalastair#pragma once // System include #include // Utils #include "metkit/mars2grib/backend/concepts/param/paramEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t paramMatcher(const MarsDict_t& mars, const OptDict_t& opt) { return static_cast(ParamType::ParamId); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/param/paramConceptDescriptor.h0000664000175000017500000000711415203070342030370 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 ParamConcept.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 { static constexpr std::string_view entryName() { return paramName; } template static constexpr std::string_view variantName() { return paramTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return ¶mMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/param/paramEnum.h0000664000175000017500000001030515203070342025636 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 /// `param.h` / `paramOp` 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.18.2/src/metkit/mars2grib/backend/concepts/longrange/0000775000175000017500000000000015203070342024415 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeEncoding.h0000664000175000017500000001435215203070342030216 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 longrangeOp.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::Default) && (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}); // 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.18.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeEnum.h0000664000175000017500000001053515203070342027373 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 /// `longrange.h` / `longrangeOp` 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 { Default = 0 }; /// /// @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::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeConceptDescriptor.h0000664000175000017500000001277415203070342032130 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 LongrangeConcept.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.18.2/src/metkit/mars2grib/backend/concepts/longrange/longrangeMatcher.h0000664000175000017500000000136215203070342030050 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t longrangeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "method") && has(mars, "system")) { return static_cast(LongrangeType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/destine/0000775000175000017500000000000015203070342024074 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/destine/destineConceptDescriptor.h0000664000175000017500000001452515203070342031262 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 DestineConcept.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.18.2/src/metkit/mars2grib/backend/concepts/destine/destineEncoding.h0000664000175000017500000002202215203070342027345 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 destineOp.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.18.2/src/metkit/mars2grib/backend/concepts/destine/destineEnum.h0000664000175000017500000001053515203070342026531 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 /// `destine.h` / `destineOp` 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.18.2/src/metkit/mars2grib/backend/concepts/destine/destineMatcher.h0000664000175000017500000000323415203070342027206 0ustar alastairalastair#pragma once // System 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_ { template std::size_t destineMatcher(const MarsDict_t& mars, 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::Mars2GribGenericException; 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 Mars2GribGenericException{"Unknown value \"" + get_or_throw(mars, "dataset") + "\" for mars keyword \"dataset\"!"}; } } else { throw Mars2GribGenericException{ "Missing required mars keyword \"dataset\" for class \"d1\" without \"anoffset\"!"}; } } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/concepts.md0000664000175000017500000001271515203070342024607 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. 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. --- ## 4. 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. --- ## 5. 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**. --- ## 6. 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 --- ## 7. 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 --- ## 8. 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. --- ## 9. 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**. --- ## 10. 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*. --- ## 11. 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. --- ## 12. 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.18.2/src/metkit/mars2grib/backend/concepts/composition/0000775000175000017500000000000015203070342025004 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/composition/compositionEncoding.h0000664000175000017500000001513515203070342031174 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 compositionOp.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/constituentType.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::set_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribConceptException; if constexpr (compositionApplicable()) { try { MARS2GRIB_LOG_CONCEPT(composition); // ============================================================= // Structural validation // ============================================================= /// @todo [owner: dgov][scope: concept][reason: completeness][prio: low] // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Variant == CompositionType::Chem) { // Structural validation /// @todo [owner: dgov][scope: concept][reason: completeness][prio: low] // Deductions long constituentType = deductions::resolve_ConstituentType_or_throw(mars, par, opt); // Encoding set_or_throw(out, "constituentType", constituentType); } } 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.18.2/src/metkit/mars2grib/backend/concepts/composition/compositionEnum.h0000664000175000017500000001165115203070342030351 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 /// `composition.h` / `compositionOp` 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.18.2/src/metkit/mars2grib/backend/concepts/composition/compositionConceptDescriptor.h0000664000175000017500000001470115203070342033076 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 CompositionConcept.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.18.2/src/metkit/mars2grib/backend/concepts/composition/compositionMatcher.h0000664000175000017500000000145315203070342031027 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t compositionMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "chem")) { return static_cast(CompositionType::Chem); } if (has(mars, "wavelength")) { return static_cast(CompositionType::Aerosol); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/satellite/0000775000175000017500000000000015203070342024427 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteEncoding.h0000664000175000017500000002325415203070342030243 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 satelliteOp.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/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}); // Deductions long channel = deductions::resolve_Channel_or_throw(mars, par, opt); // Encoding set_or_throw(out, "channel", channel); } 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.18.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteConceptDescriptor.h0000664000175000017500000000725415203070342032151 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 SatelliteConcept.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 { static constexpr std::string_view entryName() { return satelliteName; } template static constexpr std::string_view variantName() { return satelliteTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &satelliteMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteEnum.h0000664000175000017500000001051115203070342027411 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 /// `satellite.h` / `satelliteOp` 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.18.2/src/metkit/mars2grib/backend/concepts/satellite/satelliteMatcher.h0000664000175000017500000000135015203070342030071 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t satelliteMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "channel") && has(mars, "ident") && has(mars, "instrument")) { return static_cast(SatelliteType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/packing/0000775000175000017500000000000015203070342024055 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/packing/packingEncoding.h0000664000175000017500000001753215203070342027321 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 packingOp.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/laplacianOperator.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() { return (Stage == StagePreset && Section == SecDataRepresentationSection); } /// /// @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}); // Get bits per value long bitsPerValue = deductions::resolve_BitsPerValueGridded_or_throw(mars, par, opt); // Set bits per value set_or_throw(out, "bitsPerValue", bitsPerValue); } if constexpr (Variant == PackingType::Ccsds) { // Check sample structure validation::match_DataRepresentationTemplateNumber_or_throw(opt, out, {42}); // Get bits per value long bitsPerValue = deductions::resolve_BitsPerValueGridded_or_throw(mars, par, opt); // Set bits per value set_or_throw(out, "bitsPerValue", bitsPerValue); } if constexpr (Variant == PackingType::SpectralComplex) { // Check sample structure validation::match_DataRepresentationTemplateNumber_or_throw(opt, out, {51}); // Get bits per value long bitsPerValue = deductions::resolve_BitsPerValueSpectral_or_throw(mars, par, opt); double laplacianOperator = deductions::resolve_LaplacianOperator_or_throw(mars, par, opt); long subSetTruncation = deductions::resolve_SubSetTruncation_or_throw(mars, par, opt); // Set bits per value set_or_throw(out, "bitsPerValue", bitsPerValue); set_or_throw(out, "laplacianOperator", laplacianOperator); 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.18.2/src/metkit/mars2grib/backend/concepts/packing/packingConceptDescriptor.h0000664000175000017500000000717415203070342031226 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 PackingConcept.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 { static constexpr std::string_view entryName() { return packingName; } template static constexpr std::string_view variantName() { return packingTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &packingMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/packing/packingEnum.h0000664000175000017500000001076515203070342026500 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 /// `packing.h` / `packingOp` 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.18.2/src/metkit/mars2grib/backend/concepts/packing/packingMatcher.h0000664000175000017500000000224515203070342027151 0ustar alastairalastair#pragma once // System 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_ { template std::size_t packingMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; 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 Mars2GribGenericException{"Unknown value \"" + packing + "\" for mars keyword \"packing\"!"}; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/level/0000775000175000017500000000000015203070342023550 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/level/levelConceptDescriptor.h0000664000175000017500000001443715203070342030414 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 LevelConcept.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.18.2/src/metkit/mars2grib/backend/concepts/level/levelEnum.h0000664000175000017500000002132515203070342025660 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 /// `level.h` / `levelOp` 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 /// /// @warning /// Do not reorder existing enumerators, as they 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, Hybrid, Theta, PotentialVorticity, SnowLayer, SoilLayer, SeaIceLayer, OceanSurface, DepthBelowSeaLayer, OceanSurfaceToBottom, LakeBottom, MixingLayer, OceanModel, OceanModelLayer, MixedLayerDepthByDensity, MixedLayerDepthByTemperature, SnowLayerOverIceOnWater, IceTopOnWater, IceLayerOnWater, EntireMeltPond, WaterSurfaceToIsothermalOceanLayer, AbstractSingleLevel, AbstractMultipleLevel, HeightAboveSeaAt10M, HeightAboveSeaAt2M, HeightAboveGroundAt10M, HeightAboveGroundAt2M, 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"); DEF(LevelType::Hybrid, "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::AbstractMultipleLevel, "abstractMultipleLevel"); DEF(LevelType::HeightAboveSeaAt10M, "heightAboveSeaAt10m"); DEF(LevelType::HeightAboveSeaAt2M, "heightAboveSeaAt2m"); DEF(LevelType::HeightAboveGroundAt10M, "heightAboveGroundAt10m"); DEF(LevelType::HeightAboveGroundAt2M, "heightAboveGroundAt2m"); DEF(LevelType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/level/levelMatcher.h0000664000175000017500000003724515203070342026347 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 // System 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 { inline std::size_t matchSFC(const long param) { 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, 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); } if (matchAny(param, 228007, 228011)) { return static_cast(LevelType::EntireLake); } if (matchAny(param, 129172)) { return static_cast(LevelType::HeightAboveGround); } 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); } 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); } if (matchAny(param, 140233, 140245, 140249, 141233, 141245, 143233, 143245, 144233, 144245, 145233, 145245)) { return static_cast(LevelType::HeightAboveSeaAt10M); } 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))) { 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, range(131074, 131077), 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)) { return static_cast(LevelType::Surface); } if (matchAny(param, 228045, 235322, 237322, 238322, 239322)) { return static_cast(LevelType::Tropopause); } // Satellite if (matchAny(param, 194)) { return static_cast(LevelType::Surface); } // Chemical if (matchAny(param, range(228080, 228085), range(233032, 233035), range(235062, 235064))) { return static_cast(LevelType::Surface); } // Wave period if (matchAny(param, range(140114, 140120))) { return compile_time_registry_engine::MISSING; } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype SFC", Here()); } inline std::size_t matchHL(const long param) { 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()); } inline std::size_t matchML(const long param) { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; if (matchAny(param, range(21, 23), range(75, 77), range(129, 133), 135, 138, 152, range(155, 157), 203, range(246, 248), range(162100, 162113), 260290, 260292, 260293)) { return static_cast(LevelType::Hybrid); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype ML", Here()); } inline std::size_t matchPL(const long param, const long level) { 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)) { 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()); } inline std::size_t matchPT(const long param) { 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)) { return static_cast(LevelType::Theta); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype PT", Here()); } inline std::size_t matchPV(const long param) { 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)) { return static_cast(LevelType::PotentialVorticity); } throw utils::exceptions::Mars2GribMatcherException( "No mapping exists for param \"" + std::to_string(param) + "\" on levtype PV", Here()); } inline std::size_t matchSOL(const long param) { 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()); } inline std::size_t matchAL(const long param) { 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()); } inline std::size_t matchO2D(const long param) { 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))) { 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)) { return static_cast(LevelType::DepthBelowSeaLayer); } if (matchAny(param, 262120, 262123)) { 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()); } inline std::size_t matchO3D(const long param) { 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()); } } // namespace impl template std::size_t levelMatcher(const MarsDict_t& mars, const OptDict_t& opt) { 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 == "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()); }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/level/levelEncoding.h0000664000175000017500000002735015203070342026506 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 levelOp.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` /// - hybrid vertical coordinate parameters (`pv` array) /// /// 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 (hybrid levels). /// /// 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 hybrid vertical coordinates require a PV array describing the /// vertical transformation. /// /// @tparam Variant Level concept variant /// /// @return `true` if a PV array is required, /// `false` otherwise. /// template constexpr bool needPv() { if constexpr (Variant == LevelType::Hybrid) { 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), /// while others encode only the level type. /// /// @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::Hybrid || Variant == LevelType::IsobaricInHpa || Variant == LevelType::IsobaricInPa || Variant == LevelType::Isothermal || Variant == LevelType::PotentialVorticity || Variant == LevelType::Theta || Variant == LevelType::OceanModel) { 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. /// /// Hybrid levels 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. /// - Se 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.18.2/src/metkit/mars2grib/backend/concepts/reference-time/0000775000175000017500000000000015203070342025333 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeConceptDescriptor.h0000664000175000017500000000741715203070342033645 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 ReferenceTimeConcept.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 { static constexpr std::string_view entryName() { return referenceTimeName; } template static constexpr std::string_view variantName() { return referenceTimeTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &referenceTimeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeEncoding.h0000664000175000017500000002344715203070342031742 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 referenceTimeOp.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 /// /// @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/hindcastDateTime.h" #include "metkit/mars2grib/backend/deductions/referenceDateTime.h" #include "metkit/mars2grib/backend/deductions/significanceOfReferenceTime.h" // Tables #include "metkit/mars2grib/backend/tables/significanceOfReferenceTime.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 /// /// 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); // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Section == SecIdentificationSection) { // Deductions 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)) { // Deductions eckit::DateTime dateTime = deductions::resolve_ReferenceDateTime_or_throw(mars, par, opt); // 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)) { // Deductions eckit::DateTime referenceDateTime = deductions::resolve_HindcastDateTime_or_throw(mars, par, opt); // 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}); // Deduction eckit::DateTime dateTime = deductions::resolve_ReferenceDateTime_or_throw(mars, par, opt); // 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.18.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeEnum.h0000664000175000017500000001103215203070342031103 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 /// `referenceTime.h` / `referenceTimeOp` 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.18.2/src/metkit/mars2grib/backend/concepts/reference-time/referenceTimeMatcher.h0000664000175000017500000000133515203070342031567 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t referenceTimeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "hdate")) { return static_cast(ReferenceTimeType::Reforecast); } return static_cast(ReferenceTimeType::Standard); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/derived/0000775000175000017500000000000015203070342024063 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/derived/derivedEnum.h0000664000175000017500000001342515203070342026510 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 /// `derived.h` / `derivedOp` 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 { Individual = 0, Derived, PerturbedParameters, RandomPatterns, MeanUnweightedAll, MeanWeightedAll, StddevCluster, StddevClusterNorm, SpreadAll, LargeAnomalyIndex, MeanUnweightedCluster, Iqr, MinAll, MaxAll, VarianceAll, 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::Individual, "individual"); DEF(DerivedType::Derived, "derived"); DEF(DerivedType::PerturbedParameters, "perturbedParameters"); DEF(DerivedType::RandomPatterns, "randomPatterns"); DEF(DerivedType::MeanUnweightedAll, "meanUnweightedAll"); DEF(DerivedType::MeanWeightedAll, "meanWeightedAll"); DEF(DerivedType::StddevCluster, "stddevCluster"); DEF(DerivedType::StddevClusterNorm, "stddevClusterNorm"); DEF(DerivedType::SpreadAll, "spreadAll"); DEF(DerivedType::LargeAnomalyIndex, "largeAnomalyIndex"); DEF(DerivedType::MeanUnweightedCluster, "meanUnweightedCluster"); DEF(DerivedType::Iqr, "iqr"); DEF(DerivedType::MinAll, "minAll"); DEF(DerivedType::MaxAll, "maxAll"); DEF(DerivedType::VarianceAll, "varianceAll"); DEF(DerivedType::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/derived/derivedConceptDescriptor.h0000664000175000017500000001452515203070342031240 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 DerivedConcept.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.18.2/src/metkit/mars2grib/backend/concepts/derived/derivedMatcher.h0000664000175000017500000000210115203070342027154 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t derivedMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; const auto& type = get_or_throw(mars, "type"); if (type == "em" || // Ensemble mean type == "es" || // Ensemble standard deviation type == "taem" || // Time-averaged ensemble mean type == "taes" || // Time-averaged ensemble standard deviation type == "efi" || // Extreme forecast index type == "sot" // Shift of tails ) { return static_cast(DerivedType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/derived/derivedEncoding.h0000664000175000017500000001562015203070342027331 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 derivedOp.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/derivedForecast.h" #include "metkit/mars2grib/backend/deductions/numberOfForecastsInEnsemble.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); } /// /// @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); // 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.18.2/src/metkit/mars2grib/backend/concepts/origin/0000775000175000017500000000000015203070342023730 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/origin/originConceptDescriptor.h0000664000175000017500000000714415203070342030751 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 OriginConcept.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 { static constexpr std::string_view entryName() { return originName; } template static constexpr std::string_view variantName() { return originTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &originMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/origin/originMatcher.h0000664000175000017500000000071515203070342026677 0ustar alastairalastair#pragma once // System include #include // Utils #include "metkit/mars2grib/backend/concepts/origin/originEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t originMatcher(const MarsDict_t& mars, const OptDict_t& opt) { return static_cast(OriginType::Default); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/origin/originEnum.h0000664000175000017500000001035515203070342026221 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 /// `origin.h` / `originOp` 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.18.2/src/metkit/mars2grib/backend/concepts/origin/originEncoding.h0000664000175000017500000001307215203070342027042 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 originOp.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.18.2/src/metkit/mars2grib/backend/concepts/mars/0000775000175000017500000000000015203070342023403 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/mars/marsMatcher.h0000664000175000017500000000134415203070342026024 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t marsMatcher(const MarsDict_t& mars, const OptDict_t& opt) { 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; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/mars/marsConceptDescriptor.h0000664000175000017500000001257715203070342030105 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 MarsConcept.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.18.2/src/metkit/mars2grib/backend/concepts/mars/marsEnum.h0000664000175000017500000001027315203070342025346 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 /// `mars.h` / `marsOp` 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.18.2/src/metkit/mars2grib/backend/concepts/mars/marsEncoding.h0000664000175000017500000001705215203070342026172 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 marsOp.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.18.2/src/metkit/mars2grib/backend/concepts/tables/0000775000175000017500000000000015203070342023713 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/tables/tablesEncoding.h0000664000175000017500000001726115203070342027014 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 tablesOp.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.18.2/src/metkit/mars2grib/backend/concepts/tables/tablesConceptDescriptor.h0000664000175000017500000000714415203070342030717 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 TablesConcept.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 { static constexpr std::string_view entryName() { return tablesName; } template static constexpr std::string_view variantName() { return tablesTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &tablesMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/tables/tablesMatcher.h0000664000175000017500000000137615203070342026651 0ustar alastairalastair#pragma once // System 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/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t tablesMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; return static_cast(TablesType::Default); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/tables/tablesEnum.h0000664000175000017500000001043215203070342026163 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 /// `tables.h` / `tablesOp` 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.18.2/src/metkit/mars2grib/backend/concepts/EncodingCallbacksRegistry.h0000664000175000017500000002223215203070342027672 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. /// #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.18.2/src/metkit/mars2grib/backend/concepts/wave/0000775000175000017500000000000015203070342023403 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/wave/waveEnum.h0000664000175000017500000001045315203070342025346 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 /// `wave.h` / `waveOp` 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.18.2/src/metkit/mars2grib/backend/concepts/wave/waveEncoding.h0000664000175000017500000003040015203070342026162 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 waveOp.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}); } 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.18.2/src/metkit/mars2grib/backend/concepts/wave/waveConceptDescriptor.h0000664000175000017500000000706415203070342030100 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 WaveConcept.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 { static constexpr std::string_view entryName() { return waveName; } template static constexpr std::string_view variantName() { return waveTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &waveMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/wave/waveMatcher.h0000664000175000017500000000226715203070342026031 0ustar alastairalastair#pragma once // System 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/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t waveMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; const auto param = get_or_throw(mars, "param"); if (matchAny(param, range(140114, 140120))) { return static_cast(WaveType::Period); } if (matchAny(param, 140251)) { ASSERT(has(mars, "frequency")); ASSERT(has(mars, "direction")); return static_cast(WaveType::Spectra); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/ensemble/0000775000175000017500000000000015203070342024233 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleEnum.h0000664000175000017500000001100115203070342027014 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 /// `ensemble.h` / `ensembleOp` 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 }; /// /// @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"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleConceptDescriptor.h0000664000175000017500000001456015203070342031557 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 EnsembleConcept.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.18.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleEncoding.h0000664000175000017500000001676015203070342027657 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 ensembleOp.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" // 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) && (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** ensemble variant, which /// represents 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); } } 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.18.2/src/metkit/mars2grib/backend/concepts/ensemble/ensembleMatcher.h0000664000175000017500000000127215203070342027504 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t ensembleMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "number")) { return static_cast(EnsembleType::Individual); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/0000775000175000017500000000000015203070342026022 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthMatcher.h0000664000175000017500000000155415203070342032516 0ustar alastairalastair#pragma once // System 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" namespace metkit::mars2grib::backend::concepts_ { template std::size_t shapeOfTheEarthMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::has; // 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); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEncoding.h0000664000175000017500000001346615203070342032666 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 shapeOfTheEarthOp.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() { 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.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescriptor.hmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthConceptDescrip0000664000175000017500000000750515203070342033614 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 ShapeOfTheEarthConcept.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 { static constexpr std::string_view entryName() { return shapeOfTheEarthName; } template static constexpr std::string_view variantName() { return shapeOfTheEarthTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &shapeOfTheEarthMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/shape-of-the-earth/shapeOfTheEarthEnum.h0000664000175000017500000001076315203070342032041 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 /// `shapeOfTheEarth.h` / `shapeOfTheEarthOp` 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 { Default = 0 }; /// /// @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::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/point-in-time/0000775000175000017500000000000015203070342025132 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeConceptDescriptor.h0000664000175000017500000000734215203070342033123 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 PointInTimeConcept.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 { static constexpr std::string_view entryName() { return pointInTimeName; } template static constexpr std::string_view variantName() { return pointInTimeTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &pointInTimeMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeMatcher.h0000664000175000017500000000606015203070342031050 0ustar alastairalastair#pragma once // System 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/paramMatcher.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t pointInTimeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::util::param_matcher::matchAny; using metkit::mars2grib::util::param_matcher::range; using metkit::mars2grib::utils::dict_traits::get_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(131074, 131077), 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(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))) { return static_cast(PointInTimeType::Default); } return compile_time_registry_engine::MISSING; } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEnum.h0000664000175000017500000001061115203070342030366 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 /// `pointInTime.h` / `pointInTimeOp` 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.18.2/src/metkit/mars2grib/backend/concepts/point-in-time/pointInTimeEncoding.h0000664000175000017500000001710415203070342031214 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 pointInTimeOp.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 deduction of forecast time /// - Stage-specific encoding logic /// - 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/point-in-time/pointInTimeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" // Tables #include "metkit/mars2grib/backend/tables/timeUnits.h" // Deductions #include "metkit/mars2grib/backend/deductions/forecastTimeInSeconds.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: /// /// - Deduces the forecast time offset from the input dictionaries /// - 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); // Deductions long marsStepInSeconds = deductions::resolve_ForecastTimeInSeconds_or_throw(mars, par, opt); // Basic checks if (marsStepInSeconds % 3600 != 0) { throw Mars2GribConceptException( std::string(pointInTimeName), std::string(pointInTimeTypeName()), std::to_string(Stage), std::to_string(Section), "Only full hour steps are supported currently", Here()); } long marsStepInHours = marsStepInSeconds / 3600; // ============================================================= // Variant-specific logic // ============================================================= if constexpr (Stage == StageAllocate) { // Encoding setMissing_or_throw(out, "hoursAfterDataCutoff"); setMissing_or_throw(out, "minutesAfterDataCutoff"); } if constexpr (Stage == StagePreset) { // Encoding set_or_throw(out, "indicatorOfUnitOfTimeRange", static_cast(TimeUnit::Hour)); } if constexpr (Stage == StageRuntime) { // Encoding set_or_throw(out, "forecastTime", marsStepInHours); } } 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.18.2/src/metkit/mars2grib/backend/concepts/representation/0000775000175000017500000000000015203070342025503 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/representation/representationMatcher.h0000664000175000017500000000335515203070342032230 0ustar alastairalastair#pragma once // System 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_ { template std::size_t representationMatcher(const MarsDict_t& mars, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; if (has(mars, "truncation")) { return static_cast(RepresentationType::SphericalHarmonics); } const auto marsGrid = get_or_throw(mars, "grid"); const auto gridType = 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()); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/representation/representationEncoding.h0000664000175000017500000005335315203070342032376 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 representationOp.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 // 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" // 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_ { /// /// @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() { 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); } 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); } 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"); } 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); } 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); } 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); } else if constexpr (Variant == RepresentationType::Fesom) { MARS2GRIB_CONCEPT_THROW(representation, "Support for Fesom representation not implemented..."); } 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.18.2/src/metkit/mars2grib/backend/concepts/representation/representationEnum.h0000664000175000017500000001263415203070342031551 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 /// `representation.h` / `representationOp` 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, 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::Default, "default"); #undef DEF } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/representation/representationConceptDescriptor.h0000664000175000017500000000745615203070342034305 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 { static constexpr std::string_view entryName() { return representationName; } template static constexpr std::string_view variantName() { return representationTypeName(); } 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; } template static constexpr Fm entryCallbacks() { if constexpr (Capability == 0) { return &representationMatcher; } else { return nullptr; } } }; } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/data-type/0000775000175000017500000000000015203070342024331 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h0000664000175000017500000001045415203070342027106 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 /// `dataType.h` / `dataTypeOp` 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.18.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeConceptDescriptor.h0000664000175000017500000001456315203070342031641 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 DataTypeConcept.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.18.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeMatcher.h0000664000175000017500000000072615203070342027566 0ustar alastairalastair#pragma once // System include #include // Utils #include "metkit/mars2grib/backend/concepts/data-type/dataTypeEnum.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::concepts_ { template std::size_t dataTypeMatcher(const MarsDict_t& mars, const OptDict_t& opt) { return static_cast(DataTypeType::Default); } } // namespace metkit::mars2grib::backend::concepts_ metkit-1.18.2/src/metkit/mars2grib/backend/concepts/data-type/dataTypeEncoding.h0000664000175000017500000001603315203070342027727 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 dataTypeOp.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.18.2/src/metkit/mars2grib/backend/concepts/MatchingCallbacksRegistry.h0000664000175000017500000001724315203070342027704 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. /// #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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/0000775000175000017500000000000015203070342026320 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeEntryCallbacksRegistry.h0000664000175000017500000001716115203070342033767 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/common.h0000664000175000017500000001260415203070342027764 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/EntryVariantRegistry.h0000664000175000017500000005626715203070342032670 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeVariantCallbacksRegistry.hmetkit-1.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makeVariantCallbacksRegistry0000664000175000017500000002672415203070342034051 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/compileTimeRegistryEngine.md0000664000175000017500000001653515203070342034002 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/RegisterEntryDescriptor.h0000664000175000017500000002240315203070342033337 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/utils.h0000664000175000017500000001266115203070342027637 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.18.2/src/metkit/mars2grib/backend/compile-time-registry-engine/makePhaseCallbacksRegistry.h0000664000175000017500000003043015203070342033720 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.18.2/src/metkit/mars2grib/backend/sections/0000775000175000017500000000000015203070342022452 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/sections/initializers/0000775000175000017500000000000015203070342025160 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer2.h0000664000175000017500000001200215203070342031256 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer3.h0000664000175000017500000001252515203070342031271 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); } else { // 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializerCore.h0000664000175000017500000000700115203070342032010 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer1.h0000664000175000017500000000600115203070342031257 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer0.h0000664000175000017500000000572215203070342031267 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer4.h0000664000175000017500000000646615203070342031301 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionRegistry.h0000664000175000017500000002533615203070342030537 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>}, {24, &allocateTemplateNumber2<2, 24, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {36, &allocateTemplateNumber2<2, 36, 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>}}; /// /// @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>}, {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>}, {142, &allocateTemplateNumber4<4, 142, MarsDict_t, ParDict_t, OptDict_t, OutDict_t>}, {143, &allocateTemplateNumber4<4, 143, 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.18.2/src/metkit/mars2grib/backend/sections/initializers/sectionInitializer5.h0000664000175000017500000000656215203070342031277 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.18.2/src/metkit/mars2grib/backend/sections/resolver/0000775000175000017500000000000015203070342024313 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/sections/resolver/Recipe.h0000664000175000017500000002441515203070342025701 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.18.2/src/metkit/mars2grib/backend/sections/resolver/Select.h0000664000175000017500000002007315203070342025705 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.18.2/src/metkit/mars2grib/backend/sections/resolver/resolver.md0000664000175000017500000001755315203070342026511 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.18.2/src/metkit/mars2grib/backend/sections/resolver/TemplateSignatureKey.h0000664000175000017500000001621215203070342030574 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.18.2/src/metkit/mars2grib/backend/sections/resolver/dsl.h0000664000175000017500000000037415203070342025252 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.18.2/src/metkit/mars2grib/backend/sections/resolver/ActiveConceptsData.h0000664000175000017500000001770715203070342030204 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[conceptId] << "\""; 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.18.2/src/metkit/mars2grib/backend/sections/resolver/SectionTemplateSelector.h0000664000175000017500000004377315203070342031303 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.18.2/src/metkit/mars2grib/backend/sections/resolver/Recipes.h0000664000175000017500000001344015203070342026060 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.18.2/src/metkit/mars2grib/backend/sections/resolver/ResolvedTemplateData.h0000664000175000017500000001421515203070342030540 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.18.2/src/metkit/mars2grib/backend/sections/resolver/SectionLayoutData.h0000664000175000017500000001746315203070342030073 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.18.2/src/metkit/mars2grib/backend/sections/resolver/CompressionMask.h0000664000175000017500000003240515203070342027605 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.18.2/src/metkit/mars2grib/backend/tables/0000775000175000017500000000000015203070342022075 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/tables/timeUnits.h0000664000175000017500000001216115203070342024230 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.18.2/src/metkit/mars2grib/backend/tables/backgroundProcess.h0000664000175000017500000002573315203070342025736 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.18.2/src/metkit/mars2grib/backend/tables/typeOfGeneratingProcess.h0000664000175000017500000002326715203070342027071 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.18.2/src/metkit/mars2grib/backend/tables/typeOfInterval.h0000664000175000017500000001663615203070342025235 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.18.2/src/metkit/mars2grib/backend/tables/typeOfEnsembleForecast.h0000664000175000017500000002111515203070342026656 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.18.2/src/metkit/mars2grib/backend/tables/shapeOfTheReferenceSystem.h0000664000175000017500000001774015203070342027331 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.18.2/src/metkit/mars2grib/backend/tables/significanceOfReferenceTime.h0000664000175000017500000001457715203070342027631 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.18.2/src/metkit/mars2grib/backend/tables/typeOfStatisticalProcessing.h0000664000175000017500000001630715203070342027765 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.18.2/src/metkit/mars2grib/backend/tables/typeOfProcessedData.h0000664000175000017500000002270115203070342026160 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.18.2/src/metkit/mars2grib/backend/tables/derivedForecast.h0000664000175000017500000001543715203070342025371 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.18.2/src/metkit/mars2grib/backend/tables/productionStatusOfProcessedData.h0000664000175000017500000002134115203070342030570 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.18.2/src/metkit/mars2grib/backend/tables/typeOfTimeIntervals.h0000664000175000017500000001352615203070342026232 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.18.2/src/metkit/mars2grib/backend/deductions/0000775000175000017500000000000015203070342022764 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/deductions/offsetToEndOf4DvarWindow.h0000664000175000017500000001037515203070342027741 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.18.2/src/metkit/mars2grib/backend/deductions/backgroundProcess.h0000664000175000017500000001510715203070342026617 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.18.2/src/metkit/mars2grib/backend/deductions/timeSpanInSeconds.h0000664000175000017500000000713515203070342026531 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" // Exceptions #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 time span from the MARS dictionary and convert it to seconds. /// /// This deduction retrieves the value associated with the key `timespan` /// from the MARS dictionary (`mars`). The value is expected to be /// convertible to a `long` and is treated as mandatory. /// /// The retrieved value is interpreted according to standard MARS /// conventions as a time span expressed in **hours**. It is converted /// to seconds by applying a fixed scaling factor: /// /// \f[ /// \text{timeSpanInSeconds} = \text{timespan} \times 3600 /// \f] /// /// The resolved time span (in seconds) is logged for diagnostic and /// traceability purposes. /// /// @tparam MarsDict_t /// Type of the MARS dictionary, expected to contain the key `timespan`. /// /// @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 time span is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The time span expressed in seconds, returned as a `long`. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `timespan` is not present in the MARS dictionary, /// - the associated value cannot be converted to `long`, /// - any unexpected error occurs during dictionary access or conversion. /// /// @note /// This deduction assumes that the MARS `timespan` value is expressed /// in hours. No alternative units (e.g. minutes or seconds) are /// currently supported. /// /// @note /// The function follows a fail-fast strategy and uses nested exception /// propagation to preserve full error provenance across API boundaries. /// template long resolve_TimeSpanInSeconds_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 { // Get the mars.timespan long marsTimespanVal = get_or_throw(mars, "timespan"); long timeSpanInSeconds = marsTimespanVal * 3600; // Logging of the timeSpan MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "timeSpan: deduced from mars dictionary with value: "; logMsg += std::to_string(timeSpanInSeconds) + " [seconds]"; return logMsg; }()); return timeSpanInSeconds; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException("Unable to get `timespan` from Mars dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/waveFrequencyNumber.h0000664000175000017500000000767215203070342027146 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.18.2/src/metkit/mars2grib/backend/deductions/model.h0000664000175000017500000001057015203070342024240 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.18.2/src/metkit/mars2grib/backend/deductions/expver.h0000664000175000017500000001131415203070342024446 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.18.2/src/metkit/mars2grib/backend/deductions/numberOfForecastsInEnsemble.h0000664000175000017500000001404415203070342030531 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.18.2/src/metkit/mars2grib/backend/deductions/typeOfGeneratingProcess.h0000664000175000017500000001475015203070342027755 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, [[maybe_unused]] 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::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") { tables::TypeOfGeneratingProcess result = 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 += "'"; 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.18.2/src/metkit/mars2grib/backend/deductions/satelliteNumber.h0000664000175000017500000001027315203070342026277 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.18.2/src/metkit/mars2grib/backend/deductions/activity.h0000664000175000017500000001121415203070342024770 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.18.2/src/metkit/mars2grib/backend/deductions/typeOfEnsembleForecast.h0000664000175000017500000001371415203070342027553 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.18.2/src/metkit/mars2grib/backend/deductions/lengthOfTimeWindow.h0000664000175000017500000001422715203070342026720 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.18.2/src/metkit/mars2grib/backend/deductions/forecastTimeInSeconds.h0000664000175000017500000000776515203070342027407 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::deductions { /// /// @brief Resolve the forecast valid time from the MARS dictionary using a step expressed in seconds. /// /// This deduction computes the forecast valid time by retrieving the MARS key /// `step` and interpreting it as a forecast lead time expressed in hours. /// The lead time is converted to seconds and added to the reference time /// to obtain the forecastTime in seconds as a `long`. /// /// The conversion follows the conventional MARS interpretation: /// - `step` is assumed to be expressed in hours, /// - the corresponding number of seconds is obtained as /// \f$ \text{step} \times 3600 \f$. /// /// The resolved forecast lead time (in seconds) is logged for diagnostic /// and traceability purposes. /// /// @tparam MarsDict_t /// Type of the MARS dictionary, expected to contain the key `step`. /// /// @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 forecast step is retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The forecast valid time as a `long`, representing the number of seconds, /// obtained by converting the step to seconds. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - the key `step` is not present in the MARS dictionary, /// - the associated value cannot be converted to `long`, /// - any unexpected error occurs during conversion or time computation. /// /// @note /// This deduction assumes that the MARS `step` value is expressed in /// hours. Alternative units (e.g. minutes or seconds) are not supported. /// /// @note /// The reference time to which the forecast step is applied is assumed /// to be available in the surrounding context. This function does not /// resolve the reference time itself. /// /// @note /// The function follows a fail-fast strategy and uses nested exception /// propagation to preserve full error provenance across API boundaries. /// template long resolve_ForecastTimeInSeconds_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 { // Get the mars.step long marsStep = get_or_throw(mars, "step"); // Convert step in seconds (Assumed to be in hours) /// @todo add constant to avoid hardcoding 3600 long marsStepInSecondsVal = marsStep * 3600; // Logging of the forecastTime MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "forecastTime: deduced from mars dictionary with value: "; logMsg += std::to_string(marsStepInSecondsVal) + " [seconds]"; return logMsg; }()); // Success exit point return marsStepInSecondsVal; } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException("Unable to compute forecast time", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/periodItMin.h0000664000175000017500000001125015203070342025357 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.18.2/src/metkit/mars2grib/backend/deductions/significanceOfReferenceTime.h0000664000175000017500000001532115203070342030504 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 standardReferenceDateTime.h /// - @ref hindcastReferenceDateTime.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"}}; 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.18.2/src/metkit/mars2grib/backend/deductions/statisticsDescriptor.h0000664000175000017500000001631415203070342027373 0ustar alastairalastair#pragma once #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" // Deductions #include "metkit/mars2grib/backend/deductions/forecastTimeInSeconds.h" #include "metkit/mars2grib/backend/deductions/numberOfTimeRanges.h" #include "metkit/mars2grib/backend/deductions/timeSpanInSeconds.h" // Utils #include "metkit/mars2grib/backend/deductions/detail/timeUtils.h" #include "metkit/mars2grib/backend/deductions/timeIncrementInSeconds.h" // Exceptions #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { struct StatisticalProcessing { long numberOfTimeRanges = 0; std::vector typeOfStatisticalProcessing; std::vector typeOfTimeIncrement; std::vector indicatorOfUnitForTimeRange; std::vector lengthOfTimeRange; std::vector indicatorOfUnitForTimeIncrement; std::vector lengthOfTimeIncrement; }; template inline StatisticalProcessing getTimeDescriptorFromMars_orThrow( const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt, long outerStatOp // typeOfStatisticalProcessing for inner loop ) { using metkit::mars2grib::backend::deductions::detail::parseStatType_or_throw; using metkit::mars2grib::backend::deductions::detail::Period; using metkit::mars2grib::backend::deductions::detail::previousMonthLengthHours; using metkit::mars2grib::backend::deductions::detail::StatOp; using metkit::mars2grib::backend::deductions::detail::StatTypeBlock; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { StatisticalProcessing out{}; // --------------------------------------------------------------------- // Number of loops // --------------------------------------------------------------------- long numberOfTimeRangesVal = numberOfTimeRanges(mars, par); // Validate number of time ranges if (numberOfTimeRangesVal < 1 || numberOfTimeRangesVal > 3) { throw Mars2GribDeductionException("Unexpected number of time loops", Here()); } // Initialize output structure out.numberOfTimeRanges = numberOfTimeRangesVal; out.typeOfStatisticalProcessing.reserve(numberOfTimeRangesVal); out.typeOfTimeIncrement.reserve(numberOfTimeRangesVal); out.indicatorOfUnitForTimeRange.reserve(numberOfTimeRangesVal); out.lengthOfTimeRange.reserve(numberOfTimeRangesVal); out.indicatorOfUnitForTimeIncrement.reserve(numberOfTimeRangesVal); out.lengthOfTimeIncrement.reserve(numberOfTimeRangesVal); // --------------------------------------------------------------------- // Parse stattype blocks (outer loops) // --------------------------------------------------------------------- std::vector blocks; if (numberOfTimeRangesVal > 1) { // Get the stattype from mars std::string statTypeVal = get_or_throw(mars, "stattype"); // Parse stattype blocks = parseStatType_or_throw(statTypeVal); } // --------------------------------------------------------------------- // End date (needed for monthly length) // --------------------------------------------------------------------- eckit::DateTime forecastTime = resolve_ForecastTimeInSeconds_or_throw(mars, par, opt); long endYear = forecastTime.date().year(); long endMonth = forecastTime.date().month(); const long timeStepSeconds = timeIncrementInSeconds_or_throw(mars, par); const long timeSpanInSeconds = resolve_TimeSpanInSeconds_or_throw(mars, par, opt); if (timeSpanInSeconds % 3600 != 0) { throw Mars2GribDeductionException("`timespan` must be multiple of 3600 seconds", Here()); } const long timeSpanHours = timeSpanInSeconds / 3600; // --------------------------------------------------------------------- // Fill SoA (exactly like Fortran) // --------------------------------------------------------------------- for (long i = 0; i < numberOfTimeRangesVal; ++i) { const bool isInnerLoop = (i == numberOfTimeRangesVal - 1); // Common fields (hard-coded in Fortran) out.typeOfTimeIncrement[i] = 2; // multIO fixed value out.indicatorOfUnitForTimeRange[i] = 1; // hours out.indicatorOfUnitForTimeIncrement[i] = 13; // seconds if (isInnerLoop) { // ------------------------------------------------------------- // Inner loop (timespan) // ------------------------------------------------------------- out.typeOfStatisticalProcessing[i] = outerStatOp; out.lengthOfTimeRange[i] = timeSpanHours; out.lengthOfTimeIncrement[i] = timeStepSeconds; } else { // ------------------------------------------------------------- // Outer loop(s) from stattype // ------------------------------------------------------------- const auto& blk = blocks[i]; out.typeOfStatisticalProcessing[i] = static_cast(blk.op); // lengthOfTimeRange if (blk.period == Period::Daily) { out.lengthOfTimeRange[i] = 24; } else if (blk.period == Period::Monthly) { out.lengthOfTimeRange[i] = previousMonthLengthHours(endYear, endMonth); } else { throw std::runtime_error("Unsupported period"); } // lengthOfTimeIncrement if (i == numberOfTimeRangesVal - 2) { // Next loop is inner → use timespan out.lengthOfTimeIncrement[i] = timeSpanHours * 3600; } else { // Next loop is another stattype block const auto& nextBlk = blocks[i + 1]; if (nextBlk.period == Period::Daily) { out.lengthOfTimeIncrement[i] = 24 * 3600; } else if (nextBlk.period == Period::Monthly) { out.lengthOfTimeIncrement[i] = previousMonthLengthHours(endYear, endMonth) * 3600; } else { throw Mars2GribDeductionException("Unsupported next period", Here()); } } } return out; } } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Unable to compute statistics descriptor from Mars dictionary", Here())); }; // Remove compiler warning mars2gribUnreachable(); } } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/subCentre.h0000664000175000017500000000771415203070342025100 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.18.2/src/metkit/mars2grib/backend/deductions/channel.h0000664000175000017500000001131615203070342024547 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.18.2/src/metkit/mars2grib/backend/deductions/referenceDateTime.h0000664000175000017500000001052415203070342026512 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 "eckit/types/Date.h" #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/backend/deductions/detail/timeUtils.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 reference date and time from the MARS dictionary. /// /// This deduction constructs an `eckit::DateTime` object from the MARS /// dictionary entries `date` and `time`. Both values are treated as /// mandatory and are expected to be provided in the conventional MARS /// integer formats: /// /// - `date`: calendar date encoded as `YYYYMMDD` /// - `time`: clock time encoded as `HHMMSS` /// /// The raw integer values are first converted into canonical `eckit::Date` /// and `eckit::Time` objects using dedicated conversion utilities, and are /// then combined into a single `eckit::DateTime` instance. /// /// The resolved date and time are logged for diagnostic and traceability /// purposes. /// /// @tparam MarsDict_t /// Type of the MARS dictionary, expected to contain the keys `date` and `time`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary from which the reference date and time are retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @return /// The reference date and time resolved from the MARS dictionary, returned /// as an `eckit::DateTime` object. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - either `date` or `time` is missing from the MARS dictionary, /// - the associated values cannot be converted to `long`, /// - the integer values do not represent a valid calendar date or time, /// - any unexpected error occurs during conversion or dictionary access. /// /// @note /// The conversion assumes standard MARS integer encodings for date /// (`YYYYMMDD`) and time (`HHMMSS`). Validation and normalization are /// delegated to the underlying conversion utilities. /// /// @note /// A future enhancement may retrieve date and time as strings and rely /// on higher-level Metkit parsing utilities for normalization and /// validation. /// /// @note /// This deduction follows a fail-fast strategy and uses nested exception /// propagation to preserve full error provenance across API boundaries. /// template eckit::DateTime resolve_ReferenceDateTime_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 MIVAL: get as string and parse/normalize with metkit utilities // Get the mars.date and mars.time long marsDate = get_or_throw(mars, "date"); long marsTime = get_or_throw(mars, "time"); // Convert to canonical format eckit::Date date = detail::convert_YYYYMMDD2Date_or_throw(marsDate); eckit::Time time = detail::convert_hhmmss2Time_or_throw(marsTime); // Logging of the resolution MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "date,time: deduced from mars dictionary with value: "; logMsg += std::to_string(marsDate) + "," + std::to_string(marsTime); return logMsg; }()); return eckit::DateTime(date, time); } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Unable to get `date` and `time` from Mars dictionary to deduce `dateTime`", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/paramId.h0000664000175000017500000001006615203070342024515 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.18.2/src/metkit/mars2grib/backend/deductions/waveFrequencyGrid.h0000664000175000017500000003617515203070342026603 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.18.2/src/metkit/mars2grib/backend/deductions/centre.h0000664000175000017500000001105015203070342024412 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 /// mandatory 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 the key `origin` is missing, cannot be retrieved as a string, /// or 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_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory MARS origin std::string origin = get_or_throw(mars, "origin"); // 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.18.2/src/metkit/mars2grib/backend/deductions/shapeOfTheEarth.h0000664000175000017500000000750515203070342026156 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. /// /// 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) { // Default value tables::ShapeOfTheReferenceSystem shapeOfTheEarth = tables::ShapeOfTheReferenceSystem::EarthSphericalRadius6371229; // Logging of the channel MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`shapeOfTheEarth` defaulted from input dictionaries: value='"; logMsg += tables::enum2name_ShapeOfTheReferenceSystem_or_throw(shapeOfTheEarth); logMsg += "'"; return logMsg; }()); // Success exit point return shapeOfTheEarth; }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/allowedReferenceValue.h0000664000175000017500000001737215203070342027412 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: `mars["param"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the GRIB `allowedReferenceValue` by retrieving /// the mandatory MARS parameter identifier (`param`) and consulting 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. /// /// 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 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 the key `param` is missing, cannot be retrieved as an integral value, /// 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::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}}, }; // Default reference value double ret = 0.0; // Retrieve mandatory MARS allowedReferenceValue 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.18.2/src/metkit/mars2grib/backend/deductions/numberOfTimeRanges.h0000664000175000017500000001301715203070342026673 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 numberOfTimeRanges.h /// @brief Deduction of the GRIB `numberOfTimeRanges` key. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **number of time ranges** associated with statistical /// processing. /// /// The deduction determines the number of time ranges based on the /// presence and structure of MARS statistical metadata. /// /// In particular: /// - the MARS key `timespan` is mandatory /// - the MARS key `stattype` is used to determine the number of /// statistical blocks when present /// /// 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 statisticsEncoding.h /// /// Related deductions: /// - @ref timeSpanInSeconds.h /// - @ref timeIncrementInSeconds.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Details #include "metkit/mars2grib/backend/deductions/detail/timeUtils.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 number of time ranges for statistical processing. /// /// @section Deduction contract /// - Reads: `mars["timespan"]`, optionally `mars["stattype"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction computes the number of time ranges required by GRIB /// statistical processing templates. /// /// Resolution rules: /// - if `timespan` is missing → failure /// - if `stattype` is missing → returns `1` /// - otherwise: /// - the number of time ranges is computed as /// `countBlocks(stattype) + 1` /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support access to `timespan` /// and optionally `stattype`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary providing statistical metadata. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @return /// The number of time ranges. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If `timespan` is missing or if any unexpected error occurs during /// deduction. /// /// @note /// This deduction is deterministic and does not rely on pre-existing /// GRIB header state. /// template long numberOfTimeRanges(const MarsDict_t& mars, const ParDict_t& par) { using metkit::mars2grib::backend::deductions::detail::countBlocks; using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::dict_traits::has; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Get the mars.levelist bool hasTimespan = has(mars, "timespan"); bool hasStatType = has(mars, "stattype"); // Error if timespan is missing if (!hasTimespan) { throw Mars2GribDeductionException("`timespan` is required to compute number of time ranges", Here()); } // Handle trivial case if (!hasStatType) { // Retrieve number Of Timeranges long numberOfTimeRanges = 1; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`numberOfTimeRanges` resolved from input dictionaries: value='"; logMsg += std::to_string(numberOfTimeRanges) + "'"; return logMsg; }()); return numberOfTimeRanges; } if (hasStatType) { // Retrieve MARS stattype std::string statTypeVal = get_or_throw(mars, "stattype"); // Count number of blocks in stattype long numberOfBlocks = static_cast(countBlocks(statTypeVal)); // Compute number of time ranges long numberOfTimeRanges = numberOfBlocks + 1; // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`numberOfTimeRanges` resolved from input dictionaries: value='"; logMsg += std::to_string(numberOfTimeRanges) + "'"; return logMsg; }()); // Number of time ranges = number of blocks + 1 return numberOfTimeRanges; } // Remove compiler warning mars2gribUnreachable(); } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `numberOfTimeRanges` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/typeOfProcessedData.h0000664000175000017500000001650615203070342027055 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") { result = tables::TypeOfProcessedData::ForecastProducts; } else if (marsType == "pf") { result = tables::TypeOfProcessedData::PerturbedForecastProducts; } else if (marsType == "cf") { result = tables::TypeOfProcessedData::ControlForecastProducts; } else if (marsType == "ssd" || 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.18.2/src/metkit/mars2grib/backend/deductions/derivedForecast.h0000664000175000017500000001450315203070342026251 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 == "taes") { derivedForecast = tables::DerivedForecast::SpreadAllMembers; } 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.18.2/src/metkit/mars2grib/backend/deductions/level.h0000664000175000017500000001155215203070342024250 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.18.2/src/metkit/mars2grib/backend/deductions/scaledValueOfCentralWaveNumber.h0000664000175000017500000001062715203070342031165 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.18.2/src/metkit/mars2grib/backend/deductions/generation.h0000664000175000017500000001136715203070342025300 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.18.2/src/metkit/mars2grib/backend/deductions/timeIncrementInSeconds.h0000664000175000017500000000466415203070342027560 0ustar alastairalastair#pragma once #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { 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 { // Get the mars.expver auto lengthOfTimeStepInSeconds_opt = get_opt(par, "timeIncrementInSeconds"); // TODO MIVAL: Validate (if present needs to be > 0) 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(); }; 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.18.2/src/metkit/mars2grib/backend/deductions/hindcastDateTime.h0000664000175000017500000001147415203070342026356 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 "eckit/types/Date.h" #include "eckit/types/DateTime.h" #include "eckit/types/Time.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/backend/deductions/detail/timeUtils.h" #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::deductions { /// /// @brief Resolve the hindcast reference date and time from the MARS dictionary. /// /// This deduction retrieves the hindcast reference date and time from the /// MARS dictionary entries `hdate` (mandatory) and `htime` (optional) and /// combines them into an `eckit::DateTime` object. /// /// The values are expected to follow the standard MARS integer encodings: /// - `hdate`: calendar date encoded as `YYYYMMDD` /// - `htime`: clock time encoded as `HHMMSS` (defaulting to `000000` if missing) /// /// These fields are typically used for hindcast or reforecast products, /// where the reference time of the forecast differs from the nominal /// analysis or forecast reference time. /// /// The resolved hindcast date and time (if present) are logged for diagnostic and /// traceability purposes. /// /// @tparam MarsDict_t /// Type of the MARS dictionary, expected to contain the key `hdate`, /// `htime` instead is optional and defaulted to 0 when missing. /// /// @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 hindcast date and time are retrieved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The hindcast reference date and time resolved from the MARS dictionary, /// returned as an `eckit::DateTime` object. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If: /// - either `hdate` is missing from the MARS dictionary, /// - the associated values cannot be converted to `long`, /// - the integer values do not represent a valid calendar date or time, /// - any unexpected error occurs during dictionary access or conversion. /// /// @note /// This deduction assumes standard MARS integer encodings for hindcast /// date (`YYYYMMDD`) and time (`HHMMSS`) if present. Validation and normalization /// are expected to be handled by the underlying conversion utilities. /// /// @note /// A future enhancement may retrieve hindcast date and time as strings /// and rely on higher-level Metkit parsing utilities for improved /// normalization and validation. /// /// @note /// This function follows a fail-fast strategy and uses nested exception /// propagation to preserve full error provenance across API boundaries. /// template eckit::DateTime resolve_HindcastDateTime_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::exceptions::Mars2GribDeductionException; try { // TODO MIVAL: get as string and parse/normalize with metkit utilities // Get the mars.date and mars.time long marsDate = get_or_throw(mars, "hdate"); long marsTime = get_opt(mars, "htime").value_or(0); // Convert to canonical format eckit::Date date = detail::convert_YYYYMMDD2Date_or_throw(marsDate); eckit::Time time = detail::convert_hhmmss2Time_or_throw(marsTime); // Logging of the resolution MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "hindcast[date,time]: deduced from mars dictionary with value: "; logMsg += std::to_string(marsDate) + "," + std::to_string(marsTime); return logMsg; }()); return eckit::DateTime(date, time); } catch (...) { // Rethrow nested exceptions std::throw_with_nested(Mars2GribDeductionException( "Unable to get `date` and `time` from Mars dictionary to deduce `dateTime`", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/realization.h0000664000175000017500000001162415203070342025462 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.18.2/src/metkit/mars2grib/backend/deductions/experiment.h0000664000175000017500000001137615203070342025325 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.18.2/src/metkit/mars2grib/backend/deductions/productionStatusOfProcessedData.h0000664000175000017500000001541515203070342031464 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.18.2/src/metkit/mars2grib/backend/deductions/perturbationNumber.h0000664000175000017500000001141015203070342027021 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. */ /// /// @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 /// #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 perturbation number (`number`) from the MARS dictionary. /// /// @section Deduction contract /// - Reads: `mars["number"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws on error /// /// This deduction retrieves the perturbation number associated with the /// current field from the MARS dictionary. /// /// The value uniquely identifies the ensemble member within an ensemble /// forecast. Its interpretation (e.g. control vs perturbed members) is /// handled elsewhere and is not enforced here. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must contain `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 the MARS dictionary. /// /// @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 range checks or consistency validation. /// 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.18.2/src/metkit/mars2grib/backend/deductions/deductions.md0000664000175000017500000001002715203070342025447 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.18.2/src/metkit/mars2grib/backend/deductions/methodNumber.h0000664000175000017500000001076715203070342025601 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.18.2/src/metkit/mars2grib/backend/deductions/detail/0000775000175000017500000000000015203070342024226 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/backend/deductions/detail/pv_137_be.h0000664000175000017500000015334015203070342026072 0ustar alastairalastair#pragma once #include "metkit/mars2grib/utils/generalUtils.h" // namespace metkit::mars2grib::backend::deductions::pv_detail::data { /// /// @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. /// 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::datametkit-1.18.2/src/metkit/mars2grib/backend/deductions/detail/timeUtils.h0000664000175000017500000002120115203070342026352 0ustar alastairalastair#pragma once #include #include #include #include #include #include #include "eckit/exception/Exceptions.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::backend::deductions::detail { enum class Period { Daily, Monthly }; enum class StatOp { Average, Minimum, Maximum, StdDev }; // ============================================================= // Decoded block // ============================================================= struct StatTypeBlock { Period period; StatOp op; }; // ============================================================= // Utilities // ============================================================= // Count number of blocks in stattype (Fortran-equivalent logic) inline std::size_t countBlocks(std::string_view stattype) { if (stattype.empty()) return 0; std::size_t blocks = 1; for (char c : stattype) { if (c == '_') ++blocks; } return blocks; } // Compute month length in hours (Julian / truncated-Gregorian rule) inline long previousMonthLengthHours(int year, int month) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (month < 1 || month > 12) { throw Mars2GribGenericException("Invalid month (must be 1..12)", Here()); } switch (month) { case 1: case 2: case 4: case 6: case 8: case 9: case 11: return 31 * 24; case 5: case 7: case 10: case 12: return 30 * 24; case 3: return ((year % 4) == 0 ? 29 : 28) * 24; } throw Mars2GribGenericException("Unreachable", Here()); } // Compute month length in hours (Julian / truncated-Gregorian rule) inline long monthLengthHours(int year, int month) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (month < 1 || month > 12) { throw Mars2GribGenericException("Invalid month (must be 1..12)", Here()); } switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31 * 24; case 4: case 6: case 9: case 11: return 30 * 24; case 2: return ((year % 4) == 0 ? 29 : 28) * 24; } throw Mars2GribGenericException("Unreachable", Here()); } // ============================================================= // Decoding helpers // ============================================================= inline Period decodePeriod_orThrow(std::string_view s) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (s == "da") return Period::Daily; if (s == "mo") return Period::Monthly; throw Mars2GribGenericException("Invalid period token: " + std::string(s), Here()); } inline StatOp decodeOp_orThrow(std::string_view s) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (s == "av") return StatOp::Average; if (s == "mn") return StatOp::Minimum; if (s == "mx") return StatOp::Maximum; if (s == "sd") return StatOp::StdDev; throw Mars2GribGenericException("Invalid operation token: " + std::string(s), Here()); } // ============================================================= // Parser + semantic validation // ============================================================= inline std::vector parseStatType_or_throw(const std::string& stattype) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; std::vector blocks; std::size_t pos = 0; while (pos < stattype.size()) { if (pos + 4 > stattype.size()) { throw Mars2GribGenericException("Invalid stattype format", Here()); } auto period = decodePeriod_orThrow(stattype.substr(pos, 2)); auto op = decodeOp_orThrow(stattype.substr(pos + 2, 2)); blocks.push_back({period, op}); pos += 4; if (pos < stattype.size()) { if (stattype[pos] != '_') { throw Mars2GribGenericException("Invalid stattype separator (expected '_')", Here()); } ++pos; } } // Semantic validation: only one mo, one da, correct order int moIndex = -1; int daIndex = -1; for (std::size_t i = 0; i < blocks.size(); ++i) { if (blocks[i].period == Period::Monthly) { if (moIndex != -1) throw Mars2GribGenericException("Invalid stattype: more than one 'mo'", Here()); moIndex = static_cast(i); } if (blocks[i].period == Period::Daily) { if (daIndex != -1) throw Mars2GribGenericException("Invalid stattype: more than one 'da'", Here()); daIndex = static_cast(i); } } if (moIndex != -1 && daIndex != -1 && moIndex > daIndex) { throw Mars2GribGenericException("Invalid stattype order: 'mo' must precede 'da'", Here()); } return blocks; } // ============================================================= // Pretty printing (test/debug) // ============================================================= inline const char* toString(Period p) { switch (p) { case Period::Daily: return "Daily"; case Period::Monthly: return "Monthly"; } return "Unknown"; } inline const char* toString(StatOp op) { switch (op) { case StatOp::Average: return "Average"; case StatOp::Minimum: return "Minimum"; case StatOp::Maximum: return "Maximum"; case StatOp::StdDev: return "StandardDeviation"; } return "Unknown"; } inline void prettyPrint(const std::vector& blocks) { std::cout << "Decoded stattype (" << blocks.size() << " block(s)):\n"; for (std::size_t i = 0; i < blocks.size(); ++i) { std::cout << " [" << i << "] " << "Period = " << toString(blocks[i].period) << ", Operation = " << toString(blocks[i].op) << '\n'; } } // If unit is missing default is hours!!! inline long toSeconds_or_throw(std::string_view step) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; if (step.empty()) { throw Mars2GribGenericException("Empty step string", Here()); } // Split numeric part and optional unit std::size_t pos = 0; while (pos < step.size() && std::isdigit(static_cast(step[pos]))) { ++pos; } if (pos == 0) { throw Mars2GribGenericException("Invalid step format (no numeric part): " + std::string(step), Here()); } long value = 0; try { value = std::stol(std::string(step.substr(0, pos))); } catch (...) { throw Mars2GribGenericException("Invalid numeric value in step: " + std::string(step), Here()); } // Default unit: hours char unit = 'h'; if (pos < step.size()) { if (pos + 1 != step.size()) { throw Mars2GribGenericException("Invalid step format (trailing characters): " + std::string(step), Here()); } unit = step[pos]; } switch (unit) { case 'h': // hours return value * 3600L; case 'm': // minutes return value * 60L; case 's': // seconds return value; case 'd': // days return value * 86400L; default: throw Mars2GribGenericException(std::string("Unknown step unit: '") + unit + "'", Here()); } } inline eckit::Date convert_YYYYMMDD2Date_or_throw(long YYYYMMDD) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; long YYYY = YYYYMMDD / 10000; long MM = (YYYYMMDD / 100) % 100; long DD = YYYYMMDD % 100; // @todo Validate YYYY, MM, DD ranges? try { return eckit::Date(YYYY, MM, DD); } catch (const eckit::Exception& e) { throw Mars2GribGenericException("Invalid date value: " + std::string(e.what()), Here()); } } inline eckit::Time convert_hhmmss2Time_or_throw(long hhmmss) { using metkit::mars2grib::utils::exceptions::Mars2GribGenericException; long hh = hhmmss / 10000; long mm = (hhmmss / 100) % 100; long ss = hhmmss % 100; // @todo Validate hh, mm, ss ranges? try { return eckit::Time(hh, mm, ss); } catch (const eckit::Exception& e) { throw Mars2GribGenericException("Invalid time value: " + std::string(e.what()), Here()); } } } // namespace metkit::mars2grib::backend::deductions::detail metkit-1.18.2/src/metkit/mars2grib/backend/deductions/bitsPerValue.h0000664000175000017500000003346015203070342025550 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 laplacianOperator.h /// - @ref subSetTrunc.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/enableOptions.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 = utils::bitsPerValueCompressionEnabled(opt); // 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.18.2/src/metkit/mars2grib/backend/deductions/stream.h0000664000175000017500000000765615203070342024446 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.18.2/src/metkit/mars2grib/backend/deductions/periodItMax.h0000664000175000017500000001122415203070342025362 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.18.2/src/metkit/mars2grib/backend/deductions/satelliteSeries.h0000664000175000017500000001025415203070342026300 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.18.2/src/metkit/mars2grib/backend/deductions/localTablesVersion.h0000664000175000017500000000717615203070342026743 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.18.2/src/metkit/mars2grib/backend/deductions/dataset.h0000664000175000017500000001127715203070342024572 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.18.2/src/metkit/mars2grib/backend/deductions/laplacianOperator.h0000664000175000017500000001130615203070342026576 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 laplacianOperator.h /// @brief Deduction of the Laplacian operator coefficient. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **Laplacian operator coefficient** from the parameter /// dictionary. /// /// The deduction retrieves the coefficient explicitly from user-provided /// parameters 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 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 packingEncoding.h /// /// Related deductions: /// - @ref bitsPerValue.h /// - @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 { /// /// @brief Resolve the Laplacian operator coefficient from input dictionaries. /// /// @section Deduction contract /// - Reads: `par["laplacianOperator"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the Laplacian operator coefficient by retrieving /// the mandatory parameter dictionary key `laplacianOperator` and returning /// its value as a `double`. /// /// The value is taken verbatim from the parameter dictionary and overrides /// any implicit or default behavior. No validation beyond type conversion /// is performed. /// /// @tparam MarsDict_t /// Type of the MARS dictionary (unused by this deduction). /// /// @tparam ParDict_t /// Type of the parameter dictionary. Must support keyed access to /// `laplacianOperator` and conversion to `double`. /// /// @tparam OptDict_t /// Type of the options dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary (unused). /// /// @param[in] par /// Parameter dictionary from which the Laplacian operator coefficient /// is resolved. /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved Laplacian operator coefficient. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `laplacianOperator` is missing, cannot be converted to /// `double`, or if any unexpected error occurs during deduction. /// template double resolve_LaplacianOperator_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 Laplacian operator coefficient double laplacianOperator = get_or_throw(par, "laplacianOperator"); // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`laplacianOperator` resolved from input dictionaries: value='"; logMsg += std::to_string(laplacianOperator); logMsg += "'"; return logMsg; }()); // Success exit point return laplacianOperator; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `laplacianOperator` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/waveDirectionGrid.h0000664000175000017500000003452515203070342026557 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.18.2/src/metkit/mars2grib/backend/deductions/tablesVersion.h0000664000175000017500000001372715203070342025767 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.18.2/src/metkit/mars2grib/backend/deductions/resolution.h0000664000175000017500000001036615203070342025346 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.18.2/src/metkit/mars2grib/backend/deductions/constituentType.h0000664000175000017500000001232415203070342026360 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 constituentType.h /// @brief Deduction of the constituent (chemical species) type identifier. /// /// This header defines deduction utilities used by the mars2grib backend /// to resolve the **constituent / chemical species identifier** from /// MARS metadata. /// /// The deduction retrieves the identifier directly from the MARS /// dictionary and performs basic numeric validation before exposing /// the value to the encoding layer. /// /// Deductions are responsible for: /// - extracting values from MARS, parameter, and option dictionaries /// - applying explicit and minimal validation logic /// - returning strongly typed values to concept operations /// /// Deductions: /// - do NOT encode GRIB keys directly /// - do NOT apply semantic inference or defaulting /// - do NOT perform GRIB table validation /// /// Error handling follows a strict fail-fast strategy: /// - missing or invalid inputs cause immediate failure /// - errors are reported using domain-specific deduction exceptions /// - original errors are preserved via nested exception propagation /// /// Logging follows the mars2grib deduction policy: /// - RESOLVE: value derived via deduction logic from input dictionaries /// - OVERRIDE: value provided by parameter dictionary overriding deduction logic /// /// @section References /// Concept: /// - @ref compositionEncoding.h /// /// Related deductions: /// - @ref paramId.h /// /// @ingroup mars2grib_backend_deductions /// #pragma once // System includes #include // Core deduction includes #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/mars2grib/utils/logUtils.h" #include "metkit/mars2grib/utils/mars2gribExceptions.h" namespace metkit::mars2grib::backend::deductions { /// /// @brief Resolve the constituent (chemical species) type identifier from input dictionaries. /// /// @section Deduction contract /// - Reads: `mars["chem"]` /// - Writes: none /// - Side effects: logging (RESOLVE) /// - Failure mode: throws /// /// This deduction resolves the constituent (chemical species) type /// identifier by retrieving the mandatory MARS key `chem` and returning /// its value as a `long`. /// /// A basic numeric validity check is applied. Only values in the /// inclusive range `[0, 900]` are accepted. Values outside this range /// result in a deduction failure. /// /// No semantic interpretation, normalization, or defaulting is applied. /// The meaning of the identifier is defined by upstream MARS/GRIB /// conventions. /// /// @tparam MarsDict_t /// Type of the MARS dictionary. Must support keyed access to `chem` /// and conversion to `long`. /// /// @tparam ParDict_t /// Type of the parameter dictionary (unused by this deduction). /// /// @tparam OptDict_t /// Type of the options dictionary (unused by this deduction). /// /// @param[in] mars /// MARS dictionary from which the constituent type identifier is resolved. /// /// @param[in] par /// Parameter dictionary (unused). /// /// @param[in] opt /// Options dictionary (unused). /// /// @return /// The resolved constituent (chemical species) type identifier. /// /// @throws metkit::mars2grib::utils::exceptions::Mars2GribDeductionException /// If the key `chem` is missing, cannot be converted to `long`, if the /// value is outside the accepted range, or if any unexpected error /// occurs during deduction. /// /// @note /// This deduction enforces conservative numeric validation and does /// not consult chemical metadata tables or GRIB code tables. /// template long resolve_ConstituentType_or_throw(const MarsDict_t& mars, const ParDict_t& par, const OptDict_t& opt) { using metkit::mars2grib::utils::dict_traits::get_or_throw; using metkit::mars2grib::utils::exceptions::Mars2GribDeductionException; try { // Retrieve mandatory MARS constituent type long constituentType = get_or_throw(mars, "chem"); // Validate if (constituentType < 0 || constituentType > 900) { throw Mars2GribDeductionException( "Invalid `constituentType`: value='" + std::to_string(constituentType) + "'", Here()); } // Emit RESOLVE log entry MARS2GRIB_LOG_RESOLVE([&]() { std::string logMsg = "`constituentType` resolved from input dictionaries: value='"; logMsg += std::to_string(constituentType); logMsg += "'"; return logMsg; }()); // Success exit point return constituentType; } catch (...) { // Rethrow nested exceptions std::throw_with_nested( Mars2GribDeductionException("Failed to resolve `constituentType` from input dictionaries", Here())); }; // Remove compiler warning mars2gribUnreachable(); }; } // namespace metkit::mars2grib::backend::deductions metkit-1.18.2/src/metkit/mars2grib/backend/deductions/pvArray.h0000664000175000017500000005513015203070342024565 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.18.2/src/metkit/mars2grib/backend/deductions/generatingProcessIdentifier.h0000664000175000017500000001220415203070342030621 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 "eckit/log/Log.h" #include "metkit/mars2grib/utils/generalUtils.h" #include "metkit/config/LibMetkit.h" #include "metkit/mars2grib/utils/enableOptions.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.18.2/src/metkit/mars2grib/backend/deductions/type.h0000664000175000017500000000735715203070342024132 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.18.2/src/metkit/mars2grib/backend/deductions/scaleFactorOfCentralWaveNumber.h0000664000175000017500000001050615203070342031157 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.18.2/src/metkit/mars2grib/backend/deductions/instrumentType.h0000664000175000017500000001151215203070342026207 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.18.2/src/metkit/mars2grib/backend/deductions/class.h0000664000175000017500000001115015203070342024240 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/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.18.2/src/metkit/mars2grib/backend/deductions/systemNumber.h0000664000175000017500000000726015203070342025637 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.18.2/src/metkit/mars2grib/backend/deductions/waveDirectionNumber.h0000664000175000017500000000761415203070342027121 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.18.2/src/metkit/mars2grib/backend/deductions/subSetTrunc.h0000664000175000017500000001410515203070342025417 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 /// - @ref laplacianOperator.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 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; 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 auto marsTruncation = get_or_throw(mars, "truncation"); if (marsTruncation < 0) { std::string logMsg = "Invalid MARS truncation: value='" + std::to_string(marsTruncation) + "' is negative"; throw Mars2GribDeductionException(logMsg, Here()); } long defaultSubSetTrunc = std::min(20L, marsTruncation); if (has(par, "subSetTruncation")) { // Retrieve subSetTruncation from parameter dictionary long subSetTrunc = get_or_throw(par, "subSetTruncation"); // Validate that subSetTruncation does not exceed MARS truncation if (subSetTrunc < 0) { std::string logMsg = "Invalid `subSetTruncation`:"; logMsg += " value='" + std::to_string(subSetTrunc); logMsg += "' is negative"; throw Mars2GribDeductionException(logMsg, Here()); } if (subSetTrunc > marsTruncation) { std::string logMsg = "Invalid `subSetTruncation`:"; logMsg += " value='" + std::to_string(subSetTrunc) + "'"; logMsg += " exceeds MARS truncation='" + std::to_string(marsTruncation) + "'"; throw Mars2GribDeductionException(logMsg, Here()); } // Emit OVERRIDE log entry MARS2GRIB_LOG_OVERRIDE([&]() { std::string logMsg = "`subSetTruncation` overridden from parameter dictionary: value='"; logMsg += std::to_string(subSetTrunc); logMsg += "'"; return logMsg; }()); // Success exit point return subSetTrunc; } else { // Emit DEFAULT log entry for defaulting MARS2GRIB_LOG_DEFAULT([&]() { std::string logMsg = "`subSetTruncation` defaulted: value='"; logMsg += std::to_string(defaultSubSetTrunc); logMsg += "'"; return logMsg; }()); // Success exit point return defaultSubSetTrunc; } } 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.18.2/src/metkit/mars2grib/CoreOperations.h0000664000175000017500000006075415203070342022355 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 (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during metadata normalization", 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 (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during header encoding", 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 ParDict_t& misc, const OptDict_t& opt, std::unique_ptr handle) { try { metkit::mars2grib::backend::encodeValues(values, misc, opt, *handle); return handle; } catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during value encoding", 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) { using metkit::mars2grib::utils::exceptions::printExtendedStack; // 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, activeMisc, options, std::move(gribHeader)); } catch (const std::exception& e) { printExtendedStack(e); throw; } catch (...) { // Fallback for non-standard exceptions throw metkit::mars2grib::utils::exceptions::Mars2GribGenericException("Unknown error during encoding", 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 (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during cache preparation", 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, activeMisc, options, std::move(gribHeader)); } catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during encoding finalisation", 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 (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( mars2grib::utils::exceptions::Mars2GribGenericException("Error during header test dump", Here())); } } }; } // namespace metkit::mars2grib metkit-1.18.2/src/metkit/mars2grib/utils/0000775000175000017500000000000015203070342020374 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/utils/configConverter.h0000664000175000017500000002456615203070342023717 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.18.2/src/metkit/mars2grib/utils/mars2gribExceptions.h0000664000175000017500000003745315203070342024513 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 // 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(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 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(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 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(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(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'; // verifica eccezione annidata try { std::rethrow_if_nested(e); } 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; try { std::rethrow_if_nested(e); } 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.18.2/src/metkit/mars2grib/utils/paramMatcher.h0000664000175000017500000000172615203070342023157 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.18.2/src/metkit/mars2grib/utils/logUtils.h0000664000175000017500000000470315203070342022353 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.18.2/src/metkit/mars2grib/utils/generalUtils.h0000664000175000017500000000010415203070342023176 0ustar alastairalastair#pragma once #define mars2gribUnreachable() __builtin_unreachable()metkit-1.18.2/src/metkit/mars2grib/utils/enableOptions.h0000664000175000017500000000245615203070342023356 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 "metkit/mars2grib/api/Options.h" #include "metkit/mars2grib/utils/generalUtils.h" namespace metkit::mars2grib::utils { template inline bool checksEnabled(const Options& opt) { using metkit::mars2grib::utils::dict_traits::dict_supports_checks_v; if constexpr (!dict_supports_checks_v) { return false; } return opt.applyChecks; } inline bool overrideEnabled(const Options& opt) { return opt.enableOverride; } inline bool bitsPerValueCompressionEnabled(const Options& opt) { return opt.enableBitsPerValueCompression; } inline bool normalizeMarsEnabled(const Options& opt) { return opt.normalizeMars; } inline bool normalizeMiscEnabled(const Options& opt) { return opt.normalizeMisc; } inline bool fixMarsGridEnabled(const Options& opt) { return opt.fixMarsGrid; } } // namespace metkit::mars2grib::utils metkit-1.18.2/src/metkit/mars2grib/utils/type_traits_name.h0000664000175000017500000000752315203070342024123 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.18.2/src/metkit/mars2grib/utils/dictionary_traits/0000775000175000017500000000000015203070342024127 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_eckit_configuration.h0000664000175000017500000005074415203070342032525 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.18.2/src/metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h0000664000175000017500000001675715203070342031374 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.18.2/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_codes_handle.h0000664000175000017500000004674315203070342031113 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.18.2/src/metkit/mars2grib/docs/0000775000175000017500000000000015203070342020164 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/docs/diagrams/0000775000175000017500000000000015203070342021753 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/docs/diagrams/concept.svg0000664000175000017500000002702315203070342024133 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.18.2/src/metkit/mars2grib/docs/diagrams/architecture.svg0000664000175000017500000002772715203070342025175 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.18.2/src/metkit/mars2grib/docs/diagrams/concept.dot0000664000175000017500000000540215203070342024117 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.18.2/src/metkit/mars2grib/docs/diagrams/architecture.dot0000664000175000017500000000632715203070342025155 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.18.2/src/metkit/mars2grib/docs/doxygen/0000775000175000017500000000000015203070342021641 5ustar alastairalastairmetkit-1.18.2/src/metkit/mars2grib/docs/doxygen/mars2grib_architecture.md0000664000175000017500000001174115203070342026621 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.18.2/src/metkit/mars2grib/docs/doxygen/mars2grib_overview.md0000664000175000017500000000317315203070342026005 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.18.2/src/metkit/mars2grib/docs/doxygen/mars2grib.config.in0000664000175000017500000044427015203070342025340 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/forecastTimeInSeconds.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/productionStatusOfProcessedData.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/referenceDateTime.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/statisticsDescriptor.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/paramId.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/methodNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/numberOfTimeRanges.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/hindcastDateTime.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/timeUtils.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/detail/pv_137_be.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/timeSpanInSeconds.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/systemNumber.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/resolution.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/constituentType.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/experiment.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/class.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/subSetTrunc.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/timeIncrementInSeconds.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/laplacianOperator.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/channel.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/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/type.h \ @MARS2GRIB_SOURCE_DIRECTORY@/backend/deductions/satelliteNumber.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 # , /