shift_future_epw() is the recommended user-facing interface in Create Future EPW Files. Use shift_plan() and shift_explain() when you want to inspect the stages before running them. shift_morph() and shift_epw() remain the lower-level wrappers around EpwMorpher, the store-native morphing engine.

Use EpwMorpher directly when you need to inspect monthly summaries, preview factor calculations, diagnose missing variables, choose morphing case grouping, change the statistical downscaling backend, or rerun only the morph/write part of a workflow.

Inputs

EpwMorpher consumes completed extraction outputs in an EsgStore and a baseline EPW file. The examples below assume the store already has a future extraction plan named region and a historical reference extraction plan named reference_region.

store <- EsgStore$new("~/cmip6-singapore-store", create = FALSE)

epw <- system.file(
    "extdata", "examples", "SGP_Singapore.486980_IWEC.epw",
    package = "epwshiftr",
    mustWork = TRUE
)

morpher <- epw_morpher(
    store = store,
    epw = epw,
    site_id = "SIN",
    recipe = epw_morph_recipe("belcher"),
    label = "Singapore baseline"
)

The helper functions used by the high-level workflow are available directly:

epw_morph_variables("minimal")
epw_morph_variables("recommended")
epw_morph_variables("extended")

periods <- epw_morph_periods(`2060s` = 2060L)
reference_spec <- historical_reference(1995:2014)

request <- shift_cmip6_scenario(
    source = "BCC-CSM2-MR",
    scenario = c("ssp126", "ssp585"),
    years = 2055:2065,
    variables = "belcher",
    frequency = "mon"
)

"recommended" is the strict Belcher recipe set used by the main workflow. "minimal" is useful for relaxed demonstrations, and "extended" adds related variables for future recipes.

When you already have a recipe or backend object, ask that object for its variables instead of choosing a named set manually:

belcher_recipe <- epw_morph_recipe("belcher")
epw_morph_variables(belcher_recipe)
epw_morph_variables(belcher_recipe, include_optional = TRUE)
epw_morph_variables(epw_morph_backend("belcher"))

The enhanced Belcher profile is the default. Required variables block strict execution when absent; optional tasmax, tasmin, and snd inputs improve the result when matching future/reference data exist but do not otherwise block it. The high-level shift_cmip6(table = NULL) workflow maps atmospheric variables to Amon and snd to LImon automatically.

Recipes and Backends

A recipe selects a backend and carries backend-specific profile, options, and method overrides. The default recipe is "belcher", which applies the Belcher-style statistical downscaling steps used by the high-level workflow.

enhanced_recipe <- epw_morph_recipe(
    "belcher",
    options = belcher_options(
        transition_hours = 72L,
        snow_depth = "auto",
        design_conditions = "drop"
    )
)

# Reproduce the numerical and header behavior from before enhanced profiles.
legacy_recipe <- epw_morph_recipe("belcher", profile = "legacy")

Complete Recipe Catalog

Backends describe executable statistical engines, while the complete recipe catalog describes named scientific methods assembled from those engines and the seven component stages. Inspecting the catalog does not run a method or serialize any function:

epw_morph_recipes()
epw_morph_recipe_spec("epwshiftr_daily_power")
epw_morph_recipe_spec("epwshiftr_daily_btws")
epw_morph_recipe_spec("eames_monthly_temperature")
epw_morph_recipe_spec("ek_daily_factors")
epw_morph_recipe_spec("monthly_percentile_temperature")

The initial catalog distinguishes the following complete methods:

Recipe Policy Implementation Purpose
belcher_monthly paper_faithful Existing backend adapter Belcher monthly comparison with matching historical and future model data
epwshiftr_monthly harmonized Existing backend adapter Enhanced monthly workflow with shared epwshiftr physical and output policies
epwshiftr_daily_power harmonized Seven-stage pipeline Calendar-neutral daily temperature signal and constrained power projection
epwshiftr_daily_btws harmonized Seven-stage pipeline Existing daily CMIP6 temperature signal combined with the BTWS hourly projection
eames_monthly_temperature harmonized Seven-stage pipeline Eames monthly mean/average-daily-extrema changes and BTWS, with monthly statistics derived from daily CMIP6 inputs
ek_daily_factors paper_faithful, harmonized Seven-stage pipeline Ek day-of-year mean/DTR factors and combined hourly temperature transform without an added smoothing window
monthly_percentile_temperature paper_faithful, harmonized Seven-stage pipeline Arima month-wise model CDF change functions selected by each TMY day’s percentile in a multi-year observed CDF
sobie_curry_daily paper_faithful, harmonized Seven-stage pipeline Published daily mean/DTR transformations with independent or shared humidity closure

