🇬🇧 English · 🇪🇸 Español
Transiciones no lineales, dominios y fechas de anuncio
Puremacro resuelve problemas deterministas no lineales de frontera definidos
mediante un modelo .mod admitido o residuos de equilibrio en Python. Las
ecuaciones se resuelven conjuntamente en el tiempo; no se exige una solución
previa por perturbación. Esto no implica que todo modelo tenga solución,
que sea única o que Newton la encuentre desde cualquier conjetura. El modelo
debe respetar las interfaces de ecuaciones y temporalidad admitidas. Las
fronteras, los dominios y los supuestos de información siguen siendo decisiones
económicas de quien especifica el experimento.
| Interfaz | Qué calcula |
|---|---|
solve_perfect_foresight |
Un problema determinista de frontera con horizonte finito. |
solve_transition |
El mismo problema con continuación explícita, transformaciones de dominio, escalas y aceptación opcional del horizonte. |
solve_announced_path |
Segmentos realizados de planes finitos revisados en fechas de información especificadas. |
extended_path |
Planes móviles con la innovación actual realizada y las futuras fijadas en cero; no integra de forma no lineal el riesgo futuro. |
Estas rutinas calculan trayectorias, no una función de política estocástica
global. Para ese problema distinto, consulte los
métodos globales de equilibrio. La
guía de flujos DSGE compara los métodos disponibles y los
conecta con los cuadernos del curso. Para cargar modelos y ejecutar
comandos, consulte la guía de modelos no lineales y la
guía de inspección de .mod.
El problema matemático de horizonte finito
Escriba el modelo con n variables endógenas y m entradas exógenas como
Cada evaluación devuelve n residuos. El vector desconocido es
\(Y=(y_1^\top,\ldots,y_T^\top)^\top\in\mathbb R^{nT}\).
El rezago inicial y el adelanto final se fijan:
El sistema contiene T fechas interiores, con n*T ecuaciones e incógnitas.
El solucionador no añade una ecuación que exija y_T == y_end. Un modelo sin
adelantos no usa la frontera final en sus residuos dinámicos; uno sin rezagos
no usa allí la frontera inicial. Aun así, proporcione un vector completo en el
orden declarado. Las ecuaciones determinan qué entradas son económicamente
relevantes; el solucionador no infiere una nueva especificación de fronteras
para variables de estado y de salto.
El orden de argumentos es exactamente f(lead, current, lag, shock). En una
ecuación .mod, el residuo es lado izquierdo menos lado derecho. La interfaz
presentada tiene un adelanto, un valor actual y un rezago; un modelo con
historia más larga debe reducirse a esta forma mediante estados auxiliares
explícitos o transformaciones admitidas por el analizador. Esta guía no promete
soporte automático para cualquier construcción temporal.
y_end también es la referencia fija \(\bar y\) de STEADY_STATE() durante
una resolución. No se sustituye por el iterado actual. Una función que declare
explícitamente el argumento steady_state recibe la misma referencia. Un
vector terminal explícito es una asignación de frontera; no tiene por qué ser
un estado estacionario por llamarse y_end. El alias de bajo nivel y_ss
cumple la misma función. Una asignación .mod endval y una resolución
explícita del estado estacionario terminal son operaciones distintas; consulte
la guía del modelo para el orden de comandos.
Convenciones de arreglos y fechas
| Objeto | Convención |
|---|---|
y_init, y_end |
Un valor físico por variable endógena. solve_transition también acepta diccionarios por nombre de variable. |
shocks |
Se recomienda forma (T, m); la fila 0 corresponde a la fecha 1. Para un choque se acepta un vector de longitud T. Sin choques se usa (T, 0). Son entradas exógenas efectivas de las ecuaciones, sin logaritmos, conversiones porcentuales ni escalamiento por desviación estándar automáticos. |
initial_path |
Niveles físicos de forma (T, n), sin filas de frontera. En continuación inicializa la resolución de la base; las trayectorias aceptadas inicializan las etapas siguientes. |
.path devuelta |
DataFrame con índice 1,...,T, solo fechas interiores. El refinamiento puede devolver más filas que la trayectoria original de choques. |
terminal_error |
max(abs(y_T - y_end)); distancia descriptiva, no un residuo adicional de equilibrio ni una prueba automática de precisión del truncamiento. |
solve_transition obtiene T de la trayectoria de choques. No pase periods,
n_periods, exogenous_path, y_ss ni surprise a esta interfaz: entran en
conflicto con su experimento explícito. En la interfaz de menor nivel
solve_perfect_foresight, indique periods=T: omitirlo no deduce el horizonte
de shocks. Esa API también admite un arreglo heredado de T+2 filas y
descarta la primera y la última, o trunca otros arreglos más largos.
Exactamente T filas evita esa ambigüedad.
Comenzar con un modelo no lineal
read_mod construye una especificación no lineal sin resolver primero una
aproximación local. Este modelo pequeño permite ver la temporalidad. Ejecute
los bloques Python de la guía en orden y en una misma sesión; los bloques
posteriores reutilizan estas importaciones y el ejemplo lineal. Inglés y
español contienen bloques ejecutables idénticos. El gráfico final requiere
Matplotlib.
import numpy as np
from puremacro.dsge import read_mod, solve_perfect_foresight, solve_transition
model = read_mod("""
var x; varexo e;
model; x = 0.25*x(+1) + 0.4*x(-1) + e; end;
initval; x=0; e=0; end;
""")
shocks = np.r_[0.1, np.zeros(39)][:, None]
transition = solve_transition(
model, y_init=[0.0], y_end=[0.0], shocks=shocks,
horizon_tolerance=1e-8, compare_periods=20,
max_periods=160, shock_tail=[0.0], linear_solver="krylov",
)
assert transition.converged, transition.summary()
assert transition.reason == "horizon_accepted"
path = transition.path
print(transition.summary())
El destino es una innovación en la fecha 1 seguida de ceros. El flujo resuelve
ese experimento y comprueba después que duplicar el horizonte tenga un efecto
suficientemente pequeño sobre las primeras 20 fechas. transition.path
devuelve una copia; compruebe siempre transition.converged antes de tratarla
como un destino aceptado.
Newton disperso directo o Newton–Krylov sin matriz
En una trayectoria actual defina
Con fronteras fijas, el jacobiano apilado y su producto son
Ambos métodos buscan una dirección de Newton de \(Jd=-F(Y)\) y usan búsqueda
de paso. linear_solver="direct" construye la matriz dispersa apilada y
resuelve directamente el sistema lineal. Para un modelo analizado pasado
directamente a solve_perfect_foresight, la incidencia simbólica limita las
entradas almacenadas al patrón estructural. Los bloques de funciones Python,
incluidas las transformadas por la interfaz de transición, pueden almacenar
un patrón más completo por fecha.
linear_solver="krylov" aplica productos con el jacobiano apilado y utiliza
LGMRES por defecto; GMRES reiniciado es una opción explícita. Resulta útil
cuando formar o factorizar el sistema completo cuesta mucho, siempre que la
iteración lineal tenga un precondicionador eficaz. Los productos sin matriz no
hacen pequeños todos los costos: los bloques por fecha y sus factores todavía
pueden ser densos. Ningún método será necesariamente más rápido en todo modelo.
method="auto" usa primeras derivadas simbólicas para modelos analizados,
conservando la referencia fija de STEADY_STATE. Para funciones Python prueba
compatibilidad con paso complejo y, de no detectarla, usa diferencias
centrales. Proporcione
jacobian_fn(lead,current,lag,shock) -> (A,B,C) para controlar las derivadas,
como abajo. method="analytic" exige un modelo analizado o esa función.
Siguen disponibles method="central" y "complex"; el paso complejo exige
que los residuos conserven las perturbaciones complejas y sean adecuados para
esa diferenciación. Una prueba automática no demuestra esa propiedad.
def equilibrium(lead, current, lag, shock):
return current - 0.25*lead - 0.4*lag - shock
def date_jacobian(lead, current, lag, shock):
return np.array([[-0.25]]), np.array([[1.0]]), np.array([[-0.4]])
fixed_problem = dict(
y_init=[0.0], y_end=[0.0], periods=len(shocks), shocks=shocks,
variable_names=["x"], tol=1e-10,
)
direct = solve_perfect_foresight(model, **fixed_problem)
krylov = solve_perfect_foresight(
equilibrium, jacobian_fn=date_jacobian,
linear_solver="krylov", **fixed_problem,
)
assert direct.converged and krylov.converged
np.testing.assert_allclose(direct.path, krylov.path, atol=1e-9, rtol=0)
assert krylov.solver_diagnostics["jvp_method"] == "analytic"
Precondicionadores y combinaciones admitidas
El precondicionador Krylov predeterminado factoriza un sistema tridiagonal por
bloques construido con jacobianos fijos de la referencia terminal. Entre los
nombres admitidos están steady_block_tridiagonal, steady_splu y
current_block_tridiagonal; este último se reconstruye con la trayectoria
actual. La guía de Newton apilado explica el protocolo
de operadores, las opciones de Jacobi por bloques y los residuos lineales.
solve_transition y extended_path con Krylov reutilizan la factorización
estacionaria predeterminada solo si el horizonte, las formas, los tipos de
datos y las entradas numéricas de los bloques coinciden exactamente.
Cambiar parámetros, referencias u horizonte puede exigir reconstruirla.
reuse_preconditioner=False desactiva la caché. Una opción explícita
krylov_options["preconditioner"], incluido None, o time_block tiene
prioridad sobre la reutilización automática. El contador de la caché registra
sus propias construcciones, no todas las factorizaciones de un operador externo.
| Combinación solicitada | Comportamiento actual |
|---|---|
| Directo + ecuaciones ordinarias, dominios/escalas o MCP | Admitida. |
| Directo + política terminal, incluida asignación MCP explícita | Admitida; se incluye la regla de la cadena terminal. |
| Krylov + ecuaciones ordinarias y transformaciones de dominio/escala | Admitida. |
Krylov + cotas MCP o mcp=True |
Admitida; Newton–Krylov semisuave con precondicionamiento por filas activas. |
Krylov + terminal_policy |
Admitida; la regla de la cadena terminal entra en el producto JVP y el bloque precondicionador de la fecha T. |
Directo + cualquier krylov_options distinto de None |
Rechazo explícito. |
terminal_jacobian sin terminal_policy |
Rechazo explícito. |
Las claves de alto nivel de krylov_options son preconditioner, forcing,
krylov_method, inner_m, outer_k, krylov_maxiter, side, max_log_move,
time_block, progress y checkpoint. Otros controles del motor de menor
nivel no se aceptan automáticamente aquí. Krylov nunca cambia silenciosamente
el problema no lineal al método directo. Un precondicionador de Thomas por
bloques puede recurrir a su factorización SuperLU documentada si un pivote es
inadecuado; eso afecta la construcción del precondicionador, no la elección
del solucionador no lineal.
Continuación, base y presupuesto de resoluciones
La continuación resuelve una familia explícita de experimentos. Con entradas base \((E^0,b^0,\theta^0)\) y objetivo \((E^1,b^1,\theta^1)\),
La implementación copia exactamente los extremos para las fracciones cero y
uno. El rezago inicial y_init permanece fijo. La frontera terminal y su
referencia STEADY_STATE siguen a b en cada etapa. Una política terminal
suministrada se conserva tal cual; no se recalibra de manera oculta al cambiar
los parámetros.
La base predeterminada es choques cero y baseline_end=y_init. Es una base de
cálculo, no una afirmación de que el estado inicial sea estacionario. Indique
baseline_shocks con la misma forma del objetivo y baseline_end si otra
base corresponde al experimento económico. La base debe resolverse con éxito.
continuation=False solicita una resolución directa del objetivo.
Para NonlinearModel, baseline_params cambia solamente los parámetros
nombrados y ya calibrados; los demás conservan sus valores objetivo. El
diccionario del modelo original no se modifica. No se resuelven estados
estacionarios intermedios automáticamente. Las cotas MCP admitidas que dependen
de parámetros se evalúan con la calibración de cada etapa. Las funciones
de residuos, derivadas y política terminal suministradas por el usuario deben
ser coherentes con el experimento que representan.
El incremento inicial es initial_step=0.25. Una etapa fallida divide el
incremento entre dos; una aceptada lo multiplica por 1.5, sin superar la distancia
restante a uno. min_step=1/1024 limita las reducciones. max_iter rige cada
resolución numérica; max_solves=40 cuenta conjuntamente la base, los intentos
de continuación aceptados y fallidos, y los refinamientos de horizonte.
Son presupuestos de iteraciones y resoluciones, no plazos de tiempo real.
calibrated = read_mod("""
var x; parameters theta; theta=999;
model; x^3 = 1 + theta; end;
initval; x=1; end;
""")
parameter_path = solve_transition(
calibrated, y_init=[1.0], y_end=[1.0], shocks=np.zeros((3, 0)),
baseline_params={"theta": 0.0}, max_iter=4, max_solves=40, tol=1e-9,
)
assert parameter_path.converged, parameter_path.summary()
np.testing.assert_allclose(parameter_path.path["x"], 10.0, atol=1e-9)
assert parameter_path.continuation_history[-1]["fraction"] == 1.0
assert calibrated.parameter_values["theta"] == 999.0
limited = solve_transition(
model, y_init=[0.0], y_end=[0.0], shocks=shocks, max_solves=1,
)
assert not limited.converged
assert limited.reason == "solve_budget_exhausted"
El ejemplo cúbico no tiene variables exógenas, de ahí la forma (3, 0). Su
objetivo exacto es x=10. La comprobación final muestra que una base resuelta
por sí sola nunca se presenta como éxito del objetivo. La continuación puede
ayudar a alcanzar una raíz; no elimina singularidades, no garantiza una rama
única ni asegura que todas las bases conduzcan a la misma solución objetivo.
Dominios económicos y escalas de residuos
variable_bounds especifica dominios abiertos. Con
variable_scale[j]=s_j positivo, el solucionador usa coordenadas monótonas:
Dominio de y_j |
Valor físico decodificado de z_j |
|---|---|
| Sin cotas | \(y_j=s_jz_j\) |
| \(y_j>\ell_j\) | \(y_j=\ell_j+\exp(s_jz_j)\) |
| \(y_j<u_j\) | \(y_j=u_j-\exp(-s_jz_j)\) |
| \(\ell_j<y_j<u_j\) | \(y_j=\ell_j+(u_j-\ell_j)/(1+\exp(-s_jz_j))\) |
Las fronteras, conjeturas y salidas de la política terminal deben estar estrictamente dentro del dominio. Durante las evaluaciones transformadas, candidatos con desbordamiento o saturación numérica se rechazan antes de evaluar los residuos económicos; no se recorta una política a un valor factible. Esto protege logaritmos y potencias definidos solo en el dominio, pero no corrige cualquier raíz mal condicionada.
equation_scale[i]=q_i>0 sustituye la fila f_i por f_i/q_i. Para una
transición ordinaria, la aceptación exige convergencia del método y
Esta comprobación explícita por fila es más estricta que confiar solamente
en la bandera de un método cuyo criterio también puede escalar por una magnitud
del jacobiano. variable_scale no sustituye unidades económicas interpretables
en las ecuaciones. Las escalas pueden ser escalares positivos, vectores o
diccionarios completos. Un diccionario sigue el orden de residuos mediante los
nombres de variables declarados; use un vector cuando esos nombres oculten
un orden diferente de ecuaciones.
def log_equation(lead, current, lag, shock):
return 1000.0 * (np.log(current) - shock)
positive = solve_transition(
log_equation, y_init=[1.0], y_end=[1.0],
shocks=np.full(10, -3.0), variable_names=["c"],
variable_bounds={"c": (0.0, None)},
equation_scale=[1000.0], variable_scale=[2.0], tol=1e-9,
)
assert positive.converged, positive.summary()
np.testing.assert_allclose(positive.path["c"], np.exp(-3.0), rtol=1e-8)
assert positive.scaled_residual_norm <= 1e-9
physical_error = np.max(np.abs(1000.0*(np.log(positive.path["c"]) + 3.0)))
np.testing.assert_allclose(positive.solution.residual_norm, physical_error, atol=1e-12)
En un PerfectForesightResult ordinario dentro de transition.solution,
residual_norm e initial_residual_norm conservan las unidades originales de
las ecuaciones; transition.scaled_residual_norm informa el criterio escalado
por fila. En MCPResult, los residuos y diagnósticos de complementariedad
conservan las unidades transformadas/escaladas del solucionador, aunque la
trayectoria se devuelva en niveles físicos. Compruebe por separado la
factibilidad y complementariedad físicas cuando importen sus tolerancias
económicas.
Un dominio abierto no representa una restricción que deba poder ser activa. Use MCP para esa condición. Si una variable tiene dominio y cota de complementariedad, esta última debe estar estrictamente dentro del dominio para tener coordenada transformada finita; un dominio con límite inferior cero y una cota inferior activa en cero son incompatibles.
Aceptación del horizonte y colas de choques permanentes
Un residuo apilado pequeño verifica un problema de frontera finita. No demuestra
que esa frontera aproxime adecuadamente un horizonte infinito. Con
horizon_tolerance, el flujo primero resuelve el objetivo exacto y después fija
\(T'=\min(2T,\texttt{max_periods})\). Mantiene condición inicial, prefijo de
choques, calibración objetivo y política/referencia terminal; amplía los
choques explícitamente e inicializa la resolución más larga con la anterior.
Para P=compare_periods, el estadístico de aceptación es
Es una diferencia absoluta en unidades físicas. No se divide entre
equation_scale ni variable_scale. Elija la tolerancia según las unidades
reportadas. compare_periods no puede superar el horizonte inicial y
max_periods debe superarlo. El refinamiento termina con la primera comparación
aceptada, al llegar al máximo horizonte, ante una resolución ampliada fallida
o al agotarse el presupuesto compartido. La comparación verifica solamente la
ventana común declarada.
Se exige un shock_tail explícito. Puede ser un escalar/fila compatible con
las fechas nuevas, o una función shock_tail(old_T, new_T) que devuelva la
extensión. Sus argumentos son longitudes de horizonte, no fechas de choques
contadas desde uno. No se impone silenciosamente una interpretación permanente
o transitoria.
Por ejemplo, si la productividad logarítmica satisface
\(a_t=\rho a_{t-1}+u_t\), cambiar solo y_end no mantiene la productividad
en a_star. Desde a_0=0, un nivel permanente a_star que comienza en la
fecha 1 requiere u_1=a_star y después u_t=(1-rho)*a_star. La misma innovación
recurrente debe mantenerse al ampliar la cola:
productivity = read_mod("""
var a; varexo innovation; parameters rho; rho=0.9;
model; a = rho*a(-1) + innovation; end;
initval; a=0; innovation=0; end;
""")
rho, target_a, initial_horizon = 0.9, 0.2, 12
permanent_innovations = np.full((initial_horizon, 1), (1.0-rho)*target_a)
permanent_innovations[0, 0] = target_a
permanent = solve_transition(
productivity, y_init=[0.0], y_end=[target_a],
shocks=permanent_innovations, continuation=False,
horizon_tolerance=1e-10, compare_periods=12, max_periods=48,
shock_tail=[(1.0-rho)*target_a],
)
assert permanent.converged, permanent.summary()
np.testing.assert_allclose(permanent.path["a"], target_a, atol=1e-10)
assert permanent.horizon_history[-1]["accepted"]
horizon_history registra los horizontes anterior/nuevo, la ventana, diferencia
máxima, tolerancia y aceptación. terminal_error puede ser positivo en una
solución finita válida y no sustituye esta comparación.
Anuncios, sorpresas y revisiones de creencias
En una resolución de previsión perfecta se conoce desde el inicio toda la trayectoria de choques. Un choque no nulo en la fecha 8 puede alterar las fechas 1–7 mediante ecuaciones con adelantos. Una sorpresa conocida solo en la fecha 8 es un experimento de información distinto, aunque finalmente el arreglo exógeno realizado sea el mismo.
ShockAnnouncement(date, shocks, y_end=None) registra una nueva trayectoria
de creencias sobre el calendario completo. El primer anuncio debe ocurrir en
la fecha 1; los posteriores deben aumentar estrictamente dentro del mismo
calendario. Todos los arreglos tienen la misma forma. Las revisiones pueden
cambiar choques actuales/futuros y opcionalmente la frontera/referencia terminal,
pero todas las filas anteriores al anuncio deben coincidir con los choques ya
realizados. Si se omite y_end, persiste la frontera anunciada anteriormente.
from puremacro.dsge import ShockAnnouncement, solve_announced_path
news = np.zeros((40, 1))
news[7, 0] = 0.1
anticipated = solve_announced_path(
model, [ShockAnnouncement(1, news)], y_init=[0.0], y_end=[0.0],
)
surprise = solve_announced_path(
model, [ShockAnnouncement(1, np.zeros_like(news)), ShockAnnouncement(8, news)],
y_init=[0.0], y_end=[0.0],
)
assert anticipated.converged and surprise.converged
assert np.max(np.abs(anticipated.path.iloc[:7].to_numpy())) > 1e-4
np.testing.assert_allclose(surprise.path.iloc[:7], 0.0, atol=1e-12)
assert surprise.solver_diagnostics["completed_periods"] == 40
En la fecha de anuncio d, se resuelven las fechas d,...,T desde el estado
efectivamente alcanzado en d-1, y se realiza solo el segmento anterior al
siguiente anuncio. Las revisiones futuras nunca se revelan antes. El calendario
restante se reduce; no se sustituye de manera oculta por un horizonte móvil
constante. Un plan fallido detiene la realización y devuelve converged=False,
el prefijo completado y diagnósticos por anuncio. La realización empalmada no
tiene un único contexto de auditoría de previsión perfecta ex ante.
solve_announced_path acepta opciones del solucionador de previsión perfecta
de bajo nivel, no las opciones de continuación/dominio/horizonte de la
interfaz de transición. surprise, periods, n_periods, exogenous_path,
y_ss y una sola initial_path entran en conflicto con los planes fechados
y se rechazan explícitamente. No se ofrece aceptación automática de horizonte
para todo este calendario informativo; si hace falta, compare calendarios
más largos construidos explícitamente.
Para sorpresas repetidas de un periodo, simulate_surprise_shocks resuelve
en cambio un horizonte móvil fijo, realiza la primera fecha y repite. Su base
predeterminada de choques futuros es cero.
solve_perfect_foresight(..., surprise=True) utiliza ese experimento móvil.
extended_path puede recibir o generar la innovación actual y también fija
las futuras en cero. Esas trayectorias son una aproximación de equivalencia
cierta; sustituir la innovación media en ecuaciones no lineales no calcula por
sí solo las expectativas no lineales.
Complementariedad: variables, filas y signos
Para una cota inferior de la variable j, la asignación explícita (i,s) significa
Para una cota superior se usa u_j-y_j >= 0 y s*f_i <= 0. Con dos cotas
finitas, s*f_i es no negativo en la inferior, cero en el interior y no
positivo en la superior. Las demás filas siguen siendo igualdades. Por eso
el residuo de la igualdad original no tiene por qué anularse con una restricción
activa.
mcp_bounds asigna nombres o índices de variable desde cero a (lower, upper);
None indica una cota ausente. mcp_equations asigna las mismas claves a una
fila de residuo desde cero o a (row, sign). Una fila entera sola significa
signo +1. La asignación explícita debe cubrir exactamente las variables
acotadas, con filas distintas. Los órdenes de ecuaciones y variables pueden diferir.
La etiqueta admitida [mcp='r > lower'] indica una cota inferior para r,
asociada con la ecuación etiquetada y su signo izquierdo menos derecho. Se
admiten <, <=, > y >= con una variable y una expresión finita de cota
que dependa solo de parámetros calibrados. La notación representa una cota
MCP cerrada, no un dominio abierto. Cotas dinámicas, etiquetas duplicadas para
una variable y desigualdades arbitrarias no forman un lenguaje general de
restricciones; las cotas bilaterales usan la API Python.
bounded_model = read_mod("""
var x r; varexo e; parameters lower; lower=0;
model;
[mcp='r > lower'] r=e;
x=1-3*r;
end;
initval; x=1; r=0; e=0; end;
""")
bounded = solve_transition(
bounded_model, y_init=[1.0, 0.0], y_end=[1.0, 0.0],
shocks=-np.ones((5, 1)), mcp=True,
)
assert bounded.converged, bounded.summary()
np.testing.assert_allclose(bounded.path["r"], 0.0, atol=1e-8)
def reversed_residual(lead, current, lag, shock):
x, r = current
return np.array([shock[0]-r, x+3.0*r-1.0])
explicit = solve_perfect_foresight(
reversed_residual, y_init=[1.0, 0.0], y_end=[1.0, 0.0],
periods=5, shocks=-np.ones((5, 1)), variable_names=["x", "r"],
mcp_bounds={"r": (0.0, None)}, mcp_equations={"r": (0, -1)},
)
assert explicit.converged
np.testing.assert_allclose(explicit.path, bounded.path, atol=1e-8)
gap = bounded.path["r"].to_numpy()
margin = gap + 1.0
assert np.min(gap) >= -1e-8 and np.min(margin) >= -1e-8
assert np.max(np.abs(np.hypot(gap, margin)-gap-margin)) < 1e-8
Aquí r es la segunda variable, pero su ecuación complementaria es la primera.
La etiqueta implica mcp_equations={"r": (0, 1)}; la función invierte esa
ecuación deliberadamente y por eso usa (0, -1). Las comprobaciones finales
verifican brechas físicas no negativas y un residuo Fischer–Burmeister
independientemente de la bandera del solucionador.
Use mcp=True para activar las etiquetas admitidas. Cotas explícitas no vacías
también seleccionan MCP nativo. Sin asignaciones/etiquetas explícitas, la
interfaz Python anterior conserva su heurística de cargas para fila/signo;
especificar la asignación aclara las restricciones económicas de signo.
Se utiliza el solucionador semisuave nativo, sin afirmar equivalencia con
todos los algoritmos u opciones de complementariedad de Dynare. Tanto
linear_solver="direct" como linear_solver="krylov" admiten cotas y etiquetas
MCP; Krylov actualiza su proyección por filas activas y el precondicionador
cuando cambia el conjunto activo, y registra physical_mcp_feasibility,
physical_complementarity_residual y physical_residual_norm tanto en
MCPResult como en TransitionResult.
Políticas terminales y último bloque del jacobiano
Si se conoce un mapa terminal fiable g, terminal_policy(last_state) fija
\(y_{T+1}=g(y_T)\). Debe devolver un vector finito en el mismo orden de
variables. y_end permanece como referencia fija de STEADY_STATE. El último
bloque diagonal pasa a ser
Proporcione terminal_jacobian(last_state) como matriz cuadrada o permita que
el solucionador diferencie el mapa con diferencias centrales. Con
dominios/escalas, solve_transition recibe estas funciones en coordenadas
físicas y compone su regla de la cadena con la transformación (physical_scale
y normalized_horizon_tolerance también proporcionan aceptación de horizonte
invariante ante cambios de unidades cuando las variables tienen escalas dispares).
En el ejemplo lineal, una cola estable x_t=r^t*x_0 cumple
0.25*r**2-r+0.4=0. Su raíz menor da una cola exacta para este modelo lineal
particular sin choques:
stable_root = (1.0 - np.sqrt(1.0 - 4.0*0.25*0.4)) / (2.0*0.25)
terminal = solve_perfect_foresight(
model, y_init=[1.0], y_end=[0.0], periods=5, shocks=np.zeros((5, 1)),
terminal_policy=lambda last: stable_root * last,
terminal_jacobian=lambda last: np.array([[stable_root]]), tol=1e-10,
)
assert terminal.converged
np.testing.assert_allclose(terminal.path["x"], stable_root**np.arange(1, 6), atol=1e-10)
from puremacro.validation import audit_result
terminal_audit = audit_result(terminal, checks=["equations"])
assert np.max(np.abs(terminal_audit.maps["path_equations"])) < 1e-9
Un mapa terminal lineal local en un modelo no lineal es una aproximación cuya
adecuación todavía requiere una comprobación de horizonte o económica. Tanto
"direct" como "krylov" admiten políticas terminales y conservan los mismos
metadatos para repetir la auditoría. Las auditorías ordinarias conservadas vuelven a
evaluar la política y mantienen la referencia fija; además comparan su
entrada/salida terminal original para detectar cambios de estado de la función.
Mantenga las funciones sin cambios después de resolver. Esa repetición no
demuestra identidad matemática en todo el dominio de la política. Las
trayectorias MCP requieren sus propias comprobaciones físicas de complementariedad.
Diagnósticos, fallos y presentación
TransitionResult.converged exige alcanzar el objetivo exacto y aprobar todas
las comprobaciones de horizonte solicitadas. solution conserva el último
resultado numérico disponible; en un fallo puede ser intermedio o fallido.
path está vacío si no se conservó un resultado. La convergencia de una
resolución interna no basta para declarar éxito del flujo completo.
reason |
Interpretación |
|---|---|
target_solved |
Objetivo exacto aceptado; no se solicitó comprobar el horizonte. |
horizon_accepted |
Objetivo exacto y comparación de horizonte en la ventana común aceptados. |
baseline_failed |
La base especificada no superó la aceptación numérica. |
target_failed |
Falló el intento directo con continuation=False. |
continuation_step_exhausted |
Seguir reduciendo violaría min_step. |
solve_budget_exhausted |
Terminó el presupuesto compartido antes de completar lo solicitado. |
horizon_solve_failed |
Falló numéricamente un problema objetivo ampliado. |
horizon_budget_exhausted |
Se alcanzó el máximo horizonte sin la concordancia requerida. |
continuation_history registra la fracción, periodos, parámetros y aceptación
de cada intento. Los intentos numéricos completados incluyen residuos,
iteraciones y advertencias; las excepciones conservan un registro de error.
Los errores de contrato de entrada se propagan, no se presentan como éxitos
parciales. solves, scaled_residual_norm y preconditioner_builds resumen
el flujo conservado; inspeccione los historiales para distinguir una resolución
difícil de ecuaciones de un horizonte inadecuado.
En resultados ordinarios, solution.solver_diagnostics incluye detalles del
método/derivadas, las escalas y la norma original del residuo. linear_history
de Krylov informa true_linear_residual de la dirección devuelta, tolerancia
lineal solicitada, estado de iteraciones, paso y retrocesos. Un estado positivo
de la iteración interna no implica convergencia no lineal; examine el resultado
final y los residuos. extended_path.solver_diagnostics registra además
resoluciones móviles y construcciones/reutilizaciones del precondicionador.
Tablas y gráficos muestran el candidato conservado, sin alterar su estado.
to_frame() y to_dataframe() devuelven copias. Markdown, LaTeX y Typst usan
el formato y escape compartidos de puremacro.reports.
plot(ax=..., show=False) respeta los ejes suministrados y no abre una ventana
por defecto.
Si no se conservó una solución numérica, solicitar un gráfico produce un error claro.
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
assert transition.converged, transition.summary()
continuation_table = pd.DataFrame(transition.continuation_history)
horizon_table = pd.DataFrame(transition.horizon_history)
assert continuation_table.iloc[-1]["fraction"] == 1.0
assert bool(horizon_table.iloc[-1]["accepted"])
exported = transition.to_frame()
pd.testing.assert_frame_equal(exported, transition.to_dataframe())
print(transition.to_markdown(head=3, digits=6))
assert r"\begin{tabular}" in transition.to_latex(head=3)
assert "#table(" in transition.to_typst(head=3)
figure, axes = plt.subplots()
assert transition.plot(ax=axes, show=False) is figure
plt.close(figure)
equation_audit = audit_result(transition.solution, checks=["equations"])
assert np.max(np.abs(equation_audit.maps["path_equations"])) < 1e-8
La auditoría ordinaria comprueba el experimento finito guardado independientemente
de la bandera de convergencia. Un residuo pequeño no certifica unicidad,
precisión de horizonte infinito ni optimalidad estocástica. Los tests de
tests/test_dsge_transition_workflows.py cubren trayectorias lineales y de raíz
estable exactas, coincidencia de derivadas, presupuestos y extremos exactos de
continuación, dominios/escalas, fechas de información, signos MCP, auditorías
terminales, reutilización de precondicionadores y exportaciones. Establecen
esos contratos en los casos probados, no convergencia universal para cualquier modelo.