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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .buildlibrary
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ValidationKey: '22925485'
ValidationKey: '23121280'
AutocreateReadme: yes
AutocreateCITATION: yes
AcceptedWarnings:
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -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: 'mrmfa: Input data generation for the REMIND MFA'
version: 1.11.1
date-released: '2026-07-01'
version: 1.12.0
date-released: '2026-07-10'
abstract: The mrmfa packages contains data preprocessing for the REMIND MFA.
authors:
- family-names: Dürrwächter
Expand Down
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Type: Package
Package: mrmfa
Title: Input data generation for the REMIND MFA
Version: 1.11.1
Date: 2026-07-01
Version: 1.12.0
Date: 2026-07-10
Authors@R: c(
person("Jakob", "Dürrwächter", , "jakobdu@pik-potsdam.de", role = c("aut", "cre")),
person("Bennet", "Weiss", , "bennet.weiss@pik-potsdam.de", role = "aut"),
Expand Down
29 changes: 18 additions & 11 deletions R/calcCeFloorspaceEDGEB.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
#'
#' @param scenarios EDGE-B scenarios (character vector or string). Available: "SSP1", "SSP2", "SSP3", "SSP4", "SSP5".
#' @param collapse Logical. If TRUE, redundant dimensions (e.g. scenario if only one requested) are removed.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation
#' (see \link{toolHistoricallyConsistentSmoothing}); smoothed historical values are identical across scenarios.
#' @param dof Integer. Degrees of freedom for spline interpolation.
#' @author Bennet Weiss
calcCeFloorspaceEDGEB <- function(scenarios = "SSP2", collapse = TRUE, smooth = FALSE, dof = 8) {
Expand All @@ -12,13 +13,8 @@ calcCeFloorspaceEDGEB <- function(scenarios = "SSP2", collapse = TRUE, smooth =
# Unit conversion from million m2 to m2
floorspace <- floorspace * 1e6

# Remove redundant dimensions (e.g. Scenario, if only one is given)
if (collapse) {
floorspace <- collapseDim(floorspace)
}

# Remove buildings total
floorspace <- floorspace[, , (Variable <- "buildings"), invert = TRUE]
floorspace <- floorspace[, , "buildings", invert = TRUE]

# enforce MFA naming convention
getNames(floorspace) <- gsub("commercial", "Com", getNames(floorspace))
Expand All @@ -27,10 +23,21 @@ calcCeFloorspaceEDGEB <- function(scenarios = "SSP2", collapse = TRUE, smooth =
floorspace_years <- getYears(floorspace, as.integer = TRUE)
# smooth if requested
if (smooth) {
# smooth data and interpolate missing data; ensure pegging of key years
# remove data beyond 2100 from smoothing due to low data quality
years <- floorspace_years[floorspace_years<=2100]
floorspace[, years] <- toolTimeSpline(floorspace[, years], dof = dof)
# smooth data while keeping the historical period identical across scenarios
# exclude data beyond 2100 from smoothing due to low data quality
years <- floorspace_years[floorspace_years <= 2100]
scens <- getItems(floorspace, dim = "scenario")
refScenario <- if ("SSP2" %in% scens) "SSP2" else scens[1]
floorspace[, years] <- toolHistoricallyConsistentSmoothing(
floorspace[, years],
refScenario = refScenario,
dof = dof
)
}

# Remove redundant dimensions (e.g. scenario, if only one is given)
if (collapse) {
floorspace <- collapseDim(floorspace)
}

# interpolate to yearly data
Expand Down
11 changes: 9 additions & 2 deletions R/calcCoGDP.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
#' @param perCapita Logical. If TRUE, GDP is returned as per capita.
#' @param scenarios Character vector or string specifying the scenarios to use for the future.
#' @param collapse Logical. If TRUE, redundant dimensions (e.g. scenario if only one requested) are removed.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation
#' (see \link{toolHistoricallyConsistentSmoothing}); smoothed historical values
#' are identical across scenarios.
#' @param dof Integer. Degrees of freedom for spline interpolation.
#' Higher values lead to a closer fit to the original data, while lower values result in smoother curves.
#' @return List with Magpie object of GDP (given in 2005 USD) and metadata in calcOutput format.
Expand Down Expand Up @@ -62,8 +64,13 @@ calcCoGDP <- function(perCapita = FALSE, scenarios = "SSP2", collapse = TRUE, sm

if (smooth) {
# smooth data and interpolate missing data
# exclude data beyond 2100 from smoothing due to low data quality
years <- startyear:2100
gdp[, years] <- toolTimeSpline(gdp[, years], dof = dof, peggedYears = c(1900, 2023, 2100))
gdp[, years] <- toolHistoricallyConsistentSmoothing(
gdp[, years],
refScenario = if ("SSP2" %in% getItems(gdp, dim = "scenario")) "SSP2" else getItems(gdp, dim = "scenario")[1],
dof = dof
)
}

# finalize for calcOutput
Expand Down
14 changes: 10 additions & 4 deletions R/calcCoPopulation.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#' @author Merlin Jo Hosak, Bennet Weiss
#' @param scenarios Character vector or string specifying the scenarios to use for the future.
#' @param collapse Logical. If TRUE, redundant dimensions (e.g. scenario if only one requested) are removed.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation.
#' @param smooth Logical. If TRUE, data is smoothed using spline interpolation
#' (see \link{toolHistoricallyConsistentSmoothing}); smoothed historical values
#' are identical across scenarios.
#' @param dof Integer. Degrees of freedom for spline interpolation.
#' Higher values lead to a closer fit to the original data, while lower values result in smoother curves.
#' @return List with Magpie object of population and metadata in calcOutput
Expand All @@ -39,10 +41,14 @@ calcCoPopulation <- function(scenarios = "SSP2", collapse = TRUE, smooth = FALSE
pop <- toolBackcastByReference(x = pop, ref = worldHist)

if (smooth) {
# smooth data and interpolate missing data; ensure pegging of key years
# remove data beyond 2100 from smoothing due to low data quality
# smooth data and interpolate missing data
# exclude data beyond 2100 from smoothing due to low data quality
years <- min(getYears(pop, as.integer = TRUE)):2100
pop[, years] <- toolTimeSpline(pop[, years], dof = dof, peggedYears = c(1900, 2023, 2100))
pop[, years] <- toolHistoricallyConsistentSmoothing(
pop[, years],
refScenario = if ("SSP2" %in% getItems(pop, dim = "scenario")) "SSP2" else getItems(pop, dim = "scenario")[1],
dof = dof
)
}

# build description including scenario and smoothing note
Expand Down
111 changes: 111 additions & 0 deletions R/toolHistoricallyConsistentSmoothing.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#' Historically consistent time smoothing across scenarios
#'
#' @description
#' Smooths a magpie object over time using \link[madrat]{toolTimeSpline} while
#' ensuring that smoothed values in the historical period are identical across
#' all scenarios. Plain per-scenario spline smoothing lets scenario-specific
#' future data pull the fit inside the historical period, so smoothed
#' historical values would otherwise differ between scenarios.
#' After smoothing, all years up to \code{lastHistYear} are replaced in every
#' scenario by the smoothed values of \code{refScenario}. To avoid a
#' derivative discontinuity at the transition, each non-reference scenario is
#' faded from the reference trajectory to its own trajectory over
#' \code{fadeLength} years using the smootherstep polynomial
#' \eqn{w(t) = 6t^5 - 15t^4 + 10t^3}, whose first and second derivatives
#' vanish at both endpoints. The blended curve therefore matches the slope of
#' the shared history at the start of the fade window and the slope of the
#' scenario-specific spline at its end, yielding a C2-continuous transition.
#'
#' @param x A magpie object with a 'scenario' dimension.
#' @param lastHistYear Integer or NULL. Last year considered historical. If
#' NULL (default), it is detected automatically as the last year up to which
#' all scenarios in \code{x} share identical values. If given, the historical
#' period is forced up to this year and \code{refScenario} values are written
#' there even where scenarios originally differ. Ignored if x contains a
#' single scenario.
#' @param refScenario Character. Name of the scenario whose smoothed values
#' define the shared history (default "SSP2"). Must be located in 'scenario'
#' dimension of \code{x}. Ignored if x contains a single scenario.
#' @param fadeLength Integer > 0. Length of the transition window in years.
#' 1 produces a hard splice, which is generally discontinuous.
#' @param dof Degrees of freedom passed to \link[madrat]{toolTimeSpline}.
#' @param verbose Logical. If TRUE, print informational messages (e.g. the
#' automatically detected last historical year). Default FALSE.
#' @param ... Further arguments (e.g. peggedYears, anchorFactor) passed to
#' \link[madrat]{toolTimeSpline}.
#' @return Smoothed magpie object with scenario-independent history.
#' @author Bennet Weiss
toolHistoricallyConsistentSmoothing <- function(
x,
lastHistYear = NULL,
refScenario = "SSP2",
fadeLength = 10,
dof = 8,
verbose = FALSE,
...
) {
if (!is.numeric(fadeLength) || length(fadeLength) != 1 || fadeLength <= 0) {
stop("fadeLength must be a single positive number (larger than 0).")
}

smoothed <- toolTimeSpline(x, dof = dof, ...)

# without the scenario dimension or with a single scenario there is nothing to harmonize
if (!"scenario" %in% getSets(x)) {
warning("No dimension 'scenario' found in x. Returning plainly smoothed object.")
return(smoothed)
}
scens <- getItems(x, dim = "scenario")
if (length(scens) <= 1) {
return(smoothed)
}

if (!refScenario %in% scens) {
stop(
"refScenario '", refScenario, "' not found in dimension 'scenario'. Available: ", paste(scens, collapse = ", ")
)
}

# selector for a single scenario in the scenario (sub)dimension
sel_refScen <- list(scenario = refScenario)
otherScens <- setdiff(scens, refScenario)
years <- getYears(smoothed, as.integer = TRUE)

if (is.null(lastHistYear)) {
# detect the historical period as the leading run of years for which all
# scenarios in the (unsmoothed) input share identical values
refArr <- x[, , sel_refScen]
otherArrs <- x[, , list(scenario = otherScens)]
for (i in seq_along(years)) {
sameThisYear <- all(refArr[, i, ] == otherArrs[, i, ])
if (!sameThisYear) break
lastHistYear <- years[i]
}
Comment thread
bennet21 marked this conversation as resolved.
if (is.null(lastHistYear)) {
Comment thread
fbenke-pik marked this conversation as resolved.
warning(
"Scenarios already differ in the first year; no common historical ",
"period could be detected. Returning plainly smoothed object."
)
return(smoothed)
}
if (verbose) message("Detected last historical year: ", lastHistYear)
}

if(min(years) > lastHistYear) {
warning("No years <= lastHistYear (", lastHistYear, ") in data. Returning plainly smoothed object.")
return(smoothed)
}
if (lastHistYear >= max(years)) {
warning("lastHistYear >= last data year. All scenarios are replaced by refScenario values.")
}

# prepare the fade weights: use C2-continuous smootherstep polynomial (see description) to transition from 0 to 1
tNorm <- pmax(0, pmin(1, (years - lastHistYear) / fadeLength))
w <- new.magpie(years = years, fill = (6 * tNorm^5 - 15 * tNorm^4 + 10 * tNorm^3))

for (s in otherScens) {
# overwrite hist period with refScen, then fade from reference to scenario-specific spline
smoothed[, , s] <- (1 - w) * smoothed[, , sel_refScen] + w * smoothed[, , s]
}
return(smoothed)
}
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Input data generation for the REMIND MFA

R package **mrmfa**, version **1.11.1**
R package **mrmfa**, version **1.12.0**

[![R build status](https://github.com/pik-piam/mrmfa/workflows/check/badge.svg)](https://github.com/pik-piam/mrmfa/actions) [![codecov](https://codecov.io/gh/pik-piam/mrmfa/branch/master/graph/badge.svg)](https://app.codecov.io/gh/pik-piam/mrmfa)

Expand Down Expand Up @@ -38,17 +38,17 @@ In case of questions / problems please contact Jakob Dürrwächter <jakobdu@pik-

To cite package **mrmfa** in publications use:

Dürrwächter J, Weiss B, Schweiger L, Benke F, Hosak M, Zhang Q (2026). "mrmfa: Input data generation for the REMIND MFA." Version: 1.11.1, <https://github.com/pik-piam/mrmfa>.
Dürrwächter J, Weiss B, Schweiger L, Benke F, Hosak M, Zhang Q (2026). "mrmfa: Input data generation for the REMIND MFA." Version: 1.12.0, <https://github.com/pik-piam/mrmfa>.

A BibTeX entry for LaTeX users is

```latex
@Misc{,
title = {mrmfa: Input data generation for the REMIND MFA},
author = {Jakob Dürrwächter and Bennet Weiss and Leonie Schweiger and Falk Benke and Merlin Jo Hosak and Qianzhi Zhang},
date = {2026-07-01},
date = {2026-07-10},
year = {2026},
url = {https://github.com/pik-piam/mrmfa},
note = {Version: 1.11.1},
note = {Version: 1.12.0},
}
```
3 changes: 2 additions & 1 deletion man/calcCeFloorspaceEDGEB.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion man/calcCoGDP.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion man/calcCoPopulation.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions man/toolHistoricallyConsistentSmoothing.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion tests/testthat/test-dummy.R

This file was deleted.

Loading
Loading