Create a configured recipe by its stable catalog name. The selected policy, catalog version, backend profile, and component names are persisted with the workflow:

monthly_comparison <- epw_morph_recipe(
    "belcher_monthly",
    policy = "paper_faithful"
)
daily_power <- epw_morph_recipe(
    "epwshiftr_daily_power",
    policy = "harmonized"
)
daily_btws_comparison <- epw_morph_recipe(
    "epwshiftr_daily_btws",
    policy = "harmonized"
)
eames_monthly_temperature <- epw_morph_recipe(
    "eames_monthly_temperature",
    policy = "harmonized"
)
ek_daily <- epw_morph_recipe(
    "ek_daily_factors",
    policy = "paper_faithful"
)
monthly_percentile_temperature <- epw_morph_recipe(
    "monthly_percentile_temperature",
    policy = "paper_faithful"
)
sobie_curry <- epw_morph_recipe(
    "sobie_curry_daily",
    policy = "paper_faithful"
)
sobie_curry_harmonized <- epw_morph_recipe(
    "sobie_curry_daily",
    policy = "harmonized"
)

Catalog input roles are validated before backend execution. The two monthly entries remain explicitly labelled backend adapters because their established runner has not yet been decomposed into executable components; the daily entries are checked against their registered seven-stage pipelines. Ad hoc recipes such as epw_morph_recipe("belcher") remain supported and preserve their previous defaults.

The BTWS comparison requires daily tas, tasmin, and tasmax from matching historical and future model periods. It reuses epwshiftr’s calendar-neutral daily target signal, baseline EPW sequence, specific-humidity closure, and output stages, while replacing only the hourly stage with equations (7)–(16) from Eames et al. (2024). The complete recipe is therefore intentionally labelled as a combination rather than a reproduction of the paper’s monthly UKCP18 workflow. The paper directs implementations to reduce m for a positive stretch or n for a negative stretch when the default m = n = 1 would leave [0, 1], but does not publish solver code. epwshiftr uses deterministic bisection to retain the largest admissible exponent and records S, m, n, closure errors, and every mean-shift fallback reason.

At the high-level workflow, select the same daily temperature method and change only its hourly reconstruction component:

btws_method <- daily_temperature(
    historical_reference(years = 1995:2014),
    reconstruction = "btws"
)

Eames et al. did not drive BTWS with daily CMIP6 series. Their paper applies monthly UKCP18 change factors for mean temperature and the monthly averages of daily minimum and maximum temperature to an hourly baseline month, with BTWS executed day by day. eames_monthly_temperature implements that temporal structure for temperature: it aggregates matching daily CMIP6 tas, tasmin, and tasmax into the same three monthly statistics and maps one factor set to every EPW day in the corresponding month:

eames_method <- eames_temperature(
    historical_reference(years = 1995:2014)
)

The CMIP6 aggregation is a source adaptation because the paper used UKCP18. The recipe is also explicitly temperature-only: it reuses epwshiftr’s specific-humidity closure and does not implement the paper’s radiation, cloud, pressure, or other non-temperature transformations. The epwshiftr_daily_btws recipe remains available to compare hourly reconstruction algorithms under the same daily-varying CMIP6 signal.

Ek et al. (2018) instead form a climate baseline for each day of the annual cycle and apply daily Belcher-style change factors to the inherited hourly weather sequence. The temperature recipe derives mean temperature and DTR from matching tasmin and tasmax, applies no additional smoothing window, and uses the combined shift-and-stretch equation:

xh=xh+Δxd+αDTR,d(xhx0,d) x_h' = x_h + \Delta\bar{x}_d + \alpha_{\mathrm{DTR},d}(x_h - \bar{x}_{0,d})

Here alpha_DTR is implemented as the relative modeled DTR change. This is the interpretation that makes zero climate change an identity and satisfies the paper’s stated daily mean and variance behavior; the original publication’s generic factor equations and variance wording are not fully self-consistent, so the interpretation is retained explicitly in recipe provenance.

ek_method <- ek_daily_temperature(
    historical_reference(years = 1995:2014),
    policy = "paper_faithful"
)

The initial recipe is temperature-only. Its paper_faithful policy preserves the baseline humidity fields so the Ek temperature transformation can be compared directly; policy = "harmonized" applies the shared specific-humidity closure. Wind, cloud, radiation, and other transformations are not included where the source publication is contradictory or underspecified.

