diff --git a/.buildlibrary b/.buildlibrary index 53289f7d..5e7f262d 100644 --- a/.buildlibrary +++ b/.buildlibrary @@ -1,4 +1,4 @@ -ValidationKey: '44432955' +ValidationKey: '44466342' AcceptedWarnings: - Invalid URL: .* - 'Warning: package ''.*'' was built under R version' diff --git a/CITATION.cff b/CITATION.cff index d20d7fd7..23efe142 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,8 +2,8 @@ cff-version: 1.2.0 message: If you use this software, please cite it using the metadata from this file. type: software title: 'mrremind: MadRat REMIND Input Data Package' -version: 0.220.5 -date-released: '2025-03-04' +version: 0.220.6 +date-released: '2025-03-10' abstract: The mrremind packages contains data preprocessing for the REMIND model. authors: - family-names: Baumstark diff --git a/DESCRIPTION b/DESCRIPTION index 110c055b..fceb04a8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: mrremind Title: MadRat REMIND Input Data Package -Version: 0.220.5 -Date: 2025-03-04 +Version: 0.220.6 +Date: 2025-03-10 Authors@R: c( person("Lavinia", "Baumstark", , "lavinia@pik-potsdam.de", role = c("aut", "cre")), person("Renato", "Rodrigues", role = "aut"), diff --git a/NAMESPACE b/NAMESPACE index f44955c2..d566135e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -63,12 +63,14 @@ importFrom(dplyr,distinct) importFrom(dplyr,filter) importFrom(dplyr,full_join) importFrom(dplyr,group_by) +importFrom(dplyr,group_modify) importFrom(dplyr,left_join) importFrom(dplyr,matches) importFrom(dplyr,mutate) importFrom(dplyr,pull) importFrom(dplyr,rename) importFrom(dplyr,select) +importFrom(dplyr,slice_tail) importFrom(dplyr,summarise) importFrom(dplyr,ungroup) importFrom(magrittr,"%>%") diff --git a/R/calcFEdemand.R b/R/calcFEdemand.R index 0cfb85fe..961b9773 100644 --- a/R/calcFEdemand.R +++ b/R/calcFEdemand.R @@ -3,22 +3,20 @@ #' @author Falk Benke calcFEdemand <- function(scenario) { feBuildings <- calcOutput("FeDemandBuildings", - subtype = "FE", - scenario = scenario, - warnNA = FALSE, - aggregate = FALSE - ) - feIndustry <- calcOutput("FeDemandIndustry", scenarios = scenario, warnNA = FALSE, aggregate = FALSE) + subtype = "FE", + scenario = scenario, + warnNA = FALSE, + aggregate = FALSE) + feIndustry <- calcOutput("FeDemandIndustry", + scenarios = scenario, + warnNA = FALSE, + aggregate = FALSE) - # Add up industry and buildings contributions to stationary + t <- intersect(getYears(feBuildings), getYears(feIndustry)) stationaryItems <- c("fehes", "feh2s") - feStationary <- feIndustry[, , stationaryItems] + feBuildings[, , stationaryItems] - remind <- mbind( - feBuildings[, , stationaryItems, invert = TRUE], - feIndustry[, , stationaryItems, invert = TRUE], - feStationary - ) + remind <- mbind(feBuildings[, t, ], feIndustry[, t, ]) + remind <- remind[, , stationaryItems, invert = TRUE] list( x = remind, diff --git a/R/calcFeDemandBuildings.R b/R/calcFeDemandBuildings.R index a478c01e..6f01c8cf 100644 --- a/R/calcFeDemandBuildings.R +++ b/R/calcFeDemandBuildings.R @@ -4,8 +4,14 @@ #' @param scenario A string (or vector of strings) designating the scenario(s) to be returned. #' #' @author Robin Hasse +#' +#' @importFrom dplyr filter mutate distinct select + calcFeDemandBuildings <- function(subtype, scenario) { + # end of history + eoh <- 2025 + if (!subtype %in% c("FE", "FE_buildings", "UE_buildings")) { stop(paste0("Unsupported subtype: ", subtype)) } @@ -14,12 +20,13 @@ calcFeDemandBuildings <- function(subtype, scenario) { scenario <- mrdrivers::toolReplaceShortcuts(scenario) %>% unique() # Data Processing ---- - stationary <- readSource("Stationary", subset = scenario) + ononspec <- calcOutput("FeDemandONONSPEC", scenario = scenario, eoh = eoh, + aggregate = FALSE) buildings <- readSource("EdgeBuildings", subtype = "FE", subset = scenario) # Aggregate to 5-year averages to suppress volatility buildings <- toolAggregateTimeSteps(buildings) - stationary <- toolAggregateTimeSteps(stationary) + ononspec <- toolAggregateTimeSteps(ononspec) if (subtype == "FE") { # Drop RCP dimension (use fixed RCP) @@ -27,18 +34,18 @@ calcFeDemandBuildings <- function(subtype, scenario) { } else { # For each scenario add the rcp scenarios present in buildings to stationary - stationary <- do.call(mbind, lapply(scenario, function(scen) { + ononspec <- do.call(mbind, lapply(scenario, function(scen) { rcps <- getItems(mselect(buildings, scenario = scen), dim = "rcp") - toolAddDimensions(mselect(stationary, scenario = scen), dimVals = rcps, dimName = "rcp", dimCode = 3.2) + toolAddDimensions(mselect(ononspec, scenario = scen), dimVals = rcps, dimName = "rcp", dimCode = 3.2) })) } # Extrapolate years missing in buildings, but existing in stationary buildings <- time_interpolate(buildings, - interpolated_year = getYears(stationary), + interpolated_year = getYears(ononspec), extrapolation_type = "constant") - data <- mbind(stationary, buildings) + data <- mbind(ononspec, buildings) # Prepare Mapping mapping <- toolGetMapping(type = "sectoral", name = "structuremappingIO_outputs.csv", where = "mrcommons") diff --git a/R/calcFeDemandONONSPEC.R b/R/calcFeDemandONONSPEC.R new file mode 100644 index 00000000..07664316 --- /dev/null +++ b/R/calcFeDemandONONSPEC.R @@ -0,0 +1,177 @@ +#' Calculate historic and projected other non-specified energy demand +#' +#' Project the IEA flow ONONSPEC into the future. As we have no idea, where this +#' energy demand comes from, we use a very generic methos to project it into the +#' future: We assume an asymptotic model. It starts from the level +#' \eqn{x_\text{EOD}} at the end of data (EOD) with EOD slope +#' \eqn{\dot{x}_\text{EOD}} and approaches the fraction \eqn{\varepsilon} of +#' this slope within \eqn{\Delta t}. Both \eqn{x_\text{EOD}} and +#' \eqn{\dot{x}_\text{EOD}} are determined through a linear regression of the +#' last \eqn{n} time steps with IEA data. +#' \deqn{x(t) = x_\text{EOD} + \dfrac{\dot{x}_\text{EOD}}{c} +#' \cdot [1 - \exp (-c \cdot (t - t_\text{EOD}))]} +#' with the decay rate \eqn{c = -\dfrac{\ln \varepsilon}{\Delta t}} \cr +#' \eqn{\Delta t} is differentiated by scenarios thus approaching a `low`, +#' `med` and `high` long-term level. To assure that scenarios don't differ at +#' the end of history (EOH), time steps between EOD and EOH are projected with +#' `med` value of \eqn{\Delta t}. +#' +#' Each scenario \eqn{s} has a differentiated \eqn{\Delta t_s}. For +#' \eqn{\dot{x}_\text{EOD} > 0}, the `high` (`low`) scenario assumes a longer +#' (shorter) time span and for \eqn{\dot{x}_\text{EOD} < 0} vice versa to reach +#' a higher (lower) long-term value. We want to make sure that until the end of +#' history (EOH), all scenarios are still identical. So we take the `med` +#' parameterisation until EOH. Afterwards, we adjust the model such that we +#' start with EOH level and slope and still reach the target slope +#' \eqn{\varepsilon \cdot \dot{x}_\text{EOD}} until +#' \eqn{t_\text{EOD} + \Delta t_s}: +#' \deqn{x_s(t) = x_\text{EOH} + \dfrac{\dot{x}_\text{EOH}}{c_s} +#' \cdot [1 - \exp (-c_s \cdot (t - t_\text{EOH}))]} +#' with the decay rate +#' \eqn{c_s = -\dfrac{\ln \varepsilon } +#' {\Delta t_s - (t_\text{EOH} - t_\text{EOD})} +#' \cdot \left(1 - \dfrac{t_\text{EOH} - t_\text{EOD}} +#' {\Delta t_\text{med}} \right)} +#' +#' @author Robin Hasse +#' +#' @param scenario character vector of remind demand scenarios +#' @param eoh numeric, end of history: last time step without scenario +#' differentiation + +#' @returns list with MagPIE object +#' +#' @importFrom dplyr filter group_by group_modify slice_tail ungroup left_join +#' mutate select + +calcFeDemandONONSPEC <- function(scenario, eoh) { + + # SETTINGS ------------------------------------------------------------------- + + # project in 5yr time steps until + endOfProjection <- 2150 + + # approach constant level after ... years + yearsUntilConst <- c(15, 20, 25) + + # ratio of EOD slope that qualifies as constant + constTolerance <- 0.05 + + # number of last time steps to consider in linear regression of EOD + nTimeStepsEOD <- 10 + + + + # FUNCTIONS ------------------------------------------------------------------ + + .getFlowsIEA <- function() { + data <- calcOutput("IOEdgeBuildings", + subtype = "output_EDGE", + aggregate = FALSE) + getSets(data) <- c("region", "period", "item") + # ONONSPEC have been mapped to feoth* before + itemsONONSPEC <- grep("^feoth.+$", getItems(data, 3), value = TRUE) + data[, , itemsONONSPEC] + } + + + .getEODcoefs <- function(x, key) { + m <- stats::lm(value~period, x) + eod <- max(x$period) + coefs <- data.frame(eod = eod, + slopeEOD = stats::coef(m)[["period"]], + valueEOD = stats::predict(m, list(period = eod))) + } + + + .expandScenarios <- function(x, key = NULL, dt = NULL) { + scens <- data.frame(levelScen = c("low", "med", "high")) + if (!is.null(dt)) { + scens$dt = if (all(x$slopeEOD > 0)) dt else rev(dt) + } + merge(scens, x) + } + + + .expandPeriods <- function(x, key) { + start <- unique(x$eod) + 1 + tHist <- if (start <= eoh - 1) seq(start, eoh - 1, 1) else NULL + tFuture <- seq(eoh, endOfProjection, 5) + periods <- data.frame(period = c(tHist, tFuture)) + merge(periods, x) + } + + + .calcAsymptotic <- function(xStart, xDotStart, dt, t, tStart, epsCorrection = 0, + eps = constTolerance) { + decay <- -log(eps) * (1 + epsCorrection) / dt + x <- xStart + xDotStart / decay * (1 - exp(-decay * (t - tStart))) + pmax(0, x) + } + + + .projectFlows <- function(flowsData) { + flowsData %>% + filter(!is.na(.data$value)) %>% + group_by(dplyr::across(tidyselect::all_of(c("region", "item")))) %>% + arrange(.data$period) %>% + slice_tail(n = nTimeStepsEOD) %>% + group_modify(.getEODcoefs) %>% + group_modify(.expandPeriods) %>% + mutate(valueHist = .calcAsymptotic(xStart = .data$valueEOD, + xDotStart = .data$slopeEOD, + dt = yearsUntilConst[2], + t = .data$period, + tStart = .data$eod)) %>% + group_modify(.expandScenarios, yearsUntilConst) %>% + mutate(valueFuture = .calcAsymptotic(xStart = .data$valueHist[.data$period == eoh], + xDotStart = .data$slopeEOD * constTolerance^((eoh - .data$eod) / yearsUntilConst[2]), + dt = .data$dt - (eoh - .data$eod), + t = .data$period, + tStart = eoh, + epsCorrection = -(eoh - .data$eod) / yearsUntilConst[2]), + value = ifelse(.data$period > eoh, .data$valueFuture, .data$valueHist)) %>% + ungroup() %>% + select("region", "period", "levelScen", "item", "value") + } + + + .extrapolateFlows <- function(flowsData) { + flowsProj <- .projectFlows(flowsData) + rbind(.expandScenarios(flowsData), flowsProj) + } + + + .createScenMapping <- function(scenario) { + map <- data.frame(scenario = scenario, levelScen = "med") + map[map$scenario %in% c("SSP3", "SSP5", "SSP2IndiaHigh"), "levelScen"] <- "high" + map[map$scenario %in% c("SSP1", "SSP2_lowEn"), "levelScen"] <- "low" + map + } + + + .mapRemindScens <- function(flows, scenario) { + .createScenMapping(scenario) %>% + left_join(flows, by = "levelScen", relationship = "many-to-many") %>% + select("region", "period", "scenario", "item", "value") + } + + + + # CALCULATE ------------------------------------------------------------------ + + flowsData <- tibble::as_tibble(.getFlowsIEA()) + flows <- .extrapolateFlows(flowsData) + flows <- .mapRemindScens(flows, scenario) + flows <- as.magpie(flows, spatial = "region", temporal = "period") + + + + # OUTPUT --------------------------------------------------------------------- + + list(x = flows, + weight = NULL, + min = 0, + unit = "EJ/yr", + description = "Other non-specified energy demand") +} diff --git a/README.md b/README.md index 6e6ebf38..0d0e6cd9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MadRat REMIND Input Data Package -R package **mrremind**, version **0.220.5** +R package **mrremind**, version **0.220.6** [![CRAN status](https://www.r-pkg.org/badges/version/mrremind)](https://cran.r-project.org/package=mrremind) [![R build status](https://github.com/pik-piam/mrremind/workflows/check/badge.svg)](https://github.com/pik-piam/mrremind/actions) [![codecov](https://codecov.io/gh/pik-piam/mrremind/branch/master/graph/badge.svg)](https://app.codecov.io/gh/pik-piam/mrremind) [![r-universe](https://pik-piam.r-universe.dev/badges/mrremind)](https://pik-piam.r-universe.dev/builds) @@ -39,7 +39,7 @@ In case of questions / problems please contact Lavinia Baumstark . +Baumstark L, Rodrigues R, Levesque A, Oeser J, Bertram C, Mouratiadou I, Malik A, Schreyer F, Soergel B, Rottoli M, Mishra A, Dirnaichner A, Pehl M, Giannousakis A, Klein D, Strefler J, Feldhaus L, Brecha R, Rauner S, Dietrich J, Bi S, Benke F, Weigmann P, Richters O, Hasse R, Fuchs S, Mandaroux R, Koch J (2025). "mrremind: MadRat REMIND Input Data Package." Version: 0.220.6, . A BibTeX entry for LaTeX users is @@ -47,9 +47,9 @@ A BibTeX entry for LaTeX users is @Misc{, title = {mrremind: MadRat REMIND Input Data Package}, author = {Lavinia Baumstark and Renato Rodrigues and Antoine Levesque and Julian Oeser and Christoph Bertram and Ioanna Mouratiadou and Aman Malik and Felix Schreyer and Bjoern Soergel and Marianna Rottoli and Abhijeet Mishra and Alois Dirnaichner and Michaja Pehl and Anastasis Giannousakis and David Klein and Jessica Strefler and Lukas Feldhaus and Regina Brecha and Sebastian Rauner and Jan Philipp Dietrich and Stephen Bi and Falk Benke and Pascal Weigmann and Oliver Richters and Robin Hasse and Sophie Fuchs and Rahel Mandaroux and Johannes Koch}, - date = {2025-03-04}, + date = {2025-03-10}, year = {2025}, url = {https://github.com/pik-piam/mrremind}, - note = {Version: 0.220.5}, + note = {Version: 0.220.6}, } ``` diff --git a/man/calcFeDemandONONSPEC.Rd b/man/calcFeDemandONONSPEC.Rd new file mode 100644 index 00000000..4750310e --- /dev/null +++ b/man/calcFeDemandONONSPEC.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/calcFeDemandONONSPEC.R +\name{calcFeDemandONONSPEC} +\alias{calcFeDemandONONSPEC} +\title{Calculate historic and projected other non-specified energy demand} +\usage{ +calcFeDemandONONSPEC(scenario, eoh) +} +\arguments{ +\item{scenario}{character vector of remind demand scenarios} + +\item{eoh}{numeric, end of history: last time step without scenario +differentiation} +} +\value{ +list with MagPIE object +} +\description{ +Project the IEA flow ONONSPEC into the future. As we have no idea, where this +energy demand comes from, we use a very generic methos to project it into the +future: We assume an asymptotic model. It starts from the level +\eqn{x_\text{EOD}} at the end of data (EOD) with EOD slope +\eqn{\dot{x}_\text{EOD}} and approaches the fraction \eqn{\varepsilon} of +this slope within \eqn{\Delta t}. Both \eqn{x_\text{EOD}} and +\eqn{\dot{x}_\text{EOD}} are determined through a linear regression of the +last \eqn{n} time steps with IEA data. +\deqn{x(t) = x_\text{EOD} + \dfrac{\dot{x}_\text{EOD}}{c} + \cdot [1 - \exp (-c \cdot (t - t_\text{EOD}))]} +with the decay rate \eqn{c = -\dfrac{\ln \varepsilon}{\Delta t}} \cr +\eqn{\Delta t} is differentiated by scenarios thus approaching a \code{low}, +\code{med} and \code{high} long-term level. To assure that scenarios don't differ at +the end of history (EOH), time steps between EOD and EOH are projected with +\code{med} value of \eqn{\Delta t}. +} +\details{ +Each scenario \eqn{s} has a differentiated \eqn{\Delta t_s}. For +\eqn{\dot{x}_\text{EOD} > 0}, the \code{high} (\code{low}) scenario assumes a longer +(shorter) time span and for \eqn{\dot{x}_\text{EOD} < 0} vice versa to reach +a higher (lower) long-term value. We want to make sure that until the end of +history (EOH), all scenarios are still identical. So we take the \code{med} +parameterisation until EOH. Afterwards, we adjust the model such that we +start with EOH level and slope and still reach the target slope +\eqn{\varepsilon \cdot \dot{x}_\text{EOD}} until +\eqn{t_\text{EOD} + \Delta t_s}: +\deqn{x_s(t) = x_\text{EOH} + \dfrac{\dot{x}_\text{EOH}}{c_s} + \cdot [1 - \exp (-c_s \cdot (t - t_\text{EOH}))]} +with the decay rate +\eqn{c_s = -\dfrac{\ln \varepsilon } + {\Delta t_s - (t_\text{EOH} - t_\text{EOD})} + \cdot \left(1 - \dfrac{t_\text{EOH} - t_\text{EOD}} + {\Delta t_\text{med}} \right)} +} +\author{ +Robin Hasse +}