Source code for ogzaf.warm_start
"""
Warm-start the steady-state solve from a previously solved state.
OG-Core cold-starts every steady-state solve from constants (household savings
and labour at fixed values, the outer unknowns from the ``initial_guess_*``
parameters). That seed can sit far from the solution, and the single
constants OG-Core accepts for ``b`` and ``n`` cannot carry the shape of a
solved distribution. This module seeds the solver from a state that has
already solved: the household matrices and every outer unknown together, so
they are mutually consistent, with ``factor`` passed directly.
``b`` and ``n`` are in model units, so a seed stays valid across modest
parameter moves. Regenerate it with ``python examples/save_warm_start.py``
after any large recalibration.
Ported from OG-KOR's ogkor/warm_start.py (itself from OG-JPN).
"""
import os
import pickle
import numpy as np
import ogcore.SS as SS
from ogcore import firm
CUR_PATH = os.path.abspath(os.path.dirname(__file__))
SEED_PATH = os.path.join(CUR_PATH, "data", "ss_warm_start.pkl")
[docs]
def save_seed(ss_vars, path=SEED_PATH):
"""
Store the parts of a solved steady state needed to warm-start another.
Args:
ss_vars (dict): a solved ``SS_vars.pkl``
path (str): where to write the seed
Returns:
str: the path written
"""
def scalar(x):
return float(np.asarray(x).sum()) if np.ndim(x) else float(x)
seed = {
"b": np.asarray(ss_vars["b_sp1"], dtype=float),
"n": np.asarray(ss_vars["n"], dtype=float),
"r": scalar(ss_vars["r"]),
"r_p": scalar(ss_vars["r_p"]),
"TR": scalar(ss_vars["TR"]),
"factor": scalar(ss_vars["factor"]),
"BQ": np.asarray(ss_vars["BQ"], dtype=float).ravel(),
}
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wb") as fh:
pickle.dump(seed, fh)
return path
[docs]
def enable(path=SEED_PATH):
"""
Patch ``SS.SS_initial_guesses`` to start from the stored seed.
Call once before solving. Does nothing if no seed is stored, so a fresh
checkout still runs (slowly) rather than failing.
Args:
path (str): seed written by :func:`save_seed`
Returns:
bool: True if a warm start is now active
"""
if not os.path.exists(path):
return False
with open(path, "rb") as fh:
seed = pickle.load(fh)
original = SS.SS_initial_guesses
def warm(p, b_val=None, n_val=None, r_tr_scalars=[1.0, 1.0]):
if seed["b"].shape != (p.S, p.J) or seed["BQ"].size != p.J:
# Seed built for different model dimensions; fall back rather than
# hand the solver a wrongly-shaped guess.
return original(p, b_val, n_val, r_tr_scalars)
# Scale by the retry-ladder factors so ogcore's DEV_FACTOR_LIST can
# still explore around the warm point if the first attempt fails.
r_g = r_tr_scalars[0] * seed["r"]
r_p_g = r_tr_scalars[0] * seed["r_p"]
TR_g = r_tr_scalars[1] * seed["TR"]
Y_g = TR_g / p.alpha_T[-1]
BQ_g = seed["BQ"] * r_tr_scalars[1]
BQ_items = [BQ_g.sum()] if p.use_zeta else list(BQ_g)
w_g = firm.get_w_from_r(r_g, p, "SS")
guesses = (
[r_p_g, r_g, w_g] + list(np.ones(p.M)) + [Y_g] + BQ_items + [TR_g]
)
if p.baseline:
# Passed directly: as a parameter it can exceed the 500,000 cap.
guesses = guesses + [seed["factor"]]
return guesses, seed["b"].copy(), seed["n"].copy()
SS.SS_initial_guesses = warm
return True