Arima et al. (2024) use four distinct inputs: the baseline TMY, historical and future daily model values, and multi-year observed daily weather for the target location. For temperature, historical and future inverse CDFs are constructed within each month and subtracted at common percentiles:

ΔTm(p)=Ff,m1(p)Fc,m1(p) \Delta T_m(p) = F^{-1}_{f,m}(p) - F^{-1}_{c,m}(p)

The change function is smoothed with an endpoint-aware nine-point moving mean repeated three times. The baseline TMY daily mean is then located in the observed monthly empirical CDF, and the selected additive change is applied to all 24 hours of that day. Model calendars therefore contribute samples to monthly distributions; this method does not map model dates directly onto the 365 EPW days.

arima_method <- arima_temperature(
    reference = historical_reference(years = 1995:2014),
    observed_reference = shift_reference_plan(
        plan_id = observed_plan_ids,
        periods = epw_morph_periods(observed = 1995:2014)
    ),
    policy = "paper_faithful"
)

The publications do not specify empirical plotting positions, quantile interpolation, or endpoint evaluation. The implementation therefore records its midpoint probability grid, type-7 quantiles, linear factor interpolation, and endpoint clamping in every result. The initial recipe covers additive dry-bulb temperature for one model case; multi-model averaging and the published non-temperature transformations remain separate extensions.

The Sobie-Curry recipe requires daily tas, tasmin, tasmax, huss, and surface pressure ps from matching historical and future model periods. It uses a circular 21-day window on the shared 365-day annual-phase grid, preserves the baseline CWEC/EPW sequence, and adjusts dry-bulb temperature and atmospheric pressure. Paper-faithful mode independently adjusts dew point and relative humidity. Harmonized mode applies the smoothed future-minus-historical daily huss change to baseline EPW specific humidity, clips the target only at zero or saturation, and derives relative humidity and dew point from projected temperature and pressure. Other EPW fields are unchanged. The paper-faithful dew-point standard-deviation factor is implemented as sigma_future / sigma_historical - 1: this preserves the paper’s stated past-future difference and makes zero climate change an identity transform. The printed ratio without - 1, when added to the original anomaly as shown in the paper, would double hourly anomalies under zero change.

The profiles resolve to a complete, persisted option contract:

Option Enhanced default Legacy default Meaning and allowed values
transition_hours 72 0 Total cyclic smoothing window centered on each month boundary; integer 0336. Precipitation is never smoothed.
humidity_source "auto" "hurs" Whole-case humidity source: "auto", "huss", or "hurs".
diffuse_model "rbl_2010" "preserve_fraction" Recalculate diffuse radiation with Ridley–Boland–Lauret 2010 or retain the baseline diffuse fraction.
illuminance_model "perez_1990" "preserve" Recalculate illuminance/luminance with Pérez 1990 or preserve the prior relation.
snow_depth "auto" "off" Use snd when both future and reference cases contain it, require it, or do not query it: "auto", "required", "off".
ground_temperatures "recalculate" "preserve" Recalculate the EPW ground-temperature header with Kusuda–Achenbach or retain it.
typical_extreme_periods "recalculate" "preserve" Recalculate six hemisphere-aware periods or retain the baseline header.
design_conditions "drop" "preserve" Write DESIGN CONDITIONS,0 or retain the baseline design conditions.

belcher_options() creates a complete option list using enhanced defaults. For a legacy profile, pass a partial named list when only selected legacy defaults should change, for example belcher(profile = "legacy", options = list(snow_depth = "auto")). Use print(belcher(), verbose = TRUE) to inspect all resolved values that will be stored with a task.

Enhanced tdb = "auto" uses combined morphing month by month when complete tasmax and tasmin are available and falls back to shift with a diagnostic otherwise. humidity_source = "auto" selects one source for the whole case: complete huss + tas + ps is preferred for the default shift humidity method, while an explicit non-shift RH override keeps the HURS path. Radiation and EPW header policies are likewise persisted in the recipe so resumed jobs cannot change behavior when package defaults evolve.

Enhanced Calculation Stages

Temperature "combined" uses the mean daily range of the baseline EPW,

Repw=meand(Tmax,d)meand(Tmin,d), R_{epw}=\operatorname{mean}_d(T_{max,d})- \operatorname{mean}_d(T_{min,d}),

rather than the difference between one absolute monthly maximum and minimum. The hourly transformation remains

T=T+ΔT+α(TTepw), T'=T+\Delta\bar{T}+\alpha(T-\bar{T}_{epw}),

