required_packages <- c("jsonlite", "ggplot2")
missing_packages <- required_packages[!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)]
if (length(missing_packages) > 0) {
stop("Missing package(s): ", paste(missing_packages, collapse = ", "), call. = FALSE)
}Step 5 - Exogenous Transition
2 Step 5: IOT Load + Exogenous Transition (Advanced/Optional)
2.1 Objective
Run an exogenous energy transition experiment where green/brown targets are imposed and closure rules determine how the rest of the economy adjusts.
2.2 Why this step matters
This step teaches closure logic explicitly: different assumptions can produce different sector-adjustment paths even under the same headline growth targets.
2.3 What changed from Step 4
- Transition is now imposed exogenously (targets), not generated endogenously.
- Closure options are made explicit and compared.
- This is advanced/optional and intended as a methodological deep dive.
2.4 Equations
Economic question: with fixed aggregate and sector targets, which additional closure assumptions are needed to produce a consistent IO path?
Core yearly consistency problem: - Technology update from base transactions and output guess: A_t = Z_base / x_t - IO consistency: x_t = (I - A_t)^(-1) f_t - Exogenous targets: g_green = eps_green, g_brown = eps_brown, aggregate g_target - Closure decides how remaining sectors/final demand absorb residual adjustment.
Underdetermination (equation counting intuition): - Imposing two energy targets plus one aggregate growth target usually leaves more unknown sector adjustments than independent constraints. - Therefore extra closure assumptions are required to select one feasible path.
Closure options (economic interpretation): - residual-others: non-target sectors absorb remaining adjustment. - fixed-others: non-target sectors are held close to baseline and demand adjusts elsewhere. - uniform-demand: adjustment spreads broadly across demand components. - eps-only: follow only sector target rates with minimal additional balancing restrictions.
Symbol glossary: - eps_green, eps_brown: imposed sector growth rates. - g_target: aggregate growth target. - g_other: implied adjustment for non-target sectors. - rel_io_resid: numerical consistency residual from iterative solve.
Model status in Step 5: - Exogenous: target growth rates and closure choice. - Endogenous: sector outputs and implied residual growth allocation. - Calibrated: base IO structure from loaded IOT.
Reference note: closure sensitivity emphasis follows structural-transition SFC-IO work such as Pettena & Raberto (2025).
2.5 TFM (Step 5)
| Flow | Households | Government | Production (IOT aggregate) | Sum |
|---|---|---|---|---|
Income Y |
+Y |
0 |
-Y |
0 |
Taxes T |
-T |
+T |
0 |
0 |
Consumption C |
-C |
0 |
+C |
0 |
Government demand G |
0 |
-G |
+G |
0 |
Change in wealth/debt Delta_V (= Delta_B) |
-Delta_V |
+Delta_B |
0 |
0 |
| Column sum | 0 | 0 | 0 | 0 |
From column sums: - Delta_V = YD - C. - Delta_B = G - T. - Y = C + G. - Delta_V = Delta_B.
Step-5 interpretation: - Exogenous transition targets and closure options change the sector allocation behind production, while these compact institutional stock-flow identities stay the same. - A fully sector-expanded TFM would show the target sectors and residual sectors explicitly.
2.6 BSM (Step 5)
| Stock | Households | Government | Production (IOT aggregate) | Sum |
|---|---|---|---|---|
Wealth/debt stock V (=B) |
+V |
-B |
0 |
0 |
Net worth NW |
+NW_h |
+NW_g |
+NW_p |
0 |
Net-worth identities: - NW_h = V - NW_g = -B - NW_p = 0 (in this minimal closure, production has no autonomous financial stock) - with V = B, NW_h + NW_g + NW_p = 0.
Interpretation for Step 5: - Closure rules change how output adjusts across production sectors. - They do not add new institutional financial assets/liabilities in this minimal setup. - So production appears explicitly as a sector in BSM, with net worth set to zero by construction.
Define Step 5 exogenous-transition function.
2.7 Algorithm (pseudo-code)
- Choose closure option and sector targets (
eps_green,eps_brown,g_target). - For each year, solve IO consistency under that closure.
- Record sector outputs and implied residual adjustments.
- Compare alternative closures under identical targets.
Pseudo-code: each year call the closure solver, enforce exogenous brown/green rates, and store GDP plus energy-sector outputs.
closure_file <- if (file.exists("closure_utils.R")) "closure_utils.R" else "../closure_utils.R"
source(closure_file)
simulate_iot_exogenous_transition <- function(iot,
T = 20,
closure_option = "residual-others",
g_target = 0.01,
eps_green = 0.04,
eps_brown = -0.04,
max_iter = 20) {
idx <- find_energy_indices(iot$sector_codes)
x_prev <- as.numeric(iot$x0)
F_prev <- as.numeric(iot$F0)
years <- iot$base_year + seq_len(T) - 1
out <- data.frame(
year = years,
closure = closure_option,
GDP = NA_real_,
X_green = NA_real_,
X_brown = NA_real_,
g_green = NA_real_,
g_brown = NA_real_,
g_other = NA_real_,
io_resid = NA_real_
)
for (tt in seq_len(T)) {
sol <- solve_io_consistency(
Z_base = iot$Z0,
F_prev = F_prev,
x_init = x_prev,
diag_mat = diag(iot$n),
option = closure_option,
eps_R = eps_green,
eps_N = eps_brown,
g = g_target,
idx_ren = idx$idx_green,
idx_nren = idx$idx_brown,
p_out_ren = 1,
p_out_nren = 1,
va_coeff = iot$va_coeff,
target = "output",
max_iter = max_iter,
rel_io_tol = 1e-8
)
x_now <- pmax(as.numeric(sol$X), 0)
F_now <- pmax(as.numeric(sol$F), 0)
out[tt, c("GDP", "X_green", "X_brown", "g_green", "g_brown", "g_other", "io_resid")] <- c(
sum(iot$va_coeff * x_now),
x_now[idx$idx_green],
x_now[idx$idx_brown],
sol$g_R,
sol$g_N,
sol$g_O,
sol$rel_io_resid
)
x_prev <- x_now
F_prev <- F_now
}
out
}Load IOT for this advanced step.
Use the same core configuration machinery and cached data path as Step 2.
step5_T <- if (interactive()) 12 else 20
step5_max_iter <- if (interactive()) 12 else 20
if (!exists("core_iot", inherits = FALSE)) {
core_config <- make_core_config()
core_iot <- download_or_load_iot(core_config)
}
iot <- core_iot
c(country = iot$country, year = iot$base_year, n_sectors = iot$n) country year n_sectors
"AT" "2020" "64"
Run baseline closure path.
Use baseline closure and fixed aggregate growth target. Store GDP and energy-sector paths as the comparison baseline.
base_closure <- "residual-others"
base_g_target <- 0.01
base_eps_green <- 0.04
base_eps_brown <- -0.04
step5_baseline <- simulate_iot_exogenous_transition(
iot,
T = step5_T,
closure_option = base_closure,
g_target = base_g_target,
eps_green = base_eps_green,
eps_brown = base_eps_brown,
max_iter = step5_max_iter
)Core play: switch closure option.
Run identical targets under a different closure rule and compare GDP path response.
closure_options <- c("residual-others", "fixed-others", "uniform-demand", "eps-only")
closure_play <- closure_options[3] # @exercise[id=step5_closure;kind=core;question_expr=closure_options[3];prompt="Switch closure option and inspect GDP and green/brown paths";hint="Compare fixed-others vs uniform-demand"]
step5_closure_play <- simulate_iot_exogenous_transition(
iot,
T = step5_T,
closure_option = closure_play,
g_target = base_g_target,
eps_green = base_eps_green,
eps_brown = base_eps_brown,
max_iter = step5_max_iter
)Plot closure comparison.
Overlay GDP paths to isolate closure sensitivity.
par(mfrow = c(1, 2), mar = c(4, 4, 3, 1), cex.main = 0.9)
y_gdp <- range(c(step5_baseline$GDP, step5_closure_play$GDP), na.rm = TRUE)
ratio_base <- step5_baseline$X_brown / pmax(step5_baseline$X_green, 1e-9)
ratio_play <- step5_closure_play$X_brown / pmax(step5_closure_play$X_green, 1e-9)
y_ratio <- range(c(ratio_base, ratio_play), na.rm = TRUE)
if (!all(is.finite(y_gdp))) stop("Non-finite GDP values in Step 5 core plot.", call. = FALSE)
if (!all(is.finite(y_ratio))) stop("Non-finite brown/green ratio in Step 5 core plot.", call. = FALSE)
if (y_gdp[1] == y_gdp[2]) y_gdp <- y_gdp + c(-1, 1) * max(1, 0.05 * abs(y_gdp[1]))
if (y_ratio[1] == y_ratio[2]) y_ratio <- y_ratio + c(-1, 1) * max(1, 0.05 * abs(y_ratio[1]))
plot(step5_baseline$year, step5_baseline$GDP, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "GDP (million EUR)", ylim = y_gdp, main = "Step 5 Core: GDP by closure")
lines(step5_closure_play$year, step5_closure_play$GDP, lwd = 2, col = "firebrick")
legend("topleft", legend = c(paste0("base (", base_closure, ")"), paste0("play (", closure_play, ")")),
col = c("steelblue", "firebrick"), lty = 1, bty = "n", cex = 0.8)
plot(step5_baseline$year, ratio_base,
type = "l", lwd = 2, col = "steelblue", ylim = y_ratio,
xlab = "year", ylab = "X_brown / X_green", main = paste0("Step 5 Core: mix (base ", base_closure, ")"))
lines(step5_closure_play$year, ratio_play, lwd = 2, col = "firebrick")par(mfrow = c(1, 1))Optional play: strengthen exogenous transition rates.
Change green/brown target rates and inspect the brown/green output ratio path.
eps_green_play <- 0.02 # @exercise[id=step5_eps;kind=optional;question_expr=0.02;prompt="Change exogenous green growth target and compare brown/green ratio";hint="Use eps_brown = -eps_green"]
eps_brown_play <- -eps_green_play
step5_eps_play <- simulate_iot_exogenous_transition(
iot,
T = step5_T,
closure_option = base_closure,
g_target = base_g_target,
eps_green = eps_green_play,
eps_brown = eps_brown_play,
max_iter = step5_max_iter
)
ratio_base <- step5_baseline$X_brown / pmax(step5_baseline$X_green, 1e-9)
ratio_play <- step5_eps_play$X_brown / pmax(step5_eps_play$X_green, 1e-9)
y_ratio <- range(c(ratio_base, ratio_play), na.rm = TRUE)
if (!all(is.finite(y_ratio))) stop("Non-finite brown/green ratio in Step 5 optional plot.", call. = FALSE)
if (y_ratio[1] == y_ratio[2]) y_ratio <- y_ratio + c(-1, 1) * max(1, 0.05 * abs(y_ratio[1]))
plot(step5_baseline$year, ratio_base, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "X_brown / X_green", ylim = y_ratio,
main = paste0("Step 5 Optional: base eps_g=", base_eps_green))
lines(step5_eps_play$year, ratio_play, lwd = 2, col = "darkgreen")
legend("topright", legend = c(paste0("base (eps_g=", base_eps_green, ")"), paste0("play (eps_g=", eps_green_play, ")")),
col = c("steelblue", "darkgreen"), lty = 1, bty = "n", cex = 0.85)2.8 Insight (Step 5)
- Under the same headline targets, closure choices redistribute adjustment differently across sectors.
- Report both GDP and sector-mix outcomes; aggregate growth alone hides structural burden sharing.
2.9 What can go wrong / interpretation caveat
- Some closure/target combinations may be numerically feasible but economically implausible.
- Always inspect residuals and sector trajectories, not only headline GDP.
2.10 Interpretation
Closure choice and exogenous energy targets change the sectoral adjustment path even when aggregate growth is fixed.