Step 4 - SIM+IOT+RoW

1 Shared Setup

Load minimal packages and global options only.

Check that required packages are installed. Stop early with a clear error if anything is missing. This keeps the tutorial run deterministic and minimal.

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)
}

1.1 Dependency Context

This step is standalone: it reuses Step 2 IO/calibration helpers and introduces only the new SIM+IOT+RoW simulator here. Economically, this preserves domestic calibration while adding an external sector channel. Algorithmically, helpers provide data and calibration; open-economy dynamics are defined and explained in this step file.

helper_dir <- if (dir.exists('R/helpers')) 'R/helpers' else '../R/helpers'
source(file.path(helper_dir, 'cache_io.R'))
source(file.path(helper_dir, 'jsonstat_parse.R'))
source(file.path(helper_dir, 'iot_load.R'))
source(file.path(helper_dir, 'config_wealth.R'))
source(file.path(helper_dir, 'calibration.R'))

1.2 Function and Variable Location Map

This step reuses helper calibration/loading and defines SIM+IOT+RoW dynamics locally.

Helper functions (file paths): - make_core_config(), load_wealth_init() in R/helpers/config_wealth.R. - download_or_load_iot() in R/helpers/iot_load.R. - build_sim_iot_row_from_data() in R/helpers/calibration.R.

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, so m_i is the share of final demand for sector i that leaks to imports.
  • Calibration source: m_i comes from Step 2 IOT calibration using the imports row (P7/IMP) as m_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_scale multiplies calibrated m_i to 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)

  1. Start from Step 2/3 calibrated domestic structure.
  2. Add exogenous exports and compute import leakage by sector.
  3. Run IO mapping on net domestic demand.
  4. Update macro stocks and flows, then compute TB = EX - IM.
  5. 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.