where alpha is the future-minus-reference DTR change divided by RepwR_{epw}. A month with missing extrema, a non-finite factor, or Repw0.1CR_{epw}\leq0.1\ ^\circ\mathrm{C} uses shift for that month and records the fallback in factors/diagnostics.

The default 72-hour cyclic transition is centered on every month boundary, including December–January. A constrained smoothstep system retains the original monthly mean of each factor. Combined temperature also corrects the baseline-temperature/time-varying-alpha covariance; precipitation is excluded from smoothing so monthly totals and the baseline wet-hour mask remain intact.

For state-based humidity, the baseline EPW dry bulb, RH, and station pressure are converted to hourly specific humidity. The monthly future-minus-reference huss change is applied before RH and dew point are recovered using morphed temperature and pressure. Saturation clipping enforces 0 <= RH <= 100 and Tdew <= Tdb. The "auto" source decision is made once for a complete case, so future and reference data never mix HUSS and HURS paths.

The enhanced short-wave chain integrates solar geometry at one-minute steps over each EPW interval to recalculate extraterrestrial horizontal and direct normal irradiation (N10/N11). RBL 2010 partitions morphed GHI into DHI and DNI; the result is bounded, non-negative, and closed against the effective solar projection. Pérez 1990 then recalculates N16–N19 illuminance/luminance, with night values and EPW sentinels handled explicitly.

When enabled, snd is converted from CMIP metres to EPW centimetres and scales the existing EPW snow-depth sequence by the future/reference monthly ratio. Zero reference snow or a baseline without snow does not synthesize new snow events. Final writing derives header updates from the persisted hourly Parquet artifact: ground temperatures use Kusuda–Achenbach, typical/extreme periods use complete rolling seven-day windows and hemisphere-aware seasons, and design conditions follow the selected drop/preserve policy.

epw_morph_backends()

belcher <- epw_morph_backend("belcher")
belcher$label
belcher$required_variables()
belcher$methods()
belcher$rules()

The backend rules are the contract between planning and execution. Each rule declares a backend step, the EPW weather field it produces or checks, required and optional CMIP variables, method choices, and whether the step is derived from other backend outputs. preview_plan(), summarise_baseline(), and epw_morph_variables() read these rules; they do not keep a separate hard-coded list of Belcher fields.

Use method overrides when the algorithm is still Belcher but you want a different transformation for selected primary variables:

belcher_shift_recipe <- epw_morph_recipe(
    "belcher",
    methods = c(
        tdb = "shift",
        rh = "shift"
    )
)

morpher <- epw_morpher(
    store = store,
    epw = epw,
    site_id = "SIN",
    recipe = belcher_shift_recipe,
    label = "Singapore baseline"
)

The override names must be backend step names. For the built-in Belcher backend, the primary overrideable steps are tdb, rh, p, hor_ir, glob_rad, and wind; allowed values are "shift", "stretch", and "combined", with "auto" additionally available for tdb. Precipitation is handled by a dedicated conservative step that preserves the baseline wet-hour timing. Derived steps such as dew point, direct normal radiation, and precipitation rate are declared in the rules but are not independent method overrides.

Adding a Backend

Register a new backend when the variable requirements, output fields, or execution algorithm change. A backend is an EpwMorphBackend R6 object with a rule table and a runner function. The built-in "belcher" backend is just the first registration; it is not a special execution path.

The runner receives one canonical context:

  • context$inputs: role-addressable future-weather inputs. Its weather_template, observed_reference, model_historical, and model_future properties remain distinct even when one role is absent;
  • context$epw: the package’s internal baseline EPW representation;
  • context$climate: store-native climate rows with variable_id, time, period, year, lon, lat, dist, units, value, and case metadata;
  • context$reference_climate: optional matching historical model rows;
  • context$observed_reference: optional observations or reanalysis rows;
  • context$recipe: the selected recipe, including method overrides and rules;
  • context$by, context$case, context$strict, and context$warning.

context$epw, context$climate, and context$reference_climate remain available for existing backends. New components should select inputs by their role through context$inputs instead of interpreting a generic reference.

Future-weather methods can be assembled from seven ordered component stages. Each component declares its required and optional input roles, supported representations, frequencies, calendars, variable alternatives, input and output kinds, dimensional scope, and whether it is stochastic. Compatibility is checked from stage order and the intermediate kinds exchanged between components.

