🇬🇧 English · 🇪🇸 Español
MRIO dinámico con capital específico por sector
puremacro.trade.dynamic es una extensión dinámica, determinista y con
previsión perfecta del modelo multipaís de insumo-producto con coeficientes
fijos, en la que cada celda país-industria de origen mantiene su propio stock
de capital instalado con costos de ajuste a la Tobin-q. Los resultados son
objetos congelados con to_dataframe(), to_markdown(), to_latex() y
to_typst(), los constructores validan su entrada, los errores son
estructurados y ninguna solución devuelve converged=True sin superar en cada
fecha un certificado contable independiente.
No es el CGE estático de solve_trade_equilibrium: no hay sustitución de
Armington, el trabajo es inelástico y móvil dentro de cada país, los países
están en autarquía financiera y el consumo del gobierno está dentro de la
canasta del hogar. Su medida de bienestar es un equivalente de consumo CRRA, no
la EV hicksiana de compute_hicksian_welfare. Lea las
limitaciones del final antes de interpretar cualquier número.
Inicio rápido sobre una tabla sintética
from puremacro.trade import calibrate_trade_model
from puremacro.trade.data import synthetic_codes, synthetic_icio_table
from puremacro.trade.dynamic import (
DynamicAccounts, DynamicTariff, DynamicEconomy, calibrate_dynamic,
solve_dynamic_steady_state, solve_dynamic_transition, tariff_path,
stability_report,
)
# A seeded synthetic table: twelve countries, five sectors (S01 and S02 are merchandise).
countries, sectors = synthetic_codes(12, 5)
calib = calibrate_trade_model(synthetic_icio_table(12, 5, seed=2), country_codes=countries, sector_codes=sectors)
accounts = DynamicAccounts.from_trade_calibration(calib, merchandise_sectors=("S01", "S02"))
calibration = calibrate_dynamic(accounts, beta=.96, delta=.08)
print(calibration.summary())
economy = DynamicEconomy(calibration, adjustment_cost=2., risk_aversion=2.)
baseline = solve_dynamic_steady_state(economy) # the benchmark
tariff = DynamicTariff.uniform(calibration, "C01", .05, sectors="merchandise")
terminal = solve_dynamic_steady_state(economy, tariff) # exact 2C condensation
print(terminal.to_dataframe().loc[["C01", "C02"]])
report = stability_report(economy, baseline) # Blanchard-Kahn count
print(report.summary())
run = solve_dynamic_transition(economy, tariff_path(economy.zero_policy(), tariff, horizon=40),
terminal=terminal)
print(run.summary())
print(run.welfare.to_dataframe().loc[["C01", "C02"]])
Los flujos sintéticos son extracciones aleatorias genéricas, así que los
números son salidas del software, no estimaciones. La calibración no necesita
ajustes (60 celdas activas, sin reparaciones factoriales ni de inversión), la
referencia es determinada (60 raíces estables para 60 stocks predeterminados)
y un arancel permanente del 5% de C01 sobre todas sus importaciones de
mercancías se resuelve a 40 fechas en tres iteraciones de Newton con un
certificado cercano a 2e-11. La trayectoria de 40 fechas es un horizonte
resuelto, no aceptado: use run_horizon_ladder (sección siguiente) antes de
leer sus cifras de bienestar.
Un ejemplo pequeño que reproduce la batería de pruebas
Las cuentas analíticas de dos países y dos sectores son el fixture que usan las pruebas de este repositorio. Son un fixture de software, nunca evidencia empírica.
import numpy as np
from puremacro.trade.dynamic import (
analytic_two_country_accounts, calibrate_dynamic, DynamicEconomy,
DynamicTariff, tariff_path, solve_dynamic_steady_state, run_horizon_ladder,
)
accounts = analytic_two_country_accounts()
calibration = calibrate_dynamic(accounts)
economy = DynamicEconomy(calibration)
zero = economy.zero_policy()
rates = np.zeros((calibration.n_cells, calibration.n_countries))
rates[calibration.country == 0, 1] = .06 # BBB taxes AAA's goods at 6%
rates[calibration.country == 1, 0] = .08 # AAA taxes BBB's goods at 8%
shock = DynamicTariff.build(rates, consumption_rates=rates * .7,
investment_rates=rates * 1.3, label="analytic tariff")
terminal = solve_dynamic_steady_state(economy, shock, tol=1e-11)
# Announced at date 0, implemented at date 2, permanent.
ladder = run_horizon_ladder(
economy, lambda T: tariff_path(zero, shock, horizon=T, announcement=2),
horizons=(80, 160, 320), terminal=terminal, tol=1e-10,
)
print(ladder.to_dataframe())
accepted = ladder.accepted # the 320-date solution (status "verified")
print(accepted.welfare.summary()) # AAA -0.24%, BBB +0.11% of benchmark consumption
En este fixture la comparación de 80 a 160 coincide en las primeras 20 fechas (3.6e-7 en logaritmos) pero el bienestar todavía se mueve 0.0055 puntos porcentuales, así que se rechaza; la de 160 a 320 supera la ventana (2.7e-12), el bienestar (4.3e-5 pp) y la distancia al estado terminal (4.3e-5). La raíz estable más lenta ronda 0.98, por lo que hacen falta cientos de fechas.
El modelo
Celda j en el país c(j), fecha t, precios de referencia iguales a uno.
El valor agregado es Cobb-Douglas normalizado a la referencia en capital
específico del sector y trabajo nacional; los insumos intermedios y las
canastas finales tienen coeficientes fijos:
v[j,t] = w[c(j),t]^(1-alpha[j]) (R[j,t]/R0[j])^alpha[j]
(1-tax[j]) p[j,t] = sum_i A[i,j] (1+tau[i,c(j),t]) p[i,t] + b[j] v[j,t]
L[j,t] = (1-alpha[j]) b[j] v[j,t] y[j,t] / w[c(j),t]
K[j,t] = alpha[j] b[j] v[j,t] y[j,t] / R[j,t]
y = A y + omegaC C + omegaI I_national
PC[c] = sum_i (1+tauC[i,c]) omegaCtax[i,c] p[i] / (1-tC[c]) (exempt X part added)
PI[c] = sum_i (1+tauI[i,c]) omegaI[i,c] p[i] / (1-tI[c])
Capital, instalación y ecuación de Euler:
K[j,t+1] = (1-delta) K[j,t] + K[j,t] Phi(x[j,t]), x = I/K
Phi(x) = x - phi/2 (x-delta)^2
q[j,t] = PI[c(j),t] / Phi'(x[j,t])
q[j,t] = m[c,t+1] (R[j,t+1] + q[j,t+1] G[j,t+1]), G = 1-delta+Phi(x)-x Phi'(x)
m[c,t+1] = beta (C[c,t+1]/C[c,t])^(-sigma) PC[c,t]/PC[c,t+1]
El presupuesto del hogar paga exactamente el consumo y la inversión que entrega el vaciado de bienes:
PC C + PI sum_{j in c} I[j] + XN0[c] w_last
= w L0[c] + sum_{j in c} R K + product and production tax receipts + tariff receipts
No hay bono negociado. Las exportaciones netas de referencia XN0 son
transferencias mundiales balanceadas fijas en unidades del salario del último
país (el numerario). Un presupuesto redundante (el del país con mayor escala
presupuestaria) se sustituye por la ecuación del numerario y se comprueba
después junto con todas las demás cuentas. Como el último país del registro es
a la vez el numerario salarial y el ancla de las transferencias, el orden del
registro de países tiene contenido económico: reordenar los países cambia el
cierre y por tanto las asignaciones reales cuando se mueven los salarios
relativos (aproximadamente en el cambio del salario relativo del ancla por
XN0). Reordenar los sectores es un mero cambio de etiquetas.
Eliminación. Las incógnitas retenidas por fecha son
z_t = [log w (C), log C/C0 (C), log K_{t+1}/K0 (N)], es decir N + 2C por
fecha. La instalación se invierte en su rama creciente,
x = delta + 2g/(1 + sqrt(1 - 2 phi g)) con g = K'/K - 1; el vaciado de
bienes usa respuestas de Leontief guardadas YC = (I-A)^-1 omegaC,
YI = (I-A)^-1 omegaI; los precios factoriales salen de las condiciones
Cobb-Douglas; los precios de productor resuelven un sistema lineal ajustado por
aranceles para cada política distinta (LU dispersa hasta 600 celdas; por encima
de 600 celdas, LU densa cuando la matriz de coeficientes tiene densidad mayor
que .25 y en otro caso BiCGSTAB precondicionado con respaldo GMRES). El residuo
retenido son C ecuaciones de vaciado del trabajo, C presupuestos con un
numerario y N ecuaciones de Euler. DynamicEconomy.state_jvp/states_jvp
dan productos jacobiano-vector analíticos exactos, por lotes para cada
política.
Certificado. Cada fecha aceptada supera 17 comprobaciones reconstruidas de
forma independiente (bienes, beneficio cero, trabajo, acumulación de capital,
pagos factoriales, demanda de capital y de trabajo, tecnología, condición de
primer orden de la inversión, Euler, recaudación arancelaria, presupuesto
público, todos los presupuestos incluido el omitido, cuenta corriente, balance
mundial de transferencias, valor de los bienes de inversión) a
max(1e-8, tol).
Estado estacionario. solve_dynamic_steady_state(method="condensed")
resuelve una condensación exacta de 2C en salarios nacionales y precios de
la inversión con un jacobiano analítico denso (por defecto por encima de 240
incógnitas); "full" resuelve las N + 2C ecuaciones estacionarias con un
Newton sin matriz. Ambos reevalúan el residuo completo y el certificado. Ambos
son métodos de Newton locales sin respaldo de continuación, y method="auto"
no reintenta con el otro método ante un fallo: un arancel grande puede
necesitar start desde el estado estacionario de un arancel menor, y un
arancel prohibitivo puede no tener estado estacionario interior (en el fixture
analítico un arancel uniforme del 100% lanza DynamicSolveError). Un start
no factible o una política cuyo sistema de precios ajustado por aranceles no
tiene solución positiva lanza EconomicDomainError (un ValueError) antes de
cualquier paso de Newton.
Transición. solve_dynamic_transition apila T (N + 2C) incógnitas con
capital inicial fijo y un estado estacionario terminal resuelto aparte bajo la
última política (mantenida más allá del horizonte), y ejecuta un Newton
inexacto sin matriz: lgmres (con respaldo gmres) precondicionado por una
tridiagonal aproximada capital-tiempo más bloques nacionales
(preconditioner="capital"), término forzante
max(1e-5, min(.1, sqrt(||F||))), un tope de .75 en los movimientos en
logaritmos y divisiones de Armijo a la mitad. Un estancamiento de la búsqueda
lineal lanza DynamicSolveError nombrando la ecuación, la fecha y la celda, la
menor tasa de instalación del último iterado aceptado (incluida la fecha límite
terminal) y lo que el paso de Newton completo le haría. La conjetura inicial
por defecto es una mezcla exponencial del estado inicial al terminal; cuando un
choque grande haría desinvertir a esa conjetura (tasa de instalación x <= 0)
el solucionador usa una mezcla lineal, y cuando ni siquiera la mezcla lineal es
factible ninguna trayectoria de ese horizonte puede mantener la inversión
interior, lo que se informa como un DynamicSolveError que nombra la celda
(metadata["start"] registra la conjetura usada). Los fallos de la búsqueda
lineal del estado estacionario nombran la ecuación con mayor residuo en
location. Por defecto la transición ejecuta primero stability_report sobre
el estado terminal y rechaza un cierre indeterminado
(DynamicDeterminacyError); require_determinacy=False resuelve de todos
modos la pila finita.
Libro de la calibración estacionaria
Un año de insumo-producto no es un estado estacionario. calibrate_dynamic
deriva R0 = PI0 (1/beta - 1 + delta), K0 = alpha VA0 / R0, la inversión de
reposición delta K0, reasigna el flujo de inversión estacionario dentro de la
absorción observada (recompone la canasta solo donde la composición observada
no puede financiar la reposición, con investment_policy="reallocate"), agrupa
la variación de existencias, los objetos valiosos y las compras exentas en el
exterior en la canasta de consumo, y concilia los impuestos sobre el uso final.
factor_policy="reclassify_losses" convierte los componentes factoriales
negativos en subsidios a la producción y recorta las participaciones a
capital_share_bounds. Todo queda registrado en calibration.report y en
calibration.to_dataframe("countries" | "factors" | "investment").
Puentes: DynamicAccounts.from_icio acepta ICIOData (esquema procesado
C/I/Cx: una columna I que contiene variación de existencias puede tener celdas
negativas, y entonces negative_investment debe ser "raise" o
"to_inventory"; las participaciones del capital salen de las filas de trabajo
y capital de la tabla), RawIOData (las categorías de los cargadores como
HFCE/NPISH/GGFC/GFCF/INVNT/DPABR se asignan al contrato
("C","G","X","V","VAL"); sin detalle factorial se supone y registra la
separación 2/3-1/3) y cualquier objeto tabla de insumo-producto con Z, F,
fd_codes, VA, TLS, TFD, output, country_codes y sector_codes. La
columna EXPORT del cargador de EXIOBASE (exportaciones a regiones fuera de la
tabla) no tiene asignación por defecto porque no son compras de residentes en
el exterior; pase fd_map={"EXPORT": "X"} u otro destino explícitamente.
from_arrays acepta F como (M, N, K) o (M, N*K) y TFD como (N, K) o
plano (N*K,); una tabla de impuestos transpuesta (K, N) se rechaza cuando
N != K (con tantos países como códigos de uso final no se puede detectar la
orientación, así que pase filas de destino y columnas de categoría). El valor
por defecto merchandise_sectors="isic_a_c" marca como bienes los 22 códigos
de bienes de las 45 industrias de la OCDE y los códigos que son la letra sola
A, B o C o empiezan por una de ellas seguida de un dígito, salvo el C33 de
FIGARO (reparación e instalación); un objeto tabla que lleva su propia
merchandise_mask la conserva, y otros registros, como los códigos i01... de
EXIOBASE o los códigos sintéticos S01..., necesitan una lista explícita de
sectores. La regla y la lista de bienes resultante se registran en
metadata["merchandise_sectors"]. from_trade_calibration lee
calib.data_calibra; su columna Cx pasa a ser X (compras de residentes en el
exterior) solo para una tabla sin asignación de usos finales registrada y para
la asignación DPABR de la OCDE, el Cx de los cargadores FIGARO, WIOD y Eora
(existencias y objetos valiosos con signo) pasa a ser V, y el Cx de EXIOBASE,
que incluye su columna de exportaciones, necesita un cx_category explícito.
Políticas
DynamicTariff guarda tres tablas de tasas ad valorem N x C (entregas
intermedias, de consumo y de inversión) por celda de origen y país de destino;
las entregas domésticas y las compras de residentes en el exterior nunca pagan.
Los aranceles específicos por sector de destino no son representables. Las
tasas deben ser mayores que -1: cero es libre comercio y las tasas negativas
son subsidios a la importación.
DynamicTariff.uniform(cal, "C01", .1, sectors="merchandise", exporters=("C02",), uses=("intermediate",)),
from_rates y build validan las tablas. La huella (sha256 de las tres
tablas) siempre se deriva de las tablas guardadas, nunca se pasa, así que
dataclasses.replace(policy, rates=...) produce una huella nueva; las
políticas idénticas comparten una factorización de la red de precios, y una
política cuyas tablas ya no coinciden con su huella se rechaza. Una selección
sin ninguna celda de origen extranjera (por ejemplo exporters=("C01",) para el
importador "C01") lanza ValueError en lugar de devolver una política nula
con etiqueta de arancel. tariff_path(baseline, shock, horizon=T, announcement=2, duration=5)
construye esquemas anticipados, permanentes o temporales.
Aceptación del horizonte
Una pila finita es una aproximación a una trayectoria de horizonte infinito.
compare_horizons(short, long) exige: (1) que los primeros periods=20 estados
retenidos coincidan dentro de 1e-5 en logaritmos; (2) que el bienestar en
equivalente de consumo coincida dentro de 1e-4 puntos porcentuales; (3) una
comprobación terminal sobre la solución larga. El valor por defecto
terminal_check="state_gap" exige max |z_T - z*| <= 1e-4 y da el estado
verified. El modo opcional "discounted_wealth" exige que el valor del
capital terminal descontado
sea como mucho 1e-6 y da el estado explícitamente más débil
verified_window_and_welfare: no certifica ni la convergencia completa al
estado terminal ni una cota rigurosa de la cola de utilidad. Ninguna de estas
tolerancias es una cota de error frente a la solución desconocida de horizonte
infinito. Ambas soluciones deben venir de la misma economía (el resultado de la
transición registra una huella de los arreglos de calibración, el costo de
ajuste, la aversión al riesgo y el numerario salarial en
metadata["economy_fingerprint"]), partir del mismo estado inicial, terminar
en el mismo estado terminal y seguir la misma política anunciada.
run_horizon_ladder resuelve horizontes crecientes, arrancando cada uno en
caliente desde la trayectoria anterior, y se detiene en la primera comparación
aceptada.
Bienestar
consumption_equivalent_welfare devuelve, por país, el cambio proporcional
permanente en el consumo de referencia e con
sum_t beta^t u(e) = sum_t beta^t u(C_t/C0) para una u CRRA (la misma
risk_aversion que la ecuación de Euler; logarítmica en uno), la cola
estacionaria ponderada por beta^T/(1-beta), el equivalente de gasto anual a
precios de referencia y su porcentaje del PIB de referencia. No hay agregación
entre países ni descomposición por canales.
Diagnóstico de determinación
stability_report(economy, steady) linealiza las ecuaciones de cada fecha con
los JVP propios del modelo, forma el haz
[[A_plus,0],[0,I]] s_{t+1} = [[-A_0,-A_minus_K],[E_K,0]] s_t con
s_t = (dz_t, dK_t) y cuenta los valores propios generalizados: la
determinación exige exactamente N raíces estables para los N stocks
predeterminados. El resultado informa de la raíz estable más lenta, la raíz
inestable más pequeña y las participaciones por país de su vector propio. El
punto debe ser estacionario bajo la política pasada (o la que lleve un
resultado de estado estacionario): el informe rechaza, con un ValueError, un
estado cuyo residuo F(z, z, z) supera stationarity_tol (por defecto
1e-8). El problema denso tiene orden n_vars + N y su costo crece con el cubo
de ese orden (segundos con unos cientos de celdas, decenas de segundos cerca de
mil). solve_dynamic_transition guarda el informe en la economía por huella de
la política terminal, de modo que una escalera de horizontes lo paga una vez.
Qué está validado aquí y qué no
Verificado por pruebas de este repositorio (tests/test_trade_dynamic.py), en
el fixture analítico, el fixture congelado OCDE de tres regiones y tablas
sintéticas con semilla:
- Estacionariedad de la referencia, identidades factoriales y tributarias, cuentas independientes con bucles escalares en estados estacionarios y a lo largo de transiciones, JVP analíticos frente a diferencias centradas (2e-6), la condensación exacta frente a la solución completa, el jacobiano condensado frente a diferencias finitas, la evaluación por lotes, el momento de la anticipación, las políticas temporales, las guardas de la comparación de horizontes, el diagnóstico del extremo descontado y las identidades del bienestar en equivalente de consumo.
puremacro.dsge.solve_perfect_foresightsobre las mismas ecuaciones a 40 fechas como oráculo independiente de Newton apilado; los backends iterativos de precios y bienes coinciden con la LU dispersa a 1e-10 en una tabla dispersa con semilla.- Guardas: una política reconstruida con
dataclasses.replacese resuelve como política nueva, las comparaciones de horizontes rechazan soluciones de economías distintas,stability_reportrechaza puntos no estacionarios y la barrera de determinación lanza un error estructurado en un fixture indeterminado. - Tablas sintéticas: estados estacionarios con y sin un arancel del 10% en una tabla de 5 por 4 y (lenta) una tabla determinada de 12 por 5 con una transición certificada de 40 fechas y una escalera de horizontes.
No validado aquí:
- Parámetros. Una sola tabla de insumo-producto no identifica los stocks de
capital históricos, la depreciación, los costos de ajuste ni las preferencias
intertemporales. Los ejemplos usan
beta=.96,delta=.08, costo de ajuste 2 y aversión al riesgo CRRA 2; la aceptación numérica no valida esas elecciones de parámetros económicos. - Inversión. Es un modelo de inversión interior; no resuelve un problema de complementariedad con irreversibilidad de la inversión. El choque admisible puede depender del horizonte: cuando un horizonte largo lleva la instalación de alguna celda hacia cero en la fecha límite terminal, la búsqueda lineal se estanca y el error nombra la celda. Un fallo de la búsqueda lineal es evidencia, no prueba, de que no existe una trayectoria interior.
- Alcance. Abastecimiento fijo, valor agregado Cobb-Douglas, trabajo nacional fijo y autarquía financiera con transferencias balanceadas; no hay elasticidades de Armington ni de oferta de trabajo. La variación registrada de existencias y los objetos valiosos no identifican stocks de existencias, y el consumo incluye la demanda final del gobierno y de las instituciones sin fines de lucro en la canasta del hogar representativo.
- Horizontes. Los horizontes largos son intrínsecos (raíces estables más lentas cerca de 0.98 en los fixtures), y una escalera aceptada en una tabla no dice nada de otra.
- Determinación. La indeterminación local es una propiedad del cierre
linealizado en una tabla dada, no un defecto del solucionador, y las tablas
grandes pueden ser indeterminadas. Ejecute
stability_reportantes de confiar enrequire_determinacy=False. El criteriostate_gappuede rechazar una escalera quediscounted_wealthaceptaría; trate los resultados dediscounted_wealthsolo como afirmaciones sobre la ventana inicial.
Relacionado
- Contabilidad de comercio consistente, bienestar hicksiano del consumo y política arancelaria para el modelo estático que esta extensión no sustituye.
puremacro.dsge.solve_perfect_foresightpara el Newton apilado genérico que se usa como oráculo independiente.- Estado de la validación estructural.