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 4 - SIM+IOT+RoW
2 Step 4: SIM+IOT+RoW
2.1 Objective
Open the calibrated SIM-IOT baseline by adding exports, import leakage, and trade-balance accounting in a minimal SIM+IOT+RoW closure.
2.2 Why this step matters
Many ecological-economy questions are about demand leakage, external constraints, and trade-offs between domestic growth and external balance. This step introduces that logic without full MRIO complexity.
2.3 What changed from Step 3
- Added explicit export demand and import leakage.
- Added trade-balance and net-foreign-asset accounting bridge.
- Kept the Step 3 domestic stock-flow identities unchanged.
2.4 Equations
Economic question: how does introducing external demand and import leakage change macro outcomes and accounting closure?
Closed-to-open decomposition: - Domestic final demand by sector: FD_dom,i,t = C_i,t + G_i,t + I_i + EX_i,t - Import leakage: IM_i,t = m_i * FD_dom,i,t - Net domestic demand: FD_net,i,t = FD_dom,i,t - IM_i,t - Production mapping: x_t = L * FD_net,t - Trade totals: EX_t = sum_i EX_i,t, IM_t = sum_i IM_i,t, TB_t = EX_t - IM_t
Macro block (unchanged domestic SFC core): - Y_t = sum_i(v_i * x_i,t), T_t = tau_y * Y_t, YD_t = Y_t - T_t - C_t = c_y * YD_t + c_v * V_{t-1} - V_t = V_{t-1} + YD_t - C_t, B_t = B_{t-1} + (G_t - T_t)
Symbol glossary: - EX_i,t: exogenous export demand by sector. - m_i: import propensity applied to domestic final demand. - FD_net: demand met by domestic production technology. - TB_t: trade balance in period t. - Delta_NFA_t: net foreign asset change, here equal to TB_t.
Model status in Step 4: - Exogenous: export path assumptions, import leakage scale scenario. - Endogenous: x, Y, TB, V, B paths. - Calibrated: domestic IO/SIM parameters inherited from Step 2 calibration.
2.5 What imports are counted here in this tutorial?
- In this SIM+IOT+RoW implementation, imports are modeled as proportional leakage from domestic final-demand uses.
- With total-use national IO tables, some imported content is already embedded in observed technical structure; interpretation must be explicit.
- Full MRIO would separate foreign technology coefficients and bilateral origins; this step intentionally does not.
2.6 What is import leakage here, and where does m_i come from?
- Definition in this tutorial:
IM_i,t = m_i * FD_dom,i,t, som_iis the share of final demand for sectorithat leaks to imports. - Calibration source:
m_icomes from Step 2 IOT calibration using the imports row (P7/IMP) asm_i = IM_i,0 / x_i,0(bounded to keep simulation stable). - Why this choice: it is a transparent, data-available proxy in national IO tables when we do not estimate a full bilateral MRIO import-demand system.
- Scenario lever:
import_leakage_scalemultiplies calibratedm_ito test lower/higher leakage assumptions.
Reference note: this is a pedagogical open-economy bridge, not a full global MRIO closure.
2.7 TFM (SIM+IOT+RoW extension)
| Flow | Households | Government | Production | RoW | Sum |
|---|---|---|---|---|---|
Exports EX |
0 |
0 |
+EX |
-EX |
0 |
Imports IM |
0 |
0 |
-IM |
+IM |
0 |
Change in net foreign assets Delta_NFA |
0 |
0 |
-Delta_NFA |
+Delta_NFA |
0 |
| Column sum | 0 | 0 | 0 | 0 | 0 |
From column sums: - Domestic external account (production proxy): EX - IM - Delta_NFA = 0, so Delta_NFA = TB = EX - IM. - RoW account: -EX + IM + Delta_NFA = 0.
Aggregate-to-sector interpretation: - Each aggregate flow is a sum over sector flows: EX = sum_i EX_i, IM = sum_i IM_i. - A fully sector-expanded open-economy TFM would keep separate production columns by sector and bilateral import/export lines; the table above is the compact external-account projection.
2.8 BSM (SIM+IOT+RoW implication)
| Stock | Households | Government | Production | RoW | Sum |
|---|---|---|---|---|---|
Wealth/debt stock V (=B) |
+V |
-B |
0 |
0 |
0 |
Net foreign assets NFA |
0 |
0 |
+NFA |
-NFA |
0 |
Net worth NW |
+NW_h |
+NW_g |
+NW_p |
+NW_row |
0 |
Net-worth identities (column-sum closure): - NW_h = V - NW_g = -B - NW_p = NFA - NW_row = -NFA - with V = B, NW_h + NW_g + NW_p + NW_row = 0
Sector-expansion interpretation: - This compact BSM explicitly keeps all Step 4 institutional sectors (households, government, production, RoW). - In a full sector-expanded BSM, NW_p could be decomposed across production sectors while aggregate external closure remains identical.
Step 4 adds external demand (EX) and import leakage (IM) while keeping domestic SIM-IOT closures unchanged. Interpretation: this is SIM+IOT+RoW (national table + leakage), not full bilateral MRIO accounting.
2.9 Algorithm (pseudo-code)
- Start from Step 2/3 calibrated domestic structure.
- Add exogenous exports and compute import leakage by sector.
- Run IO mapping on net domestic demand.
- Update macro stocks and flows, then compute
TB = EX - IM. - Compare GDP and TB under alternative leakage/export assumptions.
Define SIM+IOT+RoW simulation helper.
Local equations implemented in the next code block: - FD_dom,i,t = C_i,t + G_i,t + I_i + EX_i,t - IM_i,t = m_eff,i * FD_dom,i,t - FD_net,i,t = FD_dom,i,t - IM_i,t - x_t = L * FD_net,t, Y_t = sum_i(v_i * x_i,t) - TB_t = EX_t - IM_t, with EX_t = sum_i EX_i,t and IM_t = sum_i IM_i,t
Detailed loop logic for simulate_sim_iot_rowlite(): 1. Initialize domestic stocks and base composition vectors. 2. Set exogenous export path EX_i,t and fiscal path G_t. 3. (Optional) update brown/green demand shares if transition speed is positive. 4. Build effective import coefficients m_eff from calibrated m_i and scenario scale. 5. Compute demand solved by domestic production: FD_net = FD_dom - IM. 6. Map FD_net through L to get sector output and aggregate GDP. 7. Update SFC stocks (V, B) and external balance (EX, IM, TB) each period.
Interpretation note: - This function keeps the domestic SFC core unchanged and adds only the external leakage/export channel. - That makes policy sensitivity (imports vs exports) easier to interpret in isolation.
simulate_sim_iot_rowlite <- function(calib,
T = 20,
c_y = 0.82,
c_v = 0.03,
g_G = 0.01,
ex_growth = 0,
import_leakage_scale = 1,
transition_speed = 0.0015) {
years <- calib$base_year + seq_len(T) - 1
out <- data.frame(
year = years,
GDP = NA_real_, TAX = NA_real_, YD = NA_real_, C = NA_real_, G = NA_real_,
V = NA_real_, DEF = NA_real_, B = NA_real_, EX = NA_real_, IM = NA_real_, TB = NA_real_,
betaC_brown = NA_real_, betaC_green = NA_real_
)
V_prev <- calib$V0
B_prev <- calib$B0
G_prev <- calib$G0
x_prev <- as.numeric(calib$L %*% (calib$beta_C * sum(calib$I_i0) + calib$beta_G * calib$G0 + calib$I_i0 + calib$EX_i0))
beta_C <- calib$beta_C
beta_G <- calib$beta_G
for (tt in seq_len(T)) {
G_t <- if (tt == 1) calib$G0 else G_prev * (1 + g_G)
EX_i_t <- calib$EX_i0 * (1 + ex_growth)^(tt - 1)
if (is.finite(calib$idx$idx_green) && is.finite(calib$idx$idx_brown)) {
signal <- (x_prev[calib$idx$idx_green] - x_prev[calib$idx$idx_brown]) / pmax(sum(abs(x_prev)), 1)
shift <- max(0, transition_speed * (1 + signal))
shift <- min(shift, max(beta_C[calib$idx$idx_brown] - 1e-8, 0))
beta_C[calib$idx$idx_brown] <- beta_C[calib$idx$idx_brown] - shift
beta_C[calib$idx$idx_green] <- beta_C[calib$idx$idx_green] + shift
}
m_eff <- pmin(pmax(calib$m_i * import_leakage_scale, 0), 0.99)
S <- 1 - m_eff
k <- as.numeric(t(calib$va_coeff) %*% (calib$L %*% (S * beta_C)))
b <- as.numeric(t(calib$va_coeff) %*% (calib$L %*% (S * (beta_G * G_t + calib$I_i0 + EX_i_t))))
den <- 1 - c_y * (1 - calib$tau_y) * k
C_t <- max((c_y * (1 - calib$tau_y) * b + c_v * V_prev) / den, 0)
fd_dom_t <- beta_C * C_t + beta_G * G_t + calib$I_i0 + EX_i_t
im_i_t <- m_eff * fd_dom_t
fd_net_t <- pmax(fd_dom_t - im_i_t, 0)
x_t <- pmax(as.numeric(calib$L %*% fd_net_t), 0)
Y_t <- sum(calib$va_coeff * x_t)
TAX_t <- calib$tau_y * Y_t
YD_t <- Y_t - TAX_t
V_t <- V_prev + YD_t - C_t
DEF_t <- G_t - TAX_t
B_t <- B_prev + DEF_t
EX_t <- sum(EX_i_t)
IM_t <- sum(im_i_t)
TB_t <- EX_t - IM_t
out[tt, c("GDP", "TAX", "YD", "C", "G", "V", "DEF", "B", "EX", "IM", "TB", "betaC_brown", "betaC_green")] <- c(
Y_t, TAX_t, YD_t, C_t, G_t, V_t, DEF_t, B_t, EX_t, IM_t, TB_t,
if (is.finite(calib$idx$idx_brown)) beta_C[calib$idx$idx_brown] else NA_real_,
if (is.finite(calib$idx$idx_green)) beta_C[calib$idx$idx_green] else NA_real_
)
x_prev <- x_t
V_prev <- V_t
B_prev <- B_t
G_prev <- G_t
}
out
}Build SIM+IOT+RoW calibration and baseline path.
First, print active core configuration so participants can verify the exact data context.
Economic purpose of this check: - Trade and leakage dynamics depend on sector structure and export composition. - If configuration changes (country/year/table), parameter interpretation changes too.
core_config <- make_core_config()
core_config_view <- list(
country = core_config$country,
year = core_config$year,
scope = core_config$scope,
table_type = core_config$table_type,
iot_dataset = iot_schema(core_config$table_type)$dataset_id,
cache_dir = core_config$cache_dir
)
core_config_view$country
[1] "AT"
$year
[1] "2020"
$scope
[1] "TOTAL"
$table_type
[1] "product_by_product"
$iot_dataset
[1] "naio_10_cp1700"
$cache_dir
[1] "data"
How build_sim_iot_row_from_data() extends Step 2 calibration: 1. It keeps all domestic Step 2 calibrated objects (L, va_coeff, shares, tau_y, V0, B0). 2. It adds export vector EX_i0 as the base external-demand anchor. 3. It adds import coefficients m_i needed for RoW leakage dynamics. 4. It preserves energy-sector indices for optional transition overlays.
In the next chunk, we construct calib4 and then run baseline with transition_speed = 0. This keeps Step 4 baseline focused on open-economy effects (exports/imports) before adding any transition mechanism.
if (!exists("calib4", inherits = FALSE)) {
core_iot <- download_or_load_iot(core_config)
core_wealth <- load_wealth_init(core_config)
calib4 <- build_sim_iot_row_from_data(core_iot, core_wealth)
}
base_import_leakage_scale <- 1
base_ex_growth <- 0
step4_baseline <- simulate_sim_iot_rowlite(calib4, T = 20, transition_speed = 0,
import_leakage_scale = base_import_leakage_scale,
ex_growth = base_ex_growth)Print a compact RoW calibration snapshot.
How to interpret this snapshot: - EX0_total sets base external-demand scale. - Mean/max import propensity indicate expected leakage strength and potential external constraint. - V0, B0, tau_y confirm the domestic SFC core remains anchored while opening the economy.
row_snapshot <- list(
base_year = calib4$base_year,
n_sectors = calib4$n,
tau_y = round(calib4$tau_y, 4),
EX0_total_mEUR = round(sum(calib4$EX_i0), 2),
mean_import_propensity = round(mean(calib4$m_i), 4),
max_import_propensity = round(max(calib4$m_i), 4),
V0_mEUR = round(calib4$V0, 2),
B0_mEUR = round(calib4$B0, 2),
monetary_unit = core_config$iot_unit
)
row_snapshot$base_year
[1] 2020
$n_sectors
[1] 64
$tau_y
[1] 0.2332
$EX0_total_mEUR
[1] 501637.7
$mean_import_propensity
[1] 0.2179
$max_import_propensity
[1] 0.95
$V0_mEUR
[1] 562808.8
$B0_mEUR
[1] 230734.9
$monetary_unit
[1] "MIO_EUR"
Core play: import leakage.
This play changes one external parameter: import_leakage_scale. Economic meaning: higher leakage shifts more demand abroad for a given domestic final-demand level. Run this chunk, then compare GDP and TB paths to see how domestic multipliers weaken/strengthen with leakage.
import_leakage_scale_play <- 0.8 # TODO [core:step4_imports] Change import leakage and inspect GDP/trade balance Hint: Above 1 increases leakage, below 1 reduces leakage
step4_core_play <- simulate_sim_iot_rowlite(calib4, T = 20, import_leakage_scale = import_leakage_scale_play)Plot Step 4 core comparison.
How to read the plot pair: - GDP panel: domestic activity response after accounting for leakages. - Trade-balance panel: whether scenario shifts the economy toward external surplus or deficit. - Joint interpretation: a policy/scenario can raise demand but still worsen external balance if import leakage dominates.
par(mfrow = c(1, 2), mar = c(4, 4, 3, 1), cex.main = 0.9)
y_gdp <- range(c(step4_baseline$GDP, step4_core_play$GDP), na.rm = TRUE)
y_tb <- range(c(step4_baseline$TB, step4_core_play$TB), na.rm = TRUE)
if (!all(is.finite(y_gdp))) stop("Non-finite GDP values in Step 4 core plot.", call. = FALSE)
if (!all(is.finite(y_tb))) stop("Non-finite TB values in Step 4 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_tb[1] == y_tb[2]) y_tb <- y_tb + c(-1, 1) * max(1, 0.05 * abs(y_tb[1]))
plot(step4_baseline$year, step4_baseline$GDP, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "GDP (million EUR)", ylim = y_gdp, main = paste0("Step 4 Core: GDP (base m=", base_import_leakage_scale, ")"))
lines(step4_core_play$year, step4_core_play$GDP, lwd = 2, col = "firebrick")
legend("topleft", legend = c(paste0("base (m=", base_import_leakage_scale, ")"), paste0("play (m=", import_leakage_scale_play, ")")),
col = c("steelblue", "firebrick"), lty = 1, bty = "n", cex = 0.85)
plot(step4_baseline$year, step4_baseline$TB, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "TB = EX - IM (million EUR)", ylim = y_tb, main = paste0("Step 4 Core: TB (base m=", base_import_leakage_scale, ")"))
lines(step4_core_play$year, step4_core_play$TB, lwd = 2, col = "firebrick")par(mfrow = c(1, 1))Optional play: export growth.
This play isolates the export-demand channel by changing ex_growth. Economic meaning: stronger exogenous foreign demand raises domestic final demand and output through IO multipliers. Compare this with the import-leakage play to discuss external-demand-led growth versus leakage-constrained growth.
ex_growth_play <- 0.03 # TODO [optional:step4_exports] Change export growth and inspect GDP response Hint: Positive export growth raises final demand
step4_opt <- simulate_sim_iot_rowlite(calib4, T = 20, ex_growth = ex_growth_play, import_leakage_scale = base_import_leakage_scale, transition_speed = 0)
par(mfrow = c(1, 2), mar = c(4, 4, 3, 1), cex.main = 0.9)
y_gdp <- range(c(step4_baseline$GDP, step4_opt$GDP), na.rm = TRUE)
y_tb <- range(c(step4_baseline$TB, step4_opt$TB), na.rm = TRUE)
if (!all(is.finite(y_gdp))) stop("Non-finite GDP values in Step 4 optional plot.", call. = FALSE)
if (!all(is.finite(y_tb))) stop("Non-finite TB values in Step 4 optional 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_tb[1] == y_tb[2]) y_tb <- y_tb + c(-1, 1) * max(1, 0.05 * abs(y_tb[1]))
plot(step4_baseline$year, step4_baseline$GDP, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "GDP (million EUR)", ylim = y_gdp, main = paste0("GDP: base ex_g=", base_ex_growth))
lines(step4_opt$year, step4_opt$GDP, lwd = 2, col = "darkgreen")
legend("topleft", legend = c(paste0("base (ex_g=", base_ex_growth, ")"), paste0("play (ex_g=", ex_growth_play, ")")),
col = c("steelblue", "darkgreen"), lty = 1, bty = "n", cex = 0.85)
plot(step4_baseline$year, step4_baseline$TB, type = "l", lwd = 2, col = "steelblue",
xlab = "year", ylab = "TB = EX - IM (million EUR)", ylim = y_tb, main = paste0("TB: base ex_g=", base_ex_growth))
lines(step4_opt$year, step4_opt$TB, lwd = 2, col = "darkgreen")par(mfrow = c(1, 1))2.10 Insight (Step 4 core play)
- Higher import leakage can reduce domestic GDP response even when total demand is similar.
- Trade-balance movement identifies whether demand expansion is domestic-output-led or import-led.
- Policy lever insight: domestic industrial strategy and trade structure jointly shape transition multipliers.
2.11 What can go wrong / interpretation caveat
- SIM+IOT+RoW is not a substitute for MRIO when foreign technology and embodied emissions matter quantitatively.
- Import leakage parameters are scenario assumptions and should be stress-tested.
2.12 MRIO caveat (equations/text only)
Imports in this step use domestic leakage rates. In MRIO, imported bundles should be mapped to foreign technologies and foreign emission intensities.