Component Used for Main function Problem addressed
preprocess Preparing every source before climate calculations Normalize variables, units, missing values, spatial selections, and source metadata into method-ready inputs Raw EPW, observations, reanalysis, and climate-model data otherwise have incompatible schemas and units
calendar Placing sources on a shared annual and temporal coordinate Preserve each source calendar, calculate annual phase, form circular windows, and align 360-, 365-, and 366-day inputs Direct date matching either loses days or assigns different seasonal positions to nominally similar dates
signal Estimating or transferring the future climate signal Apply delta, scaling, quantile-based, weather-typing, or other statistical transformations to aligned groups Different methods require different reference inputs and assumptions but need one validated, comparable execution contract
sequence Constructing the order of future days or weather states Preserve, resample, rearrange, or generate daily sequences, including stochastic and analogue methods A climatology or corrected distribution does not define event order, persistence, spells, or compound extremes
hourly Converting daily targets or selected days into hourly weather Reconstruct 24-hour profiles from an EPW template, an hourly library, or a method-specific disaggregation model Daily CMIP data cannot be written directly as an hourly EPW and naive interpolation damages diurnal shape and extrema
physics Closing related weather variables after transformation Enforce temperature statistics, humidity closure, radiation balance, bounds, and cross-variable consistency Independently transformed variables can violate thermodynamic, radiative, or EPW constraints
output Producing the final weather artifact and its record Assemble fields and headers, write EPW or intermediate artifacts, and retain method settings, provenance, and diagnostics Numerically valid series are not yet a standards-compliant, reproducible, and auditable future weather file

These components run inside the existing high-level workflow rather than replacing it. shift_request(), collection, download, and extraction obtain and persist the source data. shift_morph() builds the four role-addressable inputs, compiles the component names stored in the selected recipe, and executes the scientific stages through the backend. shift_epw() remains the durable final file writer. The built-in daily_temperature() and sobie_curry_daily() methods use all seven stages, including explicit sequence components that preserve the baseline EPW day order. The ek_daily_temperature() method uses the same seven-stage execution boundary. arima_temperature() also uses all seven stages, but its calendar component forms native-calendar monthly distributions and its signal component requires both historical model output and a separate observed reference.

Bias-adjustment implementations belong to the signal stage. Their common apply operation validates the role-addressable inputs, resolves variable-specific settings, and executes aligned groups. Each method supplies only an apply_group kernel receiving the group’s input payloads, resolved settings, and key. Published defaults retain their references, experimental defaults are labelled and warned about, and user overrides do not change that provenance. The execution result records the settings actually used after overrides. Calendar mapping and group alignment happen before the kernel, so a method cannot silently impose a Gregorian, no-leap, or fixed 366-day interpretation. Group failures either abort or return an explicit diagnostic with a NULL result; they are never silently converted to missing numeric values.

Eight package-native standalone signals currently use this contract:

Registry name Correction Output backbone Variable-profile evidence
linear_scaling_daily Monthly observed-minus-model mean bias, additive for temperature and multiplicative for precipitation model_future Published defaults for tas, tasmin, tasmax, and pr
delta_change_daily Monthly historical-to-future mean change, additive for temperature and multiplicative for precipitation observed_reference Published defaults for tas, tasmin, tasmax, and pr
quantile_mapping_daily Fobs1(Fhist(xfuture))F^{-1}_{obs}(F_{hist}(x_{future})) in calendar-neutral circular daily windows model_future Published method-variable profiles for tas and pr; implementation-selected defaults for hurs, psl, rlds, sfcWind, tasmin, and tasmax remain experimental
quantile_delta_mapping_daily Transfer xfutureFhist1(p)x_{future} - F^{-1}_{hist}(p) or xfuture/Fhist1(p)x_{future} / F^{-1}_{hist}(p) onto Fobs1(p)F^{-1}_{obs}(p), where p=Ffuture,t(xfuture)p = F_{future,t}(x_{future}) model_future Published absolute-change temperature and relative-change precipitation profiles; implementation-selected defaults for other supported variables remain experimental
equidistant_cdf_matching_daily Apply xfuture+Fobs1(p)Fhist1(p)x_{future} + F^{-1}_{obs}(p) - F^{-1}_{hist}(p), where p=Ffuture(xfuture)p = F_{future}(x_{future}), using the Li et al. parametric distributions model_future Li et al. published monthly profiles for tas and pr; native calendar-month daily pooling is an experimental epwshiftr adaptation
scaled_distribution_mapping_daily Parametric distribution and recurrence-interval scaling, absolute for temperature and relative for precipitation model_future Published profiles for tas and pr; applying the Normal branch to tasmin and tasmax remains experimental
cdf_transform_daily Construct Fobs,future(x)=Fobs,hist(Fmodel,hist1(Fmodel,future(x)))F_{obs,future}(x) = F_{obs,hist}(F^{-1}_{model,hist}(F_{model,future}(x))), then quantile match the future-model sequence to that target CDF model_future Published daily profiles for pr, tas, tasmin, tasmax, rsds, and sfcWind
isimip3basd_daily Generate pseudo-future observations by transferring modeled quantile changes, then map the future-model distribution onto the pseudo-future target model_future Published ISIMIP3BASD 3.0.x profiles for eight direct variables and the three prsnratio, tasrange, and tasskew reconstruction components

