diff --git a/Changelog.md b/Changelog.md index 905f8e3..9782a82 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,7 +1,7 @@ Release 3.x.x ------------- - Added `separate_point_sources` output config option to configure wheter point sources should be output separately for chimere grids -- Added `scenario` model config option that causes to first search for input files with the `_scenario` suffix +- Added `scenario` model config option that causes to first search for input files with the `_scenario` suffix, for point sources the scnario name is check as infix: emap_{scenario}_{pollutant}_{year}_*.csv - Support scaling of the emissions throug the scaling input file - Support automatic scaling of the point sources when they exceed the reported total emissions diff --git a/README.md b/README.md index b802971..904d538 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ This section configures the model run - `report_year` the report year of the emission data of the model run - `spatial_pattern_exceptions` path to an xlsx file in which exceptions for spatial patterns are configured. These exceptions overrule the standard rules for spatial patterns. - `included_pollutants` List of pollutants to include in the model run, this setting is optional, if it is not present all the configured pollutants will be included in the run. -- `scenario` First search for input files with the `_scenario` suffix before using the default input files, allows easy creation of scenarios with modified input files +- `scenario` First search for emission input files with the `_scenario` suffix before using the default emission input files, allows easy creation of scenarios with modified input files. For point sources `emap_{scenario}_{pollutant}_{year}_*.csv` is checked before `emap_{pollutant}_{year}_*.csv` - `point_source_rescale_threshold` The threshold for allowing automatic rescaling of point sources when they exceed the reported total emissions [0 - 100] ### Output section diff --git a/logic/emissioninventory.cpp b/logic/emissioninventory.cpp index e477a84..4d9fd9e 100644 --- a/logic/emissioninventory.cpp +++ b/logic/emissioninventory.cpp @@ -348,18 +348,41 @@ EmissionInventory create_emission_inventory(SingleEmissions totalEmissionsNfr, static SingleEmissions read_country_pollutant_point_sources(const fs::path& dir, const Pollutant& pol, const RunConfiguration& cfg, RunSummary& runSummary) { - auto match = fmt::format("emap_{}_{}_", pol.code(), static_cast(cfg.year())); + // first check if emap_{scenario}_{pol}_{year}_ exists + // otherwise use emap_{pol}_{year}_ - SingleEmissions result(cfg.year()); + auto scenarioMatch = std::string(); + auto match = fmt::format("emap_{}_{}_", pol.code(), static_cast(cfg.year())); + if (auto scenario = cfg.scenario(); !scenario.empty()) { + scenarioMatch = fmt::format("emap_{}_{}_{}_", scenario, pol.code(), static_cast(cfg.year())); + } + + std::unordered_set pathsToUse; - for (auto iter : fs::directory_iterator(dir)) { - if (iter.is_regular_file() && iter.path().extension() == ".csv") { - const auto& path = iter.path(); - if (str::starts_with(path.filename().u8string(), match)) { - merge_unique_emissions(result, parse_point_sources(path, cfg)); - runSummary.add_point_source(path); + auto insertMatchingFiles = [&pathsToUse](std::string_view match, const fs::path& dir) { + for (auto iter : fs::directory_iterator(dir)) { + if (iter.is_regular_file() && iter.path().extension() == ".csv") { + const auto& path = iter.path(); + if (str::starts_with(path.filename().u8string(), match)) { + pathsToUse.insert(iter.path()); + } } } + }; + + if (!scenarioMatch.empty()) { + // Find scenario specific matches + insertMatchingFiles(scenarioMatch, dir); + } + + if (pathsToUse.empty()) { + insertMatchingFiles(match, dir); + } + + SingleEmissions result(cfg.year()); + for (auto& path : pathsToUse) { + merge_unique_emissions(result, parse_point_sources(path, cfg)); + runSummary.add_point_source(path); } return result; diff --git a/logic/include/emap/modelpaths.h b/logic/include/emap/modelpaths.h index 84ed22f..4fb1af5 100644 --- a/logic/include/emap/modelpaths.h +++ b/logic/include/emap/modelpaths.h @@ -30,6 +30,7 @@ class ModelPaths fs::path scalings_path() const; const fs::path& data_root() const noexcept; + void set_data_root(const fs::path& root); const fs::path& output_path() const noexcept; fs::path boundaries_vector_path() const noexcept; fs::path eez_boundaries_vector_path() const noexcept; diff --git a/logic/include/emap/runconfiguration.h b/logic/include/emap/runconfiguration.h index a6aea1e..ea057a5 100644 --- a/logic/include/emap/runconfiguration.h +++ b/logic/include/emap/runconfiguration.h @@ -67,6 +67,8 @@ class RunConfiguration fs::path scalings_path() const; const fs::path& data_root() const noexcept; + void set_data_root(const fs::path& root); + const fs::path& output_path() const noexcept; const fs::path& spatial_pattern_exceptions() const noexcept; fs::path boundaries_vector_path() const noexcept; diff --git a/logic/modelpaths.cpp b/logic/modelpaths.cpp index 8216662..2b52f03 100644 --- a/logic/modelpaths.cpp +++ b/logic/modelpaths.cpp @@ -70,6 +70,11 @@ const fs::path& ModelPaths::data_root() const noexcept return _dataRoot; } +void ModelPaths::set_data_root(const fs::path& root) +{ + _dataRoot = root; +} + const fs::path& ModelPaths::output_path() const noexcept { return _outputRoot; diff --git a/logic/runconfiguration.cpp b/logic/runconfiguration.cpp index 39ec9d9..ae112ec 100644 --- a/logic/runconfiguration.cpp +++ b/logic/runconfiguration.cpp @@ -88,6 +88,11 @@ const fs::path& RunConfiguration::data_root() const noexcept return _paths.data_root(); } +void RunConfiguration::set_data_root(const fs::path& root) +{ + _paths.set_data_root(root); +} + const fs::path& RunConfiguration::output_path() const noexcept { return _paths.output_path(); diff --git a/logic/runsummary.cpp b/logic/runsummary.cpp index 239cdc7..2fec0d5 100644 --- a/logic/runsummary.cpp +++ b/logic/runsummary.cpp @@ -357,6 +357,11 @@ void RunSummary::write_summary(const fs::path& outputDir) const write_summary_spreadsheet(outputDir / "summary.xlsx"); } +const std::set& RunSummary::used_point_sources() const noexcept +{ + return _pointSources; +} + void RunSummary::write_summary_spreadsheet(const fs::path& path) const { std::error_code ec; diff --git a/logic/runsummary.h b/logic/runsummary.h index e0cc663..bd1ce6f 100644 --- a/logic/runsummary.h +++ b/logic/runsummary.h @@ -33,6 +33,8 @@ class RunSummary void write_summary(const fs::path& outputDir) const; + const std::set& used_point_sources() const noexcept; + private: struct GnfrCorrection { diff --git a/logic/test/emissioninventorytest.cpp b/logic/test/emissioninventorytest.cpp index 0914760..7f3fc6a 100644 --- a/logic/test/emissioninventorytest.cpp +++ b/logic/test/emissioninventorytest.cpp @@ -3,6 +3,7 @@ #include "emap/configurationparser.h" #include "emap/scalingfactors.h" +#include "infra/tempdir.h" #include "runsummary.h" #include "testconfig.h" #include "testconstants.h" @@ -21,7 +22,13 @@ static RunConfiguration create_config(const SectorInventory& sectorInv, const Po outputConfig.path = "./out"; outputConfig.outputLevelName = "GNFR"; - return RunConfiguration("./data", {}, ModelGrid::Invalid, ValidationType::NoValidation, 2016_y, 2021_y, "", 100.0, {}, sectorInv, pollutantInv, countryInv, outputConfig); + return RunConfiguration("./data", {}, ModelGrid::ChimereCams, ValidationType::NoValidation, 2016_y, 2021_y, "test", 100.0, {}, sectorInv, pollutantInv, countryInv, outputConfig); +} + +static void create_empty_point_source_file(const fs::path& path) +{ + const auto csvHeader = "type;scenario;year;reporting_country;nfr_sector;pollutant;emission;unit;x;y;hoogte_m;diameter_m;temperatuur_C;warmteinhoud_MW;debiet_Nm3/u;dv;type_emissie;EIL_nummer;exploitatie_naam;NACE_code;EIL_Emissiepunt_Jaar_Naam;Activiteit_type"; + file::write_as_text(path, csvHeader); } TEST_CASE("Emission inventory") @@ -166,6 +173,39 @@ TEST_CASE("Emission inventory") checkEmission(inv, EmissionIdentifier(countries::DE, EmissionSector(sectors::nfr::Nfr1A3ai_i), pollutants::As), 111.0, 0.0); checkEmission(inv, EmissionIdentifier(countries::DE, EmissionSector(sectors::nfr::Nfr1A3aii_i), pollutants::As), 222.0, 0.0); } + + SUBCASE("Use scenario point sources if present") + { + TempDir temp("pointscenario"); + + // Modify the data root, so we can change the available point source files + cfg.set_data_root(temp.path()); + + const auto pointSourcesPath = temp.path() / "01_data_emissions" / "inventory" / "reporting_2021" / "pointsources" / "BEF"; + + SUBCASE("Both scenario and non scenario available") + { + const auto pm10ScenarioPath1 = pointSourcesPath / fmt::format("emap_test_PM10_{}_something.csv", static_cast(cfg.year())); + const auto pm10ScenarioPath2 = pointSourcesPath / fmt::format("emap_test_PM10_{}_something_else.csv", static_cast(cfg.year())); + const auto pm10OtherScenarioPath = pointSourcesPath / fmt::format("emap_test2_PM10_{}_something.csv", static_cast(cfg.year())); + const auto pm10NonScenarioPath = pointSourcesPath / fmt::format("emap_PM10_{}_something.csv", static_cast(cfg.year())); + const auto noxNonScenarioPath = pointSourcesPath / fmt::format("emap_NOx_{}_something.csv", static_cast(cfg.year())); + + create_empty_point_source_file(pm10ScenarioPath1); + create_empty_point_source_file(pm10ScenarioPath2); + create_empty_point_source_file(pm10OtherScenarioPath); + create_empty_point_source_file(pm10NonScenarioPath); + create_empty_point_source_file(noxNonScenarioPath); + + RunSummary summary; + read_country_point_sources(cfg, countries::BEF, summary); + + CHECK(summary.used_point_sources().size() == 3); + CHECK(summary.used_point_sources().count(pm10ScenarioPath1) == 1); // The PM10 scenario specific file should be used, the other PM10 files are ignored + CHECK(summary.used_point_sources().count(pm10ScenarioPath2) == 1); // The PM10 scenario specific file should be used, the other PM10 files are ignored + CHECK(summary.used_point_sources().count(noxNonScenarioPath) == 1); // The regular NOx file will be used as there is no scenario specific one + } + } } } \ No newline at end of file