quantile_mapping_daily uses linear empirical-CDF interpolation with average-rank ties, type-7 inverse quantiles, and endpoint clamping. Its default 31-day window is selected by canonical annual phase, so 360-, 365-, and 366-day source calendars are not paired by nominal date. Precipitation uses a mixed dry-day/positive-amount hurdle distribution rather than the continuous mapping unchanged. Dry-day randomization is controlled by a recorded seed and a fixed method-local generator, leaving R’s global random-number state untouched. Bounds, sample coverage, tail use, clipping, dry-day frequencies, and the resolved stochastic settings are retained in result provenance.

quantile_delta_mapping_daily differs from ordinary Quantile Mapping by estimating each projected value’s probability from a time-dependent future distribution. Its default calendar-neutral 91-day seasonal window represents the published three-month pool, while a symmetric 31-year future window makes the publication’s 30-year moving-period calculation executable on whole model years. Temperature transfers absolute changes in quantiles and precipitation transfers relative changes. Precipitation values at or below the published 0.05 mm/day trace threshold are replaced by deterministic positive uniforms before adjustment and censored back to zero afterward. Window sample counts, transferred changes, clipping, censoring, and effective seeds are retained in provenance.

equidistant_cdf_matching_daily implements the additive equation published by Li et al. (2010): xfuture=xfuture+Fobs1(p)Fhist1(p)x'_{future} = x_{future} + F^{-1}_{obs}(p) - F^{-1}_{hist}(p), where p=Ffuture(xfuture)p = F_{future}(x_{future}). Cannon et al. (2015) showed that this equation is mathematically equivalent to the absolute form of Quantile Delta Mapping. The component remains separate because it preserves the Li distribution model: temperature uses four-parameter Beta distributions whose observed range is extended by half a sample standard deviation, while precipitation uses a point mass at zero plus a zero-location Gamma distribution for wet values. The original method was applied to monthly fields, not daily series. epwshiftr therefore marks its native CF-calendar month pooling of daily values as an experimental frequency adaptation rather than a published daily default. Negative adjusted precipitation values are clipped to zero. Fitted parameters, monthly sample coverage, probability clamping, dry counts, clipping, and this frequency provenance remain inspectable in the result.

scaled_distribution_mapping_daily instead fits the parametric distributions and recurrence-interval changes from Switanek et al. (2017) within each calendar month. Temperature is linearly detrended, fitted with Normal distributions, adjusted through two-tailed recurrence intervals, and restored with the projected trend and observed-minus-historical model mean bias. Precipitation values below 0.1 mm/day are dry; positive amounts use zero-location Gamma maximum-likelihood fits, multiplicative quantile scaling, one-tailed recurrence intervals, and the published expected wet-day equation. The projected period is divided into disjoint 10-year output blocks fitted on surrounding 30-year windows where data exist. Edge truncation, distribution parameters, sample coverage, corrected wet counts, and the method’s inability to turn additional dry projected days into wet days are explicit in result provenance. Native 360-, 365-, and 366-day calendars contribute samples to their own calendar months without Gregorian date coercion.

cdf_transform_daily differs from both ordinary Quantile Mapping and QDM by first estimating a complete future observed-reference CDF from the historical observed/model relation and the future-model CDF. The implementation aligns the historical and future model samples by the same observed-minus-historical-model mean shift used in the method authors’ R package. It then evaluates the chained empirical CDF on an explicit grid, extends unsupported tails through the published constant-correction rule, and uses the left endpoint of CDF plateaus as the generalized inverse. Following the Famien et al. (2018) daily Africa application, each native calendar month is fitted in a 17-year future window and writes only its central 9-year block. Those numbers are application defaults rather than requirements of the original CDF-t transformation. Famien et al. do not specify how to supply the four-year flanks at the start and end of a requested series, so epwshiftr truncates unavailable flanks and records this package-selected edge policy explicitly. For precipitation, Singularity Stochastic Removal replaces sub-threshold values with deterministic uniforms below 10810^{-8} kg m-2 s-1 before CDF-t and returns adjusted sub-threshold values to zero afterward. The generator and effective role-specific seeds are recorded without changing R’s global random state.

isimip3basd_daily implements the daily marginal bias-adjustment stage from Lange (2019) with the official ISIMIP3BASD 3.0.x variable configuration. It first transfers the modeled historical-to-future change at common quantiles onto historical observations to form a pseudo-future target, then maps the future-model sequence to that target. Variable profiles select additive, multiplicative/mixed, or bounded change transfer; Normal, Gamma, Weibull, or empirical distribution mapping; physical threshold frequencies; optional annual-mean detrending; snow-ratio missing-value imputation; and the short-wave upper-bound climatology. Short-wave rsds bounds apply to its dimensionless upper-bound fraction; the final result is rescaled to the future physical radiation magnitude. The registered direct variables are hurs, pr, ps, psl, rlds, rsds, sfcWind, and tas. prsnratio, tasrange, and tasskew are adjusted as published intermediate components; reconstructing prsn, tasmin, and tasmax from them remains a separate physics-stage operation rather than three independent marginal adjustments.

The original software requires a proleptic Gregorian time coordinate. epwshiftr instead assigns each native 360-, 365-, or 366-day observation to a calendar-neutral annual phase and applies the published 31-day circular window on a common 365-day target grid. This calendar adaptation, every effective seed, parameter fit or empirical fallback, transferred bound frequency, detrending decision, short-wave upper-bound cycle, and clipping count are retained in result provenance. The component implements marginal bias adjustment only; the ISIMIP MBCnSD spatial downscaling workflow is a separate stage and is not implied by selecting this signal.

The runner returns epw_morph_result() with complete hourly EPW weather data. EpwMorpher$run() writes that data as Parquet and adds the case metadata columns needed by write_epw().

Rule tables must include step, epw_field, method, and required. They can use either the compact scalar columns variable_id and optional_variable_id or list columns required_variables and optional_variables. Use a method_choices list column when one step supports a narrower set of method values than the backend as a whole.

This minimal backend copies the baseline EPW and offsets dry-bulb temperature. It is deliberately simple: a real backend would read context$climate, validate its required variables, and calculate weather fields from those rows.

offset_rules <- data.table::data.table(
    step = "dry",
    epw_field = "dry_bulb_temperature",
    variable_id = "tas",
    optional_variable_id = NA_character_,
    method = "plus_one",
    required = TRUE,
    derived = FALSE,
    method_choices = list(c("plus_one", "plus_two"))
)

offset_runner <- function(context, backend) {
    epw <- context$epw$clone()
    suppressMessages(epw$drop_unit())

    weather <- data.table::as.data.table(epw$data())
    amount <- switch(
        context$recipe$methods[["dry"]],
        plus_two = 2,
        plus_one = 1
    )
    weather[, dry_bulb_temperature := dry_bulb_temperature + amount]

    epw_morph_result(context, epw = epw, data = weather)
}

offset_backend <- EpwMorphBackend$new(
    name = "constant_offset",
    label = "Constant dry-bulb offset",
    methods = c(dry = "plus_one"),
    method_choices = c("plus_one", "plus_two"),
    rules = offset_rules,
    runner = offset_runner
)

epw_morph_register_backend("constant_offset", offset_backend)

offset_recipe <- epw_morph_recipe(
    "constant_offset",
    backend = "constant_offset",
    methods = c(dry = "plus_two")
)

epw_morph_variables(offset_recipe)

Use the recipe exactly like the built-in one:

morpher <- epw_morpher(
    store = store,
    epw = epw,
    site_id = "SIN",
    recipe = offset_recipe,
    label = "Singapore baseline"
)

Prefer registering a new backend name over overwriting "belcher". Use overwrite = TRUE only for interactive experiments where replacing an existing registration is intentional.

Preflight Before Writing

Preflight checks extraction coverage and baseline readiness without modifying store state.

diagnostics <- morpher$preflight(
    plan_id = region$plan_id,
    periods = periods,
    reference_plan_id = reference_region$plan_id,
    reference_periods = reference_periods,
    strict = TRUE
)

diagnostics

Blocking diagnostics should be fixed before planning. Common causes are missing required variables, incomplete monthly coverage, missing baseline EPW fields, or using periods that are not covered by the extracted climate data.

Summarise Climate and Baseline

Morphing uses monthly summary statistics. The climate summary comes from store extraction outputs; the baseline summary comes from the EPW file.

climate <- morpher$summarise_climate(
    plan_id = region$plan_id,
    periods = periods,
    strict = TRUE,
    overwrite = FALSE
)

reference_climate <- morpher$summarise_climate(
    plan_id = reference_region$plan_id,
    periods = reference_periods,
    strict = TRUE,
    overwrite = FALSE
)

baseline <- morpher$summarise_baseline(overwrite = FALSE)

unique(climate$summary_id)
unique(reference_climate$summary_id)
unique(baseline$baseline_id)

The summary IDs are stable for the selected extraction plans, periods, baseline, and recipe. Reusing them lets you preview or rerun later stages without re-extracting climate data.

Preview and Create a Morphing Plan

preview_plan() calculates plan rows, factor rows, and diagnostics without writing them. plan() persists the selected plan and factor rows.

preview <- morpher$preview_plan(
    summary_id = unique(climate$summary_id),
    reference_summary_id = unique(reference_climate$summary_id),
    baseline_id = unique(baseline$baseline_id),
    by = c("source_id", "experiment_id", "variant_label", "period"),
    strict = TRUE
)

preview$plan
preview$factors
preview$diagnostics

plan <- morpher$plan(
    summary_id = unique(climate$summary_id),
    reference_summary_id = unique(reference_climate$summary_id),
    baseline_id = unique(baseline$baseline_id),
    by = c("source_id", "experiment_id", "variant_label", "period"),
    strict = TRUE
)

The by columns define morphing cases. The default creates one case per model, experiment, member, and period. Add or remove grouping columns only when those columns exist in the climate summary and reflect the cases you intend to write.

Diagnose and Run

Run diagnostics before executing strict plans.

morph_id <- plan$morph_id[[1L]]

morpher$diagnose(morph_id)
morpher$check(morph_id)

results <- morpher$run(
    morph_id,
    overwrite = FALSE,
    resume = TRUE
)

run() writes hourly morphed weather as Parquet artifacts under the store. It does not write EPW text files yet.

Write EPW Files

Write EnergyPlus Weather files after morphing succeeds.

outputs <- morpher$write_epw(
    morph_id,
    dir = "outputs/future-epw",
    separate = TRUE,
    overwrite = FALSE,
    resume = TRUE
)

morpher$status(morph_id)
morpher$outputs(morph_id)

The high-level shift_epw() function wraps this step and returns a ShiftOutputs stage. Use shift_outputs() and shift_data() when you do not need lower-level manifest IDs.

For the enhanced profile, final EPW writing uses the persisted hourly Parquet artifact to recalculate ground temperatures and six hemisphere-aware typical/extreme periods. Stale design conditions are written as DESIGN CONDITIONS,0; set the corresponding belcher_options() policies to "preserve" when the original headers must remain untouched.

One-Call Workflow

For most scripts, use the task-oriented API and supply matching historical CMIP6 data when they are available. epwshiftr resolves the store stages, EPW writing, and final export path:

run <- shift_future_epw(
    epw = epw,
    climate = shift_cmip6(
        model = "BCC-CSM2-MR",
        scenarios = c("ssp126", "ssp585")
    ),
    periods = list(`2060s` = 2055:2065),
    method = belcher(
        reference = historical_reference(1995:2014)
    ),
    dir = "~/Downloads/epwshiftr-test"
)

shift_status(run)
shift_outputs(run)
shift_missing(run)

If no suitable historical reference data exist, belcher() is the fallback: it uses the EPW climatology directly and does not infer a historical request.

When you already have lower-level extraction plan IDs and need direct control of the morphing engine, use workflow().

result <- morpher$workflow(
    plan_id = region$plan_id,
    periods = periods,
    reference_plan_id = reference_region$plan_id,
    reference_periods = reference_periods,
    by = c("source_id", "experiment_id", "variant_label", "period"),
    strict = TRUE,
    dir = "outputs/future-epw",
    overwrite = FALSE,
    resume = TRUE
)

names(result)

For most users, the equivalent high-level path is still clearer:

plan <- shift_future_epw(
    epw = epw,
    climate = shift_cmip6(
        model = "BCC-CSM2-MR",
        scenarios = c("ssp126", "ssp585")
    ),
    periods = list(`2060s` = 2055:2065),
    method = belcher(
        reference = historical_reference(1995:2014)
    ),
    dir = "~/Downloads/epwshiftr-test",
    store = "~/cmip6-singapore-store",
    dry_run = TRUE
)

shift_explain(plan)
run <- shift_run(plan)

Relationship to Other Layers