diff --git a/.gitignore b/.gitignore index afc7adc..4526876 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ __pycache__/ .ipynb_checkpoints/ .DS_Store -.vscode +.vscode/ +.pytest_cache/ *.zip scripts/ +wiki/ +*.egg-info/ diff --git a/notebooks/hank.ipynb b/notebooks/hank.ipynb index a52cc83..8871724 100644 --- a/notebooks/hank.ipynb +++ b/notebooks/hank.ipynb @@ -10,7 +10,6 @@ "2. [Solve for a steady state with multiple calibration targets](#2-calibration)\n", "3. [Compute linearized impulse responses: unwrap convenience function](#3-linear)\n", "4. [Compute nonlinear impulse responses: quasi-Newton performs well even for large nonlinearities](#4-nonlinear)\n", - "5. [Check local determinacy](#5-determinacy)\n", "\n", "This notebook accompanies the working paper by Auclert, Bardóczy, Rognlie, Straub (2019): \"Using the Sequence-Space Jacobian to Solve and Estimate Heterogeneous-Agent Models\". Please see the [Github repository](https://github.com/shade-econ/sequence-jacobian) for more information and code.\n", "\n", @@ -89,8 +88,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj\n", - "from sequence_jacobian import simple, het" + "from sequence_jacobian import simple, het, create_model\n", + "from sequence_jacobian.models import hank" ] }, { @@ -133,7 +132,10 @@ "metadata": {}, "outputs": [], "source": [ - "def transfers(pi_e, Div, Tax, e_grid, div_rule, tax_rule): \n", + "def transfers(pi_e, Div, Tax, e_grid):\n", + " # default incidence rules are proportional to skill\n", + " tax_rule, div_rule = e_grid, e_grid # scale does not matter, will be normalized anyway\n", + "\n", " div = Div / np.sum(pi_e * div_rule) * div_rule\n", " tax = Tax / np.sum(pi_e * tax_rule) * tax_rule\n", " T = div - tax\n", @@ -155,8 +157,8 @@ "metadata": {}, "outputs": [], "source": [ - "sj.hank.household.add_hetinput(transfers, overwrite=True, verbose=False)\n", - "household = sj.hank.household" + "household = hank.household\n", + "household.add_hetinput(transfers, overwrite=True, verbose=False)" ] }, { @@ -173,77 +175,26 @@ "\n", "\n", "## 2 Calibrating the steady state\n", - "Similarly to the RBC example, we calibrate the discount factor $\\beta$ and disutility of labor $\\varphi$ to hit a target for the interest rate and effective labor $L=1.$\n", - "\n", - "This is a two-dimensional rootfinding problem that we solve by Broyden's method, which we implemented in ``utilities/solvers.py``. It takes a function $f: \\mathbb{R}^n \\to \\mathbb{R}^n$ and an initial guess for its roots, $x_0 \\in \\mathbb{R}^n$, and backtracks whenever $f$ returns a `ValueError`.\n", - "\n", - "The calibration has two substantive steps. First, express analytically all variables that don't depend on $(\\beta, \\varphi).$ Second, construct the residual function that takes the current guesses $(\\beta, \\varphi)$ and maps them into deviations from the calibration targets. This just requires an evaluation of the household block. The rootfinder does the rest. \n", - "\n", - "Although additional efficiency gains would be possible here (for instance, by updating our initial guesses for policy and distribution along the way), we will not implement them, since they are not our focus here." + "Similarly to the RBC example, we calibrate the discount factor $\\beta$ and disutility of labor $\\varphi$ to hit a target for the interest rate and effective labor $L=1.$ Additionally we calibrate the wage $w$ such that the Phillips curve relation is satisfied in steady state." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ - "def hank_ss(beta_guess=0.986, vphi_guess=0.8, r=0.005, eis=0.5, frisch=0.5, mu=1.2, B_Y=5.6, rho_s=0.966, sigma_s=0.5,\n", - " kappa=0.1, phi=1.5, nS=7, amax=150, nA=500, tax_rule=None, div_rule=None):\n", - " \"\"\"Solve steady state of full GE model. Calibrate (beta, vphi) to hit target for interest rate and Y.\"\"\"\n", - "\n", - " # set up grid\n", - " a_grid = sj.utilities.discretize.agrid(amax=amax, n=nA)\n", - " e_grid, pi_e, Pi = sj.utilities.discretize.markov_rouwenhorst(rho=rho_s, sigma=sigma_s, N=nS)\n", - " \n", - " # default incidence rules are proportional to skill\n", - " if tax_rule is None:\n", - " tax_rule = e_grid # scale does not matter, will be normalized anyway\n", - " if div_rule is None:\n", - " div_rule = e_grid\n", - " assert len(tax_rule) == len(div_rule) == len(e_grid), 'Incidence rules are inconsistent with income grid.'\n", - "\n", - " # solve analytically what we can\n", - " B = B_Y\n", - " w = 1 / mu\n", - " Div = (1 - w)\n", - " Tax = r * B\n", - " T = transfers(pi_e, Div, Tax, e_grid, div_rule, tax_rule)\n", - "\n", - " # initialize guess for policy function iteration\n", - " fininc = (1 + r) * a_grid + T[:, np.newaxis] - a_grid[0]\n", - " coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] + T[:, np.newaxis]\n", - " Va = (1 + r) * (0.1 * coh) ** (-1 / eis)\n", - "\n", - " # residual function\n", - " def res(x):\n", - " beta_loc, vphi_loc = x\n", - " # precompute constrained c and n which don't depend on Va\n", - " c_const_loc, n_const_loc = sj.hank.solve_cn(w * e_grid[:, np.newaxis], fininc, eis, frisch, vphi_loc, Va)\n", - " if beta_loc > 0.999 / (1 + r) or vphi_loc < 0.001:\n", - " raise ValueError('Clearly invalid inputs')\n", - " out = household.ss(Va=Va, Pi=Pi, a_grid=a_grid, e_grid=e_grid, pi_e=pi_e, w=w, r=r, beta=beta_loc, eis=eis,\n", - " Div=Div, Tax=Tax, frisch=frisch, vphi=vphi_loc, c_const=c_const_loc, n_const=n_const_loc,\n", - " tax_rule=tax_rule, div_rule=div_rule, ssflag=True)\n", - " return np.array([out['A'] - B, out['N_e'] - 1])\n", + "blocks = [household, hank.firm, hank.monetary, hank.fiscal, hank.mkt_clearing, hank.nkpc,\n", + " hank.income_state_vars, hank.asset_state_vars]\n", + "hank_model = create_model(blocks, name=\"One Asset HANK\")\n", "\n", - " # solve for beta, vphi\n", - " (beta, vphi), _ = sj.utilities.solvers.broyden_solver(res, np.array([beta_guess, vphi_guess]), verbose=False)\n", + "calibration = {\"r\": 0.005, \"rstar\": 0.005, \"eis\": 0.5, \"frisch\": 0.5, \"B_Y\": 5.6, \"B\": 5.6, \"mu\": 1.2,\n", + " \"rho_s\": 0.966, \"sigma_s\": 0.5, \"kappa\": 0.1, \"phi\": 1.5, \"Y\": 1, \"Z\": 1, \"L\": 1,\n", + " \"pi\": 0, \"nS\": 7, \"amax\": 150, \"nA\": 500}\n", + "unknowns_ss = {\"beta\": 0.986, \"vphi\": 0.8, \"w\": 0.8}\n", + "targets_ss = {\"asset_mkt\": 0, \"labor_mkt\": 0, \"nkpc_res\": 0.}\n", "\n", - " # extra evaluation for reporting\n", - " c_const, n_const = sj.hank.solve_cn(w * e_grid[:, np.newaxis], fininc, eis, frisch, vphi, Va)\n", - " ss = household.ss(Va=Va, Pi=Pi, a_grid=a_grid, e_grid=e_grid, pi_e=pi_e, w=w, r=r, beta=beta, eis=eis,\n", - " Div=Div, Tax=Tax, frisch=frisch, vphi=vphi, c_const=c_const, n_const=n_const,\n", - " tax_rule=tax_rule, div_rule=div_rule, ssflag=True)\n", - " \n", - " # check Walras's law\n", - " walras = 1 - ss['C']\n", - " assert np.abs(walras) < 1E-8\n", - " \n", - " # add aggregate variables\n", - " ss.update({'B': B, 'phi': phi, 'kappa': kappa, 'Y': 1, 'rstar': r, 'Z': 1, 'mu': mu, 'L': 1, 'pi': 0,\n", - " 'walras': walras, 'ssflag': False})\n", - " return ss" + "ss = hank_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { @@ -255,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -272,8 +223,7 @@ } ], "source": [ - "ss = hank_ss()\n", - "plt.plot(ss['a_grid'], ss['n'].T)\n", + "plt.plot(ss['a_grid'], ss.internal[\"household\"]['n'].T)\n", "plt.xlabel('Assets'), plt.ylabel('Labor supply')\n", "plt.show()" ] @@ -318,7 +268,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.1 Define simple blocks" + "### 3.1 Cut to the chase\n", + "The recommended way to obtain the general equilibrium Jacobians is to use the `solve_jacobian` method for the `hank_model` object." ] }, { @@ -326,50 +277,6 @@ "execution_count": 6, "metadata": {}, "outputs": [], - "source": [ - "@simple\n", - "def firm(Y, w, Z, pi, mu, kappa):\n", - " L = Y / Z\n", - " Div = Y - w * L - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y\n", - " return L, Div\n", - "\n", - "@simple\n", - "def monetary(pi, rstar, phi):\n", - " r = (1 + rstar(-1) + phi * pi(-1)) / (1 + pi) - 1\n", - " return r\n", - "\n", - "@simple\n", - "def fiscal(r, B):\n", - " Tax = r * B\n", - " return Tax\n", - "\n", - "@simple\n", - "def mkt_clearing(A, N_e, C, L, Y, B, pi, mu, kappa):\n", - " asset_mkt = A - B\n", - " labor_mkt = N_e - L\n", - " goods_mkt = Y - C - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y\n", - " return asset_mkt, labor_mkt, goods_mkt\n", - "\n", - "@simple\n", - "def nkpc(pi, w, Z, Y, r, mu, kappa):\n", - " nkpc_res = kappa * (w / Z - 1 / mu) + Y(+1) / Y *\\\n", - " (1 + pi(+1)).apply(np.log) / (1 + r(+1)) - (1 + pi).apply(np.log)\n", - " return nkpc_res" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 3.2 Cut to the chase\n", - "The surest way to obtain the general equilibrium Jacobians is to use the `get_G` convenience function. Notice the `save=True` option. This means that we're saving the HA Jacobians calculated along the way for later use." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], "source": [ "# setup\n", "T = 300\n", @@ -378,17 +285,16 @@ "targets = ['nkpc_res', 'asset_mkt', 'labor_mkt']\n", "\n", "# general equilibrium jacobians\n", - "block_list = [firm, monetary, fiscal, nkpc, mkt_clearing, household] \n", - "G = sj.get_G(block_list, exogenous, unknowns, targets, T, ss, save=True)" + "G = hank_model.solve_jacobian(ss, exogenous, unknowns, targets, T=T)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.3 Break down `get_G`\n", + "### 3.2 Break down `solve_jacobian`\n", "\n", - "Under the hood, the very powerful `jac.get_G` performs the following steps:\n", + "Under the hood, the `solve_jacobian` method performs the following steps:\n", " - orders the blocks so that we move forward along the model's DAG\n", " - computes the partial Jacobians $\\mathcal{J}^{o,i}$ from all blocks (if their Jacobian is not supplied already), only with respect to the inputs that actually change: unknowns, exogenous shocks, outputs of earlier blocks\n", " - forward accumulates partial Jacobians $\\mathcal{J}^{o,i}$ to form total Jacobians $\\mathbf{J}^{o,i}$\n", @@ -409,58 +315,61 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "curlyJs, required = sj.jacobian.curlyJ_sorted(block_list, unknowns+exogenous, ss, T)" + "import sequence_jacobian.jacobian.drivers as jacobian\n", + "from sequence_jacobian.jacobian.classes import JacobianDict\n", + "\n", + "curlyJs, required = jacobian.curlyJ_sorted(blocks, unknowns + exogenous, ss, T)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The first output `curlyJs` is a list of nested dictionaries. Each entry in the list contains all the necessary Jacobians for the corresponding block. Blocks are ordered according to the topological sort.\n", + "The first output `curlyJs` is a list of `JacobianDict` objects. Each `JacobianDict` contains all the necessary Jacobians for the corresponding block. Blocks are ordered according to the topological sort.\n", "\n", "For example, the first block is `monetary`, because it only takes an unknown $\\pi$ and an exogenous $r^*$ as inputs. Let's take a look. " ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'r': {'pi': SimpleSparse({(-1, 0): 1.500, (0, 0): -1.005}), 'rstar': SimpleSparse({(-1, 0): 1.000})}}\n" + "The JacobianDict for the monetary block is: \n" ] } ], "source": [ - "print(curlyJs[0])" + "print(f\"The JacobianDict for the monetary block is: {curlyJs[0]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Since this is a simple block, the Jacobians are represented as a instances of the `SimpleSparse` class. Note that `jac.curlyJ_sorted` correctly determined that it is not necessary to differentiate with respect to the Taylor rule parameter $\\phi$ (if we wanted to consider shocks to this parameter, we'd just have to include it among the exogenous inputs.)\n", + "Note that `curlyJ_sorted` correctly determined that it is not necessary to differentiate with respect to the Taylor rule parameter $\\phi$ (if we wanted to consider shocks to this parameter, we'd just have to include it among the exogenous inputs.)\n", "\n", "The second output `required` is a set of extra variables (not unknowns and exogenous) that we have to differentiate with respect to, because they are outputs of some blocks and inputs of others. " ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'r', 'Tax', 'Div', 'C', 'A', 'N_e', 'L'}\n" + "{'A', 'C', 'N_e', 'r', 'Div', 'L', 'Tax'}\n" ] } ], @@ -480,23 +389,12 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_keys(['nkpc_res', 'asset_mkt', 'labor_mkt'])\n", - "dict_keys(['pi', 'Y', 'w'])\n" - ] - } - ], + "outputs": [], "source": [ - "J_curlyH_U = sj.jacobian.forward_accumulate(curlyJs, unknowns, targets, required)\n", - "J_curlyH_Z = sj.jacobian.forward_accumulate(curlyJs, exogenous, targets, required)\n", - "print(J_curlyH_U.keys())\n", - "print(J_curlyH_U['asset_mkt'].keys())" + "J_curlyH_U = jacobian.forward_accumulate(curlyJs, unknowns, targets, required)\n", + "J_curlyH_Z = jacobian.forward_accumulate(curlyJs, exogenous, targets, required)" ] }, { @@ -508,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -521,8 +419,8 @@ } ], "source": [ - "H_U = sj.jacobian.pack_jacobians(J_curlyH_U, unknowns, targets, T)\n", - "H_Z = sj.jacobian.pack_jacobians(J_curlyH_Z, exogenous, targets, T)\n", + "H_U = J_curlyH_U[targets, unknowns].pack(T)\n", + "H_Z = J_curlyH_Z[targets, exogenous].pack(T)\n", "print(H_U.shape)\n", "print(H_Z.shape)" ] @@ -537,20 +435,11 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 17, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "dict_keys(['pi', 'w', 'Y'])\n" - ] - } - ], + "outputs": [], "source": [ - "G_U = sj.jacobian.unpack_jacobians(-np.linalg.solve(H_U, H_Z), exogenous, unknowns, T)\n", - "print(G_U.keys())" + "G_U = JacobianDict.unpack(-np.linalg.solve(H_U, H_Z), unknowns, exogenous, T)" ] }, { @@ -562,27 +451,27 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ - "curlyJs = [G_U] + curlyJs\n", - "outputs = set().union(*(curlyJ.keys() for curlyJ in curlyJs)) - set(targets)\n", + "curlyJs_aug = [G_U] + curlyJs\n", + "outputs = set().union(*(curlyJ.outputs for curlyJ in curlyJs_aug)) - set(targets)\n", "\n", - "G2 = sj.jacobian.forward_accumulate(curlyJs, exogenous, outputs, required | set(unknowns))" + "G2 = jacobian.forward_accumulate(curlyJs_aug, exogenous, outputs, required | set(unknowns))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.4 Results\n", + "### 3.3 Results\n", "First let's check that we have correctly reconstructed the steps of `jac.get_G`." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -600,12 +489,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 20, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -649,7 +538,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -677,24 +566,7 @@ " max error for asset_mkt is 5.01E-10\n", " max error for labor_mkt is 1.26E-11\n" ] - } - ], - "source": [ - "rho_r, sig_r = 0.61, -0.01/4\n", - "drstar = sig_r * rho_r ** (np.arange(T))\n", - "rstar = ss['r'] + drstar\n", - "\n", - "H_U = sj.get_H_U(block_list, unknowns, targets, T, ss, use_saved=True)\n", - "H_U_factored = sj.utilities.misc.factor(H_U)\n", - "\n", - "td_nonlin = sj.td_solve(ss, block_list, unknowns, targets, H_U_factored=H_U_factored,rstar=rstar)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ + }, { "data": { "image/png": "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\n", @@ -709,8 +581,14 @@ } ], "source": [ - "dC_lin = 100 * G['C']['rstar'] @ drstar / ss['C']\n", - "dC_nonlin = 100 * (td_nonlin['C']/ss['C'] - 1) \n", + "rho_r, sig_r = 0.61, -0.01/4\n", + "rstar_shock_path = {\"rstar\": sig_r * rho_r ** (np.arange(T))}\n", + "\n", + "td_nonlin = hank_model.solve_impulse_nonlinear(ss, rstar_shock_path, unknowns, targets)\n", + "td_lin = hank_model.solve_impulse_linear(ss, rstar_shock_path, unknowns, targets)\n", + "\n", + "dC_nonlin = 100 * td_nonlin.deviations().normalize()[\"C\"]\n", + "dC_lin = 100 * td_lin.normalize()[\"C\"]\n", "\n", "plt.plot(dC_lin[:21], label='linear', linestyle='-', linewidth=2.5)\n", "plt.plot(dC_nonlin[:21], label='nonlinear', linestyle='--', linewidth=2.5)\n", @@ -731,7 +609,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -795,13 +673,13 @@ ], "source": [ "rho_r, sig_r = 0.61, -0.10/4\n", - "drstar = sig_r * rho_r ** (np.arange(T))\n", - "rstar = ss['r'] + drstar\n", + "rstar_shock_path = {\"rstar\": sig_r * rho_r ** (np.arange(T))}\n", "\n", - "td_nonlin = sj.td_solve(ss, block_list, unknowns, targets, H_U_factored=H_U_factored, rstar=rstar)\n", + "td_nonlin = hank_model.solve_impulse_nonlinear(ss, rstar_shock_path, unknowns, targets)\n", + "td_lin = hank_model.solve_impulse_linear(ss, rstar_shock_path, unknowns, targets)\n", "\n", - "dC_lin = 100 * G['C']['rstar'] @ drstar / ss['C']\n", - "dC_nonlin = 100 * (td_nonlin['C']/ss['C'] - 1) \n", + "dC_nonlin = 100 * td_nonlin.deviations().normalize()[\"C\"]\n", + "dC_lin = 100 * td_lin.normalize()[\"C\"]\n", "\n", "plt.plot(dC_lin[:21], label='linear', linestyle='-', linewidth=2.5)\n", "plt.plot(dC_nonlin[:21], label='nonlinear', linestyle='--', linewidth=2.5)\n", @@ -811,405 +689,6 @@ "plt.legend()\n", "plt.show()" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## 5. Local determinacy\n", - "Local determinacy boils down to the invertibility of the matrix $H_U$. The steady state is a locally-determinate equilibrium if and only if $H_U$ is invertible. \n", - "\n", - "**Numerical approach.** In practice, $H_U$ is obtained numerically for a finite horizon, and thus we can never expect it to be exactly singular, even if equilibrium is indeterminate. Still, near-singularity of $H_U$, especially when it becomes more drastic as the truncation horizon $T$ is increased, is a likely indication of indeterminacy.\n", - "\n", - "In practice, we have found that indeterminacy is best detected by looking at the last few singular values: if the smallest is discontinuously smaller than the second and third smallest, then indeterminacy is likely.\n", - "\n", - "**Our contribution: winding number criterion.** A better solution is to use the winding number criterion introduced in our paper, which rapidly gives an exact answer. This criterion exploits the \"asymptotic time invariant\" structure of the Jacobians in SHADE models: within each Jacobian, each diagonal eventually converges to some constant, and these constants are close to zero far enough away from the main diagonal.\n", - "\n", - "Given knowledge of the asymptotic structure of $H_U$, which is encoded in an array $A$, the criterion calculates the \"winding number\" of the curve\n", - "\n", - "$$\n", - "\\det A(\\lambda) = \\det\\sum_{j=-\\infty}^\\infty A_j e^{ij\\lambda} \\tag{1}\n", - "$$\n", - "\n", - "as $\\lambda$ varies from $0$ to $2\\pi$. Here, $A_j$ is the $n_u\\times n_u$ matrix representing the asymptotic value on the $j$th diagonal above the main diagonal for all pairs of targets and unknowns. The \"winding number\" is the number of times the curve (1) wraps counterclockwise around the origin in the complex plane.\n", - "\n", - "A winding number of 0 indicates that the model has a unique solution around the steady state, while a winding number of -1 or less indicates indeterminacy.\n", - "\n", - "**Example in our HANK model.** As it is well-known, determinacy in the New Keynesian models requires that the interest rate rule is sufficiently responsive to inflation. Therefore, we're going to illustrate the issue by varying the parameter $\\phi$ and tracing its effect on $H_U.$\n", - "\n", - "### 5.1 Stable case\n", - "Let's start with the the baseline calibration with $\\phi=1.5$. Both approaches show the model is determinate, as expected." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Smallest singular values: 0.0720, 0.0715, 0.0715\n", - "Winding number: 0\n" - ] - } - ], - "source": [ - "# smallest singular values\n", - "_, s, _ = np.linalg.svd(H_U)\n", - "print(f'Smallest singular values: {s[-3]:.4f}, {s[-2]:.4f}, {s[-1]:.4f}')\n", - "\n", - "# winding number test\n", - "# first, use get_H_U with asymptotic=True to get array A representing asymptotic H_U\n", - "A = sj.get_H_U(block_list, unknowns, targets, T, ss, asymptotic=True, save=True, use_saved=True)\n", - "\n", - "# then apply winding number criterion\n", - "wn = sj.determinacy.winding_criterion(A)\n", - "print(f'Winding number: {wn}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.2 Unstable case\n", - "Let's see what happens with $\\phi=0.75$. First of all, we'll have to recompute the Jacobian. It's important to realize that $\\phi$ does not affect the steady state, and affects dynamics only through the monetary block. Thus, recomputing the Jacobians of the household block would be wasteful. We can avoid this by setting ``use_saved=True``. " - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "ss2 = {**ss, 'phi': 0.75}\n", - "H_U2 = sj.get_H_U(block_list, unknowns, targets, T, ss2, use_saved=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This time both tests reveal clear indeterminacy: the smallest singular value is discontinuously smaller than the others, and the winding number is -1." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Smallest singular values: 0.0967, 0.0960, 0.0000\n", - "Winding number: -1\n" - ] - } - ], - "source": [ - "# smallest singular values\n", - "_, s2, _ = np.linalg.svd(H_U2)\n", - "print(f'Smallest singular values: {s2[-3]:.4f}, {s2[-2]:.4f}, {s2[-1]:.4f}')\n", - "\n", - "# winding number\n", - "A2 = sj.get_H_U(block_list, unknowns, targets, T, ss2, asymptotic=True, use_saved=True)\n", - "wn2 = sj.determinacy.winding_criterion(A2)\n", - "print(f'Winding number: {wn2}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Not surprisingly, if we tried to use this Jacobian to compute impulse responses, we'd fail. (We'll wrap in a try/except block to avoid a giant error message.)" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "On iteration 0\n", - " max error for nkpc_res is 0.00E+00\n", - " max error for asset_mkt is 1.41E-01\n", - " max error for labor_mkt is 2.68E-02\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\michaelcai\\PycharmProjects\\sequence-jacobian\\sequence_jacobian\\blocks\\support\\simple_displacement.py:207: RuntimeWarning: invalid value encountered in log\n", - " return Displace(f(numeric_primitive(self), **kwargs), ss=f(self.ss))\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "On iteration 1\n", - " max error for nkpc_res is NAN\n", - " max error for asset_mkt is NAN\n", - " max error for labor_mkt is NAN\n", - "array must not contain infs or NaNs\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\michaelcai\\PycharmProjects\\sequence-jacobian\\sequence_jacobian\\models\\hank.py:41: RuntimeWarning: invalid value encountered in less\n", - " iconst = np.nonzero(a < a_grid[0])\n" - ] - } - ], - "source": [ - "try:\n", - " td_nonlin = sj.td_solve(ss2, block_list, unknowns, targets, H_U=H_U2, rstar=rstar)\n", - "except ValueError as e:\n", - " print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In fact, it fails after the first iteration: since the Jacobian is nearly singular, using its inverse in Newton's method leads to a very large step to the next guess, which then is outside the admissible domain and leads to an error within the household routine." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.3 Why use the winding number criterion?\n", - "\n", - "It's very fast and precise. We can use bisection, for instance, to get the exact threshold at which the model becomes determinate. It turns out that this is at approximately $\\phi=1.005$." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Threshold for determinacy: phi=1.005\n" - ] - } - ], - "source": [ - "phi_low = 0.8\n", - "phi_high = 1.2\n", - "while phi_high - phi_low > 1E-6:\n", - " phi_mid = (phi_low + phi_high)/2\n", - " ss_cur = {**ss, 'phi': phi_mid}\n", - " A_cur = sj.get_H_U(block_list, unknowns, targets, T, ss_cur,\n", - " asymptotic=True, use_saved=True)\n", - " wn_cur = sj.determinacy.winding_criterion(A_cur)\n", - " if wn_cur == 0:\n", - " phi_high = phi_mid\n", - " else:\n", - " phi_low = phi_mid\n", - "phi_threshold = (phi_low + phi_high)/2\n", - "print(f'Threshold for determinacy: phi={phi_threshold:.3f}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can compare to the results from looking at singular values. Specifically, we'll look at the ratio of the smallest to the second-smallest singular value for a range of $\\phi$ around the determinacy threshold we've identified.\n", - "\n", - "This takes several seconds, because the singular value decomposition is costly and we need to redo it for every $\\phi$." - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "# non-uniform grid of phis to get extra precision near\n", - "# where we know from winding number test the threshold lies\n", - "phis = np.unique(np.concatenate((np.linspace(0.99, 1.00, 5),\n", - " np.linspace(1.00, 1.01, 10),\n", - " np.linspace(1.01, 1.02, 5))))\n", - "\n", - "sv_ratio = np.empty_like(phis)\n", - "for it, phi in enumerate(phis):\n", - " ss_cur = {**ss, 'phi': phi}\n", - " H_U_cur = sj.get_H_U(block_list, unknowns, targets, T, ss_cur, use_saved=True)\n", - "\n", - " _, s, _ = np.linalg.svd(H_U_cur)\n", - " sv_ratio[it] = s[-1] / s[-2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's plot this ratio against the winding number plus 1, which jumps up at the determinacy threshold we've already calculated." - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# winding number plus 1 jumps up at phi_threshold\n", - "phis_wn = [phis[0], phi_threshold, phi_threshold, phis[-1]]\n", - "wns = [0, 0, 1, 1]\n", - "\n", - "plt.plot(phis_wn, wns, linewidth=2, label=r'winding number + 1')\n", - "plt.plot(phis, sv_ratio, linewidth=2, label=r'singular value ratio', linestyle='--')\n", - "plt.legend(framealpha=0)\n", - "plt.xlabel(r'Taylor rule coefficient $\\phi$');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We see that the two approaches give consistent answers, but the winding number approach is far more precise and immediate." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 5.4 Visualizing the winding number criterion\n", - "To see how this works, we can also directly plot the curve $\\det A(\\lambda)$ for which we're taking the winding number. The function `det.detA_path`, which is called by `det.winding_criterion` under the hood, provides this.\n", - "\n", - "**Indeterminate case.** First let's do so for an indeterminate case $\\phi=1.001$." - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "phi = 1.001\n", - "ss_cur = {**ss, 'phi': phi}\n", - "A_cur = sj.get_H_U(block_list, unknowns, targets, T, ss_cur,\n", - " asymptotic=True, use_saved=True)\n", - "\n", - "det_Alambda = sj.determinacy.detA_path(A_cur)\n", - "x, y = det_Alambda.real, det_Alambda.imag\n", - "\n", - "# plot curve\n", - "plt.plot(x, y, label=r'$\\det A(\\lambda)$', linewidth=3);\n", - "\n", - "# dot for origin\n", - "plt.plot(0, 0, marker='o', markersize=5, color=\"black\")\n", - "\n", - "# arrow to show orientation (using rate of change around lambda=0)\n", - "plt.arrow(x[0], y[0], 0.001*(x[1]-x[-2]), 0.001*(y[1]-y[-2]), color='C0',\n", - " width=0.0001, head_width=0.05, head_length=0.08)\n", - "plt.title(r'Indeterminate case: $\\phi=1.001$')\n", - "plt.xlabel(r'Real')\n", - "plt.ylabel(r'Imaginary');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We clearly see the winding number of -1 here, corresponding to a single clockwise trajectory around the origin.\n", - "\n", - "**Determinate case.** Now let's try the same for $\\phi=1.007$." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "phi = 1.007\n", - "ss_cur = {**ss, 'phi': phi}\n", - "A_cur = sj.get_H_U(block_list, unknowns, targets, T, ss_cur,\n", - " asymptotic=True, use_saved=True)\n", - "\n", - "det_Alambda = sj.determinacy.detA_path(A_cur)\n", - "x, y = det_Alambda.real, det_Alambda.imag\n", - "\n", - "# plot curve\n", - "plt.plot(x, y, label=r'$\\det A(\\lambda)$', linewidth=3);\n", - "\n", - "# dot for origin\n", - "plt.plot(0, 0, marker='o', markersize=5, color=\"black\")\n", - "\n", - "# arrow to show orientation (using rate of change around lambda=0)\n", - "plt.arrow(x[0], y[0], 0.001*(x[1]-x[-2]), 0.001*(y[1]-y[-2]), color='C0',\n", - " width=0.0001, head_width=0.05, head_length=0.08)\n", - "plt.title(r'Determinate case: $\\phi=1.007$')\n", - "plt.xlabel(r'Real')\n", - "plt.ylabel(r'Imaginary');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here the winding number is zero: the curve has shifted such that it no longer wraps around the origin at all." - ] } ], "metadata": { diff --git a/notebooks/het_jacobian.ipynb b/notebooks/het_jacobian.ipynb index ae1c365..93f871f 100644 --- a/notebooks/het_jacobian.ipynb +++ b/notebooks/het_jacobian.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -30,7 +30,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj" + "from sequence_jacobian import create_model\n", + "from sequence_jacobian.models import hank" ] }, { @@ -45,12 +46,21 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "ss = sj.hank.hank_ss()\n", - "household = sj.hank.household" + "blocks = [hank.household, hank.firm, hank.monetary, hank.fiscal, hank.mkt_clearing, hank.nkpc,\n", + " hank.income_state_vars, hank.asset_state_vars]\n", + "hank_model = create_model(blocks, name=\"One Asset HANK\")\n", + "\n", + "calibration = {\"r\": 0.005, \"rstar\": 0.005, \"eis\": 0.5, \"frisch\": 0.5, \"B_Y\": 5.6, \"B\": 5.6, \"mu\": 1.2,\n", + " \"rho_s\": 0.966, \"sigma_s\": 0.5, \"kappa\": 0.1, \"phi\": 1.5, \"Y\": 1, \"Z\": 1, \"L\": 1,\n", + " \"pi\": 0, \"nS\": 7, \"amax\": 150, \"nA\": 500}\n", + "unknowns_ss = {\"beta\": 0.986, \"vphi\": 0.8, \"w\": 0.8}\n", + "targets_ss = {\"asset_mkt\": 0, \"labor_mkt\": 0, \"nkpc_res\": 0.}\n", + "\n", + "ss = hank_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { @@ -63,42 +73,28 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "T = 300\n", - "shock_list=['w', 'r', 'Div', 'Tax']\n", - "Js = household.jac(ss, T, shock_list)" - ] - }, - { - "cell_type": "markdown", + "execution_count": 12, "metadata": {}, - "source": [ - "`Js` is a nested dict, with keys on the first level being aggregate outputs:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "scrolled": true - }, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['N_e', 'A', 'C', 'N'])" + "" ] }, - "execution_count": 4, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "Js.keys()" + "T = 300\n", + "exogenous = ['w', 'r', 'Div', 'Tax']\n", + "\n", + "household = hank.household\n", + "\n", + "Js = household.jacobian(ss, exogenous=exogenous, T=T)\n", + "Js" ] }, { @@ -107,23 +103,23 @@ "source": [ "Note that we have Jacobians for four outputs: assets `A`, consumption `C`, labor `N`, and skill-weighted labor `NS` (which is the effective labor provided to firms).\n", "\n", - "We have these four outputs since we did not specify a list of outputs, and the default of the `jac` function is to calculate Jacobians for all outputs reported by the HetBlock (i.e. all outputs of the underlying function `hank.household()`).\n", + "We have these four outputs since we did not specify a list of outputs, and the default of the `jacobian` function is to calculate Jacobians for all outputs reported by the `HetBlock` (i.e. all outputs of the underlying function `hank.household()`).\n", "\n", "For each output—for instance, assets `A`—we have Jacobians for all four inputs we asked about." ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['r', 'Tax', 'w', 'Div'])" + "dict_keys(['Tax', 'Div', 'w', 'r'])" ] }, - "execution_count": 5, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -141,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 14, "metadata": { "scrolled": true }, @@ -152,7 +148,7 @@ "(300, 300)" ] }, - "execution_count": 6, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -172,7 +168,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 15, "metadata": { "scrolled": true }, @@ -207,7 +203,7 @@ "metadata": {}, "source": [ "### 1.2 Under the hood: the fake news algorithm\n", - "How did the `.jac()` method calculate all 16 300-by-300 Jacobians so quickly and automatically? Let's take a look inside the fake news algorithm. We'll go over `het.HetBlock.jac()` almost line-by-line, skipping only the saving and loading of saved data (which is not part of the algorithm), and providing additional detail in steps 3 and 4.\n", + "How did the `jacobian` method calculate all 16 300-by-300 Jacobians so quickly and automatically? Let's take a look inside the fake news algorithm. We'll go over the `jacobian` of `HetBlock` almost line-by-line, skipping only the saving and loading of saved data (which is not part of the algorithm) and providing additional detail in steps 3 and 4.\n", "\n", "**Preliminary processing of steady state.** First, there are some preliminaries. (This part is more specific to our code and notation, although useful for understanding the algorithm that comes later.)\n", "\n", @@ -216,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -225,7 +221,7 @@ "{'a', 'c', 'n', 'n_e'}" ] }, - "execution_count": 8, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -244,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -260,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 18, "metadata": { "scrolled": true }, @@ -268,10 +264,10 @@ { "data": { "text/plain": [ - "dict_keys(['Va_p', 'frisch', 'vphi', 'e_grid', 'a_grid', 'Pi_p', 'w', 'beta', 'r', 'T', 'eis'])" + "dict_keys(['Va_p', 'T', 'e_grid', 'beta', 'a_grid', 'frisch', 'w', 'Pi_p', 'eis', 'r', 'vphi'])" ] }, - "execution_count": 10, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -282,16 +278,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "dict_keys(['e_grid', 'pi_e', 'Div', 'Tax'])" + "dict_keys(['Tax', 'e_grid', 'Div', 'pi_e'])" ] }, - "execution_count": 11, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -309,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 20, "metadata": { "scrolled": true }, @@ -320,7 +316,7 @@ "(7, 7)" ] }, - "execution_count": 12, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -340,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -349,7 +345,7 @@ "(dict_keys(['a']), dict_keys(['a']), dict_keys(['a']))" ] }, - "execution_count": 13, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -367,7 +363,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 22, "metadata": { "scrolled": false }, @@ -378,7 +374,7 @@ "((7, 500), dtype('uint32'))" ] }, - "execution_count": 14, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -398,14 +394,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 805 ms\n" + "Wall time: 713 ms\n" ] } ], @@ -413,9 +409,9 @@ "%%time\n", "h = 1E-4\n", "curlyYs, curlyDs = {}, {}\n", - "for i in shock_list:\n", + "for i in exogenous:\n", " curlyYs[i], curlyDs[i] = household.backward_iteration_fakenews(i, output_list, ssin_dict,\n", - " ssout_list, ss['D'], Pi.T.copy(), sspol_i, sspol_pi,\n", + " ssout_list, ss.internal[\"household\"]['D'], Pi.T.copy(), sspol_i, sspol_pi,\n", " sspol_space, T, h, ss_for_hetinput)" ] }, @@ -428,7 +424,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -437,7 +433,7 @@ "300" ] }, - "execution_count": 16, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -448,7 +444,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 26, "metadata": { "scrolled": true }, @@ -486,7 +482,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 27, "metadata": { "scrolled": true }, @@ -497,7 +493,7 @@ "(7, 500)" ] }, - "execution_count": 18, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -522,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -562,14 +558,14 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 50.9 ms\n" + "Wall time: 38.9 ms\n" ] } ], @@ -577,7 +573,7 @@ "%%time\n", "curlyPs = {}\n", "for o in output_list:\n", - " curlyPs[o] = household.forward_iteration_fakenews(ss[o], Pi, sspol_i, sspol_pi, T-1)" + " curlyPs[o] = household.forward_iteration_fakenews(ss.internal[\"household\"][o], Pi, sspol_i, sspol_pi, T-1)" ] }, { @@ -591,7 +587,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -600,7 +596,7 @@ "(7, 500)" ] }, - "execution_count": 21, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -622,7 +618,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 31, "metadata": { "scrolled": true }, @@ -658,14 +654,14 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 85.8 ms\n" + "Wall time: 93.8 ms\n" ] } ], @@ -673,7 +669,7 @@ "%%time\n", "Fs = {o.capitalize(): {} for o in output_list}\n", "for o in output_list:\n", - " for i in shock_list:\n", + " for i in exogenous:\n", " F = np.empty((T,T))\n", " F[0, ...] = curlyYs[i][o]\n", " F[1:, ...] = curlyPs[o].reshape(T-1, -1) @ curlyDs[i].reshape(T, -1).T\n", @@ -699,7 +695,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 34, "metadata": {}, "outputs": [ { @@ -735,7 +731,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 35, "metadata": { "scrolled": true }, @@ -788,7 +784,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 37, "metadata": {}, "outputs": [ { @@ -804,7 +800,7 @@ "Js_original = Js\n", "Js = {o.capitalize(): {} for o in output_list}\n", "for o in output_list:\n", - " for i in shock_list:\n", + " for i in exogenous:\n", " # implement recursion (30): start with J=F and accumulate terms along diagonal\n", " J = Fs[o.capitalize()][i].copy()\n", " for t in range(1, J.shape[1]):\n", @@ -841,23 +837,23 @@ "## 2 Not using the fake news algorithm? Costlier, direct approach\n", "Suppose that we wanted to get the same Jacobians without using the fake news algorithm. The \"direct\" approach discussed in the paper (which could less charitably be called \"brute force\") uses numerical differentiation to do this.\n", "\n", - "The idea is to run `household.td`, which calculates the nonlinear household impulse response to a given shock, to a small version of each shock (multiplied by $h$) at each date $s$ from 0 up to $T$. We take the results, and rescaling by $h^{-1}$ we get each column $s$ of the Jacobian.\n", + "The idea is to run `household.impulse_nonlinear`, which calculates the nonlinear household impulse response to a given shock, to a small version of each shock (multiplied by $h$) at each date $s$ from 0 up to $T$. We take the results, and rescaling by $h^{-1}$ we get each column $s$ of the Jacobian.\n", "\n", - "One crucial caveat is that since the numerically calculated steady state is not exactly a fixed point of backward or forward iteration, applying `household.td` without any shocks to the steady state does not return exactly the steady state. This numerical error can become quite significant in calculating the Jacobian when we blow it up by $h^{-1}$. We address this below in a simple way: first running `household.td` without any shocks, and then subtracting all the results by this to get the numerical derivative.\n", + "One crucial caveat is that since the numerically calculated steady state is not exactly a fixed point of backward or forward iteration, applying `household.impulse_nonlinear` without any shocks to the steady state does not return exactly the steady state. This numerical error can become quite significant in calculating the Jacobian when we blow it up by $h^{-1}$. We address this below in a simple way: first running `household.impulse_nonlinear` without any shocks, and then subtracting all the results by this to get the numerical derivative.\n", "\n", "Below we differentiate with respect to only a single input, $r$, to avoid making the notebook run for too long." ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 1min 33s\n" + "Wall time: 1min 27s\n" ] } ], @@ -874,12 +870,12 @@ "# (better than subtracting by ss since ss not exact)\n", "# monotonic=True lets us know there is monotonicity of policy rule, makes TD run faster\n", "# .td requires at least one input 'shock', so we put in steady-state w\n", - "td_noshock = household.td(ss, w=np.full(T, ss['w']), monotonic=True)\n", + "td_noshock = household.impulse_nonlinear(ss, {\"w\": np.zeros(T)}, monotonic=True)\n", "\n", "for i in short_shock_list:\n", " # simulate with respect to a shock at each date up to T\n", " for t in range(T):\n", - " td_out = household.td(ss, **{i: ss[i]+h*(np.arange(T) == t)})\n", + " td_out = household.impulse_nonlinear(ss, {i: h*(np.arange(T) == t)})\n", " \n", " # store results as column t of J[o][i] for each outcome o\n", " for o in output_list:\n", @@ -890,26 +886,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that this took about 90 seconds. Since almost all the work is done separately for each shock, with all 4 input variables in `shock_list` this would take about 360 seconds.\n", + "We see that this took about 90 seconds. Since almost all the work is done separately for each shock, with all 4 input variables in `exogenous` this would take about 360 seconds.\n", "\n", "Compare this to the time needed to get the Jacobian using our fake news algorithm, which is around one second:" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 939 ms\n" + "Wall time: 859 ms\n" ] } ], "source": [ - "%time _ = household.jac(ss, T, shock_list)" + "%time _ = household.jacobian(ss, exogenous=exogenous, T=T)" ] }, { @@ -926,7 +922,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 43, "metadata": {}, "outputs": [ { diff --git a/notebooks/intro_to_sequence_jacobian.ipynb b/notebooks/intro_to_sequence_jacobian.ipynb index 9c82d5d..c63b670 100644 --- a/notebooks/intro_to_sequence_jacobian.ipynb +++ b/notebooks/intro_to_sequence_jacobian.ipynb @@ -6,6 +6,8 @@ "source": [ "# An introduction to the `sequence-jacobian` toolkit\n", "\n", + "## NOTE: This notebook is outdated with the development of the new `sequence-jacobian` API. Please refer to the other notebooks as of now.\n", + "\n", "This notebook serves as an introduction to the `sequence-jacobian` toolkit and the classes and main functions it provides for solving dynamic general equilibrium models in sequence space.\n", "\n", "This introduction will cover the following topics:\n", @@ -16,14 +18,14 @@ " 1. `SimpleBlock`\n", " 2. `HetBlock`\n", " 3. `SolvedBlock`\n", - "2. The main functions of `sequence-jacobian`\n", + "2. The primary functions of `sequence-jacobian`\n", " - `steady_state`\n", " - `get_G`\n", - " - `td_map`\n", + " - `td_solve`\n", "\n", "The notebook accompanies the working paper by Auclert, Bardóczy, Rognlie, Straub (2019): \"Using the Sequence-Space Jacobian to Solve and Estimate Heterogeneous-Agent Models\". Please see the [Github repository](https://github.com/shade-econ/sequence-jacobian) for more information and code.\n", "\n", - "Also, be sure to check out the other model notebooks in this directory to see some applications of `sequence-jacobian`." + "Also, this notebook borrows material from the other model example notebooks so be sure to check out them out to see a more complete model solution workflow using `sequence-jacobian`." ] }, { @@ -119,14 +121,14 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", + "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj\n", - "from sequence_jacobian import simple, het, solved\n", + "from sequence_jacobian import simple, het, solved, steady_state, get_G, td_solve\n", "from sequence_jacobian import utilities as utils" ] }, @@ -137,6 +139,17 @@ "# 1 How do `Block` objects work?" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first step of solving a model is to come up with a \"Directed Acyclic Graph\" (**DAG**) representation for it and specify its building `Block`s.\n", + "\n", + "`Block` objects are collections of the model's equilibrium conditions, typically grouped as the conceptual pieces of the model, e.g. firm problem, household problem, market clearing conditions. Each `Block` takes a set of parameters and variables as inputs and produces another set as outputs.\n", + "\n", + "A model's equilibrium conditions and associated unknown variables can be re-written as a directed acyclic graph (DAG) of `Block`s, whose structure can be exploited for fast computing the model's steady state and solving for linear and non-linear dynamic responses of endogenous variables to shocks." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -318,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -426,7 +439,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -519,12 +532,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Sometimes within the structure of the DAG we can specify a set of equations that constitute a smaller, self-contained DAG. One example of this is the New Keynesian Phillips Curve, which is a `SolvedBlock` in our two-asset heterogeneous agent model." + "Sometimes within the structure of the directed acyclic graph (DAG) we can specify a set of equations that constitute a smaller, self-contained DAG. One example of this is the New Keynesian Phillips Curve, which is a `SolvedBlock` in our two-asset heterogeneous agent model." ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -565,7 +578,710 @@ { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "### Steady state with a standard DAG" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For a given DAG, `steady_state` solves for the steady state of a model. Although it is not required for users to use `steady_state` in order to utilize the other functions provided by the `sequence-jacobian` toolkit, it may be convenient to do so since the primary functions in `sequence-jacobian` require the basically the same set of arguments.\n", + "\n", + "Consider the following set of `Block` objects that contain the equilibrium conditions for the standard real business cycle (RBC) model." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "@simple\n", + "def rbc_firm(K, L, Z, alpha, delta):\n", + " r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta\n", + " w = (1 - alpha) * Z * (K(-1) / L) ** alpha\n", + " Y = Z * K(-1) ** alpha * L ** (1 - alpha)\n", + " return r, w, Y\n", + "\n", + "@simple\n", + "def rbc_household(K, L, w, eis, frisch, vphi, delta):\n", + " C = (w / vphi / L ** (1 / frisch)) ** eis\n", + " I = K - (1 - delta) * K(-1)\n", + " return C, I\n", + "\n", + "@simple\n", + "def rbc_mkt_clearing(r, C, Y, I, K, L, w, eis, beta):\n", + " goods_mkt = Y - C - I\n", + " euler = C ** (-1 / eis) - beta * (1 + r(+1)) * C(+1) ** (-1 / eis)\n", + " walras = C + K - (1 + r) * K(-1) - w * L\n", + " return goods_mkt, euler, walras" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we would like to calibrate the steady state such that output $Y$ is normalized to 1, and the euler equation and goods market clearing hold. Given we have three calibration `target` variables, we need three free or `unknown` variables to hit those targets. Beyond that, we might like to specify some fixed variables or parameters in steady state, which we can specify in the `calibration` dictionary.\n", + "\n", + "Once we have provided all of these arguments to `steady_state` and specified which root-finding algorithm we would like it to use in the keyword argument `solver`, it will solve for the steady state of the DAG." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Solving for the steady state as a standard DAG\n", + "rbc_calibration = {\"L\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11, \"beta\": 1/(1 + 0.01)}\n", + "rbc_blocks = [rbc_household, rbc_firm, rbc_mkt_clearing]\n", + "rbc_ss_unknowns = {\"vphi\": 0.9, \"K\": 2., \"Z\": 1.}\n", + "rbc_ss_targets = {\"euler\": 0., \"goods_mkt\": 0., \"Y\": 1.}\n", + "rbc_ss = steady_state(rbc_blocks, rbc_calibration, rbc_ss_unknowns, rbc_ss_targets, solver=\"broyden_custom\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Steady state with an analytical solution-augmented DAG\n", + "\n", + "In this alternative way of solving for the steady state we will make use of a new kind of block called a `HelperBlock`, whose purpose is to provide a more flexible way of using the sequence-jacobian toolkit to calibrate a model's steady state. \n", + "\n", + "A `HelperBlock` works identically to the `SimpleBlock`s that we constructed above using the decorator `@simple` for the end-user, but using the decorator `@helper` instead. Under the hood the sequence-jacobian toolkit handles them differently when the blocks are sorted/used outside of the steady state.\n", + "\n", + "In the case of the RBC model, given our choice of fixed $r = 0.01$, normalizing to $Y = 1$ lets us provide a *complete* analytical characterization of the steady state. In steps: we choose the discount rate $\\beta$ to hit a given real interest rate $r$, the disutility of labor $\\varphi$ to hit labor $L=1$, and normalize TFP $Z$ to get output $Y=1$." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from sequence_jacobian import helper\n", + "\n", + "@helper\n", + "def rbc_steady_state_solution(r, eis, delta, alpha):\n", + " rk = r + delta\n", + " Z = (rk / alpha) ** alpha # normalize so that Y=1\n", + " K = (alpha * Z / rk) ** (1 / (1 - alpha))\n", + " Y = Z * K ** alpha\n", + " w = (1 - alpha) * Z * K ** alpha\n", + " I = delta * K\n", + " C = Y - I\n", + " beta = 1 / (1 + r)\n", + " vphi = w * C ** (-1 / eis)\n", + "\n", + " return Z, K, Y, w, I, C, beta, vphi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the solution is entirely analytical it is not necessary to include any `unknown`s. Also, we can specify `solver=\"solved\"` to avoid using a root-finding algorithm. However, given the present structure of the code, we still require that you provide some target values to verify that the steady state given by the analytical solution indeed produces one that satisfies some set of target equations, like the euler equation, goods market clearing, or Walras' Law, as a gut-check to ensure we don't proceed forward with something we don't want!\n", + "\n", + "Also, if all of your targets are implicit functions, i.e. you want to target their values equal to 0, you can use a list of their names instead of a dict(ionary)." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "rbc_calibration_helper = {\"L\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11}\n", + "rbc_blocks_helper = [rbc_household, rbc_firm, rbc_mkt_clearing, rbc_steady_state_solution]\n", + "rbc_ss_unknowns_helper = {}\n", + "rbc_ss_targets_helper = [\"euler\", \"goods_mkt\"]\n", + "rbc_ss_helper = steady_state(rbc_blocks_helper, rbc_calibration_helper, rbc_ss_unknowns_helper, rbc_ss_targets_helper, solver=\"solved\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'L': 1.0,\n", + " 'r': 0.009999999999999995,\n", + " 'eis': 1.0,\n", + " 'frisch': 1.0,\n", + " 'delta': 0.025,\n", + " 'alpha': 0.11,\n", + " 'Z': 0.8816460975214567,\n", + " 'K': 3.1428571428571432,\n", + " 'Y': 1.0,\n", + " 'w': 0.8900000000000001,\n", + " 'I': 0.07857142857142874,\n", + " 'C': 0.9214285714285713,\n", + " 'beta': 0.9900990099009901,\n", + " 'vphi': 0.9658914728682173,\n", + " 'euler': 0.0,\n", + " 'goods_mkt': 0.0,\n", + " 'walras': 0.0}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rbc_ss_helper" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Gut Check**: To verify that this steady state delivers the same thing that we got from computing it along the standard DAG, we can use one of the developer tools provided in `sequence-jacobian`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "L resid: 0.0\n", + "r resid: 9.327198735586961e-12\n", + "eis resid: 0.0\n", + "frisch resid: 0.0\n", + "delta resid: 0.0\n", + "alpha resid: 0.0\n", + "beta resid: 0.0\n", + "vphi resid: 5.6993409991434874e-11\n", + "K resid: 7.346452335355025e-10\n", + "Z resid: 5.1535109513167754e-11\n", + "w resid: 2.913902452661432e-11\n", + "Y resid: 3.274069904080079e-11\n", + "I resid: 1.836619745176904e-11\n", + "C resid: 2.4201751713803787e-11\n", + "euler resid: 1.0022205287896213e-11\n", + "goods_mkt resid: 7.530864820637362e-11\n", + "walras resid: 7.530831513946623e-11\n" + ] + } + ], + "source": [ + "import sequence_jacobian.utilities.devtools as dtools\n", + "\n", + "dtools.compare_steady_states(rbc_ss, rbc_ss_helper)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It checks out!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Steady state in the general case\n", + "\n", + "In practice, your own steady state workflow will likely be somewhere in between the two cases previously described. To compute the steady state it may be easy to analytically solve some portions of the model, but other portions may only be numerically computable, specified as a set of unknowns and targets within a DAG. This may also be desirable for performance reasons, since typically computing a portion of the DAG analytically is less costly than providing the root-finding algorithm with an additional dimension to solve.\n", + "\n", + "Thankfully, any combination of the above two methods is permissible in the `sequence-jacobian` toolkit. You need only provide the analytical solution component specific to the steady state as a `HelperBlock` in the standard list of blocks, specify your unknowns and targets, and call `steady_state`! You don't even need to swap out the `HelperBlock` from the list of blocks when computing general equilibrium Jacobians or computing non-linear transition dynamics." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.B `get_G`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once a steady state for the model is obtained, `get_G` supplies the general equilibrium Jacobian, which defines the linearized impulse responses of any set of endogenous variables `dY` to any set of exogenous shocks `dZ`.\n", + "\n", + "Several other notebooks, including `rbc.ipynb`, `krusell_smith.ipynb`, and `hank.ipynb`, go into further details of what is happening under the hood when `get_G` is called, so it should suffice to simply reiterate how `get_G` is used in each of these model contexts here." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introducing the `JacobianDict` class" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For the sake of explanation let us consider the following DAG, which represents the RBC model.\n", + "\n", + "![Directed Acyclic Graph for RBC model](../figures/rbc_dag.png)\n", + "\n", + "We can call `get_G` on the list of `Block` objects and specify the `exogenous`, `unknown`, `target` variables that collectively constitute this DAG, and additionaly provide the length of time by which the variables deviate from their steady state values, `T`, and the steady state values, `ss`, to obtain an instance of the `JacobianDict` class." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "rbc_G = get_G(block_list=rbc_blocks, exogenous=['Z'], unknowns=['K', 'L'], targets=['euler', 'goods_mkt'], T=300, ss=rbc_ss)\n", + "rbc_G" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As seen from its display, this `JacobianDict` can find the linearized impulse response of any of the listed `outputs` to a shock path any of its listed `inputs`. Let us create a linearized impulse response $dC = G^{C, Z} dZ$ below, where $dZ$ equals 0.01 on impact and decays at an exponential rate with a persistence of 0.8." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "dOut_rbc = rbc_G[[\"C\"]] @ {\"Z\": 0.01 * 0.8 ** np.arange(300)}\n", + "\n", + "plt.plot(100 * dOut_rbc[\"C\"][:50]/rbc_ss[\"C\"], linewidth=2.5)\n", + "plt.title(r'Consumption response to TFP shock in the RBC Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because the `JacobianDict` defines the linearized relationship between any input path to any output path across time (in \"sequence space\"), it is simple to also find the response to a news shock." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "dOut_rbc_news = rbc_G[[\"C\"]] @ {\"Z\": np.concatenate((np.zeros(10), 0.01 * 0.8 ** np.arange(290)))}\n", + "\n", + "plt.plot(100 * dOut_rbc_news[\"C\"][:50]/rbc_ss[\"C\"], linewidth=2.5, color=\"orange\")\n", + "plt.title(r'Consumption response to TFP news shock at t = 10 in the RBC Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Krusell Smith Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can represent this model as a simple DAG in just 1 unknown $K$ and 1 target, asset market clearing:\n", + "\n", + "![Directed Acyclical Graph](../figures/ks_dag.png)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from sequence_jacobian.models import krusell_smith\n", + "\n", + "# Use the pre-defined blocks from the model module\n", + "# We can include the definitions of the grids for income and assets as blocks as well, hence the inclusion of\n", + "# krusell_smith.income_state_vars and krusell_smith.asset_state_vars as additional SimpleBlocks\n", + "ks_blocks = [krusell_smith.household, krusell_smith.firm, krusell_smith.mkt_clearing, krusell_smith.income_state_vars,\n", + " krusell_smith.asset_state_vars, krusell_smith.firm_steady_state_solution]\n", + "\n", + "ks_calibration = {\"eis\": 1, \"delta\": 0.025, \"alpha\": 0.11, \"rho\": 0.966, \"sigma\": 0.5, \"L\": 1.0,\n", + " \"nS\": 2, \"nA\": 10, \"amax\": 200, \"r\": 0.01}\n", + "ks_ss_unknowns = {\"beta\": (0.98/1.01, 0.999/1.01)}\n", + "ks_ss_targets = {\"K\": \"A\"}\n", + "ks_ss = steady_state(ks_blocks, ks_calibration, ks_ss_unknowns, ks_ss_targets, solver=\"brentq\")\n", + "\n", + "ks_G = get_G(block_list=ks_blocks, exogenous=['Z'], unknowns=['K'], targets=['asset_mkt'], T=300, ss=ks_ss)\n", + "\n", + "dOut_ks = ks_G[[\"C\"]] @ {\"Z\": 0.01 * 0.8 ** np.arange(300)}\n", + "\n", + "plt.plot(100 * dOut_ks[\"C\"][:50]/ks_ss[\"C\"], linewidth=2.5)\n", + "plt.title(r'Consumption response to TFP shock in the KS Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "dOut_ks_news = ks_G[[\"C\"]] @ {\"Z\": np.concatenate((np.zeros(10), 0.01 * 0.8 ** np.arange(290)))}\n", + "\n", + "plt.plot(100 * dOut_ks_news[\"C\"][:50]/ks_ss[\"C\"], linewidth=2.5, color=\"orange\")\n", + "plt.title(r'Consumption response to TFP news shock at t = 10 in the KS Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### One Asset HANK Model Example" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "from sequence_jacobian.models import hank\n", + "\n", + "# Use the pre-defined blocks from the model module\n", + "hank_blocks = [hank.household, hank.firm, hank.monetary, hank.fiscal, hank.mkt_clearing, hank.nkpc,\n", + " hank.income_state_vars, hank.asset_state_vars, hank.partial_steady_state_solution]\n", + "\n", + "hank_calibration = {\"r\": 0.005, \"rstar\": 0.005, \"eis\": 0.5, \"frisch\": 0.5, \"mu\": 1.2, \"B_Y\": 5.6,\n", + " \"rho_s\": 0.966, \"sigma_s\": 0.5, \"kappa\": 0.1, \"phi\": 1.5, \"Y\": 1, \"Z\": 1, \"L\": 1,\n", + " \"pi\": 0, \"nS\": 2, \"amax\": 150, \"nA\": 10}\n", + "hank_ss_unknowns = {\"beta\": 0.986, \"vphi\": 0.8}\n", + "hank_ss_targets = {\"asset_mkt\": 0, \"labor_mkt\": 0}\n", + "hank_ss = steady_state(hank_blocks, hank_calibration, hank_ss_unknowns, hank_ss_targets, solver=\"broyden_custom\")\n", + "\n", + "hank_G = get_G(block_list=hank_blocks, exogenous=['rstar', 'Z'], unknowns=['pi', 'w', 'Y'],\n", + " targets=['nkpc_res', 'asset_mkt', 'labor_mkt'], T=300, ss=hank_ss)\n", + "\n", + "dOut_hank = hank_G[[\"C\"]] @ {\"Z\": 0.01 * 0.8 ** np.arange(300)}\n", + "\n", + "plt.plot(100 * dOut_hank[\"C\"][:50]/hank_ss[\"C\"], linewidth=2.5)\n", + "plt.title(r'Consumption response to TFP shock in the One Asset HANK Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "dOut_hank_news = hank_G[[\"C\"]] @ {\"Z\": np.concatenate((np.zeros(10), 0.01 * 0.8 ** np.arange(290)))}\n", + "\n", + "plt.plot(100 * dOut_hank_news[\"C\"][:50]/hank_ss[\"C\"], linewidth=2.5, color=\"orange\")\n", + "plt.title(r'Consumption response to TFP news shock at t = 10 in the One Asset HANK Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.C `td_solve`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One can also use `sequence-jacobian` to solve for non-linear impulse responses of a set of endogenous variables by providing the full path of a set of shocks. This may be useful to see if the true impulse response of a set of variables actually does scale linearly, irrespective of the size, and is symmetric across sign. We will show a few comparisons of the non-linear responses with the corresponding linear responses plotted above." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### RBC Example" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "On iteration 0\n", + " max error for goods_mkt is 8.91E-04\n", + " max error for euler is 2.75E-03\n", + "On iteration 1\n", + " max error for goods_mkt is 9.21E-05\n", + " max error for euler is 4.07E-05\n", + "On iteration 2\n", + " max error for goods_mkt is 4.07E-07\n", + " max error for euler is 4.66E-07\n", + "On iteration 3\n", + " max error for goods_mkt is 5.74E-09\n", + " max error for euler is 5.76E-09\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "rbc_td = td_solve(ss=rbc_ss, block_list=rbc_blocks,\n", + " unknowns=['K', 'L'], targets=['goods_mkt', 'euler'],\n", + " Z=rbc_ss['Z'] + 0.01 * 0.8 ** np.arange(300))\n", + "dOut_rbc_nonlin = 100 * (rbc_td[\"C\"]/rbc_ss[\"C\"] - 1)\n", + "\n", + "plt.plot(100 * dOut_rbc[\"C\"][:50]/rbc_ss[\"C\"], linewidth=2.5, label=\"linear\")\n", + "plt.plot(dOut_rbc_nonlin[:50], linewidth=2.5, linestyle=\"--\", label=\"non-linear\")\n", + "plt.title(r'Consumption response to TFP shock in the RBC Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Krusell Smith Example" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "On iteration 0\n", + " max error for asset_mkt is 3.43E-02\n", + "On iteration 1\n", + " max error for asset_mkt is 1.43E-05\n", + "On iteration 2\n", + " max error for asset_mkt is 3.68E-08\n", + "On iteration 3\n", + " max error for asset_mkt is 7.72E-11\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYIAAAEWCAYAAABrDZDcAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nO3dd3hUZfbA8e9JrySU0BI6QektYEBULCC6FuwFXcsqrr2sura1+1t7d1fFghVlRRSwgNhQBKkR6b2EGkoIIaSf3x/3gkNMwgCZ3CRzPs8zT+bWOe/kzpz73vfO+4qqYowxJniFeB2AMcYYb1kiMMaYIGeJwBhjgpwlAmOMCXKWCIwxJshZIjDGmCBniaCOEZF7ROQNr+Mw3hCR1iKiIhJWxfv9QUSu8nPdBSIysIped6CIZFbFvmoDEXlQRN73c12//ycHUucTgYhcLCKzRCRXRDaKyFciMsDruKpCeR8SVf0/Va2SgyNYHcyHsZxtv3KPtVwRKRKRQp/pV93/WanPvFwRGe/zukXuvGwR+UVE+lVt6QJPVTur6g+Hsq2bxNpXcUh79325iPzsM11PRKaKyBgRCReRFPf5VhHZKSK/i8jlFexroBvrp2Xmd3fn/xCIMgRKnU4EInIb8Dzwf0AToCXwH+BML+OqC6r6jLOuUNVTVDVOVeOAD4An906r6t/d1Tb4zItT1dN9dvGxu20S8DPwqYhINRejzhOR+sBkYA1wgaoWAe8B64BWQEPgr8DmSnaTBfQXkYY+8y4DlgYk6ACqs4lARBKAh4HrVfVTVd2tqkWqOl5V73DXiRSR50Vkg/t4XkQi3WUDRSRTRP4hIlvc2sQVPvs/VUQWisguEVkvIre78/c763Dn7TvLEZGRIvIfnzPHqSLS1H3tHSKyWER6+my7WkTudl9rh4i8LSJRIhILfAU09zmzbF72bFZEznCr6tluVbJjmX3fLiLz3DOgj0UkqoL383I31udEZDvwoDv/ShFZ5MY2UURaufPFXXeLu+95ItLF5z14VUS+cd+/H/du5y7vLyIz3e1mikh/n2U/iMgjbiy7RGSSiDRyl0WJyPsiss0t70wRabL3eBCRN93/43oReVREQssp5xDgHuAC9z39zZ3fXETGich2EVkuIldXdOxVBfeL6R2gKc6XUtk4+4pT080Rkc0i8myZVYaJyFpxzm7v9dmuwmPeXX6miGS4+13hvh9lX7uZ+/+8vbzY3ePqJPf5gyIyWkTedf9fC0QkrYLtprhPf3Pf+wt8llX0OYwUkafdsm52j6vo8vbvs00j4DtgAXCJqha7i/oAI93vimJVnauqX1Wyq0LgM+BCd7+hwPk4JwC+r1fZ8dzGPf53icg3QKMy26aLUzPMFpHfpIouuf2JqtbJBzAEKAbCKlnnYWA60BjnDOwX4BF32UB3+4eBcOBUIA+o7y7fCBzjPq8P9HKfXw78XOZ1FGjvPh8JbAV6A1E4B+QqnLOPUOBR4HufbVcD84EWQANgKvCoT4yZZV7rQeB993kHYDcwyC3DncByIMJn3zOA5u6+FwF/r+C9utx9P24EwoBoYKi7v47uvPuAX9z1TwZmA4mAuOs083kPdgHHApHAC3vfMzeOHcCl7j4vcqcbust/AFa4ZYt2px93l10DjAdi3PeyN1DPXfYZ8BoQ6/6/ZwDXVFDWfe+hz7wfcWqTUUAPnLPBEw9wDI7c+7/ymfen/1kF/7tI4ClgXQXrTgMudZ/HAenu89Y4x9sI9/3pDhQAHf045vsCO93jJQRIBo70ed+vcve/FBheSblXAyf5lCkf5/MTCvwbmF7Jtvs+K35+Dp8HxrnHTbz7//93JcfwQpwE8CogZZZPxvl8XQi0PMD/diCQCfQHfnXnnQpMdN+nH/w8nqcBz7r/72NxPhd7j4FkYJu73xD3/7INSPL9n1TJ92VV7KQmPoBhwKYDrLMCONVn+mRgtc8/eg8+iQTYwh8fuLU4Xzz1yjnYDpQIRvgsuxFY5DPdFcgu86H6u8/0qcAK34OxzGs96HMg/QsY7bMsBFgPDPTZ9yU+y58EXq3gvbocWFtm3lfA38rsPw+nan0CzhdGOhBSZruRwEc+03FACU6yuxSYUWb9acDlPgf/fT7LrgO+dp9fifPF1q3M9k1wvgyjfeZdhE/Creg9dKdbuPHF+8z7N87ZY2XH10jKTwSlQLbP43yf1y10523BOUnoXcG+pwAPAY3KzG/tHm8pPvNmABf6ccy/BjxXwev9gPOFtRq46ADlXs3+iWCyz7JOwJ5Kti0vEZT7OcQ5wdgNtPNZ1g9YVckxvAsoAo4qZ3l94HGcRFECZAB9KtjXQNzPHrAMOAL4COd7xzcRVHg841yqLgZifZZ9yB+f338C75XZdiJwmc//pEoSQZ29NISTORtJ5deym+NcI9xrjTtv3z70j2ojOF9yce7zc3C+lNe4VbuDadTzve64p5zpuP1XZ10lMVZmv/Kpaqm7r2SfdTb5PPctX3nWlZluBbzgVluzge04H85kVf0OeBl4BdgsIq+LSL3y9qWque62zcvG7FrjZ8zv4XxQPnIvezwpIuFunOHARp9YX8M5K/ZHc2C7qu6qJKaDsUFVE30eo32WjXbnNVbVE1R1dgX7+BtOrWixe7nhtDLLK3qPKjvmW+AkiooMwzmR+KSSdcpTNpaoA3wuy6roc5iEU/ub7fN//dqdX5HfgNuBr8TnEiyAqu5Q1btUtTPOyUMG8JnIAdto3gNuAI4HxpZZVtnx3BzYoaq7yyzbqxVw3t6yueUbADQ7QDwHrS4ngmk4VdKhlayzAefN3qulO++AVHWmqp6J82XyGbD3w7wb5+AEQESaHkTMFWlRQYwH6jp2v/K5B3QLnA/zoSj7eutwLq/4fqlFq+ovAKr6oqr2BjrjfGnd4bPtvjKJSBxOFXpD2ZhdLf2JWZ02oIdUtRNOlf00nEtu63BqBI184qznfuD9KecGoIGIxB9sTIGiqstU9SKc4+8J4BNx2o0OpLJjfh3QrpJtH8S5rPlhee0rHtiKc+LU2ef/mqBOY3uFVPUFnDP/b8Rttypnna3A0/xx2bQy7+HUTL9U1bwyyyo7njcC9cv831r6PF+HUyPw/XzFqurjB4jnoNXZRKCqO4H7gVdEZKiIxIhzi9gpIvKku9oo4D4RSXIbkO4HDnjboIhEiMgwEUlQp1EvB6cqCc4ZR2cR6SFOw+uDVVCc68W5ta0BTkPmx+78zUBDcRrGyzMa+IuInOieGf8D5wvxlyqICZzrrHeLSGfY1yB7nvu8j4gc5b7ubpykXOKz7akiMkBEIoBHcK6zrgO+BDqIc9tvmNtg2AmYcKBgROR4Eenqfknl4FwCKFHVjcAk4BlxbhkMEZF2InJcBbvaDLQWkRAAN65fgH+L0yDdDeeM/IMKtg84EblERJLcWl62O7uksm1clR3zbwJXuMdLiIgki8iRPtsWAefhtLO8t/f9qWKbgbb+rOiWfQTwnIg0BnBjPtmPbZ/EaZuaLCJHuNs+ISJd3OMuHrgWWK6q2w6wr1XAccC95Syu8HhW1TXALOAh9ztlAOB7B9n7wOkicrKIhLrH3kARSTlQ+Q5WnU0EAKr6LHAbTiNmFk6GvQHnDB6chtlZwDzgd2COO88flwKrRSQH+DtwifuaS3EatibjXDv8ucI9+O9DnC+yle7jUfe1FuN8sFe6Vcf9Lhmp6hI3rpdwzp5OB05X1cIqiAlVHYtzNvqR+z7MB05xF9fD+ZDuwKnubsM5w/It0wM4l4R641x2wP3QnYaTtLbhNHCf5p6hHUhTnMsWOTgN3z/yx5fcX4EInMbCHe56FVWx/+f+3SYic9znF+Fcf9+AU/1/QFW/8SOmQBkCLBCRXJwvtAtVNd+P7So85lV1BnAF8BxOo/GPlDmbdY+ds3FqIm8FIBk8CLzjHs/n+7H+P3FuWJjuHoOTca7XH5CqPgK8AXwrIu1wavJjcRLrSpyyn+Hnvn5W1T9dTfDjeL4YOArnc/AA8K7PtutwbnW/hz++v+4gAN/b4jY6mBpKRFbjNAhN9jqWqiIiI3Ea2u7zOhZjTB2vERhjjDkwSwTGGBPk7NKQMcYEOasRGGNMkKt1HYc1atRIW7du7XUYxhhTq8yePXurqpb7Y7talwhat27NrFmzvA7DGGNqFREp+wvnfezSkDHGBDlLBMYYE+QsERhjTJCrdW0Expi6oaioiMzMTPLz/ekZw/grKiqKlJQUwsPD/d7GEoExxhOZmZnEx8fTunVrDtzTs/GHqrJt2zYyMzNp06aN39vZpSFjjCfy8/Np2LChJYEqJCI0bNjwoGtZQZMIikpKmbrcnw4sjTHVxZJA1TuU9zQoEsHugmKuemcWw9/8kak/fet1OMYYU6MERSJYsy2PZatW80H4o3SZfCm/z5nmdUjGmBogLs4ZzGzDhg2ce+65HkfjnaBIBJ2a12PEsXvoEbKSBNlN43EXs3L5Yq/DMsbUEM2bN+eTTw52KOaDU1xcfOCVPBIUiQCg86DL+L3jrQA0YTshH5zNxo2eDTlrjKlBVq9eTZcuzvDFI0eO5Oyzz2bIkCGkpqZy55137ltv0qRJ9OvXj169enHeeeeRm5sLwMMPP0yfPn3o0qULw4cPZ2+vzgMHDuSee+7huOOO44UXXqj+gvkpqG4f7Xr+A8x7YxPd1o+ita5nwRtnE3PTJBISKhry1xhTHR4av4CFG3KqfL+dmtfjgdM7H/R2GRkZzJ07l8jISI444ghuvPFGoqOjefTRR5k8eTKxsbE88cQTPPvss9x///3ccMMN3H///QBceumlTJgwgdNPd4Yfzs7O5scff6zSclW1oEoEiND1b6/w+0tZdN0xmc4li5n1n3PpctsEoiIjvY7OmKC1cEMOv67a7nUY+5x44on7ThA7derEmjVryM7OZuHChRx99NEAFBYW0q9fPwC+//57nnzySfLy8ti+fTudO3felwguuOACbwpxEIIrEQASEkrnaz9g8fOncGTeHNIKZvDTy5fR/5YPCQ0NmitlxtQonZrXq1H7jfQ5MQwNDaW4uBhVZdCgQYwaNWq/dfPz87nuuuuYNWsWLVq04MEHH9zvPv7Y2NhDC74aBV0iAAiJiKLN9Z+y6vkTaVO0gr45k3ltzASuO/8Mr0MzJigdyuWb6paens7111/P8uXLad++PXl5eWRmZtK4cWMAGjVqRG5uLp988kmtuwMpaE+BI2Pr0+jv41kW2o6/Ft7Fk3NC+Xr+Jq/DMsbUUElJSYwcOZKLLrqIbt26kZ6ezuLFi0lMTOTqq6+ma9euDB06lD59+ngd6kGrdWMWp6WlaVUOTLNu225OfelnduUXUy8qjC9vPoaU+jFVtn9jTPkWLVpEx44dvQ6jTirvvRWR2aqaVt76QVsj2KtFw1ieOrcbADn5xfzr/W8pqsH3+xpjTFUL+kQAMKRLMy7r14rjQ+byzNa/M3XkvV6HZIwx1cYSgevuk9vzcNQoGkgux6x7jTk/feF1SMYYUy0sEbiioqKQc9+kQMMJFSX52xvYstl+eWyMqfssEfhI6ZTO4h53A043FOveupySklKPozLGmMCyRFBG96G3Ma/eQAB6F8zg5/ce8jYgY4wJMEsEZYmQetXbbJQmAPRb9RJLfvvV46CMMbXJ5Zdfvq8306uuuoqFCxd6HFHlLBGUI7peAwqGjqBEhQgpQT6/zgbYNsYckjfeeINOnToFbP9V0b21JYIKtO5+HBkt/wrA7mIYMWm2xxEZY6ra6tWr6dixI1dffTWdO3dm8ODB7Nmzh4yMDNLT0+nWrRtnnXUWO3bsAJxupf/5z3/St29fOnTowE8//XTA1xg4cCB7fwQbFxfHvffeS/fu3UlPT2fz5s0AZGVlcc4559CnTx/69OnD1KlTAZgxYwb9+/enZ8+e9O/fnyVLlgBOV9nnnXcep59+OoMHDz7s9yGgfQ2JyBDgBSAUeENVH69gvXOB/wF9VLXqfjZ8mLpf8jivvBjPs9uOQqdlc0zPbHq0SPQ6LGPqprkfQMaHla/TtCuc4vM1snEefH13+ev2uBh6Djvgyy5btoxRo0YxYsQIzj//fMaMGcOTTz7JSy+9xHHHHcf999/PQw89xPPPPw84Z+AzZszgyy+/5KGHHmLy5Mn+lpDdu3eTnp7OY489xp133smIESO47777uPnmm7n11lsZMGAAa9eu5eSTT2bRokUceeSRTJkyhbCwMCZPnsw999zDmDFjAJg2bRrz5s2jQYMGfr9+RQKWCEQkFHgFGARkAjNFZJyqLiyzXjxwE1DjLsSHRcZw0qV38cJLP1NYUsrt//uNCTcOICo81OvQjKl7stfCmp8Pbpv8nRVv03qAX7to06YNPXr0AKB3796sWLGC7OxsjjvuOAAuu+wyzjvvvH3rn3322fvWXb169UGFGxERwWmnnbZv+2+++QaAyZMn79eOkJOTw65du9i5cyeXXXYZy5YtQ0QoKirat86gQYOqJAlAYGsEfYHlqroSQEQ+As4EyraaPAI8CdwewFgO2RFN47n5pFSemriE5Vt2Merz8Vxx7lCvwzKm7klsCa0O8OXdtOv+01EJFW+T2NKvly3b5XR2drZf6+/tnhrgiiuuYO7cuTRv3pwvv/yywm3Dw8MRkT9tX1payrRp04iOjt5v/RtvvJHjjz+esWPHsnr1agYOHLhvWVV2bx3IRJAMrPOZzgSO8l1BRHoCLVR1gohUmAhEZDgwHKBlS//+uVXpmmPbMmfePK7Y+jR9f1/MovaN6dijf7XHYUyd1nOYX5dy9tOsG1xRtb0AJCQkUL9+fX766SeOOeYY3nvvvX21g4q8/fbbh/WagwcP5uWXX+aOO+4AnBHSevTowc6dO0lOTgacdoFACWRjsZQzb19XpyISAjwH/ONAO1LV11U1TVXTkpKSqjBE/4SFhnD/CY1JD1lEhJQQNu56u4vImDrsnXfe4Y477qBbt25kZGTsG4YyUF588UVmzZpFt27d6NSpE6+++ioAd955J3fffTdHH300JSUlAXv9gHVDLSL9gAdV9WR3+m4AVf23O50ArABy3U2aAtuBMyprMK7qbqgPxqy3biVt7VsATEm+mmOvftqTOIypC6wb6sCpSd1QzwRSRaSNiEQAFwLj9i5U1Z2q2khVW6tqa2A6B0gCXusx7P9YHdoKgH6Zb9kPzYwxdULAEoGqFgM3ABOBRcBoVV0gIg+LSK0cEzIsMhoZ+h9KVAiXEkrH3WhjFxhjar2A/qBMVb9U1Q6q2k5VH3Pn3a+q48pZd2BNrg3s1arrAOYmXwxAx5IlTB/9lMcRGVN71bYREmuDQ3lP7ZfFh6DLsMfZJM6A1T2XvMD6NSs8jsiY2icqKopt27ZZMqhCqsq2bduIioo6qO0C+sviuioqth47TniCpt9egQIfjP+KO66/ft/9wcaYA0tJSSEzM5OsrCyvQ6lToqKiSElJOahtLBEcoo7HnM24hRk8tiqVzZkN6JCxgaE9k70Oy5haIzw8nDZt2ngdhsEuDR2W4y79FyVxzQB4eMJCduwu9DgiY4w5eJYIDkNCTDgPnO50L7t9dyHPjZvmcUTGGHPwLBEcptO6NeOkDoncEDqWuxefy7xpk7wOyRhjDoolgsMkIjxyfCI3hn1GtBQSN+kf5Ofv8TosY4zxmyWCKtCsTWfmtb8GgLa6lpkf2DjHxpjawxJBFel5wb9YE+r0jNpn7RusXva7xxEZY4x/LBFUkbCIKIpOeRaAKCki+5Ob0dJSj6MyxpgDs0RQhdqnDWJ2w9MB6FEwm2kT3vQ4ImOMOTBLBFWswyXPsoN6AKTOeZQd27d6HJExxlTOEkEVi6/fmLVp9wCQRDaT//eqxxEZY0zlLBEEQLdTr+Hn2EFcWXg7d6zqya8rt3kdkjHGVMgSQQBISAit/vYuv4Q6gwHd+9l8Cout4dgYUzNZIgiQFg1iuOnEVACWb8llxBTrqtoYUzNZIgigq49pS4cmcRwlizjmxwvIXL3U65CMMeZPLBEEUHhoCM8cH8XHkY/QTVaw6eNbbBAOY0yNY4kgwLr2OIq5CYMASNszlZkTP/A4ImOM2Z8lgmrQZtjz5BALQMvpD7Bz5w6PIzLGmD9YIqgGiY1TWNHtdgCaspXf37/b44iMMeYPlgiqSY+hN7M0oiMA6Vs+ZknGVI8jMsYYhyWCaiIhoUSf/RJFGkqYlML4WyguKvI6LGOMsURQnVoc2YeMlGEAHFGylF8+fdHjiIwxxo9EICJHi0is+/wSEXlWRFoFPrS6qevFj5EpzXi9+C/ctqAd67NtNDNjjLf8qRH8F8gTke7AncAa4N2ARlWHRcXWI/OCyfxf8TC2Fobz4LgFXodkjAly/iSCYnV+BXUm8IKqvgDEBzasui39yBTO6pkMwDcLNzNxwSaPIzLGBDN/EsEuEbkbuAT4QkRCgfDAhlX33fuXjiREh5PILraOuYNdOdleh2SMCVL+JIILgALgb6q6CUgGngpoVEGgUVwk/3dMFJMj72BY6Tjm2W8LjDEe8atGgHNJ6CcR6QD0AEYFNqzgcMqx/dke0RyA9M2jWDznJ48jMsYEI38SwRQgUkSSgW+BK4CRgQwqWISEhRF9zssUaSihooR8cQtFRYVeh2WMCTL+JAJR1TzgbOAlVT0L6BzYsIJHiyP7kNHyrwB0KFnOrx/92+OIjDHBxq9EICL9gGHAF+680MCFFHy6X/wY60OaAdBr+StkrlzscUTGmGDiTyK4GbgbGKuqC0SkLfB9YMMKLhHRseQOehqAGClg6+gb0FIb2tIYUz0OmAhUdYqqnqGqT7jTK1X1psCHFlyO6Hcas+ufCkCP/Jn8OuENjyMyxgQL62uoBkm99Hm2U4+M0rY8Oxe25RZ4HZIxJggENBGIyBARWSIiy0XkrnKW/11EfheRDBH5WUQ6BTKemq5egybMH/wRZxc+zIw9yTwyYaHXIRljgkDAEoH7C+RXgFOATsBF5XzRf6iqXVW1B/Ak8Gyg4qktjunXnxM6Og3Hn2Vs4PvFWzyOyBhT1/nT+2gbt8fRT0Vk3N6HH/vuCyx32xQKgY9w+ivaR1VzfCZjgaAf2V1EeGRoZ+IjwwDlh09eZleODW1pjAmcMD/W+Qx4ExgPHMytLMnAOp/pTOCosiuJyPXAbUAEcEJ5OxKR4cBwgJYtWx5ECLVTs4Ro7h+UTJOJf+fY4t/55d319L/hTa/DMsbUUf5cGspX1RdV9XtV/XHvw4/tpJx5fzrjV9VXVLUd8E/gvvJ2pKqvq2qaqqYlJSX58dK137n9O9I42sm76VljmD9toscRGWPqKn8SwQsi8oCI9BORXnsffmyXCbTwmU4BNlSy/kfAUD/2GxQkJJR6579GvoYTIkq9SbewZ3eu12EZY+ogfxJBV+Bq4HHgGffxtB/bzQRS3TaGCOBCYL+2BRFJ9Zn8C7DMn6CDRfP2XZmXej0ALXUDc977041Xxhhz2PxJBGcBbVX1OFU93n2Uey3fl6oWAzcAE4FFwGj3l8kPi8gZ7mo3iMgCEcnAaSe47BDLUWf1vvA+loc5+fKojR+weI4/V+WMMcZ//jQW/wYkAgd9H6Oqfgl8WWbe/T7Pbz7YfQab0LBwIs75D4UfDSFCSoiYcCMFnX8lMjLa69CMMXWEPzWCJsBiEZl4kLePmirSsmNfMlpfBUDb0jXMfv9fHkdkjKlL/KkRPBDwKMwB9Rz2MKuemEij4s2MXyXUW7+TLskJXodljKkD/Ol07kdgMc6A9fHAIj9vHzVVKDwiipKzRnBq0VOMKh7IbaMzKCgu8TosY0wd4M8vi88HZgDnAecDv4rIuYEOzPxZ+y59OfeEdACWbs7l2W+WehyRMaYu8KeN4F6gj6pepqp/xek6wi5Se+S649vRLcW5JPTDTz+y0MY5NsYcJn8SQYiq+t4xtM3P7UwAhIeG8Mx53Rke/hXjwu8ldvw15O3e5XVYxphazJ8v9K/dO4YuF5HLcYar/PIA25gASm0Sz0kd6hMpxbTS9WS8favXIRljajF/GovvAF4DugHdgddV9Z+BDsxUrvdFD7AkvCMA/bf+j3k/2R29xphDU2kiEJFQEZmsqp+q6m2qequqjq2u4EzFQsPCiL/wDfZoBABJ397GzuztHkdljKmNKk0EqloC5ImI3bBeAzVv14X5nf8BQDOyWDzyBo8jMsbURn51Qw38LiJvisiLex+BDsz4J+3cO5gf2ROAo7K/YPakDz2OyBhT2/iTCL7AuV10CjDb52FqAAkJJemSEezC6XuozS93smX9Go+jMsbUJhUmAhH51n3aSVXfKfuopviMH5q0SGVZn4cB+LGkG/d8sZzS0qAf9dMY46fK+hpqJiLHAWeIyEeUGXFMVecENDJzUHr9ZTgvZ0Xz9OIGsDKfET+t5Jrj2nkdljGmFhDV8s8c3W4k/gYMAGaVWaz+jEkQCGlpaTprVtlwDEBuQTF/efEn1mzLIzxUGHvd0dYxnTEGABGZrapp5S2r8NKQqn6iqqcAT/oMSOP3wDSm+sVFhvHChT0JCxFCS/KZPfJO8nbneB2WMaaG8+cHZY9URyCmavRokci9x9ZnfMR9XFb0EfPetFtKjTGVsz6D6qC/ntSH4qj6AKRv/5zZE9/3OCJjTE1miaAOCg0Lo8ElI8khBoC20+5i8/pVHkdljKmp/EoEblcTzUWk5d5HoAMzh6dJy1SWH/UYAPXZRdY7f6W4qNDjqIwxNZE/A9PcCGwGvsH5cdkXwIQAx2WqQK9TrmRW/VMB6FI4jxkj7/Q4ImNMTeRPjeBm4AhV7ayqXd1Ht0AHZqpGl6tfZ1VIKwD6r3+b377/xOOIjDE1jT+JYB2wM9CBmMCIiokn7MJ3ydNIAJr9eDsbtu7wOCpjTE3iTyJYCfwgIneLyG17H4EOzFSdFh16sLjPo2RqI64puIXrRy+ksLjU67CMMTWEP4lgLU77QAQQ7/MwtUiv04bzZrdRzNVU5q7N5smvF3sdkjGmhqisryEAVPUhABGJdyY1N+BRmYD45xm9mbnhF+avz+GNn1fRt1U8g7u28DosY4zH/LlrqIuIzAXmAwtEZCn/5vAAAB1lSURBVLaIdA58aKaqRYWH8srFvYiPDGNQyCw6jTmB9SutZmBMsPPn0tDrwG2q2kpVWwH/AEYENiwTKK0axvLfwZGMiHiWFLaQ98Ew8vOskmdMMPMnEcSq6vd7J1T1ByA2YBGZgBtw9EB+bXw+AKkly/n9tSvQUms8NiZY+XXXkIj8S0Rau4/7AOuvoJbrddXLLIzoCkCfnZOYPur/PI7IGOMVfxLBlUAS8Ckw1n1+RSCDMoEXHhFJk799xGYaAtBn6TPM/3mcx1EZY7zgTzfUO1T1JlXtpao9VfVmVbVfJNUBDZukkDP0HfI1nDApJXnydWxYvcTrsIwx1ayyMYufd/+OF5FxZR/VF6IJpNQexzC/tzPecX12see9C2wwG2OCTGW/I3jP/ft0dQRivJN2xnVMX/8b6Zs/YmNhDK9+OpcnLzkWETnwxsaYWq+yoSpnu097qOqPvg+gR/WEZ6pL2lUv8VbCDVxWdBf/W5DLa1NWeh2SMaaa+NNYfFk58y6v4jiMx8LCIxg6/AGa1Y8D4ImvFzNpwSaPozLGVIfK2gguEpHxQJsy7QPfA9v82bmIDBGRJSKyXETuKmf5bSKyUETmici3ItLq0ItiDleD2AhevzSNmIhQREtZ9/HtLMv4yeuwjDEBVlkbwS/ARqAR8IzP/F3AvAPtWERCgVeAQUAmMFNExqnqQp/V5gJpqponItcCTwIXHFwRTFXq1LweL13Yg9JRFzMoZDZZn/3MpoaTadqivdehGWMCpLI2gjWq+oOq9ivTRjBHVYv92HdfYLmqrlTVQuAj4Mwyr/G9qua5k9OBlEMtiKk6J3ZqSnynkwBIYgd5b59Dbo7dMWxMXeVPp3PpIjJTRHJFpFBESkTEn/sLk3EGtdkr051Xkb8BX/mxX1MN0i+6h1+TzgWgbelqVvznPBvz2Jg6yp/G4peBi4BlQDRwFfCSH9uVd++hlruiyCVAGvBUBcuHi8gsEZmVlZXlx0ubqpB2zWtkRKcD0D1/JrNfu8b6JDKmDvInEaCqy4FQVS1R1beB4/3YLBPw7ew+BdhQdiUROQm4FzhDVQsqeP3XVTVNVdOSkpL8CdlUgdCwMFKv+5gVoW0BOGrrp/z60WMeR2WMqWr+JII8EYkAMkTkSRG5Ff96H50JpIpIG3f7C4H9fpEsIj2B13CSwJaDjN1Ug9j4ROKuHMMWGgDQd8kzzPnqLY+jMsZUJX8SwaVAKHADsBvnLP+cA23kNijfAEwEFgGjVXWBiDwsIme4qz0FxAH/E5EM67qiZmqS3Jacsz9gt0YRIsoXv2QwdflWr8MyxlQRUS33sn2NlZaWprNmzfI6jKD0+5TPeWfSdD4pHkBsRCijhqfTLSXR67CMMX4Qkdmqmlbessp+UDba/fu7+4Ov/R6BCtbUXF2PPZPjz78JEdhdWMLlb89k+RYb3cyY2q6yH5Td7P49rToCMbXDX7o1Y0deF+77bD5huzez5dXTib9yBE1S7AdnxtRWFSYCVd3oPj0b5/r++uoJydR0l6S3Ynd2Fqf+cjMtSrNY89aZZF87mcSkZl6HZow5BP40FtcDJonITyJyvYg0CXRQpuYbPrgXmU0GAtCqNJMtr53Bbvv1sTG1kj8jlD2kqp2B64HmwI8iMjngkZkaTUJCOOrvrzGz3iAAOhQvZc3Lp7Mn1wa1Maa28esHZa4twCacnkcbByYcU5uEhIbS/foPyIg+CoBOhb+z8qXTyM/b5XFkxpiD4U9fQ9eKyA/Atzg9kV6tqt0CHZipHSIiIznyprH8FuXclda54DeWv3A6+Xl2N5ExtYU/NYJWwC2q2llVHyjTjbQxREXH0uHGz5kX2QuALgVz+e3liykoLvE4MmOMP/xpI7gLiBORKwBEJElE2gQ8MlOrRMfG0f6mz5kf0YNsjeXhHYO59v05lgyMqQX8uTT0APBP4G53VjjwfiCDMrVTTGw92tw0noeSnmOBtuG7xVu4/oO5FBZbj6XG1GT+XBo6CzgDp58hVHUDEB/IoEztFRtXj0euPofereoDMHnRZp5+411rMzCmBvMnERSq0yGRAoiIPz2PmiAWFxnGyCv60LNlIv1CFvCPjbez8vlTyM3Z7nVoxphy+JMIRovIa0CiiFwNTAZGBDYsU9vFR4Xz7pV9uS3+OyKliE6F89j44mB2btvkdWjGmDL8aSx+GvgEGAMcAdyvqv6MUGaCXHxUOF1vGcNcd5Sz1OJl7HhlEFs3rvE4MmOML39HKPtGVe9Q1dtV9ZtAB2XqjqjoWDrfMo6Z8ScC0Lp0LQWvD2bTmiUeR2aM2auybqh3iUhORY/qDNLUbhGRkfS6eTTT658JQLJuIuTtIaxbmuFxZMYYqCQRqGq8qtYDngfuApJxxh3+J/Bo9YRn6orQsDCOunEkvzQZBkBjthP/4V9YMHeax5EZY/y5NHSyqv5HVXepao6q/hc/hqo0piwJCaHfNS8ztdW1AKwubcLFYzbz9XxrQDbGS/4kghIRGSYioSISIiLDAPu5qDkkEhLC0Vc8ztTuTzC8+A52Fodz7QezeevnVV6HZkzQ8icRXAycD2x2H+e584w5ZEef9XeeuOxEYiJCUYWHJyzg03eeo6S42OvQjAk6/tw+ulpVz1TVRqqapKpDVXV1NcRm6rjjj2jM6Gv6kRQfyY2hYzl71YPMf/Z08nfbvQjGVKeDGY/AmCrXJTmBscN7MTRyFgDd835h3XMnsHW9XSoyprpYIjCeS0lqQKObvtvXjXVq8TIYMZCls2wgPGOqgyUCUyMkJDbkyNu+ZloD57cGjcim9fjzmf3p8x5HZkzd53ciEJF0EflORKaKyNBABmWCU0RkJOk3vsO0jvdRqKFESAm95z3AzFeuoLgw3+vwjKmzKvtlcdMys27D6Y56CPBIIIMywUtE6HfBHSwd8iHbSACgT9anPP/aq+zYXehxdMbUTZXVCF4VkX+JSJQ7nY1z2+gFgN3WYQKqS78hFFzxLUtD2/N28cm8vD6VM175md8zd3odmjF1TmVdTAwFMoAJInIpcAtQCsQAdmnIBFzzVqmk3PYDczreDsC67Xs457+/MPr7mWipjXpmTFWptI1AVccDJwOJwKfAElV9UVWzqiM4Y2Ji43nx4j7cfcqRhIYIUSW7SP/+IuY9fSo527d4HZ4xdUJlbQRniMjPwHfAfOBC4CwRGSUi7aorQGNEhGuOa8foa9L5V8yntAzJonveNPa81I9ls771Ojxjar3KagSP4tQGzgGeUNVsVb0NuB94rDqCM8ZX71YNGHT9S8yOGQBAE91Km/HnMuP9+9FS6/7KmENVWSLYiVMLuBDYVwdX1WWqemGgAzOmPIkNk+h1+3h+Sb2TQg0jTErpu/wFFjx5ElvXr/A6PGNqpcoSwVk4DcPFWCdzpgaRkBD6D7uXFWeOZb00AaBL/hwiRwxg7oTXQdXjCI2pXSq7a2irqr6kqq+qqt0uamqcjr2OJe7m6UxPOBWAePKInvEi//hoJjvzijyOzpjaw7qYMLVaQmID0m8dxax+r7CJhtxWdC1jfsvi5Oen8POyrV6HZ0ytYInA1AlpJ19CyM0ZNO7QB4BNOflc8uZ0vnz9PnKy7W5nYypjicDUGY3r1+Pty/vw2FldiA4P5cyQqZy64SWKnk8j4+u3rO3AmAoENBGIyBARWSIiy0XkrnKWHysic0SkWETODWQsJjiICMOOasVXNx/DuQlLAGhINj2m38q8p4aQZXcWGfMnAUsEIhIKvAKcAnQCLhKRTmVWWwtcDnwYqDhMcGrdKJYBd3zC9B6Ps514ALrlTSdmRH9mfvxvSm1ITGP2CWSNoC+wXFVXqmoh8BFwpu8K7jCY83D6MDKmSklICOlDr6X02hn8Wm8wALHk02fR46x4vB8r5v7gbYDG1BCBTATJwDqf6Ux33kETkeEiMktEZmVlWcOfOTiNmjTnqNv+x9yBb7Me53cHqcVLafTZRTzwv1/ZllvgcYTGeCuQiUDKmXdIrXWq+rqqpqlqWlJS0mGGZYJVz4Fnk3j7LKY2u4xCDeOF4nN4Z/ZWjn/6B0ZOXUVxiVVMTXAKZCLIBFr4TKcAGwL4esYcUGxcPY6+5kU2XDKFVW2dH8zn5Bfz4PiF/Pepu1k4ZYzdXWSCTiATwUwgVUTaiEgETp9F4wL4esb4rXVqZ966sh9v/DWNlg1iaCGbGb7nTTp9dyULnjieVfN+9jpEY6pNwBKBqhYDNwATgUXAaFVdICIPi8gZACLSR0QygfOA10RkQaDiMaYsEeGkTk2YdOux3NVLKSIMgM75c2nz6V+Y++xZbFy10OMojQk80VpWDU5LS9NZs2Z5HYapg7ZsWsfyTx4kLWssEeJ0a12koWQ0OYvUcx8msfEh3etgTI0gIrNVNa28ZfbLYmNcjZu2oP8Nb7LhkinMiDsBgHApoc+WT4h4pScT33/aOrMzdZIlAmPKaJ3ahb63j2XRGROYF9ETgBgpYOTCUgY88R3PTFpCdl6hx1EaU3Xs0pAxlVBVMn4cy7rpY7kp+wL23hUdFxnKC22m0/vUq+2SkakV7NKQMYdIROg58GxO/+e7vHV5H7qnJADQq2gOJ65+jshXejDzP1exec1ijyM15tBZjcCYg6Cq/LA0i92f/YPT9vxxN3SJCvMSjifxpNto0+0YDyM0pnyV1QgsERhzCLS0lHlTJ8DPz9G9YM5+yxZGdkf73UinY89BQqzSbWoGSwTGBNCSjKns/PZZeuV8R5j80U3Ff6Oupt7xNzK0RzKxkWEeRmiMJQJjqsX61UtZ88XTdN/yOREU0b/gRbKoT3xkGOempXBZt1hat2rtdZgmSFkiMKYaZW/LYup343lseSs27MwHIJ48pkdez5qoIynseTmdj7+I8MhojyM1wcQSgTEeKC4p5dvFW3h32mpSV33Ag+Hv7luWTTzLmpxKk4FX07JjH++CNEHDEoExHlu9bD4bvnmJzpvHkyC791u2LKwDOUdewJGDriQ2oYFHEZq6zhKBMTVE3u5d/P7th0TP/5BuhRn7LftOezOu4zOc2SOZAamNCA+1O45M1bFEYEwNtHbFQtZ9N4L26z+nCdu4ufA6Pi8dAECD2Aiub7uZAe0b0KHPyUhIqMfRmtquskRg97QZ45GW7TrRst1zFBc9yZyfPid0QzMiF+dQUFzK9t2FdFnyEkcsW8zmrxuypvFJJPQ+h9ReJxISZh9bU7WsRmBMDZJbUMzE+Zv4ds5insu8gEgp3m/5NhJZ2eh44nqeTYejTiE0LNyjSE1tY5eGjKmFtm7ewNLv36Peyi/oWDCPUNn/s5pNPCPbvUjHXv05JrURMRFWUzAVs0RgTC2XtWkdy6eMJmbFF3TKzyBcStijEfQoeJ0CIogIC6Ff24ZcU+8X2vYZQtNWR3odsqlhLBEYU4ds37qZpVNGk7l2JfduHUxBsdOtRUvZzJTIWwFYF5LMxkb9iT7yJNr3PYXouAQvQzY1gCUCY+qoPYUlTF2+lW8Xb6be/Pe5u/S1P61TqKGsiOxMTsqxNOp2Mm26DiDEbk0NOpYIjAkCpSWlrJg/gy1zxpGw4Sc6FC7YN/byXhu1AUPkVY5q25D0tg3p1yaRI5rE251IQcBuHzUmCISEhpDaPZ3U7ukA7Mzezu8zJlKwdDLJ26bRStczvbQjO4uKmbRwM5MWbqaPLObNyGdYFdONgmZ9SDjiGNp0G0BEVIzHpTHVyWoExgSJzFVLyFi9hW82xzFtxTa27CrgptBPuS38k/3WK9QwVkWkkt2oF1Ft+9Oi67E0aNrSo6hNVbFLQ8aY/agqq7flsXr658Qu/ZSUnN9ozpZy151V2oFb456ge0oiPVok0rNZJJ2axlkDdC1jl4aMMfsREdo0iqXNaRcDFwOwMXMlmb99R/Gq6TTcMZe2xSsJk1IyStuxbvse1m3fw4R5Gxka8jPPhP+XNaEpZMV3pKRxV+q1TaNFp3TirNO8WskSgTEGgGYpbWmW0ha4CoBdOTtYPW8qMdkRnLStARnrstmaW0D3kBWEitKqdB2tdq6DnZNgGTAR1ktTtsR2ILtpf0p6XckRTeNJqR+NiHhaNlM5SwTGmHLF16tP1wGn0RWnzqCqbNyZz9o5xfyyrCmx2xfQomApDcjZt02ybiI5dxMTlhRww/xuzn4iwzit4XrO5ju00RHEpnSmSbvuNGzWxsZ0riEsERhj/CIiNE+MpvkJZ8IJZwKgpaVs3LCajYumk792LtFb59M4fwWLSlvt225XQTFxm2fSJ3w8bB8PS4HvIJdoNoS1ICeuLSUN2hOW3J2ErqfQskEsEWGWIKqTJQJjzCGTkBCfS0oX75s/PK+Q4zbnsmRTDos27aLN8lB25sbuNyhPHHvoULwUspdCNkxf3pGTvoklRKBFgxiOTtjK0KKv0PqtiWqSSmLyETRtfQSRUbEelLRus0RgjKlyCTER9G3TgL5t9jYev4yWvkjW5kw2Lc8gN3MBsnUx8TnLaVKUSSOyWVnaFIBShTXb8uixYw59I8ZAFk4tAihVYbM0YHtEM3ZHJ1OQ2I5N3a4npX40LRrE0KReFKEh1h5xsCwRGGOqhYSEkNSsJUnNWgJn7Lds5/YsumTt4OncGFZtzWVl1m7arYfcvGjiZM++9UJEacI2mhRug8L5LN/RnGFLBuxb3iZkC6MiHiU7vDG7o5pSFJdMSEIykQ1bEpvUkvrNWlG/UTIhoTbQjy9LBMYYzyU0SKJbgyS67Te3N1r6GFlZ68las4TcjcsozlpOeM4a4vasp0HRJtZq4/22aEoWTdlK06KtULQQdgEb93+tIg3l5Oj3SEioT5OEKBrHR3JS9idExDckukEycY2Sqd+4BfUaNA6akeEsERhjaiwJCSGpSQuSmrQATvrT8oTCIiZnF5C5I491O/ZQsr6YmWsGE7NnI4nFW2hcupXwMv0t5RPBsmwgewcAkRTyQNSzf9p3kYaSLfXICa1PXngDvky5GW3YgUZxETSIjaDVnoUkxEQR17ApiY2aERUTH4i3oFpYIjDG1FpREeG0bxxO+8Zx7pxWwJn7lpcUF7Np01qyN69hd9Y6CrdnkrcnjzNimrMpJ58tOfmE79pW7r7DpYQkdpBUsgNKVnLrgo0s1z9qCBMj7qR9SOa+6TyNJEfiyQ1NID+sHgUR9fm9yZnsaNKfxJhwEmPCaVa4hkTZQ0xiI2ITGhKfmEREZFQg3pqDYonAGFNnhYaF0TSlLU1T2u43/8Qy6+XuOoPtm9aSk7We/O2ZFO3cBLlbCN2zlYj8rcQWbUfiGhO2WygudbrlaSA5++0jRgqIoQBKtkIJUAAfbuvAmPlJ+9Z5KuxV0sOm7LfdHo1gl8SRFxJLfmg88+IG8EuTi4mPCic+KoyWJetos3suxfXbUdTyGAYesf/lsKpgicAYE/Ti4hOJi0+E1G4VrvMNzo/qcvYUs213AVtXjWRd9kaKdmVRumsL7NlBWP52wguziSraSVzJTnZFNIT8P/bhe/vsXtFSSDTboXQ7lMLMrOZ8tnHDvuWXhH7DheFv83VJH+4MjWPegydXZdEBSwTGGOM3ESEhJpyEmHBIKluv+LPXgeKSUnLyi9m5p4j89Un8tn0tRbnbKM7djuZnQ/5OQgt2Ela0i4iiXeyKbkcLiWZXfjG78oupRx4AuzSa+KjwwJQrkL2PisgQ4AUgFHhDVR8vszwSeBfoDWwDLlDV1ZXt03ofNcYEC1Vlz548dmdvJbewlPzIhnRsVu+Q9uVJ76MiEgq8AgwCMoGZIjJOVRf6rPY3YIeqtheRC4EngAsCFZMxxtQmIkJMTCwxMbEkHXj1QxbIDj36AstVdaWqFgIf4duc7zgTeMd9/glwolg3hcYYU60CmQiSgXU+05nuvHLXUdViYCfQsOyORGS4iMwSkVlZWVkBCtcYY4JTIBNBeWf2ZRsk/FkHVX1dVdNUNS0pKZAVJGOMCT6BTASZQAuf6RRgQ0XriEgYkABsD2BMxhhjyghkIpgJpIpIGxGJAC4ExpVZZxxwmfv8XOA7rW2DKBtjTC0XsLuGVLVYRG4AJuLcPvqWqi4QkYeBWao6DngTeE9EluPUBC4MVDzGGGPKF9AflKnql8CXZebd7/M8HzgvkDEYY4ypXEB/UBYIIpIFrDnEzRsBW6swnNoiWMsNwVt2K3dw8afcrVS13Lttal0iOBwiMquiX9bVZcFabgjeslu5g8vhlttGiDbGmCBnicAYY4JcsCWC170OwCPBWm4I3rJbuYPLYZU7qNoIjDHG/Fmw1QiMMcaUYYnAGGOCXNAkAhEZIiJLRGS5iNzldTyBIiJvicgWEZnvM6+BiHwjIsvcv/W9jDEQRKSFiHwvIotEZIGI3OzOr9NlF5EoEZkhIr+55X7Ind9GRH51y/2x281LnSMioSIyV0QmuNN1vtwislpEfheRDBGZ5c47rOM8KBKBzyA5pwCdgItEpJO3UQXMSGBImXl3Ad+qairwrTtd1xQD/1DVjkA6cL37P67rZS8ATlDV7kAPYIiIpOMM8vScW+4dOINA1UU3A4t8poOl3Merag+f3w4c1nEeFIkA/wbJqRNUdQp/7sHVdwCgd4Ch1RpUNVDVjao6x32+C+fLIZk6XnZ15LqT4e5DgRNwBnuCOlhuABFJAf4CvOFOC0FQ7goc1nEeLInAn0Fy6rImqroRnC9MoLHH8QSUiLQGegK/EgRldy+PZABbgG+AFUC2O9gT1N3j/XngTqDUnW5IcJRbgUkiMltEhrvzDus4D2inczWIXwPgmNpPROKAMcAtqpoTDCOfqmoJ0ENEEoGxQMfyVqveqAJLRE4DtqjqbBEZuHd2OavWqXK7jlbVDSLSGPhGRBYf7g6DpUbgzyA5ddlmEWkG4P7d4nE8ASEi4ThJ4ANV/dSdHRRlB1DVbOAHnDaSRHewJ6ibx/vRwBkishrnUu8JODWEul5uVHWD+3cLTuLvy2Ee58GSCPwZJKcu8x0A6DLgcw9jCQj3+vCbwCJVfdZnUZ0uu4gkuTUBRCQaOAmnfeR7nMGeoA6WW1XvVtUUVW2N83n+TlWHUcfLLSKxIhK/9zkwGJjPYR7nQfPLYhE5FeeMYe8gOY95HFJAiMgoYCBOt7SbgQeAz4DRQEtgLXCeqtapIUFFZADwE/A7f1wzvgennaDOll1EuuE0DobinNiNVtWHRaQtzplyA2AucImqFngXaeC4l4ZuV9XT6nq53fKNdSfDgA9V9TERachhHOdBkwiMMcaUL1guDRljjKmAJQJjjAlylgiMMSbIWSIwxpggZ4nAGGOCnCUCY6qYiCSKyHVex2GMvywRGFOF3J5uE4GDSgTisM+j8YQdeCaoici97jgVk0VklIjcLiI/iEiau7yR240BItJaRH4SkTnuo787f6A7FsKHOD9oexxo5/YX/5S7zh0iMlNE5vmMGdDaHT/hP8AcoIWIjBSR+W5/87dW/ztiglGwdDpnzJ+ISG+c7gl64nwW5gCzK9lkCzBIVfNFJBUYBeztD74v0EVVV7m9n3ZR1R7u6wwGUt11BBgnIsfi/AL0COAKVb3OjSdZVbu42yVWZXmNqYglAhPMjgHGqmoegIgcqP+pcOBlEekBlAAdfJbNUNVVFWw32H3MdafjcBLDWmCNqk53568E2orIS8AXwKSDLI8xh8QSgQl25fWxUswfl02jfObfitN/U3d3eb7Pst2VvIYA/1bV1/ab6dQc9m2nqjtEpDtwMnA9cD5wpT+FMOZwWBuBCWZTgLNEJNrt0fF0d/5qoLf7/Fyf9ROAjapaClyK09FbeXYB8T7TE4Er3bESEJFkty/5/YhIIyBEVccA/wJ6HVKpjDlIViMwQUtV54jIx0AGsAan91KAp4HRInIp8J3PJv8BxojIeTjdHZdbC1DVbSIyVUTmA1+p6h0i0hGY5g6UkwtcgnN5yVcy8LbP3UN3H3YhjfGD9T5qjEtEHgRyVfVpr2MxpjrZpSFjjAlyViMwxpggZzUCY4wJcpYIjDEmyFkiMMaYIGeJwBhjgpwlAmOMCXL/DxDvM/rk/2LeAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ks_td = td_solve(ss=ks_ss, block_list=ks_blocks,\n", + " unknowns=['K'], targets=['asset_mkt'],\n", + " Z=ks_ss['Z'] + 0.01 * 0.8 ** np.arange(300))\n", + "dOut_ks_nonlin = 100 * (ks_td[\"C\"]/ks_ss[\"C\"] - 1)\n", + "\n", + "plt.plot(100 * dOut_ks[\"C\"][:50]/ks_ss[\"C\"], linewidth=2.5, label=\"linear\")\n", + "plt.plot(dOut_ks_nonlin[:50], linewidth=2.5, linestyle=\"--\", label=\"non-linear\")\n", + "plt.title(r'Consumption response to TFP shock in the KS Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### One Asset HANK Example" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "On iteration 0\n", + " max error for nkpc_res is 8.25E-04\n", + " max error for asset_mkt is 2.19E-02\n", + " max error for labor_mkt is 8.55E-03\n", + "On iteration 1\n", + " max error for nkpc_res is 2.29E-06\n", + " max error for asset_mkt is 4.13E-04\n", + " max error for labor_mkt is 8.89E-05\n", + "On iteration 2\n", + " max error for nkpc_res is 1.29E-08\n", + " max error for asset_mkt is 1.30E-05\n", + " max error for labor_mkt is 9.45E-07\n", + "On iteration 3\n", + " max error for nkpc_res is 3.04E-09\n", + " max error for asset_mkt is 4.51E-07\n", + " max error for labor_mkt is 3.11E-08\n", + "On iteration 4\n", + " max error for nkpc_res is 1.11E-10\n", + " max error for asset_mkt is 1.48E-08\n", + " max error for labor_mkt is 1.68E-09\n", + "On iteration 5\n", + " max error for nkpc_res is 3.46E-12\n", + " max error for asset_mkt is 4.23E-10\n", + " max error for labor_mkt is 6.60E-11\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "hank_td = td_solve(ss=hank_ss, block_list=hank_blocks,\n", + " unknowns=['pi', 'w', 'Y'], targets=['nkpc_res', 'asset_mkt', 'labor_mkt'],\n", + " Z=hank_ss['Z'] + 0.01 * 0.8 ** np.arange(300))\n", + "dOut_hank_nonlin = 100 * (hank_td[\"C\"]/hank_ss[\"C\"] - 1)\n", + "\n", + "plt.plot(100 * dOut_hank[\"C\"][:50]/hank_ss[\"C\"], linewidth=2.5, label=\"linear\")\n", + "plt.plot(dOut_hank_nonlin[:50], linewidth=2.5, linestyle=\"--\", label=\"non-linear\")\n", + "plt.title(r'Consumption response to TFP shock in the One Asset HANK Model')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.legend()\n", + "plt.show()" + ] } ], "metadata": { diff --git a/notebooks/krusell_smith.ipynb b/notebooks/krusell_smith.ipynb index 8edca00..d831b0d 100644 --- a/notebooks/krusell_smith.ipynb +++ b/notebooks/krusell_smith.ipynb @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 58, "metadata": {}, "outputs": [], "source": [ @@ -79,14 +79,15 @@ "import sys\n", "sys.path.append(\"..\")\n", "\n", + "import copy\n", "import numpy as np\n", "from numba import njit\n", "import scipy.optimize as opt\n", "import scipy.linalg as linalg\n", "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj\n", - "from sequence_jacobian import simple, het" + "from sequence_jacobian import simple, het, create_model, estimation\n", + "import sequence_jacobian.utilities as utils" ] }, { @@ -98,16 +99,21 @@ "## 1 Set up heterogeneous-agent block\n", "The main task here is to write a **backward iteration function** that represents the Bellman equation. This has to be a single step of an iterative solution method such as value function iteration that solves for optimal policy on a grid. For the standard income fluctuation problem we're dealing with here, the endogenous gridpoint method of [Carroll (2006)](https://www.sciencedirect.com/science/article/pii/S0165176505003368) is the best practice. \n", "\n", - "Once we have the backward iteration function, we can use the decorator `@het` to turn it into a HetBlock. All we have to do is specify the transition matrix for exogenous states `exogenous`, the policy corresponding to the endogenous state(s) `policy` (currently up to two states), and the backward variable `backward` on which we're iterating (here the first derivative `Va` of the value function with respect to assets)." + "Once we have the backward iteration function, we can use the decorator `@het` to turn it into a HetBlock. All we have to do is specify the transition matrix for exogenous states `exogenous`, the policy corresponding to the endogenous state(s) `policy` (currently up to two states), and the backward variable `backward` on which we're iterating (here the first derivative `Va` of the value function with respect to assets) and a function that initializes a guess for the backward variable, `backward_init`." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "@het(exogenous='Pi', policy='a', backward='Va')\n", + "def household_init(a_grid, e_grid, r, w, eis):\n", + " coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis]\n", + " Va = (1 + r) * (0.1 * coh) ** (-1 / eis)\n", + " return Va\n", + "\n", + "@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init)\n", "def household(Va_p, Pi_p, a_grid, e_grid, r, w, beta, eis):\n", " \"\"\"Single backward iteration step using endogenous gridpoint method for households with CRRA utility.\n", "\n", @@ -131,8 +137,8 @@ " uc_nextgrid = (beta * Pi_p) @ Va_p\n", " c_nextgrid = uc_nextgrid ** (-eis)\n", " coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis]\n", - " a = sj.utilities.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid)\n", - " sj.utilities.optimized_routines.setmin(a, a_grid[0])\n", + " a = utils.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid)\n", + " utils.optimized_routines.setmin(a, a_grid[0])\n", " c = coh - a\n", " Va = (1 + r) * c ** (-1 / eis)\n", " return Va, a, c" @@ -140,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -162,6 +168,46 @@ "As its name suggests, HetBlock is a general class of heterogeneous-agent blocks that comes with useful methods, such as solving for steady-state policy functions by iteration, updating the distribution of agents across states using these policy rules interpolated against a grid, and computing/storing Jacobians. We are going to cover the the most important methods in this notebook." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The rest of the blocks that constitute the Krusell Smith model are listed below but can also be found in the `krusell_smith.py` module in `sequence_jacobian/models`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "@simple\n", + "def firm(K, L, Z, alpha, delta):\n", + " r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta\n", + " w = (1 - alpha) * Z * (K(-1) / L) ** alpha\n", + " Y = Z * K(-1) ** alpha * L ** (1 - alpha)\n", + " return r, w, Y\n", + "\n", + "\n", + "@simple\n", + "def mkt_clearing(K, A, Y, C, delta):\n", + " asset_mkt = A - K\n", + " goods_mkt = Y - C - delta * K\n", + " return asset_mkt, goods_mkt\n", + "\n", + "\n", + "@simple\n", + "def income_state_vars(rho, sigma, nS):\n", + " e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho, sigma=sigma, N=nS)\n", + " return e_grid, Pi\n", + "\n", + "\n", + "@simple\n", + "def asset_state_vars(amax, nA):\n", + " a_grid = utils.discretize.agrid(amax=amax, n=nA)\n", + " return a_grid" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -178,65 +224,19 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ - "def ks_ss(lb=0.98, ub=0.999, r=0.01, eis=1, delta=0.025, alpha=0.11, rho=0.966, sigma=0.5, nS=7, nA=500, amax=200):\n", - " \"\"\"Solve steady state of full GE model. Calibrate beta to hit target for interest rate.\n", - " \n", - " Parameters\n", - " ----------\n", - " lb : scalar, lower bound of interval bracketing beta\n", - " ub : scalar, upper bound of interval bracketing beta\n", - " r : scalar, real interest rate\n", - " eis : scalar, elasticity of intertemporal substitution\n", - " delta : scalar, depreciation rate\n", - " alpha : scalar, capital share\n", - " rho : scalar, autocorrelation of income process\n", - " sigma : scalar, cross-sectional sd of log income\n", - " nS : int, number of income gridpoints\n", - " nA : int, number of capital gridpoints\n", - " amax : scalar, upper bound of capital grid\n", + "blocks = [household, firm, mkt_clearing, income_state_vars, asset_state_vars]\n", + "ks_model = create_model(blocks, name=\"Krusell-Smith\")\n", "\n", - " Returns\n", - " ----------\n", - " ss : dict, steady state values\n", - " \"\"\"\n", - " # set up grid\n", - " a_grid = sj.utilities.discretize.agrid(amax=amax, n=nA)\n", - " e_grid, pi_s, Pi = sj.utilities.discretize.markov_rouwenhorst(rho=rho, sigma=sigma, N=nS)\n", - " \n", - " # solve analytically what we can\n", - " rk = r + delta\n", - " Z = (rk / alpha) ** alpha # normalize so that Y=1\n", - " K = (alpha * Z / rk) ** (1 / (1 - alpha))\n", - " Y = Z * K ** alpha\n", - " w = (1 - alpha) * Z * (alpha * Z / rk) ** (alpha / (1 - alpha))\n", - " \n", - " # initialize guess for policy function iteration\n", - " coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis]\n", - " Va = (1 + r) * (0.1 * coh) ** (-1 / eis)\n", - "\n", - " # solve for beta\n", - " beta_min = lb / (1 + r)\n", - " beta_max = ub / (1 + r)\n", - " beta, sol = opt.brentq(lambda bet: household.ss(Pi=Pi, a_grid=a_grid, e_grid=e_grid, r=r, w=w, beta=bet, eis=eis,\n", - " Va=Va)['A'] - K, beta_min, beta_max, full_output=True)\n", - " if not sol.converged:\n", - " raise ValueError('Steady-state solver did not converge.')\n", - "\n", - " # extra evaluation for reporting\n", - " ss = household.ss(Pi=Pi, a_grid=a_grid, e_grid=e_grid, r=r, w=w, beta=beta, eis=eis, Va=Va)\n", - " \n", - " # check Walras's law\n", - " walras = Y - ss['C'] - delta * K\n", - " assert np.abs(walras) < 1E-8\n", - " \n", - " # add aggregate variables\n", - " ss.update({'w': w, 'Z': Z, 'K': K, 'L': 1, 'Y': Y, 'alpha': alpha, 'delta': delta, 'walras': walras})\n", + "calibration = {\"eis\": 1, \"delta\": 0.025, \"alpha\": 0.11, \"rho\": 0.966, \"sigma\": 0.5, \"L\": 1.0,\n", + " \"nS\": 7, \"nA\": 500, \"amax\": 200}\n", + "unknowns_ss = {\"beta\": 0.98, \"Z\": 0.85, \"K\": 3.}\n", + "targets_ss = {\"r\": 0.01, \"Y\": 1., \"asset_mkt\": 0.}\n", "\n", - " return ss" + "ss = ks_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { @@ -250,14 +250,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "metadata": { "scrolled": false }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXgAAAEGCAYAAABvtY4XAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOydd3RV15m+n3ulq957r0iARJHovXcDNmDcMQaXNNuJM4kn+U3KTDJO1iQzmZnMZGIbgxDY2AbTm+m9SkIC1OioF9Rvr/v3x5EBJchcX5ANeD9reRlJ+5y7xbK/e+7+3u99VUIIJBKJRPLoof6mNyCRSCSSnkEWeIlEInlEkQVeIpFIHlFkgZdIJJJHFFngJRKJ5BHF/ZvewO2EhYWJpKSkb3obEolE8tBQUFDQJIQIv9PPHqgCn5SURH5+/je9DYlEInloUKlUFd39TB7RSCQSySOKLPASiUTyiCILvEQikTyiyAIvkUgkjyiywEskEskjiizwEolE8ogiC7xEIpE8osgCL5FIJN8QwiEwXmhBe6iqR+7/QA06SSQSybcBYXVgKGxEe7QaW6MRt2BP/EbFotLc32duWeAlEonka8Kus6A/WYfuRB0OvRVNtC/BT/fGp38YKvf7f6AiC7xEIpH0MNZGA7qjNejPNIBN4NUnBL+xsXimBKJSqXrsdWWBl0gkkh5ACIH5Sju6I9WYLrSCuxrfwZH4jY5FE+HztexBFniJRCK5jwibA8O5G+iO1GCt06P20xAwJQHfEdG4+Xl8rXuRBV4ikUjuAw6DFd3penTHa3F0WHCP8CF4QRo+WRH3vXnqLLLASyQSyT1gazaiPVqDIb8BYXXgmRaE/4I0PNODe/R83RlkgZdIJJKviBACS0UH2iM1mEqbQa3CZ2A4fmPj8Ij2/aa3dxNZ4CUSicRJhF1gLGlCd6QGS5UWlbc7/hPi8RsZg1uAa+frQggM7W34BgXf593KAi+RSCR3xWGyoc9rQHesBnubGfdQL4IeT8VncCRqDzeX7mm32bh48igF2zdh0utY+l/voVa7dq/ukAVeIpFIusHWZkJ3rBb96XqE2Y5HUgBBc1Lx6huCSu3a+bpJr+P8vl2c+XwruuYmgmPiGDb3SYRD3HfzGFngJRKJ5G+wVGvRHqnBeP4GAN79w/EfE4tHvL/L92xvrOfMji2cP7AHq8lIQr8BTH3lByRnDUal7hmVjSzwEolEgmL8ZSprQXu0Gsu1DlSebviNjsVvdAzuQV4u37f2YhkF2zZx6fQJVGoVfUaNY/DseUQkpdzH3d8ZWeAlEsm3GofFjqGgAd2xWmxNRtyCPAl8LAXfoZGovVwrkQ6HncunT5C/bSN1ly7g6evL0LnzyZoxG/+QsPv8G3SPLPASieRbib3Dgu5ELfpTdTgMNjRxfoQ82wfvfmGo3Fw7X7cYDRQf2MOZnVtob2wgKDKaSUu+Q+aEKXh4ed/n3+DuyAIvkUi+VVjq9OiO1mAoagSHwCsjFP+xsXgkBrg8mNTRdIPCz7dyft8uzAY9sX0yGL/oZVKHDL/vypivgizwEonkkUcIgflSG9oj1ZgvtaHSqPEdFoX/6Fjcw1x/sm64epn8bRu5ePIoQgjSh49m8OwniO7V26nrLSYbZcfqaKnTM/GFPi7voztkgZdIJI8swurAUNSI9mgNtgYDan8PAqYn4Tc8CrWPxrV7OhxcOZNHwfaNVJcW4+HtTfaMOQyaOZeA8Ain7qFtMXHuQDWlR2qwmOzEpAVhs9px10gdvEQikXwpdr21M1ijFofOiibKl+CF6fgMDHc5WMNqNlFyaD9ndmyita4W/7Bwxi96mf6TpuPp45z9741KLYV7KrlS0IgAUgeFkzUlgcikAJf2dDdkgZdIJI8M1hudwRoFjWBz4NU7WAnWSA1y+Xxd19pC0a7tnN2zA5NOS1RqGo/98G3Sh49G7Xb3J27hEFQUN1O0t5Kai21ovNzoPzGOAZPiCAjt2carLPASieShRgiB+Wo7uiM1mMpbwF2Fb3YkfmNi0ES6bvx1o+IaBds3U37sIHa7nV5DRjB49hPE9s5w6s3CZrFTfrKes/uqaGsw4BfsyagFvcgYE4On99dTemWBl0gkDyXC7sB4rgnt0RqsNTrUvu74T07Ab6TrwRpCCCrOniF/+yYqzhXi7ulJ/8kzGDzrcYKiop26h6HDwvlD1RQfqsGksxKe4M/UlzNIHRSBm9udj4eExYLK4/6HgfRogVepVNcBLWAHbEKIIT35ehKJ5NHHYbShP12H7lgt9g4L7uHeBM3vhW92BCoXm5Q2q5Xyowcp2L6JpqoKfINDGPPMiwyYOhNvP+fsCVpq9RTtq+TiqQbsdgdJ/cPImhJPTFr3x0PGoiKaV+Zirawkaf1n990//ut4gp8ohGj6Gl5HIpE8wthaTMr5en49wuLAMzWQoPlpeKUHu2z8ZdRpObdnJ4Wfb0Xf1kpYQhIzvv8WfUaPw8397iobIQTVF1op2lNFZUkzbho1fUZFkzU5nqDIOzdehd2Odt8+WnJWYiwsRO3vT/DTTyGs1vv+FC+PaCQSyQONuaID3ZFqjCXNoPoiWCMWjxg/l+/ZVl9HwY5NFB/ci81sJmngIGb84Mck9s9y6inabnNwOb+Bon1VNFXp8PbXMGxOMv3Gx+LdzfGQQ6+nbcNGWlatwlpVhSYujsj/9/8IWjAftW/PhIT0dIEXwG6VSiWA94QQ7//tApVK9RrwGkBCQkIPb0cikTwMCMdtwRqVWlRe7viPj1OCNQI9Xb5vzYUy8rdu4HL+Sdzc3OgzZgJDHnuCsIQkp6436a2UHKnh/IFq9O0WgqN9mbioD+nDIrvVsFsbGmj98ENaP12Lo6MD76wsIn7yE/ynTEblhArnXlAJIXru5ipVjBCiVqVSRQB7gDeEEIe7Wz9kyBCRn5/fY/uRSCQPNg6zHUN+PdpjtdhbTLiFeOE/JlYJ1vB0rRj+rfGXl68fA6fNImv6bPyCQ5y6R/sNI2f3V1F2vA6b2U5cn2CypiaQkBHS7RO/qayM5pwcOnbsBIcD/6lTCXlpMT7Z2V3W3TDcoLylnLFxY136/VQqVUF3/c0efYIXQtR2/rtRpVJtBIYB3RZ4iUTy7cTeYUF3vBbdqTqE0YZHYgBBs5Lxygh1+XzdYjIqxl87Nt8y/lr6XfqNn4LGyzn737or7RTtreRa0Q1UahXpQyMZOCWesLg7N16Fw4Hu8GFaclZiOHUKlY8Pwc89S8iiRXjEx3dZe6HlAqtKV7Hj2g683b058NQBPN1c/3RyJ3qswKtUKl9ALYTQdv55GvCbnno9iUTy8GGt16M9csv4yzszFL+xcXgmuj7ZqWtppvDzrZzduxOzXk9Met+vZPzlcAiuFt6gaG8lDdc68PRxJ3t6IgMmxOEbdOcC7DCZaN+yhZaVuViuXsU9MpKIn/6EoIULcQu49bsIIThee5zcklxO1J3A292bhekLWdR30X0v7tCzT/CRwMbOjy/uwBohxOc9+HoSieQhQAiB+Uob2sM1mC+23jfjrxsV18jftpHyY4cRDgdpw0YyePY8YtKdM/GymGyUHa/j3P4qOppMBIR5MfbpdPqMjMKjG194W3MzrWs+pvXjj7G3tOCZ0ZeYP/6BgBkzUGluqXAsdgvbr25nVekqLrddJtw7nB8O+iEL0xcS6Bno8u98N3qswAshrgIDe+r+Eonk4ULYHRjONaE7XI21To/aT0PAtER8h0fj5uui8ZcQXD97hvxtG6k8X4TG04uB02YyaObjBEVGOXUPXati/FVypBaL0UZ0aiCjF6SRNDAMdTfHQ+bLl2nJzaV98xaExYLfxImEvPQSPsOGdjmTbzO18emFT/m4/GOaTc2kB6fzzph3mJk0E42bBmxmKPwIbpTDtN+69HfwZUiZpEQi6VEcJhv60/XojtVgb7fgHuFN8II0fLIiUGlcM/6642DSs4sZOGUmXn7OySdvVGkp2lvJ5bxGhBCkZEeQNTWeqOQ7P1ELITCcPElzTg76w0dQeXoSOG8eIYsX45mS3GXt9fbrrC5dzZYrWzDZTYyOHc3ijMWMiB6hvAEYWiB/OZxeBroGiOwPVhNoXI8GvBOywEskkh7B1mZCd7QWfV49wmzHMyWQoHn3OJik7eDsnp0U7drm2mCSQ1BR0kzR3ipqLrSi8XSj34RYBk6KJ6Cb4yFhsdC+YwctK3Mxl5fjFhpK2JtvEPzMM7iH3FLhCCEoaCggtzSXQ1WHcFe7Myd1Dov6LqJXcC9lUfMVOPl/ylO7zQipk2Heu5AyEe7zFCvIAi+RSO4zlhod2sPVGM/fAMB7QDj+Y+PwiHV9MKm1vpYzOza7PJhks9q50Gn81VpvwDfIk5HzU8kcE4NnN77w9vZ2WteupXX1h9gaG/FM60X0O/9KwOzZqD1vNUStDit7ru9hVekqSppLCPIM4rUBr/FMn2cI8w4DIaDiOBz/X7iwA9w0MOApGPEDLKF92HauloufX+BnM2Xgh0QieQARDoHpYiu6w9WYr7aj8nTDb3QsfqNjcA9y7dhBCEHthTLyt210eTDJ0GGh+HANxYeqMWqthMX7MWVJBr2GdG/8ZamupiV3FW3r1yMMBnxHjST6nX/Fd8yYLm8mWouWDZc28GHZh9Tr60kKSOKXI37JnNQ5eLt7g90GxeuVwl57BrxDYNxPYOirtLuHsOZUJSuP76ehw0yfKH9+NCUNLxn4IZFIHhRuJiYdqcbWaMQt0IPAWcn4DotC3Y3y5G447HYunT5BwbaN1F1WBpOGP7HwKw0mtdbrKdpbxYWT9dhtDhL7h5I1JYHY9C8x/jp7luaclWh37wa1msDHZhHy0kt49e3bZV2trpYPyz5kw6UN6K16hkQO4Z+G/xPj4sahVqnB1AGnl8Opd6G9CkJS4bE/wcBnqdLB8gPXWJtfiMFiZ3SvUP5twQDGp4ffd6MxkAVeIpG4wN8lJkX7EvJ0b7wHhKHq5sn4btzrYJIQgpqLbRTtraTivGL81XtkFFmT4wmOurPXi7Db0R04QHPOSowFBaj9/QlduoTgF15AE9VVhVPcVExuSS57KvYAMC1pGoszF5MZmqksaKtSivqZVWDugMTRMPMPkD6Dwup2lq0r4/PietQqFXMHxvDy2GQyY3pOIgmywEskkq+ArcmI9mgNhoIGhPWLxKQ4PFMDXX4C1bY0Ufj5Ns65OJhktzu4nN9I0d7Km8ZfQ2cn0398LN7+3Rh/GY20b9qkDCZVVKCJiSHy5z8jcMGTuPndejOwO+wcrD7IqpJVnGk8g5/Gj0UZi3iuz3NE+3X6w9ecgRP/CyWblK8z58HIH2CPzmZPaQMfvHeS/IpWArzceW1cKi+NSiIq8P6qZbpDFniJRHJXzBUd6A5XYyxtBrUKn+wI/MfG3lNiUuP1qxRs20j58SMuDSaZDVZKjtRy7kA1+jYzwVE+THi+N72HR+Hucec3BltTE61r1tC65mPsbW149e9P7H/+Cf+pU1G53yqHBquBLVe2sLp0NZXaSmJ8Y/jpkJ8yP20+fh5+4HBA+Q6lsFccA88AGPE9GP5dDD7RfFZQzYo1B7nebCA+xJtfz8ngqSHx+Hp+vSVXFniJRHJHhENgKm1Ge7hacXT0dsd/Qrzi6BjgemLSvQ4mdTR1Gn8dq8NqthPbO5gJz/cmMbN73xrzlSu0rFypDCZZrfhNmkTokpfwHjy4yyePG4YbfFz+MWsvrqXd3E7/sP78cdAfmZIwBXe1O1gMkPcBnPg/aLkCgfEw/XeQvYhGiwe5J67z0an9tBmsZCcE8faMPkzLiMTdxWOre0UWeIlE0gWHxY6hoAHt0RrszYqjY9DcVHyGRKLu5sn4btisVsqOHqBg2yaaqytdGkyqv9pO0d4qrhY2olKp6DU0gqzJCYQndGP8JQSGU6dpzlmB/tBhZTBpfudgUnLXwaSLrRdZVaIYf9kcNibGT2Rx5mKyI7KVNwBtA+Qtg7zlYGyBmEHw5Aro+zgXbhhZtvUqW4pqsTocTMuI5LVxKQxOdK4h3JPIAi+RSACway3oTtSiP1mHw2DDI8GfwBnJeH/Jk/HduNfBJIdDcP1sE0V7K6m70q4Yf01LoP+EePyC72zOJaxWOj7/nOacHMylZbiFhBD2xusEP/vs3w0m3W785eXmxfy0+SzKWERiQKKyqKEUTvwFzq8FuxV6z4JRryPiR3D0SjPLcs9w+OINvDVuPDMsnqWjk0kK65nwDleQBV4i+ZZjbeh0dCxUHB29MkLxH3dvjo6t9bUUbN9MycG92CxffTDJarFz4UQdRXuraL9hxD/UizFPpdF3VHS3xl92rZa2tetoWb0aW309HikpRP32NwTOndtlMMlit7Dj2g5yS3K53HaZMO8w3sx+k4XpCwnyClIGk67sV/TrV/aBuzcMehFGfB9LYDJbztbywcajlNdrCff35KfTe/P88ASCfO5/aPa9Igu8RPItRAiB+Wo7usPVmC50OjoOjcJvTCwaFx0d79tg0qFqzh+qwaSzEpHoz/RX+5GSFYa6m3Nsa20tLatW07ZuHQ69Hp/hw4n651/jN24cKvWta9rN7ay7uI6Pyj6iydhEr6Be/Hb0b5mVPAsPNw+wWaDoY6Vx2lAMfpEw6Rcw5GXa8OOjU5XkHt9Po9ZM70h//vjkAOZmxeDp3rOpTPeCLPASybcIYXdgPN+E9kgN1hodal8NAVMT8R3huqOjw2Hnct5J8rduuJmY5Mpg0tl9VZSfrMdudZA0IIzsqQlE9+pefmk8X0xLTg4du3YBEDBzJiFLXsI7M7PLumpt9c3BJKPNyMjokbwz+h1GxoxU7m1sg4K/wKn3QFsH4X3h8b9A/4VUtNtYsecaa/OrMVrtjE0L448LBzIuLaxHBpPuN7LASyTfAhwmG/q8enRHa7G3m3EP9yZ4fho+2a47OlrNJkoO7qNg+ybaGuoIjIxi0pLv0G/CVKcHk+qutFO0p5Jr55pwc3NiMMnhQHfwEC05ORjy8lD7+hLy4ouELHoBTUxMl7Xnb5xnZclK9lbuRY2amckzWZy5mN4hvZUFrRW3BpMsOkgeD3P/F3pNpqCylWUfF7O7tB43tYq5A2N5ZWwyfaNdP7b6JpAFXiJ5hLG1mdEdr0F/6jZHxydS8eod4nLj1NDeRuGu7RTt3o5J20FUr3TmPLeYXsNGupSY5OWrYcisJPqPj8OnG/mlw2SiffMWWlauxHLtGu7R0US8/TZBC5/Ezf+WisYhHByqOsTKkpWcaTyDv8afxZmLea7Pc0T5dsowawqU8/XSTaBSQ78FMPJ17JH92V1Sz7K/HudMZRuB3hq+Oz6VxaOSiAz4egaT7jeywEskjyCWWh26IzUYzt4ABN79w/EfG4tHN1miztBSW0PB9o2UHtqPzWohdchwhsyeR2yfTOcap2Y7ZcfrOLuvko4mE4Hh3ox/Np3eI6PRdDeY1NKiJCatWYO9pQWvjAxi/vhHAmZM75KYZLKZbg4mXe+4TrRvNG8PfZv5afPx1fgqg0kXdsLx/7k1mDTydRj+XfRekazLr2LF6oNUthhICPHhX+Zm8uTguK99MOl+83DvXiKR3EQIgflyG9rD1ZgvtaHycMNvZDR+Y2JxD3b9CbSmvJT8bRu4nH8KN3d3MsZNYvBjTxAaG3/3iwF9u5nzB6spPlSD2WAjKiXg7olJV68piUmbNiHMZvzGjydk6dK/S0xqMbXwSfknfFL+Ca3mVjJCM/jDuD8wNXGqMphkNUJ+jiJ1bL7UZTCpweLByuPX+ehkMR0mG4MSgvj5zD5My4zCzcVPNw8assBLJA85wu7AeK4J7RdReP4aAmYk4Tc8GrW3i46ODjtX8k6Rt20DdRfL8fLzZ8S8p8iaPhvfoGCn7tFSq6doXyUXTtXjsAtSssLJmpJAdGr3iUnG/Hyac1ai278flYcHgY8/TshLi/FMTe2y9lr7tZuJSWa7mfFx41mcuZghkUOUNwB9kzJxenoZGJogeiAsWA4Zj1PWaGTZlqtsPVuL3SGYnhnFK2NTGJzo3O/1MCELvETykOIw29CfblCi8NrMt6LwsiNQud9D4/TQfgq2b6StvrNx+hUdHWsvtlHY6ejorlGTMTqGgZPiCYr0ufM1Nhva3btpXpGDqbgYt6Agwr7/fYKfexb3sLAu9z7TeIaVJSs5VHUIjVrDnNQ5vJjxIilBKcqipstw8i9QtAZsJkibDqPeQCSO5vDlZj5YeYYjl5rw8XDj+eGJLB2dTELonff1KCALvETykGHvsKA7XoPuZD3CZMMjOYCgx++xcdrRTtGubRTt2o7xi8bpW1+hcWp3cOXMDQr3VHKjUou3v4Zhc5LpNz4Wb787N07tOj1tn62jddVqrLW1eCQmEvXPvybw8cdRe9/S4tscNvZV7iO3JJfzTecJ9Ay8Q2LSCeV8/YvEpIHPwMjXMQf3YnNRLcs3HeVCg5YIf0/entGb54clEthNktOjhCzwEslDgrXRgPZw9c2JU+9+YfiPi8Mj/v41TlMGD2PonPlON04tJhtlx+o4u68KbYuJoMi7Ozpa6+tpWb2atrXrcGi1eA8ZTOQ//T/8Jk7sMphksBrYeHkjq0tXU6OrIcE/gV8M/wVze829lZhUslEp7DUF4B0M434Kw16lVRXER6cqyD1xgBtaJTHpPxYOZM7AGDxc/HTzMCILvETyACOEwHKtA+3hakzlLTcnTv3HxuIe6trEKUDNhTLyt65XGqdubkrjdPY8pxunulYz5w9WUXy4FovRRkxaEGOfSSepX/e+NaayMppzcujYsRMcDvynTyN0yRK8Bwzosu6G4QZrytfw6YVP0Vq0ZEdk89MhP2VC/ATc1G5g1kH+u8pRTFslhKTAY/8BA5/jeodg+d5rrCsowGR1MC49nD89lcyYXg/HYNL9RhZ4ieQBRDgExpImtIdrsFZpUfu6EzAlAd+RMfc0cXol/xR5Wzsbp75+DH/iKbJnON84ba7RUbSnkot5DQiHICU7gqyp8UQld9841R89SvOKFRhOnETl40Pwc88S8uKLeMTFdVl7qfUSuSW5bL+2HbvDzpTEKbyY8SJZEVnKAm29Mm2avxxM7RA/HKb/DpE+k4KqDt7/pJQ9ZQ1o1Goez4rhlbEp9I5y/dPNo4As8BLJA8TfWfWGehH0RCo+g1y36rVazJQeUiZOW+tqCYyI/MoTp9XlrRTtqaSytAV3DzWZ42IZOCmewPA7f4oQFgvt23fQsmIF5kuXcI+IIOIn/0DQU0/hFhDQ5d4n606SW5rLsZpjeLt7szB9IYv6LiI+oPPTREOp4g9zbi0IO/SZDaPewBYzhF0lDSx79xRFVW0E+Wj4wYRevDgqkQj/h3Mw6X4jC7xE8gBg11nQnahDf7IWh96GR/y9W/X+XeM0NY3ZP/oZacNGonZzPgqvcE8lzdU6vAM8GP54Cv3GxeLVzacIxdFxLS2rVmNraMAzLY3o3/+ewMdmofK41Wy1Oqx8fu1zcktyudB6gVCvUN7IfoOn0p+6zdHxgHK+fmUfaHxgyBIY8T10vgmszatixZqDVLcaSQr14bePZ7JgcBw+HrKk3Y7825BIvkG+yDjV5zeAzYFX3xClcZoU4PKZcWtdDQXbN1FycN+txuns+cT2da5xajbaKD1Sy7kDVehalSi8iYv60HtYFG7d+NZY6+oUR8e1axVHxxEjiP7X3+I7ZkyX19RatKy/uJ4Pyz6kwdBASmAKvxn1G2alzMLTzVNxdDz7iVLYG4rBN+Kmo2ODzadzMGkfHSYbQxKD+eXsDKb0jXxkBpPuNz1e4FUqlRuQD9QIIWb39OtJJA8D5srOjNOS2zJOx8WhiXBdk600TjfctOpVJk7nERrnXONU22Li3P4qSo7WYjXZie0dxPjnvjwKz1ReTvOKFUrjVAgCZswgZOmSv3N0rNPV8WHZh6y/tB69Vc/wqOH8auSvGBM7BrVK3eno+FfF/EtbB+F9FOOvAU9xsdnCsu1X2VRUg90hmNEvilfHppCd8OgNJt1vvo4n+B8CZcDDZcMmkdxnhENgKm9RMk6vd6Dycsd/fDx+o1zPOP2icZq/dSO1F8tcapzeqNRStLeSy/mNCKDX4AiypsQT0U3ghxAC/fHjtCxfgf74cVQ+PoQ8/xzBi17EIy62y9rS5lJWlqxk9/XdAExPms7izMVkhGYoC9oq4eS7cCa309FxHMz9H0TqZE5ca+H91Wc5eOEGXho1zw1LYOmYZBJDH5zEpAedHi3wKpUqDngMeAf4cU++lkTyoCJsDgyFjWgPV2O7YcQtyJPA2Sn4Do1E7aKZ1d82TgPCI5n40nfoP9H5xmllaQtFeyqpLm9F4+lG/4lxDJgUR0A38kthtdKxcyfNK3Iwl5fjFh5G+FtvEfzM07gF3lLROISDozVHyS3J5XT9aXw1vrzQ9wWe7/s80X7RyqKaM0rjtGST8nW/BTDqdWwR/dlRXM/7fzlGcU0HYX4e/MPUdF4YkUiw74OXmPSg09NP8P8FvA18u7VKkm8lDoMV3al6dMdrcGitaKJ9CXmmN979w1B1k050N5TG6XaKdm3DqO0gMiWN2T/6R9KGjXKucWpzcCmvgcI9lbTU6vEN9GDkvFQyx8bg2c1kp12no23dZ7SsWoWtrg6P1FSi3/lXAubMQX1b49RsN7P96nZyS3K52n6VSJ9IfjLkJ8xPm4+/h3+no+PnnY6OR8HDH0Z+H4Z/F51XFJ/mVbEi9yA1bUZSwn35/fz+zMuOxUvz4CYmPej0WIFXqVSzgUYhRIFKpZrwJeteA14DSEhI6KntSCRfG7ZWE7qjNejz6hEWB55pQfg/FYdnryDXG6f1tRRs20TJoX3YLGZSBg1l6JwFX6lxWnKkhnP7qtC3WwiN9WXyS31JGxKJWzeTndaGBlpXr6b107U4tFp8hg4l6te/+rsovDZTG2svrmVN2RqaTc30CenD78f+nulJ09GoNWA1QcFKxdGx6SIExMG0d2DQizRaPMi5rXE6LCmEf56byeQ+Ed06TUqcRyWE6Jkbq1S/BxYBNsAL5Qx+gxDihe6uGTJkiMjPz++R/UgkPY2lRof2SOnizmMAACAASURBVDXGczcAFT4Dw/EbG4tHjJ/L96y/fJG8Leu5ePo4bm5u9B07iSGznW+c6lrNnNtfRfGRGqwmO3F9gsmemkB8Rki3bwymixdpWZFD+/btYLcrE6dLl+Ldv3+XddXaalaVrmLT5U0YbUbGxI5hceZihkcN73R0bFYcHfOWgf4GRA2AUW9C5hNcbDKx7LBsnN4PVCpVgRBiyJ1+1mNP8EKInwM/79zABOAnX1bcJZKHESEE5kudHuyXOz3YR8UqHuxBni7f83pRAXlb1lNVeh5PH1+GPf4k2TPmOJ1x+rcTp70GR5A9LZHwhDuflgohMJw6RfPyFeiPHEHl7U3w008TsvhFPOK7vpmUNJWQU5LDnoo9qFVqHkt+jMWZi0kLTut88SvK03rRGrAZIW1ap6PjGE5ca2HZqkIOdDZOnx2WwMuycdpjSB28ROICwi4wnr+B9tAXHuwe9+zBbrdZKT92mPytG2iqqsAvNIzxi15mwOTpeHjfXT4phKD2UhuFezqtejsnTrMmxxMQ1k3j1Gaj4/NdtKxYgam0FLfQUMJ/+CZBzzyDe3Bwl3sfqTnCypKV5NXn4afx46XMl3i+7/NE+EQoi6rz4dh/Q9lWxdFxwFMw8g1soensKK5n2V+Oc76mnVBfD348NZ1FsnHa4/TYEY0ryCMayYOOw2LHkN+A9kg19lYlvNp/XNw9ebBbjAbO7dtFwY7N6JqbCItPZOjcBfQeNRY397v7znyRcVq4p5LG6x14+2voPyGO/uPj8PK78/UOvZ629etpWZmrWPUmJxOy5CXFqtfz1icPq93K9mtK4/Ry22UifSJZlLGIBWkL8PPwUxqnl3bBsT9D5XHwDIShS5UoPI8wPs2rYvnRa0rjNMyXV8amMH+QbJzeT76RIxqJ5FHCrreiP1GL7ngtDoMNjwR/gman4tXXdQ92fVsrZ3Zu4ezuHZgNeuIz+jPt1ddJyhrsVOPUZrFTfqKOwr1VdNwwKhmnz/Wmz4gvseptbKT1w49o/eQTHB0deA8eTOQv/gm/CRO6NE61Fi3rLq7jo9KPaDQ2khacxu/G/I4ZyTOUxqnNDGdWK4qYpgtK43T675TGqVnDymPX+fDkWTpMNoYmBcvG6TeELPASyZdga7lNEWPttBIYH4dn0p3dE52hpbaa/G0bKT20D7vdTvqwUQyZO5/oXr2dut6ks3L+UDXnD1Zj1FqJSApg1LxUkrPCu884vXJFmTjdshVhs+E/dSqhS5fgnZXVZV29vp6Pyj5i3cV1ysRp9HB+M/o3jIoZpbzpGNsgf4Xi6qirh8j+MH8ZZM7jUpOJ97dcZXNRLVaHgxmZUbw6LoVBsnH6jSELvERyByy1OrSHb1PEZIXjPz4OTaTrzcDai+XkbVmvWAm4u9Nv4lQGP/YEwdGxd78Y6GgyUrS3irLjtdgsDpL6h5I9LYHobuSXQggMeXm0rMhBd/AgKk9PAp9cQOhLL+GRmNhl7cXWi+SW5LLj6g4EgmmJ03ip30u3TZxWwcm/3po4TZkI8/6KSJ7AyWutvH9b4/SZYfGycfqA4FSBV6lU84F/AyIAVec/Qggh7QckjwxCCMxX29EeqsZ8sfX+KGIcDq4W5pG3ZQM15SV4+fp95fDqxooOCvdUcqWgEZVaRfqwSLKmJhDajfxS2Gxo9+6lefkKTOfP4xYcTNjrrysZpyG3VDhCCPLq81hRsuKmVe/TfZ5mUcYiYv0633Tqzyvn68Xrla/7LVCseiP63bFx+sKIREJk4/SBwdkn+D8Ac4QQZT25GYnkm+BmuMahaqzVOtR+GgKmJyqKGBdzO+02K2VHD5G/dQPN1ZX4h4UzcfGr9Js0DQ+vuycxCSGoKm3hzO5Kai604uHlRtbUBAZMjMcv+M5vNg6DgbYNG2lZuRJrdTWaxIRuM073VuwlpySH0uZSQrxCeCP7DZ7u/TSBnoGdVr37lcJ+9QB4+MHw78KI76H3jlYap19MnIb58rt5/WXj9AHF2QLfIIu75FFDWB3ozzSgO1KDrcmohGvM64XvoAhULhYrs0HPub2fc2bHZnStLYQnJDHr9X8gfeRY3Nzv/r/bTQ/23ZU01+jwDfRg1PxeZIyNwbMb+aWtuZnWjz6i9aM12Nvb8c7KIuIf38Z/0iRUt9kX/G3GaVJAEr8e+WvmpM5RrHrtViVU4/iflSd3v0iY/GsYsoRGqzcrj1/nw86J06FJwfx6jmLVKxunDy7OFvh8lUr1KbAJMH/xTSHEhh7ZlUTSgziMNnQn69Adq8Ghs6KJ8yPk+T54Z4a5rIjRtTQripg9O7EYDST0G8j07/2IxAHZTodXlx6t5ew+xYM9JMaXyYv7kja0eysBS2UlzTk5tG/YiLBY8Js8idClS/EZNKjLumZj882M03ZzO1nhWfx06E+ZGD9Rseo1a+H0B8oZe3sVhPW+adV76Qur3kLZOH0YcbbABwAGYNpt3xOALPCShwZbu1lRxJyqR1jseKYH4z8uDs/UQJc9Ypqrq8jftoHSwwcQDgfpI0YzdO4CIlN6OXW9vt3MuQPVlByuwWxQwqvHP9ebxH6h3e7JeL6Y5uXL0e7ejcrNjcAnHidkyVI8U5K7rLvefp3c0ly2XN6C1WFlYvxElvRb8jcZp+8qqhhTOySMgln/jkibyslrbSz78Bz7yxvx0qh5eqjSOE0Kk43ThwmnCrwQYklPb0Qi6SmsDXq0h2swFDWCEHgPCFdSk+7BI6amvJS8reu5kn8Kdw9PBkyZzuDH5hEUGeXU9a31eor2VFJ+qh5hF6Rkh5M9NZHI5C/xYD96jOYPPsBw6hRqf39CX36Z4EUvoImI6LK2qLGIlSUr2V+5H41aw9xec3kx40WSAzvfAG5cUI5hzq1VjmX6zoHRP8QWPYidxfUs+78TnKtWGqdvTUln0UjZOH1YcVZFEwf8DzAa5cn9KPBDIUR1D+5NIrknzNcVRYyprAWVRo3vsCj8x8bhHuJaILNwOLhScJq8LeuVcA3/AEY++SxZ02fjE+CcLr7usmIlcO1sE24aNRmjYhg4JZ6gbpKchNVKx+ef0/zBcswXLuAeGUnE228T9NRC3PxuvUE5hIODVQdZWbKSwsZCAjwCeKX/KzzX9znCvMOUxun1Y0phv/g5uHtB9iIY+QMM/olK4/QjJeNUNk4fHZw9oskB1gALO79+ofN7U3tiUxKJq9xMTTpUjaWiA7WPO/6TE5TUpG6Cou+GzWql9PB+8rdtpLW2moDwSCYt+Q79JjgZruEQXDvXROHuSuqvtuPlq2HIY0kMmBCHt/+dn4wdBgNtn31G88qV2Grr8OiVSvTvfkfg7Me6hFeb7Wa2XtlKbkku1zuuE+Mbw8+G/Yx5vebho/EBh10J1Tj+Z6gpAO8QGP8zGPYqTcKfVcevs+rkftoMVoYkBvOr2bJx+ijhlBeNSqUqEkJk3e1794r0opG4irA5MBR1piY1KqlJ/mNj8Rkahbqbsf27YdLrOLtnJ4U7t6BvayUiKZWhc+eTPmKMU+EaNqudi6eUcI22BgMBYV4MnJxA31HRaDzvfP3fKWKGDCb05ZfxGz++i5VAu7mdTy98etODvW9IX5b0W8LUxKm4q93BYoCijxRXx9ZrEJwEI1+HrOe53iH44OhV1uVXY7E7mNo3ku+MT2FwonNOlZIHi/vhRdOkUqleAD7u/PpZoPl+bE4iuRccZhv6U/XojtZg77Cgibr31CRdSzMFOzZzds9OrCYjiQOymfmDfyCh/0DnwzUO13B2XxWGDgvhCf5MeyWT1Oxw1N3s6Y6KmJdfxic7u8u6Gl0Nq0tXs+HSBow2I6NjRrOk3xKGRQ27zYN9GZx+HwzNEDsYpvwz9J3D2Rot768rZ2dxHe5qNfMHxfLK2BR6Rbjei5A82Dhb4JcC/wv8J8oZ/PHO70kk3wh2nQXdsVp0J+oQJhueKYEEL0jDMz3YZUVMS201eVs2UHZkPw67g/SRYxRFTHKqU9fr282c3VdFyeEaLCY78RkhTJmWQFzv7vdkPF9M84rlaHfdrohZgmdKSpd1Zc1l5JTksPv6blSomJk8k8WZi+kd0ulf03JVeVov/EjxYE+fAaPeRCSM5NClJt77II8TV5vx93LnO+NTWTIqiYgA13oRkocHZ1U0lcDcHt6LRHJXbK0mtIerMeQ3IGwOvDNC8Z8Qj0e867G/9ZcvcnrLZ1w6fQJ3dw39Jk1nyGznFTFtDQYK91RSfrIOYRekDo5g0F3CNfRHj9G8fDmGkydR+/kR+vJSghct6qKIEUJwqv4UK86v4ETdiZvh1S9kvECUb+feqgvgeKcHu9r9pge7NTSdrWdreX/TUcrrtUQFePFPs/ryzLB4/L1c60VIHj6+tMCrVKq3hRB/UKlU/4Py5N4FIcSbPbYzieQ2rA16tAerMZxtBFT4ZEco5l/dqE/uhhCCyvNnOb15HZXFZ/H08WX4EwvJnjHHaY+YhusdFO6q4ErRDdzcFUVM1tR4AsO/TBGzi+blyzGXl+MeEUHET39K0NNPdVHE2B129lbuZUXxCkqbSwn1CuWHg37IU72fIsAjQFHEXNythGtUHFU82Ef/UAmv9gjjk9OVrDh6gNp2E+mRfvzHwoHMGRiDh4t+9ZKHl7s9wX9hTyA7n5JvBHNlB9qD1ZhKm1Fp1PiNjMFvbJzL5l8Oh51Lp05wevM6Gq9dwTc4hHHPL2HAlJl4+jiXmlRV1sKZXYpHjKePO4OnJzJgUjw+AV+miFmveMTU1uKR2r0iZvPlzawsWUmVtooE/wR+NfJXzE2de8tK4OynSmFvLIGAWCW8evBiblg8WHn8GqtPKB7sw5NDeGdefyb0Dnf5yEry8POlBV4IsbXzjwYhxLrbf6ZSqRbe4RKJ5J65mXN6sArz1XZU3vdL6riPvC3raauvIzg6hqmvvUHGuEm4a5xITbI7uFJ4gzO7Kmiq6vSIWdCLzLExeHh14xHT0kLrhx/eUsQMHkzkL36B34SuipgOSwefln/Kh2Uf0mJqoV9oP96a8BaT4ifhpnYDix7yVihn7O1VEN4Hnvgr9HuSq60Wlm2/xvoz1VjtipXAa+NkeLVEwdkm68+BdU58TyJxGeEQGIs7XR1rdKgDPAh8LBnfYdGou5EV3g2zwcDZPTs4s3ML+tYWIlN6Meetn9Fr2EjUaiekjl+kJu2ppKPJRFCkDxMX9aH3sCjcNN0rYlpWrqRt/QaE2YzflMmELn0Zn0FdFTEN+gZWl65m3cV1GGwGRseMZmm/pQyNGnpLEXP6PUURY2yFhJEw698hbRqF1e289/F5dpXWo3FT8+TgOF4dm0KytBKQ3MbdzuBnArOAWJVK9efbfhQA2HpyY5JvD8LmwHCmU8PeZMQ9zJvgBWn3lHP6t3F4Cf2zmPmDH5PQzzmpo0lvpfhwDef2V2HUWolMDmD0k2kkD+jekMxYXELz8g/uqoi52naVnJIctl3dhhCC6UnTWdJvCX1C+igLWq8rT+tnViuKmN6zYPSPcMQN4+DFRt5ddorT11oI8HLnBxN6sXhUEuH+rh1ZSR5t7vYEX4ty/j4XKLjt+1rgrZ7alOTbgcNsR3+6Du2RGhwdFjSx9+7q2NZQT/7WDRQf3IPdZiN92CiGPv4kUalpTl2vazVzdl8lJUdqsZrtJGSGMmh6AjFp3acm6Y8dp3n5BxhO3KaIeWERmsi/94hZXrycg1UH8XLzYmH6Ql7MeJE4/zhlQf15OPpfULIRVGoY8DSMfhNLcBpbztby/vrDXGzQERPoxS9nZ/D00Hj8PGUom6R77nYGfxY4q1Kp1qCkOPVBUdNcEEJYvob9SR5B7HoruuNKgLUwKhp2/yfT8eymiDpD4/WrnN78GRdPHEXtpiZj3CSGzFlASIxzcXit9XoKd1dy4VQ9QkCvwREMmp5AWFw3Uke7He3u3TQtW4a5tKxbRYxDODhSfYQVxSs403iGQM9Avjvwuzzb51lCvEIURcy1w0phv7JPCdcY8T0Y8X20nhF8crqK5UcPUN9hok+UP//59EBmD4hB4+IQl+TbhbNv/1OB94ArKIU+WaVSfUcIsbPHdiZ55LC1mdEdqUZ/ujPAOiMU/wlxeCa4lvwohKC6rJjTmz/jelEBGi9vBs9+gsGzHscvJNSpe9RfbefMrgqunWvC3V1N5thYsqbEExB259Qlh9lM+6bNNC9fjrWyEo/kZKL/9bcEzJ2L+jZFjNVhZee1neQU53C57TLRvtH849B/ZH7a/K4eMcf+G2rPgG8ETP4VDFlKo9WbnOPX+fBkMVqTjZEpofzbkwMYlxYmFTGSr4SzBf5PwEQhxGUAlUqVCmwHZIGX3BVrowHtoWoMhY0A9xxgLRwOLhecIm/zZ9RduoB3QCBjnnmRgVNn4eV397F7IQSVJS2c2VVB7aU2PH3cGTIziQETuzf/sut0tH3yCc25udhvNOHVvz8Rf/5v/CdP/rvUpPWX1rOqdBX1+np6BfXid2N+x4zkGWjUGrCaID8Hjv8PtFyB4GSY/Z8w8DmutNl4f/tVNhbWYHM4mNk/mu+MS2FAXJBLf08SibMFvvGL4t7JVaCxB/YjeYSwVGnRHqzCWNqMyl2N34ho/MbG4h7s2oj8FzmneZs/o6W2msCISCa//H0yJ0xG43H3JqPD7uByQSNndilxeH7Bnox+shcZY75E6tjURMuq1bR+/DEOrRbfUaMI/eMf8Rk+vMvT9BepSZ+Uf0KHpYPBkYP55YhfMjZ2rLLO2Ab5y+Hku6BvhOgsWLgS+s6loKqD9z4uZk9ZAx5uSrjGK2OTSQyVihjJveFsgS9RqVQ7gLUoZ/ALgTyVSjUfZHSf5BZCCMxX2tAerMZ8uQ2Vlzv+E+MVDbufa6ERFpOR8/t2k799I7rmJiXn9M2f0ttJV0erxU7ZsTqK9laibTYRHO1EHF5VFc0rVtC+fgPCasV/+nRCX3kF736ZXdZVaavILcll0+VNWOwWJsZPZGn/pQwMH6gs6KiFk/8H+SvBooXUSTD6R4iksRy4eIO/vn+KvOutBPloeGNSGotHJhLqJxUxkvuDswXeC2gAxnd+fQMIAeYgo/sk3PJh7zhQhbVKi9rfg8BZyfgOi0LdzdPx3TDpdBTu2sqZnVsxaTuIy+jHtFdfJylrsHOujgYr5w/WcO6AInWMSglk7FNpJPXvXqVjunCB5mUf0LFjR6fU8QlCli7BM7lrHF5Zcxk5xTnsqtiFWqVmbupcFmcuJiWwUxJ546LiEXP2UxB2yJynpCZF9GfbuTre/bPiERMb5M2v5yiKGB8PqYiR3F9kZJ/knhAOgfH8DbQHqrDWG3AL8SJoXi98B0Wi6mYQ6G7o21op2L6Jot07sJqMpAwayrAnniK2d1+nrjd0WDi7r5Lzh2qwmuwk9gtl0PREYtLufJYthMBYUEDTsmXoDx1G7eNDyEsvEbJ4cRep4xfmXznFORyvPY6vxpfFGYt5IeMFInw611WdVhQxF7YrqUmDX4KRP8Dol8Da/Crezz1ITZuR9Eg//vSU4hEjFTGSnsLZyL5k4A0g6fZrhBDdOkyqVCov4DDg2XnNZ0KIX9/LZiUPDsLmwFDYiPZQ53BShDfBT6XjMzAClZtrSo/2xgbytm6g+MBuHDY7vUeNZdjjTxKemHz3i4GOJiOFeyopO1aHw+5QXB2nJxLejdOkcDjQHTxE87JlGAsLcQsJIfxHPyT42WdxC7wVwWd32NlXuY/lxcu/xPxrl1LYK4+DVxCMexuGf4c2VQCrT1SQc3w/LXoLgxOD+Ze5mUzqEyFTkyQ9jrOfCTcBy4GtgMPJa8zAJCGETqVSaYCjKpVqpxDipAv7lDwgCKsdfV4D2kPV2NvNaGJ8CXm+L96ZoS4PJzVXV3F68zrKjh5EpVKTOWEyQ+cuIDgqxrnra3UU7qrkYl4DKhX0GRFF9rREgiK/xNVxxw6aP/gA86XLaGJiiPzlLwiaPx+19y15pNVuZdvVbawoXsH1jut3Nv8q+liJw2sshYA4mP57GPQidSY3lh+4xprT+Rgsdib1ieB7E1IZmiRTkyRfH84WeJMQ4s93X3YLoWQB6jq/1HT+c/d8QMkDicNsQ3+yHu2Rahw6Kx6JAQTN74XXPQRs1F+5xOlN67iUdwJ3Dw+yZ8xhyOx5+IeGOXV9w7UOCj6/zrWzTbh7qBkwMY6sKfH4daPScRiNtK3fQMuKFVhra/FMSyPmj38gYMYMVLcZjn0hdcwtyaXB0EDfkL78+/h/Z0rClK7mX8f/FzqqISID5r0H/RZwudnM+1uvsLGwBoeAOQOi+c74VPpGu6b1l0juBWczWZ8D0oDdKE/mAAghztzlOjcUi4NewF+EEP94hzWvAa8BJCQkDK6oqPgq+5f0MA6DMnWqPdY5dZoWRMDEeDySA10q7F8MJ53auJaKc4V4+viSPWM22TPn4hMQ6Nz1F1op2Flx0663/8Q4RcPejUrH3t5O65o1tKxajb21Fe9Bgwh97VUl5/S236Hd3M6a8jWsKVtDm7mNIZFDeKX/K4yKGdUpdWyF08vg5F/B2KKYf415C9KmUVTdzl8PXmZ3qSJ1fGZoPK+MTSE+xDW/eonEWb4sk9XZAv97YBHKJOsXRzRCCDHJyQ0EARuBN4QQxd2tk6HbDw52rQXt0Rr0J+oQFjtefUMImJTgcnKSEIJrhfmc2riW2otl+AQGMfixJxg4dZZzPuwOwbVzTRTsvE5jhRafQA+yJieQOa57Dbu1oYGWlbm0ffopDoMBv/HjCX3tVXwGD+6yrtHQyKqSVTddHSfETeDl/i+TFdGZKd9RByf/ogwoWXSQNh3G/hgRP5wjl5r468ErnLjaTICXO4tHJbF4VBJhUuoo+Zq4H6Hb84AUV/1nhBBtKpXqIDAD6LbAS755bG0mtIeq0ec1gN2B94BwAibGo4lybejG4bBz8eQxTm9ax42Ka/iHhTNp6XfpN3GqU8NJdruDy3kNFOyqpLVOT0CYF+Of602fkVG4a+6sgTdfu0bz8uW0b94CDgcBs2YR+srLePXu3WVdRUcFOcU5bLmyBYdwMCN5Bkv7LSU9OF1Z0HxFsRI4+zE4bJA5H8a8hT0ikx3n63h301FKajuIDPDkn2b15dnhCdL8S/JA4ex/jWeBIL7C9KpKpQoHrJ3F3RuYAvzbV9+i5OvA2mREe7AKw5lOO4FBEfhPiEfTjSfL3bDbrJQePkDels9oraslOCaOGd9/iz6jx+Pmfvf/7GwWO2XH6yjcXYm2xURIjC9TX86g16AI1N3ICk3l5TS99x7az3eh8vAgeOFCQpYuwSMursu68pZyPjj/AXsq9uCucmd+2nwWZy4m3j9eWVB3Do7+J5RuArUGsl+AUW9i8k9g/Zlq3l99kIpmAylhvvxhwQAez47B0901v3qJpCdxtsBHAuUqlSqPrmfwXxbEHQ3kdp7Dq4G1QohtLu9U0iNY6/V0HKjCeO4GuKnxHR6F//g43INcsxOwmk2c37+bvK0b0DU3EZGcypwf/5xeQ0c4FbBhNtooPlTN2X1fDCcFMPaZdJL6da/SMZwppPm999AdOoTa15fQV14hZPGLuIfdatYKIShoKOCD4g84VnMMX40vL2W+xKKMRYR5d66rOA5H/gSX94CHP4x6A0Z8nw5NKB+erGDF0QM06cwMjAvk5y8MYmpGFG5S6ih5gHG2wH9l/boQ4hyQfdeFkm8ES5WWjgNVStaphxt+4+LwHxOLWzdmW3fDpNdxdvcOCrZvwqjtIK5vP6a/9gaJAwc51Yw1dFg4t7+K84dqsBhtxGeEMHhG4pf7sB8/TvO772HIy8MtKEjRsD/3HG4BAV3WHa4+zAfnP6DoRhEhXiG8mf0mT/d5+paG/cLnyhN71UnwCYVJv4Chr9Jo9WLF0et8dPIsWrONsWlhfG98FiNTQ6Wro+ShwNlJ1kM9vRHJ14O5ogPt/kpMF1pRebsTMEXJOlX7uJZ1atR2ULB9M4Wfb8ViNJCcPYRhTywkrk/m3S8GtC0mZTjpaC02m4PUrHAGzUgkIvHOskLhcKDbv5+m997HdP487pGRRP78ZwQtXIj6tmatzWFj1/VdLC9ezqXWS0T7RvPzYT9nXto8vN29wW6Dc+uUwt5YAoHxMPMPkL2I6x2C93ZeZX1B9U1Xx++NT6Vf7N1VPhLJg4Szk6xabmnYPVA07XohhBT3PiSYr7XTsa8S8+U21L7uBMxIwm9kNGoXm4L6tlbyt23k7O4dWM0m0oaPYvi8p4lMTnXq+rZGA2d2VXDhRD0A6cMjGTQ9keBumrnCZqNjxw6a3n8fy+UraOLjifrNvxD4xBNdfNjNdjObL28mpziHal01KYEpvDPmHWYmz7xl15u3XBlOar0OYb3hiXeh/5OUNhj5v8/K2XG+Dne1mieHxPHa2BSSZM6p5CHF2Sf4Lto4lUr1BDCsR3YkuW8IITBfbUe7rxLz1XbUfhrFAGxENGoP15qC2pYm8ras5/zeXdhtNnqPGsvweU8RFp/o1PUttXoKPr/OpbwG1G5qMsfG8P/bu++4Ksv3geOfm733EmSIGxVQcI809yptmJaVZWpl3/au76/dt3KU5ij31izNNHPnStzgAlyALJUpe3Pu3x/PMaVAiAQE7/frxQt4zvOcc/Gcw8XDfe77ugIHeGHjeIsGGz9vIG3BAooTErTFSVOnYjNoIOKmN2tzinJYe34tyyOWk5qfSjundrze8XX6ePbBQBhAQRYcmwUH52jlej2CYMBn0HIIx+MzmL38BL+fTcbK1IgJvXwZ370JLjbVex9CUe4U1bp8k1JuEEK8fbuDUW4PKSWFFzPI2hVH0aUsrbLjMF+tsmM1E3tmchJHN/7Emd070Ol0+PW8l04jHq5yS7yUuGyOb7lE1IkUjIwNCOjrSWB/Lyxty58qqcvN5doPa0lfvJiSlBTM/P1x9DZr+wAAIABJREFUffcdrHr3RhjcmEWTXpDOiogVrDm3huyibLo06sIXPb+gk1snbZw8JwUOz4UjC6AwE3x7Q48FSJ+e7L+Yxuz5hzkck469hTGv9W/BE119sK3mcJWi3GmqOkTzwE3fGgDBqLIDdxwpJQXnr5G9K46iuGwMbU2wu78plsFu1a7seO1KIoc3/Ejk/t2AoG2ffnS6/yFsXdyqdPzV6EyOb7nEpdNpmJgZEjTIm4C+nrdcdZq+YgXXli2nNDMTiy5dcP/qSyy6dCnzxmZSbhJLwpfw0/mfKCwtpK9XX8a3G09bp7baDhlxWtek0OVQUgCth0OPV9A1as/2iKvMnh3C6cRM3Gy0BtZjOqlyvUrDU9VX9PCbvi4BLgH33/ZolGqRUlIQmU7W73EUJ+RgaGeqlewNckVU0NCiMmkJcRxa/wPnQvZjaGREQP8hdLzvwSrViZFScvlCBsd+u0TC2WuYWhrR+b4mtOvdGNMKro5LUlJIX7qUa6tWa6tO+/TBadJEzAMDy+yXkJ3AojOL2HBxAzqpY6jvUMa3HY+vnb4Oe+oFbarj6bXa9/6joftLFDs0Y+OJy8z9YR8Xk3PwdrTgiwfaMbKDh5rDrjRYqh58PSZ1koKINC2xX87F0MEM+webY9HepdqJPflSNIfX/8D5IyEYm5gSNGwEwcNGYmlnX3k8UhIfkc6xLZe4cjETcxsTuj3Q7NblBBITSVu4kIyf1iFLSrAZPBjHiRP+tuo0OjOahacXsjl6MwbCgJHNRvJU26dobK1fxHTlFOyfBhG/aHXYO06Abi9QYNGIH4/F891erQ57KzdrZo5pz5C2bhipOuxKA1fVIZqvgE+BfGArEAC8LKVcUYOxKRWQOkn+mVSyf4+j+GoeRo5m2D/cAotAZ0Q1k9aVi+c4tP4Hoo8fwcTcnM4jHqbDkPurVgDsL3VirOxN6flIC/y6N8KogjH/wugY0ubNI/PXX0EI7Ebcj+P48Zj4+JTZ72z6Weafms+O2B2YGpryaOtHedLvSVwtXbUd4o/C/qlwfqu2OKnHK9DlebKN7Fh5OI4F+7XFSe297Pj4fq0Ou5rDrtwtqjpEM0BK+aYQYiSQgNaTdTegEnwtkjpJ/qkUsn6PpyQ5DyNncxweaYm5v3O1m2wknA3n0Lo1xJ4Kw8zSim4PP0b7QcMxs7Kq9FidThIVmszxLZdIS9TqxPQZ24qWXdwq7HVaeOECqXO/I2vLFoSpKfaPjsHxqacwbtSozH4nU04y/9R89ibsxcrYimfaPcNYv7E4mDloi5Ni9sG+Kdpnc3vo8x50mkC6zpIlB2JYEhJKVoG2OOn53u3p4uugErty16lqgr8+cDoEWC2lTFe/LLVHa4uXStauWEqS8zFytcBhTCvMb9Fb9Jb3JyXx4ac5tG418RGnMbexpeej4wgcMAQT88orO5aW6rhwJInjW2PJSMrD3s2Cfk/50Tz4FnViIiNJnfsd2du3Y2BhgeMz43EYNw4jR8cycR29epR5p+dx+MphbE1teSHwBca0HlO2c9K+qZBwBKxcYcCnEPQUVwuMmL8rmlWH48gvLmVgG1ee792MAM/y2/Qpyt2gqgl+kxDiLNoQzfP6QmIFNReWAvrEHp5K1s44SpLyMHKxwOHRVpi3/XeJ/eBPq0iIPIOlvQO9n3gG/76DMDarfM53abGOs4euELotlqzUAhwbWzFwQluatneuMJ7806dJnTOXnN27MbCywvG5Z3F44gmM7G+M6Usp2Z+4n/mn5nMi5QRO5k68Hvw6D7d4GAtjC9DpIHyDNsZ+9ZS26nTIVG3VaWYp32+OYt3xREql5P4Ad57t3ZQWrtUra6woDUmV6sEDCCHsgSwpZakQwgKwkVJevZ3BqHrwGiklBeFpZO2Mo/hqLkbO5tj088K8XcWJtLL7+2ti73T/w/j3HYiRSeW1Z0qKS4n44zKh2+LIzSjEtYkNwYN98G5XcU2WvNBQUufMJfePPzCwtcXhySdwGDu2TJ0YndSxK24X80/NJzI9kkaWjXi67dOMbD5S3xKvBM78pM2KST0HDk2h52vgP4qzKfnM3RPFppOXMTI0YFRwYyb1aqoabCh3ndtRDx6gNeAjhLj5mGX/KjKlDCklBRHpZO2MpfhKLkZO+jH2gNuX2PuMm1T1xF5USvgflwnbFktuZhGNmtnS98nWNG5Vfps+KSV5R46SOmcOeYcPa02sX3sV+zGPYmh1Y7l/ia6ELTFbWHh6IVGZUXjbePNxt48Z5jsMY0NjKCmEY4u0JtYZseDaFh5aBH4jOJGYzawVJ9kZmYSliSETevoyvodadaoo5anqLJrlQFPgBFCq3yxRCf62kFJScDZdu2JPzMHwz1kxLtV68/S2JPb9lwndFkteVhEeLezo/3QbPFqWP1VSSknugRBS584l//hxDJ2dcHn7LexHjSpTAKyotIiNURtZeHohCTkJNLNrxle9vmKA94AbvU6PzNcWKGVf1soJDP4SWgziaOw1Zi4+xv4LqdhZGPNKvxY82c0bO4vqVb9UlLtBVa/ggwE/WdXxHKVKrq88zdoRqy1QcjDD/qEW2jz2OkjsxUWlhO9LJGx7nJbYW9ox4Jk2eLSoOLHn7NlD6tzvKDh1CiM3N1zffx+7hx7E4KYx/cLSQn46/xOLzywmKS+JNo5teKPjG/T27K2vE5MJRxfAwdmQlwY+PWHEHGSTewiJTmfmvEMcjknHycqEtwe3YmwXb9U5SVGqoKq/JWcAN+BKDcZy15BSUnghg6wdsRTFZ2NoZ6otUOrgUq157H9N7Fb2Dtz71CTa3VvFxF5Yypl9iYTtiCM/qwiPlvYMnNAG9+YVJHadjuydO0n97jsKIyIx9vDA7aOPsB1ZtrJjfkn+n4k9JT+FDi4d+Ljbx3R176oN8eSlaw2sD3+v1Ylp1h96vY707Mye8yl8+91BQuMycLE25b/D/Hi0kxfm1ayloyh3o6omeCcgQghxhKp3dFLKURCVQdb2WIpiszC0/XclBW5LYt+bSNiOWPKzi2ncyp6OE9vi3qz8qYWytJTsbdtInfsdhRcuYOLtTaPPP8d2+DCE8Y0SBHnFefx4/kcWn1lMWkEaHd068mWvL+no1lHbITcNDs6CI/O0Jtath0PP19C5BbIjMolZsw5wOjETDztzPhnRloeDGmNWQf9VRVEqVtUE/2FNBnE3KIrPJnP7JQovZGBgoy8C1tGtThJ7UUEJZ/YmcmJnHPnZxXi2tqfj0CY0ukViz/ptC6lz51IUHY1J06a4T5mCzeBBZUr25hXnsebcGpaGLyW9IJ3OjToz1X8qwW76N/hzUuDgt1plx+I8aDMSer1BqXNrtpy5wqy1+zl7NRtvRwu+etCfEe09MKlmyQVFUf5BRychhCugvwTjiJSyyg2472bFV3PJ3B5LQUQaBhZG2A5tglWXRohqXpEmnA0n5IcVxEecrlZiP70ngRM74ynIKcbLz4GOw5rg5lt+OYI/E/ucORTFxGDaogUe33yN9YABZUr25hbnsvrsapaGLyWjMINu7t14NuBZ2rvoOzZmJ2kNNo4uhNJCaPsQ9HqdEofmbDx5mdkr9hKVkktTZ0u+fiSA4f7uqk6MotwGVZ1FMwqYAuwBBPCtEOINKeVPNRhbvVaSlk/WzjjyTiQjTAy11ng9PDCooOhWZa5GXeDA2hVcOnEcC1s7+oybiH/fQf8sse+IpyC3GK82DnQcWkli37JVS+zR0Zg2b47HjBlY9+9XJrFnF2WzKnIVyyOXk1mYSU+PnkwKmESAc4C2Q9YVOPANHF8CpcXgPwp6vkaRXVN+DktgzpK9xKbl0crNmtmPdmBQW9XEWlFup6pmm/eAjtev2vUrWXcCKsH/RWlmIVm/x5F7NAlhKLRm1r0aY2hZvSYSKbExHFi7kqhjhzCztqHX2KcJHDAEY9PK530XF5Zyek8CYdvjKMgtxrutI8FDfXBrcovEvnUrqXPmUhQVpSX2b77BekD/Mok9qyiLlRErWR65nOyibHo37s2kgEk3arFnJmhz2EOXgSyFgNHQ41UKbHzKVHb0b2zLvMeD6NfaFQOV2BXltqtqgjf4y5BMGlrjD0WvNKeI7D0J5By6DBIsO7th08cLQ5vqzdNOS4zn4I+rOHdwP6YWlnQfNZYOQ+6rUq2YkuJSwvdd5vi2WPKzivBq40CnYb64Nqm4kXX21q2kzJlD0cUoTJs3K3coJrMwk+URy1kZuZKc4hzu9byXSQGT8HP003a4Fqs1sQ7T16ALfBR6vkq+pScrD8cyb99ukrMLCfK257ORbbmnhbMqAKYoNaiqCX6rEGIbsFr//SPAbzUTUv2iKyghe38iOfsTkcWlWHRwxaavF0YO1VtZmXH1CgfXrSZy/x6MTE3p8sAjBA0dWaXqjqUlOiIPXObYllhyMwrxaGlP54ltK37zVKcje9s2UmbPpuhiFCbNmuLx9XSsBw4sk9gzCjJYFrGMVWdXkVucS3/v/kz0n0grh1baDukxWp2Yk6tBGECHJ6DHy+RZuLPiUCzz9v1Oak4RXX0d+eaRQLo2rbjEgaIot88tE7wQohngKqV8Q9+2rwfaGPxBYGUtxHfHkiU6cg5eJnt3PLq8EszbOWHT3xtjl+rVQslKTebQ+h8I37MTAwNDgoaNoON9D1apHntpqY5zh65ybPMlstMLaNTUln5P+dG4opWnOh3Z27eTOns2hRcuYtK0KR7Tp2E9aNDfEvuS8CWsPrua/JJ8BvgMYKL/RFrYt9B2SIvSJ/Y1YGAEweOh+0vkmbuy4lAs3+/dTVpuET2bO/Fi3+Z09HGo1rlRFKV6KruC/wZ4F0BKuR5YDyCECNbfNrziQxsmqZPknUwha9slSjMKMW1uh+1AH0waV696Yc61dI5s+JFTO7cAENB/CJ1GPIyVfeXJUKeTXDhylSObL5GVko+LtzW9H2uJp1/5tc+1xL5Dn9gvaNMdp03FZtAghOGNWT2ZhZksi1jGysiV5BXnMchnEBP9J9LMvpm2Q+oFrWTv6bVgaAKdJ2mJ3dTpb4n95X7NCfJWiV1R6kJlCd5HSnnqrxullMeEED41EtEdrOD8NTK3xFB8JRdjd0vsH2yOWQWrPSu9r9wcjm5cR+hvGyktKaZtn/50eeARbJxcKj1W6iQXQ5M5+msM167m4eRpxZDn/fGpoLqjlJLsHTtInTWbwvPnMfH1xX3qVG0e+02JPbsomxWRK1gevpzs4mwGeA/guYDnbiT2tCjY+5WW2I3MoOtk6Pof8kwdWX4wlnn7TqvErih3kMoS/K0Gks1vZyB3sqLEHDK3xFB4MQNDBzMcRuu7KFVj5kdxUSFhWzZx9JefKMjNoVX3e+g26jHs3dwrPVZKScyJVI78Gk1aYi4O7pYMmtQW3wqqTUopyd23j5QZMymIiMCkSRNtgdKQwWUSe25xLqsiV7EkfAlZRVn09erLcwHP0dJB3xc1PUbrnnRyjXbF3vUF6PYieSb2+sR+SiV2RbkDVZbgjwohJkgp59+8UQgxHjh+qwOFEJ5o1SbdAB0wT0o5498EW9tK0gvI3HaJ/JMp2iKlYb7aIqVqrK7UlZZyZs8ODv60mpz0NJoEBtFjzJO4+PhWeqyUkrjwdA5vjCYlLhs7Vwv6j/ejWVDF0wtzDx0mZcYM8sPCMG7cmEZf/A/b4cPLJPbrK08Xn1lMRmEGvRv35rnA527MismI0xL7iVXaGHvnZ/VDMdev2E+QlltErxbOvNS3OUHe1ftvRlGUmlFZgn8Z+FkI8Rg3EnowYAKMrOTYEuA1KWWoEMIaOC6E2CGljPhXEdeC0txisn+PI+fQFYSBwLqPJ9b3NK7WIiUpJRcOH+CPNcu5diWRRs1bMuQ/r+Pp165Kx1+JyuTQhiguX8jAxsmMvk+2pkUn1wpb4+WfOEHyjBnkHTyEkasrbh9+iN2DD5SpFZNfks/ac2tZdGYR6QXp9PDoweTAyWXnse+fBqHLQQjtzdMer5Br6szyQ9oVe7pK7Ipyx7tlxpJSJgHdhBB9AP1vP5ullL9XdsdSyivoq09KKbOFEJGAB3DHJnhZoiMn5DJZv8chC0uxDHbDpp8Xhram1bq/2NMn2L9qKUnRF3Bs7MX9r79P0+DOVZoimJaYw6Fforl0KhULGxN6jW6BXw/3CptZF0REkDJjJjl792Lo4IDrO29jN3o0BqY3Yr9etnfB6QWk5qfStVFXng98nkCXQG2HrMta96TQpVr/0w5PQM/XyDVz1Sf23SqxK0o9UuWWff/qQbQ3ZPcBbaWUWX+5bSIwEcDLyysoNja2xuP5K62TUhqZv8VQklaAWUt7bIc0wdjVsvKDy5EUfZH9q5cSeyoMa0dnuo16DL9efTAwqLz+TGZKPkd+jeb8kSRMzIzoMNAL/z6eGJuWf2xhVBQpM78le9s2DGxscBw/Hoexj2FgeSP2otIi1l9Yz/zT80nOS6ajW0cmB04myDVI2yE7SVugdGyRtvK0/Vjo+Rr5Fh4sO3iJ7/dFq8SuKHeoW7Xsq/EEL4SwAvYCn+mnWlaoLnqyFl3OIfPXaAqjMzFyMcduqC9mLav3JmFm8lX2r1rKuYP7MbO2ocvIUQT0H1KlejF5WUUc++0S4fsTEQaCgHsb036AN2YVlDgoio8nddYsMjf9ioGZGQ7jnsRh3LgyPU9LdCVsitrE3JNzuZJ7hQ4uHZgcOJlOjTppO+SkaLViji6E0iIIHAO93qDAypPVR+KYvTuK1JxC/ZunLVRiV5Q70O3qyVqdBzYG1gErK0vuta00u4jMbZfIO56EgbmRVr63U6NqdVIqyMnh0M8/cGLrJoSBIZ1HPkLH+x7A1KLy/wAK80sI2x7LyV3xlJZI/Lo3InhIE6zsyx8WKk5KInX2HDLWr0cYGuIwbhyOE57ByP5G8pVSsiN2B7NOzCImM4Y2jm34sOuHNxpt5KZp1R2PzIOSAvB/BHq9QZFtE348Hs+s3/dwJbOALr4OfDe2A8FqgZKi1Es1luCFNtC8EIiUUk6vqcf5p2Sxjuw/EsneHY8s1WHV3QObez0xsPjnxcBKS4o5se03Dq1bTUFeLm3u6Uv3R8Zi7eBU6bElRaWc2pNA6LZYCnNLaB7sQqfhvti5lr8StjQzk7QFC0hfthyp02E/ahSOz07C2OXGvHkpJSGXQ5gZNpOItAia2jblm97fcK/XvVpiL8iCQ3MgZJbWaKPdQ3DPW5TYN+XnsERm/r6H+PR8OnjZMe3hALo1q/znUBTlzlWTV/DdgceB00KIE/pt70op66yGTf65dDI2RlGaVoCZn6M2zu70z6fzSym5cCSE/SuXkJF0Ba92gdwz9ukqTXnU6STnDl3l8MZocjMK8WrjSJf7fXH2Kn8lrK6ggGsrVpA6fwG6rCxshg3D+cX/YOLpWWa/sOQwZoTO4HjScTysPPisx2cMbTJUa2ZdnK/1PN0/HfLTofV90Oc9dE4t2XTqMjOW7CM6NZe2HjZ8/FRbeqsiYIrSINRYgpdS/oFWt6bOlaQXkPFrNAURaRg5m+M0vm21V6BePn+WvcsXcvl8JI6NvXjgnY/wCehQpYQYF5FGyLoo0hJzcPGxof/TfhU3tC4pIXPDBlK+nUVJUhKWvXri8uqrmLVqVWa/c+nnmBk2k30J+3A0c+Tdzu/yUPOHMDY01mqwH1uqrT7NvgJN74V7/4t0b8+28CS+Xrmfc0nZtHS15vvHgxjg56oSu6I0IA26Nb0s1pG9L4Gs3fEIATaDfLDu4VGthUpZKcnsXbmY8wf3Y2FrR/+JL9C2d38MDCufGZOakEPI+ovER6Rj42TGgGfa0CzIpeKyAjt3kvL1NxRFR2MW4I/7lK+w7NSpzH6xWbHMDpvNlktbsDGx4eUOLzOm1RgsjC1Ap4NTP8Luz+BaDHh2hgfmI316sOdcCtNm/cGZxCx8nS2ZOaY9w9o1UvXYFaUBarAJ/ubhGPN2TtgO9cXI7p/PZy8uLODoxnUc/WUdCEGXB0fT8b4HMTGrfGgn51ohhzdFc/bgFUzNjej+UDPa3dMYQ+Py/8DkHjlCyrTp5J88iYmvLx7fzsS6X78yfwiu5l7lu5PfseHiBkwMTZjQbgLj2o7DxsRGm7t+9jf4/VNIDgfXdvDoWmg+gJDoNKbODSE0LgNPB3OmPhzAiEDVGk9RGrIGl+BLc4rI2BRN/smUfzUcI6Xk/KE/2Lt8EdlpKbTs2pNeY5+qUjGwooISQrfFcnJnPDopCezrSdBgnwqnPBacO0fytGnk7tuvrT795GPsRo4s09A6qyiLBacXsDJiJRLJmFZjGN9uPE7m+jdCY/bBro8h4Sg4NIWHFoHfSE5fzuarRUfYfyGVRrZmfD6yHQ8HN8ZYJXZFafAaTIKXUpIXmkzm5mh0haXY9PPCurdntYZjki9Fs3vpPBIizuDs3YQhL7xGY7+2lR6nK9URceAKRzZFk59dTPNgF7qMaIpNBW/kFiclkzJzBpnrf8bA2hqX11/DfuxYDMxu1HgrKi1i9dnVzD89n6zCLIY3Hc7kwMm4W+mLkyUe1xJ79B6w8YDhMyHwMaLTC5i25gSbT13B3sKY94e2ZmwXb8yq2exbUZT6p0Ek+JK0fK79fJHCixmYeNtg/2DzajXeyMvKJGTtCk7t3IaplRX9nplMu74DqrQCNf5sOn+svUD65Vzcm9sxdHIzXH3Kb5Gny8sjbdFi0hYuRJaU4PDEEzg99yyGdjc6L+mkji0xW/g27FsScxLp7t6dV4JeuVHhMS1KS+wRG8DCCQb+D4Kf5moezPglkrXH4jE1MuDFvs2Z0LMJ1mbV6wmrKEr9Ve8TvC6vmKSZYQDYjdAvVvqHbxhKnY7Tv29n/6olFObnEThoKN0eeqxKbfIyU/IJWXeR6BMp2DiZaeV7A8ufZihLS8nc8AspM2ZQkpyM9YABuLz+GiZeXmX2O3TlENOPTScyPZJWDq34vv/3dHPvpt2YkwJ7v4Tji8HQFO55G7q9QGapGXN3RrH4QAw6KXm8izeT+zTD2bp6dXQURan/6n2CN7Awxm5EM0x9bTGqRlGw5EvR7Fw4hyvnz9LYry19n34OJ0/vSo8rKijh+JZYTuyKw8DQgC4jfAno64lRBUMguYcOkfTlVxRGRmIW4I/HN19j0aFDmX3OpZ/j69CvOZB4AHdLd/7X838MaTIEA2EARblwcDYcmKHNaw96Eu55m3xTJxaHxPDdniiyC0sYGejBK/1b4OlQvdaBiqI0HPU+wQNYtq/8jc+/KsrPI+THVYRu2YiZlTWDJ79K6559Kp0HLnWSc4evcvDnKPKyimjZxY2uI5piWcEMncKoKJKnTCVnzx6M3d21FnlDhvxtZsy3Yd+yKWoT1ibWvB78OqNbjcbU0BRKSyBsCez5AnKSoPVw6PsBxfZNWXssnhk7d5OcXUjfVi68PrAlrRuVPyykKMrdp0Ek+H/i+irU3UvmkZOehn+/QfQY8yTmVpX3VE2KyWLfmnMkx2bj4mPD4Ofa4dak/KbYpRkZpHw7i2tr1mBgbq69gfr442XK9+YV57HozCKWhi9FJ3WMazOO8e3GY2tqq015jPwVdn0EqefBswuMWo707MSWM1eZsmQfMam5BHvbM/uxDqqhtaIof3NXJfica+nsXDCHqGOHcPbyYfgrb+PeonWlxxXkFnNoQxThf1zGwsaEfuNa06KTW/lt8kpLyfjxR1K+mUFpVhb2ox/B6YUXMHK4kYB1UsemqE3MDJ1Jcn4yg5sM5uUOL9+YGRN/BLa/D/GHwakFjF4FLYdwPC6Dz/Rz2Vu6WrNoXDB9Wpa/YEpRFOWuSPBSSsL37mLPsvmUFhXT67GnCBo6otJVqFJKzh68Ssj6ixTmFhPQx5NOw5tgYl7+acs7doyrn31OYWQkFh074vr+e5i1bFlmn9CkUL46+hXhaeG0c2rHtN7TbjTcyIiDHR9A+HqwcoVh30D7x4nNKOTLVaH8dvoqLtamfPlgOx4K8sRQrT5VFOUWGnyCz0pNZsf82Vw6cRyPVn4MmPQSDu4elR6XlpjD3tXnuHIxEzdfG3qNCcTZs/xhnOKrV0meMpWszZsxatQIj6+nYz1oUJkr68ScRL4+/jXbLm3DxcKFz3t8zlDfodobqIXZWsONkFkgDKDXm9D9Ja6VmPDtb+dZfugSxoYGvNKvBRN6NcHCpME/bYqi3AYNNlNIKQnfs5PdS+chdZJ7n5pE4IChCINbL3wqKijh6OZLnNwVj6m5EX0eb0XrruVPvdQVFpK+eAmp338PpaU4Pf8cjs88g4HFjRksucW5LDi9gGXhyzAQBjwf8DxPtnlSXzOmFMKWw++faG+gthsF/T6gwKIRyw5e4tvfL5JbWMIjHT15pV8LXGzM/haDoihKRRpkgs/PyWbnvFmcP3wAT792DHzuJWxd3Co9Lj4ind0rz5KdVoBfD3e6jmiKmVX5C4Ry/jjA1Y8/pjguDuv+/XB56y1MGjf+83YpJb9G/8r049NJzU9lmO8wXurwEm6W+jhi9sO2d+DqaWjcCUavQucexKZTl5mybS8J1/Lp09KZd4a0poVr5W8AK4qi/FWDS/Dx4af4bfZ08jKu0fPRcQQPH1npStSC3GIOrLvI2ZAr2LlaMPK1Drg3tyt33+LkZJK/+IKs37Zg4uOD58IFWHXvXmafc+nn+Pzw54Qmh9LOqR0z+szA39lfuzEtCnb8H5z9FWw94cGF0PZBwuIz+GhuCCfiM/BrZMPKZ/zprhpuKIryLzSYBC91Og6t/4GQn1Zh7+bOo59Ow9W3WaXHRYUms3fNeQpyiukwyJuOQ33KXawkS0u59sMPpEz/GllUhNN/XsBxwgQMbuq3mlWUxZwTc1hzdg3WJtZ82PVDRjYfqR9nz4H9U7VxdiNTuPe/0HUySfmCL9eeZH1YIi7Wpkx5yJ8HOzRW5XuZ/14jAAATwElEQVQVRfnXGkSCL8jJYcvsaUSHHsWvZx/6PTMZY7Nbj1fnZxexd9U5osJScPK0YvgLARV2VcoPD+fqhx9RcPo0lt264vZ//4eJj8+ft0sp2RS9iWnHpnGt4BqjWo7iP+3/c2M++5n12rTHrEQIeFQbZzdzZuEfMczefZGSUsnzvZvyfJ9mWJk2iKdEUZQ7QL3PJvnZWax871WyU1Pp+/RzBAwYUum88EunU/l9+VkK84rpMsKXwP5eGJZTPleXn0/KjJmkL1uGoYMD7lOnYjO07P2fSz/HZ4c/Iyw5DH9nf+b2m4ufo592Y3Ik/PYGXNoPbv7w0GKkZye2hSfx2W97iU/PZ2AbV94b4oeXoyotoCjK7VXvE7yZlTUtuvSgaVBnPFreetFScWEpB9ZdJHxfIo4eltz3YiBOjcsvKJZ75AhX3v8vxXFx2I1+BJdXX8XQ5kYZgPySfL47+R1Lw5diY2LDx90+5v5m92vDMQVZWkGww9+BiRUMnQZBT3E2OZePFxwmJCqNlq7WrHymsxpnVxSlxtT7BC+EoNej4yrdL+lSFjsWhZOZkk9gfy8639ek3LF2XW4uydOmc23VKow9PfFauhTLzmXb5YVcDuGTg5+QkJPAA80f4NWgV28Mx5xaqw3H5CRDhyeg7wdkGdow/dezLDt4CRtzYz65vw1jOnmpbkqKotSoep/gKyOl5PSeBA78dBELGxNGvNwej5bld3jKPXhQu2q/fBn7Jx7H5eWXy8xpTy9IZ+rRqWyK3oSPjQ+LBi6io1tH7ca0KPj1FYjZCx5BMGY10r0DG09e5tPNYaTmFPJYZy9eH9ASOwuTch9fURTldmrQCb4ov4Tfl58lKjQZn3aO9B3nV27bPF1BAclTpnJt5UpMfHzwXrmiTCnf62+iTjk6hZyiHCb6T2Si/0St2mNJEYTMgL1TtNkxQ6dB0NNcTM3lv/MPczA6jYDGtix8Mhj/xuVPvVQURakJDTbBX7uay+Y5p8hKLaDryKa07+9V7mrUgrNnSXz9dYouRuHw5BM4v/JKmZZ5yXnJfHTwI/Yl7CPAOYAPun5Ac/vm2o1xh2HTS5ASCX4jYPCX5Jk68e328yzYH425sSGfjmjLmE5eqm6Moii1rkEm+PjIdLbOO4OhkWDEK4G4l9N0W+p0pC9ZSsrXX2NoZ4fnggVY9bixYElKyeaYzfzv8P8oKi3irY5v8WjrR7U3UfMztDK+xxZpi5XG/AAtB7E9/CofbdpHYkY+DwU15u3BrXCyUh2VFEWpGw0uwYfvT2Tv6vPYu1kwdLI/No5/b3hdkpbG5TfeJDckBKt+fWn0yScY2d/4I5Can8qnhz5lV9wuApwD+LT7p/jY+mg3ntuqXbXnJkOXydDnXZILjfi/5cfZGn6VVm7W/PhsV1WfXVGUOtegEnzotlgO/hyFd1tHBoxvU25Z37zQMBJfeYXSjAzcPv4Iu4cfLjOvfVfcLj4K+Yic4hxeDXqVJ/yewNDAULtq3/oOnFwFLm3g0TXIRoGsPRbPZ5sjKSjR8eaglkzo6Yuxmh2jKModoMEk+KObYziyKYbmHV3pO6713xYuSSm5tnw5SV9NwdjdHZ81qzFrfWPefEFJAVOOTmHt+bW0dmjN/3r+j6Z2TbUbz2+HTS9qUx97vQG93iQ2s5h39HPaOzVx4IsH2uHrXHmTbkVRlNpSYwleCLEIGAYkSynb1tTjgDYsc2RTDK26uNHnidZ/q+OiKyzkyvv/JWvTJqz69sX9f5+XWbR04doF3tz3JhczLvJUm6f4T/v/YGxoDAWZsPVdOLECnFvB6FWUuAWy6EAM03ecx9jAgM9GtmVMRy9VO0ZRlDtOTV7BLwFmActq8DEoyCkmZN1FvNs60ufxVn9LtCVpaSS88B/yw8JwfvllHCdN/HNIRkrJj+d/5KujX2FlbMX3/b6nm0c37cC4Q7DuGa1+TI9XoffbRF0r5tXvDnIyPoN+rV35dERb3GxVjXZFUe5MNZbgpZT7hBA+NXX/15lZGTPitQ7YOptj8JdhmcKoKOInTqIkLQ2PGTOwGTjgz9vyS/L55OAnbIreRHf37nza41OczJ2gtESr+rj3S22GzNPb0XkEs+zgJb7YehYzY0NmjmnPcP9Gqheqoih3tDofgxdCTAQmAnh5eVXrPsprpZcfHk78+GfAyAjv5cswb9fuz9sSshN4Zc8rnEs/x/OBzzPJf5I2/TEjHtZPgLiDWnelodO4XGDMm4uO8MfFVHq3dObLB/1xVZ2VFEWpB+o8wUsp5wHzAIKDg+XtuM+8sDDiJ07CwNoK78WLMfH2/vO2kMshvLnvTXQ6HbP6zqJX417aDZGb4JfJWhu9kfMg4BE2hCXy31/OUKqTfD6yHWM6eaqrdkVR6o06T/C3W0FEBPHPTMDIyQmvxYswdnf/87Z159fxyaFPaGLbhBl9ZuBl46UNyfz+CRz4Btzbw0OLyLX04r9rT7A+NJEgb3umjwrA29GyDn8qRVGUf65BJfiiuDjiJkzEwNYGr6VLMHbT+p9KKfk27Fvmn55Pd/fuTL1nKlYmVpCbCj89rRUIC3oKBn9JRHIhLyz+g5jUXF7q25wX+zZXZQYURamXanKa5GqgN+AkhEgAPpBSLqypx9Pl5RH//PNQUoLX8mV/JvcSXQkfhHzAxqiNPNj8Qd7r8h7GBsaQGAo/PA65KXD/bGTgY6w4HMcnv0ZgZ27Mymc6062pqtWuKEr9VZOzaMbU1H2X5+qnn1EUFY3XwgWY+voCUKwr5t3977L10laeD3yeZ/2f1cbQIzfBuglg6Qzjt5Pn1Ja31pxg08nL3NPCmWmjAlQNGUVR6r0GMUSTe+QImevX4zhxIpbdtHnsxbpi3tz7JjvjdvJa0GuMaztOa8gRMktryKGv2R5fZMWEOSGcS8rmjYEtee6epmrRkqIoDUKDSPCpc+di5OaG03PPAtqY+4chH7IzbidvdXyLsX5jQaeDLW/C0fngdz+M/J4/LuXywuo/0Okki8d1pHdLlzr+SRRFUW6fep/gS3NyKY6Nw370IxiYa5Ujvw37lo1RG5kcOFlL7qUl2hTIU2ug6wvQ/xOWHY7jw43hNHOxYt7jwfg4qVkyiqI0LPU+wRtaWdJ0x3ZkcTEAv0X/xvzT83moxUNM8p+kdVxa/wxE/AJ93kfX4zW+3HqO7/dF06+1CzNGt8fStN6fBkVRlL9pEJlNGBoiDA2JyYzho4Mf0d6lPe92fhchdTeS+8DPKQh+ltd/OMGvp67wRFdvPhjeRk2BVBSlwWoQCR5AJ3W8/8f7mBia8FWvrzAWRlqJX31yzwuaxISlRzlwMY13BrdiYi9ftSpVUZQGrcEk+HUX1nEq9RSf9/gcN0s3+P1TCF0GPV8np8Mknl58lGOX0pk+KoAHOjSu63AVRVFqXINI8MWlxXx/8nvau7RnmO8wCN8A+6ZA+8fJ7vYW4xYd4UR8BjNGt2d4gHvld6goitIANIjecjvjdpKUl8RE/4mIlHOw4Xlo3InCgV8xaUUoJ+MzmDVGJXdFUe4uDeIKflfcLpzMnejm2hEW9gdjc3QPL+PV9WcJiUpj+qgABrdrVNdhKoqi1KoGkeAj0yIJcg3CIGQmXDkJo5bx5YEMNp+6wjuDW6kxd0VR7kr1fohGJ3Vczr2Mh4kd7J8Ore/jt9JOfL8vmrFdvJjYy7euQ1QURakT9T7BCwS7Ht7Fk6nJUFpMXNBbvPHjSdp72fF/w9qoqZCKoty16v0QjRACB4zg1E/o2j3My9szMTI0YM5jHTAxqvd/vxRFUaqtYWTAyI1QnMtm08GExmXwwXA/Gtma13VUiqIodareX8EDELUbnaUL7x8xpWdzO0a296jriBRFUepcw7iCTzpDlEkrMgtKeHtwKzXuriiKQgNJ8DIrkWMZlvT3c6WNu21dh6MoinJHqP8JXqcjsvWLbCgIZmwX77qORlEU5Y5R/8fgDQxYrhtMhMllujd1rOtoFEVR7hj1/woeOJWQQXtve4wMG8SPoyiKcls0iIx4JbMAT3s1LVJRFOVm9T7B63SSe1o4E+xjX9ehKIqi3FHq/Ri8gYHg60cC6zoMRVGUO069v4JXFEVRyqcSvKIoSgOlEryiKEoDVaMJXggxSAhxTghxUQjxdk0+lqIoilJWjSV4IYQhMBsYDPgBY4QQfjX1eIqiKEpZNXkF3wm4KKWMllIWAWuA+2vw8RRFUZSb1GSC9wDib/o+Qb+tDCHERCHEMSHEsZSUlBoMR1EU5e5Skwm+vJq98m8bpJwnpQyWUgY7OzvXYDiKoih3l5pc6JQAeN70fWPg8q0OOH78eKoQIraaj+cEpFbz2Jqk4vpnVFz/jIrrn2mIcVVYRldI+beL6ttCCGEEnAf6AonAUeBRKWV4DT3eMSllcE3c97+h4vpnVFz/jIrrn7nb4qqxK3gpZYkQ4gVgG2AILKqp5K4oiqL8XY3WopFS/gb8VpOPoSiKopSvIa1knVfXAVRAxfXPqLj+GRXXP3NXxVVjY/CKoihK3WpIV/CKoijKTVSCVxRFaaDqfYK/UwqaCSE8hRC7hRCRQohwIcRL+u0fCiEShRAn9B9D6iC2S0KI0/rHP6bf5iCE2CGEuKD/XKstsYQQLW86JyeEEFlCiJfr6nwJIRYJIZKFEGdu2lbuORKamfrX3CkhRIdajmuKEOKs/rF/FkLY6bf7CCHybzp339VyXBU+d0KId/Tn65wQYmAtx/XDTTFdEkKc0G+vlfN1i9xQ868vKWW9/UCbfhkF+AImwEnAr45iaQR00H9tjbYGwA/4EHi9js/TJcDpL9u+At7Wf/028GUdP49X0RZs1Mn5AnoBHYAzlZ0jYAiwBW21dhfgcC3HNQAw0n/95U1x+dy8Xx2cr3KfO/3vwUnAFGii/501rK24/nL7NOD/avN83SI31Pjrq75fwd8xBc2klFeklKH6r7OBSMqpvXMHuR9Yqv96KTCiDmPpC0RJKau7ivlfk1LuA9L/srmic3Q/sExqDgF2QohGtRWXlHK7lLJE/+0htFXitaqC81WR+4E1UspCKWUMcBHtd7dW4xJCCGAUsLomHvsWMVWUG2r89VXfE3yVCprVNiGED9AeOKzf9IL+X61FtT0UoieB7UKI40KIifptrlLKK6C9AAGXOojrutGU/aWr6/N1XUXn6E563T2NdrV3XRMhRJgQYq8QomcdxFPec3ennK+eQJKU8sJN22r1fP0lN9T466u+J/gqFTSrTUIIK2Ad8LKUMguYCzQFAoEraP8i1rbuUsoOaLX5JwshetVBDOUSQpgA9wE/6jfdCeerMnfE604I8R5QAqzUb7oCeEkp2wOvAquEEDa1GFJFz90dcb6AMZS9kKjV81VObqhw13K2Vet81fcE/48LmtUkIYQx2hO4Ukq5HkBKmSSlLJVS6oD51NC/prcipbys/5wM/KyPIen6v336z8m1HZfeYCBUSpmkj7HOz9dNKjpHdf66E0I8CQwDHpP6gVv9EEia/uvjaGPdLWorpls8d3fC+TICHgB+uL6tNs9XebmBWnh91fcEfxRoLoRoor8SHA1srItA9ON7C4FIKeX0m7bfPHY2Ejjz12NrOC5LIYT19a/R3qA7g3aentTv9iTwS23GdZMyV1V1fb7+oqJztBF4Qj/boQuQef1f7doghBgEvAXcJ6XMu2m7s9A6qSGE8AWaA9G1GFdFz91GYLQQwlQI0UQf15HaikuvH3BWSplwfUNtna+KcgO18fqq6XeQa/oD7R3n82h/fd+rwzh6oP0bdQo4of8YAiwHTuu3bwQa1XJcvmgzGE4C4dfPEeAI7AIu6D871ME5swDSANubttXJ+UL7I3MFKEa7ghpf0TlC+xd6tv41dxoIruW4LqKN0V5/nX2n3/dB/XN8EggFhtdyXBU+d8B7+vN1Dhhcm3Hpty8Bnv3LvrVyvm6RG2r89aVKFSiKojRQ9X2IRlEURamASvCKoigNlErwiqIoDZRK8IqiKA2USvCKoigNlErwyl1FCDFSCCGFEK1u432OEEL43a77U5TbRSV45W4zBvgDbVHc7TICrTqgotxR1Dx45a6hrwVyDugDbJRSttKvvvwBsEFrQv8cEIK28jAYbYHKIinl10KIpmgLUJyBPGAC4AD8CmTqPx4EhgLPotWJiZBS3s4/JopSZUZ1HYCi1KIRwFYp5XkhRLq+kUIfYJuU8jP9snULtGJZHlLKtgBC31ADrTHys1LKC0KIzsAcKeW9QoiNwK9Syp/0+78NNJFSFt50rKLUOpXglbvJGOAb/ddr9N9vAhbpi0FtkFKeEEJEA75CiG+BzWillq2AbsCPWmkRQGtgUZ5TwEohxAZgQ838KIpSOTVEo9wVhBCOaLVJktGGXQz1n73ROu4MBV4Epkgpl+kT+kBgHJACvAyck1L+rfGCEGIJZa/gDdE6C92HVnOkjbzRoENRao16k1W5WzyE1iXHW0rpI6X0BGLQEnGylHI+2rh7ByGEE2AgpVwH/Bet3VoWECOEeBj+7JsZoL/vbLRWbAghDABPKeVu4E3ADrCqvR9TUW5QQzTK3WIM8MVftq1DqzKYK4QoBnKAJ9C65yzWJ2uAd/SfHwPmCiHeB4zRhnlO6j/PF0K8iDY7Z6EQwhatKuDXUsqMGvupFOUW1BCNoihKA6WGaBRFURooleAVRVEaKJXgFUVRGiiV4BVFURooleAVRVEaKJXgFUVRGiiV4BVFURqo/wc4Yg6igEQSpgAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -269,8 +269,7 @@ } ], "source": [ - "ss = ks_ss()\n", - "plt.plot(ss['a_grid'][:10], ss['a'][:, :10].T)\n", + "plt.plot(ss[\"a_grid\"], ss.internal[\"household\"][\"c\"].T)\n", "plt.xlabel('Assets'), plt.ylabel('Consumption')\n", "plt.show()" ] @@ -287,19 +286,19 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 564 ms\n" + "Wall time: 775 ms\n" ] } ], "source": [ - "%time ss = sj.krusell_smith.ks_ss()" + "%time ss = ks_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { @@ -311,19 +310,21 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 1.42 s\n" + "Wall time: 1.69 s\n" ] } ], "source": [ - "%time _ = sj.krusell_smith.ks_ss(nA=2000)" + "calibration_highA = {**calibration, **{\"nA\": 2000}}\n", + "\n", + "%time _ = ks_model.solve_steady_state(calibration_highA, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { @@ -347,33 +348,19 @@ "evaluated at the steady state. Every column can be interpreted as the impulse response to a one-period news shock.\n", "\n", "### 3.1 Simple blocks\n", - "To build intuition, let's start with the firm block. In our code, simple blocks are specified as regular Python functions with the added decorator ``@simple``. In the body of the function, we directly implement the corresponding equilibrium conditions. The decorator turns the function into an instance of ``SimpleBlock``, a class that, among other things, knows how to handle time displacements such as `K(-1)` to denote 1-period lags and `r(+1)` to denote 1-period leads. In general, one can write (-s) and (+s) to denote s-period lags and leads." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "@simple\n", - "def firm(K, L, Z, alpha, delta):\n", - " r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta\n", - " w = (1 - alpha) * Z * (K(-1) / L) ** alpha\n", - " Y = Z * K(-1) ** alpha * L ** (1 - alpha)\n", - " return r, w, Y" + "To build intuition, let's start with the firm block we instantiated above. In our code, simple blocks are specified as regular Python functions with the added decorator ``@simple``. In the body of the function, we directly implement the corresponding equilibrium conditions. The decorator turns the function into an instance of ``SimpleBlock``, a class that, among other things, knows how to handle time displacements such as `K(-1)` to denote 1-period lags and `r(+1)` to denote 1-period leads. In general, one can write (-s) and (+s) to denote s-period lags and leads." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Simple blocks can compute their Jacobians by using the method `SimpleBlock.jac` This takes in the steady state dict returned by `ks_ss` and two optional inputs: the truncation horizon and list of variables to differentiate with respect to. It returns the Jacobians in a nested dict, where the first level is the output variable $Y$ and the second level is the input variable $X$." + "Simple blocks can compute their Jacobians by using the `jacobian` method. This takes in the `SteadyStateDict` object returned by the `ks_model`'s `solve_steady_state` method and two optional inputs: the truncation horizon and list of variables to differentiate with respect to. It returns the Jacobians in a nested dict, where the first level is the output variable $Y$ and the second level is the input variable $X$." ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -389,7 +376,7 @@ } ], "source": [ - "J_firm = firm.jac(ss, T=5, shock_list=['K', 'Z'])\n", + "J_firm = firm.jacobian(ss, exogenous=['K', 'Z'], T=5)\n", "print(J_firm['Y']['Z']) # Jacobian of output Y vs. TFP Z" ] }, @@ -397,14 +384,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "By default, `SimpleBlock.jac` compoutes the Jacobian for each input-output pair. In practice, it only makes sense to do so with respect to endogenous variables and shocks, hence the `shock_list` option. In this model, capital and TFP are the only inputs that will ever change.\n", + "By default, `jacobian` compoutes the Jacobian for each input-output pair. In practice, it only makes sense to do so with respect to endogenous variables and shocks, hence the `exogenous` option. In this model, capital and TFP are the only inputs that will ever change.\n", "\n", - "The Jacobian is diagonal because the production function does not depend on leads or lags of productivity. Such sparsity is very common for simple blocks, and we wrote the SimpleBlock class to take full advantage of it. For example, if we leave the truncation parameter $T$ unspecified, which is recommended, `SimpleBlock.jac` returns a more efficient sparse representation of the Jacobian." + "The Jacobian is diagonal because the production function does not depend on leads or lags of productivity. Such sparsity is very common for simple blocks, and we wrote the SimpleBlock class to take full advantage of it. For example, if we leave the truncation parameter $T$ unspecified, which is recommended, `jacobian` returns a more efficient sparse representation of the Jacobian." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -416,7 +403,7 @@ } ], "source": [ - "J_firm_sparse = firm.jac(ss, shock_list=['K', 'Z'])\n", + "J_firm_sparse = firm.jacobian(ss, exogenous=['K', 'Z'])\n", "print(J_firm_sparse['Y']['Z'])" ] }, @@ -434,12 +421,12 @@ "### 3.2 HA blocks\n", "HA blocks have more complicated Jacobians, but they have a regular structure that we can exploit to calculate them very quickly. For comprehensive coverage of our **fake news algorithm**, please see the [het-agent Jacobian notebook](het_jacobian.ipynb) as well as the paper.\n", "\n", - "HetBlocks have a `HetBlock.jac` method that is analogous to `SimpleBlock.jac` above." + "A `HetBlock` object has a `jacobian` method that is analogous to one above for `SimpleBlock` objects." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 30, "metadata": { "scrolled": true }, @@ -457,7 +444,7 @@ } ], "source": [ - "J_ha = household.jac(ss, T=5, shock_list=['r', 'w'])\n", + "J_ha = household.jacobian(ss, exogenous=['r', 'w'], T=5)\n", "print(J_ha['C']['r'])" ] }, @@ -467,12 +454,12 @@ "source": [ "Notice that this matrix is no longer sparse. This generally the case for HA blocks. The Bellman equation implies that policies are forward-looking, and then aggregates are also backward-looking due to persistence coming via the distribution.\n", "\n", - "Fortunately, our `SimpleSparse` Jacobians play nicely with these full matrices, so that we can easily combine the Jacobians of simple blocks and HA blocks. For example, the multiplication operator `@` maps any combination of SimpleSparse and full matrices into full matrices. " + "Fortunately, our `SimpleSparse` Jacobian objects are conformable with standard `np.array` objects, so that we can easily combine the Jacobians of simple blocks and HA blocks. For example, the multiplication operator `@` maps any combination of SimpleSparse and `np.array` objects into `np.array` objects as in standard `np.array` matrix multiplication. " ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 31, "metadata": { "scrolled": false }, @@ -527,18 +514,22 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 48, "metadata": { "scrolled": true }, "outputs": [], "source": [ + "# Import the JacobianDict class, since we are manually constructing Jacobians to demonstrate how the automatic construction\n", + "# works in the code\n", + "from sequence_jacobian.jacobian.classes import JacobianDict\n", + "\n", "# firm Jacobian: r and w as functions of K and Z\n", - "J_firm = firm.jac(ss, shock_list=['K', 'Z'])\n", + "J_firm = firm.jacobian(ss, exogenous=['K', 'Z'])\n", "\n", "# household Jacobian: curlyK (called 'a' for assets by J_ha) as function of r and w\n", "T = 300\n", - "J_ha = household.jac(ss, T=T, shock_list=['r', 'w'])" + "J_ha = household.jacobian(ss, exogenous=['r', 'w'], T=T)" ] }, { @@ -551,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ @@ -568,11 +559,12 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 41, "metadata": {}, "outputs": [], "source": [ - "J = {**J_firm, 'curlyK': {'K' : J_curlyK_K, 'Z' : J_curlyK_Z}}" + "J = copy.deepcopy(J_firm)\n", + "J.update(JacobianDict({'curlyK': {'K' : J_curlyK_K, 'Z' : J_curlyK_Z}}))" ] }, { @@ -587,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ @@ -604,7 +596,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -623,7 +615,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ @@ -643,7 +635,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 45, "metadata": {}, "outputs": [], "source": [ @@ -658,32 +650,20 @@ "\n", "These steps can be automatized for the entire class of SHADE models. Any SHADE model is characterized by its blocks, exogenous shocks, unknowns, and targets. The only other things we need to know are the steady state around which to linearize, and the truncation horizon.\n", "\n", - "If we define a \"market clearing\" block that returns the target (asset market clearing), we can get the general equilibrium Jacobians by simply calling `jacobian.get_G`. Note that `get_G` takes in the model blocks (in arbitrary order), the names of exogenous shocks, the names of unknown endogenous variables, the names of target equations, the truncation horizon, and the steady state dict." + "Using the market clearing block we instantiated earlier in conjunction with the firm and household blocks allows us to calculate the general equilibrium Jacobians by calling the `ks_model`'s `solve_jacobian` method. Note that this method takes in the `SteadyStateDict` object we solved for earlier along with the names of exogenous shocks, the names of unknown endogenous variables, the names of target equations, and the truncation horizon." ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 46, "metadata": {}, "outputs": [], "source": [ - "@simple\n", - "def mkt_clearing(K, A):\n", - " asset_mkt = A - K\n", - " return asset_mkt" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "G2 = sj.jacobian.get_G(block_list=[firm, mkt_clearing, household], # we could replace household with J_ha here\n", - " exogenous=['Z'],\n", - " unknowns=['K'],\n", - " targets=['asset_mkt'],\n", - " T=T, ss=ss)" + "exogenous = ['Z']\n", + "unknowns = ['K']\n", + "targets = ['asset_mkt']\n", + "\n", + "G2 = ks_model.solve_jacobian(ss, exogenous, unknowns, targets, T=T)" ] }, { @@ -695,7 +675,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 47, "metadata": {}, "outputs": [], "source": [ @@ -715,7 +695,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 50, "metadata": { "scrolled": true }, @@ -737,6 +717,7 @@ "rhos = np.array([0.2, 0.4, 0.6, 0.8, 0.9])\n", "dZ = 0.01*ss['Z']*rhos**(np.arange(T)[:, np.newaxis]) # get T*5 matrix of dZ\n", "dr = G['r'] @ dZ\n", + "\n", "plt.plot(10000*dr[:50, :])\n", "plt.title(r'$r$ response to 1% $Z$ shocks with $\\rho=(0.2 ... 0.9)$')\n", "plt.ylabel(r'basis points deviation from ss')\n", @@ -753,7 +734,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 52, "metadata": { "scrolled": true }, @@ -762,7 +743,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 21.9 ms\n" + "Wall time: 16.9 ms\n" ] } ], @@ -787,7 +768,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 54, "metadata": { "scrolled": false }, @@ -808,6 +789,7 @@ "source": [ "dZ = 0.01*(np.arange(T)[:, np.newaxis] == np.array([5, 10, 15, 20, 25]))\n", "dK = G['K'] @ dZ\n", + "\n", "plt.plot(dK[:50])\n", "plt.title('$K$ response to 1% Z news shocks for $t=5,...,25$')\n", "plt.show()" @@ -868,7 +850,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 55, "metadata": {}, "outputs": [], "source": [ @@ -894,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 56, "metadata": { "scrolled": false }, @@ -905,7 +887,7 @@ "(300, 4, 2)" ] }, - "execution_count": 27, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -926,7 +908,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 60, "metadata": { "scrolled": false }, @@ -941,8 +923,8 @@ ], "source": [ "sigmas = np.array([sigma_persist, sigma_trans])\n", - "Sigma = sj.estimation.all_covariances(dX, sigmas) # burn-in for jit\n", - "%time Sigma = sj.estimation.all_covariances(dX, sigmas)" + "Sigma = estimation.all_covariances(dX, sigmas) # burn-in for jit\n", + "%time Sigma = estimation.all_covariances(dX, sigmas)" ] }, { @@ -956,7 +938,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 61, "metadata": { "scrolled": true }, @@ -975,7 +957,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 62, "metadata": { "scrolled": false }, @@ -1035,23 +1017,23 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 65, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 1.99 ms\n" + "Wall time: 2.99 ms\n" ] }, { "data": { "text/plain": [ - "-48613.762381432694" + "-45684.835800463035" ] }, - "execution_count": 31, + "execution_count": 65, "metadata": {}, "output_type": "execute_result" } @@ -1064,9 +1046,9 @@ "sigma_measurement = np.full(4, 0.05)\n", "\n", "# calculate log-likelihood\n", - "sj.estimation.log_likelihood(Y, Sigma, sigma_measurement)\n", + "estimation.log_likelihood(Y, Sigma, sigma_measurement)\n", "\n", - "%time sj.estimation.log_likelihood(Y, Sigma, sigma_measurement)" + "%time estimation.log_likelihood(Y, Sigma, sigma_measurement)" ] }, { @@ -1081,7 +1063,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 69, "metadata": {}, "outputs": [], "source": [ @@ -1098,10 +1080,10 @@ " M = np.stack([dX1, dX2], axis=2)\n", " \n", " # calculate all covariances\n", - " Sigma = sj.estimation.all_covariances(M, np.array([sigma_persist, sigma_trans]))\n", + " Sigma = estimation.all_covariances(M, np.array([sigma_persist, sigma_trans]))\n", " \n", " # calculate log=likelihood from this\n", - " return sj.estimation.log_likelihood(Y, Sigma, sigma_measurement)" + " return estimation.log_likelihood(Y, Sigma, sigma_measurement)" ] }, { @@ -1113,12 +1095,12 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 70, "metadata": {}, "outputs": [], "source": [ "# stack covariances into matrix using helper function, then do a draw using NumPy routine\n", - "V = sj.estimation.build_full_covariance_matrix(Sigma, sigma_measurement, 100)\n", + "V = estimation.build_full_covariance_matrix(Sigma, sigma_measurement, 100)\n", "Y = np.random.multivariate_normal(np.zeros(400), V).reshape((100, 4))" ] }, @@ -1131,7 +1113,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 71, "metadata": { "scrolled": true }, @@ -1140,7 +1122,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Wall time: 307 ms\n" + "Wall time: 237 ms\n" ] } ], @@ -1151,14 +1133,14 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 72, "metadata": { "scrolled": true }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -1213,12 +1195,12 @@ "source": [ "### 7.1 Implementation\n", "\n", - "Our quasi-Newton method can be implemented in two steps. First, build the nonlinear function $H(U, Z).$ Second, guess $U$ for a given $Z$ and iterate until convergence. We automatized both of these, so all we need to do is call a single function, `nonlinear.td_solve`." + "Our quasi-Newton method can be implemented in two steps. First, build the nonlinear function $H(U, Z).$ Second, guess $U$ for a given $Z$ and iterate until convergence. We automatized both of these, so all we need to do is call the `solve_impulse_nonlinear` method for the `ks_model` object. We will also solve for the linearized dynamics using the `solve_impulse_linear` method for comparison." ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 77, "metadata": { "scrolled": false }, @@ -1239,10 +1221,10 @@ } ], "source": [ - "Z = ss['Z'] + 0.01*0.8**np.arange(T)\n", + "Z_shock_path = {\"Z\": 0.01*0.8**np.arange(T)}\n", "\n", - "td_nonlin = sj.td_solve(ss=ss, block_list=[firm, household, mkt_clearing],\n", - " unknowns=['K'], targets=['asset_mkt'], H_U=H_K, monotonic=True, Z=Z)" + "td_nonlin = ks_model.solve_impulse_nonlinear(ss, Z_shock_path, unknowns, targets)\n", + "td_lin = ks_model.solve_impulse_linear(ss, Z_shock_path, unknowns, targets)" ] }, { @@ -1256,7 +1238,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 81, "metadata": { "scrolled": true }, @@ -1275,8 +1257,8 @@ } ], "source": [ - "dr_nonlin = 10000 * (td_nonlin['r'] - ss['r'])\n", - "dr_lin = 10000 * G['r'] @ (Z - ss['Z'])\n", + "dr_nonlin = 10000 * td_nonlin.deviations()['r']\n", + "dr_lin = 10000 * td_lin['r']\n", "\n", "plt.plot(dr_nonlin[:50], label='nonlinear', linewidth=2.5)\n", "plt.plot(dr_lin[:50], label='linear', linestyle='--', linewidth=2.5)\n", @@ -1298,7 +1280,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 82, "metadata": {}, "outputs": [ { @@ -1333,13 +1315,14 @@ } ], "source": [ - "Z = ss['Z'] + 0.1*0.8**np.arange(T)\n", + "big_Z_shock_path = {\"Z\": 0.1*0.8**np.arange(T)}\n", "\n", - "td_nonlin = sj.td_solve(ss, [firm, household, mkt_clearing], ['K'], ['asset_mkt'], H_K, monotonic=True, Z=Z)\n", + "td_nonlin = ks_model.solve_impulse_nonlinear(ss, big_Z_shock_path, unknowns, targets)\n", + "td_lin = ks_model.solve_impulse_linear(ss, big_Z_shock_path, unknowns, targets)\n", "\n", "# extract interest rate response, scale to basis points\n", - "dr_nonlin = 10000 * (td_nonlin['r'] - ss['r'])\n", - "dr_lin = 10000 * G['r'] @ (Z - ss['Z'])\n", + "dr_nonlin = 10000 * td_nonlin.deviations()['r']\n", + "dr_lin = 10000 * td_lin['r']\n", "\n", "plt.plot(dr_nonlin[:50], label='nonlinear', linewidth=2.5)\n", "plt.plot(dr_lin[:50], label='linear', linestyle='--', linewidth=2.5)\n", @@ -1349,13 +1332,6 @@ "plt.legend()\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/rbc.ipynb b/notebooks/rbc.ipynb index 445a4fa..10912fc 100644 --- a/notebooks/rbc.ipynb +++ b/notebooks/rbc.ipynb @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -61,8 +61,7 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj\n", - "from sequence_jacobian import simple, helper" + "from sequence_jacobian import simple, create_model" ] }, { @@ -118,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -139,7 +138,7 @@ "def mkt_clearing(r, C, Y, I, K, L, w, eis, beta):\n", " goods_mkt = Y - C - I\n", " euler = C ** (-1 / eis) - beta * (1 + r(+1)) * C(+1) ** (-1 / eis)\n", - " walras = C + K - (1 + r) * K(-1) - w * L # we can the check dynamic version too\n", + " walras = C + K - (1 + r) * K(-1) - w * L\n", " return goods_mkt, euler, walras" ] }, @@ -156,136 +155,56 @@ "source": [ "## 2 Steady state\n", "\n", - "The next step of solving a model is to compute its steady state. The sequence-jacobian toolkit provides functionality for computing a model's steady state from its DAG representation, but if the user already has a pre-computed steady state they can supply this in the format of a dict(ionary) mapping parameters and variable names to their values.\n", - "\n", - "We will describe an additional, enhanced framework for steady state calibration at the end of this notebook." + "The next step of solving a model is to compute its steady state. The sequence-jacobian toolkit provides functionality for computing a model's steady state from its DAG representation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 2.1 Steady state with a standard DAG\n", + "We will use an alternate arrangement of the DAG, since we want to calibrate a few variable/parameter values in steady state to hit certain targets, such as normalizing $Y = 1$ for a given steady state value of $Z$ or ensuring we solve for the $\\beta$ such that $r = 0.01$.\n", "\n", - "In the first case, we will use almost exactly the same arrangement of the DAG, with two unknowns and two targets to compute the steady state, providing an initial set of fixed variables/parameters.\n", - "\n", - "The one difference is that we will use $\\varphi$ (`vphi`) as an unknown instead of $L$ because we want to calibrate the steady state aggregate labor supply $L=1$. Verifying that it is a valid unknown, observe that $\\varphi$ influences the $euler$ target indirectly through $L$ mapping into $r$ and the $goods\\_mkt$ target indirectly through $L$ mapping into $C$.\n", - "\n", - "Because the `steady_state` function uses a root-finding algorithm, specified in the keyword argument `solver`, one must either provide a set of initial values as given below with $\\varphi : 0.9$ and $K : 2.$, or a set of bounds (depending on the solver's requirements), provided as a tuple of numerical values e.g. $\\varphi : (0.5, 0.99)$." + "The `solver` keyword argument specifies which root-finding algorithm will be used to solve for the steady state. Any of the generic root-finding algorithms listed in `scipy.optimize` can be used." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ - "# Solving for the steady state as a standard DAG\n", - "calibration = {\"L\": 1., \"Z\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11, \"beta\": 1/(1 + 0.01)}\n", "blocks = [household, firm, mkt_clearing]\n", - "unknowns_ss = {\"vphi\": 0.9, \"K\": 2.}\n", - "targets_ss = {\"euler\": 0., \"goods_mkt\": 0.}\n", - "ss = sj.steady_state(blocks, calibration, unknowns_ss, targets_ss, solver=\"broyden_custom\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can inspect the values contained in the returned dict(ionary) below to confirm that indeed the targets equations have been satisfied with $goods\\_mkt \\approx 0$ and $euler \\approx 0$ and further we have verified that Walras law is also satisfied by including the resource constraint as an additional variable in the DAG." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'L': 1.0,\n", - " 'Z': 1.0,\n", - " 'r': 0.010000000006606863,\n", - " 'eis': 1.0,\n", - " 'frisch': 1.0,\n", - " 'delta': 0.025,\n", - " 'alpha': 0.11,\n", - " 'beta': 0.9900990099009901,\n", - " 'vphi': 0.965891472871577,\n", - " 'K': 3.6206932399091794,\n", - " 'w': 1.0253144949496455,\n", - " 'Y': 1.1520387583703882,\n", - " 'C': 1.0615214273518796,\n", - " 'I': 0.0905173309977294,\n", - " 'goods_mkt': 2.0779156173489355e-11,\n", - " 'euler': -6.162292898181931e-12,\n", - " 'walras': -2.077937821809428e-11}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ss" + "rbc_model = sj.create_model(blocks, name=\"RBC\")\n", + "\n", + "calibration = {\"L\": 1., \"Z\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11}\n", + "unknowns_ss = {\"vphi\": 0.9, \"beta\": 0.99, \"K\": 2., \"Z\": 1.}\n", + "targets_ss = {\"goods_mkt\": 0., \"r\": 0.01, \"euler\": 0., \"Y\": 1.}\n", + "\n", + "ss = rbc_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "However, suppose we additionally want to normalize $Y = 1$ in steady state. The current structure of the DAG with the unknowns $\\varphi, K$ and targets $euler, goods\\_mkt$ do not deliver this directly; however, we can easily accommodate this by including another unknown and target. In this case, we will choose $Z$ as the unknown and target $Y = 1$. To set a target equal to a non-zero value, if the variable is not written as an implicit function like $euler$ or $goods\\_mkt$, one simply needs to write the desired value in the dictionary in lieu of a 0." + "We can inspect the values contained in the returned `SteadyStateDict` object below to confirm that indeed the targets equations have been satisfied with the goods market clearing condition satisfied, the euler equation residual equal to 0, and as an additional check we have verified that Walras law is satisfied by including the resource constraint as an additional variable in the DAG." ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# Solving for the steady state as a standard DAG\n", - "calibration = {\"L\": 1., \"Z\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11, \"beta\": 1/(1 + 0.01)}\n", - "blocks = [household, firm, mkt_clearing]\n", - "unknowns_ss = {\"vphi\": 0.9, \"K\": 2., \"Z\": 1.}\n", - "targets_ss = {\"euler\": 0., \"goods_mkt\": 0., \"Y\": 1.}\n", - "ss = sj.steady_state(blocks, calibration, unknowns_ss, targets_ss, solver=\"broyden_custom\")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, + "execution_count": 21, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "{'L': 1.0,\n", - " 'Z': 0.8816460975729918,\n", - " 'r': 0.010000000009327194,\n", - " 'eis': 1.0,\n", - " 'frisch': 1.0,\n", - " 'delta': 0.025,\n", - " 'alpha': 0.11,\n", - " 'beta': 0.9900990099009901,\n", - " 'vphi': 0.9658914729252107,\n", - " 'K': 3.142857142122498,\n", - " 'w': 0.8900000000291391,\n", - " 'Y': 1.0000000000327407,\n", - " 'C': 0.9214285714043695,\n", - " 'I': 0.07857142855306254,\n", - " 'goods_mkt': 7.530864820637362e-11,\n", - " 'euler': -1.0022205287896213e-11,\n", - " 'walras': -7.530831513946623e-11}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Goods Market Clearing: 2.220446049250313e-16, Euler equation: 1.1102230246251565e-15, Walras: -4.440892098500626e-16\n" + ] } ], "source": [ - "ss" + "print(f\"Goods Market Clearing: {ss['goods_mkt']}, Euler equation: {ss['euler']}, Walras: {ss['walras']}\")" ] }, { @@ -296,20 +215,20 @@ "\n", "The linearized impulse responses of the model are fully characterized by the general equilibrium Jacobians $G$. These matrices map *any* sequence of shocks into an impulse response, e.g. $dC = G^{C,Z} dZ.$ Once we have them, we're pretty much done!\n", "\n", - "We can get all of these in a single call to the function `jacobian.get_G`. This function takes in the model blocks (in arbitrary order), the names of exogenous shocks, the names of unknown endogenous variables, the names of target equations, the truncation horizon, and the steady state dict." + "We can get all of these in a single call to the `solve_jacobian` method of the `rbc_model` object. This function takes in the `SteadyStateDict` we obtained from calling `rbc_model.solve_steady_state`, the names of exogenous shocks, the names of unknown endogenous variables, the names of target equations, and the truncation horizon." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ - "G = sj.get_G(block_list=blocks,\n", - " exogenous=['Z'],\n", - " unknowns=['K', 'L'],\n", - " targets=['euler', 'goods_mkt'],\n", - " T=300, ss=ss)" + "exogenous = [\"Z\"]\n", + "unknowns = [\"K\", \"L\"]\n", + "targets = [\"euler\", \"goods_mkt\"]\n", + "\n", + "G = rbc_model.solve_jacobian(ss, exogenous, unknowns, targets, T=300)" ] }, { @@ -323,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -363,7 +282,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -379,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -412,22 +331,59 @@ "For those of you familiar with Dynare, these impulse responses are identical to what you could obtain by running the perfect foresight solver `simul` with the `linear_approximation` option." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that we can also perform the same calculation as the one above using the `solve_impulse_linear` method of the `rbc_model` object." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "regular_Z_shock_path = {\"Z\": dZ[:, 0]}\n", + "\n", + "dC_alt = 100 * rbc_model.solve_impulse_linear(ss, regular_Z_shock_path, unknowns, targets).normalize()[\"C\"][:50]\n", + "\n", + "plt.plot(dC_alt, label='regular shock', linewidth=2.5)\n", + "plt.title(r'Consumption response to TFP shocks')\n", + "plt.ylabel(r'% deviation from ss')\n", + "plt.xlabel(r'quarters')\n", + "plt.show()" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4 Nonlinear solution\n", "\n", - "To obtain nonlinear impulse responses that capture the different scale and sign effects of shocks, we use `nonlinear.td_solve`. Similarly to `get_G` above, it takes in the steady state dict, the model blocks (in arbitrary order), the names of unknown endogenous variables and the names of target equations.\n", + "To obtain nonlinear impulse responses that capture the different scale and sign effects of shocks, we use the `solve_impulse_nonlinear` method of the `rbc_model`. Similarly to `solve_jacobian` above, it takes in the `SteadyStateDict` object, the names of unknown endogenous variables and the names of target equations.\n", "\n", - "However, the names of the exogenous variables would not be sufficient, since we're calculating the nonlinear response to a specific shock. Instead, `td_solve` takes the *sequences* for any exogenous variables that are shocked.\n", + "However, the names of the exogenous variables would not be sufficient information, since we're calculating the nonlinear response to a specific shock path. Instead, `solve_impulse_nonlinear` requires the full *sequences* for any exogenous variables that are shocked.\n", "\n", "So for the news shock above, we can just call: " ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -435,26 +391,23 @@ "output_type": "stream", "text": [ "On iteration 0\n", - " max error for goods_mkt is 7.86E-04\n", " max error for euler is 1.04E-02\n", + " max error for goods_mkt is 7.86E-04\n", "On iteration 1\n", - " max error for goods_mkt is 6.75E-05\n", " max error for euler is 6.68E-05\n", + " max error for goods_mkt is 6.75E-05\n", "On iteration 2\n", - " max error for goods_mkt is 1.27E-07\n", " max error for euler is 3.85E-07\n", + " max error for goods_mkt is 1.27E-07\n", "On iteration 3\n", - " max error for goods_mkt is 1.47E-09\n", - " max error for euler is 3.60E-09\n" + " max error for euler is 3.60E-09\n", + " max error for goods_mkt is 1.47E-09\n" ] } ], "source": [ - "td_nonlin = sj.td_solve(ss=ss, \n", - " block_list=[firm, household, mkt_clearing],\n", - " unknowns=['K', 'L'],\n", - " targets=['goods_mkt', 'euler'],\n", - " Z=ss['Z']+dZ[:, 1])" + "news_Z_shock_path = {\"Z\": dZ[:, 1]}\n", + "td_nonlin = rbc_model.solve_impulse_nonlinear(ss, news_shock_path, unknowns, targets)" ] }, { @@ -466,7 +419,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 61, "metadata": {}, "outputs": [ { @@ -483,7 +436,7 @@ } ], "source": [ - "dC_nonlin = 100 * (td_nonlin['C']/ss['C'] - 1)\n", + "dC_nonlin = 100 * td_nonlin.deviations().normalize()[\"C\"]\n", "\n", "plt.plot(dC[:50, 1], label='linear', linewidth=2.5)\n", "plt.plot(dC_nonlin[:50], label='nonlinear', linestyle='--', linewidth=2.5)\n", @@ -500,167 +453,6 @@ "source": [ "For those of you familiar with Dynare, these impulse responses are identical to what you could obtain by running the perfect foresight solver `simul`." ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Appendix" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A.1 Steady state with an analytical solution-augmented DAG\n", - "\n", - "In this alternative way of solving for the steady state we will make use of a new kind of block called a `HelperBlock`, whose purpose is to provide a more flexible way of using the sequence-jacobian toolkit to calibrate a model's steady state. \n", - "\n", - "A `HelperBlock` works identically to the `SimpleBlock`s that we constructed above using the decorator `@simple` for the end-user, but using the decorator `@helper` instead. Under the hood the sequence-jacobian toolkit handles them differently when the blocks are sorted/used outside of the steady state, like in the computation of the general equilibrium Jacobian which we will get to later. \n", - "\n", - "Recall in the first case, we wanted to set up the DAG to normalize $Y = 1$. Choosing $Z$ as an additional unknown and targeting $Y = 1$ turned out to be an easy fix, but in general there may be certain variables we would like to calibrate that are not so straightforward to work with. An alternate route to achieve this would be to use a `HelperBlock` to solve out a portion of the DAG analytically for the purposes of steady state calibration.\n", - "\n", - "In the case of the RBC model, given our choice of fixed $r = 0.01$, normalizing to $Y = 1$ lets us provide a *complete* analytical characterization of the steady state. In steps: we choose the discount rate $\\beta$ to hit a given real interest rate $r$, the disutility of labor $\\varphi$ to hit labor $L=1$, and normalize TFP $Z$ to get output $Y=1$." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "@helper\n", - "def steady_state_solution(r, eis, delta, alpha):\n", - " rk = r + delta\n", - " Z = (rk / alpha) ** alpha # normalize so that Y=1\n", - " K = (alpha * Z / rk) ** (1 / (1 - alpha))\n", - " Y = Z * K ** alpha\n", - " w = (1 - alpha) * Z * K ** alpha\n", - " I = delta * K\n", - " C = Y - I\n", - " beta = 1 / (1 + r)\n", - " vphi = w * C ** (-1 / eis)\n", - "\n", - " return Z, K, Y, w, I, C, beta, vphi" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Note 1**: Because the solution is entirely analytical it is not necessary to include any `unknown`s. Also, we can specify `solver=\"solved\"` to avoid using a root-finding algorithm. However, given the present structure of the code, we still require that you provide some target values to verify that the steady state given by the analytical solution indeed produces one that satisfies some set of target equations, like the euler equation, goods market clearing, or Walras' Law, as a gut-check to ensure we don't proceed forward with something we don't want!\n", - "\n", - "**Note 2**: If all of your targets are implicit functions, i.e. you want to target their values equal to 0, you can use a list of their names instead of a dict(ionary)." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "calibration = {\"L\": 1., \"r\": 0.01, \"eis\": 1., \"frisch\": 1., \"delta\": 0.025, \"alpha\": 0.11}\n", - "blocks = [household, firm, mkt_clearing, steady_state_solution]\n", - "unknowns_ss = {}\n", - "targets_ss = [\"euler\", \"goods_mkt\"]\n", - "ss_helper = sj.steady_state(blocks, calibration, unknowns_ss, targets_ss, solver=\"solved\")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'L': 1.0,\n", - " 'r': 0.009999999999999995,\n", - " 'eis': 1.0,\n", - " 'frisch': 1.0,\n", - " 'delta': 0.025,\n", - " 'alpha': 0.11,\n", - " 'Z': 0.8816460975214567,\n", - " 'K': 3.1428571428571432,\n", - " 'Y': 1.0,\n", - " 'w': 0.8900000000000001,\n", - " 'I': 0.07857142857142874,\n", - " 'C': 0.9214285714285713,\n", - " 'beta': 0.9900990099009901,\n", - " 'vphi': 0.9658914728682173,\n", - " 'goods_mkt': 0.0,\n", - " 'euler': 0.0,\n", - " 'walras': 0.0}" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ss_helper" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Gut Check**: To verify that this steady state delivers the same thing that we got from computing it along the standard DAG, we can use one of the developer tools provided in `sequence-jacobian`." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "L resid: 0.0\n", - "Z resid: 5.1535109513167754e-11\n", - "r resid: 9.327198735586961e-12\n", - "eis resid: 0.0\n", - "frisch resid: 0.0\n", - "delta resid: 0.0\n", - "alpha resid: 0.0\n", - "beta resid: 0.0\n", - "vphi resid: 5.6993409991434874e-11\n", - "K resid: 7.346452335355025e-10\n", - "w resid: 2.913902452661432e-11\n", - "Y resid: 3.274069904080079e-11\n", - "C resid: 2.4201751713803787e-11\n", - "I resid: 1.836619745176904e-11\n", - "goods_mkt resid: 7.530864820637362e-11\n", - "euler resid: 1.0022205287896213e-11\n", - "walras resid: 7.530831513946623e-11\n" - ] - } - ], - "source": [ - "import sequence_jacobian.utilities.devtools as dtools\n", - "\n", - "dtools.compare_steady_states(ss, ss_helper)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It checks out!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Steady state in the general case\n", - "\n", - "In practice, your own steady state workflow will likely be somewhere in between the two cases previously described. To compute the steady state it may be easy to analytically solve some portions of the model, but other portions may only be numerically computable, specified as a set of unknowns and targets within a DAG. This may also be desirable for performance reasons, since typically computing a portion of the DAG analytically is less costly than providing the root-finding algorithm with an additional dimension to solve.\n", - "\n", - "Thankfully, any combination of the above two methods is permissible in the `sequence-jacobian` toolkit. You need only provide the analytical solution component specific to the steady state as a `HelperBlock` in the standard list of blocks, specify your unknowns and targets, and call `steady_state`! You don't even need to swap out the `HelperBlock` from the list of blocks when computing general equilibrium Jacobians or computing non-linear transition dynamics." - ] } ], "metadata": { diff --git a/notebooks/two_asset.ipynb b/notebooks/two_asset.ipynb index 4dae356..295310c 100644 --- a/notebooks/two_asset.ipynb +++ b/notebooks/two_asset.ipynb @@ -56,8 +56,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", - "import sequence_jacobian as sj\n", - "from sequence_jacobian import simple, het, solved" + "from sequence_jacobian import simple, het, solved, create_model\n", + "from sequence_jacobian.models import two_asset" ] }, { @@ -66,105 +66,66 @@ "source": [ "## 1 Calibrate steady state\n", "\n", - "We developed an efficient backward iteration function to solve the Bellman equation in (1). Although we view this as a contribution on its own, discussing the algorithm goes beyond the scope of this notebook. If you are interested in how we solve a two-asset model with convex portfolio-adjustment costs in discrete time, please see appendix B of the paper for a detailed description and `two_asset.py` for the implementation." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "ss = sj.two_asset.two_asset_ss(verbose=False)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2 Define simple blocks\n", + "We developed an efficient backward iteration function to solve the Bellman equation in (1). Although we view this as a contribution on its own, discussing the algorithm goes beyond the scope of this notebook. If you are interested in how we solve a two-asset model with convex portfolio-adjustment costs in discrete time, please see appendix B of the paper for a detailed description and `two_asset.py` for the implementation.\n", "\n", - "Compare these to the equations in appendix A.3 of the paper." + "To solve for the steady state, we will use the blocks we have set up in the `two_asset.py` module located in `sequence_jacobian/models`, so we will omit the step-by-step discussion of the steady state, since this procedure is repeated and discussed in the other model notebooks." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "@simple\n", - "def dividend(Y, w, N, K, pi, mup, kappap, delta):\n", - " psip = mup / (mup - 1) / 2 / kappap * (1 + pi).apply(np.log) ** 2 * Y\n", - " I = K - (1 - delta) * K(-1)\n", - " div = Y - w * N - I - psip\n", - " return psip, I, div\n", + "blocks = [two_asset.household, two_asset.make_grids,\n", + " two_asset.pricing_solved, two_asset.arbitrage_solved, two_asset.production_solved,\n", + " two_asset.dividend, two_asset.taylor, two_asset.fiscal, two_asset.share_value,\n", + " two_asset.finance, two_asset.wage, two_asset.union, two_asset.mkt_clearing]\n", + "two_asset_model = create_model(blocks, name=\"Two-Asset HANK\")\n", "\n", - "@simple\n", - "def taylor(rstar, pi, phi):\n", - " i = rstar + phi * pi\n", - " return i\n", - "\n", - "@simple\n", - "def fiscal(r, w, N, G, Bg):\n", - " tax = (r * Bg + G) / w / N\n", - " return tax\n", + "helper_blocks = [two_asset.partial_ss_step1, two_asset.partial_ss_step2]\n", "\n", - "@simple\n", - "def finance(i, p, pi, r, div, omega, pshare):\n", - " rb = r - omega\n", - " ra = pshare * (div + p) / p(-1) + (1-pshare) * (1 + r) - 1\n", - " fisher = 1 + i(-1) - (1 + r) * (1 + pi)\n", - " return rb, ra, fisher\n", - "\n", - "@simple\n", - "def wage(pi, w, N, muw, kappaw):\n", - " piw = (1 + pi) * w / w(-1) - 1\n", - " psiw = muw / (1 - muw) / 2 / kappaw * (1 + piw).apply(np.log) ** 2 * N\n", - " return piw, psiw\n", - "\n", - "@simple\n", - "def union(piw, N, tax, w, U, kappaw, muw, vphi, frisch, beta):\n", - " wnkpc = kappaw * (vphi * N**(1+1/frisch) - muw*(1-tax)*w*N*U) + beta *\\\n", - " (1 + piw(+1)).apply(np.log) - (1 + piw).apply(np.log)\n", - " return wnkpc\n", - "\n", - "@simple\n", - "def mkt_clearing(p, A, B, Bg):\n", - " asset_mkt = p + Bg - B - A\n", - " return asset_mkt" + "calibration = {\"Y\": 1., \"r\": 0.0125, \"rstar\": 0.0125, \"tot_wealth\": 14, \"delta\": 0.02, \"kappap\": 0.1, \"muw\": 1.1,\n", + " \"Bh\": 1.04, \"Bg\": 2.8, \"G\": 0.2, \"eis\": 0.5, \"frisch\": 1, \"chi0\": 0.25, \"chi2\": 2, 'psip': 0.0,\n", + " \"epsI\": 4, \"omega\": 0.005, \"kappaw\": 0.1, \"phi\": 1.5, \"nZ\": 3, \"nB\": 50, \"nA\": 70,\n", + " \"nK\": 50, \"bmax\": 50, \"amax\": 4000, \"kmax\": 1, \"rho_z\": 0.966, \"sigma_z\": 0.92}\n", + "unknowns_ss = {\"beta\": 0.976, \"chi1\": 6.5, \"vphi\": 1.71, \"Z\": 0.4678, \"alpha\": 0.3299, \"mup\": 1.015, 'w': 0.66}\n", + "targets_ss = {\"asset_mkt\": 0., \"B\": \"Bh\", 'wnkpc': 0., 'piw': 0.0, \"K\": 10., \"wealth\": \"tot_wealth\", \"N\": 1.0}\n", + "ss = two_asset_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver=\"hybr\",\n", + " helper_blocks=helper_blocks,\n", + " helper_targets=[\"wnkpc\", \"piw\", \"K\", \"wealth\", \"N\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3 Define solved blocks\n", + "## 2 Define solved blocks\n", "\n", - "Solved blocks are mini SHADE models embedded as blocks inside larger SHADE models. Like simple blocks, solved blocks correspond to aggregate equilibrium conditions: they map sequences of aggregate inputs directly into sequences of aggregate outputs. The difference is that in the case of simple blocks, this mapping has to be analytical, while solved blocks are designed to accommodate implicit relationships that can only be evaluated numerically. \n", + "Solved blocks are miniature models embedded as blocks inside of our larger model. Like simple blocks, solved blocks correspond to aggregate equilibrium conditions: they map sequences of aggregate inputs directly into sequences of aggregate outputs. The difference is that in the case of simple blocks, this mapping has to be analytical, while solved blocks are designed to accommodate implicit relationships that can only be evaluated numerically. \n", "\n", "Such implicit mappings between variables become more common as macro complexity increases. Solved blocks are a valuable tool to simplify the DAG of large macro models.\n", "\n", - "### 3.1 Price setting \n", + "### 2.1 Price setting \n", "The Phillips curve characterizes $(\\pi)$ conditional on $(Y, mc, r):$ \n", "\n", "$$\n", "\\log(1+\\pi_t) = \\kappa_p \\left(mc_t - \\frac{1}{\\mu_p} \\right) + \\frac{1}{1+r_{t+1}} \\frac{Y_{t+1}}{Y_t} \\log(1+\\pi_{t+1})\n", "$$\n", "\n", - "Inflation shows up with two different time displacements, and so we could not express it analytically. Instead, we write a function that returns the residual of the equation, and use the decorator `@solved` to make it into a SolvedBlock." + "Inflation shows up with two different time displacements, and so we could not express it analytically. Instead, we write a function that returns the residual of the equation, and use the decorator `@solved` to make it into a `SolvedBlock`." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "scrolled": false }, "outputs": [], "source": [ "@solved(unknowns={'pi': (-0.1, 0.1)}, targets=['nkpc'], solver=\"brentq\")\n", - "def pricing(pi, mc, r, Y, kappap, mup):\n", + "def pricing_solved(pi, mc, r, Y, kappap, mup):\n", " nkpc = kappap * (mc - 1/mup) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) / \\\n", " (1 + r(+1)) - (1 + pi).apply(np.log)\n", " return nkpc" @@ -174,14 +135,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When our routines encounter a solved block in `block_list`, they compute its Jacobian via the the implicit function theorem, as if it was a SHADE model on its own. Given the Jacobian, the rest of the code applies without modification. " + "When our routines encounter a solved block in `blocks`, they compute its Jacobian via the the implicit function theorem, as if it was a SHADE model on its own. Given the Jacobian, the rest of the code applies without modification. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.2 Equity price\n", + "### 2.2 Equity price\n", "The no arbitrage condition characterizes $(p)$ conditional on $(d, p, r).$\n", "\n", "$$\n", @@ -191,14 +152,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "scrolled": true }, "outputs": [], "source": [ "@solved(unknowns={'p': (10, 15)}, targets=['equity'], solver=\"brentq\")\n", - "def arbitrage(div, p, r):\n", + "def arbitrage_solved(div, p, r):\n", " equity = div(+1) + p(+1) - p * (1 + r(+1))\n", " return equity" ] @@ -207,7 +168,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.3 Investment with adjustment costs\n", + "### 2.3 Investment with adjustment costs\n", "\n", "Sometimes multiple equilibrium conditions can be combined in a self-contained solved block. Investment subject to capital adjustment costs is such a case. In particular, we can use the following four equations to solve for $(K, Q)$ conditional on $(Y, w, r)$.\n", " \n", @@ -240,12 +201,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Solved blocks that contain multiple simple blocks have to be initialized with the `solved_block.solved` function instead of the decorator `@solved`." + "Solved blocks that contain multiple simple blocks have to be initialized with the `solved` function instead of the decorator `@solved`." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -281,67 +242,24 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 4 Determinacy check\n", + "## 3 Dynamics\n", "\n", - "Let's start by defining the inputs common across our convenience functions. Since computing the Jacobian of this two-asset HA block is somewhat costly, it's a good idea to save it for subsequent use." + "As before, we can compute $G$ and calculate impulse responses. To speed up the computation of $G$, we can reuse the pre-computed Jacobian of the household so it is not redundantly computed." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ + "exogenous = [\"rstar\", \"Z\", \"G\"]\n", + "unknowns = [\"r\", \"w\", \"Y\"]\n", + "targets = [\"asset_mkt\", \"fisher\", \"wnkpc\"]\n", "T = 300\n", - "block_list = [sj.two_asset.household, pricing, arbitrage, production, \n", - " dividend, taylor, fiscal, finance, wage, union, mkt_clearing]\n", - "exogenous = ['rstar', 'Z', 'G']\n", - "unknowns = ['r', 'w', 'Y']\n", - "targets = ['asset_mkt', 'fisher', 'wnkpc']" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We are ready to apply the winding number criterion. Recall that a winding number of 0 indicates that the model has a unique solution around the steady state, while a winding number of -1 or less indicates indeterminacy." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Winding number: 0\n" - ] - } - ], - "source": [ - "A = sj.get_H_U(block_list, unknowns, targets, T, ss, asymptotic=True, save=True)\n", - "wn = sj.determinacy.winding_criterion(A)\n", - "print(f'Winding number: {wn}')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Linearized dynamics\n", "\n", - "Computing $G$ is fast using the saved HA Jacobian." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "G = sj.get_G(block_list, exogenous, unknowns, targets, T=T, ss=ss, use_saved=True)" + "J_ha = two_asset.household.jacobian(ss=ss, T=T, exogenous=['N', 'r', 'ra', 'rb', 'tax', 'w'])\n", + "G = two_asset_model.solve_jacobian(ss, exogenous, unknowns, targets, T=T, Js={'household': J_ha})" ] }, { @@ -353,14 +271,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": { "scrolled": true }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -394,7 +312,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": { "scrolled": true }, @@ -406,33 +324,33 @@ "On iteration 0\n", " max error for asset_mkt is 4.22E-06\n", " max error for fisher is 2.50E-03\n", - " max error for wnkpc is 6.15E-08\n", + " max error for wnkpc is 5.08E-08\n", "On iteration 1\n", - " max error for asset_mkt is 2.79E-04\n", - " max error for fisher is 1.49E-06\n", - " max error for wnkpc is 2.61E-05\n", + " max error for asset_mkt is 2.66E-04\n", + " max error for fisher is 1.56E-06\n", + " max error for wnkpc is 2.15E-05\n", "On iteration 2\n", - " max error for asset_mkt is 8.78E-06\n", - " max error for fisher is 9.99E-08\n", - " max error for wnkpc is 7.67E-07\n", + " max error for asset_mkt is 7.57E-06\n", + " max error for fisher is 9.69E-08\n", + " max error for wnkpc is 6.57E-07\n", "On iteration 3\n", - " max error for asset_mkt is 5.15E-07\n", - " max error for fisher is 2.62E-09\n", - " max error for wnkpc is 2.08E-08\n", + " max error for asset_mkt is 4.02E-07\n", + " max error for fisher is 2.25E-09\n", + " max error for wnkpc is 1.64E-08\n", "On iteration 4\n", - " max error for asset_mkt is 3.12E-08\n", - " max error for fisher is 1.39E-10\n", - " max error for wnkpc is 1.03E-09\n", + " max error for asset_mkt is 2.20E-08\n", + " max error for fisher is 1.07E-10\n", + " max error for wnkpc is 7.47E-10\n", "On iteration 5\n", - " max error for asset_mkt is 1.92E-09\n", - " max error for fisher is 7.90E-12\n", - " max error for wnkpc is 5.64E-11\n" + " max error for asset_mkt is 1.23E-09\n", + " max error for fisher is 5.47E-12\n", + " max error for wnkpc is 3.73E-11\n" ] } ], "source": [ - "td_nonlin = sj.td_solve(ss, block_list, unknowns, targets,\n", - " rstar=ss['r']+drstar[:,2], use_saved=True)" + "td_nonlin = two_asset_model.solve_impulse_nonlinear(ss, {\"rstar\": drstar[:, 2]},\n", + " unknowns, targets, Js={'household': J_ha})" ] }, { @@ -444,14 +362,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -463,7 +381,7 @@ } ], "source": [ - "dY_nonlin = 100 * (td_nonlin['Y'] - 1) \n", + "dY_nonlin = 100 * td_nonlin.deviations()[\"Y\"]\n", "\n", "plt.plot(dY[:21, 2], label='linear', linestyle='-', linewidth=2.5)\n", "plt.plot(dY_nonlin[:21], label='nonlinear', linestyle='--', linewidth=2.5)\n", @@ -483,7 +401,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -493,35 +411,35 @@ "On iteration 0\n", " max error for asset_mkt is 4.22E-06\n", " max error for fisher is 2.50E-04\n", - " max error for wnkpc is 6.15E-08\n", + " max error for wnkpc is 5.08E-08\n", "On iteration 1\n", - " max error for asset_mkt is 3.31E-06\n", - " max error for fisher is 1.58E-08\n", - " max error for wnkpc is 2.56E-06\n", + " max error for asset_mkt is 3.20E-06\n", + " max error for fisher is 1.71E-08\n", + " max error for wnkpc is 2.10E-06\n", "On iteration 2\n", - " max error for asset_mkt is 8.70E-08\n", - " max error for fisher is 2.67E-10\n", - " max error for wnkpc is 7.56E-09\n", + " max error for asset_mkt is 7.70E-08\n", + " max error for fisher is 2.57E-10\n", + " max error for wnkpc is 5.83E-09\n", "On iteration 3\n", - " max error for asset_mkt is 9.90E-10\n", - " max error for fisher is 1.78E-12\n", - " max error for wnkpc is 7.87E-11\n" + " max error for asset_mkt is 8.06E-10\n", + " max error for fisher is 1.54E-12\n", + " max error for wnkpc is 5.69E-11\n" ] } ], "source": [ - "td_nonlin = sj.td_solve(ss, block_list, unknowns, targets,\n", - " rstar=ss['r']+0.1*drstar[:,2], use_saved=True)" + "td_nonlin = two_asset_model.solve_impulse_nonlinear(ss, {\"rstar\": 0.1 * drstar[:, 2]},\n", + " unknowns, targets, Js={'household': J_ha})" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -533,7 +451,7 @@ } ], "source": [ - "dY_nonlin = 100 * (td_nonlin['Y'] - 1) \n", + "dY_nonlin = 100 * td_nonlin.deviations()[\"Y\"]\n", "\n", "plt.plot(0.1*dY[:21, 2], label='linear', linestyle='-', linewidth=2.5)\n", "plt.plot(dY_nonlin[:21], label='nonlinear', linestyle='--', linewidth=2.5)\n", @@ -568,7 +486,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.8.3" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 933f4b5..1f49b43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ -numpy -scipy -numba +numpy>=1.18 +scipy>=1.5 +numba>=0.50 +xarray>=0.17 diff --git a/sequence_jacobian/__init__.py b/sequence_jacobian/__init__.py deleted file mode 100644 index dfaea3d..0000000 --- a/sequence_jacobian/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Public-facing objects.""" - -from . import asymptotic, determinacy, estimation, jacobian, nonlinear, utilities - -from .models import rbc, krusell_smith, hank, two_asset - -from .blocks.simple_block import simple -from .blocks.het_block import het, hetoutput -from .blocks.helper_block import helper -from .blocks.solved_block import solved -from .blocks.combined_block import combine -from .blocks.support.simple_displacement import apply_function - -from .visualization.draw_dag import draw_dag, draw_solved, inspect_solved - -from .steady_state import steady_state -from .jacobian import get_G, get_H_U, get_impulse -from .nonlinear import td_solve -from .utilities import discretize -from .utilities import interpolate -from .utilities.discretize import agrid -from .utilities.optimized_routines import setmin diff --git a/sequence_jacobian/asymptotic.py b/sequence_jacobian/asymptotic.py deleted file mode 100644 index a42a8df..0000000 --- a/sequence_jacobian/asymptotic.py +++ /dev/null @@ -1,208 +0,0 @@ -"""Classes and methods for calculating asymptotic Jacobians""" - -import numpy as np -from numpy.fft import rfft, rfftn, irfft, irfftn - -from . import jacobian as jac -from . import determinacy - - -class AsymptoticTimeInvariant: - """Represents the asymptotic behavior of infinite matrix that is asymptotically time invariant, - given by vector v of -(tau-1), ... , 0, ..., tau-1 asymptotic column entries around main diagonal. - - Conveniently overloads matrix multiplication operator @, addition operator +, etc., so that we - can use the same code on these as for ordinary matrices: if A and B are of the ATI class, - then A @ B is also of the ATI class and gives the asymptotic columns around diagonal of the - product of matrices whose asymptotic columns are given respectively by A and B.""" - - # give higher priority than simple_block.SimpleSparse, which when mixed with ATI is converted - # to it using .asymptotic_time_invariant property and then handled by methods in this class - __array_priority__ = 2000 - - def __init__(self, v): - self.v = v - - # v should be -(tau-1), ... , 0, ..., tau-1 asymp column around main diagonal - self.tau = (len(v)+1) // 2 - assert self.tau*2 - 1 == len(v), f'{len(v)}' - - @property - def vfft(self): - """FFT of v padded on the right with 2*tau-1 0s, used for multiplication below""" - # we could cache this, but so fast it isn't really necessary - # TODO: maybe it should be cached after all now that we don't need other stuff? - return rfft(self.v, 4*self.tau-3) - - def changetau(self, tau): - """Return new with lower or higher tau, trimming or padding with zeros as needed""" - if tau == self.tau: - return self - elif tau < self.tau: - return AsymptoticTimeInvariant(self.v[self.tau - tau: tau + self.tau - 1]) - else: - v = np.zeros(2*tau-1) - v[tau - self.tau: tau + self.tau - 1] = self.v - return AsymptoticTimeInvariant(v) - - def __getitem__(self, i): - """Get convenient slice of v, properly centered so -2 maps to entry v_(-2), etc.""" - if isinstance(i, slice): - return self.v[slice(i.start+self.tau-1, i.stop+self.tau-1, i.step)] - else: - return self.v[i+self.tau-1] - - @property - def T(self): - """Transpose""" - return AsymptoticTimeInvariant(self.v[::-1]) - - def __pos__(self): - return self - - def __neg__(self): - return AsymptoticTimeInvariant(-self.v) - - def __matmul__(self, other): - """If the vectors v and w represent the asymptotic diagonals of ATI matrices, their - the product of the matrices is ATI, with asymptotic diagonals represented by vector x - that is *convolution* of v and w: - - x[i] = sum_(j=-infty)^infty v[j]*w[i-j] - - If v and w both have nonzero elements with indices -(tau-1),...,(tau-1), then x[i] - will be nonzero for indices -(2*tau-2),...,(2*tau-2). - - We could obtain this full vector x using, e.g., np.convolve(v, w). - - When tau is large it is more efficient, however, to use the FFT: - irfft(rfft(v, 4*tau-3), rfft(w, 4*tau-3), 4*tau-3) is identical to np.convolve(v, w). - - By convention, to prevent exploding dimensionality, we then return the middle - -(tau-1), ..., (tau-1) elements of the convolution, dropping the extra (tau-1) on each side. - """ - if isinstance(other, AsymptoticTimeInvariant): - # make sure the two arguments have equal tau by enlarging the smaller - newself = self - if other.tau < self.tau: - other = other.changetau(self.tau) - elif other.tau > self.tau: - newself = self.changetau(other.tau) - - # convolve using FFT, then drop first and last (tau-1) entries - return AsymptoticTimeInvariant(irfft(newself.vfft*other.vfft, 4*newself.tau-3)[newself.tau-1:-(newself.tau-1)]) - elif hasattr(other, 'asymptotic_time_invariant'): - # if one of the arguments can be converted to ATI (for now, just SimpleSparse) - # do so and then take product - return self @ other.asymptotic_time_invariant - else: - return NotImplemented - - def __rmatmul__(self, other): - return self @ other - - def __add__(self, other): - if isinstance(other, AsymptoticTimeInvariant): - # make sure the two arguments have equal tau (same as matmul) - newself = self - if other.tau < self.tau: - other = other.changetau(self.tau) - elif other.tau > self.tau: - newself = self.changetau(other.tau) - - # now just add the corresponding vectors v - return AsymptoticTimeInvariant(newself.v + other.v) - elif hasattr(other, 'asymptotic_time_invariant'): - # convert non-ATI argument to ATI if possible (same as matmul) - return self + other.asymptotic_time_invariant - else: - return NotImplemented - - def __radd__(self, other): - return self + other - - def __sub__(self, other): - return self + (-other) - - def __rsub__(self, other): - return -self + other - - def __mul__(self, a): - if not np.isscalar(a): - return NotImplemented - return AsymptoticTimeInvariant(a*self.v) - - def __rmul__(self, a): - return self * a - - def __repr__(self): - return f'AsymptoticTimeInvariant({self.v!r})' - - def __eq__(self, other): - return np.array_equal(self.v, other.v) if isinstance(other, AsymptoticTimeInvariant) else False - - -def invert_jacdict(jacdict, unknowns, targets, tau, test_invertible=False): - """Given a nested dict of ATI Jacobians that maps unknowns -> targets, e.g. an asymptotic - H_U matrix, get the inverse H_U^(-1) as a nested dict. - - This is implemented by inverting the FFT-based multiplication that was implemented above - for ATI, making use of the linearity of the FFT: - - We take the FFT of each ATI Jacobian, padded out to 4*tau-3 as above - (This is done by first packing all Jacobians into a single array A) - - Then, we take the FFT of the identity, centered aroun d2*tau-1 since - we intend it to be the result of a product - - We solve frequency-by-frequency, i.e. for each of 4*tau-3 omegas we solve a k*k - linear system to get A_rfft[omega,...]^(-1)*id_rfft[omega,...] - - We take the inverse FFT of the results, then take only the first 2*tau-1 elements - to get (approximate) inverse Jacobians with times -(tau-1),...,(tau-1), same as - original Jacobians - - We unpack these to get a nested dict of ATI Jacobians that inverts original 'jacdict' - - Parameters - ---------- - jacdict : dict of dict, ATI (or convertible to ATI) Jacobians where jacdict[t][u] gives - asymptotic mapping from unknowns u to targets t in H_U - unknowns : list, names of unknowns in H_U - targets : list, names of targets in H_U - tau : int, convert all ATI Jacobians to size tau and provide inverse in size tau - test_invertible : [optional] bool, use winding number criterion to test whether we should - really be inverting this system (i.e. whether determinate solution) - - Returns - ------- - inv_jacdict : dict of dict, ATI Jacobians where inv_jacdict[u][t] gives asymptotic mapping - from targets t to unknowns u in H_U^(-1) - """ - - k = len(unknowns) - assert k == len(targets) - - # stack the k^2 Jacobians relating unknowns to targets into an A matrix - A = jac.pack_asymptotic_jacobians(jacdict, unknowns, targets, tau) - - if test_invertible: - # use winding number criterion to test invertibility - if determinacy.winding_criterion(A, N=4096) != 0: - raise ValueError('Trying to invert asymptotic time invariant system of Jacobians' + - ' but winding number test says that it is not uniquely invertible!') - - # take FFT of first dimension (time) of A (i.e. take FFT separtely of all k^2 Jacobians) - A_rfft = rfftn(A, s=(4*tau-3,), axes=(0,)) - - # take FFT of identity operator (for efficiency, reuse smaller calc) - id_vec_rfft = rfft(np.arange(4*tau-3)==(2*tau-2)) - id_rfft = np.zeros((2*tau-1, k, k), dtype=np.complex128) - for i in range(k): - id_rfft[:, i, i] = id_vec_rfft - - # now solve the linear system to invert A frequency-by-frequency - # (since frequency is leading dimension, np.linalg.solve automatically does this) - A_rfft_inv = np.linalg.solve(A_rfft, id_rfft) - - # take inverse FFT of this to get full A - # then take first 2*tau-1 entries to get approximate A from -(tau-1),...,0,...,(tau-1) - A_inv = irfftn(A_rfft_inv, s=(4*tau-3,), axes=(0,))[:2*tau-1, :, :] - - # unstack this - return jac.unpack_asymptotic_jacobians(A_inv, targets, unknowns, tau) diff --git a/sequence_jacobian/base.py b/sequence_jacobian/base.py deleted file mode 100644 index df40714..0000000 --- a/sequence_jacobian/base.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Type aliases, custom functions and errors for the base-level functionality of the package""" - -from typing import Any - -from .primitives import Block - - -# Useful type aliases -Array = Any -BlockArray = Array[Block] diff --git a/sequence_jacobian/blocks/combined_block.py b/sequence_jacobian/blocks/combined_block.py deleted file mode 100644 index 20fccfa..0000000 --- a/sequence_jacobian/blocks/combined_block.py +++ /dev/null @@ -1,39 +0,0 @@ -"""CombinedBlock class and the combine function to generate it""" - -from copy import deepcopy - -from .. import utilities as utils -from ..steady_state import eval_block_ss - - -def combine(*args): - # TODO: Implement a check that all args are child types of AbstractBlock, when that is properly implemented - return CombinedBlock(*args) - - -class CombinedBlock: - - def __init__(self, *args, name=None): - self.blocks = args - if name is not None: - self.name = name - - # Find all outputs (including those used as intermediary inputs) - self.outputs = set().union(*[block.outputs for block in self.blocks]) - - # Find all inputs that are *not* intermediary outputs - all_inputs = set().union(*[block.inputs for block in self.blocks]) - self.inputs = all_inputs.difference(self.outputs) - - self.blocks_sorted_indices = utils.graph.block_sort(self.blocks) - - def ss(self, **kwargs): - ss_values = deepcopy(kwargs) - for i in self.blocks_sorted_indices: - ss_values.update(eval_block_ss(self.blocks[i], ss_values)) - return ss_values - - # TODO: Define td method for CombinedBlock - def td(self, ss, **kwargs): - pass - diff --git a/sequence_jacobian/blocks/helper_block.py b/sequence_jacobian/blocks/helper_block.py deleted file mode 100644 index bf548db..0000000 --- a/sequence_jacobian/blocks/helper_block.py +++ /dev/null @@ -1,30 +0,0 @@ -"""HelperBlock class and @helper decorator to generate it""" - -from .. import utilities as utils - - -def helper(f): - return HelperBlock(f) - - -class HelperBlock: - """ A block for providing pre-computed solutions in lieu of solving for variables within the DAG. - Key methods are .ss, .td, and .jac, like HetBlock. - """ - - def __init__(self, f): - self.f = f - self.input_list = utils.misc.input_list(f) - self.output_list = utils.misc.output_list(f) - self.inputs = set(self.input_list) - self.outputs = set(self.output_list) - - def __repr__(self): - return f"" - - # Currently does not use any of the machinery in SimpleBlock to deal with time displacements and hence - # can handle non-scalar inputs. - def ss(self, *args, **kwargs): - args = [x for x in args] - kwargs = {k: v for k, v in kwargs.items()} - return self.f(*args, **kwargs) diff --git a/sequence_jacobian/blocks/het_block.py b/sequence_jacobian/blocks/het_block.py deleted file mode 100644 index ff99cc5..0000000 --- a/sequence_jacobian/blocks/het_block.py +++ /dev/null @@ -1,980 +0,0 @@ -import numpy as np -import copy - -from .. import utilities as utils -from .. import asymptotic - - -def het(exogenous, policy, backward, backward_init=None): - def decorator(back_step_fun): - return HetBlock(back_step_fun, exogenous, policy, backward, backward_init=backward_init) - return decorator - - -class HetBlock: - """Part 1: Initializer for HetBlock, intended to be called via @het() decorator on backward step function. - - IMPORTANT: All `policy` and non-aggregate output variables of this HetBlock need to be *lower-case*, since - the methods that compute steady state, transitional dynamics, and Jacobians for HetBlocks automatically handle - aggregation of non-aggregate outputs across the distribution and return aggregates as upper-case equivalents - of the `policy` and non-aggregate output variables specified in the backward step function. - """ - - def __init__(self, back_step_fun, exogenous, policy, backward, backward_init=None): - """Construct HetBlock from backward iteration function. - - Parameters - ---------- - back_step_fun : function - backward iteration function - exogenous : str - name of Markov transition matrix for exogenous variable - (now only single allowed for simplicity; use Kronecker product for more) - policy : str or sequence of str - names of policy variables of endogenous, continuous state variables - e.g. assets 'a', must be returned by function - backward : str or sequence of str - variables that together comprise the 'v' that we use for iterating backward - must appear both as outputs and as arguments - - It is assumed that every output of the function (except possibly backward), including policy, - will be on a grid of dimension 1 + len(policy), where the first dimension is the exogenous - variable and then the remaining dimensions are each of the continuous policy variables, in the - same order they are listed in 'policy'. - - The Markov transition matrix between the current and future period and backward iteration - variables should appear in the backward iteration function with '_p' subscripts ("prime") to - indicate that they come from the next period. - - Currently, we only support up to two policy variables. - """ - - # self.back_step_fun is one iteration of the backward step function pertaining to a given HetBlock. - # i.e. the function pertaining to equation (14) in the paper: v_t = curlyV(v_{t+1}, X_t) - self.back_step_fun = back_step_fun - - # self.back_step_outputs and self.back_step_inputs are all of the output and input arguments of - # self.back_step_fun, the variables used in the backward iteration, - # which generally include value and/or policy functions. - self.back_step_output_list = utils.misc.output_list(back_step_fun) - self.back_step_outputs = set(self.back_step_output_list) - self.back_step_inputs = set(utils.misc.input_list(back_step_fun)) - - # See the docstring of HetBlock for details on the attributes directly below - self.exogenous = exogenous - self.policy, self.back_iter_vars = (utils.misc.make_tuple(x) for x in (policy, backward)) - - # self.inputs_to_be_primed indicates all variables that enter into self.back_step_fun whose name has "_p" - # (read as prime). Because it's the case that the initial dict of input arguments for self.back_step_fun - # contains the names of these variables that omit the "_p", we need to swap the key from the unprimed to - # the primed key name, such that self.back_step_fun will properly call those variables. - # e.g. the key "Va" will become "Va_p", associated to the same value. - self.inputs_to_be_primed = {self.exogenous} | set(self.back_iter_vars) - - # self.non_back_iter_outputs are all of the outputs from self.back_step_fun excluding the backward - # iteration variables themselves. - self.non_back_iter_outputs = self.back_step_outputs - set(self.back_iter_vars) - - # self.outputs and self.inputs are the *aggregate* outputs and inputs of this HetBlock, which are used - # in utils.graph.block_sort to topologically sort blocks along the DAG - # according to their aggregate outputs and inputs. - self.outputs = {o.capitalize() for o in self.non_back_iter_outputs} - self.inputs = self.back_step_inputs - {k + '_p' for k in self.back_iter_vars} - self.inputs.remove(exogenous + '_p') - self.inputs.add(exogenous) - - # A HetBlock can have heterogeneous inputs and heterogeneous outputs, henceforth `hetinput` and `hetoutput`. - # See docstring for methods `add_hetinput` and `add_hetoutput` for more details. - self.hetinput = None - self.hetinput_inputs = set() - self.hetinput_outputs = set() - self.hetinput_outputs_order = tuple() - - # start without a hetoutput - self.hetoutput = None - self.hetoutput_inputs = set() - self.hetoutput_outputs = set() - self.hetoutput_outputs_order = tuple() - - if len(self.policy) > 2: - raise ValueError(f"More than two endogenous policies in {back_step_fun.__name__}, not yet supported") - - # Checking that the various inputs/outputs attributes are correctly set - if self.exogenous + '_p' not in self.back_step_inputs: - raise ValueError(f"Markov matrix '{self.exogenous}_p' not included as argument in {back_step_fun.__name__}") - - for pol in self.policy: - if pol not in self.back_step_outputs: - raise ValueError(f"Policy '{pol}' not included as output in {back_step_fun.__name__}") - if pol[0].isupper(): - raise ValueError(f"Policy '{pol}' is uppercase in {back_step_fun.__name__}, which is not allowed") - - for back in self.back_iter_vars: - if back + '_p' not in self.back_step_inputs: - raise ValueError(f"Backward variable '{back}_p' not included as argument in {back_step_fun.__name__}") - - if back not in self.back_step_outputs: - raise ValueError(f"Backward variable '{back}' not included as output in {back_step_fun.__name__}") - - for out in self.non_back_iter_outputs: - if out[0].isupper(): - raise ValueError("Output '{out}' is uppercase in {back_step_fun.__name__}, which is not allowed") - - # Add the backward iteration initializer function (the initial guesses for self.back_iter_vars) - if backward_init is None: - # TODO: Think about implementing some "automated way" of providing - # an initial guess for the backward iteration. - self.backward_init = backward_init - else: - self.backward_init = backward_init - - # 'saved' arguments start empty - self.saved = {} - self.prelim_saved = {} - self.saved_shock_list = [] - self.saved_output_list = [] - - # note: should do more input checking to ensure certain choices not made: 'D' not input, etc. - - def __repr__(self): - """Nice string representation of HetBlock for printing to console""" - if self.hetinput is not None: - if self.hetoutput is not None: - return f"" - else: - return f"" - else: - return f"" - - '''Part 2: high-level routines, with first three called analogously to SimpleBlock counterparts - - ss : do backward and forward iteration until convergence to get complete steady state - - td : do backward and forward iteration up to T to compute dynamics given some shocks - - jac : compute jacobians of outputs with respect to shocked inputs, using fake news algorithm - - ajac : compute asymptotic columns of jacobians output by jac, also using fake news algorithm - - - add_hetinput : add a hetinput to the HetBlock that first processes inputs through function hetinput - - add_hetoutput: add a hetoutput to the HetBlock that is computed after the entire ss computation, or after - each backward iteration step in td - ''' - - def ss(self, backward_tol=1E-8, backward_maxit=5000, forward_tol=1E-10, forward_maxit=100_000, - hetoutput=False, **kwargs): - """Evaluate steady state HetBlock using keyword args for all inputs. Analog to SimpleBlock.ss. - - Parameters - ---------- - backward_tol : [optional] float - in backward iteration, max abs diff between policy in consecutive steps needed for convergence - backward_maxit : [optional] int - maximum number of backward iterations, if 'backward_tol' not reached by then, raise error - forward_tol : [optional] float - in forward iteration, max abs diff between dist in consecutive steps needed for convergence - forward_maxit : [optional] int - maximum number of forward iterations, if 'forward_tol' not reached by then, raise error - - kwargs : dict - The following inputs are required as keyword arguments, which show up in 'kwargs': - - The exogenous Markov matrix, e.g. Pi=... if self.exogenous=='Pi' - - A seed for each backward variable, e.g. Va=... and Vb=... if self.back_iter_vars==('Va','Vb') - - A grid for each policy variable, e.g. a_grid=... and b_grid=... if self.policy==('a','b') - - All other inputs to the backward iteration function self.back_step_fun, except _p added to - for self.exogenous and self.back_iter_vars, for which the method uses steady-state values. - If there is a self.hetinput, then we need the inputs to that, not to self.back_step_fun. - - Other inputs in 'kwargs' are optional: - - A seed for the distribution: D=... - - If no seed for the distribution is provided, a seed for the invariant distribution - of the Markov process, e.g. Pi_seed=... if self.exogenous=='Pi' - - Returns - ---------- - ss : dict, contains - - ss inputs of self.back_step_fun and (if present) self.hetinput - - ss outputs of self.back_step_fun - - ss distribution 'D' - - ss aggregates (in uppercase) for all outputs of self.back_step_fun except self.back_iter_vars - """ - - ss = copy.deepcopy(kwargs) - - # extract information from kwargs - Pi = kwargs[self.exogenous] - grid = {k: kwargs[k+'_grid'] for k in self.policy} - D_seed = kwargs.get('D', None) - pi_seed = kwargs.get(self.exogenous + '_seed', None) - - # run backward iteration - sspol = self.policy_ss(kwargs, tol=backward_tol, maxit=backward_maxit) - ss.update(sspol) - - # run forward iteration - D = self.dist_ss(Pi, sspol, grid, forward_tol, forward_maxit, D_seed, pi_seed) - ss.update({"D": D}) - - # aggregate all outputs other than backward variables on grid, capitalize - aggregates = {o.capitalize(): np.vdot(D, sspol[o]) for o in self.non_back_iter_outputs} - ss.update(aggregates) - - if hetoutput: - hetoutputs = self.hetoutput.evaluate(ss) - aggregate_hetoutputs = self.hetoutput.aggregate(hetoutputs, D, ss, mode="ss") - else: - hetoutputs = {} - aggregate_hetoutputs = {} - ss.update({**hetoutputs, **aggregate_hetoutputs}) - - # clear any previously saved Jacobian info for safety, since we're computing new SS - self.clear_saved() - - return ss - - def td(self, ss, monotonic=False, returnindividual=False, grid_paths=None, **kwargs): - """Evaluate transitional dynamics for HetBlock given dynamic paths for inputs in kwargs, - assuming that we start and end in steady state ss, and that all inputs not specified in - kwargs are constant at their ss values. Analog to SimpleBlock.td. - - CANNOT provide time-varying paths of grid or Markov transition matrix for now. - - Parameters - ---------- - ss : dict - all steady-state info, intended to be from .ss() - monotonic : [optional] bool - flag indicating date-t policies are monotonic in same date-(t-1) policies, allows us - to use faster interpolation routines, otherwise use slower robust to nonmonotonicity - returnindividual : [optional] bool - return distribution and full outputs on grid - grid_paths: [optional] dict of {str: array(T, Number of grid points)} - time-varying grids for policies - kwargs : dict of {str : array(T, ...)} - all time-varying inputs here, with first dimension being time - this must have same length T for all entries (all outputs will be calculated up to T) - - Returns - ---------- - td : dict - if returnindividual = False, time paths for aggregates (uppercase) for all outputs - of self.back_step_fun except self.back_iter_vars - if returnindividual = True, additionally time paths for distribution and for all outputs - of self.back_Step_fun on the full grid - """ - - # infer T from kwargs, check that all shocks have same length - shock_lengths = [x.shape[0] for x in kwargs.values()] - if shock_lengths[1:] != shock_lengths[:-1]: - raise ValueError('Not all shocks in kwargs are same length!') - T = shock_lengths[0] - - # copy from ss info - Pi_T = ss[self.exogenous].T.copy() - D = ss['D'] - - # construct grids for policy variables either from the steady state grid if the grid is meant to be - # non-time-varying or from the provided `grid_path` if the grid is meant to be time-varying. - grid = {} - use_ss_grid = {} - for k in self.policy: - if grid_paths is not None and k in grid_paths: - grid[k] = grid_paths[k] - use_ss_grid[k] = False - else: - grid[k] = ss[k+"_grid"] - use_ss_grid[k] = True - - # allocate empty arrays to store result, assume all like D - individual_paths = {k: np.empty((T,) + D.shape) for k in self.non_back_iter_outputs} - hetoutput_paths = {k: np.empty((T,) + D.shape) for k in self.hetoutput_outputs} - - # backward iteration - backdict = ss.copy() - for t in reversed(range(T)): - # be careful: if you include vars from self.back_iter_vars in kwargs, agents will use them! - backdict.update({k: v[t,...] for k, v in kwargs.items()}) - individual = {k: v for k, v in zip(self.back_step_output_list, - self.back_step_fun(**self.make_inputs(backdict)))} - backdict.update({k: individual[k] for k in self.back_iter_vars}) - - if self.hetoutput is not None: - hetoutput = self.hetoutput.evaluate(backdict) - for k in self.hetoutput_outputs: - hetoutput_paths[k][t, ...] = hetoutput[k] - - for k in self.non_back_iter_outputs: - individual_paths[k][t, ...] = individual[k] - - D_path = np.empty((T,) + D.shape) - D_path[0, ...] = D - for t in range(T-1): - # have to interpolate policy separately for each t to get sparse transition matrices - sspol_i = {} - sspol_pi = {} - for pol in self.policy: - if use_ss_grid[pol]: - grid_var = grid[pol] - else: - grid_var = grid[pol][t, ...] - if monotonic: - # TODO: change for two-asset case so assumption is monotonicity in own asset, not anything else - sspol_i[pol], sspol_pi[pol] = utils.interpolate.interpolate_coord(grid_var, - individual_paths[pol][t, ...]) - else: - sspol_i[pol], sspol_pi[pol] =\ - utils.interpolate.interpolate_coord_robust(grid_var, individual_paths[pol][t, ...]) - - # step forward - D_path[t+1, ...] = self.forward_step(D_path[t, ...], Pi_T, sspol_i, sspol_pi) - - # obtain aggregates of all outputs, made uppercase - aggregates = {o.capitalize(): utils.optimized_routines.fast_aggregate(D_path, individual_paths[o]) - for o in self.non_back_iter_outputs} - if self.hetoutput: - aggregate_hetoutputs = self.hetoutput.aggregate(hetoutput_paths, D_path, backdict, mode="td") - else: - aggregate_hetoutputs = {} - - # return either this, or also include distributional information - if returnindividual: - return {**aggregates, **aggregate_hetoutputs, **individual_paths, **hetoutput_paths, 'D': D_path} - else: - return {**aggregates, **aggregate_hetoutputs} - - def jac(self, ss, T, shock_list, output_list=None, h=1E-4, save=False, use_saved=False): - """Assemble nested dict of Jacobians of agg outputs vs. inputs, using fake news algorithm. - - Parameters - ---------- - ss : dict, - all steady-state info, intended to be from .ss() - T : [optional] int - number of time periods for T*T Jacobian - shock_list : list of str - names of input variables to differentiate wrt (main cost scales with # of inputs) - output_list : list of str - names of output variables to get derivatives of, if not provided assume all outputs of - self.back_step_fun except self.back_iter_vars - h : [optional] float - h for numerical differentiation of backward iteration - save : [optional] bool - store curlyYs, curlyDs, curlyPs, F, and J from calculation inside HetBlock itself - useful to avoid redundant work when evaluating .jac or .ajac again - use_saved : [optional] bool - use J stored inside HetBlock to calculate the Jacobian, raises error if not available - - Returns - ------- - J : dict of {str: dict of {str: array(T,T)}} - J[o][i] for output o and input i gives T*T Jacobian of o with respect to i - """ - # The default set of outputs are all outputs of the backward iteration function - # except for the backward iteration variables themselves - if output_list is None: - output_list = self.non_back_iter_outputs - - relevant_shocks = [i for i in self.back_step_inputs | self.hetinput_inputs if i in shock_list] - - # if we're supposed to use saved Jacobian, extract T-by-T submatrices for each (o,i) - if use_saved: - return utils.misc.extract_nested_dict(savedA=self.saved['J'], - keys1=[o.capitalize() for o in output_list], - keys2=relevant_shocks, shape=(T, T)) - - # step 0: preliminary processing of steady state - (ssin_dict, Pi, ssout_list, ss_for_hetinput, - sspol_i, sspol_pi, sspol_space) = self.jac_prelim(ss, save) - - # step 1 of fake news algorithm - # compute curlyY and curlyD (backward iteration) for each input i - curlyYs, curlyDs = {}, {} - for i in relevant_shocks: - curlyYs[i], curlyDs[i] = self.backward_iteration_fakenews(i, output_list, ssin_dict, ssout_list, - ss['D'], Pi.T.copy(), sspol_i, sspol_pi, sspol_space, T, h, - ss_for_hetinput) - - # step 2 of fake news algorithm - # compute prediction vectors curlyP (forward iteration) for each outcome o - curlyPs = {} - for o in output_list: - curlyPs[o] = self.forward_iteration_fakenews(ss[o], Pi, sspol_i, sspol_pi, T-1) - - # steps 3-4 of fake news algorithm - # make fake news matrix and Jacobian for each outcome-input pair - F = {o.capitalize(): {} for o in output_list} - J = {o.capitalize(): {} for o in output_list} - for o in output_list: - for i in relevant_shocks: - F[o.capitalize()][i] = HetBlock.build_F(curlyYs[i][o], curlyDs[i], curlyPs[o]) - J[o.capitalize()][i] = HetBlock.J_from_F(F[o.capitalize()][i]) - - if save: - self.saved_shock_list, self.saved_output_list = relevant_shocks, output_list - self.saved = {'curlyYs' : curlyYs, 'curlyDs' : curlyDs, 'curlyPs' : curlyPs, 'F': F, 'J': J} - - return J - - def ajac(self, ss, T, shock_list, output_list=None, h=1E-4, Tpost=None, save=False, use_saved=False): - """Like .jac, but outputs asymptotic columns of Jacobians as AsymptoticTimeInvariant objects - with nonzero entries -(T-1),...,(Tpost-1) representing asymptotic entries in diagonals, - measured relative to main diagonal. - - Does additional iteration on curlyPs as necessary to extend Tpost beyond T, since common case - is that curlyYs and curlyDs from backward iteration converge to zero much more quickly than - curlyPs from forward iteration.""" - - # default outputs are just all outputs of back it function except backward variables - if output_list is None: - output_list = self.non_back_iter_outputs - - relevant_shocks = [i for i in self.back_step_inputs | self.hetinput_inputs if i in shock_list] - - # if Tpost not provided, assume it is 2*T by default - if Tpost is None: - Tpost = 2*T - elif Tpost < T: - raise ValueError(f'must have Tpost={Tpost} less than T={T}') - - # saved last by ajac, directly extract - if use_saved and 'curlyYs' not in self.saved: - asympJ = {} - for o in output_list: - asympJ[o.capitalize()] = {} - for i in relevant_shocks: - asympJ[o.capitalize()][i] = asymptotic.AsymptoticTimeInvariant( - self.saved['asympJ'][o.capitalize()][i][-(Tpost-1): Tpost]) - return asympJ - - # was either saved last by jac or not saved at all, need to do more work! - - # step 0: preliminary processing of steady state - (ssin_dict, Pi, ssout_list, ss_for_hetinput, - sspol_i, sspol_pi, sspol_space) = self.jac_prelim(ss, save, use_saved) - - if use_saved and 'curlyYs' in self.saved: - # was saved by jac, first copy curlyYs, curlyDs, curlyPs - curlyYs = utils.misc.extract_nested_dict(savedA=self.saved['curlyYs'], - keys1=relevant_shocks, keys2=output_list, shape=(T,)) - curlyDs = utils.misc.extract_dict(savedA=self.saved['curlyDs'], keys=relevant_shocks, shape=(T,)) - curlyPs_old = utils.misc.extract_dict(savedA=self.saved['curlyPs'], keys=output_list, shape=(T - 1,)) - - # now need curlyPs that go to T+Tpost-1, not just T - curlyPs = {} - for o in output_list: - curlyP_extrarows = self.forward_iteration_fakenews(curlyPs_old[o][-1, ...], - Pi, sspol_i, sspol_pi, Tpost) - curlyPs[o] = np.concatenate((curlyPs_old[o][:-1, ...], curlyP_extrarows), axis=0) - else: - # was not saved at all, get curlyYs, curlyDs, curlyPs for ourselves - # step 1: compute curlyY and curlyD (backward iteration) for each input i (same as jac) - curlyYs, curlyDs = {}, {} - for i in relevant_shocks: - curlyYs[i], curlyDs[i] = self.backward_iteration_fakenews(i, output_list, - ssin_dict, ssout_list, ss['D'], Pi.T.copy(), sspol_i, - sspol_pi, sspol_space, T, h, ss_for_hetinput) - - # step 2: compute prediction vectors curlyP (forward iteration) for each outcome o - # here go to (T-1) + (Tpost-1) rather than (T-1) - curlyPs = {} - for o in output_list: - curlyPs[o] = self.forward_iteration_fakenews(ss[o], Pi, sspol_i, sspol_pi, T-1+Tpost-1) - - # steps 3-4: make fake news matrix and Jacobian for each outcome-input pair - J = {o.capitalize(): {} for o in output_list} - asympJ = {o.capitalize(): {} for o in output_list} - for o in output_list: - for i in relevant_shocks: - F = HetBlock.build_F(curlyYs[i][o], curlyDs[i], curlyPs[o]) - J[o.capitalize()][i] = HetBlock.J_from_F(F) - asympJ[o.capitalize()][i] = asymptotic.AsymptoticTimeInvariant( - np.concatenate((np.zeros(Tpost-T), J[o.capitalize()][i][:, -1]))) - - # if supposed to save, record J and asympJ for use by jac or ajac - if save: - self.saved_shock_list, self.saved_output_list = relevant_shocks, output_list - self.saved = {'J': J, 'asympJ': asympJ} - - return asympJ - - def add_hetinput(self, hetinput, overwrite=False, verbose=True): - """Add a hetinput to this HetBlock. Any call to self.back_step_fun will first process - inputs through the hetinput function. - - A `hetinput` is any non-scalar-valued input argument provided to the HetBlock's backward iteration function, - self.back_step_fun, which is of the same dimensions as the distribution of agents in the HetBlock over - the relevant idiosyncratic state variables, generally referred to as `D`. e.g. The one asset HANK model - example provided in the models directory of sequence_jacobian has a hetinput `T`, which is skill-specific - transfers. - """ - if self.hetinput is not None and overwrite is False: - raise ValueError('Trying to attach hetinput when one already exists!') - else: - if verbose: - if self.hetinput is not None and overwrite is True: - print(f"Overwriting current hetinput, {self.hetinput.__name__} with new hetinput," - f" {hetinput.__name__}!") - else: - print(f"Added hetinput {hetinput.__name__} to the {self.back_step_fun.__name__} HetBlock") - - self.hetinput = hetinput - self.hetinput_inputs = set(utils.misc.input_list(hetinput)) - self.hetinput_outputs = set(utils.misc.output_list(hetinput)) - self.hetinput_outputs_order = utils.misc.output_list(hetinput) - - # modify inputs to include hetinput's additional inputs, remove outputs - self.inputs |= self.hetinput_inputs - self.inputs -= self.hetinput_outputs - - def add_hetoutput(self, hetoutput, overwrite=False, verbose=True): - """Add a hetoutput to this HetBlock. Any call to self.back_step_fun will first process - inputs through the hetoutput function. - - A `hetoutput` is any *non-scalar-value* output that the user might desire to be calculated from - the output arguments of the HetBlock's backward iteration function. Importantly, as of now the `hetoutput` - cannot be a function of time displaced values of the HetBlock's outputs but rather must be able to - be calculated from the outputs statically. e.g. The two asset HANK model example provided in the models - directory of sequence_jacobian has a hetoutput, `chi`, the adjustment costs for any initial level of assets - `a`, to any new level of assets `a'`. - """ - if self.hetoutput is not None and overwrite is False: - raise ValueError('Trying to attach hetoutput when one already exists!') - else: - if verbose: - if self.hetoutput is not None and overwrite is True: - print(f"Overwriting current hetoutput, {self.hetoutput.name} with new hetoutput," - f" {hetoutput.name}!") - else: - print(f"Added hetoutput {hetoutput.name} to the {self.back_step_fun.__name__} HetBlock") - - self.hetoutput = hetoutput - self.hetoutput_inputs = set(hetoutput.input_list) - self.hetoutput_outputs = set(hetoutput.output_list) - self.hetoutput_outputs_order = hetoutput.output_list - - # Modify the HetBlock's inputs to include additional inputs required for computing both the hetoutput - # and aggregating the hetoutput, but do not include: - # 1) objects computed within the HetBlock's backward iteration that enter into the hetoutput computation - # 2) objects computed within hetoutput that enter into hetoutput's aggregation (self.hetoutput.outputs) - # 3) D, the cross-sectional distribution of agents, which is used in the hetoutput aggregation - # but is computed after the backward iteration - self.inputs |= (self.hetoutput_inputs - self.back_step_outputs - self.hetoutput_outputs - set("D")) - # Modify the HetBlock's outputs to include the aggregated hetoutputs - self.outputs |= set([o.capitalize() for o in self.hetoutput_outputs]) - - '''Part 3: components of ss(): - - policy_ss : backward iteration to get steady-state policies and other outcomes - - dist_ss : forward iteration to get steady-state distribution and compute aggregates - ''' - - def policy_ss(self, ssin, tol=1E-8, maxit=5000): - """Find steady-state policies and backward variables through backward iteration until convergence. - - Parameters - ---------- - ssin : dict - all steady-state inputs to back_step_fun, including seed values for backward variables - tol : [optional] float - max diff between consecutive iterations of policy variables needed for convergence - maxit : [optional] int - maximum number of iterations, if 'tol' not reached by then, raise error - - Returns - ---------- - sspol : dict - all steady-state outputs of backward iteration, combined with inputs to backward iteration - """ - - # find initial values for backward iteration and account for hetinputs - original_ssin = ssin - ssin = self.make_inputs(ssin) - - old = {} - for it in range(maxit): - try: - # run and store results of backward iteration, which come as tuple, in dict - sspol = {k: v for k, v in zip(self.back_step_output_list, self.back_step_fun(**ssin))} - except KeyError as e: - print(f'Missing input {e} to {self.back_step_fun.__name__}!') - raise - - # only check convergence every 10 iterations for efficiency - if it % 10 == 1 and all(utils.optimized_routines.within_tolerance(sspol[k], old[k], tol) - for k in self.policy): - break - - # update 'old' for comparison during next iteration, prepare 'ssin' as input for next iteration - old.update({k: sspol[k] for k in self.policy}) - ssin.update({k + '_p': sspol[k] for k in self.back_iter_vars}) - else: - raise ValueError(f'No convergence of policy functions after {maxit} backward iterations!') - - # want to record inputs in ssin, but remove _p, add in hetinput inputs if there - for k in self.inputs_to_be_primed: - ssin[k] = ssin[k + '_p'] - del ssin[k + '_p'] - if self.hetinput is not None: - for k in self.hetinput_inputs: - if k in original_ssin: - ssin[k] = original_ssin[k] - return {**ssin, **sspol} - - def dist_ss(self, Pi, sspol, grid, tol=1E-10, maxit=100_000, D_seed=None, pi_seed=None): - """Find steady-state distribution through forward iteration until convergence. - - Parameters - ---------- - Pi : array - steady-state Markov matrix for exogenous variable - sspol : dict - steady-state policies on grid for all policy variables in self.policy - grid : dict - grids for all policy variables in self.policy - tol : [optional] float - absolute tolerance for max diff between consecutive iterations for distribution - maxit : [optional] int - maximum number of iterations, if 'tol' not reached by then, raise error - D_seed : [optional] array - initial seed for overall distribution - pi_seed : [optional] array - initial seed for stationary dist of Pi, if no D_seed - - Returns - ---------- - D : array - steady-state distribution - """ - - # first obtain initial distribution D - if D_seed is None: - # compute stationary distribution for exogenous variable - pi = utils.discretize.stationary(Pi, pi_seed) - - # now initialize full distribution with this, assuming uniform distribution on endogenous vars - endogenous_dims = [grid[k].shape[0] for k in self.policy] - D = np.tile(pi, endogenous_dims[::-1] + [1]).T / np.prod(endogenous_dims) - else: - D = D_seed - - # obtain interpolated policy rule for each dimension of endogenous policy - sspol_i = {} - sspol_pi = {} - for pol in self.policy: - # use robust binary search-based method that only requires grids, not policies, to be monotonic - sspol_i[pol], sspol_pi[pol] = utils.interpolate.interpolate_coord_robust(grid[pol], sspol[pol]) - - # iterate until convergence by tol, or maxit - Pi_T = Pi.T.copy() - for it in range(maxit): - Dnew = self.forward_step(D, Pi_T, sspol_i, sspol_pi) - - # only check convergence every 10 iterations for efficiency - if it % 10 == 0 and utils.optimized_routines.within_tolerance(D, Dnew, tol): - break - D = Dnew - else: - raise ValueError(f'No convergence after {maxit} forward iterations!') - - return D - - '''Part 4: components of jac(), corresponding to *4 steps of fake news algorithm* in paper - - Step 1: backward_step_fakenews and backward_iteration_fakenews to get curlyYs and curlyDs - - Step 2: forward_iteration_fakenews to get curlyPs - - Step 3: build_F to get fake news matrix from curlyYs, curlyDs, curlyPs - - Step 4: J_from_F to get Jacobian from fake news matrix - ''' - - def backward_step_fakenews(self, din_dict, output_list, ssin_dict, ssout_list, - Dss, Pi_T, sspol_i, sspol_pi, sspol_space, h=1E-4): - # shock perturbs outputs - shocked_outputs = {k: v for k, v in zip(self.back_step_output_list, - utils.differentiate.numerical_diff(self.back_step_fun, - ssin_dict, din_dict, h, - ssout_list))} - curlyV = {k: shocked_outputs[k] for k in self.back_iter_vars} - - # which affects the distribution tomorrow - pol_pi_shock = {k: -shocked_outputs[k] / sspol_space[k] for k in self.policy} - - # Include an additional term to account for the effect of a deleveraging shock affecting the grid - if "delev_exante" in din_dict: - dx = np.zeros_like(sspol_pi["a"]) - dx[sspol_i["a"] == 0] = 1. - add_term = sspol_pi["a"] * dx / sspol_space["a"] - pol_pi_shock["a"] += add_term - - curlyD = self.forward_step_shock(Dss, Pi_T, sspol_i, sspol_pi, pol_pi_shock) - - # and the aggregate outcomes today - curlyY = {k: np.vdot(Dss, shocked_outputs[k]) for k in output_list} - - return curlyV, curlyD, curlyY - - def backward_iteration_fakenews(self, input_shocked, output_list, ssin_dict, ssout_list, Dss, Pi_T, - sspol_i, sspol_pi, sspol_space, T, h=1E-4, ss_for_hetinput=None): - """Iterate policy steps backward T times for a single shock.""" - # TODO: Might need to add a check for ss_for_hetinput if self.hetinput is not None - # since unless self.hetinput_inputs is exactly equal to input_shocked, calling - # self.hetinput() inside the symmetric differentiation function will throw an error. - # It's probably better/more informative to throw that error out here. - if self.hetinput is not None and input_shocked in self.hetinput_inputs: - # if input_shocked is an input to hetinput, take numerical diff to get response - din_dict = dict(zip(self.hetinput_outputs_order, - utils.differentiate.numerical_diff_symmetric(self.hetinput, - ss_for_hetinput, {input_shocked: 1}, h))) - else: - # otherwise, we just have that one shock - din_dict = {input_shocked: 1} - - # contemporaneous response to unit scalar shock - curlyV, curlyD, curlyY = self.backward_step_fakenews(din_dict, output_list, ssin_dict, ssout_list, - Dss, Pi_T, sspol_i, sspol_pi, sspol_space, h=h) - - # infer dimensions from this and initialize empty arrays - curlyDs = np.empty((T,) + curlyD.shape) - curlyYs = {k: np.empty(T) for k in curlyY.keys()} - - # fill in current effect of shock - curlyDs[0, ...] = curlyD - for k in curlyY.keys(): - curlyYs[k][0] = curlyY[k] - - # fill in anticipation effects - for t in range(1, T): - curlyV, curlyDs[t, ...], curlyY = self.backward_step_fakenews({k+'_p': v for k, v in curlyV.items()}, - output_list, ssin_dict, ssout_list, - Dss, Pi_T, sspol_i, sspol_pi, sspol_space, h) - for k in curlyY.keys(): - curlyYs[k][t] = curlyY[k] - - return curlyYs, curlyDs - - def forward_iteration_fakenews(self, o_ss, Pi, pol_i_ss, pol_pi_ss, T): - """Iterate transpose forward T steps to get full set of curlyPs for a given outcome. - - Note we depart from definition in paper by applying the demeaning operator in addition to Lambda - at each step. This does not affect products with curlyD (which are the only way curlyPs enter - Jacobian) since perturbations to distribution always have mean zero. It has numerical benefits - since curlyPs now go to zero for high t (used in paper in proof of Proposition 1). - """ - curlyPs = np.empty((T,) + o_ss.shape) - curlyPs[0, ...] = utils.misc.demean(o_ss) - for t in range(1, T): - curlyPs[t, ...] = utils.misc.demean(self.forward_step_transpose(curlyPs[t - 1, ...], - Pi, pol_i_ss, pol_pi_ss)) - return curlyPs - - @staticmethod - def build_F(curlyYs, curlyDs, curlyPs): - T = curlyDs.shape[0] - Tpost = curlyPs.shape[0] - T + 2 - F = np.empty((Tpost + T - 1, T)) - F[0, :] = curlyYs - F[1:, :] = curlyPs.reshape((Tpost + T - 2, -1)) @ curlyDs.reshape((T, -1)).T - return F - - @staticmethod - def J_from_F(F): - J = F.copy() - for t in range(1, J.shape[1]): - J[1:, t] += J[:-1, t - 1] - return J - - '''Part 5: helpers for .jac and .ajac: preliminary processing and clearing saved info''' - - def jac_prelim(self, ss, save=False, use_saved=False): - """Helper that does preliminary processing of steady state for fake news algorithm. - - Parameters - ---------- - ss : dict, all steady-state info, intended to be from .ss() - save : [optional] bool, whether to store results in .prelim_saved attribute - use_saved : [optional] bool, whether to use already-stored results in .prelim_saved - - Returns - ---------- - ssin_dict : dict, ss vals of exactly the inputs needed by self.back_step_fun for backward step - Pi : array (S*S), Markov matrix for exogenous state - ssout_list : tuple, what self.back_step_fun returns when given ssin_dict (not exactly the same - as steady-state numerically since SS convergence was to some tolerance threshold) - ss_for_hetinput : dict, ss vals of exactly the inputs needed by self.hetinput (if it exists) - sspol_i : dict, indices on lower bracketing gridpoint for all in self.policy - sspol_pi : dict, weights on lower bracketing gridpoint for all in self.policy - sspol_space : dict, space between lower and upper bracketing gridpoints for all in self.policy - """ - output_names = ('ssin_dict', 'Pi', 'ssout_list', 'ss_for_hetinput', 'sspol_i', 'sspol_pi', 'sspol_space') - - if use_saved: - if self.prelim_saved: - return tuple(self.prelim_saved[k] for k in output_names) - else: - raise ValueError('Nothing saved to be used by jac_prelim!') - - # preliminary a: obtain ss inputs and other info, run once to get baseline for numerical differentiation - ssin_dict = self.make_inputs(ss) - Pi = ss[self.exogenous] - grid = {k: ss[k+'_grid'] for k in self.policy} - ssout_list = self.back_step_fun(**ssin_dict) - - ss_for_hetinput = None - if self.hetinput is not None: - ss_for_hetinput = {k: ss[k] for k in self.hetinput_inputs if k in ss} - - # preliminary b: get sparse representations of policy rules, and distance between neighboring policy gridpoints - sspol_i = {} - sspol_pi = {} - sspol_space = {} - for pol in self.policy: - # use robust binary-search-based method that only requires grids to be monotonic - sspol_i[pol], sspol_pi[pol] = utils.interpolate.interpolate_coord_robust(grid[pol], ss[pol]) - sspol_space[pol] = grid[pol][sspol_i[pol]+1] - grid[pol][sspol_i[pol]] - - toreturn = (ssin_dict, Pi, ssout_list, ss_for_hetinput, sspol_i, sspol_pi, sspol_space) - if save: - self.prelim_saved = {k: v for (k, v) in zip(output_names, toreturn)} - - return toreturn - - def clear_saved(self): - """Erase any saved Jacobian information from .jac or .ajac (e.g. if steady state changes)""" - self.saved = {} - self.prelim_saved = {} - self.saved_shock_list = [] - self.saved_output_list = [] - - '''Part 6: helper to extract inputs and potentially process them through hetinput''' - - def make_inputs(self, back_step_inputs_dict): - """Extract from back_step_inputs_dict exactly the inputs needed for self.back_step_fun, - process stuff through self.hetinput first if it's there. - """ - input_dict = copy.deepcopy(back_step_inputs_dict) - - # If this HetBlock has a hetinput, then we need to compute the outputs of the hetinput first and include - # them as inputs for self.back_step_fun - if self.hetinput is not None: - outputs_as_tuple = utils.misc.make_tuple(self.hetinput(**{k: input_dict[k] - for k in self.hetinput_inputs if k in input_dict})) - input_dict.update(dict(zip(self.hetinput_outputs_order, outputs_as_tuple))) - - # Check if there are entries in indict corresponding to self.inputs_to_be_primed. - # In particular, we are interested in knowing if an initial value - # for the backward iteration variable has been provided. - # If it has not been provided, then use self.backward_init to calculate the initial values. - if not self.inputs_to_be_primed.issubset(set(input_dict.keys())): - initial_value_input_args = [input_dict[arg_name] for arg_name in utils.misc.input_list(self.backward_init)] - input_dict.update(zip(utils.misc.output_list(self.backward_init), - utils.misc.make_tuple(self.backward_init(*initial_value_input_args)))) - - for i_p in self.inputs_to_be_primed: - input_dict[i_p + "_p"] = input_dict[i_p] - del input_dict[i_p] - - try: - return {k: input_dict[k] for k in self.back_step_inputs if k in input_dict} - except KeyError as e: - print(f'Missing backward variable or Markov matrix {e} for {self.back_step_fun.__name__}!') - raise - - '''Part 7: routines to do forward steps of different kinds, all wrap functions in utils''' - - def forward_step(self, D, Pi_T, pol_i, pol_pi): - """Update distribution, calling on 1d and 2d-specific compiled routines. - - Parameters - ---------- - D : array, beginning-of-period distribution - Pi_T : array, transpose Markov matrix - pol_i : dict, indices on lower bracketing gridpoint for all in self.policy - pol_pi : dict, weights on lower bracketing gridpoint for all in self.policy - - Returns - ---------- - Dnew : array, beginning-of-next-period distribution - """ - if len(self.policy) == 1: - p, = self.policy - return utils.forward_step.forward_step_1d(D, Pi_T, pol_i[p], pol_pi[p]) - elif len(self.policy) == 2: - p1, p2 = self.policy - return utils.forward_step.forward_step_2d(D, Pi_T, pol_i[p1], pol_i[p2], pol_pi[p1], pol_pi[p2]) - else: - raise ValueError(f"{len(self.policy)} policy variables, only up to 2 implemented!") - - def forward_step_transpose(self, D, Pi, pol_i, pol_pi): - """Transpose of forward_step (note: this takes Pi rather than Pi_T as argument!)""" - if len(self.policy) == 1: - p, = self.policy - return utils.forward_step.forward_step_transpose_1d(D, Pi, pol_i[p], pol_pi[p]) - elif len(self.policy) == 2: - p1, p2 = self.policy - return utils.forward_step.forward_step_transpose_2d(D, Pi, pol_i[p1], pol_i[p2], pol_pi[p1], pol_pi[p2]) - else: - raise ValueError(f"{len(self.policy)} policy variables, only up to 2 implemented!") - - def forward_step_shock(self, Dss, Pi_T, pol_i_ss, pol_pi_ss, pol_pi_shock): - """Forward_step linearized with respect to pol_pi""" - if len(self.policy) == 1: - p, = self.policy - return utils.forward_step.forward_step_shock_1d(Dss, Pi_T, pol_i_ss[p], pol_pi_shock[p]) - elif len(self.policy) == 2: - p1, p2 = self.policy - return utils.forward_step.forward_step_shock_2d(Dss, Pi_T, pol_i_ss[p1], pol_i_ss[p2], - pol_pi_ss[p1], pol_pi_ss[p2], - pol_pi_shock[p1], pol_pi_shock[p2]) - else: - raise ValueError(f"{len(self.policy)} policy variables, only up to 2 implemented!") - - -def hetoutput(custom_aggregation=None): - def decorator(f): - return HetOutput(f, custom_aggregation=custom_aggregation) - return decorator - - -class HetOutput: - def __init__(self, f, custom_aggregation=None): - self.name = f.__name__ - self.f = f - self.eval_input_list = utils.misc.input_list(f) - - self.custom_aggregation = custom_aggregation - self.agg_input_list = [] if custom_aggregation is None else utils.misc.input_list(custom_aggregation) - - # We are distinguishing between the eval_input_list and agg_input_list because custom aggregation may require - # certain arguments that are not required for simply evaluating the hetoutput - self.input_list = list(set(self.eval_input_list).union(set(self.agg_input_list))) - self.output_list = utils.misc.output_list(f) - - def evaluate(self, arg_dict): - hetoutputs = dict(zip(self.output_list, utils.misc.make_tuple(self.f(*[arg_dict[i] for i - in self.eval_input_list])))) - return hetoutputs - - def aggregate(self, hetoutputs, D, custom_aggregation_args, mode="ss"): - if self.custom_aggregation is not None: - hetoutputs_w_std_aggregation = list(set(self.output_list) - - set([utils.misc.uncapitalize(o) for o - in utils.misc.output_list(self.custom_aggregation)])) - hetoutputs_w_custom_aggregation = list(set(self.output_list) - set(hetoutputs_w_std_aggregation)) - else: - hetoutputs_w_std_aggregation = self.output_list - hetoutputs_w_custom_aggregation = [] - - # TODO: May need to check if this works properly for td - if self.custom_aggregation is not None: - hetoutputs_w_custom_aggregation_args = dict(zip(hetoutputs_w_custom_aggregation, - [hetoutputs[i] for i in hetoutputs_w_custom_aggregation])) - custom_agg_inputs = {"D": D, **hetoutputs_w_custom_aggregation_args, **custom_aggregation_args} - custom_aggregates = dict(zip([o.capitalize() for o in hetoutputs_w_custom_aggregation], - utils.misc.make_tuple(self.custom_aggregation(*[custom_agg_inputs[i] for i - in self.agg_input_list])))) - else: - custom_aggregates = {} - - if mode == "ss": - std_aggregates = {o.capitalize(): np.vdot(D, hetoutputs[o]) for o in hetoutputs_w_std_aggregation} - elif mode == "td": - std_aggregates = {o.capitalize(): utils.optimized_routines.fast_aggregate(D, hetoutputs[o]) - for o in hetoutputs_w_std_aggregation} - else: - raise RuntimeError(f"Mode {mode} is not supported in HetOutput aggregation. Choose either 'ss' or 'td'") - - return {**std_aggregates, **custom_aggregates} diff --git a/sequence_jacobian/blocks/simple_block.py b/sequence_jacobian/blocks/simple_block.py deleted file mode 100644 index 138e516..0000000 --- a/sequence_jacobian/blocks/simple_block.py +++ /dev/null @@ -1,156 +0,0 @@ -import numpy as np - -from .. import utilities as utils -from .. import jacobian -from .support.simple_displacement import ignore, numeric_primitive, Displace, AccumulatedDerivative - -'''Part 1: SimpleBlock class and @simple decorator to generate it''' - - -def simple(f): - return SimpleBlock(f) - - -class SimpleBlock: - """Generated from simple block written in Dynare-ish style and decorated with @simple, e.g. - - @simple - def production(Z, K, L, alpha): - Y = Z * K(-1) ** alpha * L ** (1 - alpha) - return Y - - which is a SimpleBlock that takes in Z, K, L, and alpha, all of which can be either constants - or series, and implements a Cobb-Douglas production function, noting that for production today - we use the capital K(-1) determined yesterday. - - Key methods are .ss, .td, and .jac, like HetBlock. - """ - - def __init__(self, f): - self.f = f - self.input_list = utils.misc.input_list(f) - self.output_list = utils.misc.output_list(f) - self.inputs = set(self.input_list) - self.outputs = set(self.output_list) - - def __repr__(self): - return f"" - - def _output_in_ss_format(self, *args, **kwargs): - """Returns output of the method ss as either a tuple of numeric primitives (scalars/vectors) or a single - numeric primitive, as opposed to Ignore/IgnoreVector objects""" - if len(self.output_list) > 1: - return tuple([numeric_primitive(o) for o in self.f(*args, **kwargs)]) - else: - return numeric_primitive(self.f(*args, **kwargs)) - - def ss(self, *args, **kwargs): - # Wrap args and kwargs in Ignore/IgnoreVector classes to be passed into the function "f" - args = [ignore(x) for x in args] - kwargs = {k: ignore(v) for k, v in kwargs.items()} - - return self._output_in_ss_format(*args, **kwargs) - - def _output_in_td_format(self, **kwargs_new): - """Returns output of the method td as a dict mapping output names to numeric primitives (scalars/vectors) - or a single numeric primitive of output values, as opposed to Ignore/IgnoreVector/Displace objects. - - Also accounts for the fact that for outputs of block.td that were *not* affected by a Displace object, i.e. - variables that remained at their ss value in spite of other variables within that same block being - affected by the Displace object (e.g. I in the mkt_clearing block of the two_asset model - is unchanged by a shock to rstar, being only a function of K's ss value and delta), - we still want to return them as paths (i.e. vectors, if they were - previously scalars) to impose uniformity on the dimensionality of the td returned values. - """ - out = self.f(**kwargs_new) - if len(self.output_list) > 1: - # Because we know at least one of the outputs in `out` must be of length T - T = np.max([np.size(o) for o in out]) - out_unif_dim = [np.full(T, numeric_primitive(o)) if np.isscalar(o) else numeric_primitive(o) for o in out] - return dict(zip(self.output_list, utils.misc.make_tuple(out_unif_dim))) - else: - return dict(zip(self.output_list, utils.misc.make_tuple(numeric_primitive(out)))) - - def td(self, ss, **kwargs): - kwargs_new = {} - for k, v in kwargs.items(): - if np.isscalar(v): - raise ValueError(f'Keyword argument {k}={v} is scalar, should be time path.') - kwargs_new[k] = Displace(v, ss=ss.get(k, None), name=k) - - for k in self.input_list: - if k not in kwargs_new: - kwargs_new[k] = ignore(ss[k]) - - return self._output_in_td_format(**kwargs_new) - - def jac(self, ss, T=None, shock_list=[]): - """Assemble nested dict of Jacobians - - Parameters - ---------- - ss : dict, - steady state values - T : int, optional - number of time periods for explicit T*T Jacobian - if omitted, more efficient SimpleSparse objects returned - shock_list : list of str, optional - names of input variables to differentiate wrt; if omitted, assume all inputs - h : float, optional - radius for symmetric numerical differentiation - - Returns - ------- - J : dict of {str: dict of {str: array(T,T)}} - J[o][i] for output o and input i gives Jacobian of o with respect to i - This Jacobian is a SimpleSparse object or, if T specific, a T*T matrix, omitted by convention - if zero - """ - - relevant_shocks = [i for i in self.inputs if i in shock_list] - - # If none of the shocks passed in shock_list are relevant to this block (i.e. none of the shocks - # are an input into the block), then return an empty dict - if not relevant_shocks: - return {} - else: - invertedJ = {shock_name: {} for shock_name in relevant_shocks} - - # Loop over all inputs/shocks which we want to differentiate with respect to - for shock in relevant_shocks: - invertedJ[shock] = compute_single_shock_curlyJ(self.f, ss, shock) - - # Because we computed the Jacobian of all outputs with respect to each shock (invertedJ[i][o]), - # we need to loop back through to have J[o][i] to map for a given output `o`, shock `i`, - # the Jacobian curlyJ^{o,i}. - J = {o: {} for o in self.output_list} - for o in self.output_list: - for i in relevant_shocks: - # Do not write an entry into J if shock `i` did not affect output `o` - if not invertedJ[i][o] or invertedJ[i][o].iszero: - continue - else: - if T is not None: - J[o][i] = invertedJ[i][o].nonzero().matrix(T) - else: - J[o][i] = invertedJ[i][o].nonzero() - - # If output `o` is entirely unaffected by all of the shocks passed in, then - # remove the empty Jacobian corresponding to `o` from J - if not J[o]: - del J[o] - - return J - - -def compute_single_shock_curlyJ(f, steady_state_dict, shock_name): - """Find the Jacobian of the function `f` with respect to a single shocked argument, `shock_name`""" - input_args = {i: ignore(steady_state_dict[i]) for i in utils.misc.input_list(f)} - input_args[shock_name] = AccumulatedDerivative(f_value=steady_state_dict[shock_name]) - - J = {o: {} for o in utils.misc.output_list(f)} - for o, o_name in zip(utils.misc.make_tuple(f(**input_args)), utils.misc.output_list(f)): - if isinstance(o, AccumulatedDerivative): - J[o_name] = jacobian.SimpleSparse(o.elements) - - return J diff --git a/sequence_jacobian/blocks/solved_block.py b/sequence_jacobian/blocks/solved_block.py deleted file mode 100644 index 09a0c49..0000000 --- a/sequence_jacobian/blocks/solved_block.py +++ /dev/null @@ -1,76 +0,0 @@ -from .. import nonlinear -from .. import jacobian as jac -from ..steady_state import steady_state -from ..blocks.simple_block import simple - - -def solved(unknowns, targets, block_list=[], solver=None, solver_kwargs={}): - """Creates SolvedBlocks. Can be applied in two ways, both of which return a SolvedBlock: - - as @solved(unknowns=..., targets=...) decorator on a single SimpleBlock - - as function solved(blocklist=..., unknowns=..., targets=...) where blocklist - can be any list of blocks - """ - - if block_list: - # ordinary call, not as decorator - return SolvedBlock(block_list, unknowns, targets, solver=solver, solver_kwargs=solver_kwargs) - else: - # call as decorator, return function of function - def singleton_solved_block(f): - return SolvedBlock([simple(f)], unknowns, targets, solver=solver, solver_kwargs=solver_kwargs) - return singleton_solved_block - - -class SolvedBlock: - """SolvedBlocks are mini SHADE models embedded as blocks inside larger SHADE models. - - When creating them, we need to provide the basic ingredients of a SHADE model: the list of - blocks comprising the model, the list on unknowns, and the list of targets. - - When we use .jac to ask for the Jacobian of a SolvedBlock, we are really solving for the 'G' - matrices of the mini SHADE models, which then become the 'curlyJ' Jacobians of the block. - - Similarly, when we use .td to evaluate a SolvedBlock on a path, we are really solving for the - nonlinear transition path such that all internal targets of the mini SHADE model are zero. - """ - - def __init__(self, block_list, unknowns, targets, solver=None, solver_kwargs={}): - self.block_list = block_list - self.unknowns = unknowns - self.targets = targets - self.solver = solver - self.solver_kwargs = solver_kwargs - - # need to have inputs and outputs!!! - self.outputs = (set.union(*(b.outputs for b in block_list)) | set(list(self.unknowns.keys()))) - set(self.targets) - self.inputs = set.union(*(b.inputs for b in block_list)) - self.outputs - - def ss(self, consistency_check=True, ttol=1e-9, ctol=1e-9, verbose=False, **calibration): - if self.solver is None: - raise RuntimeError("Cannot call the ss method on this SolvedBlock without specifying a solver.") - else: - return steady_state(self.block_list, calibration, self.unknowns, self.targets, - consistency_check=consistency_check, ttol=ttol, ctol=ctol, verbose=verbose, - solver=self.solver, **self.solver_kwargs) - - def td(self, ss, monotonic=False, returnindividual=False, verbose=False, **kwargs): - # TODO: add H_U_factored caching of some kind - # also, inefficient since we are repeatedly starting from the steady state, need option - # to provide a guess (not a big deal with just SimpleBlocks, of course) - return nonlinear.td_solve(ss, self.block_list, list(self.unknowns.keys()), self.targets, monotonic=monotonic, - returnindividual=returnindividual, verbose=verbose, **kwargs) - - def jac(self, ss, T, shock_list, output_list=None, save=False, use_saved=False): - relevant_shocks = [i for i in self.inputs if i in shock_list] - - # H_U_factored caching could be helpful here too - return jac.get_G(self.block_list, relevant_shocks, list(self.unknowns.keys()), self.targets, - T, ss, output_list, save=save, use_saved=use_saved) - - def ajac(self, ss, T, shock_list, output_list=None, save=False, use_saved=False, Tpost=None): - relevant_shocks = [i for i in self.inputs if i in shock_list] - - if Tpost is None: - Tpost = 2*T - return jac.get_G_asymptotic(self.block_list, relevant_shocks, list(self.unknowns.keys()), - self.targets, T, ss, output_list, save=save, use_saved=use_saved, Tpost=Tpost) diff --git a/sequence_jacobian/blocks/support/__init__.py b/sequence_jacobian/blocks/support/__init__.py deleted file mode 100644 index 51eaac3..0000000 --- a/sequence_jacobian/blocks/support/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Other classes and helpers to aid in the implementation of standard block functionality: .ss, .td, .jac""" diff --git a/sequence_jacobian/determinacy.py b/sequence_jacobian/determinacy.py deleted file mode 100644 index 39a8f8b..0000000 --- a/sequence_jacobian/determinacy.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Functions for implementing the winding criterion method for assessing model determinacy""" - -import numpy as np -from numpy.fft import rfftn -from numba import njit - - -def winding_criterion(A, N=4096): - """Build path of det A(lambda) and obtain its winding number, implementing winding number - criterion for determinacy that generalizes Onatski (2006). - - Parameters - ---------- - A : array ((2T-1)*k*k) - asymptotic H_U matrix, where A[t,i,j] gives Jacobian of target i vs. unknown j - at t-(T-1) above the main diagonal - N : [optional] int - number of equispaced points lambda on interval [0,2pi] for evaluating det A(lambda) - - Returns - ---------- - winding_number : int - winding number that characterizes existence and uniqueness of solutions: - 0 for determinate solution - -1 (or lower) for indeterminacy - 1 (or higher) for no solution - """ - det_Alambda = detA_path(A, N) - return winding_number(det_Alambda.real, det_Alambda.imag) - - -def detA_path(A, N=4096): - """Evaluates det A(lambda) at N equispaced points lambda on interval [0,2pi]. - - A brief derivation of how this function uses FFT to rapidly evaluate det A(lambda) follows. - - We have, letting A_(-j) denote the k*k matrix A[-j,:,:]: - - det A(lambda) = det sum_(j=-(T-1))^(T-1) A_(-j)e^(i*j*lambda) - - which, flipping the order and realigning j, can be rewritten as - - e^(lambda*i*k*(T-1)) det sum_(j=0)^(2T-2) A_(-j+(T-1))e^(-i*j*lambda) (***) - - Taking the sum in (***) for the values lambda=0,2*pi/N,...,2*pi*(N-1)/N, assuming N >= (2T-1), - is just taking the discrete Fourier transform of the sequence A_(T-1),...,A_(-(T-1)),0,...,0 - right-padded with zeros to length N. - - Hence we can rapidly, simultaneously evaluate (***) at all points lambda equispaced from lambda=0 - to lambda=2*pi using the FFT. This is implemented below, with additional efficiency from fact that - A(lambda) and A(2*pi-lambda) are conjugate. - """ - # preliminary: assume and verify shape 2*T-1, k, k for A - T = (A.shape[0]+1) // 2 - k = A.shape[1] - if not (T == (A.shape[0]+1)/2 and N >= 2*T-1 and k == A.shape[2]): - raise ValueError(f'Asymptotic A matrix has improper shape {A.shape}') - - # step 1: use FFT to calculate A(lambda) for each lambda = 2*pi*{0, 1/N, ..., 1/2} (last if N even) - # note that we need to reverse order of A_t to get sequence A_(T-1),...,A_(-(T-1)),0,...,0 - Alambda = rfftn(A[::-1,...], axes=(0,), s=(N,)) - - # step 2: take determinant of each, then multiply by e^(i*k*(T-1)*lambda) to get (***) - det_Alambda = np.empty(N+1, dtype=np.complex128) - det_Alambda[:N//2+1] = np.linalg.det(Alambda)*np.exp(2j*np.pi*k*(T-1)/N*np.arange(N//2+1)) - - # step 3: use conjugate symmetry to fill in rest - det_Alambda[N//2+1:] = det_Alambda[:(N+1)//2][::-1].conj() - - return det_Alambda - - -@njit -def winding_number(x, y): - """Compute winding number around origin of (x,y) coordinates that make closed path by - counting number of counterclockwise crossings of ray from (0,0) -> (infty,0) on x axis""" - # ensure closed path! - assert x[-1] == x[0] and y[-1] == y[0] - - winding_number = 0 - - # we iterate through coordinates (x[i], y[i]), where cur_sign is flag for - # whether current coordinate is above the x axis - cur_sign = (y[0] >= 0) - for i in range(1, len(x)): - if (y[i] >= 0) != cur_sign: - # if we're here, this means the x axis has been crossed - # this generally happens rarely, so efficiency no biggie - cur_sign = (y[i] >= 0) - - # crossing of x axis implies possible crossing of ray (0,0) -> (infty,0) - # we will evaluate three possible cases to see if this is indeed the case - if x[i] > 0 and x[i-1] > 0: - # case 1: both (x[i-1],y[i-1]) and (x[i],y[i]) on right half-plane, definite crossing - # increment winding number if counterclockwise (negative to positive y) - # decrement winding number if clockwise (positive to negative y) - winding_number += 2*cur_sign-1 - elif not (x[i] <= 0 and x[i-1] <= 0): - # here we've ruled out case 2: both (x[i-1],y[i-1]) and (x[i],y[i]) in left - # half-plane, where there is definitely no crossing - - # thus we're in ambiguous case 3, where points (x[i-1],y[i-1]) and (x[i],y[i]) in - # different half-planes: here we must analytically check whether we crossed - # x-axis to the right or the left of the origin - # [this step is intended to be rare] - cross_coord = (x[i-1]*y[i] - x[i]*y[i-1])/(y[i]-y[i-1]) - if cross_coord > 0: - winding_number += 2*cur_sign-1 - return winding_number - - diff --git a/sequence_jacobian/jacobian.py b/sequence_jacobian/jacobian.py deleted file mode 100644 index 4406a54..0000000 --- a/sequence_jacobian/jacobian.py +++ /dev/null @@ -1,756 +0,0 @@ -"""Methods for computing and manipulating both block-level and model-level Jacobians""" - -import numpy as np -import copy -from numba import njit - -from . import utilities as utils -from . import asymptotic -from .blocks import simple_block as sim - - -'''Part 1: High-level convenience routines: - - get_H_U : get H_U matrix mapping all unknowns to all targets - - get_impulse : get single GE impulse response - - get_G : get G matrices characterizing all GE impulse responses - - get_G_asymptotic : get asymptotic diagonals of the G matrices returned by get_G - - - curlyJs_sorted : get block Jacobians curlyJ and return them topologically sorted - - forward_accumulate : forward accumulation on DAG, taking in topologically sorted Jacobians -''' - - -def get_H_U(block_list, unknowns, targets, T, ss=None, asymptotic=False, Tpost=None, save=False, use_saved=False): - """Get T*n_u by T*n_u matrix H_U, Jacobian mapping all unknowns to all targets. - - Parameters - ---------- - block_list : list, simple blocks, het blocks, or jacdicts - unknowns : list of str, names of unknowns in DAG - targets : list of str, names of targets in DAG - T : int, truncation horizon - (if asymptotic, truncation horizon for backward iteration in HetBlocks) - ss : [optional] dict, steady state required if block_list contains any non-jacdicts - asymptotic : [optional] bool, flag for returning asymptotic H_U - Tpost : [optional] int, truncation horizon for asymptotic -(Tpost-1),...,0,...,(Tpost-1) - save : [optional] bool, flag for saving Jacobians inside HetBlocks - use_saved : [optional] bool, flag for using saved Jacobians inside HetBlocks - - Returns - ------- - H_U : - if asymptotic=False: - array(T*n_u*T*n_u) H_U, Jacobian mapping all unknowns to all targets - is asymptotic=True: - array((2*Tpost-1)*n_u*n_u), representation of asymptotic columns of H_U - """ - - # do topological sort and get curlyJs - curlyJs, required = curlyJ_sorted(block_list, unknowns, ss, T, asymptotic, Tpost, save, use_saved) - - # do matrix forward accumulation to get H_U = J^(curlyH, curlyU) - H_U_unpacked = forward_accumulate(curlyJs, unknowns, targets, required) - - if not asymptotic: - # pack these n_u^2 matrices, each T*T, into a single matrix - return pack_jacobians(H_U_unpacked, unknowns, targets, T) - else: - # pack these n_u^2 AsymptoticTimeInvariant objects into a single (2*Tpost-1,n_u,n_u) array - if Tpost is None: - Tpost = 2*T - return pack_asymptotic_jacobians(H_U_unpacked, unknowns, targets, Tpost) - - -def get_impulse(block_list, dZ, unknowns, targets, T=None, ss=None, outputs=None, - H_U=None, H_U_factored=None, save=False, use_saved=False): - """Get a single general equilibrium impulse response. - - Extremely fast when H_U_factored = utils.misc.factor(get_HU(...)) has already been computed - and supplied to this function. Less so but still faster when H_U already computed. - - Parameters - ---------- - block_list : list, simple blocks or jacdicts - dZ : dict, path of an exogenous variable - unknowns : list of str, names of unknowns in DAG - targets : list of str, names of targets in DAG - T : [optional] int, truncation horizon - ss : [optional] dict, steady state required if block_list contains non-jacdicts - outputs : [optional] list of str, variables we want impulse responses for - H_U : [optional] array, precomputed Jacobian mapping unknowns to targets - H_U_factored : [optional] tuple of arrays, precomputed LU factorization utils.misc.factor(H_U) - save : [optional] bool, flag for saving Jacobians inside HetBlocks - use_saved : [optional] bool, flag for using saved Jacobians inside HetBlocks - - Returns - ------- - out : dict, impulse responses to shock dZ - """ - # step 0 (preliminaries): infer T, do topological sort and get curlyJs - if T is None: - for x in dZ.values(): - T = len(x) - break - - curlyJs, required = curlyJ_sorted(block_list, unknowns + list(dZ.keys()), ss, T, - save=save, use_saved=use_saved) - - # step 1: if not provided, do (matrix) forward accumulation to get H_U = J^(curlyH, curlyU) - if H_U is None and H_U_factored is None: - H_U_unpacked = forward_accumulate(curlyJs, unknowns, targets, required) - - # step 2: do (vector) forward accumulation to get J^(o, curlyZ)dZ for all o in - # 'alloutputs', the combination of outputs (if specified) and targets - alloutputs = None - if outputs is not None: - alloutputs = set(outputs) | set(targets) - - J_curlyZ_dZ = forward_accumulate(curlyJs, dZ, alloutputs, required) - - # step 3: solve H_UdU = -H_ZdZ for dU - if H_U is None and H_U_factored is None: - H_U = pack_jacobians(H_U_unpacked, unknowns, targets, T) - - H_ZdZ_packed = pack_vectors(J_curlyZ_dZ, targets, T) - - if H_U_factored is None: - dU_packed = - np.linalg.solve(H_U, H_ZdZ_packed) - else: - dU_packed = - utils.misc.factored_solve(H_U_factored, H_ZdZ_packed) - - dU = unpack_vectors(dU_packed, unknowns, T) - - # step 4: do (vector) forward accumulation to get J^(o, curlyU)dU - # then sum together with J^(o, curlyZ)dZ to get all output impulse responses - J_curlyU_dU = forward_accumulate(curlyJs, dU, outputs, required) - if outputs is None: - outputs = J_curlyZ_dZ.keys() | J_curlyU_dU.keys() - return {o: J_curlyZ_dZ.get(o, np.zeros(T)) + J_curlyU_dU.get(o, np.zeros(T)) for o in outputs} - - -def get_G(block_list, exogenous, unknowns, targets, T, ss=None, outputs=None, - H_U=None, H_U_factored=None, save=False, use_saved=False): - """Compute Jacobians G that fully characterize general equilibrium outputs in response - to all exogenous shocks in 'exogenous' - - Faster when H_U_factored = utils.misc.factor(get_HU(...)) has already been computed - and supplied to this function. Less so but still faster when H_U already computed. - Relative benefit of precomputing these not as extreme as for get_impulse, since - obtaining and solving with H_U is a less dominant component of cost for getting Gs. - - Parameters - ---------- - block_list : list, simple blocks or jacdicts - exogenous : list of str, names of exogenous shocks in DAG - unknowns : list of str, names of unknowns in DAG - targets : list of str, names of targets in DAG - T : [optional] int, truncation horizon - ss : [optional] dict, steady state required if block_list contains non-jacdicts - outputs : [optional] list of str, variables we want impulse responses for - H_U : [optional] array, precomputed Jacobian mapping unknowns to targets - H_U_factored : [optional] tuple of arrays, precomputed LU factorization utils.misc.factor(H_U) - save : [optional] bool, flag for saving Jacobians inside HetBlocks - use_saved : [optional] bool, flag for using saved Jacobians inside HetBlocks - - Returns - ------- - G : dict of dict, Jacobians for general equilibrium mapping from exogenous to outputs - """ - - # step 1: do topological sort and get curlyJs - curlyJs, required = curlyJ_sorted(block_list, unknowns + exogenous, ss, T, - save=save, use_saved=use_saved) - - # step 2: do (matrix) forward accumulation to get - # H_U = J^(curlyH, curlyU) [if not provided], H_Z = J^(curlyH, curlyZ) - if H_U is None and H_U_factored is None: - J_curlyH_U = forward_accumulate(curlyJs, unknowns, targets, required) - J_curlyH_Z = forward_accumulate(curlyJs, exogenous, targets, required) - - # step 3: solve for G^U, unpack - if H_U is None and H_U_factored is None: - H_U = pack_jacobians(J_curlyH_U, unknowns, targets, T) - H_Z = pack_jacobians(J_curlyH_Z, exogenous, targets, T) - - if H_U_factored is None: - G_U = unpack_jacobians(-np.linalg.solve(H_U, H_Z), exogenous, unknowns, T) - else: - G_U = unpack_jacobians(-utils.misc.factored_solve(H_U_factored, H_Z), exogenous, unknowns, T) - - # step 4: forward accumulation to get all outputs starting with G_U - # by default, don't calculate targets! - curlyJs = [G_U] + curlyJs - if outputs is None: - outputs = set().union(*(curlyJ.keys() for curlyJ in curlyJs)) - set(targets) - return forward_accumulate(curlyJs, exogenous, outputs, required | set(unknowns)) - - -def get_G_asymptotic(block_list, exogenous, unknowns, targets, T, ss=None, outputs=None, - save=False, use_saved=False, Tpost=None): - """Like get_G, but rather than returning the actual matrices G, return - asymptotic.AsymptoticTimeInvariant objects representing their asymptotic columns.""" - - # step 1: do topological sort and get curlyJs - curlyJs, required = curlyJ_sorted(block_list, unknowns + exogenous, ss, T, save=save, - use_saved=use_saved, asymptotic=True, Tpost=Tpost) - - # step 2: do (matrix) forward accumulation to get - # H_U = J^(curlyH, curlyU) - J_curlyH_U = forward_accumulate(curlyJs, unknowns, targets, required) - - # step 3: invert H_U and forward accumulate to get G_U = H_U^(-1)H_Z - U_H_unpacked = asymptotic.invert_jacdict(J_curlyH_U, unknowns, targets, Tpost) - G_U = forward_accumulate(curlyJs + [U_H_unpacked], exogenous, unknowns, required | set(targets)) - - # step 4: forward accumulation to get all outputs starting with G_U - # by default, don't calculate targets! - curlyJs = [G_U] + curlyJs - if outputs is None: - outputs = set().union(*(curlyJ.keys() for curlyJ in curlyJs)) - set(targets) - return forward_accumulate(curlyJs, exogenous, outputs, required | set(unknowns)) - - -def curlyJ_sorted(block_list, inputs, ss=None, T=None, asymptotic=False, Tpost=None, save=False, use_saved=False): - """ - Sort blocks along DAG and calculate their Jacobians (if not already provided) with respect to inputs - and with respect to outputs of other blocks - - Parameters - ---------- - block_list : list, simple blocks or jacdicts - inputs : list, input names we need to differentiate with respect to - ss : [optional] dict, steady state, needed if block_list includes blocks themselves - T : [optional] int, horizon for differentiation, needed if block_list includes hetblock itself - asymptotic : [optional] bool, flag for returning asymptotic Jacobians - Tpost : [optional] int, truncation horizon for asymptotic -(Tpost-1),...,0,...,(Tpost-1) - save : [optional] bool, flag for saving Jacobians inside HetBlocks - use_saved : [optional] bool, flag for using saved Jacobians inside HetBlocks - - Returns - ------- - curlyJs : list of dict of dict, curlyJ for each block in order of topological sort - required : list, outputs of some blocks that are needed as inputs by others - """ - - # step 1: get topological sort and required - topsorted = utils.graph.block_sort(block_list, ignore_helpers=True) - required = utils.graph.find_outputs_that_are_intermediate_inputs(block_list, ignore_helpers=True) - - # Remove any vector-valued outputs that are intermediate inputs, since we don't want - # to compute Jacobians with respect to vector-valued variables - if ss is not None: - vv_vars = set([k for k, v in ss.items() if np.size(v) > 1]) - required -= vv_vars - - # step 2: compute Jacobians and put them in right order - curlyJs = [] - shocks = set(inputs) | required - for num in topsorted: - block = block_list[num] - if hasattr(block, 'ajac'): - # has 'ajac' function, is some block other than SimpleBlock - if asymptotic: - jac = block.ajac(ss, T=T, shock_list=list(shocks), Tpost=Tpost, save=save, use_saved=use_saved) - else: - jac = block.jac(ss, T=T, shock_list=list(shocks), save=save, use_saved=use_saved) - elif hasattr(block, 'jac'): - # has 'jac' but not 'ajac', must be SimpleBlock where no distinction (given SimpleSparse) - jac = block.jac(ss, shock_list=list(shocks)) - else: - # doesn't have 'jac', must be nested dict that is jac directly - jac = block - - # If the returned Jacobian is empty (i.e. the shocks do not affect any outputs from the block) - # then don't add it to the list of curlyJs to be returned - if not jac: - continue - else: - curlyJs.append(jac) - - return curlyJs, required - - -def forward_accumulate(curlyJs, inputs, outputs=None, required=None): - """ - Use forward accumulation on topologically sorted Jacobians in curlyJs to get - all cumulative Jacobians with respect to 'inputs' if inputs is a list of names, - or get outcome of apply to 'inputs' if inputs is dict. - - Optionally only find outputs in 'outputs', especially if we have knowledge of - what is required for later Jacobians. - - Note that the overloading of @ means that this works automatically whether curlyJs are ordinary - matrices, simple_block.SimpleSparse objects, or asymptotic.AsymptoticTimeInvariant objects, - as long as the first and third are not mixed (since multiplication not defined for them). - - Much-extended version of chain_jacobians. - - Parameters - ---------- - curlyJs : list of dict of dict, curlyJ for each block in order of topological sort - inputs : list or dict, input names to differentiate with respect to, OR dict of input vectors - outputs : [optional] list or set, outputs we're interested in - required : [optional] list or set, outputs needed for later curlyJs (only useful w/outputs) - - Returns - ------- - out : dict of dict or dict, either total J for each output wrt all inputs or - outcome from applying all curlyJs - """ - - if outputs is not None and required is not None: - # if list of outputs provided, we need to obtain these and 'required' along the way - alloutputs = set(outputs) | set(required) - else: - # otherwise, set to None, implies default behavior of obtaining all outputs in curlyJs - alloutputs = None - - # if inputs is list (jacflag=True), interpret as list of inputs for which we want to calculate jacs - # if inputs is dict, interpret as input *paths* to which we apply all Jacobians in curlyJs - jacflag = not isinstance(inputs, dict) - - if jacflag: - # Jacobians of inputs with respect to themselves are the identity, initialize with this - out = {i: {i: IdentityMatrix()} for i in inputs} - else: - out = inputs.copy() - - # iterate through curlyJs, in what is presumed to be a topologically sorted order - for curlyJ in curlyJs: - if alloutputs is not None: - # if we want specific list of outputs, restrict curlyJ to that before continuing - curlyJ = {k: v for k, v in curlyJ.items() if k in alloutputs} - if jacflag: - out.update(compose_jacobians(out, curlyJ)) - else: - out.update(apply_jacobians(curlyJ, out)) - - if outputs is not None: - # if we want specific list of outputs, restrict to that - # (dropping 'required' in 'alloutputs' that was needed for intermediate computations) - return {k: out[k] for k in outputs if k in out} - else: - if jacflag: - # default behavior for Jacobian case: return all Jacobians we used/calculated along the way - # except the (redundant) IdentityMatrix objects mapping inputs to themselves - return {k: v for k, v in out.items() if k not in inputs} - else: - # default behavior for case where we're calculating paths: return everything, including inputs - return out - - -'''Part 2: Somewhat lower-level routines for handling Jacobians''' - - -def chain_jacobians(jacdicts, inputs): - """Obtain complete Jacobian of every output in jacdicts with respect to inputs, by applying chain rule.""" - cumulative_jacdict = {i: {i: IdentityMatrix()} for i in inputs} - for jacdict in jacdicts: - cumulative_jacdict.update(compose_jacobians(cumulative_jacdict, jacdict)) - return cumulative_jacdict - - -def compose_jacobians(jacdict2, jacdict1): - """Compose Jacobians via the chain rule.""" - jacdict = {} - for output, innerjac1 in jacdict1.items(): - jacdict[output] = {} - for middle, jac1 in innerjac1.items(): - innerjac2 = jacdict2.get(middle, {}) - for inp, jac2 in innerjac2.items(): - if inp in jacdict[output]: - jacdict[output][inp] += jac1 @ jac2 - else: - jacdict[output][inp] = jac1 @ jac2 - return jacdict - - -def apply_jacobians(jacdict, indict): - """Apply Jacobians in jacdict to indict to obtain outputs.""" - outdict = {} - for myout, innerjacdict in jacdict.items(): - for myin, jac in innerjacdict.items(): - if myin in indict: - if myout in outdict: - outdict[myout] += jac @ indict[myin] - else: - outdict[myout] = jac @ indict[myin] - - return outdict - - -def pack_jacobians(jacdict, inputs, outputs, T): - """If we have T*T jacobians from nI inputs to nO outputs in jacdict, combine into (nO*T)*(nI*T) jacobian matrix.""" - nI, nO = len(inputs), len(outputs) - - outjac = np.empty((nO * T, nI * T)) - for iO in range(nO): - subdict = jacdict.get(outputs[iO], {}) - for iI in range(nI): - outjac[(T * iO):(T * (iO + 1)), (T * iI):(T * (iI + 1))] = make_matrix(subdict.get(inputs[iI], - np.zeros((T, T))), T) - return outjac - - -def unpack_jacobians(bigjac, inputs, outputs, T): - """If we have an (nO*T)*(nI*T) jacobian and provide names of nO outputs and nI inputs, output nested dictionary""" - nI, nO = len(inputs), len(outputs) - - jacdict = {} - for iO in range(nO): - jacdict[outputs[iO]] = {} - for iI in range(nI): - jacdict[outputs[iO]][inputs[iI]] = bigjac[(T * iO):(T * (iO + 1)), (T * iI):(T * (iI + 1))] - return jacdict - - -def pack_asymptotic_jacobians(jacdict, inputs, outputs, tau): - """If we have -(tau-1),...,(tau-1) AsymptoticTimeInvariant Jacobians (or SimpleSparse) from - nI inputs to nO outputs in jacdict, combine into (2*tau-1,nO,nI) array A""" - nI, nO = len(inputs), len(outputs) - A = np.empty((2*tau-1, nI, nO)) - for iO in range(nO): - subdict = jacdict.get(outputs[iO], {}) - for iI in range(nI): - if inputs[iI] in subdict: - A[:, iO, iI] = make_ATI_v(jacdict[outputs[iO]][inputs[iI]], tau) - else: - A[:, iO, iI] = 0 - return A - - -def unpack_asymptotic_jacobians(A, inputs, outputs, tau): - """If we have (2*tau-1, nO, nI) array A where each A[:,o,i] is vector for AsymptoticTimeInvariant - Jacobian mapping output o to output i, output nested dict of AsymptoticTimeInvariant objects""" - nI, nO = len(inputs), len(outputs) - - jacdict = {} - for iO in range(nO): - jacdict[outputs[iO]] = {} - for iI in range(nI): - jacdict[outputs[iO]][inputs[iI]] = asymptotic.AsymptoticTimeInvariant(A[:, iO, iI]) - return jacdict - - -def pack_vectors(vs, names, T): - v = np.zeros(len(names)*T) - for i, name in enumerate(names): - if name in vs: - v[i*T:(i+1)*T] = vs[name] - return v - - -def unpack_vectors(v, names, T): - vs = {} - for i, name in enumerate(names): - vs[name] = v[i*T:(i+1)*T] - return vs - - -def make_matrix(A, T): - """If A is not an outright ndarray, e.g. it is SimpleSparse, call its .matrix(T) method - to convert it to T*T array.""" - if not isinstance(A, np.ndarray): - return A.matrix(T) - else: - return A - - -def make_ATI_v(x, tau): - """If x is either a AsymptoticTimeInvariant or something that can be converted to it, e.g. - SimpleSparse, report the underlying length 2*tau-1 vector with entries -(tau-1),...,(tau-1)""" - if not isinstance(x, asymptotic.AsymptoticTimeInvariant): - return x.asymptotic_time_invariant.changetau(tau).v - else: - return x.v - - -'''Part 3: SimpleSparse and IdentityMatrix classes and related helpers''' -class SimpleSparse: - """Efficient representation of sparse linear operators, which are linear combinations of basis - operators represented by pairs (i, m), where i is the index of diagonal on which there are 1s - (measured by # above main diagonal) and m is number of initial entries missing. - - Examples of such basis operators: - - (0, 0) is identity operator - - (0, 2) is identity operator with first two '1's on main diagonal missing - - (1, 0) has 1s on diagonal above main diagonal: "left-shift" operator - - (-1, 1) has 1s on diagonal below main diagonal, except first column - - The linear combination of these basis operators that makes up a given SimpleSparse object is - stored as a dict 'elements' mapping (i, m) -> x. - - The Jacobian of a SimpleBlock is a SimpleSparse operator combining basis elements (i, 0). We need - the more general basis (i, m) to ensure closure under multiplication. - - These (i, m) correspond to the Q_(-i, m) operators defined for Proposition 2 of the Sequence Space - Jacobian paper. The flipped sign in the code is so that the index 'i' matches the k(i) notation - for writing SimpleBlock functions. - - The "dunder" methods x.__add__(y), x.__matmul__(y), x.__rsub__(y), etc. in Python implement infix - operations x + y, x @ y, y - x, etc. Defining these allows us to use these more-or-less - interchangeably with ordinary NumPy matrices. - """ - - # when performing binary operations on SimpleSparse and a NumPy array, use SimpleSparse's rules - __array_priority__ = 1000 - - def __init__(self, elements): - self.elements = elements - self.indices, self.xs = None, None - - @staticmethod - def from_simple_diagonals(elements): - """Take dict i -> x, i.e. from SimpleBlock differentiation, convert to SimpleSparse (i, 0) -> x""" - return SimpleSparse({(i, 0): x for i, x in elements.items()}) - - def matrix(self, T): - """Return matrix giving first T rows and T columns of matrix representation of SimpleSparse""" - return self + np.zeros((T, T)) - - def array(self): - """Rewrite dict (i, m) -> x as pair of NumPy arrays, one size-N*2 array of ints with rows (i, m) - and one size-N array of floats with entries x. - - This is needed for Numba to take as input. Cache for efficiency. - """ - if self.indices is not None: - return self.indices, self.xs - else: - if not self.elements: - # empty SimpleSparse - return np.empty((0, 2), dtype=int), np.empty(0) - - indices, xs = zip(*self.elements.items()) - self.indices, self.xs = np.array(indices), np.array(xs) - return self.indices, self.xs - - @property - def asymptotic_time_invariant(self): - indices, xs = self.array() - tau = np.max(np.abs(indices[:, 0]))+1 # how far out do we go? - v = np.zeros(2*tau-1) - #v[indices[:, 0]+tau-1] = xs - v[-indices[:, 0]+tau-1] = xs # switch from asymptotic ROW to asymptotic COLUMN - return asymptotic.AsymptoticTimeInvariant(v) - - @property - def T(self): - """Transpose""" - return SimpleSparse({(-i, m): x for (i, m), x in self.elements.items()}) - - @property - def iszero(self): - return not self.nonzero().elements - - def nonzero(self): - elements = self.elements.copy() - for im, x in self.elements.items(): - # safeguard to retain sparsity: disregard extremely small elements (num error) - if abs(elements[im]) < 1E-14: - del elements[im] - return SimpleSparse(elements) - - def __pos__(self): - return self - - def __neg__(self): - return SimpleSparse({im: -x for im, x in self.elements.items()}) - - def __matmul__(self, A): - if isinstance(A, SimpleSparse): - # multiply SimpleSparse by SimpleSparse, simple analytical rules in multiply_rs_rs - return multiply_rs_rs(self, A) - elif isinstance(A, np.ndarray): - # multiply SimpleSparse by matrix or vector, multiply_rs_matrix uses slicing - indices, xs = self.array() - if A.ndim == 2: - return multiply_rs_matrix(indices, xs, A) - elif A.ndim == 1: - return multiply_rs_matrix(indices, xs, A[:, np.newaxis])[:, 0] - else: - return NotImplemented - else: - return NotImplemented - - def __rmatmul__(self, A): - # multiplication rule when this object is on right (will only be called when left is matrix) - # for simplicity, just use transpose to reduce this to previous cases - return (self.T @ A.T).T - - def __add__(self, A): - if isinstance(A, SimpleSparse): - # add SimpleSparse to SimpleSparse, combining dicts, summing x when (i, m) overlap - elements = self.elements.copy() - for im, x in A.elements.items(): - if im in elements: - elements[im] += x - # safeguard to retain sparsity: disregard extremely small elements (num error) - if abs(elements[im]) < 1E-14: - del elements[im] - else: - elements[im] = x - return SimpleSparse(elements) - else: - # add SimpleSparse to T*T matrix - if not isinstance(A, np.ndarray) or A.ndim != 2 or A.shape[0] != A.shape[1]: - return NotImplemented - T = A.shape[0] - - # fancy trick to do this efficiently by writing A as flat vector - # then (i, m) can be mapped directly to NumPy slicing! - A = A.flatten() # use flatten, not ravel, since we'll modify A and want a copy - for (i, m), x in self.elements.items(): - if i < 0: - A[T * (-i) + (T + 1) * m::T + 1] += x - else: - A[i + (T + 1) * m:(T - i) * T:T + 1] += x - return A.reshape((T, T)) - - def __radd__(self, A): - try: - return self + A - except: - print(self) - print(A) - raise - - def __sub__(self, A): - # slightly inefficient implementation with temporary for simplicity - return self + (-A) - - def __rsub__(self, A): - return -self + A - - def __mul__(self, a): - if not np.isscalar(a): - return NotImplemented - return SimpleSparse({im: a * x for im, x in self.elements.items()}) - - def __rmul__(self, a): - return self * a - - def __repr__(self): - formatted = '{' + ', '.join(f'({i}, {m}): {x:.3f}' for (i, m), x in self.elements.items()) + '}' - return f'SimpleSparse({formatted})' - - def __eq__(self, s): - return self.elements == s.elements - - -def multiply_basis(t1, t2): - """Matrix multiplication operation mapping two sparse basis elements to another.""" - # equivalent to formula in Proposition 2 of Sequence Space Jacobian paper, but with - # signs of i and j flipped to reflect different sign convention used here - i, m = t1 - j, n = t2 - k = i + j - if i >= 0: - if j >= 0: - l = max(m, n - i) - elif k >= 0: - l = max(m, n - k) - else: - l = max(m + k, n) - else: - if j <= 0: - l = max(m + j, n) - else: - l = max(m, n) + min(-i, j) - return k, l - - -def multiply_rs_rs(s1, s2): - """Matrix multiplication operation on two SimpleSparse objects.""" - # iterate over all pairs (i, m) -> x and (j, n) -> y in objects, - # add all pairwise products to get overall product - elements = {} - for im, x in s1.elements.items(): - for jn, y in s2.elements.items(): - kl = multiply_basis(im, jn) - if kl in elements: - elements[kl] += x * y - else: - elements[kl] = x * y - return SimpleSparse(elements) - - -@njit -def multiply_rs_matrix(indices, xs, A): - """Matrix multiplication of SimpleSparse object ('indices' and 'xs') and matrix A. - Much more computationally demanding than multiplying two SimpleSparse (which is almost - free with simple analytical formula), so we implement as jitted function.""" - n = indices.shape[0] - T = A.shape[0] - S = A.shape[1] - Aout = np.zeros((T, S)) - - for count in range(n): - # for Numba to jit easily, SimpleSparse with basis elements '(i, m)' with coefs 'x' - # was stored in 'indices' and 'xs' - i = indices[count, 0] - m = indices[count, 1] - x = xs[count] - - # loop faster than vectorized when jitted - # directly use def of basis element (i, m), displacement of i and ignore first m - if i == 0: - for t in range(m, T): - for s in range(S): - Aout[t, s] += x * A[t, s] - elif i > 0: - for t in range(m, T - i): - for s in range(S): - Aout[t, s] += x * A[t + i, s] - else: - for t in range(m - i, T): - for s in range(S): - Aout[t, s] += x * A[t + i, s] - return Aout - - -class IdentityMatrix: - """Simple identity matrix class with which we can initialize chain_jacobians and forward_accumulate, - avoiding costly explicit construction of and operations on identity matrices.""" - __array_priority__ = 10_000 - - def sparse(self): - """Equivalent SimpleSparse representation, less efficient operations but more general.""" - return sim.SimpleSparse({(0, 0): 1}) - - def matrix(self, T): - return np.eye(T) - - def __matmul__(self, other): - """Identity matrix knows to simply return 'other' whenever it's multiplied by 'other'.""" - return copy.deepcopy(other) - - def __rmatmul__(self, other): - return copy.deepcopy(other) - - def __mul__(self, a): - return a*self.sparse() - - def __rmul__(self, a): - return self.sparse()*a - - def __add__(self, x): - return self.sparse() + x - - def __radd__(self, x): - return x + self.sparse() - - def __sub__(self, x): - return self.sparse() - x - - def __rsub__(self, x): - return x - self.sparse() - - def __neg__(self): - return -self.sparse() - - def __pos__(self): - return self - - def __repr__(self): - return 'IdentityMatrix' \ No newline at end of file diff --git a/sequence_jacobian/models/__init__.py b/sequence_jacobian/models/__init__.py deleted file mode 100644 index 29259f6..0000000 --- a/sequence_jacobian/models/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Specific Model Implementations""" \ No newline at end of file diff --git a/sequence_jacobian/models/hank.py b/sequence_jacobian/models/hank.py deleted file mode 100644 index a5fb229..0000000 --- a/sequence_jacobian/models/hank.py +++ /dev/null @@ -1,232 +0,0 @@ -import numpy as np -from numba import vectorize, njit - -from .. import utilities as utils -from ..blocks.simple_block import simple -from ..blocks.het_block import het -from ..blocks.helper_block import helper - - -'''Part 1: HA block''' - - -def household_init(a_grid, e_grid, r, w, eis, T): - fininc = (1 + r) * a_grid + T[:, np.newaxis] - a_grid[0] - coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] + T[:, np.newaxis] - Va = (1 + r) * (0.1 * coh) ** (-1 / eis) - return fininc, Va - - -@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init) -def household(Va_p, Pi_p, a_grid, e_grid, T, w, r, beta, eis, frisch, vphi): - """Single backward iteration step using endogenous gridpoint method for households with separable CRRA utility.""" - # this one is useful to do internally - ws = w * e_grid - - # uc(z_t, a_t) - uc_nextgrid = (beta * Pi_p) @ Va_p - - # c(z_t, a_t) and n(z_t, a_t) - c_nextgrid, n_nextgrid = cn(uc_nextgrid, ws[:, np.newaxis], eis, frisch, vphi) - - # c(z_t, a_{t-1}) and n(z_t, a_{t-1}) - lhs = c_nextgrid - ws[:, np.newaxis] * n_nextgrid + a_grid[np.newaxis, :] - T[:, np.newaxis] - rhs = (1 + r) * a_grid - c = utils.interpolate.interpolate_y(lhs, rhs, c_nextgrid) - n = utils.interpolate.interpolate_y(lhs, rhs, n_nextgrid) - - # test constraints, replace if needed - a = rhs + ws[:, np.newaxis] * n + T[:, np.newaxis] - c - iconst = np.nonzero(a < a_grid[0]) - a[iconst] = a_grid[0] - - # if there exist states/prior asset levels such that households want to borrow, compute the constrained - # solution for consumption and labor supply - if iconst[0].size != 0 and iconst[1].size != 0: - c[iconst], n[iconst] = solve_cn(ws[iconst[0]], rhs[iconst[1]] + T[iconst[0]] - a_grid[0], - eis, frisch, vphi, Va_p[iconst]) - - # calculate marginal utility to go backward - Va = (1 + r) * c ** (-1 / eis) - - # efficiency units of labor which is what really matters - n_e = e_grid[:, np.newaxis] * n - - return Va, a, c, n, n_e - - -def transfers(pi_e, Div, Tax, e_grid): - # default incidence rules are proportional to skill - tax_rule, div_rule = e_grid, e_grid # scale does not matter, will be normalized anyway - - div = Div / np.sum(pi_e * div_rule) * div_rule - tax = Tax / np.sum(pi_e * tax_rule) * tax_rule - T = div - tax - return T - - -household.add_hetinput(transfers, verbose=False) - - -@njit -def cn(uc, w, eis, frisch, vphi): - """Return optimal c, n as function of u'(c) given parameters""" - return uc ** (-eis), (w * uc / vphi) ** frisch - - -def solve_cn(w, T, eis, frisch, vphi, uc_seed): - uc = solve_uc(w, T, eis, frisch, vphi, uc_seed) - return cn(uc, w, eis, frisch, vphi) - - -@vectorize -def solve_uc(w, T, eis, frisch, vphi, uc_seed): - """Solve for optimal uc given in log uc space. - - max_{c, n} c**(1-1/eis) + vphi*n**(1+1/frisch) s.t. c = w*n + T - """ - log_uc = np.log(uc_seed) - for i in range(30): - ne, ne_p = netexp(log_uc, w, T, eis, frisch, vphi) - if abs(ne) < 1E-11: - break - else: - log_uc -= ne / ne_p - else: - raise ValueError("Cannot solve constrained household's problem: No convergence after 30 iterations!") - - return np.exp(log_uc) - - -@njit -def netexp(log_uc, w, T, eis, frisch, vphi): - """Return net expenditure as a function of log uc and its derivative.""" - c, n = cn(np.exp(log_uc), w, eis, frisch, vphi) - ne = c - w * n - T - - # c and n have elasticities of -eis and frisch wrt log u'(c) - c_loguc = -eis * c - n_loguc = frisch * n - netexp_loguc = c_loguc - w * n_loguc - - return ne, netexp_loguc - - -'''Part 2: Simple blocks and hetinput''' - - -@simple -def firm(Y, w, Z, pi, mu, kappa): - L = Y / Z - Div = Y - w * L - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y - return L, Div - - -@simple -def monetary(pi, rstar, phi): - r = (1 + rstar(-1) + phi * pi(-1)) / (1 + pi) - 1 - return r - - -@simple -def fiscal(r, B): - Tax = r * B - return Tax - - -@simple -def mkt_clearing(A, N_e, C, L, Y, B, pi, mu, kappa): - asset_mkt = A - B - labor_mkt = N_e - L - goods_mkt = Y - C - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y - return asset_mkt, labor_mkt, goods_mkt - - -@simple -def nkpc(pi, w, Z, Y, r, mu, kappa): - nkpc_res = kappa * (w / Z - 1 / mu) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) / (1 + r(+1))\ - - (1 + pi).apply(np.log) - return nkpc_res - - -@simple -def income_state_vars(rho_s, sigma_s, nS): - e_grid, pi_e, Pi = utils.discretize.markov_rouwenhorst(rho=rho_s, sigma=sigma_s, N=nS) - return e_grid, pi_e, Pi - - -@simple -def asset_state_vars(amax, nA): - a_grid = utils.discretize.agrid(amax=amax, n=nA) - return a_grid - - -@helper -def partial_steady_state_solution(B_Y, mu, r): - B = B_Y - w = 1 / mu - Div = (1 - w) - Tax = r * B - - return B, w, Div, Tax - - -'''Part 3: Steady state''' - - -def hank_ss(beta_guess=0.986, vphi_guess=0.8, r=0.005, eis=0.5, frisch=0.5, mu=1.2, B_Y=5.6, rho_s=0.966, sigma_s=0.5, - kappa=0.1, phi=1.5, nS=7, amax=150, nA=500): - """Solve steady state of full GE model. Calibrate (beta, vphi) to hit target for interest rate and Y.""" - - # set up grid - a_grid = utils.discretize.agrid(amax=amax, n=nA) - e_grid, pi_e, Pi = utils.discretize.markov_rouwenhorst(rho=rho_s, sigma=sigma_s, N=nS) - - # solve analytically what we can - B = B_Y - w = 1 / mu - Div = (1 - w) - Tax = r * B - T = transfers(pi_e, Div, Tax, e_grid) - - # initialize guess for policy function iteration - fininc = (1 + r) * a_grid + T[:, np.newaxis] - a_grid[0] - coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] + T[:, np.newaxis] - Va = (1 + r) * (0.1 * coh) ** (-1 / eis) - - # residual function - def res(x): - beta_loc, vphi_loc = x - # precompute constrained c and n which don't depend on Va - c_const_loc, n_const_loc = solve_cn(w * e_grid[:, np.newaxis], fininc, eis, frisch, vphi_loc, Va) - if beta_loc > 0.999 / (1 + r) or vphi_loc < 0.001: - raise ValueError('Clearly invalid inputs') - out = household.ss(Va=Va, Pi=Pi, a_grid=a_grid, e_grid=e_grid, pi_e=pi_e, w=w, r=r, beta=beta_loc, - eis=eis, Div=Div, Tax=Tax, frisch=frisch, vphi=vphi_loc, - c_const=c_const_loc, n_const=n_const_loc) - return np.array([out['A'] - B, out['N_e'] - 1]) - - # solve for beta, vphi - (beta, vphi), _ = utils.solvers.broyden_solver(res, np.array([beta_guess, vphi_guess]), verbose=False) - - # extra evaluation for reporting - c_const, n_const = solve_cn(w * e_grid[:, np.newaxis], fininc, eis, frisch, vphi, Va) - ss = household.ss(Va=Va, Pi=Pi, a_grid=a_grid, e_grid=e_grid, pi_e=pi_e, w=w, r=r, beta=beta, eis=eis, - Div=Div, Tax=Tax, frisch=frisch, vphi=vphi, c_const=c_const, n_const=n_const) - - # check Walras's law - goods_mkt = 1 - ss['C'] - assert np.abs(goods_mkt) < 1E-8 - - # add aggregate variables - ss.update({'B': B, 'phi': phi, 'kappa': kappa, 'Y': 1, 'rstar': r, 'Z': 1, 'mu': mu, 'L': 1, 'pi': 0, - 'rho_s': rho_s, 'labor_mkt': ss["N_e"] - 1, 'nA': nA, 'nS': nS, 'B_Y': B_Y, 'sigma_s': sigma_s, - 'goods_mkt': 1 - ss["C"], 'amax': amax, 'asset_mkt': ss["A"] - B, 'nkpc_res': kappa * (w - 1 / mu)}) - - # since we don't use c_const, n_const (an optimization of pre-calculating the constrained consumption and labor - # supply) in the general steady_state function, delete it from the ss dict so the test won't - # check for those variables - del ss["c_const"] - del ss["n_const"] - - return ss diff --git a/sequence_jacobian/models/krusell_smith.py b/sequence_jacobian/models/krusell_smith.py deleted file mode 100644 index 8bc5950..0000000 --- a/sequence_jacobian/models/krusell_smith.py +++ /dev/null @@ -1,125 +0,0 @@ -import numpy as np -import scipy.optimize as opt - -from .. import utilities as utils -from ..blocks.simple_block import simple -from ..blocks.het_block import het -from ..blocks.helper_block import helper - - -'''Part 1: HA block''' - - -def household_init(a_grid, e_grid, r, w, eis): - coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] - Va = (1 + r) * (0.1 * coh) ** (-1 / eis) - return Va - - -@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init) -def household(Va_p, Pi_p, a_grid, e_grid, r, w, beta, eis): - """Single backward iteration step using endogenous gridpoint method for households with CRRA utility. - - Parameters - ---------- - Va_p : array (S*A), marginal value of assets tomorrow - Pi_p : array (S*S), Markov matrix for skills tomorrow - a_grid : array (A), asset grid - e_grid : array (A), skill grid - r : scalar, ex-post real interest rate - w : scalar, wage - beta : scalar, discount rate today - eis : scalar, elasticity of intertemporal substitution - - Returns - ---------- - Va : array (S*A), marginal value of assets today - a : array (S*A), asset policy today - c : array (S*A), consumption policy today - """ - uc_nextgrid = (beta * Pi_p) @ Va_p - c_nextgrid = uc_nextgrid ** (-eis) - coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] - a = utils.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid) - utils.optimized_routines.setmin(a, a_grid[0]) - c = coh - a - Va = (1 + r) * c ** (-1 / eis) - return Va, a, c - - -'''Part 2: Simple Blocks''' - - -@simple -def firm(K, L, Z, alpha, delta): - r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta - w = (1 - alpha) * Z * (K(-1) / L) ** alpha - Y = Z * K(-1) ** alpha * L ** (1 - alpha) - return r, w, Y - - -@simple -def mkt_clearing(K, A, Y, C, delta): - asset_mkt = A - K - goods_mkt = Y - C - delta * K - return asset_mkt, goods_mkt - - -@simple -def income_state_vars(rho, sigma, nS): - e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho, sigma=sigma, N=nS) - return e_grid, Pi - - -@simple -def asset_state_vars(amax, nA): - a_grid = utils.discretize.agrid(amax=amax, n=nA) - return a_grid - - -@helper -def firm_steady_state_solution(r, delta, alpha): - rk = r + delta - Z = (rk / alpha) ** alpha # normalize so that Y=1 - K = (alpha * Z / rk) ** (1 / (1 - alpha)) - Y = Z * K ** alpha - w = (1 - alpha) * Z * (alpha * Z / rk) ** (alpha / (1 - alpha)) - - return Z, K, Y, w - - -'''Part 3: Steady state''' - - -def ks_ss(lb=0.98, ub=0.999, r=0.01, eis=1, delta=0.025, alpha=0.11, rho=0.966, sigma=0.5, - nS=7, nA=500, amax=200): - """Solve steady state of full GE model. Calibrate beta to hit target for interest rate.""" - # set up grid - a_grid = utils.discretize.agrid(amax=amax, n=nA) - e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho, sigma=sigma, N=nS) - - # solve for aggregates analytically - rk = r + delta - Z = (rk / alpha) ** alpha # normalize so that Y=1 - K = (alpha * Z / rk) ** (1 / (1 - alpha)) - Y = Z * K ** alpha - w = (1 - alpha) * Z * (alpha * Z / rk) ** (alpha / (1 - alpha)) - - # figure out initializer - coh = (1 + r) * a_grid[np.newaxis, :] + w * e_grid[:, np.newaxis] - Va = (1 + r) * (0.1 * coh) ** (-1 / eis) - - # solve for beta consistent with this - beta_min = lb / (1 + r) - beta_max = ub / (1 + r) - beta, sol = opt.brentq(lambda bet: household.ss(Pi=Pi, a_grid=a_grid, e_grid=e_grid, r=r, w=w, beta=bet, eis=eis, - Va=Va)['A'] - K, beta_min, beta_max, full_output=True) - if not sol.converged: - raise ValueError('Steady-state solver did not converge.') - - # extra evaluation to report variables - ss = household.ss(Pi=Pi, a_grid=a_grid, e_grid=e_grid, r=r, w=w, beta=beta, eis=eis, Va=Va) - ss.update({'Z': Z, 'K': K, 'L': 1, 'Y': Y, 'alpha': alpha, 'delta': delta, 'goods_mkt': Y - ss['C'] - delta * K, - 'nA': nA, 'amax': amax, 'sigma': sigma, 'rho': rho, 'nS': nS, 'asset_mkt': ss["A"] - K}) - - return ss diff --git a/sequence_jacobian/models/rbc.py b/sequence_jacobian/models/rbc.py deleted file mode 100644 index 7ff2b9b..0000000 --- a/sequence_jacobian/models/rbc.py +++ /dev/null @@ -1,87 +0,0 @@ -import numpy as np - -from ..blocks.simple_block import simple -from ..blocks.helper_block import helper - -'''Part 1: Simple blocks''' - - -@simple -def firm(K, L, Z, alpha, delta): - r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta - w = (1 - alpha) * Z * (K(-1) / L) ** alpha - Y = Z * K(-1) ** alpha * L ** (1 - alpha) - return r, w, Y - - -@simple -def household(K, L, w, eis, frisch, vphi, delta): - C = (w / vphi / L ** (1 / frisch)) ** eis - I = K - (1 - delta) * K(-1) - return C, I - - -@simple -def mkt_clearing(r, C, Y, I, K, L, w, eis, beta): - goods_mkt = Y - C - I - euler = C ** (-1 / eis) - beta * (1 + r(+1)) * C(+1) ** (-1 / eis) - walras = C + K - (1 + r) * K(-1) - w * L # we can the check dynamic version too - return goods_mkt, euler, walras - - -@helper -def steady_state_solution(r, eis, delta, alpha): - rk = r + delta - Z = (rk / alpha) ** alpha # normalize so that Y=1 - K = (alpha * Z / rk) ** (1 / (1 - alpha)) - Y = Z * K ** alpha - w = (1 - alpha) * Z * K ** alpha - I = delta * K - C = Y - I - beta = 1 / (1 + r) - vphi = w * C ** (-1 / eis) - - return Z, K, Y, w, I, C, beta, vphi - - -'''Part 2: Steady state''' - - -def rbc_ss(r=0.01, eis=1, frisch=1, delta=0.025, alpha=0.11): - """Solve steady state of simple RBC model. - - Parameters - ---------- - r : scalar, real interest rate - eis : scalar, elasticity of intertemporal substitution (1/sigma) - frisch : scalar, Frisch elasticity (1/nu) - delta : scalar, depreciation rate - alpha : scalar, capital share - - Returns - ------- - ss : dict, steady state values - """ - # solve for aggregates analytically - rk = r + delta - Z = (rk / alpha) ** alpha # normalize so that Y=1 - K = (alpha * Z / rk) ** (1 / (1 - alpha)) - Y = Z * K ** alpha - w = (1 - alpha) * Z * K ** alpha - I = delta * K - C = Y - I - - # preference params - beta = 1 / (1 + r) - vphi = w * C ** (-1 / eis) - - # check Walras's law, goods market clearing, and the euler equation - walras = C - r * K - w - goods_mkt = Y - C - I - euler = C ** (-1 / eis) - beta * (1 + r) * C ** (-1 / eis) - assert np.abs(walras) < 1E-12 - - return {'beta': beta, 'eis': eis, 'frisch': frisch, 'vphi': vphi, 'delta': delta, 'alpha': alpha, - 'Z': Z, 'K': K, 'I': I, 'Y': Y, 'L': 1, 'C': C, 'w': w, 'r': r, 'walras': walras, 'euler': euler, - 'goods_mkt': goods_mkt} - diff --git a/sequence_jacobian/models/two_asset.py b/sequence_jacobian/models/two_asset.py deleted file mode 100644 index 352f5b3..0000000 --- a/sequence_jacobian/models/two_asset.py +++ /dev/null @@ -1,415 +0,0 @@ -# pylint: disable=E1120 -import numpy as np -from numba import guvectorize - -from .. import utilities as utils -from ..blocks.simple_block import simple -from ..blocks.het_block import het, hetoutput -from ..blocks.helper_block import helper -from ..blocks.solved_block import solved -from ..blocks.support.simple_displacement import apply_function, Displace - -'''Part 1: HA block''' - - -def household_init(b_grid, a_grid, e_grid, eis, tax, w): - z_grid = income(e_grid, tax, w, 1) - Va = (0.6 + 1.1 * b_grid[:, np.newaxis] + a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) - Vb = (0.5 + b_grid[:, np.newaxis] + 1.2 * a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) - - return z_grid, Va, Vb - - -@het(exogenous='Pi', policy=['b', 'a'], backward=['Vb', 'Va'], backward_init=household_init) # order as in grid! -def household(Va_p, Vb_p, Pi_p, a_grid, b_grid, z_grid, e_grid, k_grid, beta, eis, rb, ra, chi0, chi1, chi2): - # require that k is decreasing (new) - assert k_grid[1] < k_grid[0], 'kappas in k_grid must be decreasing!' - - # precompute Psi1(a', a) on grid of (a', a) for steps 3 and 5 - Psi1 = get_Psi_and_deriv(a_grid[:, np.newaxis], - a_grid[np.newaxis, :], ra, chi0, chi1, chi2)[1] - - - # === STEP 2: Wb(z, b', a') and Wa(z, b', a') === - # (take discounted expectation of tomorrow's value function) - Wb = matrix_times_first_dim(beta*Pi_p, Vb_p) - Wa = matrix_times_first_dim(beta*Pi_p, Va_p) - W_ratio = Wa / Wb - - - # === STEP 3: a'(z, b', a) for UNCONSTRAINED === - - # for each (z, b', a), linearly interpolate to find a' between gridpoints - # satisfying optimality condition W_ratio == 1+Psi1 - i, pi = lhs_equals_rhs_interpolate(W_ratio, 1 + Psi1) - - # use same interpolation to get Wb and then c - a_endo_unc = utils.interpolate.apply_coord(i, pi, a_grid) - c_endo_unc = utils.interpolate.apply_coord(i, pi, Wb) ** (-eis) - - # === STEP 4: b'(z, b, a), a'(z, b, a) for UNCONSTRAINED === - - # solve out budget constraint to get b(z, b', a) - b_endo = (c_endo_unc + a_endo_unc + addouter(-z_grid, b_grid, -(1+ra)*a_grid) - + get_Psi_and_deriv(a_endo_unc, a_grid, ra, chi0, chi1, chi2)[0]) / (1 + rb) - - # interpolate this b' -> b mapping to get b -> b', so we have b'(z, b, a) - # and also use interpolation to get a'(z, b, a) - # (note utils.interpolate.interpolate_coord and utils.interpolate.apply_coord work on last axis, - # so we need to swap 'b' to the last axis, then back when done) - i, pi = utils.interpolate.interpolate_coord(b_endo.swapaxes(1, 2), b_grid) - a_unc = utils.interpolate.apply_coord(i, pi, a_endo_unc.swapaxes(1, 2)).swapaxes(1, 2) - b_unc = utils.interpolate.apply_coord(i, pi, b_grid).swapaxes(1, 2) - - - # === STEP 5: a'(z, kappa, a) for CONSTRAINED === - - # for each (z, kappa, a), linearly interpolate to find a' between gridpoints - # satisfying optimality condition W_ratio/(1+kappa) == 1+Psi1, assuming b'=0 - lhs_con = W_ratio[:, 0:1, :] / (1 + k_grid[np.newaxis, :, np.newaxis]) - i, pi = lhs_equals_rhs_interpolate(lhs_con, 1 + Psi1) - - # use same interpolation to get Wb and then c - a_endo_con = utils.interpolate.apply_coord(i, pi, a_grid) - c_endo_con = ((1 + k_grid[np.newaxis, :, np.newaxis])**(-eis) - * utils.interpolate.apply_coord(i, pi, Wb[:, 0:1, :]) ** (-eis)) - - - # === STEP 6: a'(z, b, a) for CONSTRAINED === - - # solve out budget constraint to get b(z, kappa, a), enforcing b'=0 - b_endo = (c_endo_con + a_endo_con - + addouter(-z_grid, np.full(len(k_grid), b_grid[0]), -(1+ra)*a_grid) - + get_Psi_and_deriv(a_endo_con, a_grid, ra, chi0, chi1, chi2)[0]) / (1 + rb) - - # interpolate this kappa -> b mapping to get b -> kappa - # then use the interpolated kappa to get a', so we have a'(z, b, a) - # (utils.interpolate.interpolate_y does this in one swoop, but since it works on last - # axis, we need to swap kappa to last axis, and then b back to middle when done) - a_con = utils.interpolate.interpolate_y(b_endo.swapaxes(1, 2), b_grid, - a_endo_con.swapaxes(1, 2)).swapaxes(1, 2) - - - # === STEP 7: obtain policy functions and update derivatives of value function === - - # combine unconstrained solution and constrained solution, choosing latter - # when unconstrained goes below minimum b - a, b = a_unc.copy(), b_unc.copy() - b[b <= b_grid[0]] = b_grid[0] - a[b <= b_grid[0]] = a_con[b <= b_grid[0]] - - # calculate adjustment cost and its derivative - Psi, _, Psi2 = get_Psi_and_deriv(a, a_grid, ra, chi0, chi1, chi2) - - # solve out budget constraint to get consumption and marginal utility - c = addouter(z_grid, (1+rb)*b_grid, (1+ra)*a_grid) - Psi - a - b - uc = c ** (-1 / eis) - - # for GE wage Phillips curve we'll need endowment-weighted utility too - u = e_grid[:, np.newaxis, np.newaxis] * uc - - # update derivatives of value function using envelope conditions - Va = (1 + ra - Psi2) * uc - Vb = (1 + rb) * uc - - return Va, Vb, a, b, c, u - - -def income(e_grid, tax, w, N): - z_grid = (1 - tax) * w * N * e_grid - return z_grid - - -household.add_hetinput(income, verbose=False) - - -# A potential hetoutput to include with the above HetBlock -@hetoutput() -def adjustment_costs(a, a_grid, r, chi0, chi1, chi2): - chi, _, _ = apply_function(get_Psi_and_deriv, a, a_grid, r, chi0, chi1, chi2) - return chi - - -"""Supporting functions for HA block""" - -def get_Psi_and_deriv(ap, a, ra, chi0, chi1, chi2): - """Adjustment cost Psi(ap, a) and its derivatives with respect to - first argument (ap) and second argument (a)""" - a_with_return = (1 + ra) * a - a_change = ap - a_with_return - abs_a_change = np.abs(a_change) - sign_change = np.sign(a_change) - - adj_denominator = a_with_return + chi0 - core_factor = (abs_a_change / adj_denominator) ** (chi2 - 1) - - Psi = chi1 / chi2 * abs_a_change * core_factor - Psi1 = chi1 * sign_change * core_factor - Psi2 = -(1 + ra)*(Psi1 + (chi2 - 1)*Psi/adj_denominator) - return Psi, Psi1, Psi2 - - -def matrix_times_first_dim(A, X): - """Take matrix A times vector X[:, i1, i2, i3, ... , in] separately - for each i1, i2, i3, ..., in. Same output as A @ X if X is 1D or 2D""" - # flatten all dimensions of X except first, then multiply, then restore shape - return (A @ X.reshape(X.shape[0], -1)).reshape(X.shape) - - -def addouter(z, b, a): - """Take outer sum of three arguments: result[i, j, k] = z[i] + b[j] + a[k]""" - return z[:, np.newaxis, np.newaxis] + b[:, np.newaxis] + a - - -@guvectorize(['void(float64[:], float64[:,:], uint32[:], float64[:])'], '(ni),(ni,nj)->(nj),(nj)') -def lhs_equals_rhs_interpolate(lhs, rhs, iout, piout): - """ - Given lhs (i) and rhs (i,j), for each j, find the i such that - - lhs[i] > rhs[i,j] and lhs[i+1] < rhs[i+1,j] - - i.e. where given j, lhs == rhs in between i and i+1. - - Also return the pi such that - - pi*(lhs[i] - rhs[i,j]) + (1-pi)*(lhs[i+1] - rhs[i+1,j]) == 0 - - i.e. such that the point at pi*i + (1-pi)*(i+1) satisfies lhs == rhs by linear interpolation. - - If lhs[0] < rhs[0,j] already, just return u=0 and pi=1. - - ***IMPORTANT: Assumes that solution i is monotonically increasing in j - and that lhs - rhs is monotonically decreasing in i.*** - """ - - ni, nj = rhs.shape - assert len(lhs) == ni - - i = 0 - for j in range(nj): - while True: - if lhs[i] < rhs[i, j]: - break - elif i < nj - 1: - i += 1 - else: - break - - if i == 0: - iout[j] = 0 - piout[j] = 1 - else: - iout[j] = i-1 - err_upper = rhs[i, j] - lhs[i] - err_lower = rhs[i-1, j] - lhs[i-1] - piout[j] = err_upper / (err_upper - err_lower) - - -'''Part 2: Simple blocks''' - - -@simple -def pricing(pi, mc, r, Y, kappap, mup): - nkpc = kappap * (mc - 1/mup) + Y(+1) / Y * (1 + pi(+1)).apply(np.log)\ - / (1 + r(+1)) - (1 + pi).apply(np.log) - return nkpc - - -@simple -def arbitrage(div, p, r): - equity = div(+1) + p(+1) - p * (1 + r(+1)) - return equity - - -@simple -def labor(Y, w, K, Z, alpha): - N = (Y / Z / K(-1) ** alpha) ** (1 / (1 - alpha)) - mc = w * N / (1 - alpha) / Y - return N, mc - - -@simple -def investment(Q, K, r, N, mc, Z, delta, epsI, alpha): - inv = (K/K(-1) - 1) / (delta * epsI) + 1 - Q - val = alpha * Z(+1) * (N(+1) / K) ** (1-alpha) * mc(+1) - (K(+1)/K - - (1-delta) + (K(+1)/K - 1)**2 / (2*delta*epsI)) + K(+1)/K*Q(+1) - (1 + r(+1))*Q - return inv, val - - -@simple -def dividend(Y, w, N, K, pi, mup, kappap, delta): - psip = mup / (mup - 1) / 2 / kappap * (1 + pi).apply(np.log) ** 2 * Y - I = K - (1 - delta) * K(-1) - div = Y - w * N - I - psip - return psip, I, div - - -@simple -def taylor(rstar, pi, phi): - i = rstar + phi * pi - return i - - -@simple -def fiscal(r, w, N, G, Bg): - tax = (r * Bg + G) / w / N - return tax - - -@simple -def finance(i, p, pi, r, div, omega, pshare): - rb = r - omega - ra = pshare * (div + p) / p(-1) + (1-pshare) * (1 + r) - 1 - fisher = 1 + i(-1) - (1 + r) * (1 + pi) - return rb, ra, fisher - - -@simple -def wage(pi, w, N, muw, kappaw): - piw = (1 + pi) * w / w(-1) - 1 - psiw = muw / (1 - muw) / 2 / kappaw * (1 + piw).apply(np.log) ** 2 * N - return piw, psiw - - -@simple -def union(piw, N, tax, w, U, kappaw, muw, vphi, frisch, beta): - wnkpc = kappaw * (vphi * N**(1+1/frisch) - muw*(1-tax)*w*N*U) + beta *\ - (1 + piw(+1)).apply(np.log) - (1 + piw).apply(np.log) - return wnkpc - - -@simple -def mkt_clearing(p, A, B, Bg, vphi, muw, tax, w, U): - asset_mkt = p + Bg - B - A - labor_mkt = vphi - muw * (1 - tax) * w * U - return asset_mkt, labor_mkt - - -@simple -def mkt_clearing_all(p, A, B, Bg, vphi, muw, tax, w, U, C, I, G, Chi, omega): - asset_mkt = p + Bg - B - A - labor_mkt = vphi - muw * (1 - tax) * w * U - goods_mkt = C + I + G + Chi + omega * B - 1 - return asset_mkt, labor_mkt, goods_mkt - - -@simple -def make_grids(bmax, amax, kmax, nB, nA, nK, nZ, rho_z, sigma_z): - b_grid = utils.discretize.agrid(amax=bmax, n=nB) - a_grid = utils.discretize.agrid(amax=amax, n=nA) - k_grid = utils.discretize.agrid(amax=kmax, n=nK)[::-1].copy() - e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho_z, sigma=sigma_z, N=nZ) - - return b_grid, a_grid, k_grid, e_grid, Pi - - -@helper -def partial_steady_state_solution(delta, K, r, tot_wealth, Bh, Bg, G, omega): - I = delta * K - mc = 1 - r * (tot_wealth - Bg - K) - alpha = (r + delta) * K / mc - mup = 1 / mc - Z = K ** (-alpha) - w = (1 - alpha) * mc - tax = (r * Bg + G) / w - div = 1 - w - I - p = div / r - ra = r - rb = r - omega - pshare = p / (tot_wealth - Bh) - - return I, mc, alpha, mup, Z, w, tax, div, p, ra, rb, pshare - - -'''Part 3: Steady state''' - - -def two_asset_ss(beta_guess=0.976, vphi_guess=2.07, chi1_guess=6.5, r=0.0125, tot_wealth=14, K=10, delta=0.02, kappap=0.1, - muw=1.1, Bh=1.04, Bg=2.8, G=0.2, eis=0.5, frisch=1, chi0=0.25, chi2=2, epsI=4, omega=0.005, kappaw=0.1, - phi=1.5, nZ=3, nB=50, nA=70, nK=50, bmax=50, amax=4000, kmax=1, rho_z=0.966, sigma_z=0.92, verbose=True): - """Solve steady state of full GE model. Calibrate (beta, vphi, chi1, alpha, mup, Z) to hit targets for - (r, tot_wealth, Bh, K, Y=N=1). - """ - - # set up grid - b_grid = utils.discretize.agrid(amax=bmax, n=nB) - a_grid = utils.discretize.agrid(amax=amax, n=nA) - k_grid = utils.discretize.agrid(amax=kmax, n=nK)[::-1].copy() - e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho_z, sigma=sigma_z, N=nZ) - - # solve analytically what we can - I = delta * K - mc = 1 - r * (tot_wealth - Bg - K) - alpha = (r + delta) * K / mc - mup = 1 / mc - Z = K ** (-alpha) - w = (1 - alpha) * mc - tax = (r * Bg + G) / w - div = 1 - w - I - p = div / r - ra = r - rb = r - omega - - # figure out initializer - z_grid = income(e_grid, tax, w, 1) - Va = (0.6 + 1.1 * b_grid[:, np.newaxis] + a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) - Vb = (0.5 + b_grid[:, np.newaxis] + 1.2 * a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) - - # residual function - def res(x): - beta_loc, vphi_loc, chi1_loc = x - if beta_loc > 0.999 / (1 + r) or vphi_loc < 0.001 or chi1_loc < 0.5: - raise ValueError('Clearly invalid inputs') - out = household.ss(Va=Va, Vb=Vb, Pi=Pi, a_grid=a_grid, b_grid=b_grid, N=1, tax=tax, w=w, e_grid=e_grid, - k_grid=k_grid, beta=beta_loc, eis=eis, rb=rb, ra=ra, chi0=chi0, chi1=chi1_loc, chi2=chi2) - asset_mkt = out['A'] + out['B'] - p - Bg - labor_mkt = vphi_loc - muw * (1 - tax) * w * out['U'] - return np.array([asset_mkt, labor_mkt, out['B'] - Bh]) - - # solve for beta, vphi, omega - (beta, vphi, chi1), _ = utils.solvers.broyden_solver(res, np.array([beta_guess, vphi_guess, chi1_guess]), - verbose=verbose) - - # extra evaluation to report variables - ss = household.ss(Va=Va, Vb=Vb, Pi=Pi, a_grid=a_grid, b_grid=b_grid, N=1, tax=tax, w=w, e_grid=e_grid, - k_grid=k_grid, beta=beta, eis=eis, rb=rb, ra=ra, chi0=chi0, chi1=chi1, chi2=chi2) - - # other things of interest - pshare = p / (tot_wealth - Bh) - - # calculate aggregate adjustment cost and check Walras's law - chi = get_Psi_and_deriv(ss['a'], a_grid, r, chi0, chi1, chi2)[0] - Chi = np.vdot(ss['D'], chi) - goods_mkt = ss['C'] + I + G + Chi + omega * ss['B'] - 1 - assert np.abs(goods_mkt) < 1E-7 - - ss.update({'pi': 0, 'piw': 0, 'Q': 1, 'Y': 1, 'N': 1, 'mc': mc, 'K': K, 'Z': Z, 'I': I, 'w': w, 'tax': tax, - 'div': div, 'p': p, 'r': r, 'Bg': Bg, 'G': G, 'chi': chi, 'Chi': Chi, 'phi': phi, - 'beta': beta, 'vphi': vphi, 'omega': omega, 'alpha': alpha, 'delta': delta, 'mup': mup, 'muw': muw, - 'frisch': frisch, 'epsI': epsI, 'a_grid': a_grid, 'b_grid': b_grid, 'z_grid': z_grid, 'e_grid': e_grid, - 'k_grid': k_grid, 'Pi': Pi, 'kappap': kappap, 'kappaw': kappaw, 'pshare': pshare, 'rstar': r, 'i': r, - 'tot_wealth': tot_wealth, 'fisher': 0, 'nZ': nZ, 'Bh': Bh, 'psiw': 0, 'psip': 0, 'inv': 0, - 'labor_mkt': vphi - muw * (1 - tax) * w * ss["U"], - 'equity': div + p - p * (1 + r), 'bmax': bmax, 'rho_z': rho_z, 'asset_mkt': p + Bg - ss["B"] - ss["A"], - 'nA': nA, 'nB': nB, 'amax': amax, 'kmax': kmax, 'nK': nK, 'nkpc': kappap * (mc - 1/mup), - 'wnkpc': kappaw * (vphi * ss["N"]**(1+1/frisch) - muw*(1-tax)*w*ss["N"]*ss["U"]), - 'sigma_z': sigma_z, 'val': alpha * Z * (ss["N"] / K) ** (1-alpha) * mc - delta - r}) - return ss - - -'''Part 4: Solved blocks for transition dynamics/Jacobian calculation''' -@solved(unknowns={'pi': (-0.1, 0.1)}, targets=['nkpc'], solver="brentq") -def pricing_solved(pi, mc, r, Y, kappap, mup): - nkpc = kappap * (mc - 1/mup) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) / \ - (1 + r(+1)) - (1 + pi).apply(np.log) - return nkpc - - -@solved(unknowns={'p': (10, 15)}, targets=['equity'], solver="brentq") -def arbitrage_solved(div, p, r): - equity = div(+1) + p(+1) - p * (1 + r(+1)) - return equity - - -production_solved = solved(block_list=[labor, investment], unknowns={'Q': 1, 'K': 10}, - targets=['inv', 'val'], solver="broyden_custom") diff --git a/sequence_jacobian/nonlinear.py b/sequence_jacobian/nonlinear.py deleted file mode 100644 index d942b68..0000000 --- a/sequence_jacobian/nonlinear.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Functions for solving for the non-linear transition dynamics provided a given shock path (e.g. solving MIT shocks)""" - -import numpy as np - -from . import utilities as utils -from . import jacobian as jac -from .blocks import het_block as het - - -def td_solve(ss, block_list, unknowns, targets, H_U=None, H_U_factored=None, monotonic=False, - returnindividual=False, tol=1E-8, maxit=30, verbose=True, save=False, use_saved=False, - grid_paths=None, **kwargs): - """Solves for GE nonlinear perfect foresight paths for SHADE model, given shocks in kwargs. - - Use a quasi-Newton method with the Jacobian H_U mapping unknowns to targets around steady state. - - Parameters - ---------- - ss : dict, all steady-state information - block_list : list, blocks in model (SimpleBlocks or HetBlocks) - unknowns : list, unknowns of SHADE DAG, the 'U' in H(U, Z) - targets : list, targets of SHADE DAG, the 'H' in H(U, Z) - H_U : [optional] array (nU*nU), Jacobian of targets with respect to unknowns - H_U_factored : [optional] tuple, LU decomposition of H_U, save time by supplying this from utils.misc.factor() - monotonic : [optional] bool, flag indicating HetBlock policy for some k' is monotonic in state k - (allows more efficient interpolation) - returnindividual: [optional] bool, flag to return individual outcomes from HetBlock.td - tol : [optional] scalar, for convergence of Newton's method we require |H| Dict[str, Union[Real, np.ndarray]]: - """Call the block's function attribute `.f` on the pre-processed steady state (keyword) arguments, - ensuring that any time displacements will be ignored when `.f` is called. - See blocks.support.simple_displacement for an example of how SimpleBlocks do this pre-processing.""" - return self.f(*ss_args, **ss_kwargs) - - @abc.abstractmethod - def td(self, ss: Dict[str, Real], shock_paths: Dict[str, np.ndarray], **kwargs) -> Dict[str, np.ndarray]: - pass - - @abc.abstractmethod - def jac(self, ss, shock_list, T): - pass - - -class Model(object): - __metaclass__ = abc.ABCMeta - - @abc.abstractmethod - def __init__(self, blocks: BlockArray, exogenous: List[str], unknowns: List[str], targets: List[str]) -> None: - self.blocks = blocks - self.exogenous = exogenous - self.unknowns = unknowns - self.targets = targets - - # TODO: Implement standard checks, as in CombinedBlock in SHADE, for cyclic dependence, the right number of - # unknowns and targets etc. to ensure that the model is well-defined. diff --git a/sequence_jacobian/utilities/__init__.py b/sequence_jacobian/utilities/__init__.py deleted file mode 100644 index e8d1bab..0000000 --- a/sequence_jacobian/utilities/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Utilities relating to: interpolation, forward step/transition, grids and Markov chains, solvers, sorting, etc.""" - -from . import differentiate, discretize, forward_step, graph, interpolate, misc, optimized_routines, solvers diff --git a/sequence_jacobian/utilities/devtools.py b/sequence_jacobian/utilities/devtools.py deleted file mode 100644 index 35671f2..0000000 --- a/sequence_jacobian/utilities/devtools.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Useful functions for debugging SSJ code""" - -import numpy as np - - -# Tools for upgrading from older SSJ code conventions -def compare_steady_states(ss_ref, ss_comp, name_map=None, verbose=True): - if name_map is None: - name_map = {} - - # Compare the steady state values present in both ss_ref and ss_comp - for key_ref in ss_ref.keys(): - if key_ref in ss_comp.keys(): - key_comp = key_ref - elif key_ref in name_map: - key_comp = name_map[key_ref] - else: - continue - if verbose: - if np.isscalar(ss_ref[key_ref]): - print(f"{key_ref} resid: {abs(ss_ref[key_ref] - ss_comp[key_comp])}") - else: - print(f"{key_ref} resid: {np.linalg.norm(ss_ref[key_ref] - ss_comp[key_comp], np.inf)}") - else: - assert np.isclose(ss_ref[key_ref], ss_comp[key_comp]) - - # Show the steady state values present in only one of ss_ref or ss_comp - ss_ref_incl_mapped = set(ss_ref.keys()) - set(name_map.keys()) - ss_comp_incl_mapped = set(ss_comp.keys()) - set(name_map.values()) - diff_keys = ss_ref_incl_mapped.symmetric_difference(ss_comp_incl_mapped) - if diff_keys: - print(f"The keys present in only one of the two steady state dicts are {diff_keys}") - diff --git a/sequence_jacobian/utilities/forward_step.py b/sequence_jacobian/utilities/forward_step.py deleted file mode 100644 index 96ffa3b..0000000 --- a/sequence_jacobian/utilities/forward_step.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Forward iteration of distribution on grid and related functions. - - - forward_step_1d - - forward_step_2d - - apply law of motion for distribution to go from D_{t-1} to D_t - - - forward_step_shock_1d - - forward_step_shock_2d - - forward_step linearized, used in part 1 of fake news algorithm to get curlyDs - - - forward_step_transpose_1d - - forward_step_transpose_2d - - transpose of forward_step, used in part 2 of fake news algorithm to get curlyPs -""" - -import numpy as np -from numba import njit - - -@njit -def forward_step_1d(D, Pi_T, x_i, x_pi): - """Single forward step to update distribution using exogenous Markov transition Pi and - policy x_i and x_pi for one-dimensional endogenous state. - - Efficient implementation of D_t = Lam_{t-1}' @ D_{t-1} using sparsity of the endogenous - part of Lam_{t-1}'. - - Note that it takes Pi_T, the transpose of Pi, as input rather than transposing itself; - this is so that when it is applied repeatedly, we can precalculate a transpose stored in - correct order rather than a view. - - Parameters - ---------- - D : array (S*X), beginning-of-period distribution over s_t, x_(t-1) - Pi_T : array (S*S), transpose Markov matrix that maps s_t to s_(t+1) - x_i : int array (S*X), left gridpoint of endogenous policy - x_pi : array (S*X), weight on left gridpoint of endogenous policy - - Returns - ---------- - Dnew : array (S*X), beginning-of-next-period dist s_(t+1), x_t - """ - - # first update using endogenous policy - nZ, nX = D.shape - Dnew = np.zeros_like(D) - for iz in range(nZ): - for ix in range(nX): - i = x_i[iz, ix] - pi = x_pi[iz, ix] - d = D[iz, ix] - Dnew[iz, i] += d * pi - Dnew[iz, i+1] += d * (1 - pi) - - # then using exogenous transition matrix - return Pi_T @ Dnew - - -def forward_step_2d(D, Pi_T, x_i, y_i, x_pi, y_pi): - """Like forward_step_1d but with two-dimensional endogenous state, policies given by x and y""" - Dmid = forward_step_endo_2d(D, x_i, y_i, x_pi, y_pi) - nZ, nX, nY = Dmid.shape - return (Pi_T @ Dmid.reshape(nZ, -1)).reshape(nZ, nX, nY) - - -@njit -def forward_step_endo_2d(D, x_i, y_i, x_pi, y_pi): - """Endogenous update part of forward_step_2d""" - nZ, nX, nY = D.shape - Dnew = np.zeros_like(D) - for iz in range(nZ): - for ix in range(nX): - for iy in range(nY): - ixp = x_i[iz, ix, iy] - iyp = y_i[iz, ix, iy] - beta = x_pi[iz, ix, iy] - alpha = y_pi[iz, ix, iy] - - Dnew[iz, ixp, iyp] += alpha * beta * D[iz, ix, iy] - Dnew[iz, ixp+1, iyp] += alpha * (1 - beta) * D[iz, ix, iy] - Dnew[iz, ixp, iyp+1] += (1 - alpha) * beta * D[iz, ix, iy] - Dnew[iz, ixp+1, iyp+1] += (1 - alpha) * (1 - beta) * D[iz, ix, iy] - return Dnew - - -@njit -def forward_step_shock_1d(Dss, Pi_T, x_i_ss, x_pi_shock): - """forward_step_1d linearized wrt x_pi""" - # first find effect of shock to endogenous policy - nZ, nX = Dss.shape - Dshock = np.zeros_like(Dss) - for iz in range(nZ): - for ix in range(nX): - i = x_i_ss[iz, ix] - dshock = x_pi_shock[iz, ix] * Dss[iz, ix] - Dshock[iz, i] += dshock - Dshock[iz, i + 1] -= dshock - - # then apply exogenous transition matrix to update - return Pi_T @ Dshock - - -def forward_step_shock_2d(Dss, Pi_T, x_i_ss, y_i_ss, x_pi_ss, y_pi_ss, x_pi_shock, y_pi_shock): - """forward_step_2d linearized wrt x_pi and y_pi""" - Dmid = forward_step_shock_endo_2d(Dss, x_i_ss, y_i_ss, x_pi_ss, y_pi_ss, x_pi_shock, y_pi_shock) - nZ, nX, nY = Dmid.shape - return (Pi_T @ Dmid.reshape(nZ, -1)).reshape(nZ, nX, nY) - - -@njit -def forward_step_shock_endo_2d(Dss, x_i_ss, y_i_ss, x_pi_ss, y_pi_ss, x_pi_shock, y_pi_shock): - """Endogenous update part of forward_step_shock_2d""" - nZ, nX, nY = Dss.shape - Dshock = np.zeros_like(Dss) - for iz in range(nZ): - for ix in range(nX): - for iy in range(nY): - ixp = x_i_ss[iz, ix, iy] - iyp = y_i_ss[iz, ix, iy] - alpha = x_pi_ss[iz, ix, iy] - beta = y_pi_ss[iz, ix, iy] - - dalpha = x_pi_shock[iz, ix, iy] * Dss[iz, ix, iy] - dbeta = y_pi_shock[iz, ix, iy] * Dss[iz, ix, iy] - - Dshock[iz, ixp, iyp] += dalpha * beta + alpha * dbeta - Dshock[iz, ixp+1, iyp] += dbeta * (1-alpha) - beta * dalpha - Dshock[iz, ixp, iyp+1] += dalpha * (1-beta) - alpha * dbeta - Dshock[iz, ixp+1, iyp+1] -= dalpha * (1-beta) + dbeta * (1-alpha) - return Dshock - - -@njit -def forward_step_transpose_1d(D, Pi, x_i, x_pi): - """Transpose of forward_step_1d""" - # first update using exogenous transition matrix - D = Pi @ D - - # then update using (transpose) endogenous policy - nZ, nX = D.shape - Dnew = np.zeros_like(D) - for iz in range(nZ): - for ix in range(nX): - i = x_i[iz, ix] - pi = x_pi[iz, ix] - Dnew[iz, ix] = pi * D[iz, i] + (1-pi) * D[iz, i+1] - return Dnew - - -def forward_step_transpose_2d(D, Pi, x_i, y_i, x_pi, y_pi): - """Transpose of forward_step_2d.""" - nZ, nX, nY = D.shape - Dmid = (Pi @ D.reshape(nZ, -1)).reshape(nZ, nX, nY) - return forward_step_transpose_endo_2d(Dmid, x_i, y_i, x_pi, y_pi) - - -@njit -def forward_step_transpose_endo_2d(D, x_i, y_i, x_pi, y_pi): - """Endogenous update part of forward_step_transpose_2d""" - nZ, nX, nY = D.shape - Dnew = np.empty_like(D) - for iz in range(nZ): - for ix in range(nX): - for iy in range(nY): - ixp = x_i[iz, ix, iy] - iyp = y_i[iz, ix, iy] - alpha = x_pi[iz, ix, iy] - beta = y_pi[iz, ix, iy] - - Dnew[iz, ix, iy] = (alpha * beta * D[iz, ixp, iyp] + alpha * (1-beta) * D[iz, ixp, iyp+1] + - (1-alpha) * beta * D[iz, ixp+1, iyp] + - (1-alpha) * (1-beta) * D[iz, ixp+1, iyp+1]) - return Dnew - - diff --git a/sequence_jacobian/utilities/graph.py b/sequence_jacobian/utilities/graph.py deleted file mode 100644 index cdc4ad3..0000000 --- a/sequence_jacobian/utilities/graph.py +++ /dev/null @@ -1,257 +0,0 @@ -"""Topological sort and related code""" - -from ..blocks.simple_block import SimpleBlock -from ..blocks.het_block import HetBlock -from ..blocks.helper_block import HelperBlock -from ..blocks.solved_block import SolvedBlock - - -def block_sort(block_list, ignore_helpers=False, calibration=None): - """Given list of blocks (either blocks themselves or dicts of Jacobians), find a topological sort. - - Relies on blocks having 'inputs' and 'outputs' attributes (unless they are dicts of Jacobians, in which case it's - inferred) that indicate their aggregate inputs and outputs - - Importantly, because including HelperBlocks in a block_list without additional measures - can introduce cycles within the DAG, allow the user to provide the calibration that will be used in the - steady_state computation to resolve these cycles. - e.g. Consider Krusell Smith: - Suppose one specifies a HelperBlock based on a calibrated value for "r", which outputs "K" (among other vars). - Normally block_sort would include the "firm" block as a dependency of the HelperBlock - because the "firm" block outputs "r", which the HelperBlock takes as an input. - However, it would also include the HelperBlock as a dependency of the "firm" block because the "firm" block takes - "K" as an input. - This would result in a cycle. However, if a "calibration" is provided in which "r" is included, then - "firm" could be removed as a dependency of HelperBlock and the cycle would be resolved. - - block_list: `list` - A list of the blocks (SimpleBlock, HetBlock, HelperBlock, etc.) to sort - ignore_helpers: `bool` - A boolean indicating whether to account for/return the indices of HelperBlocks contained in block_list - Set to true when sorting for td and jac calculations - calibration: `dict` or `None` - An optional dict of variable/parameter names and their pre-specified values to help resolve any cycles - introduced by using HelperBlocks. Read above docstring for more detail - """ - - # step 1: map outputs to blocks for topological sort - outmap = construct_output_map(block_list) - - # step 2: dependency graph for topological sort and input list - dep = construct_dependency_graph(block_list, outmap, calibration=calibration, ignore_helpers=ignore_helpers) - if ignore_helpers: - return ignore_helper_block_indices(topological_sort(dep), block_list) - else: - return topological_sort(dep) - - -def topological_sort(dep, names=None): - """Given directed graph pointing from each node to the nodes it depends on, topologically sort nodes""" - - # get complete set version of dep, and its reversal, and build initial stack of nodes with no dependencies - dep, revdep = complete_reverse_graph(dep) - nodeps = [n for n in dep if not dep[n]] - topsorted = [] - - # Kahn's algorithm: find something with no dependency, delete its edges and update - while nodeps: - n = nodeps.pop() - topsorted.append(n) - for n2 in revdep[n]: - dep[n2].remove(n) - if not dep[n2]: - nodeps.append(n2) - - # should be done: topsorted should be topologically sorted with same # of elements as original graphs! - if len(topsorted) != len(dep): - cycle_ints = find_cycle(dep, dep.keys() - set(topsorted)) - assert cycle_ints is not None, 'topological sort failed but no cycle, THIS SHOULD NEVER EVER HAPPEN' - cycle = [names[i] for i in cycle_ints] if names else cycle_ints - raise Exception(f'Topological sort failed: cyclic dependency {" -> ".join([str(n) for n in cycle])}') - - return topsorted - - -def ignore_helper_block_indices(topsorted, blocks): - return [i for i in topsorted if not isinstance(blocks[i], HelperBlock)] - - -def construct_output_map(block_list, ignore_helpers=False): - """Construct a map of outputs to the indices of the blocks that produce them. - - block_list: `list` - A list of the blocks (SimpleBlock, HetBlock, HelperBlock, etc.) to sort - ignore_helpers: `bool` - A boolean indicating whether to account for/return the indices of HelperBlocks contained in block_list - Set to true when sorting for td and jac calculations - """ - outmap = dict() - for num, block in enumerate(block_list): - if ignore_helpers and isinstance(block, HelperBlock): - continue - - # Find the relevant set of outputs corresponding to a block - if isinstance(block, SimpleBlock) or isinstance(block, HetBlock) or isinstance(block, HelperBlock) or isinstance(block, SolvedBlock): - outputs = block.outputs - elif isinstance(block, dict): - outputs = block.keys() - else: - raise ValueError(f'{block} is not recognized as block or does not provide outputs') - - for o in outputs: - # Because some of the outputs of a HelperBlock are, by construction, outputs that also appear in the - # standard blocks that comprise a DAG, ignore the fact that an output is repeated when considering - # throwing this ValueError - if o in outmap and not (isinstance(block, HelperBlock) or isinstance(block_list[outmap[o]], HelperBlock)): - raise ValueError(f'{o} is output twice') - - # Ensure that the block "outmap" maps "o" to is the actual block and not a HelperBlock if both share - # a given output, such that the dependency graph is constructed on the standard blocks, where possible - if o not in outmap or (o in outmap and not isinstance(block, HelperBlock)): - outmap[o] = num - else: - continue - return outmap - - -def construct_dependency_graph(block_list, outmap, calibration=None, ignore_helpers=False): - """Construct a dependency graph dictionary, with block indices as keys and a set of block indices as values, where - this set is the set of blocks that the key block is dependent on. - - outmap is the output map (output to block index mapping) created by construct_output_map. - - See the docstring of block_sort for more details about the other arguments. - """ - if calibration is None: - calibration = {} - dep = {num: set() for num in range(len(block_list))} - for num, block in enumerate(block_list): - if ignore_helpers and isinstance(block, HelperBlock): - continue - if hasattr(block, 'inputs'): - inputs = block.inputs - else: - inputs = set(i for o in block for i in block[o]) - for i in inputs: - # Each potential input to a given block will either be 1) output by another block, - # 2) an unknown or exogenous variable, or 3) a pre-specified variable/parameter passed into - # the steady-state computation via the `calibration' dict. - # If the block is a HelperBlock, then we want to check the calibration to see if the potential - # input is a pre-specified variable/parameter, and if it is then we will not add the block that - # produces that input as an output as a dependency. - # e.g. Krusell Smith's firm_steady_state_solution HelperBlock and firm block would create a cyclic - # dependency, if it were not for this resolution. - if i in outmap and not (i in calibration and isinstance(block, HelperBlock)): - dep[num].add(outmap[i]) - return dep - - -def find_outputs_that_are_intermediate_inputs(block_list, ignore_helpers=False): - """Find outputs of the blocks in block_list that are inputs to other blocks in block_list. - This is useful to ensure that all of the relevant curlyJ Jacobians (of all inputs to all outputs) are computed. - - See the docstring of construct_output_map for more details about the arguments. - """ - required = set() - outmap = construct_output_map(block_list, ignore_helpers=ignore_helpers) - for block in block_list: - if ignore_helpers and isinstance(block, HelperBlock): - continue - if hasattr(block, 'inputs'): - inputs = block.inputs - else: - inputs = set(i for o in block for i in block[o]) - for i in inputs: - if i in outmap: - required.add(i) - return required - - -def complete_reverse_graph(gph): - """Given directed graph represented as a dict from nodes to iterables of nodes, return representation of graph that - is complete (i.e. has each vertex pointing to some iterable, even if empty), and a complete version of reversed too. - Have returns be sets, for easy removal""" - - revgph = {n: set() for n in gph} - for n, e in gph.items(): - for n2 in e: - n2_edges = revgph.setdefault(n2, set()) - n2_edges.add(n) - - gph_missing_n = revgph.keys() - gph.keys() - gph = {**{k: set(v) for k, v in gph.items()}, **{n: set() for n in gph_missing_n}} - return gph, revgph - - -def find_cycle(dep, onlyset=None): - """Return list giving cycle if there is one, otherwise None""" - - # supposed to look only within 'onlyset', so filter out everything else - if onlyset is not None: - dep = {k: (set(v) & set(onlyset)) for k, v in dep.items() if k in onlyset} - - tovisit = set(dep.keys()) - stack = SetStack() - while tovisit or stack: - if stack: - # if stack has something, still need to proceed with DFS - n = stack.top() - if dep[n]: - # if there are any dependencies left, let's look at them - n2 = dep[n].pop() - if n2 in stack: - # we have a cycle, since this is already in our stack - i2loc = stack.index(n2) - return stack[i2loc:] + [stack[i2loc]] - else: - # no cycle, visit this node only if we haven't already visited it - if n2 in tovisit: - tovisit.remove(n2) - stack.add(n2) - else: - # if no dependencies left, then we're done with this node, so let's forget about it - stack.pop(n) - else: - # nothing left on stack, let's start the DFS from something new - n = tovisit.pop() - stack.add(n) - - # if we never find a cycle, we're done - return None - - -class SetStack: - """Stack implemented with list but tests membership with set to be efficient in big cases""" - - def __init__(self): - self.myset = set() - self.mylist = [] - - def add(self, x): - self.myset.add(x) - self.mylist.append(x) - - def pop(self): - x = self.mylist.pop() - self.myset.remove(x) - return x - - def top(self): - return self.mylist[-1] - - def index(self, x): - return self.mylist.index(x) - - def __contains__(self, x): - return x in self.myset - - def __len__(self): - return len(self.mylist) - - def __getitem__(self, i): - return self.mylist.__getitem__(i) - - def __repr__(self): - return self.mylist.__repr__() - - diff --git a/sequence_jacobian/utilities/misc.py b/sequence_jacobian/utilities/misc.py deleted file mode 100644 index a9dbef9..0000000 --- a/sequence_jacobian/utilities/misc.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Assorted other utilities""" - -import numpy as np -import scipy.linalg -import re -import inspect - - -def make_tuple(x): - """If not tuple or list, make into tuple with one element. - - Wrapping with this allows user to write, e.g.: - "return r" rather than "return (r,)" - "policy='a'" rather than "policy=('a',)" - """ - return (x,) if not (isinstance(x, tuple) or isinstance(x, list)) else x - - -def input_list(f): - """Return list of function inputs""" - return inspect.getfullargspec(f).args - - -def output_list(f): - """Scans source code of function to detect statement like - - 'return L, Div' - - and reports the list ['L', 'Div']. - - Important to write functions in this way when they will be scanned by output_list, for - either SimpleBlock or HetBlock. - """ - return re.findall('return (.*?)\n', inspect.getsource(f))[-1].replace(' ', '').split(',') - - -def demean(x): - return x - x.sum()/x.size - - -# simpler aliases for LU factorization and solution -def factor(X): - return scipy.linalg.lu_factor(X) - - -def factored_solve(Z, y): - return scipy.linalg.lu_solve(Z, y) - - -# functions for handling saved Jacobians: extract keys from dicts or key pairs -# from nested dicts, and take subarrays with 'shape' of the values -def extract_dict(savedA, keys, shape): - return {k: take_subarray(savedA[k], shape) for k in keys} - - -def extract_nested_dict(savedA, keys1, keys2, shape): - return {k1: {k2: take_subarray(savedA[k1][k2], shape) for k2 in keys2} for k1 in keys1} - - -def take_subarray(A, shape): - # verify leading dimensions of A are >= shape - if not all(m <= n for m, n in zip(shape, A.shape)): - raise ValueError(f'Saved has dimensions {A.shape}, want larger {shape} subarray') - - # take subarray along those dimensions: A[:shape, ...] - return A[tuple(slice(None, x, None) for x in shape) + (Ellipsis,)] - - -def uncapitalize(s): - # Similar to s.lower() but only makes the first character lower-case - return s[0].lower() + s[1:] - - -# The below functions are used in steady_state -def unprime(s): - """Given a variable's name as a `str`, check if the variable is a prime, i.e. has "_p" at the end. - If so, return the unprimed version, if not return itself.""" - if s[-2:] == "_p": - return s[:-2] - else: - return s - - -def dict_diff(d1, d2): - """Returns the dictionary that is the "set difference" between d1 and d2 (based on keys, not key-value pairs) - E.g. d1 = {"a": 1, "b": 2}, d2 = {"b": 5}, then dict_diff(d1, d2) = {"a": 1} - """ - o_dict = {} - for k in set(d1.keys()).difference(set(d2.keys())): - o_dict[k] = d1[k] - - return o_dict - - -def smart_zip(keys, values): - """For handling the case where keys and values may be scalars""" - if isinstance(values, float): - return zip(keys, [values]) - else: - return zip(keys, values) - - -def smart_zeros(n): - """Return either the float 0. or a np.ndarray of length 0 depending on whether n > 1""" - if n > 1: - return np.zeros(n) - else: - return 0. - - -def find_blocks_with_hetoutputs(blocks): - return [i for i, block in enumerate(blocks) if hasattr(block, "hetoutput") and block.hetoutput is not None] diff --git a/sequence_jacobian/visualization/__init__.py b/sequence_jacobian/visualization/__init__.py deleted file mode 100644 index 2850057..0000000 --- a/sequence_jacobian/visualization/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Various tools for plotting and creating visualizations""" diff --git a/sequence_jacobian/visualization/draw_dag.py b/sequence_jacobian/visualization/draw_dag.py deleted file mode 100644 index 65665a3..0000000 --- a/sequence_jacobian/visualization/draw_dag.py +++ /dev/null @@ -1,262 +0,0 @@ -"""Provides the functionality for basic DAG visualization""" - -import warnings - -# Force warnings.warn() to omit the source code line in the message -formatwarning_orig = warnings.formatwarning -warnings.formatwarning = lambda message, category, filename, lineno, line=None: \ - formatwarning_orig(message, category, filename, lineno, line='') - -from .. import utilities as utils -from ..blocks.helper_block import HelperBlock - - -# Implement DAG drawing functions as "soft" dependencies to not enforce the installation of graphviz, since -# it's not required for the rest of the sequence-jacobian code to run -try: - """ - DAG Graph routine - Requires installing graphviz package and executables - https://www.graphviz.org/ - - On a mac this can be done as follows: - 1) Download macports at: - https://www.macports.org/install.php - 2) On the command line, install graphviz with macports by typing - sudo port install graphviz - - """ - from graphviz import Digraph - - # TODO: Integrate the givedep and giveio functionality into the base block_sort functionality in SSJ - # Enhanced block sort - def block_sort_enhanced(block_list, findrequired=False, givedep=False, giveio=False, ignore_helpers=True): - """Given list of blocks (either blocks themselves or dicts of Jacobians), find a topological sort and also - optionally return which outputs must be computed as inputs of later blocks. - - Relies on blocks having 'inputs' and 'outputs' attributes (unless they are dicts of Jacobians, in which case it's - inferred) that indicate their aggregate inputs and outputs""" - - # step 1: map outputs to blocks for topological sort - outmap = dict() - outset = set() - - for num, block in enumerate(block_list): - if ignore_helpers and isinstance(block, HelperBlock): - continue - else: - if hasattr(block, 'outputs'): - outputs = block.outputs - elif isinstance(block, dict): - outputs = block.keys() - else: - raise ValueError(f'{block} is not recognized as block or does not provide outputs') - - for o in outputs: - if o in outmap: - raise ValueError(f'{o} is output twice') - outmap[o] = num - if giveio: - outset.add(o) - - # step 2: dependency graph for topological sort and input list - if ignore_helpers: - dep = {num: set() for num in range(len(block_list))} - else: - dep = {num: set() for num in range(len(block_list))} - inset = set() - if findrequired: - required = set() - for num, block in enumerate(block_list): - if ignore_helpers and isinstance(block, HelperBlock): - continue - else: - if hasattr(block, 'inputs'): - inputs = block.inputs - else: - inputs = set(i for o in block for i in block[o]) - - for i in inputs: - if giveio: - inset.add(i) - if i in outmap: - dep[num].add(outmap[i]) - if findrequired: - required.add(i) - - if givedep: - if findrequired: - if giveio: - return dep, required, inset, outset - else: - return dep, inset, outset - else: - return dep - else: - if ignore_helpers: - dep_sorted = utils.graph.ignore_helper_block_indices(utils.graph.topological_sort(dep), block_list) - else: - dep_sorted = utils.graph.topological_sort(dep) - - if findrequired: - if giveio: - return dep_sorted, required, inset, outset - else: - return dep_sorted, required - else: - return dep_sorted - - - def draw_dag(block_list, exogenous=None, unknowns=None, targets=None, - showdag=False, debug=False, leftright=False, filename='modeldag'): - """ - Visualizes a Directed Acyclic Graph (DAG) of a set of blocks, exogenous variables, unknowns, and targets - - block_list: `list` - Blocks to be represented as nodes within a DAG - exogenous: `list` (optional) - Exogenous variables, to be represented on DAG - unknowns: `list` (optional) - Unknown variables, to be represented on DAG - targets: `list` (optional) - Target variables, to be represented on DAG - showdag: `bool` - If True, export and plot pdf file. If false, export png file and do not plot - debug: `bool` - If True, returns list of candidate unknown and targets - leftright: `bool` - If True, plots DAG from left to right instead of top to bottom - - return: None - """ - - # To prevent having mutable variables as keyword arguments - exogenous = [] if exogenous is None else exogenous - unknowns = [] if unknowns is None else unknowns - targets = [] if targets is None else targets - - # obtain the topological sort - topsorted = block_sort_enhanced(block_list) - # get sorted list of blocks - block_list_sorted = [block_list[i] for i in topsorted] - # Obtain the dependency list of the sorted set of blocks - dep_list_sorted = block_sort_enhanced(block_list_sorted, givedep=True) - - # Draw DAG - dot = Digraph(comment='Model DAG') - - # Make left-to-right - if leftright: - dot.attr(rankdir='LR', ratio='compress', center='true') - else: - dot.attr(ratio='auto', center='true') - - # add initial nodes (one for exogenous, one for unknowns) provided those are not empty lists - if exogenous: - dot.node('exog', 'exogenous', shape='box') - if unknowns: - dot.node('unknowns', 'unknowns', shape='box') - if targets: - dot.node('targets', 'targets', shape='diamond') - - # add nodes sequentially in order - for i in dep_list_sorted: - if hasattr(block_list_sorted[i], 'hetinput'): - # HA block - dot.node(str(i), 'HA [' + str(i) + ']') - elif hasattr(block_list_sorted[i], 'block_list'): - # Solved block - dot.node(str(i), block_list_sorted[i].block_list[0].f.__name__ + '[solved,' + str(i) + ']') - else: - # Simple block - dot.node(str(i), block_list_sorted[i].f.__name__ + ' [' + str(i) + ']') - - # nodes from exogenous to i (figure out if needed and draw) - if exogenous: - edgelabel = block_list_sorted[i].inputs & set(exogenous) - if len(edgelabel) != 0: - edgelabel_list = list(edgelabel) - edgelabel_str = ', '.join(str(e) for e in edgelabel_list) - dot.edge('exog', str(i), label=str(edgelabel_str)) - - # nodes from unknowns to i (figure out if needed and draw) - if unknowns: - edgelabel = block_list_sorted[i].inputs & set(unknowns) - if len(edgelabel) != 0: - edgelabel_list = list(edgelabel) - edgelabel_str = ', '.join(str(e) for e in edgelabel_list) - dot.edge('unknowns', str(i), label=str(edgelabel_str)) - - # nodes from i to final targets - for target in targets: - if target in block_list_sorted[i].outputs: - dot.edge(str(i), 'targets', label=target) - - # nodes from any interior block to i - for j in dep_list_sorted[i]: - # figure out inputs of i that are also outputs of j - edgelabel = block_list_sorted[i].inputs & block_list_sorted[j].outputs - edgelabel_list = list(edgelabel) - edgelabel_str = ', '.join(str(e) for e in edgelabel_list) - - # draw edge from j to i - dot.edge(str(j), str(i), label=str(edgelabel_str)) - - if showdag: - dot.render('dagexport/' + filename, view=True, cleanup=True) - else: - dot.render('dagexport/' + filename, format='png', cleanup=True) - # print(dot.source) - - if debug: - dep, required, inputs, outputs = block_sort_enhanced(block_list_sorted, findrequired=True, - givedep=False, giveio=True) - # Candidate targets: outputs that are not inputs to any block - print("Candidate targets :") - cand_targets = outputs.difference(required) - print(cand_targets) - # Candidate exogenous and unknowns (also includes parameters) - # inputs that are not outputs of any block - print("Candidate exogenous/unknowns :") - cand_xu = inputs.difference(required) - print(cand_xu) - - - def draw_solved(solvedblock, filename='solveddag'): - # Inspects a solved block by drawing its DAG - draw_dag([solvedblock.block_list[0]], unknowns=solvedblock.unknowns, targets=solvedblock.targets, - filename=filename, showdag=True) - - - def inspect_solved(block_list): - # Inspects all the solved blocks by running through each and drawing its DAG in turn - for block in block_list: - if hasattr(block, 'block_list'): - draw_solved(block, filename=str(block.block_list[0].f.__name__)) -except ImportError: - def block_sort_enhanced(*args, **kwargs): - warnings.warn("\nThe package `graphviz` has not yet been installed. \n" - "`block_sort_enhanced` is meant to aid in DAG visualization. \n" - "For now, please use `block_sort` instead.") - pass - - - def draw_dag(*args, **kwargs): - warnings.warn("\nAttempted to use `draw_dag` when the package `graphviz` has not yet been installed. \n" - "DAG visualization tools, i.e. draw_dag, will not produce any figures unless this dependency has been installed. \n" - "Once installed, re-load sequence-jacobian to produce DAG figures.") - pass - - - def draw_solved(*args, **kwargs): - warnings.warn("\nAttempted to use `draw_solved` when the package `graphviz` has not yet been installed. \n" - "DAG visualization tools, i.e. draw_dag, will not produce any figures unless this dependency has been installed. \n" - "Once installed, re-load sequence-jacobian to produce DAG figures.") - pass - - - def inspect_solved(*args, **kwargs): - warnings.warn("\nAttempted to use `inspect_solved` when the package `graphviz` has not yet been installed. \n" - "DAG visualization tools, i.e. draw_dag, will not produce any figures unless this dependency has been installed. \n" - "Once installed, re-load sequence-jacobian to produce DAG figures.") - pass diff --git a/setup.py b/setup.py index c1f64c3..2e69c24 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from pathlib import Path -from setuptools import find_packages, setup +from setuptools import setup, find_packages with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() @@ -12,7 +12,6 @@ setup( name="sequence-jacobian", - packages=find_packages(), python_requires=">=3.7", install_requires=read("requirements.txt").splitlines(), version="0.0.1", @@ -27,4 +26,7 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], + + packages=find_packages(where='src'), + package_dir={'': 'src'}, ) diff --git a/src/sequence_jacobian/__init__.py b/src/sequence_jacobian/__init__.py new file mode 100644 index 0000000..2e14daf --- /dev/null +++ b/src/sequence_jacobian/__init__.py @@ -0,0 +1,25 @@ +"""Public-facing objects.""" + +from . import estimation, utilities + +from .blocks.simple_block import simple +from .blocks.het_block import het +from .blocks.solved_block import solved +from .blocks.combined_block import combine, create_model +from .blocks.support.simple_displacement import apply_function +from .classes.steady_state_dict import SteadyStateDict +from .classes.impulse_dict import ImpulseDict +from .classes.jacobian_dict import JacobianDict + +# Useful utilities for setting up HetBlocks +from .utilities.discretize import agrid, markov_rouwenhorst, markov_tauchen +from .utilities.interpolate import interpolate_y +from .utilities.optimized_routines import setmin + +# Ensure warning uniformity across package +import warnings + +# Force warnings.warn() to omit the source code line in the message +formatwarning_orig = warnings.formatwarning +warnings.formatwarning = lambda message, category, filename, lineno, line=None: \ + formatwarning_orig(message, category, filename, lineno, line='') diff --git a/sequence_jacobian/blocks/__init__.py b/src/sequence_jacobian/blocks/__init__.py similarity index 100% rename from sequence_jacobian/blocks/__init__.py rename to src/sequence_jacobian/blocks/__init__.py diff --git a/src/sequence_jacobian/blocks/auxiliary_blocks/__init__.py b/src/sequence_jacobian/blocks/auxiliary_blocks/__init__.py new file mode 100644 index 0000000..d4a7666 --- /dev/null +++ b/src/sequence_jacobian/blocks/auxiliary_blocks/__init__.py @@ -0,0 +1 @@ +"""Auxiliary Block types for building a coherent backend for Block handling""" diff --git a/src/sequence_jacobian/blocks/auxiliary_blocks/jacobiandict_block.py b/src/sequence_jacobian/blocks/auxiliary_blocks/jacobiandict_block.py new file mode 100644 index 0000000..03fbddc --- /dev/null +++ b/src/sequence_jacobian/blocks/auxiliary_blocks/jacobiandict_block.py @@ -0,0 +1,23 @@ +"""A simple wrapper for JacobianDicts to be embedded in DAGs""" + +from ..block import Block +from ...classes import ImpulseDict, JacobianDict + +class JacobianDictBlock(JacobianDict, Block): + """A wrapper for nested dicts/JacobianDicts passed directly into DAGs to ensure method compatibility""" + def __init__(self, nesteddict, outputs=None, inputs=None, name=None): + super().__init__(nesteddict, outputs=outputs, inputs=inputs, name=name) + Block.__init__(self) + + def __repr__(self): + return f"" + + def _impulse_linear(self, ss, inputs, outputs, Js): + return ImpulseDict(self.jacobian(ss, list(inputs.keys()), outputs, inputs.T, Js).apply(inputs)) + + def _jacobian(self, ss, inputs, outputs, T): + if not inputs <= self.inputs: + raise KeyError(f'Asking JacobianDictBlock for {inputs - self.inputs}, which are among its inputs {self.inputs}') + if not outputs <= self.outputs: + raise KeyError(f'Asking JacobianDictBlock for {outputs - self.outputs}, which are among its outputs {self.outputs}') + return self[outputs, inputs] diff --git a/src/sequence_jacobian/blocks/block.py b/src/sequence_jacobian/blocks/block.py new file mode 100644 index 0000000..2de721d --- /dev/null +++ b/src/sequence_jacobian/blocks/block.py @@ -0,0 +1,344 @@ +"""Primitives to provide clarity and structure on blocks/models work""" + +import numpy as np +from numbers import Real +from typing import Any, Dict, Union, Tuple, Optional, List +from copy import deepcopy + +from .support.steady_state import provide_solver_default, solve_for_unknowns, compute_target_values +from .support.parent import Parent +from ..utilities import misc +from ..utilities.function import input_defaults +from ..utilities.bijection import Bijection +from ..utilities.ordered_set import OrderedSet +from ..classes import SteadyStateDict, UserProvidedSS, ImpulseDict, JacobianDict, FactoredJacobianDict + +Array = Any + +class Block: + """The abstract base class for all `Block` objects.""" + + def __init__(self): + self.M = Bijection({}) + + self.steady_state_options = self.input_defaults_smart('_steady_state') + self.impulse_nonlinear_options = self.input_defaults_smart('_impulse_nonlinear') + self.impulse_linear_options = self.input_defaults_smart('_impulse_linear') + self.jacobian_options = self.input_defaults_smart('_jacobian') + self.partial_jacobians_options = self.input_defaults_smart('_partial_jacobians') + + def inputs(self): + pass + + def outputs(self): + pass + + def steady_state(self, calibration: Union[SteadyStateDict, UserProvidedSS], + dissolve: List[str] = [], options: Dict[str, dict] = {}, **kwargs) -> SteadyStateDict: + """Evaluate a partial equilibrium steady state of Block given a `calibration`.""" + inputs = self.inputs.copy() + if isinstance(self, Parent): + for k in dissolve: + inputs |= self.get_attribute(k, 'unknowns').keys() + + calibration = SteadyStateDict(calibration)[inputs] + own_options = self.get_options(options, kwargs, 'steady_state') + if isinstance(self, Parent): + return self.M @ self._steady_state(self.M.inv @ calibration, dissolve=dissolve, + options=options, **own_options) + else: + return self.M @ self._steady_state(self.M.inv @ calibration, **own_options) + + def impulse_nonlinear(self, ss: SteadyStateDict, inputs: Union[Dict[str, Array], ImpulseDict], + outputs: Optional[List[str]] = None, + internals: Union[Dict[str, List[str]], List[str]] = {}, + Js: Dict[str, JacobianDict] = {}, options: Dict[str, dict] = {}, + ss_initial: Optional[SteadyStateDict] = None, **kwargs) -> ImpulseDict: + """Calculate a partial equilibrium, non-linear impulse response of `outputs` to a set of shocks in `inputs` + around a steady state `ss`.""" + own_options = self.get_options(options, kwargs, 'impulse_nonlinear') + inputs = ImpulseDict(inputs) + actual_outputs, inputs_as_outputs = self.process_outputs(ss, + self.make_ordered_set(inputs), self.make_ordered_set(outputs)) + + if isinstance(self, Parent): + # SolvedBlocks may use Js and may be nested in a CombinedBlock, so we need to pass them down to any parent + out = self.M @ self._impulse_nonlinear(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ actual_outputs, internals, Js, options, self.M.inv @ ss_initial, **own_options) + elif hasattr(self, 'internals'): + out = self.M @ self._impulse_nonlinear(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ actual_outputs, self.internals_to_report(internals), self.M.inv @ ss_initial, **own_options) + else: + out = self.M @ self._impulse_nonlinear(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ actual_outputs, self.M.inv @ ss_initial, **own_options) + + return inputs[inputs_as_outputs] | out + + def impulse_linear(self, ss: SteadyStateDict, inputs: Union[Dict[str, Array], ImpulseDict], + outputs: Optional[List[str]] = None, Js: Dict[str, JacobianDict] = {}, + options: Dict[str, dict] = {}, **kwargs) -> ImpulseDict: + """Calculate a partial equilibrium, linear impulse response of `outputs` to a set of shocks in `inputs` + around a steady state `ss`.""" + own_options = self.get_options(options, kwargs, 'impulse_linear') + inputs = ImpulseDict(inputs) + actual_outputs, inputs_as_outputs = self.process_outputs(ss, self.make_ordered_set(inputs), self.make_ordered_set(outputs)) + + if isinstance(self, Parent): + out = self.M @ self._impulse_linear(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ actual_outputs, Js, options, **own_options) + else: + out = self.M @ self._impulse_linear(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ actual_outputs, Js, **own_options) + + return inputs[inputs_as_outputs] | out + + def partial_jacobians(self, ss: SteadyStateDict, inputs: Optional[List[str]] = None, outputs: Optional[List[str]] = None, + T: Optional[int] = None, Js: Dict[str, JacobianDict] = {}, options: Dict[str, dict] = {}, **kwargs): + if inputs is None: + inputs = self.inputs + if outputs is None: + outputs = self.outputs + + # if you have a J for this block that already has everything you need, use it + # TODO: add check for T, maybe look at verify_saved_jacobian for ideas? + if (self.name in Js) and isinstance(Js[self.name], JacobianDict) and (inputs <= Js[self.name].inputs) and (outputs <= Js[self.name].outputs): + return {self.name: Js[self.name][outputs, inputs]} + + # if it's a leaf, just call Jacobian method, include if nonzero + if not isinstance(self, Parent): + own_options = self.get_options(options, kwargs, 'jacobian') + jac = self.jacobian(ss, inputs, outputs, T, **own_options) + return {self.name: jac} if jac else {} + + # otherwise call child method with remapping (and remap your own but none of the child Js) + own_options = self.get_options(options, kwargs, 'partial_jacobians') + partial = self._partial_jacobians(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ outputs, T, Js, options, **own_options) + if self.name in partial: + partial[self.name] = self.M @ partial[self.name] + return partial + + def jacobian(self, ss: SteadyStateDict, inputs: List[str], + outputs: Optional[List[str]] = None, + T: Optional[int] = None, Js: Dict[str, JacobianDict] = {}, + options: Dict[str, dict] = {}, **kwargs) -> JacobianDict: + """Calculate a partial equilibrium Jacobian to a set of `input` shocks at a steady state `ss`.""" + own_options = self.get_options(options, kwargs, 'jacobian') + inputs = self.make_ordered_set(inputs) + outputs, _ = self.process_outputs(ss, {}, self.make_ordered_set(outputs)) + + # if you have a J for this block that has everything you need, use it + if (self.name in Js) and isinstance(Js[self.name], JacobianDict) and (inputs <= Js[self.name].inputs) and (outputs <= Js[self.name].outputs): + return Js[self.name][outputs, inputs] + + # if it's a leaf, call Jacobian method, don't supply Js + if not isinstance(self, Parent): + return self.M @ self._jacobian(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ outputs, T, **own_options) + + # otherwise remap own J (currently needed for SolvedBlock only) + Js = Js.copy() + if self.name in Js: + Js[self.name] = self.M.inv @ Js[self.name] + return self.M @ self._jacobian(self.M.inv @ ss, self.M.inv @ inputs, self.M.inv @ outputs, T=T, Js=Js, options=options, **own_options) + + solve_steady_state_options = dict(solver="", solver_kwargs={}, ttol=1e-12, ctol=1e-9, + verbose=False, constrained_method="linear_continuation", constrained_kwargs={}) + + def solve_steady_state(self, calibration: Dict[str, Union[Real, Array]], + unknowns: Dict[str, Union[Real, Tuple[Real, Real]]], + targets: Union[Array, Dict[str, Union[str, Real]]], + dissolve: List = [], options: Dict[str, dict] = {}, **kwargs): + """Evaluate a general equilibrium steady state of Block given a `calibration` + and a set of `unknowns` and `targets` corresponding to the endogenous variables to be solved for and + the target conditions that must hold in general equilibrium""" + opts = self.get_options(options, kwargs, 'solve_steady_state') + + ss = SteadyStateDict(calibration) + + solver = opts['solver'] if opts['solver'] else provide_solver_default(unknowns) + + def residual(unknown_values, unknowns_keys=unknowns.keys(), targets=targets): + ss.update(misc.smart_zip(unknowns_keys, unknown_values)) + ss.update(self.steady_state(ss, dissolve=dissolve, options=options, **kwargs)) + return compute_target_values(targets, ss) + + _ = solve_for_unknowns(residual, unknowns, solver, opts['solver_kwargs'], + tol=opts['ttol'], verbose=opts['verbose'], + constrained_method=opts['constrained_method'], + constrained_kwargs=opts['constrained_kwargs']) + + return ss + + solve_impulse_nonlinear_options = dict(tol=1E-8, maxit=30, verbose=True) + + def solve_impulse_nonlinear(self, ss: SteadyStateDict, unknowns: List[str], targets: List[str], + inputs: Union[Dict[str, Array], ImpulseDict], outputs: Optional[List[str]] = None, + internals: Union[Dict[str, List[str]], List[str]] = {}, Js: Dict[str, JacobianDict] = {}, + options: Dict[str, dict] = {}, H_U_factored: Optional[FactoredJacobianDict] = None, + ss_initial: Optional[SteadyStateDict] = None, **kwargs) -> ImpulseDict: + """Calculate a general equilibrium, non-linear impulse response to a set of shocks in `inputs` + around a steady state `ss`, given a set of `unknowns` and `targets` corresponding to the endogenous + variables to be solved for and the `targets` that must hold in general equilibrium""" + inputs = ImpulseDict(inputs) + unknowns, targets = OrderedSet(unknowns), OrderedSet(targets) + + input_names = self.make_ordered_set(inputs) + actual_outputs, inputs_as_outputs = self.process_outputs(ss, input_names | unknowns, self.make_ordered_set(outputs)) + + T = inputs.T + + Js = self.partial_jacobians(ss, input_names | unknowns, (actual_outputs | targets) - unknowns, T, Js, options, **kwargs) + + if H_U_factored is None: + H_U = self.jacobian(ss, unknowns, targets, T, Js, options, **kwargs) + H_U_factored = FactoredJacobianDict(H_U, T) + + opts = self.get_options(options, kwargs, 'solve_impulse_nonlinear') + + # Newton's method + U = ImpulseDict({k: np.zeros(T) for k in unknowns}) + if opts['verbose']: + print(f'Solving {self.name} for {unknowns} to hit {targets}') + for it in range(opts['maxit']): + results = self.impulse_nonlinear(ss, inputs | U, actual_outputs | targets, internals, Js, options, ss_initial, **kwargs) + errors = {k: np.max(np.abs(results[k])) for k in targets} + if opts['verbose']: + print(f'On iteration {it}') + for k in errors: + print(f' max error for {k} is {errors[k]:.2E}') + if all(v < opts['tol'] for v in errors.values()): + break + else: + U += H_U_factored.apply(results) + else: + raise ValueError(f'No convergence after {opts["maxit"]} backward iterations!') + + return (inputs | U)[inputs_as_outputs] | results + + solve_impulse_linear_options = {} + + def solve_impulse_linear(self, ss: SteadyStateDict, unknowns: List[str], targets: List[str], + inputs: Union[Dict[str, Array], ImpulseDict], outputs: Optional[List[str]] = None, + Js: Optional[Dict[str, JacobianDict]] = {}, options: Dict[str, dict] = {}, + H_U_factored: Optional[FactoredJacobianDict] = None, **kwargs) -> ImpulseDict: + + """Calculate a general equilibrium, linear impulse response to a set of shocks in `inputs` + around a steady state `ss`, given a set of `unknowns` and `targets` corresponding to the endogenous + variables to be solved for and the target conditions that must hold in general equilibrium""" + inputs = ImpulseDict(inputs) + unknowns, targets = OrderedSet(unknowns), OrderedSet(targets) + + input_names = self.make_ordered_set(inputs) + actual_outputs, inputs_as_outputs = self.process_outputs(ss, input_names | unknowns, self.make_ordered_set(outputs)) + + T = inputs.T + + Js = self.partial_jacobians(ss, input_names | unknowns, (actual_outputs | targets) - unknowns, T, Js, options, **kwargs) + + dH = self.impulse_linear(ss, inputs, targets, Js, options, **kwargs).get(targets) # .get(targets) fills in zeros + + if H_U_factored is None: + H_U = self.jacobian(ss, unknowns, targets, T, Js, options, **kwargs).pack(T) + dU = ImpulseDict.unpack(-np.linalg.solve(H_U, dH.pack()), unknowns, T) + else: + dU = H_U_factored @ dH + + return (inputs | dU)[inputs_as_outputs] | self.impulse_linear(ss, dU | inputs, actual_outputs, Js, options, **kwargs) + + solve_jacobian_options = {} + + def solve_jacobian(self, ss: SteadyStateDict, unknowns: List[str], targets: List[str], + inputs: List[str], outputs: Optional[List[str]] = None, T: int = 300, + Js: Dict[str, JacobianDict] = {}, options: Dict[str, dict] = {}, + H_U_factored: Optional[FactoredJacobianDict] = None, **kwargs) -> JacobianDict: + """Calculate a general equilibrium Jacobian to a set of `exogenous` shocks + at a steady state `ss`, given a set of `unknowns` and `targets` corresponding to the endogenous + variables to be solved for and the target conditions that must hold in general equilibrium""" + inputs, unknowns = self.make_ordered_set(inputs), self.make_ordered_set(unknowns) + actual_outputs, unknowns_as_outputs = self.process_outputs(ss, unknowns, self.make_ordered_set(outputs)) + + Js = self.partial_jacobians(ss, inputs | unknowns, (actual_outputs | targets) - unknowns, T, Js, options, **kwargs) + + H_Z = self.jacobian(ss, inputs, targets, T, Js, options, **kwargs) + + if H_U_factored is None: + H_U = self.jacobian(ss, unknowns, targets, T, Js, options, **kwargs).pack(T) + U_Z = JacobianDict.unpack(-np.linalg.solve(H_U, H_Z.pack(T)), unknowns, inputs, T) + else: + U_Z = H_U_factored @ H_Z + + from sequence_jacobian import combine + self_with_unknowns = combine([U_Z, self]) + return self_with_unknowns.jacobian(ss, inputs, unknowns_as_outputs | actual_outputs, T, Js, options, **kwargs) + + def solved(self, unknowns, targets, name=None, solver=None, solver_kwargs=None): + if name is None: + name = self.name + "_solved" + from .solved_block import SolvedBlock + return SolvedBlock(self, name, unknowns, targets, solver, solver_kwargs) + + def remap(self, map): + other = deepcopy(self) + other.M = self.M @ Bijection(map) + other.inputs = other.M @ self.inputs + other.outputs = other.M @ self.outputs + if hasattr(self, 'input_list'): + other.input_list = other.M @ self.input_list + if hasattr(self, 'output_list'): + other.output_list = other.M @ self.output_list + if hasattr(self, 'non_back_iter_outputs'): + other.non_back_iter_outputs = other.M @ self.non_back_iter_outputs + return other + + def rename(self, name): + renamed = deepcopy(self) + renamed.name = name + return renamed + + def default_inputs_outputs(self, ss: SteadyStateDict, inputs, outputs): + # TODO: there should be checks to make sure you don't ask for multidimensional stuff for Jacobians? + # should you be allowed to ask for it (even if not default) for impulses? + if inputs is None: + inputs = self.inputs + if outputs is None: + outputs = self.outputs - ss._vector_valued() + return OrderedSet(inputs), OrderedSet(outputs) + + def process_outputs(self, ss, inputs: OrderedSet, outputs: Optional[OrderedSet]): + if outputs is None: + actual_outputs = self.outputs - ss._vector_valued() + inputs_as_outputs = inputs + else: + actual_outputs = outputs & self.outputs + inputs_as_outputs = outputs & inputs + + return actual_outputs, inputs_as_outputs + + @staticmethod + def make_ordered_set(x): + if x is not None and not isinstance(x, OrderedSet): + return OrderedSet(x) + else: + return x + + def get_options(self, options: dict, kwargs, method): + own_options = getattr(self, method + "_options") + + if self.name in options: + merged = {**own_options, **options[self.name], **kwargs} + else: + merged = {**own_options, **kwargs} + + return {k: merged[k] for k in own_options} + + def input_defaults_smart(self, methodname): + method = getattr(self, methodname, None) + if method is None: + return {} + else: + return input_defaults(method) + + def internals_to_report(self, internals): + if self.name in internals: + if isinstance(internals, dict): + # if internals is a dict, we've specified which internals we want from each block + return internals[self.name] + else: + # otherwise internals is some kind of iterable or set, and if we're in it, we want everything + return self.internals + else: + return [] diff --git a/src/sequence_jacobian/blocks/combined_block.py b/src/sequence_jacobian/blocks/combined_block.py new file mode 100644 index 0000000..7980215 --- /dev/null +++ b/src/sequence_jacobian/blocks/combined_block.py @@ -0,0 +1,122 @@ +"""CombinedBlock class and the combine function to generate it""" + +from .block import Block +from .auxiliary_blocks.jacobiandict_block import JacobianDictBlock +from .support.parent import Parent +from ..classes import ImpulseDict, JacobianDict +from ..utilities.graph import DAG, find_intermediate_inputs + + +def combine(blocks, name="", model_alias=False): + return CombinedBlock(blocks, name=name, model_alias=model_alias) + + +# Useful functional alias +def create_model(blocks, **kwargs): + return combine(blocks, model_alias=True, **kwargs) + + +class CombinedBlock(Block, Parent, DAG): + """A combined `Block` object comprised of several `Block` objects, which topologically sorts them and provides + a set of partial and general equilibrium methods for evaluating their steady state, computes impulse responses, + and calculates Jacobians along the DAG""" + # To users: Do *not* manually change the attributes via assignment. Instantiating a + # CombinedBlock has some automated features that are inferred from initial instantiation but not from + # re-assignment of attributes post-instantiation. + def __init__(self, blocks, name="", model_alias=False, sorted_indices=None, intermediate_inputs=None): + super().__init__() + + blocks_unsorted = [b if isinstance(b, Block) else JacobianDictBlock(b) for b in blocks] + DAG.__init__(self, blocks_unsorted) + + # TODO: deprecate this, use DAG methods instead + self._required = find_intermediate_inputs(blocks) if intermediate_inputs is None else intermediate_inputs + + if not name: + self.name = f"{self.blocks[0].name}_to_{self.blocks[-1].name}_combined" + else: + self.name = name + + # now that it has a name, do Parent initialization + Parent.__init__(self, blocks) + + # If the create_model() is used instead of combine(), we will have __repr__ show this object as a 'Model' + self._model_alias = model_alias + + def __repr__(self): + if self._model_alias: + return f"" + else: + return f"" + + def _steady_state(self, calibration, dissolve, **kwargs): + """Evaluate a partial equilibrium steady state of the CombinedBlock given a `calibration`""" + + ss = calibration.copy() + for block in self.blocks: + # TODO: make this inner_dissolve better, clumsy way to dispatch dissolve only to correct children + inner_dissolve = [k for k in dissolve if self.descendants[k] == block.name] + outputs = block.steady_state(ss, dissolve=inner_dissolve, **kwargs) + ss.update(outputs) + + return ss + + def _impulse_nonlinear(self, ss, inputs, outputs, internals, Js, options, ss_initial): + original_outputs = outputs + outputs = (outputs | self._required) - ss._vector_valued() + + impulses = inputs.copy() + for block in self.blocks: + input_args = {k: v for k, v in impulses.items() if k in block.inputs} + + if input_args or ss_initial is not None: + # If this block is actually perturbed, or we start from different initial ss + # TODO: be more selective about ss_initial here - did any inputs change that matter for this one block? + impulses.update(block.impulse_nonlinear(ss, input_args, outputs & block.outputs, internals, Js, options, ss_initial)) + + return ImpulseDict({k: impulses.toplevel[k] for k in original_outputs if k in impulses.toplevel}, impulses.internals, impulses.T) + + def _impulse_linear(self, ss, inputs, outputs, Js, options): + original_outputs = outputs + outputs = (outputs | self._required) - ss._vector_valued() + + impulses = inputs.copy() + for block in self.blocks: + input_args = {k: v for k, v in impulses.items() if k in block.inputs} + + if input_args: # If this block is actually perturbed + impulses.update(block.impulse_linear(ss, input_args, outputs & block.outputs, Js, options)) + + return ImpulseDict({k: impulses.toplevel[k] for k in original_outputs if k in impulses.toplevel}, T=impulses.T) + + def _partial_jacobians(self, ss, inputs, outputs, T, Js, options): + vector_valued = ss._vector_valued() + inputs = (inputs | self._required) - vector_valued + outputs = (outputs | self._required) - vector_valued + + curlyJs = {} + for block in self.blocks: + curlyJ = block.partial_jacobians(ss, inputs & block.inputs, outputs & block.outputs, T, Js, options) + curlyJs.update(curlyJ) + + return curlyJs + + def _jacobian(self, ss, inputs, outputs, T, Js, options): + Js = self._partial_jacobians(ss, inputs, outputs, T, Js, options) + + original_outputs = outputs + total_Js = JacobianDict.identity(inputs) + + # TODO: horrible, redoing work from partial_jacobians, also need more efficient sifting of intermediates! + vector_valued = ss._vector_valued() + inputs = (inputs | self._required) - vector_valued + outputs = (outputs | self._required) - vector_valued + for block in self.blocks: + J = block.jacobian(ss, inputs & block.inputs, outputs & block.outputs, T, Js, options) + total_Js.update(J @ total_Js) + + return total_Js[original_outputs, :] + + +# Useful type aliases +Model = CombinedBlock diff --git a/src/sequence_jacobian/blocks/het_block.py b/src/sequence_jacobian/blocks/het_block.py new file mode 100644 index 0000000..aafeef7 --- /dev/null +++ b/src/sequence_jacobian/blocks/het_block.py @@ -0,0 +1,492 @@ +import copy +import numpy as np +from typing import Optional, Dict + +from .block import Block +from .. import utilities as utils +from ..classes import SteadyStateDict, ImpulseDict, JacobianDict +from ..utilities.function import ExtendedFunction, CombinedExtendedFunction +from ..utilities.ordered_set import OrderedSet +from ..utilities.bijection import Bijection +from .support.het_support import ForwardShockableTransition, ExpectationShockableTransition, lottery_1d, lottery_2d, Markov, CombinedTransition, Transition + + +def het(exogenous, policy, backward, backward_init=None, hetinputs=None, hetoutputs=None): + def decorator(backward_fun): + return HetBlock(backward_fun, exogenous, policy, backward, backward_init, hetinputs, hetoutputs) + return decorator + + +class HetBlock(Block): + def __init__(self, backward_fun, exogenous, policy, backward, backward_init=None, hetinputs=None, hetoutputs=None): + self.backward_fun = ExtendedFunction(backward_fun) + self.name = self.backward_fun.name + super().__init__() + + self.exogenous = OrderedSet(utils.misc.make_tuple(exogenous)) + self.policy, self.backward = (OrderedSet(utils.misc.make_tuple(x)) for x in (policy, backward)) + self.non_backward_outputs = self.backward_fun.outputs - self.backward + + self.outputs = OrderedSet([o.upper() for o in self.non_backward_outputs]) + self.M_outputs = Bijection({o: o.upper() for o in self.non_backward_outputs}) + self.inputs = self.backward_fun.inputs - [k + '_p' for k in self.backward] + self.inputs |= self.exogenous + self.internals = OrderedSet(['D', 'Dbeg']) | self.exogenous | self.backward_fun.outputs + + # store "original" copies of these for use whenever we process new hetinputs/hetoutputs + self.original_inputs = self.inputs + self.original_outputs = self.outputs + self.original_internals = self.internals + self.original_M_outputs = self.M_outputs + + # A HetBlock can have heterogeneous inputs and heterogeneous outputs, henceforth `hetinput` and `hetoutput`. + if hetinputs is not None: + hetinputs = CombinedExtendedFunction(hetinputs) + if hetoutputs is not None: + hetoutputs = CombinedExtendedFunction(hetoutputs) + self.process_hetinputs_hetoutputs(hetinputs, hetoutputs, tocopy=False) + + if len(self.policy) > 2: + raise ValueError(f"More than two endogenous policies in {self.name}, not yet supported") + + # Checking that the various inputs/outputs attributes are correctly set + for pol in self.policy: + if pol not in self.backward_fun.outputs: + raise ValueError(f"Policy '{pol}' not included as output in {self.name}") + if pol[0].isupper(): + raise ValueError(f"Policy '{pol}' is uppercase in {self.name}, which is not allowed") + + for back in self.backward: + if back + '_p' not in self.backward_fun.inputs: + raise ValueError(f"Backward variable '{back}_p' not included as argument in {self.name}") + + if back not in self.backward_fun.outputs: + raise ValueError(f"Backward variable '{back}' not included as output in {self.name}") + + for out in self.non_backward_outputs: + if out[0].isupper(): + raise ValueError("Output '{out}' is uppercase in {self.name}, which is not allowed") + + if backward_init is not None: + backward_init = ExtendedFunction(backward_init) + self.backward_init = backward_init + + # note: should do more input checking to ensure certain choices not made: 'D' not input, etc. + + def __repr__(self): + """Nice string representation of HetBlock for printing to console""" + if self.hetinputs is not None: + if self.hetoutputs is not None: + return f"" + else: + return f"" + else: + return f"" + + def _steady_state(self, calibration, backward_tol=1E-8, backward_maxit=5000, + forward_tol=1E-10, forward_maxit=100_000): + ss = self.extract_ss_dict(calibration) + self.update_with_hetinputs(ss) + self.initialize_backward(ss) + + ss = self.backward_steady_state(ss, tol=backward_tol, maxit=backward_maxit) + Dbeg, D = self.forward_steady_state(ss, forward_tol, forward_maxit) + ss.update({'Dbeg': Dbeg, "D": D}) + + self.update_with_hetoutputs(ss) + + # aggregate all outputs other than backward variables on grid, capitalize + toreturn = self.non_backward_outputs + if self.hetoutputs is not None: + toreturn = toreturn | self.hetoutputs.outputs + aggregates = {o.upper(): np.vdot(D, ss[o]) for o in toreturn} + ss.update(aggregates) + + return SteadyStateDict({k: ss[k] for k in ss if k not in self.internals}, + {self.name: {k: ss[k] for k in ss if k in self.internals}}) + + def _impulse_nonlinear(self, ssin, inputs, outputs, internals, ss_initial, monotonic=False): + ss = self.extract_ss_dict(ssin) + if ss_initial is not None: + # only effect of distinct initial ss on hetblock is different initial distribution + ss['Dbeg'] = ss_initial['Dbeg'] + + # identify individual variable paths we want from backward iteration, then run it + toreturn = self.non_backward_outputs + if self.hetoutputs is not None: + toreturn = toreturn | self.hetoutputs.outputs + toreturn = (toreturn | internals) - ['D', 'Dbeg'] + + individual_paths, exog_path = self.backward_nonlinear(ss, inputs, toreturn) + + # run forward iteration to get path of distribution, add to individual_paths + self.forward_nonlinear(ss, individual_paths, exog_path, monotonic) + + # obtain aggregates of all outputs, made uppercase + aggregates = {o: utils.optimized_routines.fast_aggregate( + individual_paths['D'], individual_paths[self.M_outputs.inv @ o]) for o in outputs} + + # obtain internals + internals_dict = {self.name: {k: individual_paths[k] for k in internals}} + return ImpulseDict(aggregates, internals_dict, inputs.T) - ssin + + def _impulse_linear(self, ss, inputs, outputs, Js, h=1E-4, twosided=False): + return ImpulseDict(self.jacobian(ss, list(inputs.keys()), outputs, inputs.T, Js, h=h, twosided=twosided).apply(inputs)) + + def _jacobian(self, ss, inputs, outputs, T, h=1E-4, twosided=False): + ss = self.extract_ss_dict(ss) + self.update_with_hetinputs(ss) + outputs = self.M_outputs.inv @ outputs + + # step 0: preliminary processing of steady state + exog = self.make_exog_law_of_motion(ss) + endog = self.make_endog_law_of_motion(ss) + differentiable_backward_fun, differentiable_hetinputs, differentiable_hetoutputs = self.jac_backward_prelim(ss, h, exog, twosided) + law_of_motion = CombinedTransition([exog, endog]).forward_shockable(ss['Dbeg']) + exog_by_output = {k: exog.expectation_shockable(ss[k]) for k in outputs | self.backward} + + # step 1 of fake news algorithm + # compute curlyY and curlyD (backward iteration) for each input i + curlyYs, curlyDs = {}, {} + for i in inputs: + curlyYs[i], curlyDs[i] = self.backward_fakenews(i, outputs, T, differentiable_backward_fun, + differentiable_hetinputs, differentiable_hetoutputs, + law_of_motion, exog_by_output) + + # step 2 of fake news algorithm + # compute expectation vectors curlyE for each outcome o + curlyPs = {} + for o in outputs: + curlyPs[o] = self.expectation_vectors(ss[o], T-1, law_of_motion) + + # steps 3-4 of fake news algorithm + # make fake news matrix and Jacobian for each outcome-input pair + F, J = {}, {} + for o in outputs: + for i in inputs: + if o.upper() not in F: + F[o.upper()] = {} + if o.upper() not in J: + J[o.upper()] = {} + F[o.upper()][i] = HetBlock.build_F(curlyYs[i][o], curlyDs[i], curlyPs[o]) + J[o.upper()][i] = HetBlock.J_from_F(F[o.upper()][i]) + + return JacobianDict(J, name=self.name, T=T) + + '''Steady-state backward and forward methods''' + + def backward_steady_state(self, ss, tol=1E-8, maxit=5000): + """Backward iteration to get steady-state policies and other outcomes""" + ss = ss.copy() + exog = self.make_exog_law_of_motion(ss) + + old = {} + for it in range(maxit): + for k in self.backward: + ss[k + '_p'] = exog.expectation(ss[k]) + del ss[k] + + ss.update(self.backward_fun(ss)) + + if it % 10 == 1 and all(utils.optimized_routines.within_tolerance(ss[k], old[k], tol) + for k in self.policy): + break + + old.update({k: ss[k] for k in self.policy}) + else: + raise ValueError(f'No convergence of policy functions after {maxit} backward iterations!') + + for k in self.backward: + del ss[k + '_p'] + + return ss + + def forward_steady_state(self, ss, tol=1E-10, maxit=100_000): + """Forward iteration to get steady-state distribution""" + exog = self.make_exog_law_of_motion(ss) + endog = self.make_endog_law_of_motion(ss) + + Dbeg_seed = ss.get('Dbeg', None) + pi_seeds = [ss.get(k + '_seed', None) for k in self.exogenous] + + # first obtain initial distribution D + if Dbeg_seed is None: + # stationary distribution of each exogenous + pis = [exog[i].stationary(pi_seed) for i, pi_seed in enumerate(pi_seeds)] + + # uniform distribution over endogenous + endog_uniform = [np.full(len(ss[k+'_grid']), 1/len(ss[k+'_grid'])) for k in self.policy] + + # initialize outer product of all these as guess + Dbeg = utils.multidim.outer(pis + endog_uniform) + else: + Dbeg = Dbeg_seed + + # iterate until convergence by tol, or maxit + D = exog.forward(Dbeg) + for it in range(maxit): + Dbeg_new = endog.forward(D) + D_new = exog.forward(Dbeg_new) + + # only check convergence every 10 iterations for efficiency + if it % 10 == 0 and utils.optimized_routines.within_tolerance(Dbeg, Dbeg_new, tol): + break + Dbeg = Dbeg_new + D = D_new + else: + raise ValueError(f'No convergence after {maxit} forward iterations!') + + # "D" is after the exogenous shock, Dbeg is before it + return Dbeg, D + + '''Nonlinear impulse backward and forward methods''' + + def backward_nonlinear(self, ss, inputs, toreturn): + T = inputs.T + individual_paths = {k: np.empty((T,) + ss[k].shape) for k in toreturn} + + backdict = ss.copy() + exog = self.make_exog_law_of_motion(backdict) + exog_path = [] + + for t in reversed(range(T)): + for k in self.backward: + backdict[k + '_p'] = exog.expectation(backdict[k]) + del backdict[k] + + backdict.update({k: ss[k] + v[t, ...] for k, v in inputs.items()}) + self.update_with_hetinputs(backdict) + backdict.update(self.backward_fun(backdict)) + self.update_with_hetoutputs(backdict) + + for k in individual_paths: + individual_paths[k][t, ...] = backdict[k] + + exog = self.make_exog_law_of_motion(backdict) + + exog_path.append(exog) + + return individual_paths, exog_path[::-1] + + def forward_nonlinear(self, ss, individual_paths, exog_path, monotonic): + T = len(exog_path) + Dbeg = ss['Dbeg'] + + Dbeg_path = np.empty((T,) + Dbeg.shape) + Dbeg_path[0, ...] = Dbeg + D_path = np.empty_like(Dbeg_path) + + for t in range(T): + endog = self.make_endog_law_of_motion({**ss, **{k: individual_paths[k][t, ...] for k in self.policy}}, monotonic) + + # now step forward in two, first exogenous this period then endogenous + D_path[t, ...] = exog_path[t].forward(Dbeg) + + if t < T-1: + Dbeg = endog.forward(D_path[t, ...]) + Dbeg_path[t+1, ...] = Dbeg # make this optional + + individual_paths['D'] = D_path + individual_paths['Dbeg'] = Dbeg_path + + '''Jacobian calculation: four parts of fake news algorithm, plus support methods''' + + def backward_fakenews(self, input_shocked, output_list, T, differentiable_backward_fun, + differentiable_hetinput, differentiable_hetoutput, + law_of_motion: ForwardShockableTransition, exog: Dict[str, ExpectationShockableTransition]): + """Part 1 of fake news algorithm: calculate curlyY and curlyD in response to fake news shock""" + # contemporaneous effect of unit scalar shock to input_shocked + din_dict = {input_shocked: 1} + if differentiable_hetinput is not None and input_shocked in differentiable_hetinput.inputs: + din_dict.update(differentiable_hetinput.diff({input_shocked: 1})) + + curlyV, curlyD, curlyY = self.backward_step_fakenews(din_dict, output_list, differentiable_backward_fun, + differentiable_hetoutput, law_of_motion, exog, True) + + # infer dimensions from this, initialize empty arrays, and fill in contemporaneous effect + curlyDs = np.empty((T,) + curlyD.shape) + curlyYs = {k: np.empty(T) for k in curlyY.keys()} + + curlyDs[0, ...] = curlyD + for k in curlyY.keys(): + curlyYs[k][0] = curlyY[k] + + # fill in anticipation effects of shock up to horizon T + for t in range(1, T): + curlyV, curlyDs[t, ...], curlyY = self.backward_step_fakenews({k+'_p': v for k, v in curlyV.items()}, + output_list, differentiable_backward_fun, + differentiable_hetoutput, law_of_motion, exog) + for k in curlyY.keys(): + curlyYs[k][t] = curlyY[k] + + return curlyYs, curlyDs + + def expectation_vectors(self, o_ss, T, law_of_motion: Transition): + """Part 2 of fake news algorithm: calculate expectation vectors curlyE""" + curlyEs = np.empty((T,) + o_ss.shape) + + # initialize with beginning-of-period expectation of steady-state policy + curlyEs[0, ...] = utils.misc.demean(law_of_motion[0].expectation(o_ss)) + for t in range(1, T): + # demean so that curlyEs converge to zero, in theory no effect but better numerically + curlyEs[t, ...] = utils.misc.demean(law_of_motion.expectation(curlyEs[t-1, ...])) + return curlyEs + + @staticmethod + def build_F(curlyYs, curlyDs, curlyEs): + """Part 3 of fake news algorithm: build fake news matrix from curlyY, curlyD, curlyE""" + T = curlyDs.shape[0] + Tpost = curlyEs.shape[0] - T + 2 + F = np.empty((Tpost + T - 1, T)) + F[0, :] = curlyYs + F[1:, :] = curlyEs.reshape((Tpost + T - 2, -1)) @ curlyDs.reshape((T, -1)).T + return F + + @staticmethod + def J_from_F(F): + """Part 4 of fake news algorithm: recursively build Jacobian from fake news matrix""" + J = F.copy() + for t in range(1, J.shape[1]): + J[1:, t] += J[:-1, t - 1] + return J + + def backward_step_fakenews(self, din_dict, output_list, differentiable_backward_fun, + differentiable_hetoutput, law_of_motion: ForwardShockableTransition, + exog: Dict[str, ExpectationShockableTransition], maybe_exog_shock=False): + """Support for part 1 of fake news algorithm: single backward step in response to shock""" + Dbeg, D = law_of_motion[0].Dss, law_of_motion[1].Dss + + # shock perturbs outputs + shocked_outputs = differentiable_backward_fun.diff(din_dict) + curlyV = {k: law_of_motion[0].expectation(shocked_outputs[k]) for k in self.backward} + + # if there might be a shock to exogenous processes, figure out what it is + if maybe_exog_shock: + shocks_to_exog = [din_dict.get(k, None) for k in self.exogenous] + else: + shocks_to_exog = None + + # perturbation to exog and outputs outputs affects distribution tomorrow + policy_shock = [shocked_outputs[k] for k in self.policy] + if len(policy_shock) == 1: + policy_shock = policy_shock[0] + curlyD = law_of_motion.forward_shock([shocks_to_exog, policy_shock]) + + # and also affect aggregate outcomes today + if differentiable_hetoutput is not None and (output_list & differentiable_hetoutput.outputs): + shocked_outputs.update(differentiable_hetoutput.diff({**shocked_outputs, **din_dict}, outputs=differentiable_hetoutput.outputs & output_list)) + curlyY = {k: np.vdot(D, shocked_outputs[k]) for k in output_list} + + # add effects from perturbation to exog on beginning-of-period expectations in curlyV and curlyY + if maybe_exog_shock: + for k in curlyV: + shock = exog[k].expectation_shock(shocks_to_exog) + if shock is not None: + curlyV[k] += shock + + for k in curlyY: + shock = exog[k].expectation_shock(shocks_to_exog) + # maybe could be more efficient since we don't need to calculate pointwise? + if shock is not None: + curlyY[k] += np.vdot(Dbeg, shock) + + return curlyV, curlyD, curlyY + + def jac_backward_prelim(self, ss, h, exog, twosided): + """Support for part 1 of fake news algorithm: preload differentiable functions""" + differentiable_hetinputs = None + if self.hetinputs is not None: + # always use two-sided differentiation for hetinputs + differentiable_hetinputs = self.hetinputs.differentiable(ss, h, True) + + differentiable_hetoutputs = None + if self.hetoutputs is not None: + differentiable_hetoutputs = self.hetoutputs.differentiable(ss, h, twosided) + + ss = ss.copy() + for k in self.backward: + ss[k + '_p'] = exog.expectation(ss[k]) + differentiable_backward_fun = self.backward_fun.differentiable(ss, h, twosided) + + return differentiable_backward_fun, differentiable_hetinputs, differentiable_hetoutputs + + '''HetInput and HetOutput options and processing''' + + def process_hetinputs_hetoutputs(self, hetinputs: Optional[CombinedExtendedFunction], hetoutputs: Optional[CombinedExtendedFunction], tocopy=True): + if tocopy: + self = copy.copy(self) + inputs = self.original_inputs.copy() + outputs = self.original_outputs.copy() + internals = self.original_internals.copy() + + if hetoutputs is not None: + inputs |= (hetoutputs.inputs - self.backward_fun.outputs - ['D']) + outputs |= [o.upper() for o in hetoutputs.outputs] + self.M_outputs = Bijection({o: o.upper() for o in hetoutputs.outputs}) @ self.original_M_outputs + internals |= hetoutputs.outputs + + if hetinputs is not None: + inputs |= hetinputs.inputs + inputs -= hetinputs.outputs + internals |= hetinputs.outputs + + self.inputs = inputs + self.outputs = outputs + self.internals = internals + + self.hetinputs = hetinputs + self.hetoutputs = hetoutputs + + return self + + def add_hetinputs(self, functions): + if self.hetinputs is None: + return self.process_hetinputs_hetoutputs(CombinedExtendedFunction(functions), self.hetoutputs) + else: + return self.process_hetinputs_hetoutputs(self.hetinputs.add(functions), self.hetoutputs) + + def remove_hetinputs(self, names): + return self.process_hetinputs_hetoutputs(self.hetinputs.remove(names), self.hetoutputs) + + def add_hetoutputs(self, functions): + if self.hetoutputs is None: + return self.process_hetinputs_hetoutputs(self.hetinputs, CombinedExtendedFunction(functions)) + else: + return self.process_hetinputs_hetoutputs(self.hetinputs, self.hetoutputs.add(functions)) + + def remove_hetoutputs(self, names): + return self.process_hetinputs_hetoutputs(self.hetinputs, self.hetoutputs.remove(names)) + + def update_with_hetinputs(self, d): + if self.hetinputs is not None: + d.update(self.hetinputs(d)) + + def update_with_hetoutputs(self, d): + if self.hetoutputs is not None: + d.update(self.hetoutputs(d)) + + '''Additional helper functions''' + + def extract_ss_dict(self, ss): + if isinstance(ss, SteadyStateDict): + ssnew = ss.toplevel.copy() + if self.name in ss.internals: + ssnew.update(ss.internals[self.name]) + return ssnew + else: + return ss.copy() + + def initialize_backward(self, ss): + if not all(k in ss for k in self.backward): + ss.update(self.backward_init(ss)) + + def make_exog_law_of_motion(self, d:dict): + return CombinedTransition([Markov(d[k], i) for i, k in enumerate(self.exogenous)]) + + def make_endog_law_of_motion(self, d: dict, monotonic=False): + if len(self.policy) == 1: + return lottery_1d(d[self.policy[0]], d[self.policy[0] + '_grid'], monotonic) + else: + return lottery_2d(d[self.policy[0]], d[self.policy[1]], + d[self.policy[0] + '_grid'], d[self.policy[1] + '_grid'], monotonic) \ No newline at end of file diff --git a/src/sequence_jacobian/blocks/simple_block.py b/src/sequence_jacobian/blocks/simple_block.py new file mode 100644 index 0000000..c542c80 --- /dev/null +++ b/src/sequence_jacobian/blocks/simple_block.py @@ -0,0 +1,108 @@ +"""Class definition of a simple block""" + +import numpy as np +from copy import deepcopy + +from .support.simple_displacement import ignore, Displace, AccumulatedDerivative +from .block import Block +from ..classes import SteadyStateDict, ImpulseDict, JacobianDict, SimpleSparse +from ..utilities import misc +from ..utilities.function import ExtendedFunction + +'''Part 1: SimpleBlock class and @simple decorator to generate it''' + + +def simple(f): + return SimpleBlock(f) + + +class SimpleBlock(Block): + """Generated from simple block written in Dynare-ish style and decorated with @simple, e.g. + + @simple + def production(Z, K, L, alpha): + Y = Z * K(-1) ** alpha * L ** (1 - alpha) + return Y + + which is a SimpleBlock that takes in Z, K, L, and alpha, all of which can be either constants + or series, and implements a Cobb-Douglas production function, noting that for production today + we use the capital K(-1) determined yesterday. + + Key methods are .ss, .td, and .jac, like HetBlock. + """ + + def __init__(self, f): + super().__init__() + self.f = ExtendedFunction(f) + self.name = self.f.name + self.inputs = self.f.inputs + self.outputs = self.f.outputs + + def __repr__(self): + return f"" + + def _steady_state(self, ss): + outputs = self.f.wrapped_call(ss, preprocess=ignore, postprocess=misc.numeric_primitive) + return SteadyStateDict({**ss, **outputs}) + + def _impulse_nonlinear(self, ss, inputs, outputs, ss_initial): + if ss_initial is None: + ss_initial = ss + ss_initial_flag = False + else: + ss_initial_flag = True + + input_args = {} + for k, v in inputs.items(): + if np.isscalar(v): + raise ValueError(f'Keyword argument {k}={v} is scalar, should be time path.') + input_args[k] = Displace(v + ss[k], ss[k], ss_initial[k], k) + + for k in self.inputs: + if k not in input_args: + if not ss_initial_flag or (ss_initial_flag and np.array_equal(ss_initial[k], ss[k])): + input_args[k] = ignore(ss[k]) + else: + input_args[k] = Displace(np.full(inputs.T, ss[k]), ss[k], ss_initial[k], k) + + return ImpulseDict(make_impulse_uniform_length(self.f(input_args)))[outputs] - ss + + def _impulse_linear(self, ss, inputs, outputs, Js): + return ImpulseDict(self.jacobian(ss, list(inputs.keys()), outputs, inputs.T, Js).apply(inputs)) + + def _jacobian(self, ss, inputs, outputs, T): + invertedJ = {i: {} for i in inputs} + + # Loop over all inputs/shocks which we want to differentiate with respect to + for i in inputs: + invertedJ[i] = self.compute_single_shock_J(ss, i) + + # Because we computed the Jacobian of all outputs with respect to each shock (invertedJ[i][o]), + # we need to loop back through to have J[o][i] to map for a given output `o`, shock `i`, + # the Jacobian curlyJ^{o,i}. + J = {o: {} for o in outputs} + for o in outputs: + for i in inputs: + # drop zeros from JacobianDict + if invertedJ[i][o] and not invertedJ[i][o].iszero: + J[o][i] = invertedJ[i][o] + + return JacobianDict(J, outputs, inputs, self.name, T) + + def compute_single_shock_J(self, ss, i): + input_args = {i: ignore(ss[i]) for i in self.inputs} + input_args[i] = AccumulatedDerivative(f_value=ss[i]) + + J = {o: {} for o in self.outputs} + for o_name, o in self.f(input_args).items(): + if isinstance(o, AccumulatedDerivative): + J[o_name] = SimpleSparse(o.elements) + + return J + + +# TODO: move this to impulse.py? +def make_impulse_uniform_length(out): + T = np.max([np.size(v) for v in out.values()]) + return {k: (np.full(T, misc.numeric_primitive(v)) if np.isscalar(v) else misc.numeric_primitive(v)) + for k, v in out.items()} diff --git a/src/sequence_jacobian/blocks/solved_block.py b/src/sequence_jacobian/blocks/solved_block.py new file mode 100644 index 0000000..048f1ad --- /dev/null +++ b/src/sequence_jacobian/blocks/solved_block.py @@ -0,0 +1,99 @@ +from .block import Block +from .simple_block import simple +from .support.parent import Parent +from ..classes import FactoredJacobianDict +from ..utilities.ordered_set import OrderedSet + + +def solved(unknowns, targets, solver=None, solver_kwargs={}, name=""): + """Convenience @solved(unknowns=..., targets=...) decorator on a single SimpleBlock""" + # call as decorator, return function of function + def singleton_solved_block(f): + return SolvedBlock(simple(f).rename(f.__name__ + '_inner'), f.__name__, unknowns, targets, solver=solver, solver_kwargs=solver_kwargs) + return singleton_solved_block + + +class SolvedBlock(Block, Parent): + """SolvedBlocks are mini SHADE models embedded as blocks inside larger SHADE models. + + When creating them, we need to provide the basic ingredients of a SHADE model: the list of + blocks comprising the model, the list on unknowns, and the list of targets. + + When we use .jac to ask for the Jacobian of a SolvedBlock, we are really solving for the 'G' + matrices of the mini SHADE models, which then become the 'curlyJ' Jacobians of the block. + + Similarly, when we use .td to evaluate a SolvedBlock on a path, we are really solving for the + nonlinear transition path such that all internal targets of the mini SHADE model are zero. + """ + + def __init__(self, block: Block, name, unknowns, targets, solver=None, solver_kwargs={}): + super().__init__() + + # since we dispatch to solve methods, same set of options + self.impulse_nonlinear_options = self.solve_impulse_nonlinear_options + self.steady_state_options = self.solve_steady_state_options + + self.block = block + self.name = name + self.unknowns = unknowns + self.targets = targets + self.solver = solver + self.solver_kwargs = solver_kwargs + + Parent.__init__(self, [self.block]) + + # validate unknowns and targets + if not len(unknowns) == len(targets): + raise ValueError(f'Unknowns {set(unknowns)} and targets {set(targets)} different sizes in SolvedBlock {name}') + if not set(unknowns) <= block.inputs: + raise ValueError(f'Unknowns has element {set(unknowns) - block.inputs} not in inputs in SolvedBlock {name}') + if not set(targets) <= block.outputs: + raise ValueError(f'Targets has element {set(targets) - block.outputs} not in outputs in SolvedBlock {name}') + + # what are overall outputs and inputs? + self.outputs = block.outputs | set(unknowns) + self.inputs = block.inputs - set(unknowns) + + def __repr__(self): + return f"" + + def _steady_state(self, calibration, dissolve, options, **kwargs): + if self.name in dissolve: + kwargs['solver'] = "solved" + unknowns = {k: v for k, v in calibration.items() if k in self.unknowns} + else: + unknowns = self.unknowns + if 'solver' not in kwargs: + # TODO: replace this with default option + kwargs['solver'] = self.solver + + return self.block.solve_steady_state(calibration, unknowns, self.targets, options, **kwargs) + + def _impulse_nonlinear(self, ss, inputs, outputs, internals, Js, options, ss_initial, **kwargs): + return self.block.solve_impulse_nonlinear(ss, OrderedSet(self.unknowns), OrderedSet(self.targets), + inputs, outputs, internals, Js, options, self._get_H_U_factored(Js), ss_initial, **kwargs) + + def _impulse_linear(self, ss, inputs, outputs, Js, options): + return self.block.solve_impulse_linear(ss, OrderedSet(self.unknowns), OrderedSet(self.targets), + inputs, outputs, Js, options, self._get_H_U_factored(Js)) + + def _jacobian(self, ss, inputs, outputs, T, Js, options): + return self.block.solve_jacobian(ss, OrderedSet(self.unknowns), OrderedSet(self.targets), + inputs, outputs, T, Js, options, self._get_H_U_factored(Js))[outputs] + + def _partial_jacobians(self, ss, inputs, outputs, T, Js, options): + # call it on the child first + inner_Js = self.block.partial_jacobians(ss, (OrderedSet(self.unknowns) | inputs), + (OrderedSet(self.targets) | outputs - self.unknowns.keys()), T, Js, options) + + # with these inner Js, also compute H_U and factorize + H_U = self.block.jacobian(ss, OrderedSet(self.unknowns), OrderedSet(self.targets), T, inner_Js, options) + H_U_factored = FactoredJacobianDict(H_U, T) + + return {**inner_Js, self.name: H_U_factored} + + def _get_H_U_factored(self, Js): + if self.name in Js and isinstance(Js[self.name], FactoredJacobianDict): + return Js[self.name] + else: + return None diff --git a/src/sequence_jacobian/blocks/support/__init__.py b/src/sequence_jacobian/blocks/support/__init__.py new file mode 100644 index 0000000..1e46635 --- /dev/null +++ b/src/sequence_jacobian/blocks/support/__init__.py @@ -0,0 +1,2 @@ +"""Other classes and helpers to aid standard block functionality: .steady_state, .impulse_linear, .impulse_nonlinear, +.jacobian""" diff --git a/src/sequence_jacobian/blocks/support/het_compiled.py b/src/sequence_jacobian/blocks/support/het_compiled.py new file mode 100644 index 0000000..b5033de --- /dev/null +++ b/src/sequence_jacobian/blocks/support/het_compiled.py @@ -0,0 +1,104 @@ +import numpy as np +from numba import njit + +@njit +def forward_policy_1d(D, x_i, x_pi): + nZ, nX = D.shape + Dnew = np.zeros_like(D) + for iz in range(nZ): + for ix in range(nX): + i = x_i[iz, ix] + pi = x_pi[iz, ix] + d = D[iz, ix] + + Dnew[iz, i] += d * pi + Dnew[iz, i+1] += d * (1 - pi) + + return Dnew + + +@njit +def expectation_policy_1d(X, x_i, x_pi): + nZ, nX = X.shape + Xnew = np.zeros_like(X) + for iz in range(nZ): + for ix in range(nX): + i = x_i[iz, ix] + pi = x_pi[iz, ix] + Xnew[iz, ix] = pi * X[iz, i] + (1-pi) * X[iz, i+1] + return Xnew + + +@njit +def forward_policy_shock_1d(Dss, x_i_ss, x_pi_shock): + """forward_step_1d linearized wrt x_pi""" + nZ, nX = Dss.shape + Dshock = np.zeros_like(Dss) + for iz in range(nZ): + for ix in range(nX): + i = x_i_ss[iz, ix] + dshock = x_pi_shock[iz, ix] * Dss[iz, ix] + Dshock[iz, i] += dshock + Dshock[iz, i + 1] -= dshock + + return Dshock + + +@njit +def forward_policy_2d(D, x_i, y_i, x_pi, y_pi): + nZ, nX, nY = D.shape + Dnew = np.zeros_like(D) + for iz in range(nZ): + for ix in range(nX): + for iy in range(nY): + ixp = x_i[iz, ix, iy] + iyp = y_i[iz, ix, iy] + beta = x_pi[iz, ix, iy] + alpha = y_pi[iz, ix, iy] + + Dnew[iz, ixp, iyp] += alpha * beta * D[iz, ix, iy] + Dnew[iz, ixp+1, iyp] += alpha * (1 - beta) * D[iz, ix, iy] + Dnew[iz, ixp, iyp+1] += (1 - alpha) * beta * D[iz, ix, iy] + Dnew[iz, ixp+1, iyp+1] += (1 - alpha) * (1 - beta) * D[iz, ix, iy] + return Dnew + + +@njit +def expectation_policy_2d(X, x_i, y_i, x_pi, y_pi): + nZ, nX, nY = X.shape + Xnew = np.empty_like(X) + for iz in range(nZ): + for ix in range(nX): + for iy in range(nY): + ixp = x_i[iz, ix, iy] + iyp = y_i[iz, ix, iy] + alpha = x_pi[iz, ix, iy] + beta = y_pi[iz, ix, iy] + + Xnew[iz, ix, iy] = (alpha * beta * X[iz, ixp, iyp] + alpha * (1-beta) * X[iz, ixp, iyp+1] + + (1-alpha) * beta * X[iz, ixp+1, iyp] + + (1-alpha) * (1-beta) * X[iz, ixp+1, iyp+1]) + return Xnew + + +@njit +def forward_policy_shock_2d(Dss, x_i_ss, y_i_ss, x_pi_ss, y_pi_ss, x_pi_shock, y_pi_shock): + """Endogenous update part of forward_step_shock_2d""" + nZ, nX, nY = Dss.shape + Dshock = np.zeros_like(Dss) + for iz in range(nZ): + for ix in range(nX): + for iy in range(nY): + ixp = x_i_ss[iz, ix, iy] + iyp = y_i_ss[iz, ix, iy] + alpha = x_pi_ss[iz, ix, iy] + beta = y_pi_ss[iz, ix, iy] + + dalpha = x_pi_shock[iz, ix, iy] * Dss[iz, ix, iy] + dbeta = y_pi_shock[iz, ix, iy] * Dss[iz, ix, iy] + + Dshock[iz, ixp, iyp] += dalpha * beta + alpha * dbeta + Dshock[iz, ixp+1, iyp] += dbeta * (1-alpha) - beta * dalpha + Dshock[iz, ixp, iyp+1] += dalpha * (1-beta) - alpha * dbeta + Dshock[iz, ixp+1, iyp+1] -= dalpha * (1-beta) + dbeta * (1-alpha) + return Dshock diff --git a/src/sequence_jacobian/blocks/support/het_support.py b/src/sequence_jacobian/blocks/support/het_support.py new file mode 100644 index 0000000..499b23f --- /dev/null +++ b/src/sequence_jacobian/blocks/support/het_support.py @@ -0,0 +1,276 @@ +import numpy as np +from . import het_compiled +from ...utilities.discretize import stationary as general_stationary +from ...utilities.interpolate import interpolate_coord_robust, interpolate_coord +from ...utilities.multidim import multiply_ith_dimension +from typing import Optional, Sequence, Any, List, Tuple, Union + +class Transition: + """Abstract class for PolicyLottery or ManyMarkov, i.e. some part of state-space transition""" + def forward(self, D): + pass + + def expectation(self, X): + pass + + def forward_shockable(self, Dss): + pass + + def expectation_shockable(self, Xss): + raise NotImplementedError(f'Shockable expectation not implemented for {type(self)}') + + +class ForwardShockableTransition(Transition): + """Abstract class extending Transition, allowing us to find effect of shock to transition rule + on one-period-ahead distribution. This functionality isn't included in the regular Transition + because it requires knowledge of the incoming ("steady-state") distribution and also sometimes + some precomputation. + + One crucial thing here is the order of shock arguments in shocks. Also, is None is the default + argument for a shock, we allow that shock to be None. We always allow shocks in lists to be None.""" + + def forward_shock(self, shocks): + pass + + +class ExpectationShockableTransition(Transition): + def expectation_shock(self, shocks): + pass + + + +def lottery_1d(a, a_grid, monotonic=False): + if not monotonic: + return PolicyLottery1D(*interpolate_coord_robust(a_grid, a), a_grid) + else: + return PolicyLottery1D(*interpolate_coord(a_grid, a), a_grid) + + +class PolicyLottery1D(Transition): + # TODO: always operates on final dimension, highly non-generic in that sense + def __init__(self, i, pi, grid): + # flatten non-policy dimensions into one because that's what methods accept + self.i = i.reshape((-1,) + grid.shape) + self.flatshape = self.i.shape + + self.pi = pi.reshape(self.flatshape) + + # but store original shape so we can convert all outputs to it + self.shape = i.shape + self.grid = grid + + # also store shape of the endogenous grid itself + self.endog_shape = self.shape[-1:] + + def forward(self, D): + return het_compiled.forward_policy_1d(D.reshape(self.flatshape), self.i, self.pi).reshape(self.shape) + + def expectation(self, X): + return het_compiled.expectation_policy_1d(X.reshape(self.flatshape), self.i, self.pi).reshape(self.shape) + + def forward_shockable(self, Dss): + return ForwardShockablePolicyLottery1D(self.i.reshape(self.shape), self.pi.reshape(self.shape), + self.grid, Dss) + + +class ForwardShockablePolicyLottery1D(PolicyLottery1D, ForwardShockableTransition): + def __init__(self, i, pi, grid, Dss): + super().__init__(i, pi, grid) + self.Dss = Dss.reshape(self.flatshape) + self.space = grid[self.i+1] - grid[self.i] + + def forward_shock(self, da): + pi_shock = - da.reshape(self.flatshape) / self.space + return het_compiled.forward_policy_shock_1d(self.Dss, self.i, pi_shock).reshape(self.shape) + + +def lottery_2d(a, b, a_grid, b_grid, monotonic=False): + if not monotonic: + return PolicyLottery2D(*interpolate_coord_robust(a_grid, a), + *interpolate_coord_robust(b_grid, b), a_grid, b_grid) + if monotonic: + # right now we have no monotonic 2D examples, so this shouldn't be called + return PolicyLottery2D(*interpolate_coord(a_grid, a), + *interpolate_coord(b_grid, b), a_grid, b_grid) + + +class PolicyLottery2D(Transition): + def __init__(self, i1, pi1, i2, pi2, grid1, grid2): + # flatten non-policy dimensions into one because that's what methods accept + self.i1 = i1.reshape((-1,) + grid1.shape + grid2.shape) + self.flatshape = self.i1.shape + + self.i2 = i2.reshape(self.flatshape) + self.pi1 = pi1.reshape(self.flatshape) + self.pi2 = pi2.reshape(self.flatshape) + + # but store original shape so we can convert all outputs to it + self.shape = i1.shape + self.grid1 = grid1 + self.grid2 = grid2 + + # also store shape of the endogenous grid itself + self.endog_shape = self.shape[-2:] + + def forward(self, D): + return het_compiled.forward_policy_2d(D.reshape(self.flatshape), self.i1, self.i2, + self.pi1, self.pi2).reshape(self.shape) + + def expectation(self, X): + return het_compiled.expectation_policy_2d(X.reshape(self.flatshape), self.i1, self.i2, + self.pi1, self.pi2).reshape(self.shape) + + def forward_shockable(self, Dss): + return ForwardShockablePolicyLottery2D(self.i1.reshape(self.shape), self.pi1.reshape(self.shape), + self.i2.reshape(self.shape), self.pi2.reshape(self.shape), + self.grid1, self.grid2, Dss) + + +class ForwardShockablePolicyLottery2D(PolicyLottery2D, ForwardShockableTransition): + def __init__(self, i1, pi1, i2, pi2, grid1, grid2, Dss): + super().__init__(i1, pi1, i2, pi2, grid1, grid2) + self.Dss = Dss.reshape(self.flatshape) + self.space1 = grid1[self.i1+1] - grid1[self.i1] + self.space2 = grid2[self.i2+1] - grid2[self.i2] + + def forward_shock(self, da): + da1, da2 = da + pi_shock1 = -da1.reshape(self.flatshape) / self.space1 + pi_shock2 = -da2.reshape(self.flatshape) / self.space2 + + return het_compiled.forward_policy_shock_2d(self.Dss, self.i1, self.i2, self.pi1, self.pi2, + pi_shock1, pi_shock2).reshape(self.shape) + + +class Markov(Transition): + def __init__(self, Pi, i): + self.Pi = Pi + self.Pi_T = self.Pi.T + if isinstance(self.Pi_T, np.ndarray): + # optimization: copy to get right order in memory + self.Pi_T = self.Pi_T.copy() + self.i = i + + def forward(self, D): + return multiply_ith_dimension(self.Pi_T, self.i, D) + + def expectation(self, X): + return multiply_ith_dimension(self.Pi, self.i, X) + + def forward_shockable(self, Dss): + return ForwardShockableMarkov(self.Pi, self.i, Dss) + + def expectation_shockable(self, Xss): + return ExpectationShockableMarkov(self.Pi, self.i, Xss) + + def stationary(self, pi_seed, tol=1E-11, maxit=10_000): + return general_stationary(self.Pi, pi_seed, tol, maxit) + + +class ForwardShockableMarkov(Markov, ForwardShockableTransition): + def __init__(self, Pi, i, Dss): + super().__init__(Pi, i) + self.Dss = Dss + + def forward_shock(self, dPi): + return multiply_ith_dimension(dPi.T, self.i, self.Dss) + + +class ExpectationShockableMarkov(Markov, ExpectationShockableTransition): + def __init__(self, Pi, i, Xss): + super().__init__(Pi, i) + self.Xss = Xss + + def expectation_shock(self, dPi): + return multiply_ith_dimension(dPi, self.i, self.Xss) + + +class CombinedTransition(Transition): + def __init__(self, stages: Sequence[Transition]): + self.stages = stages + + def forward(self, D): + for stage in self.stages: + D = stage.forward(D) + return D + + def expectation(self, X): + for stage in reversed(self.stages): + X = stage.expectation(X) + return X + + def forward_shockable(self, Dss): + shockable_stages = [] + for stage in self.stages: + shockable_stages.append(stage.forward_shockable(Dss)) + Dss = stage.forward(Dss) + + return ForwardShockableCombinedTransition(shockable_stages) + + def expectation_shockable(self, Xss): + shockable_stages = [] + for stage in reversed(self.stages): + shockable_stages.append(stage.expectation_shockable(Xss)) + Xss = stage.expectation(Xss) + + return ExpectationShockableCombinedTransition(list(reversed(shockable_stages))) + + def __getitem__(self, i): + return self.stages[i] + + +Shock = Any +ListTupleShocks = Union[List[Shock], Tuple[Shock]] + +class ForwardShockableCombinedTransition(CombinedTransition, ForwardShockableTransition): + def __init__(self, stages: Sequence[ForwardShockableTransition]): + self.stages = stages + self.Dss = stages[0].Dss + + def forward_shock(self, shocks: Optional[Sequence[Optional[Union[Shock, ListTupleShocks]]]]): + if shocks is None: + return None + + # each entry of shocks is either a sequence (list or tuple) + dD = None + + for stage, shock in zip(self.stages, shocks): + if shock is not None: + dD_shock = stage.forward_shock(shock) + else: + dD_shock = None + + if dD is not None: + dD = stage.forward(dD) + + if shock is not None: + dD += dD_shock + else: + dD = dD_shock + + return dD + + +class ExpectationShockableCombinedTransition(CombinedTransition, ExpectationShockableTransition): + def __init__(self, stages: Sequence[ExpectationShockableTransition]): + self.stages = stages + self.Xss = stages[-1].Xss + + def expectation_shock(self, shocks: Sequence[Optional[Union[Shock, ListTupleShocks]]]): + dX = None + + for stage, shock in zip(reversed(self.stages), reversed(shocks)): + if shock is not None: + dX_shock = stage.expectation_shock(shock) + else: + dX_shock = None + + if dX is not None: + dX = stage.expectation(dX) + + if shock is not None: + dX += dX_shock + else: + dX = dX_shock + + return dX \ No newline at end of file diff --git a/src/sequence_jacobian/blocks/support/parent.py b/src/sequence_jacobian/blocks/support/parent.py new file mode 100644 index 0000000..6c3b1ff --- /dev/null +++ b/src/sequence_jacobian/blocks/support/parent.py @@ -0,0 +1,81 @@ +class Parent: + # see tests in test_parent_block.py + + def __init__(self, blocks, name=None): + # dict from names to immediate kid blocks themselves + # dict from descendants to the names of kid blocks through which to access them + # "descendants" of a block include itself + if not hasattr(self, 'name') and name is not None: + self.name = name + + kids = {} + descendants = {} + + for block in blocks: + kids[block.name] = block + + if isinstance(block, Parent): + for k in block.descendants: + if k in descendants: + raise ValueError(f'Overlapping block name {k}') + descendants[k] = block.name + else: + descendants[block.name] = block.name + + # add yourself to descendants too! but you don't belong to any kid... + if self.name in descendants: + raise ValueError(f'Overlapping block name {self.name}') + descendants[self.name] = None + + self.kids = kids + self.descendants = descendants + + def __getitem__(self, k): + if k == self.name: + return self + elif k in self.kids: + return self.kids[k] + else: + return self.kids[self.descendants[k]][k] + + def select(self, d, kid): + """If d is a dict with block names as keys and kid is a kid, select only the entries in d that are descendants of kid""" + return {k: v for k, v in d.items() if k in self.kids[kid].descendants} + + def path(self, k, reverse=True): + if k not in self.descendants: + raise KeyError(f'Cannot get path to {k} because it is not a descendant of current block') + + if k != self.name: + kid = self.kids[self.descendants[k]] + if isinstance(kid, Parent): + p = kid.path(k, reverse=False) + else: + p = [k] + else: + p = [] + p.append(self.name) + + if reverse: + return list(reversed(p)) + else: + return p + + def get_attribute(self, k, attr): + """Gets attribute attr from descendant k, respecting any remapping + along the way (requires that attr is list, dict, set)""" + if k == self.name: + inner = getattr(self, attr) + else: + kid = self.kids[self.descendants[k]] + if isinstance(kid, Parent): + inner = kid.get_attribute(k, attr) + else: + inner = getattr(kid, attr) + if hasattr(kid, 'M'): + inner = kid.M @ inner + + if hasattr(self, 'M'): + return self.M @ inner + else: + return inner diff --git a/sequence_jacobian/blocks/support/simple_displacement.py b/src/sequence_jacobian/blocks/support/simple_displacement.py similarity index 90% rename from sequence_jacobian/blocks/support/simple_displacement.py rename to src/sequence_jacobian/blocks/support/simple_displacement.py index 8cfd2e0..62959c5 100644 --- a/sequence_jacobian/blocks/support/simple_displacement.py +++ b/src/sequence_jacobian/blocks/support/simple_displacement.py @@ -4,6 +4,7 @@ import numbers from warnings import warn +from ...utilities.misc import numeric_primitive def ignore(x): if isinstance(x, int): @@ -21,6 +22,10 @@ class IgnoreInt(int): Standard arithmetic operators including +, -, x, /, ** all overloaded to "promote" the result of any arithmetic operation with an Ignore type to an Ignore type. e.g. type(Ignore(1) + 1) is Ignore """ + + def __repr__(self): + return f'IgnoreInt({numeric_primitive(self)})' + @property def ss(self): return self @@ -37,11 +42,6 @@ def __pos__(self): def __neg__(self): return ignore(-numeric_primitive(self)) - # Tried using the multipledispatch package but @dispatch requires the classes being dispatched on to be defined - # prior to the use of the decorator @dispatch("ClassName"), hence making it impossible to overload in this way, - # as opposed to how isinstance() is evaluated at runtime, so it is valid to check isinstance even if in this module - # the class is defined later on in the module. - # Thus, we need to specially overload the left operations to check if `other` is a Displace to promote properly def __add__(self, other): if isinstance(other, Displace) or isinstance(other, AccumulatedDerivative): return other.__radd__(numeric_primitive(self)) @@ -109,6 +109,9 @@ class IgnoreFloat(float): any arithmetic operation with an Ignore type to an Ignore type. e.g. type(Ignore(1) + 1) is Ignore """ + def __repr__(self): + return f'IgnoreFloat({numeric_primitive(self)})' + @property def ss(self): return self @@ -125,11 +128,6 @@ def __pos__(self): def __neg__(self): return ignore(-numeric_primitive(self)) - # Tried using the multipledispatch package but @dispatch requires the classes being dispatched on to be defined - # prior to the use of the decorator @dispatch("ClassName"), hence making it impossible to overload in this way, - # as opposed to how isinstance() is evaluated at runtime, so it is valid to check isinstance even if in this module - # the class is defined later on in the module. - # Thus, we need to specially overload the left operations to check if `other` is a Displace to promote properly def __add__(self, other): if isinstance(other, Displace) or isinstance(other, AccumulatedDerivative): return other.__radd__(numeric_primitive(self)) @@ -200,6 +198,9 @@ def __new__(cls, x): obj = np.asarray(x).view(cls) return obj + def __repr__(self): + return f'IgnoreVector({numeric_primitive(self)})' + @property def ss(self): return self @@ -275,170 +276,179 @@ class Displace(np.ndarray): """This class makes time displacements of a time path, given the steady-state value. Needed for SimpleBlock.td()""" - def __new__(cls, x, ss=None, name='UNKNOWN'): + def __new__(cls, x, ss=None, ss_initial=None, name='UNKNOWN'): obj = np.asarray(x).view(cls) obj.ss = ss + obj.ss_initial = ss_initial obj.name = name return obj + def __array_finalize__(self, obj): + # note by Matt: not sure what this does? + self.ss = getattr(obj, "ss", None) + self.ss_initial = getattr(obj, "ss_initial", None) + self.name = getattr(obj, "name", "UNKNOWN") + + def __repr__(self): + return f'Displace({numeric_primitive(self)})' + # TODO: Implemented a very preliminary generalization of Displace to higher-dimensional (>1) ndarrays # however the rigorous operator overloading/testing has not been checked for higher dimensions. - # Should also implement some checks for the dimension of .ss, to ensure that it's always N-1 - # where we also assume that the *last* dimension is the time dimension + # (Matt: fixed so that it's the first dimension that is time dimension, consistent with everything else) def __call__(self, index): if index != 0: if self.ss is None: raise KeyError(f'Trying to call {self.name}({index}), but steady-state {self.name} not given!') newx = np.zeros(np.shape(self)) if index > 0: - newx[..., :-index] = numeric_primitive(self)[..., index:] - newx[..., -index:] = self.ss + newx[:-index] = numeric_primitive(self)[index:] + newx[-index:] = self.ss else: - newx[..., -index:] = numeric_primitive(self)[..., :index] - newx[..., :-index] = self.ss - return Displace(newx, ss=self.ss) + newx[-index:] = numeric_primitive(self)[:index] + newx[:-index] = self.ss_initial + return Displace(newx, self.ss, self.ss_initial) else: return self def apply(self, f, **kwargs): - return Displace(f(numeric_primitive(self), **kwargs), ss=f(self.ss)) + return Displace(f(numeric_primitive(self), **kwargs), ss=f(self.ss), ss_initial=f(self.ss_initial)) def __pos__(self): return self def __neg__(self): - return Displace(-numeric_primitive(self), ss=-self.ss) + return Displace(-numeric_primitive(self), ss=-self.ss, ss_initial=-self.ss_initial) def __add__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(self) + numeric_primitive(other), - ss=self.ss + other.ss) + ss=self.ss + other.ss, ss_initial=self.ss_initial + other.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(self) + numeric_primitive(other), - ss=self.ss + numeric_primitive(other)) + ss=self.ss + numeric_primitive(other), ss_initial=self.ss_initial + numeric_primitive(other)) else: # TODO: See if there is a different, systematic way we want to handle this case. warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(self) + numeric_primitive(other), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __radd__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(other) + numeric_primitive(self), - ss=other.ss + self.ss) + ss=other.ss + self.ss, ss_initial=other.ss_initial + self.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(other) + numeric_primitive(self), - ss=numeric_primitive(other) + self.ss) + ss=numeric_primitive(other) + self.ss, ss_initial=numeric_primitive(other) + self.ss_initial) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(other) + numeric_primitive(self), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __sub__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(self) - numeric_primitive(other), - ss=self.ss - other.ss) + ss=self.ss - other.ss, ss_initial=self.ss_initial - other.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(self) - numeric_primitive(other), - ss=self.ss - numeric_primitive(other)) + ss=self.ss - numeric_primitive(other), ss_initial=self.ss_initial - numeric_primitive(other)) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(self) - numeric_primitive(other), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __rsub__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(other) - numeric_primitive(self), - ss=other.ss - self.ss) + ss=other.ss - self.ss, ss_initial=other.ss_initial - self.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(other) - numeric_primitive(self), - ss=numeric_primitive(other) - self.ss) + ss=numeric_primitive(other) - self.ss, ss_initial=numeric_primitive(other) - self.ss_initial) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(other) - numeric_primitive(self), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __mul__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(self) * numeric_primitive(other), - ss=self.ss * other.ss) + ss=self.ss * other.ss, ss_initial=self.ss_initial * other.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(self) * numeric_primitive(other), - ss=self.ss * numeric_primitive(other)) + ss=self.ss * numeric_primitive(other), ss_initial=self.ss_initial * numeric_primitive(other)) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(self) * numeric_primitive(other), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __rmul__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(other) * numeric_primitive(self), - ss=other.ss * self.ss) + ss=other.ss * self.ss, ss_initial=other.ss_initial * self.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(other) * numeric_primitive(self), - ss=numeric_primitive(other) * self.ss) + ss=numeric_primitive(other) * self.ss, ss_initial=numeric_primitive(other) * self.ss_initial) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(other) * numeric_primitive(self), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __truediv__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(self) / numeric_primitive(other), - ss=self.ss / other.ss) + ss=self.ss / other.ss, ss_initial=self.ss_initial / other.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(self) / numeric_primitive(other), - ss=self.ss / numeric_primitive(other)) + ss=self.ss / numeric_primitive(other), ss_initial=self.ss_initial / numeric_primitive(other)) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(self) / numeric_primitive(other), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __rtruediv__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(other) / numeric_primitive(self), - ss=other.ss / self.ss) + ss=other.ss / self.ss, ss_initial=other.ss_initial / self.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(other) / numeric_primitive(self), - ss=numeric_primitive(other) / self.ss) + ss=numeric_primitive(other) / self.ss, ss_initial=numeric_primitive(other) / self.ss_initial) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(other) / numeric_primitive(self), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __pow__(self, power): if isinstance(power, Displace): return Displace(numeric_primitive(self) ** numeric_primitive(power), - ss=self.ss ** power.ss) + ss=self.ss ** power.ss, ss_initial=self.ss_initial ** power.ss_initial) elif np.isscalar(power): return Displace(numeric_primitive(self) ** numeric_primitive(power), - ss=self.ss ** numeric_primitive(power)) + ss=self.ss ** numeric_primitive(power), ss_initial=self.ss_initial ** numeric_primitive(power)) else: warn("\n" + f"Applying operation to {power}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(self) ** numeric_primitive(power), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) def __rpow__(self, other): if isinstance(other, Displace): return Displace(numeric_primitive(other) ** numeric_primitive(self), - ss=other.ss ** self.ss) + ss=other.ss ** self.ss, ss_initial=other.ss_initial ** self.ss_initial) elif np.isscalar(other): return Displace(numeric_primitive(other) ** numeric_primitive(self), - ss=numeric_primitive(other) ** self.ss) + ss=numeric_primitive(other) ** self.ss, ss_initial=numeric_primitive(other) ** self.ss_initial) else: warn("\n" + f"Applying operation to {other}, a vector, and {self}, a Displace." + "\n" + f"The resulting Displace object will retain the steady-state value of the original Displace object.") return Displace(numeric_primitive(other) ** numeric_primitive(self), - ss=self.ss) + ss=self.ss, ss_initial=self.ss_initial) class AccumulatedDerivative: @@ -666,14 +676,6 @@ def compute_l(i, m, j, n): return max(m, n + i) -def numeric_primitive(instance): - # If it is already a primitive, just return it - if type(instance) in {int, float, np.ndarray}: - return instance - else: - return instance.real if np.isscalar(instance) else instance.base - - # TODO: This needs its own unit test def vectorize_func_over_time(func, *args): """In `args` some arguments will be Displace objects and others will be Ignore/IgnoreVector objects. diff --git a/sequence_jacobian/steady_state.py b/src/sequence_jacobian/blocks/support/steady_state.py similarity index 52% rename from sequence_jacobian/steady_state.py rename to src/sequence_jacobian/blocks/support/steady_state.py index 19d2822..a9464f0 100644 --- a/sequence_jacobian/steady_state.py +++ b/src/sequence_jacobian/blocks/support/steady_state.py @@ -1,123 +1,46 @@ -"""A general function for computing a model's steady state variables and parameters values""" +"""Various lower-level functions to support the computation of steady states""" import warnings import numpy as np import scipy.optimize as opt -from copy import deepcopy +from numbers import Real +from functools import partial -from . import utilities as utils -from .utilities.misc import unprime, dict_diff, smart_zip, smart_zeros, find_blocks_with_hetoutputs -from .blocks.simple_block import SimpleBlock -from .blocks.helper_block import HelperBlock -from .blocks.het_block import HetBlock +from ...utilities import misc, solvers -# Find the steady state solution -def steady_state(blocks, calibration, unknowns, targets, - consistency_check=True, ttol=2e-12, ctol=1e-9, - backward_tol=1e-8, forward_tol=1e-10, - verbose=False, fragile=False, solver=None, solver_kwargs=None, - constrained_method="linear_continuation", constrained_kwargs=None): - """ - For a given model (blocks), calibration, unknowns, and targets, solve for the steady state values. - - blocks: `list` - A list of blocks, which include the types: SimpleBlock, HetBlock, HelperBlock, SolvedBlock, CombinedBlock - calibration: `dict` - The pre-specified values of variables/parameters provided to the steady state computation - unknowns: `dict` - A dictionary mapping unknown variables to either initial values or bounds to be provided to the numerical solver - targets: `dict` - A dictionary mapping target variables to desired numerical values, other variables solved for along the DAG - consistency_check: `bool` - If HelperBlocks are a portion of the argument blocks, re-run the DAG with the computed steady state values - without the assistance of HelperBlocks and see if the targets are still hit - ttol: `float` - The tolerance for the targets---how close the user wants the computed target values to equal the desired values - ctol: `float` - The tolerance for the consistency check---how close the user wants the computed target values, without the - use of HelperBlocks, to equal the desired values - backward_tol/forward_tol: `float` - See `HetBlock` .ss method docstring for details on these additional convergence tolerances - verbose: `bool` - Display the content of optional print statements within the solver for more responsive feedback - fragile: `bool` - Throw errors instead of warnings when certain criteria are not met, i.e if the consistency_check fails - solver: `string` - The name of the numerical solver that the user would like to user. Can either be a custom solver the user - implemented, or one of the standard root-finding methods in scipy.optim.root_scalar or scipy.optim.root - solver_kwargs: `dict` - The keyword arguments that the user's chosen solver requires to run - constrained_method: `str` - When using solvers that typically only take an initial value, x0, we provide a few options for manipulating - the solver to account for bounds when finding a solution. These methods are described in the - constrained_multivariate_residual function. - constrained_kwargs: - The keyword arguments that the user's chosen constrained method requires to run - - return: ss_values: `dict` - A dictionary containing all of the pre-specified values and computed values from the steady state computation - """ - - # Populate otherwise mutable default arguments +def instantiate_steady_state_mutable_kwargs(dissolve, block_kwargs, solver_kwargs, constrained_kwargs): + """Instantiate mutable types from `None` default values in the steady_state function""" + if dissolve is None: + dissolve = [] + if block_kwargs is None: + block_kwargs = {} if solver_kwargs is None: solver_kwargs = {} if constrained_kwargs is None: constrained_kwargs = {} - ss_values = deepcopy(calibration) - topsorted = utils.graph.block_sort(blocks, calibration=calibration) - - def residual(unknown_values, include_helpers=True, update_unknowns_inplace=False): - ss_values.update(smart_zip(unknowns.keys(), unknown_values)) + return dissolve, block_kwargs, solver_kwargs, constrained_kwargs - helper_outputs = {} - # Progress through the DAG computing the resulting steady state values based on the unknown_values - # provided to the residual function - for i in topsorted: - if not include_helpers and isinstance(blocks[i], HelperBlock): - continue - else: - outputs = eval_block_ss(blocks[i], ss_values, consistency_check=consistency_check, - ttol=ttol, ctol=ctol, backward_tol=backward_tol, - forward_tol=forward_tol, verbose=verbose) - if include_helpers and isinstance(blocks[i], HelperBlock): - helper_outputs.update(outputs) - ss_values.update(outputs) - else: - # Don't overwrite entries in ss_values corresponding to what has already - # been solved for in helper_blocks so we can check for consistency after-the-fact - ss_values.update(dict_diff(outputs, helper_outputs)) - - # Update the "unknowns" dictionary *in place* with its steady state values. - # i.e. the "unknowns" in the namespace in which this function is invoked will change! - # Useful for a) if the unknown values are updated while iterating each blocks' ss computation within the DAG, - # and/or b) if the user wants to update "unknowns" in place for use in other computations. - if update_unknowns_inplace: - unknowns.update(smart_zip(unknowns.keys(), [ss_values[key] for key in unknowns.keys()])) - - # Because in solve_for_unknowns, models that are fully "solved" (i.e. RBC) require the - # dict of ss_values to compute the "unknown_solutions" - return compute_target_values(targets, ss_values) - - unknown_solutions = _solve_for_unknowns(residual, unknowns, solver, solver_kwargs, - constrained_method, constrained_kwargs, - tol=ttol, verbose=verbose) - - # Check that the solution is consistent with what would come out of the DAG without the helper blocks - if consistency_check: - cresid = abs(np.max(residual(unknown_solutions, include_helpers=False))) - run_consistency_check(cresid, ctol=ctol, fragile=fragile) - - # Update to set the solutions for the steady state values of the unknowns - ss_values.update(zip(unknowns, utils.misc.make_tuple(unknown_solutions))) - - # Find the hetoutputs of the Hetblocks that have hetoutputs - for i in find_blocks_with_hetoutputs(blocks): - ss_values.update(eval_block_ss(blocks[i], ss_values, hetoutput=True)) - - return ss_values +def provide_solver_default(unknowns): + if len(unknowns) == 1: + bounds = list(unknowns.values())[0] + if not isinstance(bounds, tuple) or bounds[0] > bounds[1]: + raise ValueError("Unable to find a compatible one-dimensional solver with provided `unknowns`.\n" + " Please provide valid lower/upper bounds, e.g. unknowns = {`a`: (0, 1)}") + else: + return "brentq" + elif len(unknowns) > 1: + init_values = list(unknowns.values()) + if not np.all([isinstance(v, Real) for v in init_values]): + raise ValueError("Unable to find a compatible multi-dimensional solver with provided `unknowns`.\n" + " Please provide valid initial values, e.g. unknowns = {`a`: 1, `b`: 2}") + else: + return "broyden_custom" + else: + raise ValueError("`unknowns` is empty! Please provide a dict of keys/values equal to the number of unknowns" + " that need to be solved for.") def run_consistency_check(cresid, ctol=1e-9, fragile=False): @@ -134,12 +57,6 @@ def run_consistency_check(cresid, ctol=1e-9, fragile=False): f" If this is not an issue, adjust ctol accordingly.") -def find_target_block(blocks, target): - for block in blocks: - if target in blocks.output: - return block - - # Allow targets to be specified in the following formats # 1) target = {"asset_mkt": 0} or ["asset_mkt"] (the standard case, where the target = 0) # 2) target = {"r": 0.01} (allowing for the target to be non-zero) @@ -161,9 +78,6 @@ def compute_target_values(targets, potential_args): target_values[i] = potential_args[t] - potential_args[v] else: target_values[i] = potential_args[t] - v - # TODO: Implement feature to allow for an arbitrary explicit function expression as a potential target value - # e.g. targets = {"goods_mkt": "Y - C - I"}, so long as the expression is only comprise of generic numerical - # operators and variables solved for along the DAG prior to reaching the target. # Univariate solvers require float return values (and not lists) if len(targets) == 1: @@ -171,48 +85,71 @@ def compute_target_values(targets, potential_args): else: return target_values -# Analogous to the SHADE workflow of having blocks call utils.apply(self._fss, inputs) but not as general. -def eval_block_ss(block, potential_args, consistency_check=True, ttol=2e-12, ctol=1e-9, - backward_tol=1e-8, forward_tol=1e-10, verbose=False, **kwargs): - """ - Evaluate the .ss method of a block, given a dictionary of potential arguments. - Refer to the `steady_state` function docstring for information on args/kwargs +def compare_steady_states(ss_ref, ss_comp, tol=1e-8, name_map=None, internal=True, check_same_keys=True, verbose=False): + """Check if two steady state dicts (can be flat dicts or SteadyStateDict objects) are the same up to a tolerance""" + if name_map is None: + name_map = {} - return: A `dict` of output names (as `str`) and output values from evaluating the .ss method of a block - """ - input_args = {unprime(arg_name): potential_args[unprime(arg_name)] for arg_name in block.inputs} - - # Simple and HetBlocks require different handling of block.ss() output since - # SimpleBlocks return a tuple of un-labeled arguments, whereas HetBlocks return dictionaries - if isinstance(block, SimpleBlock) or isinstance(block, HelperBlock): - output_args = utils.misc.make_tuple(block.ss(**input_args, **kwargs)) - outputs = {o: output_args[i] for i, o in enumerate(block.output_list)} - else: # assume it's a HetBlock or a SolvedBlock - if isinstance(block, HetBlock): # since .ss for SolvedBlocks calls the steady_state driver function - outputs = block.ss(**input_args, backward_tol=backward_tol, forward_tol=forward_tol, **kwargs) - else: # since .ss for SolvedBlocks calls the steady_state driver function - outputs = block.ss(**input_args, consistency_check=consistency_check, - ttol=ttol, ctol=ctol, verbose=verbose, **kwargs) - - return outputs - - -def _solve_for_unknowns(residual, unknowns, solver, solver_kwargs, - constrained_method, constrained_kwargs, - tol=2e-12, verbose=False): - """ - Given a residual function (constructed within steady_state) and a set of bounds or initial values for + valid = True + + # Compare the steady state values present in both ss_ref and ss_comp + if internal: + if not hasattr(ss_ref, "internal") or not hasattr(ss_comp, "internal"): + warnings.warn("The provided steady state dicts do not both have .internal attrs. Will only compare" + " top-level values") + ds_to_check = [(ss_ref, ss_comp, "toplevel")] + else: + ds_to_check = [(ss_ref, ss_comp, "toplevel")] + [(ss_ref.internal[i], ss_comp.internal[i], i + "_internal") for i in ss_ref.internal] + else: + ds_to_check = [(ss_ref, ss_comp, "toplevel")] + + for ds in ds_to_check: + d_ref, d_comp, level = ds + for key_ref in d_ref.keys(): + if key_ref in d_comp.keys(): + key_comp = key_ref + elif key_ref in name_map: + key_comp = name_map[key_ref] + else: + continue + + if np.isscalar(d_ref[key_ref]): + resid = abs(d_ref[key_ref] - d_comp[key_comp]) + else: + resid = np.linalg.norm(d_ref[key_ref].ravel() - d_comp[key_comp].ravel(), np.inf) + if verbose: + print(f"{key_ref} resid: {resid}") + else: + if not np.all(np.isclose(resid, 0., atol=tol)): + valid = False + + # Show the steady state values present in only one of d_ref or d_comp, i.e. if there are missing keys + if check_same_keys: + d_ref_incl_mapped = set(d_ref.keys()) - set(name_map.keys()) + d_comp_incl_mapped = set(d_comp.keys()) - set(name_map.values()) + diff_keys = d_ref_incl_mapped.symmetric_difference(d_comp_incl_mapped) + if diff_keys: + if verbose: + print(f"At level '{level}', the keys present only one of the two steady state dicts are {diff_keys}") + valid = False + + return valid + + +def solve_for_unknowns(residual, unknowns, solver, solver_kwargs, residual_kwargs=None, + constrained_method="linear_continuation", constrained_kwargs=None, + tol=2e-12, verbose=False): + """Given a residual function (constructed within steady_state) and a set of bounds or initial values for the set of unknowns, solve for the root. - TODO: Implemented as a hidden method as of now because this function relies on the structure of steady_state - specifically and will not work with a generic residual function, due to the way it currently expects residual - to call variables not provided as arguments explicitly but that exist in its enclosing scope. residual: `function` A function to be supplied to a numerical solver that takes unknown values as arguments and returns computed targets. unknowns: `dict` Refer to the `steady_state` function docstring for the "unknowns" variable + targets: `dict` + Refer to the `steady_state` function docstring for the "targets" variable tol: `float` The absolute convergence tolerance of the computed target to the desired target value in the numerical solver solver: `str` @@ -222,15 +159,22 @@ def _solve_for_unknowns(residual, unknowns, solver, solver_kwargs, return: The root[s] of the residual function as either a scalar (float) or a list of floats """ + if residual_kwargs is None: + residual_kwargs = {} + scipy_optimize_uni_solvers = ["bisect", "brentq", "brenth", "ridder", "toms748", "newton", "secant", "halley"] scipy_optimize_multi_solvers = ["hybr", "lm", "broyden1", "broyden2", "anderson", "linearmixing", "diagbroyden", "excitingmixing", "krylov", "df-sane"] + # Wrap kwargs into the residual function + residual_f = partial(residual, **residual_kwargs) + if solver is None: raise RuntimeError("Must provide a numerical solver from the following set: brentq, broyden, solved") elif solver in scipy_optimize_uni_solvers: initial_values_or_bounds = extract_univariate_initial_values_or_bounds(unknowns) - result = opt.root_scalar(residual, method=solver, xtol=tol, **initial_values_or_bounds, **solver_kwargs) + result = opt.root_scalar(residual_f, method=solver, xtol=tol, + **initial_values_or_bounds, **solver_kwargs) if not result.converged: raise ValueError(f"Steady-state solver, {solver}, did not converge.") unknown_solutions = result.root @@ -238,12 +182,14 @@ def _solve_for_unknowns(residual, unknowns, solver, solver_kwargs, initial_values, bounds = extract_multivariate_initial_values_and_bounds(unknowns) # If no bounds were provided if not bounds: - result = opt.root(residual, initial_values, method=solver, tol=tol, **solver_kwargs) + result = opt.root(residual_f, initial_values, + method=solver, tol=tol, **solver_kwargs) else: - constrained_residual = constrained_multivariate_residual(residual, bounds, verbose=verbose, + constrained_residual = constrained_multivariate_residual(residual_f, bounds, verbose=verbose, method=constrained_method, **constrained_kwargs) - result = opt.root(constrained_residual, initial_values, method=solver, tol=tol, **solver_kwargs) + result = opt.root(constrained_residual, initial_values, + method=solver, tol=tol, **solver_kwargs) if not result.success: raise ValueError(f"Steady-state solver, {solver}, did not converge." f" The termination status is {result.status}.") @@ -254,39 +200,37 @@ def _solve_for_unknowns(residual, unknowns, solver, solver_kwargs, initial_values, bounds = extract_multivariate_initial_values_and_bounds(unknowns) # If no bounds were provided if not bounds: - unknown_solutions, _ = utils.solvers.broyden_solver(residual, initial_values, tol=tol, - verbose=verbose, **solver_kwargs) + unknown_solutions, _ = solvers.broyden_solver(residual_f, initial_values, + tol=tol, verbose=verbose, **solver_kwargs) else: - constrained_residual = constrained_multivariate_residual(residual, bounds, verbose=verbose, + constrained_residual = constrained_multivariate_residual(residual_f, bounds, verbose=verbose, method=constrained_method, **constrained_kwargs) - unknown_solutions, _ = utils.solvers.broyden_solver(constrained_residual, initial_values, - verbose=verbose, tol=tol, **solver_kwargs) + unknown_solutions, _ = solvers.broyden_solver(constrained_residual, initial_values, + verbose=verbose, tol=tol, **solver_kwargs) unknown_solutions = list(unknown_solutions) elif solver == "newton_custom": initial_values, bounds = extract_multivariate_initial_values_and_bounds(unknowns) # If no bounds were provided if not bounds: - unknown_solutions, _ = utils.solvers.newton_solver(residual, initial_values, tol=tol, - verbose=verbose, **solver_kwargs) + unknown_solutions, _ = solvers.newton_solver(residual_f, initial_values, + tol=tol, verbose=verbose, **solver_kwargs) else: - constrained_residual = constrained_multivariate_residual(residual, bounds, verbose=verbose, + constrained_residual = constrained_multivariate_residual(residual_f, bounds, verbose=verbose, method=constrained_method, **constrained_kwargs) - unknown_solutions, _ = utils.solvers.newton_solver(constrained_residual, initial_values, - verbose=verbose, tol=tol, **solver_kwargs) + unknown_solutions, _ = solvers.newton_solver(constrained_residual, initial_values, + tol=tol, verbose=verbose, **solver_kwargs) unknown_solutions = list(unknown_solutions) elif solver == "solved": - # If the entire solution is provided by the helper blocks - # Call residual() once to update ss_values and to check the targets match the provided solution. - # The initial value passed into residual (np.zeros()) in this case is irrelevant, but something - # must still be passed in since the residual function requires an argument. - assert abs(np.max(residual(smart_zeros(len(unknowns)), update_unknowns_inplace=True))) < tol - unknown_solutions = list(unknowns.values()) + # If the model either doesn't require a numerical solution or is being evaluated at a candidate solution + # simply call residual_f once to populate the `ss_values` dict + residual_f(unknowns.values()) + unknown_solutions = unknowns.values() else: raise RuntimeError(f"steady_state is not yet compatible with {solver}.") - return unknown_solutions + return dict(misc.smart_zip(unknowns.keys(), unknown_solutions)) def extract_univariate_initial_values_or_bounds(unknowns): @@ -297,7 +241,7 @@ def extract_univariate_initial_values_or_bounds(unknowns): return {"bracket": (val[0], val[1])} -def extract_multivariate_initial_values_and_bounds(unknowns): +def extract_multivariate_initial_values_and_bounds(unknowns, fragile=False): """Provided a dict mapping names of unknowns to initial values/bounds, return separate dicts of the initial values and bounds. Note: For one-sided bounds, simply put np.inf/-np.inf as the other side of the bounds, so there is @@ -308,6 +252,17 @@ def extract_multivariate_initial_values_and_bounds(unknowns): for k, v in unknowns.items(): if np.isscalar(v): initial_values.append(v) + elif len(v) == 2: + if fragile: + raise ValueError(f"{len(v)} is an invalid size for the value of an unknown." + f" the values of `unknowns` must either be a scalar, pertaining to a" + f" single initial value for the root solver to begin from," + f" a length 2 tuple, pertaining to a lower bound and an upper bound," + f" or a length 3 tuple, pertaining to a lower bound, initial value, and upper bound.") + else: + warnings.warn("Interpreting values of `unknowns` from length 2 tuple as lower and upper bounds" + " and averaging them to get a scalar initial value to provide to the solver.") + initial_values.append((v[0] + v[1])/2) elif len(v) == 3: lb, iv, ub = v assert lb < iv < ub @@ -317,6 +272,7 @@ def extract_multivariate_initial_values_and_bounds(unknowns): raise ValueError(f"{len(v)} is an invalid size for the value of an unknown." f" the values of `unknowns` must either be a scalar, pertaining to a" f" single initial value for the root solver to begin from," + f" a length 2 tuple, pertaining to a lower bound and an upper bound," f" or a length 3 tuple, pertaining to a lower bound, initial value, and upper bound.") return np.asarray(initial_values), multi_bounds diff --git a/src/sequence_jacobian/classes/__init__.py b/src/sequence_jacobian/classes/__init__.py new file mode 100644 index 0000000..44a63e8 --- /dev/null +++ b/src/sequence_jacobian/classes/__init__.py @@ -0,0 +1,4 @@ +from .steady_state_dict import SteadyStateDict, UserProvidedSS +from .impulse_dict import ImpulseDict +from .jacobian_dict import JacobianDict, FactoredJacobianDict +from .sparse_jacobians import IdentityMatrix, SimpleSparse diff --git a/src/sequence_jacobian/classes/impulse_dict.py b/src/sequence_jacobian/classes/impulse_dict.py new file mode 100644 index 0000000..b0b9b46 --- /dev/null +++ b/src/sequence_jacobian/classes/impulse_dict.py @@ -0,0 +1,115 @@ +"""ImpulseDict class for manipulating impulse responses.""" + +import numpy as np + +from .result_dict import ResultDict + +from ..utilities.ordered_set import OrderedSet +from ..utilities.bijection import Bijection +from .steady_state_dict import SteadyStateDict + +class ImpulseDict(ResultDict): + def __init__(self, data, internals=None, T=None): + if isinstance(data, ImpulseDict): + if internals is not None or T is not None: + raise ValueError('Supplying ImpulseDict and also internal or T to constructor not allowed') + super().__init__(data) + self.T = data.T + else: + if not isinstance(data, dict): + raise ValueError('ImpulseDicts are initialized with a `dict` of top-level impulse responses.') + super().__init__(data, internals) + self.T = (T if T is not None else self.infer_length()) + + def __getitem__(self, k): + return super().__getitem__(k, T=self.T) + + def __add__(self, other): + return self.binary_operation(other, lambda a, b: a + b) + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + return self.binary_operation(other, lambda a, b: a - b) + + def __rsub__(self, other): + return self.binary_operation(other, lambda a, b: b - a) + + def __mul__(self, other): + return self.binary_operation(other, lambda a, b: a * b) + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + return self.binary_operation(other, lambda a, b: a / b) + + def __rtruediv__(self, other): + return self.binary_operation(other, lambda a, b: b / a) + + def __neg__(self): + return self.unary_operation(lambda a: -a) + + def __pos__(self): + return self + + def __abs__(self): + return self.unary_operation(lambda a: abs(a)) + + def binary_operation(self, other, op): + if isinstance(other, (SteadyStateDict, ImpulseDict)): + toplevel = {k: op(v, other[k]) for k, v in self.toplevel.items()} + internals = {} + for b in self.internals: + other_internals = other.internals[b] + internals[b] = {k: op(v, other_internals[k]) for k, v in self.internals[b].items()} + return ImpulseDict(toplevel, internals, self.T) + elif isinstance(other, (float, int)): + toplevel = {k: op(v, other) for k, v in self.toplevel.items()} + internals = {} + for b in self.internals: + internals[b] = {k: op(v, other) for k, v in self.internals[b].items()} + return ImpulseDict(toplevel, internals, self.T) + else: + return NotImplementedError(f'Can only perform operations with ImpulseDicts and other ImpulseDicts, SteadyStateDicts, or numbers, not {type(other).__name__}') + + def unary_operation(self, op): + toplevel = {k: op(v) for k, v in self.toplevel.items()} + internals = {} + for b in self.internals: + internals[b] = {k: op(v) for k, v in self.internals[b].items()} + return ImpulseDict(toplevel, internals, self.T) + + def pack(self): + T = self.T + bigv = np.empty(T*len(self.toplevel)) + for i, v in enumerate(self.toplevel.values()): + bigv[i*T:(i+1)*T] = v + return bigv + + @staticmethod + def unpack(bigv, outputs, T): + impulse = {} + for i, o in enumerate(outputs): + impulse[o] = bigv[i*T:(i+1)*T] + return ImpulseDict(impulse, T=T) + + def infer_length(self): + lengths = [len(v) for v in self.toplevel.values()] + length = max(lengths) + if length != min(lengths): + raise ValueError(f'Building ImpulseDict with inconsistent lengths {max(lengths)} and {min(lengths)}') + return length + + def get(self, k): + """Like __getitem__ but with default of zero impulse""" + if isinstance(k, str): + return self.toplevel.get(k, np.zeros(self.T)) + elif isinstance(k, tuple): + raise TypeError(f'Key {k} to {type(self).__name__} cannot be tuple') + else: + try: + return type(self)({ki: self.toplevel.get(ki, np.zeros(self.T)) for ki in k}, T=self.T) + except TypeError: + raise TypeError(f'Key {k} to {type(self).__name__} needs to be a string or an iterable (list, set, etc) of strings') diff --git a/src/sequence_jacobian/classes/jacobian_dict.py b/src/sequence_jacobian/classes/jacobian_dict.py new file mode 100644 index 0000000..91b3a24 --- /dev/null +++ b/src/sequence_jacobian/classes/jacobian_dict.py @@ -0,0 +1,351 @@ +import copy +import warnings +import numpy as np + +from ..utilities.misc import factor, factored_solve +from ..utilities.ordered_set import OrderedSet +from ..utilities.bijection import Bijection +from .impulse_dict import ImpulseDict +from .sparse_jacobians import IdentityMatrix, SimpleSparse, make_matrix +from typing import Any, Dict, Union + +Array = Any + +Jacobian = Union[np.ndarray, IdentityMatrix, SimpleSparse] + +class NestedDict: + def __init__(self, nesteddict, outputs: OrderedSet=None, inputs: OrderedSet=None, name: str=None): + if isinstance(nesteddict, NestedDict): + self.nesteddict = nesteddict.nesteddict + self.outputs: OrderedSet = nesteddict.outputs + self.inputs: OrderedSet = nesteddict.inputs + self.name: str = nesteddict.name + else: + self.nesteddict = nesteddict + if outputs is None: + outputs = OrderedSet(nesteddict.keys()) + if inputs is None: + inputs = OrderedSet([]) + for v in nesteddict.values(): + inputs |= v + + if not outputs or not inputs: + outputs = OrderedSet([]) + inputs = OrderedSet([]) + + self.outputs = OrderedSet(outputs) + self.inputs = OrderedSet(inputs) + if name is None: + # TODO: Figure out better default naming scheme for NestedDicts + self.name = "NestedDict" + else: + self.name = name + + def __repr__(self): + return f'<{type(self).__name__} outputs={self.outputs}, inputs={self.inputs}>' + + def __iter__(self): + return iter(self.outputs) + + def __or__(self, other): + # non-in-place merge: make a copy, then update + merged = type(self)(self.nesteddict, self.outputs, self.inputs) + merged.update(other) + return merged + + def __getitem__(self, x): + if isinstance(x, str): + # case 1: just a single output, give subdict + return self.nesteddict[x] + elif isinstance(x, tuple): + # case 2: tuple, referring to output and input + o, i = x + o = self.outputs if o == slice(None, None, None) else o + i = self.inputs if i == slice(None, None, None) else i + if isinstance(o, str): + if isinstance(i, str): + # case 2a: one output, one input, return single Jacobian + return self.nesteddict[o][i] + else: + # case 2b: one output, multiple inputs, return dict + return subdict(self.nesteddict[o], i) + else: + # case 2c: multiple outputs, one or more inputs, return NestedDict with outputs o and inputs i + i = (i,) if isinstance(i, str) else i + return type(self)({oo: subdict(self.nesteddict[oo], i) for oo in o}, o, i) + elif isinstance(x, OrderedSet) or isinstance(x, list) or isinstance(x, set): + # case 3: assume that list or set refers just to outputs, get all of those + return type(self)({oo: self.nesteddict[oo] for oo in x}, x, self.inputs) + else: + raise ValueError(f'Tried to get impermissible item {x}') + + def get(self, *args, **kwargs): + # this is for compatibility, not a huge fan + return self.nesteddict.get(*args, **kwargs) + + def update(self, J): + if not J.outputs or not J.inputs: + return + if set(self.inputs) != set(J.inputs): + raise ValueError \ + (f'Cannot merge {type(self).__name__}s with non-overlapping inputs {set(self.inputs) ^ set(J.inputs)}') + if not set(self.outputs).isdisjoint(J.outputs): + raise ValueError \ + (f'Cannot merge {type(self).__name__}s with overlapping outputs {set(self.outputs) & set(J.outputs)}') + self.outputs = self.outputs | J.outputs + self.nesteddict = {**self.nesteddict, **J.nesteddict} + + # Ensure that every output in self has either a Jacobian or filler value for each input, + # s.t. all inputs map to all outputs + def complete(self, filler): + nesteddict = {} + for o in self.outputs: + nesteddict[o] = dict(self.nesteddict[o]) + for i in self.inputs: + if i not in nesteddict[o]: + nesteddict[o][i] = filler + return type(self)(nesteddict, self.outputs, self.inputs) + + +def deduplicate(mylist): + """Remove duplicates while otherwise maintaining order""" + return list(dict.fromkeys(mylist)) + + +def subdict(d, ks): + """Return subdict of d with only keys in ks (if some ks are not in d, ignore them)""" + return {k: d[k] for k in ks if k in d} + + +class JacobianDict(NestedDict): + def __init__(self, nesteddict, outputs=None, inputs=None, name=None, T=None, check=False): + if check: + ensure_valid_jacobiandict(nesteddict) + super().__init__(nesteddict, outputs=outputs, inputs=inputs, name=name) + self.T = T + + @staticmethod + def identity(ks): + return JacobianDict({k: {k: IdentityMatrix()} for k in ks}, ks, ks) + + def addinputs(self): + """Add any inputs that were not already in output list as outputs, with the identity""" + inputs = [x for x in self.inputs if x not in self.outputs] + return self | JacobianDict.identity(inputs) + + def __matmul__(self, x): + if isinstance(x, JacobianDict): + return self.compose(x) + elif isinstance(x, Bijection): + return self.remap(x) + else: + return self.apply(x) + + def __rmatmul__(self, x): + if isinstance(x, Bijection): + return self.remap(x) + + def remap(self, x: Bijection): + if not x: + return self + nesteddict = x @ self.nesteddict + for o in nesteddict.keys(): + nesteddict[o] = x @ nesteddict[o] + return JacobianDict(nesteddict, inputs=x @ self.inputs, outputs=x @ self.outputs) + + def __bool__(self): + return bool(self.outputs) and bool(self.inputs) + + def compose(self, J): + """Returns self @ J""" + if self.T is not None and J.T is not None and self.T != J.T: + raise ValueError(f'Trying to multiply JacobianDicts with inconsistent dimensions {self.T} and {J.T}') + + o_list = self.outputs + m_list = tuple(set(self.inputs) & set(J.outputs)) + i_list = J.inputs + + J_om = self.nesteddict + J_mi = J.nesteddict + J_oi = {} + + for o in o_list: + J_oi[o] = {} + for i in i_list: + Jout = None + for m in m_list: + if m in J_om[o] and i in J_mi[m]: + if Jout is None: + Jout = J_om[o][m] @ J_mi[m][i] + else: + Jout += J_om[o][m] @ J_mi[m][i] + if Jout is not None: + J_oi[o][i] = Jout + + return JacobianDict(J_oi, o_list, i_list) + + def apply(self, x: Union[ImpulseDict, Dict[str, Array]]): + """Returns J @ x""" + x = ImpulseDict(x) + + inputs = x.keys() & set(self.inputs) + J_oi = self.nesteddict + y = {} + + for o in self.outputs: + y[o] = np.zeros(x.T) + J_i = J_oi[o] + for i in inputs: + if i in J_i: + y[o] += J_i[i] @ x[i] + + return ImpulseDict(y, T=x.T) + + def pack(self, T=None): + if T is None: + if self.T is not None: + T = self.T + else: + raise ValueError('Trying to pack {self} into matrix, but do not know {T}') + else: + if self.T is not None and T != self.T: + raise ValueError('{self} has dimension {self.T}, but trying to pack it with alternate dimension {T}') + + J = np.empty((len(self.outputs) * T, len(self.inputs) * T)) + for iO, O in enumerate(self.outputs): + for iI, I in enumerate(self.inputs): + J_OI = self[O].get(I) + if J_OI is not None: + J[(T * iO):(T * (iO + 1)), (T * iI):(T * (iI + 1))] = make_matrix(J_OI, T) + else: + J[(T * iO):(T * (iO + 1)), (T * iI):(T * (iI + 1))] = 0 + return J + + @staticmethod + def unpack(bigjac, outputs, inputs, T): + """If we have an (nO*T)*(nI*T) jacobian and provide names of nO outputs and nI inputs, output nested dictionary""" + jacdict = {} + for iO, O in enumerate(outputs): + jacdict[O] = {} + for iI, I in enumerate(inputs): + jacdict[O][I] = bigjac[(T * iO):(T * (iO + 1)), (T * iI):(T * (iI + 1))] + return JacobianDict(jacdict, outputs, inputs, T=T) + + def factored(self, T=None): + return FactoredJacobianDict(self, T) + + +class FactoredJacobianDict: + def __init__(self, jacobian_dict: JacobianDict, T=None): + if jacobian_dict.T is None: + if T is None: + raise ValueError(f'Trying to factor (solve) {jacobian_dict} but do not know T') + self.T = T + else: + self.T = jacobian_dict.T + + H_U = jacobian_dict.pack(T) + self.targets = jacobian_dict.outputs + self.unknowns = jacobian_dict.inputs + if len(self.targets) != len(self.unknowns): + raise ValueError('Trying to factor JacobianDict unequal number of inputs (unknowns)' + f' {self.unknowns} and outputs (targets) {self.targets}') + self.H_U_factored = factor(H_U) + + def __repr__(self): + return f'<{type(self).__name__} unknowns={self.unknowns}, targets={self.targets}>' + + # TODO: test this + def to_jacobian_dict(self): + return JacobianDict.unpack(-factored_solve(self.H_U_factored, np.eye(self.T*len(self.unknowns))), + self.unknowns, self.targets, self.T) + + def __matmul__(self, x): + if isinstance(x, JacobianDict): + return self.compose(x) + elif isinstance(x, Bijection): + return self.remap(x) + else: + return self.apply(x) + + def __rmatmul__(self, x): + if isinstance(x, Bijection): + return self.remap(x) + + def remap(self, x: Bijection): + if not x: + return self + newself = copy.copy(self) + newself.unknowns = x @ self.unknowns + newself.targets = x @ self.targets + return newself + + def compose(self, J: JacobianDict): + """Returns = -H_U^{-1} @ J""" + Jsub = J[[o for o in self.targets if o in J.outputs]].pack(self.T) + out = -factored_solve(self.H_U_factored, Jsub) + return JacobianDict.unpack(out, self.unknowns, J.inputs, self.T) + + def apply(self, x: Union[ImpulseDict, Dict[str, Array]]): + """Returns -H_U^{-1} @ x""" + xsub = ImpulseDict(x).get(self.targets).pack() + out = -factored_solve(self.H_U_factored, xsub) + return ImpulseDict.unpack(out, self.unknowns, self.T) + + +def ensure_valid_jacobiandict(d): + """The valid structure of `d` is a Dict[str, Dict[str, Jacobian]], where calling `d[o][i]` yields a + Jacobian of type Jacobian mapping sequences of `i` to sequences of `o`. The null type for `d` is assumed + to be {}, which is permitted the empty version of a valid nested dict.""" + + if d and not isinstance(d, JacobianDict): + # Assume it's sufficient to just check one of the keys + if not isinstance(next(iter(d.keys())), str): + raise ValueError(f"The dict argument {d} must have keys with type `str` to indicate `output` names.") + + jac_o_dict = next(iter(d.values())) + if isinstance(jac_o_dict, dict): + if jac_o_dict: + if not isinstance(next(iter(jac_o_dict.keys())), str): + raise ValueError(f"The values of the dict argument {d} must be dicts with keys of type `str` to indicate" + f" `input` names.") + jac_o_i = next(iter(jac_o_dict.values())) + if not isinstance(jac_o_i, Jacobian): + raise ValueError(f"The dict argument {d}'s values must be dicts with values of type `Jacobian`.") + else: + if isinstance(jac_o_i, np.ndarray) and np.shape(jac_o_i)[0] != np.shape(jac_o_i)[1]: + raise ValueError(f"The Jacobians in {d} must be square matrices of type `Jacobian`.") + else: + raise ValueError(f"The argument {d} must be of type `dict`, with keys of type `str` and" + f" values of type `Jacobian`.") + + +def verify_saved_jacobian(block_name, Js, outputs, inputs, T): + """Verify that pre-computed Jacobian has all the right outputs, inputs, and length.""" + if block_name not in Js.keys(): + # don't throw warning, this will happen often for simple blocks + return False + J = Js[block_name] + + if not isinstance(J, JacobianDict): + warnings.warn(f'Js[{block_name}] is not a JacobianDict.') + return False + + if not set(outputs).issubset(set(J.outputs)): + missing = set(outputs).difference(set(J.outputs)) + warnings.warn(f'Js[{block_name}] misses required outputs {missing}.') + return False + + if not set(inputs).issubset(set(J.inputs)): + missing = set(inputs).difference(set(J.inputs)) + warnings.warn(f'Js[{block_name}] misses required inputs {missing}.') + return False + + # Jacobian of simple blocks may have a sparse representation + if T is not None: + Tsaved = J[J.outputs[0]][J.inputs[0]].shape[-1] + if T != Tsaved: + warnings.warn(f'Js[{block_name} has length {Tsaved}, but you asked for {T}') + return False + + return True diff --git a/src/sequence_jacobian/classes/result_dict.py b/src/sequence_jacobian/classes/result_dict.py new file mode 100644 index 0000000..5732182 --- /dev/null +++ b/src/sequence_jacobian/classes/result_dict.py @@ -0,0 +1,78 @@ +import copy + +from ..utilities.bijection import Bijection + +class ResultDict: + def __init__(self, data, internals=None): + if isinstance(data, ResultDict): + if internals is not None: + raise ValueError(f'Supplying {type(self).__name__} and also internals to constructor not allowed') + self.toplevel = data.toplevel.copy() + self.internals = data.internals.copy() + else: + self.toplevel: dict = data.copy() + self.internals: dict = {} if internals is None else internals.copy() + + def __repr__(self): + if self.internals: + return f"<{type(self).__name__}: {list(self.toplevel.keys())}, internals={list(self.internals.keys())}>" + else: + return f"<{type(self).__name__}: {list(self.toplevel.keys())}>" + + def __iter__(self): + return iter(self.toplevel) + + def __getitem__(self, k, **kwargs): + if isinstance(k, str): + return self.toplevel[k] + elif isinstance(k, tuple): + raise TypeError(f'Key {k} to {type(self).__name__} cannot be tuple') + else: + try: + return type(self)({ki: self.toplevel[ki] for ki in k}, **kwargs) + except TypeError: + raise TypeError(f'Key {k} to {type(self).__name__} needs to be a string or an iterable (list, set, etc) of strings') + + def __setitem__(self, k, v): + self.toplevel[k] = v + + def __matmul__(self, x): + # remap keys in toplevel + if isinstance(x, Bijection): + new = copy.deepcopy(self) + new.toplevel = x @ self.toplevel + return new + else: + return NotImplemented + + def __rmatmul__(self, x): + return self.__matmul__(x) + + def __len__(self): + return len(self.toplevel) + + def __or__(self, other): + if not isinstance(other, type(self)): + raise ValueError(f'Trying to merge a {type(self).__name__} with a {type(other).__name__}.') + merged = self.copy() + merged.update(other) + return merged + + def keys(self): + return self.toplevel.keys() + + def values(self): + return self.toplevel.values() + + def items(self): + return self.toplevel.items() + + def update(self, rdict): + if isinstance(rdict, ResultDict): + self.toplevel.update(rdict.toplevel) + self.internals.update(rdict.internals) + else: + self.toplevel.update(dict(rdict)) + + def copy(self): + return type(self)(self) diff --git a/src/sequence_jacobian/classes/sparse_jacobians.py b/src/sequence_jacobian/classes/sparse_jacobians.py new file mode 100644 index 0000000..b995a82 --- /dev/null +++ b/src/sequence_jacobian/classes/sparse_jacobians.py @@ -0,0 +1,288 @@ +import numpy as np +from numba import njit +import copy + +class IdentityMatrix: + """Simple identity matrix class, cheaper than using actual np.eye(T) matrix, + use to initialize Jacobian of a variable wrt itself""" + __array_priority__ = 10_000 + + def sparse(self): + """Equivalent SimpleSparse representation, less efficient operations but more general.""" + return SimpleSparse({(0, 0): 1}) + + def matrix(self, T): + return np.eye(T) + + def __matmul__(self, other): + """Identity matrix knows to simply return 'other' whenever it's multiplied by 'other'.""" + return copy.deepcopy(other) + + def __rmatmul__(self, other): + return copy.deepcopy(other) + + def __mul__(self, a): + return a*self.sparse() + + def __rmul__(self, a): + return self.sparse()*a + + def __add__(self, x): + return self.sparse() + x + + def __radd__(self, x): + return x + self.sparse() + + def __sub__(self, x): + return self.sparse() - x + + def __rsub__(self, x): + return x - self.sparse() + + def __neg__(self): + return -self.sparse() + + def __pos__(self): + return self + + def __repr__(self): + return 'IdentityMatrix' + + +class SimpleSparse: + """Efficient representation of sparse linear operators, which are linear combinations of basis + operators represented by pairs (i, m), where i is the index of diagonal on which there are 1s + (measured by # above main diagonal) and m is number of initial entries missing. + + Examples of such basis operators: + - (0, 0) is identity operator + - (0, 2) is identity operator with first two '1's on main diagonal missing + - (1, 0) has 1s on diagonal above main diagonal: "left-shift" operator + - (-1, 1) has 1s on diagonal below main diagonal, except first column + + The linear combination of these basis operators that makes up a given SimpleSparse object is + stored as a dict 'elements' mapping (i, m) -> x. + + The Jacobian of a SimpleBlock is a SimpleSparse operator combining basis elements (i, 0). We need + the more general basis (i, m) to ensure closure under multiplication. + + These (i, m) correspond to the Q_(-i, m) operators defined for Proposition 2 of the Sequence Space + Jacobian paper. The flipped sign in the code is so that the index 'i' matches the k(i) notation + for writing SimpleBlock functions. + + The "dunder" methods x.__add__(y), x.__matmul__(y), x.__rsub__(y), etc. in Python implement infix + operations x + y, x @ y, y - x, etc. Defining these allows us to use these more-or-less + interchangeably with ordinary NumPy matrices. + """ + + # when performing binary operations on SimpleSparse and a NumPy array, use SimpleSparse's rules + __array_priority__ = 1000 + + def __init__(self, elements): + self.elements = elements + self.indices, self.xs = None, None + + @staticmethod + def from_simple_diagonals(elements): + """Take dict i -> x, i.e. from SimpleBlock differentiation, convert to SimpleSparse (i, 0) -> x""" + return SimpleSparse({(i, 0): x for i, x in elements.items()}) + + def matrix(self, T): + """Return matrix giving first T rows and T columns of matrix representation of SimpleSparse""" + return self + np.zeros((T, T)) + + def array(self): + """Rewrite dict (i, m) -> x as pair of NumPy arrays, one size-N*2 array of ints with rows (i, m) + and one size-N array of floats with entries x. + + This is needed for Numba to take as input. Cache for efficiency. + """ + if self.indices is not None: + return self.indices, self.xs + else: + indices, xs = zip(*self.elements.items()) + self.indices, self.xs = np.array(indices), np.array(xs) + return self.indices, self.xs + + @property + def T(self): + """Transpose""" + return SimpleSparse({(-i, m): x for (i, m), x in self.elements.items()}) + + @property + def iszero(self): + return not self.nonzero().elements + + def nonzero(self): + elements = self.elements.copy() + for im, x in self.elements.items(): + # safeguard to retain sparsity: disregard extremely small elements (num error) + if abs(elements[im]) < 1E-14: + del elements[im] + return SimpleSparse(elements) + + def __pos__(self): + return self + + def __neg__(self): + return SimpleSparse({im: -x for im, x in self.elements.items()}) + + def __matmul__(self, A): + if isinstance(A, SimpleSparse): + # multiply SimpleSparse by SimpleSparse, simple analytical rules in multiply_rs_rs + return SimpleSparse(multiply_rs_rs(self, A)) + elif isinstance(A, np.ndarray): + # multiply SimpleSparse by matrix or vector, multiply_rs_matrix uses slicing + indices, xs = self.array() + if A.ndim == 2: + return multiply_rs_matrix(indices, xs, A) + elif A.ndim == 1: + return multiply_rs_matrix(indices, xs, A[:, np.newaxis])[:, 0] + else: + return NotImplemented + else: + return NotImplemented + + def __rmatmul__(self, A): + # multiplication rule when this object is on right (will only be called when left is matrix) + # for simplicity, just use transpose to reduce this to previous cases + return (self.T @ A.T).T + + def __add__(self, A): + if isinstance(A, SimpleSparse): + # add SimpleSparse to SimpleSparse, combining dicts, summing x when (i, m) overlap + elements = self.elements.copy() + for im, x in A.elements.items(): + if im in elements: + elements[im] += x + # safeguard to retain sparsity: disregard extremely small elements (num error) + if abs(elements[im]) < 1E-14: + del elements[im] + else: + elements[im] = x + return SimpleSparse(elements) + else: + # add SimpleSparse to T*T matrix + if not isinstance(A, np.ndarray) or A.ndim != 2 or A.shape[0] != A.shape[1]: + return NotImplemented + T = A.shape[0] + + # fancy trick to do this efficiently by writing A as flat vector + # then (i, m) can be mapped directly to NumPy slicing! + A = A.flatten() # use flatten, not ravel, since we'll modify A and want a copy + for (i, m), x in self.elements.items(): + if i < 0: + A[T * (-i) + (T + 1) * m::T + 1] += x + else: + A[i + (T + 1) * m:(T - i) * T:T + 1] += x + return A.reshape((T, T)) + + def __radd__(self, A): + try: + return self + A + except: + print(self) + print(A) + raise + + def __sub__(self, A): + # slightly inefficient implementation with temporary for simplicity + return self + (-A) + + def __rsub__(self, A): + return -self + A + + def __mul__(self, a): + if not np.isscalar(a): + return NotImplemented + return SimpleSparse({im: a * x for im, x in self.elements.items()}) + + def __rmul__(self, a): + return self * a + + def __repr__(self): + formatted = '{' + ', '.join(f'({i}, {m}): {x:.3f}' for (i, m), x in self.elements.items()) + '}' + return f'SimpleSparse({formatted})' + + def __eq__(self, s): + return self.elements == s.elements + + +def multiply_basis(t1, t2): + """Matrix multiplication operation mapping two sparse basis elements to another.""" + # equivalent to formula in Proposition 2 of Sequence Space Jacobian paper, but with + # signs of i and j flipped to reflect different sign convention used here + i, m = t1 + j, n = t2 + k = i + j + if i >= 0: + if j >= 0: + l = max(m, n - i) + elif k >= 0: + l = max(m, n - k) + else: + l = max(m + k, n) + else: + if j <= 0: + l = max(m + j, n) + else: + l = max(m, n) + min(-i, j) + return k, l + + +def multiply_rs_rs(s1, s2): + """Matrix multiplication operation on two SimpleSparse objects.""" + # iterate over all pairs (i, m) -> x and (j, n) -> y in objects, + # add all pairwise products to get overall product + elements = {} + for im, x in s1.elements.items(): + for jn, y in s2.elements.items(): + kl = multiply_basis(im, jn) + if kl in elements: + elements[kl] += x * y + else: + elements[kl] = x * y + return elements + + +@njit +def multiply_rs_matrix(indices, xs, A): + """Matrix multiplication of SimpleSparse object ('indices' and 'xs') and matrix A. + Much more computationally demanding than multiplying two SimpleSparse (which is almost + free with simple analytical formula), so we implement as jitted function.""" + n = indices.shape[0] + T = A.shape[0] + S = A.shape[1] + Aout = np.zeros((T, S)) + + for count in range(n): + # for Numba to jit easily, SimpleSparse with basis elements '(i, m)' with coefs 'x' + # was stored in 'indices' and 'xs' + i = indices[count, 0] + m = indices[count, 1] + x = xs[count] + + # loop faster than vectorized when jitted + # directly use def of basis element (i, m), displacement of i and ignore first m + if i == 0: + for t in range(m, T): + for s in range(S): + Aout[t, s] += x * A[t, s] + elif i > 0: + for t in range(m, T - i): + for s in range(S): + Aout[t, s] += x * A[t + i, s] + else: + for t in range(m - i, T): + for s in range(S): + Aout[t, s] += x * A[t + i, s] + return Aout + + +def make_matrix(A, T): + """If A is not an outright ndarray, e.g. it is SimpleSparse, call its .matrix(T) method + to convert it to T*T array.""" + if not isinstance(A, np.ndarray): + return A.matrix(T) + else: + return A diff --git a/src/sequence_jacobian/classes/steady_state_dict.py b/src/sequence_jacobian/classes/steady_state_dict.py new file mode 100644 index 0000000..343bcaf --- /dev/null +++ b/src/sequence_jacobian/classes/steady_state_dict.py @@ -0,0 +1,21 @@ +from copy import deepcopy + +from .result_dict import ResultDict +from ..utilities.misc import dict_diff +from ..utilities.ordered_set import OrderedSet +from ..utilities.bijection import Bijection + +import numpy as np + +from numbers import Real +from typing import Any, Dict, Union +Array = Any + +class SteadyStateDict(ResultDict): + def difference(self, data_to_remove): + return SteadyStateDict(dict_diff(self.toplevel, data_to_remove), deepcopy(self.internals)) + + def _vector_valued(self): + return OrderedSet([k for k, v in self.toplevel.items() if np.size(v) > 1]) + +UserProvidedSS = Dict[str, Union[Real, Array]] diff --git a/sequence_jacobian/estimation.py b/src/sequence_jacobian/estimation.py similarity index 98% rename from sequence_jacobian/estimation.py rename to src/sequence_jacobian/estimation.py index a633640..8becc42 100644 --- a/sequence_jacobian/estimation.py +++ b/src/sequence_jacobian/estimation.py @@ -14,7 +14,7 @@ def all_covariances(M, sigmas): Parameters ---------- - M : array (T*O*Z), stacked impulse responses of nO variables to nZ shocks (MA(T-1) representation) + M : array (T*O*Z), stacked impulse responses of nO variables to nZ shocks (MA(T-1) representation) sigmas : array (Z), standard deviations of shocks Returns diff --git a/src/sequence_jacobian/examples/__init__.py b/src/sequence_jacobian/examples/__init__.py new file mode 100644 index 0000000..50e3a0c --- /dev/null +++ b/src/sequence_jacobian/examples/__init__.py @@ -0,0 +1 @@ +"""Example models""" \ No newline at end of file diff --git a/src/sequence_jacobian/examples/hank.py b/src/sequence_jacobian/examples/hank.py new file mode 100644 index 0000000..da444da --- /dev/null +++ b/src/sequence_jacobian/examples/hank.py @@ -0,0 +1,107 @@ +import numpy as np + +from .. import utilities as utils +from ..blocks.simple_block import simple +from ..blocks.combined_block import create_model +from .hetblocks import household_labor as hh + + +'''Part 1: Blocks''' + + +@simple +def firm(Y, w, Z, pi, mu, kappa): + L = Y / Z + Div = Y - w * L - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y + return L, Div + + +@simple +def monetary(pi, rstar, phi): + r = (1 + rstar(-1) + phi * pi(-1)) / (1 + pi) - 1 + return r + + +@simple +def nkpc(pi, w, Z, Y, r, mu, kappa): + nkpc_res = kappa * (w / Z - 1 / mu) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) / (1 + r(+1))\ + - (1 + pi).apply(np.log) + return nkpc_res + + +@simple +def fiscal(r, B): + Tax = r * B + return Tax + + +@simple +def mkt_clearing(A, NE, C, L, Y, B, pi, mu, kappa): + asset_mkt = A - B + labor_mkt = NE - L + goods_mkt = Y - C - mu/(mu-1)/(2*kappa) * (1+pi).apply(np.log)**2 * Y + return asset_mkt, labor_mkt, goods_mkt + + +@simple +def nkpc_ss(Z, mu): + '''Solve (w) to hit targets for (nkpc_res)''' + w = Z / mu + return w + + +'''Part 2: Embed HA block''' + + +def make_grids(rho_s, sigma_s, nS, amax, nA): + e_grid, pi_e, Pi = utils.discretize.markov_rouwenhorst(rho=rho_s, sigma=sigma_s, N=nS) + a_grid = utils.discretize.agrid(amax=amax, n=nA) + return e_grid, pi_e, Pi, a_grid + + +def transfers(pi_e, Div, Tax, e_grid): + # hardwired incidence rules are proportional to skill; scale does not matter + tax_rule, div_rule = e_grid, e_grid + div = Div / np.sum(pi_e * div_rule) * div_rule + tax = Tax / np.sum(pi_e * tax_rule) * tax_rule + T = div - tax + return T + + +def wages(w, e_grid): + we = w * e_grid + return we + + +def labor_supply(n, e_grid): + ne = e_grid[:, np.newaxis] * n + return ne + + +'''Part 3: DAG''' + +def dag(): + # Combine blocks + household = hh.household.add_hetinputs([transfers, wages, make_grids]) + household = household.add_hetoutputs([labor_supply]) + blocks = [household, firm, monetary, fiscal, mkt_clearing, nkpc] + blocks_ss = [household, firm, monetary, fiscal, mkt_clearing, nkpc_ss] + hank_model = create_model(blocks, name="One-Asset HANK") + hank_model_ss = create_model(blocks_ss, name="One-Asset HANK") + + # Steady state + calibration = {'r': 0.005, 'rstar': 0.005, 'eis': 0.5, 'frisch': 0.5, 'B': 5.6, + 'mu': 1.2, 'rho_s': 0.966, 'sigma_s': 0.5, 'kappa': 0.1, 'phi': 1.5, + 'Y': 1., 'Z': 1., 'pi': 0., 'nS': 2, 'amax': 150, 'nA': 10} + unknowns_ss = {'beta': 0.986, 'vphi': 0.8} + targets_ss = {'asset_mkt': 0., 'NE': 1.} + cali = hank_model_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, + solver='broyden_custom') + ss = hank_model.steady_state(cali) + + # Transitional dynamics + unknowns = ['w', 'Y', 'pi'] + targets = ['asset_mkt', 'goods_mkt', 'nkpc_res'] + exogenous = ['rstar', 'Z'] + + return hank_model_ss, ss, hank_model, unknowns, targets, exogenous diff --git a/src/sequence_jacobian/examples/hetblocks/__init__.py b/src/sequence_jacobian/examples/hetblocks/__init__.py new file mode 100644 index 0000000..0fcb820 --- /dev/null +++ b/src/sequence_jacobian/examples/hetblocks/__init__.py @@ -0,0 +1 @@ +'''Heterogeneous agent blocks''' \ No newline at end of file diff --git a/src/sequence_jacobian/examples/hetblocks/household_labor.py b/src/sequence_jacobian/examples/hetblocks/household_labor.py new file mode 100644 index 0000000..571828f --- /dev/null +++ b/src/sequence_jacobian/examples/hetblocks/household_labor.py @@ -0,0 +1,85 @@ +'''Standard Incomplete Market model with Endogenous Labor Supply''' + +import numpy as np +from numba import vectorize, njit + +from ...blocks.het_block import het +from ... import utilities as utils + + +def household_init(a_grid, we, r, eis, T): + fininc = (1 + r) * a_grid + T[:, np.newaxis] - a_grid[0] + coh = (1 + r) * a_grid[np.newaxis, :] + we[:, np.newaxis] + T[:, np.newaxis] + Va = (1 + r) * (0.1 * coh) ** (-1 / eis) + return fininc, Va + + +@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init) +def household(Va_p, a_grid, we, T, r, beta, eis, frisch, vphi): + '''Single backward step via EGM.''' + uc_nextgrid = beta * Va_p + c_nextgrid, n_nextgrid = cn(uc_nextgrid, we[:, np.newaxis], eis, frisch, vphi) + + lhs = c_nextgrid - we[:, np.newaxis] * n_nextgrid + a_grid[np.newaxis, :] - T[:, np.newaxis] + rhs = (1 + r) * a_grid + c = utils.interpolate.interpolate_y(lhs, rhs, c_nextgrid) + n = utils.interpolate.interpolate_y(lhs, rhs, n_nextgrid) + + a = rhs + we[:, np.newaxis] * n + T[:, np.newaxis] - c + iconst = np.nonzero(a < a_grid[0]) + a[iconst] = a_grid[0] + + if iconst[0].size != 0 and iconst[1].size != 0: + c[iconst], n[iconst] = solve_cn(we[iconst[0]], + rhs[iconst[1]] + T[iconst[0]] - a_grid[0], + eis, frisch, vphi, Va_p[iconst]) + + Va = (1 + r) * c ** (-1 / eis) + + return Va, a, c, n + + +'''Supporting functions for HA block''' + +@njit +def cn(uc, w, eis, frisch, vphi): + """Return optimal c, n as function of u'(c) given parameters""" + return uc ** (-eis), (w * uc / vphi) ** frisch + + +def solve_cn(w, T, eis, frisch, vphi, uc_seed): + uc = solve_uc(w, T, eis, frisch, vphi, uc_seed) + return cn(uc, w, eis, frisch, vphi) + + +@vectorize +def solve_uc(w, T, eis, frisch, vphi, uc_seed): + """Solve for optimal uc given in log uc space. + + max_{c, n} c**(1-1/eis) + vphi*n**(1+1/frisch) s.t. c = w*n + T + """ + log_uc = np.log(uc_seed) + for i in range(30): + ne, ne_p = netexp(log_uc, w, T, eis, frisch, vphi) + if abs(ne) < 1E-11: + break + else: + log_uc -= ne / ne_p + else: + raise ValueError("Cannot solve constrained household's problem: No convergence after 30 iterations!") + + return np.exp(log_uc) + + +@njit +def netexp(log_uc, w, T, eis, frisch, vphi): + """Return net expenditure as a function of log uc and its derivative.""" + c, n = cn(np.exp(log_uc), w, eis, frisch, vphi) + ne = c - w * n - T + + # c and n have elasticities of -eis and frisch wrt log u'(c) + c_loguc = -eis * c + n_loguc = frisch * n + netexp_loguc = c_loguc - w * n_loguc + + return ne, netexp_loguc diff --git a/src/sequence_jacobian/examples/hetblocks/household_sim.py b/src/sequence_jacobian/examples/hetblocks/household_sim.py new file mode 100644 index 0000000..5529340 --- /dev/null +++ b/src/sequence_jacobian/examples/hetblocks/household_sim.py @@ -0,0 +1,25 @@ +'''Standard Incomplete Market model''' + +import numpy as np + +from ...blocks.het_block import het +from ... import utilities as utils + + +def household_init(a_grid, y, r, eis): + coh = (1 + r) * a_grid[np.newaxis, :] + y[:, np.newaxis] + Va = (1 + r) * (0.1 * coh) ** (-1 / eis) + return Va + + +@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init) +def household(Va_p, a_grid, y, r, beta, eis): + uc_nextgrid = beta * Va_p + c_nextgrid = uc_nextgrid ** (-eis) + coh = (1 + r) * a_grid[np.newaxis, :] + y[:, np.newaxis] + a = utils.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid) + utils.optimized_routines.setmin(a, a_grid[0]) + c = coh - a + Va = (1 + r) * c ** (-1 / eis) + return Va, a, c + \ No newline at end of file diff --git a/src/sequence_jacobian/examples/hetblocks/household_twoasset.py b/src/sequence_jacobian/examples/hetblocks/household_twoasset.py new file mode 100644 index 0000000..220b56b --- /dev/null +++ b/src/sequence_jacobian/examples/hetblocks/household_twoasset.py @@ -0,0 +1,181 @@ +import numpy as np +from numba import guvectorize + +from ...blocks.het_block import het +from ...blocks.support.simple_displacement import apply_function +from ... import utilities as utils + + +def household_init(b_grid, a_grid, z_grid, eis): + Va = (0.6 + 1.1 * b_grid[:, np.newaxis] + a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) + Vb = (0.5 + b_grid[:, np.newaxis] + 1.2 * a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) + return Va, Vb + + +def adjustment_costs(a, a_grid, ra, chi0, chi1, chi2): + chi, _, _ = apply_function(get_Psi_and_deriv, a, a_grid, ra, chi0, chi1, chi2) + return chi + + +# policy and bacward order as in grid! +@het(exogenous='Pi', policy=['b', 'a'], backward=['Vb', 'Va'], + hetoutputs=[adjustment_costs], backward_init=household_init) +def household(Va_p, Vb_p, a_grid, b_grid, z_grid, e_grid, k_grid, beta, eis, rb, ra, chi0, chi1, chi2): + # TODO: make into hetinput + # precompute Psi1(a', a) on grid of (a', a) for steps 3 and 5 + Psi1 = get_Psi_and_deriv(a_grid[:, np.newaxis], + a_grid[np.newaxis, :], ra, chi0, chi1, chi2)[1] + + # === STEP 2: Wb(z, b', a') and Wa(z, b', a') === + # (take discounted expectation of tomorrow's value function) + Wb = beta * Vb_p + Wa = beta * Va_p + W_ratio = Wa / Wb + + # === STEP 3: a'(z, b', a) for UNCONSTRAINED === + + # for each (z, b', a), linearly interpolate to find a' between gridpoints + # satisfying optimality condition W_ratio == 1+Psi1 + i, pi = lhs_equals_rhs_interpolate(W_ratio, 1 + Psi1) + + # use same interpolation to get Wb and then c + a_endo_unc = utils.interpolate.apply_coord(i, pi, a_grid) + c_endo_unc = utils.interpolate.apply_coord(i, pi, Wb) ** (-eis) + + # === STEP 4: b'(z, b, a), a'(z, b, a) for UNCONSTRAINED === + + # solve out budget constraint to get b(z, b', a) + b_endo = (c_endo_unc + a_endo_unc + addouter(-z_grid, b_grid, -(1 + ra) * a_grid) + + get_Psi_and_deriv(a_endo_unc, a_grid, ra, chi0, chi1, chi2)[0]) / (1 + rb) + + # interpolate this b' -> b mapping to get b -> b', so we have b'(z, b, a) + # and also use interpolation to get a'(z, b, a) + # (note utils.interpolate.interpolate_coord and utils.interpolate.apply_coord work on last axis, + # so we need to swap 'b' to the last axis, then back when done) + i, pi = utils.interpolate.interpolate_coord(b_endo.swapaxes(1, 2), b_grid) + a_unc = utils.interpolate.apply_coord(i, pi, a_endo_unc.swapaxes(1, 2)).swapaxes(1, 2) + b_unc = utils.interpolate.apply_coord(i, pi, b_grid).swapaxes(1, 2) + + # === STEP 5: a'(z, kappa, a) for CONSTRAINED === + + # for each (z, kappa, a), linearly interpolate to find a' between gridpoints + # satisfying optimality condition W_ratio/(1+kappa) == 1+Psi1, assuming b'=0 + lhs_con = W_ratio[:, 0:1, :] / (1 + k_grid[np.newaxis, :, np.newaxis]) + i, pi = lhs_equals_rhs_interpolate(lhs_con, 1 + Psi1) + + # use same interpolation to get Wb and then c + a_endo_con = utils.interpolate.apply_coord(i, pi, a_grid) + c_endo_con = ((1 + k_grid[np.newaxis, :, np.newaxis]) ** (-eis) + * utils.interpolate.apply_coord(i, pi, Wb[:, 0:1, :]) ** (-eis)) + + # === STEP 6: a'(z, b, a) for CONSTRAINED === + + # solve out budget constraint to get b(z, kappa, a), enforcing b'=0 + b_endo = (c_endo_con + a_endo_con + + addouter(-z_grid, np.full(len(k_grid), b_grid[0]), -(1 + ra) * a_grid) + + get_Psi_and_deriv(a_endo_con, a_grid, ra, chi0, chi1, chi2)[0]) / (1 + rb) + + # interpolate this kappa -> b mapping to get b -> kappa + # then use the interpolated kappa to get a', so we have a'(z, b, a) + # (utils.interpolate.interpolate_y does this in one swoop, but since it works on last + # axis, we need to swap kappa to last axis, and then b back to middle when done) + a_con = utils.interpolate.interpolate_y(b_endo.swapaxes(1, 2), b_grid, + a_endo_con.swapaxes(1, 2)).swapaxes(1, 2) + + # === STEP 7: obtain policy functions and update derivatives of value function === + + # combine unconstrained solution and constrained solution, choosing latter + # when unconstrained goes below minimum b + a, b = a_unc.copy(), b_unc.copy() + b[b <= b_grid[0]] = b_grid[0] + a[b <= b_grid[0]] = a_con[b <= b_grid[0]] + + # calculate adjustment cost and its derivative + Psi, _, Psi2 = get_Psi_and_deriv(a, a_grid, ra, chi0, chi1, chi2) + + # solve out budget constraint to get consumption and marginal utility + c = addouter(z_grid, (1 + rb) * b_grid, (1 + ra) * a_grid) - Psi - a - b + uc = c ** (-1 / eis) + uce = e_grid[:, np.newaxis, np.newaxis] * uc + + # update derivatives of value function using envelope conditions + Va = (1 + ra - Psi2) * uc + Vb = (1 + rb) * uc + + return Va, Vb, a, b, c, uce + + +'''Supporting functions for HA block''' + +def get_Psi_and_deriv(ap, a, ra, chi0, chi1, chi2): + """Adjustment cost Psi(ap, a) and its derivatives with respect to + first argument (ap) and second argument (a)""" + a_with_return = (1 + ra) * a + a_change = ap - a_with_return + abs_a_change = np.abs(a_change) + sign_change = np.sign(a_change) + + adj_denominator = a_with_return + chi0 + core_factor = (abs_a_change / adj_denominator) ** (chi2 - 1) + + Psi = chi1 / chi2 * abs_a_change * core_factor + Psi1 = chi1 * sign_change * core_factor + Psi2 = -(1 + ra) * (Psi1 + (chi2 - 1) * Psi / adj_denominator) + return Psi, Psi1, Psi2 + + +def matrix_times_first_dim(A, X): + """Take matrix A times vector X[:, i1, i2, i3, ... , in] separately + for each i1, i2, i3, ..., in. Same output as A @ X if X is 1D or 2D""" + # flatten all dimensions of X except first, then multiply, then restore shape + return (A @ X.reshape(X.shape[0], -1)).reshape(X.shape) + + +def addouter(z, b, a): + """Take outer sum of three arguments: result[i, j, k] = z[i] + b[j] + a[k]""" + return z[:, np.newaxis, np.newaxis] + b[:, np.newaxis] + a + + +@guvectorize(['void(float64[:], float64[:,:], uint32[:], float64[:])'], '(ni),(ni,nj)->(nj),(nj)') +def lhs_equals_rhs_interpolate(lhs, rhs, iout, piout): + """ + Given lhs (i) and rhs (i,j), for each j, find the i such that + + lhs[i] > rhs[i,j] and lhs[i+1] < rhs[i+1,j] + + i.e. where given j, lhs == rhs in between i and i+1. + + Also return the pi such that + + pi*(lhs[i] - rhs[i,j]) + (1-pi)*(lhs[i+1] - rhs[i+1,j]) == 0 + + i.e. such that the point at pi*i + (1-pi)*(i+1) satisfies lhs == rhs by linear interpolation. + + If lhs[0] < rhs[0,j] already, just return u=0 and pi=1. + + ***IMPORTANT: Assumes that solution i is monotonically increasing in j + and that lhs - rhs is monotonically decreasing in i.*** + """ + + ni, nj = rhs.shape + assert len(lhs) == ni + + i = 0 + for j in range(nj): + while True: + if lhs[i] < rhs[i, j]: + break + elif i < nj - 1: + i += 1 + else: + break + + if i == 0: + iout[j] = 0 + piout[j] = 1 + else: + iout[j] = i - 1 + err_upper = rhs[i, j] - lhs[i] + err_lower = rhs[i - 1, j] - lhs[i - 1] + piout[j] = err_upper / (err_upper - err_lower) + \ No newline at end of file diff --git a/src/sequence_jacobian/examples/krusell_smith.py b/src/sequence_jacobian/examples/krusell_smith.py new file mode 100644 index 0000000..ff06a4e --- /dev/null +++ b/src/sequence_jacobian/examples/krusell_smith.py @@ -0,0 +1,102 @@ +from .. import utilities as utils +from ..blocks.simple_block import simple +from ..blocks.combined_block import create_model +from .hetblocks import household_sim as hh + + +'''Part 1: Blocks''' + +@simple +def firm(K, L, Z, alpha, delta): + r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta + w = (1 - alpha) * Z * (K(-1) / L) ** alpha + Y = Z * K(-1) ** alpha * L ** (1 - alpha) + return r, w, Y + + +@simple +def mkt_clearing(K, A, Y, C, delta): + asset_mkt = A - K + goods_mkt = Y - C - delta * K + return asset_mkt, goods_mkt + + +@simple +def firm_ss(r, Y, L, delta, alpha): + '''Solve for (Z, K) given targets for (Y, r).''' + rk = r + delta + K = alpha * Y / rk + Z = Y / K ** alpha / L ** (1 - alpha) + w = (1 - alpha) * Z * (K / L) ** alpha + return K, Z, w + + +'''Part 2: Embed HA block''' + +def make_grids(rho, sigma, nS, amax, nA): + e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho, sigma=sigma, N=nS) + a_grid = utils.discretize.agrid(amax=amax, n=nA) + return e_grid, Pi, a_grid + + +def income(w, e_grid): + y = w * e_grid + return y + + +'''Part 3: DAG''' + +def dag(): + # Combine blocks + household = hh.household.add_hetinputs([income, make_grids]) + ks_model = create_model([household, firm, mkt_clearing], name="Krusell-Smith") + ks_model_ss = create_model([household, firm_ss, mkt_clearing], name="Krusell-Smith SS") + + # Steady state + calibration = {'eis': 1.0, 'delta': 0.025, 'alpha': 0.11, 'rho': 0.966, 'sigma': 0.5, + 'Y': 1.0, 'L': 1.0, 'nS': 2, 'nA': 10, 'amax': 200, 'r': 0.01} + unknowns_ss = {'beta': (0.98 / 1.01, 0.999 / 1.01)} + targets_ss = {'asset_mkt': 0.} + ss = ks_model_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, solver='brentq') + + # Transitional dynamics + inputs = ['Z'] + unknowns = ['K'] + targets = ['asset_mkt'] + + return ks_model_ss, ss, ks_model, unknowns, targets, inputs + + +'''Part 3: Permanent beta heterogeneity''' + +@simple +def aggregate(A_patient, A_impatient, C_patient, C_impatient, mass_patient): + C = mass_patient * C_patient + (1 - mass_patient) * C_impatient + A = mass_patient * A_patient + (1 - mass_patient) * A_impatient + return C, A + + +def remapped_dag(): + # Create 2 versions of the household block using `remap` + household = hh.household.add_hetinputs([income, make_grids]) + to_map = ['beta', *household.outputs] + hh_patient = household.remap({k: k + '_patient' for k in to_map}).rename('hh_patient') + hh_impatient = household.remap({k: k + '_impatient' for k in to_map}).rename('hh_impatient') + blocks = [hh_patient, hh_impatient, firm, mkt_clearing, aggregate] + blocks_ss = [hh_patient, hh_impatient, firm_ss, mkt_clearing, aggregate] + ks_remapped = create_model(blocks, name='KS-beta-het') + ks_remapped_ss = create_model(blocks_ss, name='KS-beta-het') + + # Steady State + calibration = {'eis': 1., 'delta': 0.025, 'alpha': 0.3, 'rho': 0.966, 'sigma': 0.5, 'Y': 1.0, 'L': 1.0, + 'nS': 3, 'nA': 100, 'amax': 1000, 'beta_impatient': 0.985, 'mass_patient': 0.5} + unknowns_ss = {'beta_patient': (0.98 / 1.01, 0.999 / 1.01)} + targets_ss = {'asset_mkt': 0.} + ss = ks_remapped_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, solver='brentq') + + # Transitional Dynamics/Jacobian Calculation + unknowns = ['K'] + targets = ['asset_mkt'] + exogenous = ['Z'] + + return ks_remapped_ss, ss, ks_remapped, unknowns, targets, ss, exogenous diff --git a/src/sequence_jacobian/examples/rbc.py b/src/sequence_jacobian/examples/rbc.py new file mode 100644 index 0000000..934bd5f --- /dev/null +++ b/src/sequence_jacobian/examples/rbc.py @@ -0,0 +1,48 @@ +from ..blocks.simple_block import simple +from ..blocks.combined_block import create_model + + +'''Part 1: Blocks''' + +@simple +def firm(K, L, Z, alpha, delta): + r = alpha * Z * (K(-1) / L) ** (alpha-1) - delta + w = (1 - alpha) * Z * (K(-1) / L) ** alpha + Y = Z * K(-1) ** alpha * L ** (1 - alpha) + return r, w, Y + + +@simple +def household(K, L, w, eis, frisch, vphi, delta): + C = (w / vphi / L ** (1 / frisch)) ** eis + I = K - (1 - delta) * K(-1) + return C, I + + +@simple +def mkt_clearing(r, C, Y, I, K, L, w, eis, beta): + goods_mkt = Y - C - I + euler = C ** (-1 / eis) - beta * (1 + r(+1)) * C(+1) ** (-1 / eis) + walras = C + K - (1 + r) * K(-1) - w * L + return goods_mkt, euler, walras + + +'''Part 2: Assembling the model''' + +def dag(): + # Combine blocks + blocks = [household, firm, mkt_clearing] + rbc_model = create_model(blocks, name="RBC") + + # Steady state + calibration = {'eis': 1., 'frisch': 1., 'delta': 0.025, 'alpha': 0.11, 'L': 1.} + unknowns_ss = {'vphi': 0.92, 'beta': 1 / (1 + 0.01), 'K': 2., 'Z': 1.} + targets_ss = {'goods_mkt': 0., 'r': 0.01, 'euler': 0., 'Y': 1.} + ss = rbc_model.solve_steady_state(calibration, unknowns_ss, targets_ss, solver='hybr') + + # Transitional dynamics + unknowns = ['K', 'L'] + targets = ['goods_mkt', 'euler'] + exogenous = ['Z'] + + return rbc_model, ss, unknowns, targets, exogenous diff --git a/src/sequence_jacobian/examples/two_asset.py b/src/sequence_jacobian/examples/two_asset.py new file mode 100644 index 0000000..a3de24b --- /dev/null +++ b/src/sequence_jacobian/examples/two_asset.py @@ -0,0 +1,187 @@ +import numpy as np + +from .. import utilities as utils +from ..blocks.simple_block import simple +from ..blocks.solved_block import solved +from ..blocks.combined_block import create_model, combine +from .hetblocks import household_twoasset as hh + + +'''Part 1: Blocks''' + +@simple +def pricing(pi, mc, r, Y, kappap, mup): + nkpc = kappap * (mc - 1 / mup) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) \ + / (1 + r(+1)) - (1 + pi).apply(np.log) + return nkpc + + +@simple +def arbitrage(div, p, r): + equity = div(+1) + p(+1) - p * (1 + r(+1)) + return equity + + +@simple +def labor(Y, w, K, Z, alpha): + N = (Y / Z / K(-1) ** alpha) ** (1 / (1 - alpha)) + mc = w * N / (1 - alpha) / Y + return N, mc + + +@simple +def investment(Q, K, r, N, mc, Z, delta, epsI, alpha): + inv = (K / K(-1) - 1) / (delta * epsI) + 1 - Q + val = alpha * Z(+1) * (N(+1) / K) ** (1 - alpha) * mc(+1) -\ + (K(+1) / K - (1 - delta) + (K(+1) / K - 1) ** 2 / (2 * delta * epsI)) +\ + K(+1) / K * Q(+1) - (1 + r(+1)) * Q + return inv, val + + +@simple +def dividend(Y, w, N, K, pi, mup, kappap, delta, epsI): + psip = mup / (mup - 1) / 2 / kappap * (1 + pi).apply(np.log) ** 2 * Y + k_adjust = K(-1) * (K / K(-1) - 1) ** 2 / (2 * delta * epsI) + I = K - (1 - delta) * K(-1) + k_adjust + div = Y - w * N - I - psip + return psip, I, div + + +@simple +def taylor(rstar, pi, phi): + i = rstar + phi * pi + return i + + +@simple +def fiscal(r, w, N, G, Bg): + tax = (r * Bg + G) / w / N + return tax + + +@simple +def finance(i, p, pi, r, div, omega, pshare): + rb = r - omega + ra = pshare(-1) * (div + p) / p(-1) + (1 - pshare(-1)) * (1 + r) - 1 + fisher = 1 + i(-1) - (1 + r) * (1 + pi) + return rb, ra, fisher + + +@simple +def wage(pi, w): + piw = (1 + pi) * w / w(-1) - 1 + return piw + + +@simple +def union(piw, N, tax, w, UCE, kappaw, muw, vphi, frisch, beta): + wnkpc = kappaw * (vphi * N ** (1 + 1 / frisch) - (1 - tax) * w * N * UCE / muw) + beta * \ + (1 + piw(+1)).apply(np.log) - (1 + piw).apply(np.log) + return wnkpc + + +@simple +def mkt_clearing(p, A, B, Bg, C, I, G, CHI, psip, omega, Y): + wealth = A + B + asset_mkt = p + Bg - wealth + goods_mkt = C + I + G + CHI + psip + omega * B - Y + return asset_mkt, wealth, goods_mkt + + +@simple +def share_value(p, tot_wealth, Bh): + pshare = p / (tot_wealth - Bh) + return pshare + + +@solved(unknowns={'pi': (-0.1, 0.1)}, targets=['nkpc'], solver="brentq") +def pricing_solved(pi, mc, r, Y, kappap, mup): + nkpc = kappap * (mc - 1 / mup) + Y(+1) / Y * (1 + pi(+1)).apply(np.log) / \ + (1 + r(+1)) - (1 + pi).apply(np.log) + return nkpc + + +@solved(unknowns={'p': (5, 15)}, targets=['equity'], solver="brentq") +def arbitrage_solved(div, p, r): + equity = div(+1) + p(+1) - p * (1 + r(+1)) + return equity + + +@simple +def partial_ss(Y, N, K, r, tot_wealth, Bg, delta): + """Solves for (mup, alpha, Z, w) to hit (tot_wealth, N, K, pi).""" + # 1. Solve for markup to hit total wealth + p = tot_wealth - Bg + mc = 1 - r * (p - K) / Y + mup = 1 / mc + + # 2. Solve for capital share to hit K + alpha = (r + delta) * K / Y / mc + + # 3. Solve for TFP to hit Y + Z = Y * K ** (-alpha) * N ** (alpha - 1) + + # 4. Solve for w such that piw = 0 + w = mc * (1 - alpha) * Y / N + + return p, mc, mup, alpha, Z, w + + +@simple +def union_ss(tax, w, UCE, N, muw, frisch): + """Solves for (vphi) to hit (wnkpc).""" + vphi = (1 - tax) * w * UCE / muw / N ** (1 + 1 / frisch) + wnkpc = vphi * N ** (1 + 1 / frisch) - (1 - tax) * w * UCE / muw + return vphi, wnkpc + + +'''Part 2: Embed HA block''' + +def make_grids(bmax, amax, kmax, nB, nA, nK, nZ, rho_z, sigma_z): + b_grid = utils.discretize.agrid(amax=bmax, n=nB) + a_grid = utils.discretize.agrid(amax=amax, n=nA) + k_grid = utils.discretize.agrid(amax=kmax, n=nK)[::-1].copy() + e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho_z, sigma=sigma_z, N=nZ) + return b_grid, a_grid, k_grid, e_grid, Pi + + +def income(e_grid, tax, w, N): + z_grid = (1 - tax) * w * N * e_grid + return z_grid + + +'''Part 3: DAG''' + +def dag(): + # Combine Blocks + household = hh.household.add_hetinputs([income, make_grids]) + production = combine([labor, investment]) + production_solved = production.solved(unknowns={'Q': 1., 'K': 10.}, + targets=['inv', 'val'], solver='broyden_custom') + blocks = [household, pricing_solved, arbitrage_solved, production_solved, + dividend, taylor, fiscal, share_value, finance, wage, union, mkt_clearing] + two_asset_model = create_model(blocks, name='Two-Asset HANK') + + # Steadt state DAG + blocks_ss = [household, partial_ss, + dividend, taylor, fiscal, share_value, finance, union_ss, mkt_clearing] + two_asset_model_ss = create_model(blocks_ss, name='Two-Asset HANK SS') + + # Steady State + calibration = {'Y': 1., 'N': 1.0, 'K': 10., 'r': 0.0125, 'rstar': 0.0125, 'tot_wealth': 14, + 'delta': 0.02, 'pi': 0., + 'kappap': 0.1, 'muw': 1.1, 'Bh': 1.04, 'Bg': 2.8, 'G': 0.2, 'eis': 0.5, + 'frisch': 1, 'chi0': 0.25, 'chi2': 2, 'epsI': 4, 'omega': 0.005, + 'kappaw': 0.1, 'phi': 1.5, 'nZ': 3, 'nB': 10, 'nA': 16, 'nK': 4, + 'bmax': 50, 'amax': 4000, 'kmax': 1, 'rho_z': 0.966, 'sigma_z': 0.92} + unknowns_ss = {'beta': 0.976, 'chi1': 6.5} + targets_ss = {'asset_mkt': 0., 'B': 'Bh'} + cali = two_asset_model_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, solver='broyden_custom') + ss = two_asset_model.steady_state(cali) + + # Transitional Dynamics/Jacobian Calculation + unknowns = ['r', 'w', 'Y'] + targets = ['asset_mkt', 'fisher', 'wnkpc'] + exogenous = ['rstar', 'Z', 'G'] + + return two_asset_model_ss, ss, two_asset_model, unknowns, targets, exogenous diff --git a/src/sequence_jacobian/utilities/__init__.py b/src/sequence_jacobian/utilities/__init__.py new file mode 100644 index 0000000..04d85a8 --- /dev/null +++ b/src/sequence_jacobian/utilities/__init__.py @@ -0,0 +1,4 @@ +"""Utilities relating to: interpolation, forward step/transition, grids and Markov chains, solvers, sorting, etc.""" + +from . import (bijection, differentiate, discretize, function, graph, interpolate, + misc, multidim, optimized_routines, ordered_set, solvers) diff --git a/src/sequence_jacobian/utilities/bijection.py b/src/sequence_jacobian/utilities/bijection.py new file mode 100644 index 0000000..0738688 --- /dev/null +++ b/src/sequence_jacobian/utilities/bijection.py @@ -0,0 +1,71 @@ +from .ordered_set import OrderedSet + +class Bijection: + def __init__(self, map): + # identity always implicit, remove if there explicitly + self.map = {k: v for k, v in map.items() if k != v} + invmap = {} + for k, v in map.items(): + if v in invmap: + raise ValueError(f'Duplicate value {v}, for keys {invmap[v]} and {k}') + invmap[v] = k + self.invmap = invmap + + @property + def inv(self): + invmap = Bijection.__new__(Bijection) # better way to do this? + invmap.map = self.invmap + invmap.invmap = self.map + return invmap + + def __repr__(self): + return f'Bijection({repr(self.map)})' + + def __getitem__(self, k): + return self.map.get(k, k) + + def __matmul__(self, x): + if x is None: + return None + elif isinstance(x, str) or isinstance(x, int): + return self[x] + elif isinstance(x, Bijection): + # compose self: v -> u with x: w -> v + # assume everything missing in either is the identity + M = {} + for v, u in self.map.items(): + w = x.invmap.get(v, v) + M[w] = u + for w, v in x.map.items(): + if v not in self.map: + M[w] = v + return Bijection(M) + elif isinstance(x, dict): + return {self[k]: v for k, v in x.items()} + elif isinstance(x, list): + return [self[k] for k in x] + elif isinstance(x, set): + return {self[k] for k in x} + elif isinstance(x, tuple): + return tuple(self[k] for k in x) + elif isinstance(x, OrderedSet): + return OrderedSet([self[k] for k in x]) + else: + return NotImplemented + + def __rmatmul__(self, x): + if isinstance(x, str): + return self[x] + elif isinstance(x, dict): + return {self[k]: v for k, v in x.items()} + elif isinstance(x, list): + return [self[k] for k in x] + elif isinstance(x, set): + return {self[k] for k in x} + elif isinstance(x, tuple): + return tuple(self[k] for k in x) + else: + return NotImplemented + + def __bool__(self): + return bool(self.map) \ No newline at end of file diff --git a/sequence_jacobian/utilities/differentiate.py b/src/sequence_jacobian/utilities/differentiate.py similarity index 100% rename from sequence_jacobian/utilities/differentiate.py rename to src/sequence_jacobian/utilities/differentiate.py diff --git a/sequence_jacobian/utilities/discretize.py b/src/sequence_jacobian/utilities/discretize.py similarity index 100% rename from sequence_jacobian/utilities/discretize.py rename to src/sequence_jacobian/utilities/discretize.py diff --git a/src/sequence_jacobian/utilities/function.py b/src/sequence_jacobian/utilities/function.py new file mode 100644 index 0000000..75512d4 --- /dev/null +++ b/src/sequence_jacobian/utilities/function.py @@ -0,0 +1,271 @@ +import re +import inspect +import numpy as np + +from .ordered_set import OrderedSet +from . import graph + +# TODO: fix this, have it twice (main version in misc) due to circular import problem +# let's make everything point to here for input_list, etc. so that this is unnecessary +def make_tuple(x): + """If not tuple or list, make into tuple with one element. + + Wrapping with this allows user to write, e.g.: + "return r" rather than "return (r,)" + "policy='a'" rather than "policy=('a',)" + """ + return (x,) if not (isinstance(x, tuple) or isinstance(x, list)) else x + + +def input_list(f): + """Return list of function inputs (both positional and keyword arguments)""" + return OrderedSet(inspect.signature(f).parameters) + + +def input_defaults(f): + defaults = {} + for p in inspect.signature(f).parameters.values(): + if p.default != p.empty: + defaults[p.name] = p.default + return defaults + + +def output_list(f): + """Scans source code of function to detect statement like + + 'return L, Div' + + and reports the list ['L', 'Div']. + + Important to write functions in this way when they will be scanned by output_list, for + either SimpleBlock or HetBlock. + """ + return OrderedSet(re.findall('return (.*?)\n', inspect.getsource(f))[-1].replace(' ', '').split(',')) + + +def metadata(f): + name = f.__name__ + inputs = input_list(f) + outputs = output_list(f) + return name, inputs, outputs + + +class ExtendedFunction: + """Wrapped function that knows its inputs and outputs. Evaluates on dict containing necessary + inputs, returns dict containing outputs by name""" + + def __init__(self, f): + if isinstance(f, ExtendedFunction): + self.f, self.name, self.inputs, self.outputs = f.f, f.name, f.inputs, f.outputs + else: + self.f = f + self.name, self.inputs, self.outputs = metadata(f) + + def __call__(self, input_dict): + # take subdict of d contained in inputs + # this allows for d not to include all inputs (if there are optional inputs) + input_dict = {k: v for k, v in input_dict.items() if k in self.inputs} + return self.outputs.dict_from(make_tuple(self.f(**input_dict))) + + def __repr__(self): + return f'<{type(self).__name__}({self.name}): {self.inputs} -> {self.outputs}>' + + def wrapped_call(self, input_dict, preprocess=None, postprocess=None): + if preprocess is not None: + input_dict = {k: preprocess(v) for k, v in input_dict.items() if k in self.inputs} + else: + input_dict = {k: v for k, v in input_dict.items() if k in self.inputs} + + output_dict = self.outputs.dict_from(make_tuple(self.f(**input_dict))) + if postprocess is not None: + output_dict = {k: postprocess(v) for k, v in output_dict.items()} + + return output_dict + + def differentiable(self, input_dict, h=1E-5, twosided=False): + return DifferentiableExtendedFunction(self.f, self.name, self.inputs, self.outputs, input_dict, h, twosided) + + +class DifferentiableExtendedFunction(ExtendedFunction): + def __init__(self, f, name, inputs, outputs, input_dict, h=1E-5, twosided=False): + self.f, self.name, self.inputs, self.outputs = f, name, inputs, outputs + self.input_dict = input_dict + self.output_dict = None # lazy evaluation of outputs for one-sided diff + self.h = h + self.default_twosided = twosided + + def diff(self, shock_dict, h=None, hide_zeros=False, twosided=None): + if twosided is None: + twosided = self.default_twosided + + if not twosided: + return self.diff1(shock_dict, h, hide_zeros) + else: + return self.diff2(shock_dict, h, hide_zeros) + + def diff1(self, shock_dict, h=None, hide_zeros=False): + if h is None: + h = self.h + + if self.output_dict is None: + self.output_dict = self(self.input_dict) + + shocked_input_dict = {**self.input_dict, + **{k: self.input_dict[k] + h * shock for k, shock in shock_dict.items() if k in self.input_dict}} + + shocked_output_dict = self(shocked_input_dict) + + derivative_dict = {k: (shocked_output_dict[k] - self.output_dict[k])/h for k in self.output_dict} + + if hide_zeros: + derivative_dict = hide_zero_values(derivative_dict) + + return derivative_dict + + def diff2(self, shock_dict, h=None, hide_zeros=False): + if h is None: + h = self.h + + shocked_input_dict_up = {**self.input_dict, + **{k: self.input_dict[k] + h * shock for k, shock in shock_dict.items() if k in self.input_dict}} + shocked_input_dict_dn = {**self.input_dict, + **{k: self.input_dict[k] - h * shock for k, shock in shock_dict.items() if k in self.input_dict}} + + shocked_output_dict_up = self(shocked_input_dict_up) + shocked_output_dict_dn = self(shocked_input_dict_dn) + + derivative_dict = {k: (shocked_output_dict_up[k] - shocked_output_dict_dn[k])/(2*h) for k in shocked_output_dict_dn} + + if hide_zeros: + derivative_dict = hide_zero_values(derivative_dict) + + return derivative_dict + + +def hide_zero_values(d): + return {k: v for k, v in d.items() if not np.allclose(v, 0)} + + +class CombinedExtendedFunction(ExtendedFunction): + def __init__(self, fs, name=None): + self.dag = graph.DAG([ExtendedFunction(f) for f in fs]) + self.inputs = self.dag.inputs + self.outputs = self.dag.outputs + self.functions = {b.name: b for b in self.dag.blocks} + + if name is None: + names = list(self.functions) + if len(names) == 1: + self.name = names[0] + else: + self.name = f'{names[0]}_{names[-1]}' + else: + self.name = name + + def __call__(self, input_dict, outputs=None): + functions_to_visit = list(self.functions.values()) + if outputs is not None: + functions_to_visit = [functions_to_visit[i] for i in self.dag.visit_from_outputs(outputs)] + + results = input_dict.copy() + for f in functions_to_visit: + results.update(f(results)) + + if outputs is not None: + return {k: results[k] for k in outputs} + else: + return results + + def call_on_deviations(self, ss, dev_dict, outputs=None): + functions_to_visit = self.filter(list(self.functions.values()), dev_dict, outputs) + + results = {} + input_dict = {**ss, **dev_dict} + for f in functions_to_visit: + out = f(input_dict) + results.update(out) + input_dict.update(out) + + if outputs is not None: + return {k: v for k, v in results.items() if k in outputs} + else: + return results + + def filter(self, function_list, inputs, outputs=None): + nums_to_visit = self.dag.visit_from_inputs(inputs) + if outputs is not None: + nums_to_visit &= self.dag.visit_from_outputs(outputs) + return [function_list[n] for n in nums_to_visit] + + def wrapped_call(self, input_dict, preprocess=None, postprocess=None): + raise NotImplementedError + + def add(self, f): + if inspect.isfunction(f) or isinstance(f, ExtendedFunction): + return CombinedExtendedFunction(list(self.functions.values()) + [f]) + else: + # otherwise assume f is iterable + return CombinedExtendedFunction(list(self.functions.values()) + list(f)) + + def remove(self, name): + if isinstance(name, str): + return CombinedExtendedFunction([v for k, v in self.functions.items() if k != name]) + else: + # otherwise assume name is iterable + return CombinedExtendedFunction([v for k, v in self.functions.items() if k not in name]) + + def children(self): + return OrderedSet(self.functions) + + def differentiable(self, input_dict, h=1E-5, twosided=False): + return DifferentiableCombinedExtendedFunction(self.functions, self.dag, self.name, self.inputs, self.outputs, input_dict, h, twosided) + + +class DifferentiableCombinedExtendedFunction(CombinedExtendedFunction, DifferentiableExtendedFunction): + def __init__(self, functions, dag, name, inputs, outputs, input_dict, h=1E-5, twosided=False): + self.dag, self.name, self.inputs, self.outputs = dag, name, inputs, outputs + diff_functions = {} + for k, f in functions.items(): + diff_functions[k] = f.differentiable(input_dict, h) + self.diff_functions = diff_functions + self.default_twosided = twosided + + def diff(self, shock_dict, h=None, outputs=None, hide_zeros=False, twosided=False): + if twosided is None: + twosided = self.default_twosided + + if not twosided: + return self.diff1(shock_dict, h, outputs, hide_zeros) + else: + return self.diff2(shock_dict, h, outputs, hide_zeros) + + def diff1(self, shock_dict, h=None, outputs=None, hide_zeros=False): + functions_to_visit = self.filter(list(self.diff_functions.values()), shock_dict, outputs) + + shock_dict = shock_dict.copy() + results = {} + for f in functions_to_visit: + out = f.diff1(shock_dict, h, hide_zeros) + results.update(out) + shock_dict.update(out) + + if outputs is not None: + return {k: v for k, v in results.items() if k in outputs} + else: + return results + + def diff2(self, shock_dict, h=None, outputs=None, hide_zeros=False): + functions_to_visit = self.filter(list(self.diff_functions.values()), shock_dict, outputs) + + shock_dict = shock_dict.copy() + results = {} + for f in functions_to_visit: + out = f.diff2(shock_dict, h, hide_zeros) + results.update(out) + shock_dict.update(out) + + if outputs is not None: + return {k: v for k, v in results.items() if k in outputs} + else: + return results + diff --git a/src/sequence_jacobian/utilities/graph.py b/src/sequence_jacobian/utilities/graph.py new file mode 100644 index 0000000..c37589f --- /dev/null +++ b/src/sequence_jacobian/utilities/graph.py @@ -0,0 +1,244 @@ +"""Topological sort and related code""" +from .ordered_set import OrderedSet +from .bijection import Bijection + +class DAG: + """Represents "blocks" that each have inputs and outputs, where output-input relationships between + blocks form a DAG. Fundamental DAG object intended to underlie CombinedBlock and CombinedExtendedFunction. + + Initialized with list of blocks, which are then topologically sorted""" + + def __init__(self, blocks): + inmap = get_input_map(blocks) + outmap = get_output_map(blocks) + adj = get_block_adjacency_list(blocks, inmap) + revadj = get_block_reverse_adjacency_list(blocks, outmap) + topsort = topological_sort(adj, revadj) + + M = Bijection({i: t for i, t in enumerate(topsort)}) + + self.blocks = [blocks[t] for t in topsort] + self.inmap = {k: M @ v for k, v in inmap.items()} + self.outmap = {k: M @ v for k, v in outmap.items()} + self.adj = [M @ adj[t] for t in topsort] + self.revadj = [M @ revadj[t] for t in topsort] + + self.inputs = OrderedSet(k for k in inmap if k not in outmap) + self.outputs = OrderedSet(outmap) + + + def visit_from_inputs(self, inputs): + """Which block numbers are ultimately dependencies of 'inputs'?""" + inputs = inputs & self.inputs + visited = OrderedSet() + for n, (block, parentset) in enumerate(zip(self.blocks, self.revadj)): + # first see if block has its input directly changed + for i in inputs: + if i in block.inputs: + visited.add(n) + break + else: + if not parentset.isdisjoint(visited): + visited.add(n) + + return visited + + def visit_from_outputs(self, outputs): + """Which block numbers are 'outputs' ultimately dependent on?""" + outputs = outputs & self.outputs + visited = OrderedSet() + for n in reversed(range(len(self.blocks))): + block = self.blocks[n] + childset = self.adj[n] + + # first see if block has its output directly used + for o in outputs: + if o in block.outputs: + visited.add(n) + break + else: + if not childset.isdisjoint(visited): + visited.add(n) + + return reversed(visited) + + +def block_sort(blocks): + """Given list of blocks (either blocks themselves or dicts of Jacobians), find a topological sort. + + Relies on blocks having 'inputs' and 'outputs' attributes (unless they are dicts of Jacobians, in which case it's + inferred) that indicate their aggregate inputs and outputs + + blocks: `list` + A list of the blocks (SimpleBlock, HetBlock, etc.) to sort + """ + inmap = get_input_map(blocks) + outmap = get_output_map(blocks) + adj = get_block_adjacency_list(blocks, inmap) + revadj = get_block_reverse_adjacency_list(blocks, outmap) + return topological_sort(adj, revadj) + + +def topological_sort(adj, revadj, names=None): + """Given directed graph pointing from each node to the nodes it depends on, topologically sort nodes""" + # get complete set version of dep, and its reversal, and build initial stack of nodes with no dependencies + revdep = adj + dep = [s.copy() for s in revadj] + nodeps = [n for n, depset in enumerate(dep) if not depset] + topsorted = [] + + # Kahn's algorithm: find something with no dependency, delete its edges and update + while nodeps: + n = nodeps.pop() + topsorted.append(n) + for n2 in revdep[n]: + dep[n2].remove(n) + if not dep[n2]: + nodeps.append(n2) + + # should be done: topsorted should be topologically sorted with same # of elements as original graphs! + if len(topsorted) != len(dep): + cycle_ints = find_cycle(dep, dep.keys() - set(topsorted)) + assert cycle_ints is not None, 'topological sort failed but no cycle, THIS SHOULD NEVER EVER HAPPEN' + cycle = [names[i] for i in cycle_ints] if names else cycle_ints + raise Exception(f'Topological sort failed: cyclic dependency {" -> ".join([str(n) for n in cycle])}') + + return topsorted + + +def get_input_map(blocks: list): + """inmap[i] gives set of block numbers where i is an input""" + inmap = dict() + for num, block in enumerate(blocks): + for i in block.inputs: + inset = inmap.setdefault(i, OrderedSet()) + inset.add(num) + + return inmap + + +def get_output_map(blocks: list): + """outmap[o] gives unique block number where o is an output""" + outmap = dict() + for num, block in enumerate(blocks): + for o in block.outputs: + if o in outmap: + raise ValueError(f'{o} is output twice') + outmap[o] = num + + return outmap + + +def get_block_adjacency_list(blocks, inmap): + """adj[n] for block number n gives set of block numbers which this block points to""" + adj = [] + for block in blocks: + current_adj = OrderedSet() + for o in block.outputs: + # for each output, if that output is used as an input by some blocks, add those blocks to adj + if o in inmap: + current_adj |= inmap[o] + adj.append(current_adj) + return adj + + +def get_block_reverse_adjacency_list(blocks, outmap): + """revadj[n] for block number n gives set of block numbers that point to this block""" + revadj = [] + for block in blocks: + current_revadj = OrderedSet() + for i in block.inputs: + if i in outmap: + current_revadj.add(outmap[i]) + revadj.append(current_revadj) + return revadj + + +def find_intermediate_inputs(blocks): + # TODO: should be deprecated + """Find outputs of the blocks in blocks that are inputs to other blocks in blocks. + This is useful to ensure that all of the relevant curlyJ Jacobians (of all inputs to all outputs) are computed. + """ + required = OrderedSet() + outmap = get_output_map(blocks) + for num, block in enumerate(blocks): + if hasattr(block, 'inputs'): + inputs = block.inputs + else: + inputs = OrderedSet(i for o in block for i in block[o]) + for i in inputs: + if i in outmap: + required.add(i) + return required + + +def find_cycle(dep, onlyset=None): + """Return list giving cycle if there is one, otherwise None""" + + # supposed to look only within 'onlyset', so filter out everything else + if onlyset is not None: + dep = {k: (set(v) & set(onlyset)) for k, v in dep.items() if k in onlyset} + + tovisit = set(dep.keys()) + stack = SetStack() + while tovisit or stack: + if stack: + # if stack has something, still need to proceed with DFS + n = stack.top() + if dep[n]: + # if there are any dependencies left, let's look at them + n2 = dep[n].pop() + if n2 in stack: + # we have a cycle, since this is already in our stack + i2loc = stack.index(n2) + return stack[i2loc:] + [stack[i2loc]] + else: + # no cycle, visit this node only if we haven't already visited it + if n2 in tovisit: + tovisit.remove(n2) + stack.add(n2) + else: + # if no dependencies left, then we're done with this node, so let's forget about it + stack.pop(n) + else: + # nothing left on stack, let's start the DFS from something new + n = tovisit.pop() + stack.add(n) + + # if we never find a cycle, we're done + return None + + +class SetStack: + """Stack implemented with list but tests membership with set to be efficient in big cases""" + + def __init__(self): + self.myset = set() + self.mylist = [] + + def add(self, x): + self.myset.add(x) + self.mylist.append(x) + + def pop(self): + x = self.mylist.pop() + self.myset.remove(x) + return x + + def top(self): + return self.mylist[-1] + + def index(self, x): + return self.mylist.index(x) + + def __contains__(self, x): + return x in self.myset + + def __len__(self): + return len(self.mylist) + + def __getitem__(self, i): + return self.mylist.__getitem__(i) + + def __repr__(self): + return self.mylist.__repr__() diff --git a/sequence_jacobian/utilities/interpolate.py b/src/sequence_jacobian/utilities/interpolate.py similarity index 100% rename from sequence_jacobian/utilities/interpolate.py rename to src/sequence_jacobian/utilities/interpolate.py diff --git a/src/sequence_jacobian/utilities/misc.py b/src/sequence_jacobian/utilities/misc.py new file mode 100644 index 0000000..7c7964c --- /dev/null +++ b/src/sequence_jacobian/utilities/misc.py @@ -0,0 +1,143 @@ +"""Assorted other utilities""" + +import numpy as np +import scipy.linalg + + +def make_tuple(x): + """If not tuple or list, make into tuple with one element. + + Wrapping with this allows user to write, e.g.: + "return r" rather than "return (r,)" + "policy='a'" rather than "policy=('a',)" + """ + return (x,) if not (isinstance(x, tuple) or isinstance(x, list)) else x + + +def numeric_primitive(instance): + # If it is already a primitive, just return it + if type(instance) in {int, float}: + return instance + elif isinstance(instance, np.ndarray): + if np.issubdtype(instance.dtype, np.number): + return np.array(instance) + else: + raise ValueError(f"The tuple/list argument provided to numeric_primitive has dtype: {instance.dtype}," + f" which is not a valid numeric type.") + elif type(instance) in {tuple, list}: + instance_array = np.asarray(instance) + if np.issubdtype(instance_array.dtype, np.number): + return type(instance)(instance_array) + else: + raise ValueError(f"The tuple/list argument provided to numeric_primitive has dtype: {instance_array.dtype}," + f" which is not a valid numeric type.") + else: + return instance.real if np.isscalar(instance) else instance.base + + +def demean(x): + return x - x.sum()/x.size + + +# simpler aliases for LU factorization and solution +def factor(X): + return scipy.linalg.lu_factor(X) + + +def factored_solve(Z, y): + return scipy.linalg.lu_solve(Z, y) + + +# The below functions are used in steady_state +def unprime(s): + """Given a variable's name as a `str`, check if the variable is a prime, i.e. has "_p" at the end. + If so, return the unprimed version, if not return itself.""" + if s[-2:] == "_p": + return s[:-2] + else: + return s + + +def uncapitalize(s): + return s[0].lower() + s[1:] + + +def list_diff(l1, l2): + """Returns the list that is the "set difference" between l1 and l2 (based on element values)""" + o_list = [] + for k in set(l1) - set(l2): + o_list.append(k) + return o_list + + +def dict_diff(d1, d2): + """Returns the dictionary that is the "set difference" between d1 and d2 (based on keys, not key-value pairs) + E.g. d1 = {"a": 1, "b": 2}, d2 = {"b": 5}, then dict_diff(d1, d2) = {"a": 1} + """ + o_dict = {} + for k in set(d1.keys()) - set(d2.keys()): + o_dict[k] = d1[k] + return o_dict + + +def smart_set(data): + # We want set to construct a single-element set for strings, i.e. ignoring the .iter method of strings + if isinstance(data, str): + return {data} + else: + return set(data) + + +def smart_zip(keys, values): + """For handling the case where keys and values may be scalars""" + if isinstance(values, float): + return zip(keys, [values]) + else: + return zip(keys, values) + + +def smart_zeros(n): + """Return either the float 0. or a np.ndarray of length 0 depending on whether n > 1""" + if n > 1: + return np.zeros(n) + else: + return 0. + +'''Tools for taste shocks used in discrete choice problems''' + + +def choice_prob(vfun, lam): + """ + Logit choice probability of choosing along first axis. + + Parameters + ---------- + vfun : array(Ns, Nz, Na): discrete choice specific value function + lam : float, scale of taste shock + + Returns + ------- + prob : array (Ns, Nz, nA): choice probability + """ + # rescale values for numeric robustness + vmax = np.max(vfun, axis=0) + vfun_norm = vfun - vmax + + # apply formula (could be njitted in separate function) + P = np.exp(vfun_norm / lam) / np.sum(np.exp(vfun_norm / lam), axis=0) + return P + + +def logsum(vfun, lam): + """Logsum formula for expected continuation value.""" + + # rescale values for numeric robustness + vmax = np.max(vfun, axis=0) + vfun_norm = vfun - vmax + + # apply formula (could be njitted in separate function) + VE = vmax + lam * np.log(np.sum(np.exp(vfun_norm / lam), axis=0)) + return VE + + +#from .function import (input_list, output_list) diff --git a/src/sequence_jacobian/utilities/multidim.py b/src/sequence_jacobian/utilities/multidim.py new file mode 100644 index 0000000..251c7d9 --- /dev/null +++ b/src/sequence_jacobian/utilities/multidim.py @@ -0,0 +1,25 @@ +import numpy as np + + +def multiply_ith_dimension(Pi, i, X): + """If Pi is a square matrix, multiply Pi times the ith dimension of X and return""" + X = X.swapaxes(0, i) + shape = X.shape + X = X.reshape((X.shape[0], -1)) + + # iterate forward using Pi + X = Pi @ X + + # reverse steps + X = X.reshape(shape) + return X.swapaxes(0, i) + + +def outer(pis): + """Return n-dimensional outer product of list of n vectors""" + pi = pis[0] + for pi_i in pis[1:]: + pi = np.kron(pi, pi_i) + return pi.reshape(*(len(pi_i) for pi_i in pis)) + + diff --git a/sequence_jacobian/utilities/optimized_routines.py b/src/sequence_jacobian/utilities/optimized_routines.py similarity index 99% rename from sequence_jacobian/utilities/optimized_routines.py rename to src/sequence_jacobian/utilities/optimized_routines.py index 94f1724..e0499e3 100644 --- a/sequence_jacobian/utilities/optimized_routines.py +++ b/src/sequence_jacobian/utilities/optimized_routines.py @@ -41,5 +41,3 @@ def fast_aggregate(X, Y): for t in range(T): Z[t] = Xnew[t, :] @ Ynew[t, :] return Z - - diff --git a/src/sequence_jacobian/utilities/ordered_set.py b/src/sequence_jacobian/utilities/ordered_set.py new file mode 100644 index 0000000..6d3952b --- /dev/null +++ b/src/sequence_jacobian/utilities/ordered_set.py @@ -0,0 +1,144 @@ +from typing import Iterable + +class OrderedSet: + """Ordered set implemented as dict (where key insertion order is preserved) mapping all to None. + + Operations on multiple ordered sets (e.g. union) order all members of first argument first, then + second argument. If a member is in both, order is as early as possible. + + See test_misc_support.test_ordered_set() for examples.""" + + def __init__(self, members: Iterable = []): + self.d = {k: None for k in members} + + def dict_from(self, s): + return dict(zip(self, s)) + + def __iter__(self): + return iter(self.d) + + def __reversed__(self): + return OrderedSet(list(self)[::-1]) + + def __repr__(self): + return f"OrderedSet({list(self)})" + + def __str__(self): + return str(list(self.d)) + + def __contains__(self, k): + return k in self.d + + def __len__(self): + return len(self.d) + + def __getitem__(self, i): + return list(self.d)[i] + + def add(self, x): + self.d[x] = None + + def difference(self, s): + return OrderedSet(k for k in self if k not in s) + + def difference_update(self, s): + self.d = self.difference(s).d + return self + + def discard(self, k): + self.d.pop(k, None) + + def intersection(self, s): + return OrderedSet(k for k in self if k in s) + + def intersection_update(self, s): + self.d = self.intersection(s).d + return self + + def isdisjoint(self, s): + return len(self.intersection(s)) == 0 + + def issubset(self, s): + return len(self.difference(s)) == 0 + + def issuperset(self, s): + return len(self.intersection(s)) == len(s) + + def remove(self, k): + self.d.pop(k) + + def symmetric_difference(self, s): + diff = self.difference(s) + for k in s: + if k not in self: + diff.add(k) + return diff + + def symmetric_difference_update(self, s): + self.d = self.symmetric_difference(s).d + return self + + def union(self, s): + return self.copy().update(s) + + def update(self, s): + for k in s: + self.add(k) + return self + + def copy(self): + return OrderedSet(self) + + def __eq__(self, s): + if isinstance(s, OrderedSet): + return list(self) == list(s) + else: + return False + + def __le__(self, s): + return self.issubset(s) + + def __lt__(self, s): + return self.issubset(s) and (len(self) != len(s)) + + def __ge__(self, s): + return self.issuperset(s) + + def __gt__(self, s): + return self.issuperset(s) and (len(self) != len(s)) + + def __or__(self, s): + return self.union(s) + + def __ior__(self, s): + return self.update(s) + + def __ror__(self, s): + return self.union(s) + + def __and__(self, s): + return self.intersection(s) + + def __iand__(self, s): + return self.intersection_update(s) + + def __rand__(self, s): + return self.intersection(s) + + def __sub__(self, s): + return self.difference(s) + + def __isub__(self, s): + return self.difference_update(s) + + def __rsub__(self, s): + return OrderedSet(s).difference(self) + + def __xor__(self, s): + return self.symmetric_difference(s) + + def __ixor__(self, s): + return self.symmetric_difference_update(s) + + def __rxor__(self, s): + return OrderedSet(s).symmetric_difference(self) diff --git a/sequence_jacobian/utilities/solvers.py b/src/sequence_jacobian/utilities/solvers.py similarity index 98% rename from sequence_jacobian/utilities/solvers.py rename to src/sequence_jacobian/utilities/solvers.py index 24bea55..d29c081 100644 --- a/sequence_jacobian/utilities/solvers.py +++ b/src/sequence_jacobian/utilities/solvers.py @@ -76,8 +76,6 @@ def broyden_solver(f, x0, y0=None, tol=1E-9, maxcount=100, backtrack_c=0.5, verb if y is None: y = f(x) - # initialize J with Newton! - J = obtain_J(f, x, y) for count in range(maxcount): if verbose: printit(count, x, y) @@ -85,6 +83,10 @@ def broyden_solver(f, x0, y0=None, tol=1E-9, maxcount=100, backtrack_c=0.5, verb if np.max(np.abs(y)) < tol: return x, y + # initialize J with Newton! + if count == 0: + J = obtain_J(f, x, y) + if len(x) == len(y): dx = np.linalg.solve(J, -y) elif len(x) < len(y): @@ -143,4 +145,3 @@ def printit(it, x, y, **kwargs): for kw, val in kwargs.items(): print(f'{kw} = {val:.3f}') print('\n') - diff --git a/tests/base/test_determinacy.py b/tests/base/test_determinacy.py deleted file mode 100644 index e1b9b2e..0000000 --- a/tests/base/test_determinacy.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Test all models' determinacy calculations""" -import pytest - -from sequence_jacobian import jacobian, determinacy - - -def test_hank_determinacy(one_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = one_asset_hank_model - T = 100 - - # Stable Case - A = jacobian.get_H_U(blocks, unknowns, targets, T, ss, asymptotic=True, save=True) - wn = determinacy.winding_criterion(A) - assert wn == 0 - - # Unstable Case - ss_unstable = {**ss, "phi": 0.75} - A_unstable = jacobian.get_H_U(blocks, unknowns, targets, T, ss_unstable, asymptotic=True, use_saved=True) - wn_unstable = determinacy.winding_criterion(A_unstable) - assert wn_unstable == -1 - - -def test_two_asset_determinacy(two_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = two_asset_hank_model - T = 100 - - A = jacobian.get_H_U(blocks, unknowns, targets, T, ss, asymptotic=True) - wn = determinacy.winding_criterion(A) - assert wn == 0 \ No newline at end of file diff --git a/tests/base/test_displacement_handlers.py b/tests/base/test_displacement_handlers.py index 1e28d9a..9080922 100644 --- a/tests/base/test_displacement_handlers.py +++ b/tests/base/test_displacement_handlers.py @@ -121,26 +121,28 @@ def test_ignore_vector(): def test_displace(): + # TODO: test ss_initial being different from ss + # Test unary operations - arg_singles = [Displace(np.array([1, 2, 3]), ss=2), Displace(np.array([1, 2, 3]), ss=2)(-1)] + arg_singles = [Displace(np.array([1, 2, 3]), 2, 2), Displace(np.array([1, 2, 3]), 2, 2)(-1)] for t1 in arg_singles: for op in ["__neg__", "__pos__"]: assert type(apply_op(op, t1)) == Displace assert np.all(numeric_primitive(apply_op(op, t1)) == apply_op(op, numeric_primitive(t1))) # Test binary operations - arg_pairs = [(Displace(np.array([1, 2, 3]), ss=2), 1), - (Displace(np.array([1, 2, 3]), ss=2), IgnoreFloat(1)), - (Displace(np.array([1, 2, 3]), ss=2), Displace(np.array([2, 3, 4]), ss=3)), - (1, Displace(np.array([1, 2, 3]), ss=2)), - (IgnoreFloat(1), Displace(np.array([1, 2, 3]), ss=2)), - - (Displace(np.array([1, 2, 3]), ss=2)(-1), 1), - (Displace(np.array([1, 2, 3]), ss=2)(-1), IgnoreFloat(1)), - (Displace(np.array([1, 2, 3]), ss=2)(-1), Displace(np.array([2, 3, 4]), ss=3)), - (Displace(np.array([1, 2, 3]), ss=2), Displace(np.array([2, 3, 4]), ss=3)(-1)), - (1, Displace(np.array([1, 2, 3]), ss=2)(-1)), - (IgnoreFloat(1), Displace(np.array([1, 2, 3]), ss=2)(-1))] + arg_pairs = [(Displace(np.array([1, 2, 3]), 2, 2), 1), + (Displace(np.array([1, 2, 3]), 2, 2), IgnoreFloat(1)), + (Displace(np.array([1, 2, 3]), 2, 2), Displace(np.array([2, 3, 4]), 3, 3)), + (1, Displace(np.array([1, 2, 3]), 2, 2)), + (IgnoreFloat(1), Displace(np.array([1, 2, 3]), 2, 2)), + + (Displace(np.array([1, 2, 3]), 2, 2)(-1), 1), + (Displace(np.array([1, 2, 3]), 2, 2)(-1), IgnoreFloat(1)), + (Displace(np.array([1, 2, 3]), 2, 2)(-1), Displace(np.array([2, 3, 4]), 3, 3)), + (Displace(np.array([1, 2, 3]), 2, 2), Displace(np.array([2, 3, 4]), 3, 3)(-1)), + (1, Displace(np.array([1, 2, 3]), 2, 2)(-1)), + (IgnoreFloat(1), Displace(np.array([1, 2, 3]), 2, 2)(-1))] for pair in arg_pairs: t1, t2 = pair for op in ["__add__", "__radd__", "__sub__", "__rsub__", "__mul__", "__rmul__", @@ -159,7 +161,6 @@ def test_displace(): t1_manual_displace[-1:] = t1.ss assert np.all(numeric_primitive(t1(1)) == t1_manual_displace) - def test_accumulated_derivative(): # Test unary operations arg_singles = [AccumulatedDerivative(), AccumulatedDerivative()(-1), AccumulatedDerivative(elements={(1, 1): 2.}, f_value=2.)] diff --git a/tests/base/test_estimation.py b/tests/base/test_estimation.py index 58df805..841c283 100644 --- a/tests/base/test_estimation.py +++ b/tests/base/test_estimation.py @@ -1,21 +1,19 @@ """Test all models' estimation calculations""" - +'' import pytest import numpy as np -import sequence_jacobian as sj -from sequence_jacobian import jacobian +from sequence_jacobian import estimation # See test_determinacy.py for the to-do describing this suppression @pytest.mark.filterwarnings("ignore:.*cannot be safely interpreted as an integer.*:DeprecationWarning") -def test_krusell_smith_estimation(krusell_smith_model): - blocks, exogenous, unknowns, targets, ss = krusell_smith_model +def test_krusell_smith_estimation(krusell_smith_dag): + _, ss, ks_model, unknowns, targets, exogenous = krusell_smith_dag np.random.seed(41234) T = 50 - G = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss) + G = ks_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T) # Step 1: Stacked impulse responses rho = 0.9 @@ -34,7 +32,7 @@ def test_krusell_smith_estimation(krusell_smith_model): # Step 2: Obtain covariance at all leads and lags sigmas = np.array([sigma_persist, sigma_trans]) - Sigma = sj.estimation.all_covariances(dX, sigmas) + Sigma = estimation.all_covariances(dX, sigmas) # Step 3: Log-likelihood calculation # random 100 observations @@ -44,5 +42,5 @@ def test_krusell_smith_estimation(krusell_smith_model): sigma_measurement = np.full(4, 0.05) # calculate log-likelihood - ll = sj.estimation.log_likelihood(Y, Sigma, sigma_measurement) + ll = estimation.log_likelihood(Y, Sigma, sigma_measurement) assert np.isclose(ll, -59921.410111251025) \ No newline at end of file diff --git a/tests/base/test_het_support.py b/tests/base/test_het_support.py new file mode 100644 index 0000000..cb834fa --- /dev/null +++ b/tests/base/test_het_support.py @@ -0,0 +1,171 @@ +import numpy as np +from sequence_jacobian.blocks.support.het_support import (Transition, + PolicyLottery1D, PolicyLottery2D, Markov, CombinedTransition, + lottery_1d, lottery_2d) + +def test_combined_markov(): + shape = (5, 6, 7) + np.random.seed(12345) + + for _ in range(10): + D = np.random.rand(*shape) + Pis = [np.random.rand(s, s) for s in shape[:2]] + markovs = [Markov(Pi, i) for i, Pi in enumerate(Pis)] + combined = CombinedTransition(markovs) + + Dout = combined.expectation(D) + Dout_forward = combined.forward(D) + + D_kron = D.reshape((-1, D.shape[2])) + Pi_kron = np.kron(Pis[0], Pis[1]) + Dout2 = (Pi_kron @ D_kron).reshape(Dout.shape) + Dout2_forward = (Pi_kron.T @ D_kron).reshape(Dout.shape) + + assert np.allclose(Dout, Dout2) + assert np.allclose(Dout_forward, Dout2_forward) + + +def test_many_markov_shock(): + shape = (5, 6, 7) + np.random.seed(12345) + + for _ in range(10): + D = np.random.rand(*shape) + Pis = [np.random.rand(s, s) for s in shape[:2]] + dPis = [np.random.rand(s, s) for s in shape[:2]] + + h = 1E-4 + Dout_up = CombinedTransition([Markov(Pi + h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))]).forward(D) + Dout_dn = CombinedTransition([Markov(Pi - h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))]).forward(D) + Dder = (Dout_up - Dout_dn) / (2*h) + + Dder2 = CombinedTransition([Markov(Pi, i) for i, Pi in enumerate(Pis)]).forward_shockable(D).forward_shock(dPis) + + assert np.allclose(Dder, Dder2) + + +def test_policy_shock(): + shape = (3, 4, 30) + grid = np.geomspace(0.5, 10, shape[-1]) + np.random.seed(98765) + + a = (np.full(shape[0], 0.01)[:, np.newaxis, np.newaxis] + + np.linspace(0, 1, shape[1])[:, np.newaxis] + + 0.001*grid**2 + 0.9*grid + 0.5) + + for _ in range(10): + D = np.random.rand(*shape) + + da = np.random.rand(*shape) + h = 1E-5 + Dout_up = lottery_1d(a + h*da, grid).forward(D) + Dout_dn = lottery_1d(a - h*da, grid).forward(D) + Dder = (Dout_up - Dout_dn) / (2*h) + + Dder2 = lottery_1d(a, grid).forward_shockable(D).forward_shock(da) + + assert np.allclose(Dder, Dder2, atol=1E-4) + + +def test_law_of_motion_shock(): + # shock everything in the law of motion, and see if it works! + shape = (3, 4, 30) + grid = np.geomspace(0.5, 10, shape[-1]) + np.random.seed(98765) + + a = (np.full(shape[0], 0.01)[:, np.newaxis, np.newaxis] + + np.linspace(0, 1, shape[1])[:, np.newaxis] + + 0.001*grid**2 + 0.9*grid + 0.5) + + for _ in range(10): + D = np.random.rand(*shape) + Pis = [np.random.rand(s, s) for s in shape[:2]] + + da = np.random.rand(*shape) + dPis = [np.random.rand(s, s) for s in shape[:2]] + + h = 1E-5 + policy_up = lottery_1d(a + h*da, grid) + policy_dn = lottery_1d(a - h*da, grid) + markovs_up = [Markov(Pi + h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))] + markovs_dn =[Markov(Pi - h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))] + Dout_up = CombinedTransition([policy_up, *markovs_up]).forward(D) + Dout_dn = CombinedTransition([policy_dn, *markovs_dn]).forward(D) + Dder = (Dout_up - Dout_dn) / (2*h) + + markovs = [Markov(Pi, i) for i, Pi, in enumerate(Pis)] + Dder2 = CombinedTransition([lottery_1d(a, grid), *markovs]).forward_shockable(D).forward_shock([da, *dPis]) + + assert np.allclose(Dder, Dder2, atol=1E-4) + + +def test_2d_policy_shock(): + shape = (3, 4, 20, 30) + a_grid = np.geomspace(0.5, 10, shape[-2]) + b_grid = np.geomspace(0.2, 8, shape[-1]) + np.random.seed(98765) + + a = (0.001*a_grid**2 + 0.9*a_grid + 0.5)[:, np.newaxis] + b = (-0.001*b_grid**2 + 0.9*b_grid + 0.5) + + a = np.broadcast_to(a, shape) + b = np.broadcast_to(b, shape) + + for _ in range(10): + D = np.random.rand(*shape) + Pis = [np.random.rand(s, s) for s in shape[:2]] + + da = np.random.rand(*shape) + db = np.random.rand(*shape) + dPis = [np.random.rand(s, s) for s in shape[:2]] + + h = 1E-5 + + policy_up = lottery_2d(a + h*da, b + h*db, a_grid, b_grid) + policy_dn = lottery_2d(a - h*da, b - h*db, a_grid, b_grid) + markovs_up = [Markov(Pi + h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))] + markovs_dn = [Markov(Pi - h*dPi, i) for i, (Pi, dPi) in enumerate(zip(Pis, dPis))] + Dout_up = CombinedTransition([policy_up, *markovs_up]).forward(D) + Dout_dn = CombinedTransition([policy_dn, *markovs_dn]).forward(D) + Dder = (Dout_up - Dout_dn) / (2*h) + + policy = lottery_2d(a, b, a_grid, b_grid) + + markovs = [Markov(Pi, i) for i, Pi, in enumerate(Pis)] + Dder2 = CombinedTransition([policy, *markovs]).forward_shockable(D).forward_shock([[da, db], *dPis]) + + assert np.allclose(Dder, Dder2, atol=1E-4) + + +def test_forward_expectations_symmetry(): + # given a random law of motion, should be identical to iterate forward on distribution, + # then aggregate, or take expectations backward on outcome, then aggregate + shape = (3, 4, 30) + grid = np.geomspace(0.5, 10, shape[-1]) + np.random.seed(1423) + + a = (np.full(shape[0], 0.01)[:, np.newaxis, np.newaxis] + + np.linspace(0, 1, shape[1])[:, np.newaxis] + + 0.001*grid**2 + 0.9*grid + 0.5) + + for _ in range(10): + D = np.random.rand(*shape) + X = np.random.rand(*shape) + Pis = [np.random.rand(s, s) for s in shape[:2]] + + markovs = [Markov(Pi, i) for i, Pi, in enumerate(Pis)] + lom = CombinedTransition([lottery_1d(a, grid), *markovs]) + + Dforward = D + for _ in range(30): + Dforward = lom.forward(Dforward) + outcome = np.vdot(Dforward, X) + + Xbackward = X + for _ in range(30): + Xbackward = lom.expectation(Xbackward) + outcome2 = np.vdot(D, Xbackward) + + assert np.isclose(outcome, outcome2) + + diff --git a/tests/base/test_jacobian.py b/tests/base/test_jacobian.py index 0e2e969..4dee686 100644 --- a/tests/base/test_jacobian.py +++ b/tests/base/test_jacobian.py @@ -2,149 +2,84 @@ import numpy as np -from sequence_jacobian import jacobian - - -def test_ks_jac(krusell_smith_model): - blocks, exogenous, unknowns, targets, ss = krusell_smith_model - household, firm, mkt_clearing, _, _, _ = blocks +def test_ks_jac(krusell_smith_dag): + _, ss, ks_model, unknowns, targets, exogenous = krusell_smith_dag + household, firm = ks_model['household'], ks_model['firm'] T = 10 # Automatically calculate the general equilibrium Jacobian - G2 = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss) + G2 = ks_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T) # Manually calculate the general equilibrium Jacobian - J_firm = firm.jac(ss, shock_list=['K', 'Z']) - J_ha = household.jac(ss, T=T, shock_list=['r', 'w']) + J_firm = firm.jacobian(ss, inputs=['K', 'Z']) + J_ha = household.jacobian(ss, T=T, inputs=['r', 'w']) J_curlyK_K = J_ha['A']['r'] @ J_firm['r']['K'] + J_ha['A']['w'] @ J_firm['w']['K'] J_curlyK_Z = J_ha['A']['r'] @ J_firm['r']['Z'] + J_ha['A']['w'] @ J_firm['w']['Z'] - J = {**J_firm, 'curlyK': {'K': J_curlyK_K, 'Z': J_curlyK_Z}} - H_K = J['curlyK']['K'] - np.eye(T) - H_Z = J['curlyK']['Z'] + J_curlyK = {'curlyK': {'K': J_curlyK_K, 'Z': J_curlyK_Z}} + + H_K = J_curlyK['curlyK']['K'] - np.eye(T) + H_Z = J_curlyK['curlyK']['Z'] + G = {'K': -np.linalg.solve(H_K, H_Z)} # H_K^(-1)H_Z - G['r'] = J['r']['Z'] + J['r']['K'] @ G['K'] - G['w'] = J['w']['Z'] + J['w']['K'] @ G['K'] - G['Y'] = J['Y']['Z'] + J['Y']['K'] @ G['K'] + G['r'] = J_firm['r']['Z'] + J_firm['r']['K'] @ G['K'] + G['w'] = J_firm['w']['Z'] + J_firm['w']['K'] @ G['K'] + G['Y'] = J_firm['Y']['Z'] + J_firm['Y']['K'] @ G['K'] G['C'] = J_ha['C']['r'] @ G['r'] + J_ha['C']['w'] @ G['w'] for o in G: assert np.allclose(G2[o]['Z'], G[o]) -def test_hank_jac(one_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = one_asset_hank_model - T = 10 - - # Automatically calculate the general equilibrium Jacobian - G2 = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss) - - # Manually calculate the general equilibrium Jacobian - curlyJs, required = jacobian.curlyJ_sorted(blocks, unknowns+exogenous, ss, T) - J_curlyH_U = jacobian.forward_accumulate(curlyJs, unknowns, targets, required) - J_curlyH_Z = jacobian.forward_accumulate(curlyJs, exogenous, targets, required) - H_U = jacobian.pack_jacobians(J_curlyH_U, unknowns, targets, T) - H_Z = jacobian.pack_jacobians(J_curlyH_Z, exogenous, targets, T) - G_U = jacobian.unpack_jacobians(-np.linalg.solve(H_U, H_Z), exogenous, unknowns, T) - curlyJs = [G_U] + curlyJs - outputs = set().union(*(curlyJ.keys() for curlyJ in curlyJs)) - set(targets) - G = jacobian.forward_accumulate(curlyJs, exogenous, outputs, required | set(unknowns)) - - for o in G: - for i in G[o]: - assert np.allclose(G[o][i], G2[o][i]) - - -def test_fake_news_v_actual(one_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = one_asset_hank_model - - household = blocks[0] - T = 40 - shock_list=['w', 'r', 'Div', 'Tax'] - Js = household.jac(ss, T, shock_list) - output_list = household.non_back_iter_outputs - - # Preliminary processing of the steady state - (ssin_dict, Pi, ssout_list, ss_for_hetinput, sspol_i, sspol_pi, sspol_space) = household.jac_prelim(ss) - - # Step 1 of fake news algorithm: backward iteration - h = 1E-4 - curlyYs, curlyDs = {}, {} - for i in shock_list: - curlyYs[i], curlyDs[i] = household.backward_iteration_fakenews(i, output_list, ssin_dict, - ssout_list, ss['D'], Pi.T.copy(), sspol_i, - sspol_pi, sspol_space, T, h, ss_for_hetinput) - - asset_effects = np.sum(curlyDs['r'] * ss['a_grid'], axis=(1, 2)) - assert np.linalg.norm(asset_effects - curlyYs["r"]["a"], np.inf) < 1e-15 - - # Step 2 of fake news algorithm: (transpose) forward iteration - curlyPs = {} - for o in output_list: - curlyPs[o] = household.forward_iteration_fakenews(ss[o], Pi, sspol_i, sspol_pi, T-1) - - persistent_asset = np.array([np.vdot(curlyDs['r'][0, ...], - curlyPs['a'][u, ...]) for u in range(30)]) - - assert np.linalg.norm(persistent_asset - Js["A"]["r"][1:31, 0], np.inf) < 3e-15 - - # Step 3 of fake news algorithm: combine everything to make the fake news matrix for each output-input pair - Fs = {o.capitalize(): {} for o in output_list} - for o in output_list: - for i in shock_list: - F = np.empty((T,T)) - F[0, ...] = curlyYs[i][o] - F[1:, ...] = curlyPs[o].reshape(T-1, -1) @ curlyDs[i].reshape(T, -1).T - Fs[o.capitalize()][i] = F - +# TODO: decide whether to get rid of this or revise it with manual solve_jacobian stuff +# def test_hank_jac(one_asset_hank_dag): +# hank_model, exogenous, unknowns, targets, ss = one_asset_hank_dag +# T = 10 - impulse = Fs['C']['w'][:10, 1].copy() # start with fake news impulse - impulse[1:10] += Js['C']['w'][:9, 0] # add unanticipated impulse, shifted by 1 +# # Automatically calculate the general equilibrium Jacobian +# G2 = hank_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T) - assert np.linalg.norm(impulse - Js["C"]["w"][:10, 1], np.inf) == 0.0 +# # Manually calculate the general equilibrium Jacobian +# curlyJs, required = curlyJ_sorted(hank_model.blocks, unknowns + exogenous, ss, T) +# J_curlyH_U = forward_accumulate(curlyJs, unknowns, targets, required) +# J_curlyH_Z = forward_accumulate(curlyJs, exogenous, targets, required) +# H_U = J_curlyH_U[targets, unknowns].pack(T) +# H_Z = J_curlyH_Z[targets, exogenous].pack(T) +# G_U = JacobianDict.unpack(-np.linalg.solve(H_U, H_Z), unknowns, exogenous, T) +# curlyJs = [G_U] + curlyJs +# outputs = set().union(*(curlyJ.outputs for curlyJ in curlyJs)) - set(targets) +# G = forward_accumulate(curlyJs, exogenous, outputs, required | set(unknowns)) - # Step 4 of fake news algorithm: recursively convert fake news matrices to actual Jacobian matrices - Js_original = Js - Js = {o.capitalize(): {} for o in output_list} - for o in output_list: - for i in shock_list: - # implement recursion (30): start with J=F and accumulate terms along diagonal - J = Fs[o.capitalize()][i].copy() - for t in range(1, J.shape[1]): - J[1:, t] += J[:-1, t-1] - Js[o.capitalize()][i] = J +# for o in G: +# for i in G[o]: +# assert np.allclose(G[o][i], G2[o][i]) - for o in output_list: - for i in shock_list: - assert np.array_equal(Js[o.capitalize()][i], Js_original[o.capitalize()][i]) -def test_fake_news_v_direct_method(one_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = one_asset_hank_model +def test_fake_news_v_direct_method(one_asset_hank_dag): + hank_model, ss, *_ = one_asset_hank_dag - household = blocks[0] + household = hank_model['household'] T = 40 - shock_list = ('r') - output_list = household.non_back_iter_outputs + exogenous = ['r'] + output_list = household.non_backward_outputs h = 1E-4 - Js = household.jac(ss, T, shock_list) - Js_direct = {o.capitalize(): {i: np.empty((T, T)) for i in shock_list} for o in output_list} + Js = household.jacobian(ss, exogenous, T=T) + Js_direct = {o.upper(): {i: np.empty((T, T)) for i in exogenous} for o in output_list} # run td once without any shocks to get paths to subtract against # (better than subtracting by ss since ss not exact) # monotonic=True lets us know there is monotonicity of policy rule, makes TD run faster - # .td requires at least one input 'shock', so we put in steady-state w - td_noshock = household.td(ss, w=np.full(T, ss['w']), monotonic=True) + # .impulse_nonlinear requires at least one input 'shock', so we put in steady-state w + td_noshock = household.impulse_nonlinear(ss, {'w': np.zeros(T)}) - for i in shock_list: + for i in exogenous: # simulate with respect to a shock at each date up to T for t in range(T): - td_out = household.td(ss, **{i: ss[i] + h * (np.arange(T) == t)}) + td_out = household.impulse_nonlinear(ss, {i: h * (np.arange(T) == t)}) # store results as column t of J[o][i] for each outcome o for o in output_list: - Js_direct[o.capitalize()][i][:, t] = (td_out[o.capitalize()] - td_noshock[o.capitalize()]) / h + Js_direct[o.upper()][i][:, t] = (td_out[o.upper()] - td_noshock[o.upper()]) / h - assert np.linalg.norm(Js["C"]["r"] - Js_direct["C"]["r"], np.inf) < 3e-4 + assert np.linalg.norm(Js['C']['r'] - Js_direct['C']['r'], np.inf) < 3e-4 diff --git a/tests/base/test_jacobian_dict_block.py b/tests/base/test_jacobian_dict_block.py new file mode 100644 index 0000000..430cb97 --- /dev/null +++ b/tests/base/test_jacobian_dict_block.py @@ -0,0 +1,39 @@ +"""Test JacobianDictBlock functionality""" + +import numpy as np + +from sequence_jacobian import combine +from sequence_jacobian.examples import rbc +from sequence_jacobian.blocks.auxiliary_blocks.jacobiandict_block import JacobianDictBlock +from sequence_jacobian import SteadyStateDict + + +def test_jacobian_dict_block_impulses(rbc_dag): + rbc_model, ss, unknowns, _, exogenous = rbc_dag + + T = 10 + J_pe = rbc_model.jacobian(ss, inputs=unknowns + exogenous, T=10) + J_block = JacobianDictBlock(J_pe) + + J_block_Z = J_block.jacobian(SteadyStateDict({}), ["Z"]) + for o in J_block_Z.outputs: + assert np.all(J_block[o].get("Z") == J_block_Z[o].get("Z")) + + dZ = 0.8 ** np.arange(T) + + dO1 = J_block @ {"Z": dZ} + dO2 = J_block_Z @ {"Z": dZ} + + for k in J_block: + assert np.all(dO1[k] == dO2[k]) + + +def test_jacobian_dict_block_combine(rbc_dag): + _, ss, _, _, exogenous = rbc_dag + + J_firm = rbc.firm.jacobian(ss, inputs=exogenous) + blocks_w_jdict = [rbc.household, J_firm, rbc.mkt_clearing] + cblock_w_jdict = combine(blocks_w_jdict) + + # Using `combine` converts JacobianDicts to JacobianDictBlocks + assert isinstance(cblock_w_jdict.blocks[0], JacobianDictBlock) diff --git a/tests/base/test_multiexog.py b/tests/base/test_multiexog.py new file mode 100644 index 0000000..9e29e30 --- /dev/null +++ b/tests/base/test_multiexog.py @@ -0,0 +1,104 @@ +import numpy as np +import sequence_jacobian as sj +from sequence_jacobian import het, simple, combine + + +def household_init(a_grid, y, r, sigma): + c = np.maximum(1e-8, y[..., np.newaxis] + np.maximum(r, 0.04) * a_grid) + Va = (1 + r) * (c ** (-sigma)) + return Va + + +def search_frictions(f, s): + Pi_e = np.vstack(([1 - s, s], [f, 1 - f])) + return Pi_e + + +def labor_income(z, w, b): + y = np.vstack((w * z, b * w * z)) + return y + + +@simple +def income_state_vars(rho_z, sd_z, nZ): + z, _, Pi_z = sj.utilities.discretize.markov_rouwenhorst(rho=rho_z, sigma=sd_z, N=nZ) + return z, Pi_z + +@simple +def asset_state_vars(amin, amax, nA): + a_grid = sj.utilities.discretize.agrid(amin=amin, amax=amax, n=nA) + return a_grid + + +@het(exogenous=['Pi_e', 'Pi_z'], policy='a', backward='Va', backward_init=household_init) +def household_multidim(Va_p, a_grid, y, r, beta, sigma): + c_nextgrid = (beta * Va_p) ** (-1 / sigma) + coh = (1 + r) * a_grid + y[..., np.newaxis] + a = sj.utilities.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid) # (x, xq, y) + a = np.maximum(a, a_grid[0]) + c = coh - a + uc = c ** (-sigma) + Va = (1 + r) * uc + + return Va, a, c + +@het(exogenous='Pi', policy='a', backward='Va', backward_init=household_init) +def household_onedim(Va_p, a_grid, y, r, beta, sigma): + c_nextgrid = (beta * Va_p) ** (-1 / sigma) + coh = (1 + r) * a_grid[np.newaxis, :] + y[:, np.newaxis] + a = sj.utilities.interpolate.interpolate_y(c_nextgrid + a_grid, coh, a_grid) # (x, xq, y) + sj.utilities.optimized_routines.setmin(a, a_grid[0]) + c = coh - a + uc = c ** (-sigma) + Va = (1 + r) * uc + + return Va, a, c + +def test_equivalence(): + calibration = dict(beta=0.95, r=0.01, sigma=2, a_grid = sj.utilities.discretize.agrid(1000, 50)) + + e1, _, Pi1 = sj.utilities.discretize.markov_rouwenhorst(rho=0.7, sigma=0.7, N=3) + e2, _, Pi2 = sj.utilities.discretize.markov_rouwenhorst(rho=0.3, sigma=0.5, N=3) + e_multidim = np.outer(e1, e2) + + e_onedim = np.kron(e1, e2) + Pi = np.kron(Pi1, Pi2) + + ss_multidim = household_multidim.steady_state({**calibration, 'y': e_multidim, 'Pi_e': Pi1, 'Pi_z': Pi2}) + ss_onedim = household_onedim.steady_state({**calibration, 'y': e_onedim, 'Pi': Pi}) + + assert np.isclose(ss_multidim['A'], ss_onedim['A']) and np.isclose(ss_multidim['C'], ss_onedim['C']) + + D_onedim = ss_onedim.internals['household_onedim']['D'] + D_multidim = ss_multidim.internals['household_multidim']['D'] + + assert np.allclose(D_onedim, D_multidim.reshape(*D_onedim.shape)) + + J_multidim = household_multidim.jacobian(ss_multidim, inputs = ['r'], outputs=['A'], T=10) + J_onedim = household_onedim.jacobian(ss_onedim, inputs = ['r'], outputs=['A'], T=10) + + assert np.allclose(J_multidim['A','r'], J_onedim['A','r']) + + +def test_pishock(): + calibration = dict(beta=0.95, r=0.01, sigma=2., f=0.4, s=0.1, w=1., b=0.5, + rho_z=0.9, sd_z=0.5, nZ=3, amin=0., amax=1000, nA=50) + + household = household_multidim.add_hetinputs([search_frictions, labor_income]) + hh = combine([household, income_state_vars, asset_state_vars]) + + ss = hh.steady_state(calibration) + + J = hh.jacobian(ss, inputs=['f', 's', 'r'], outputs=['C'], T=10) + + assert np.max(np.triu(J['C']['r'], 1)) <= 0 # low C before hike in r + assert np.min(np.tril(J['C']['r'])) >= 0 # high C after hike in r + + assert np.all(J['C']['f'] > 0) # high f increases C everywhere + assert np.all(J['C']['s'] < 0) # high s decreases C everywhere + + shock = 0.8**np.arange(10) + C_up = hh.impulse_nonlinear(ss, {'f': 1E-4*shock})['C'] + C_dn = hh.impulse_nonlinear(ss, {'f': -1E-4*shock})['C'] + dC = (C_up - C_dn)/2E-4 + assert np.allclose(dC, J['C', 'f'] @ shock, atol=2E-6) diff --git a/tests/base/test_options.py b/tests/base/test_options.py new file mode 100644 index 0000000..7989d86 --- /dev/null +++ b/tests/base/test_options.py @@ -0,0 +1,50 @@ +import numpy as np +import pytest +from sequence_jacobian.examples import krusell_smith + +def test_jacobian_h(krusell_smith_dag): + _, ss, dag, *_ = krusell_smith_dag + hh = dag['household'] + + lowacc = hh.jacobian(ss, inputs=['r'], outputs=['C'], T=10, h=0.05) + midacc = hh.jacobian(ss, inputs=['r'], outputs=['C'], T=10, h=1E-3) + usual = hh.jacobian(ss, inputs=['r'], outputs=['C'], T=10, h=1E-4) + nooption = hh.jacobian(ss, inputs=['r'], outputs=['C'], T=10) + + assert np.array_equal(usual['C','r'], nooption['C','r']) + assert np.linalg.norm(usual['C','r'] - midacc['C','r']) < np.linalg.norm(usual['C','r'] - lowacc['C','r']) + + midacc_alt = hh.jacobian(ss, inputs=['r'], outputs=['C'], T=10, options={'household': {'h': 1E-3}}) + assert np.array_equal(midacc['C', 'r'], midacc_alt['C', 'r']) + + lowacc = dag.jacobian(ss, inputs=['K'], outputs=['C'], T=10, options={'household': {'h': 0.05}}) + midacc = dag.jacobian(ss, inputs=['K'], outputs=['C'], T=10, options={'household': {'h': 1E-3}}) + usual = dag.jacobian(ss, inputs=['K'], outputs=['C'], T=10, options={'household': {'h': 1E-4}}) + + assert np.linalg.norm(usual['C','K'] - midacc['C','K']) < np.linalg.norm(usual['C','K'] - lowacc['C','K']) + + +def test_jacobian_steady_state(krusell_smith_dag): + dag = krusell_smith_dag[2] + calibration = {"eis": 1, "delta": 0.025, "alpha": 0.11, "rho": 0.966, "sigma": 0.5, + "L": 1.0, "nS": 2, "nA": 10, "amax": 200, "r": 0.01, 'beta': 0.96, + "Z": 0.85, "K": 3.} + + pytest.raises(ValueError, dag.steady_state, calibration, options={'household': {'backward_maxit': 10}}) + + ss1 = dag.steady_state(calibration) + ss2 = dag.steady_state(calibration, options={'household': {'backward_maxit': 100000}}) + assert ss1['A'] == ss2['A'] + + +def test_steady_state_solution(krusell_smith_dag): + dag_ss, ss, *_ = krusell_smith_dag + + calibration = {'eis': 1.0, 'delta': 0.025, 'alpha': 0.11, 'rho': 0.966, 'sigma': 0.5, + 'Y': 1.0, 'L': 1.0, 'nS': 2, 'nA': 10, 'amax': 200, 'r': 0.01} + unknowns_ss = {'beta': (0.98 / 1.01, 0.999 / 1.01)} + targets_ss = {'asset_mkt': 0.} + + ss2 = dag_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, solver="brentq", + ttol=1E-2, ctol=1E-2) + assert not np.isclose(ss['asset_mkt'], ss2['asset_mkt']) diff --git a/tests/base/test_public_classes.py b/tests/base/test_public_classes.py new file mode 100644 index 0000000..55aeac3 --- /dev/null +++ b/tests/base/test_public_classes.py @@ -0,0 +1,52 @@ +"""Test public-facing classes""" + +import numpy as np +import pytest + +from sequence_jacobian import het +from sequence_jacobian.classes.steady_state_dict import SteadyStateDict +from sequence_jacobian.classes.impulse_dict import ImpulseDict +from sequence_jacobian.utilities.bijection import Bijection + +def test_impulsedict(krusell_smith_dag): + _, ss, ks_model, unknowns, targets, _ = krusell_smith_dag + T = 200 + + # Linearized impulse responses as deviations + ir_lin = ks_model.solve_impulse_linear(ss, unknowns, targets, inputs={'Z': 0.01 * 0.5**np.arange(T)}, outputs=['C', 'K', 'r']) + + # Get method + assert isinstance(ir_lin, ImpulseDict) + assert isinstance(ir_lin[['C']], ImpulseDict) + assert isinstance(ir_lin['C'], np.ndarray) + + # Merge method + temp = ir_lin[['C', 'K']] | ir_lin[['r']] + assert list(temp.keys()) == ['C', 'K', 'r'] + + # SS and scalar multiplication + dC1 = 100 * ir_lin['C'] / ss['C'] + dC2 = 100 * ir_lin[['C']] / ss + assert np.allclose(dC1, dC2['C']) + + +def test_bijection(): + # generate and invert + mymap = Bijection({'a': 'a1', 'b': 'b1'}) + mymapinv = mymap.inv + assert mymap['a'] == 'a1' and mymap['b'] == 'b1' + assert mymapinv['a1'] == 'a' and mymapinv['b1'] == 'b' + + # duplicate keys rejected + with pytest.raises(ValueError): + Bijection({'a': 'a1', 'b': 'a1'}) + + # composition with another bijection (flows backwards) + mymap2 = Bijection({'a1': 'a2'}) + assert (mymap2 @ mymap)['a'] == 'a2' + + # composition with SteadyStateDict + ss = SteadyStateDict({'a': 2.0, 'b': 1.0}) + ss_remapped = ss @ mymap + assert isinstance(ss_remapped, SteadyStateDict) + assert ss_remapped['a1'] == ss['a'] and ss_remapped['b1'] == ss['b'] diff --git a/tests/base/test_simple_block.py b/tests/base/test_simple_block.py index 35937ed..95155e7 100644 --- a/tests/base/test_simple_block.py +++ b/tests/base/test_simple_block.py @@ -1,7 +1,13 @@ -from sequence_jacobian import simple, utilities +"""Test SimpleBlock functionality""" +import copy + import numpy as np import pytest +from sequence_jacobian import simple +from sequence_jacobian.classes.steady_state_dict import SteadyStateDict + + @simple def F(K, L, Z, alpha): Y = Z * K(-1)**alpha * L**(1-alpha) @@ -24,35 +30,35 @@ def taylor(r, pi, phi): return i -@pytest.mark.parametrize("block,ss", [(F, (1, 1, 1, 0.5)), - (investment, (1, 1, 0.05, 1, 1, 1, 0.05, 2, 0.5)), - (taylor, (0.05, 0.01, 1.5))]) +@pytest.mark.parametrize("block,ss", [(F, SteadyStateDict({"K": 1, "L": 1, "Z": 1, "alpha": 0.5})), + (investment, SteadyStateDict({"Q": 1, "K": 1, "r": 0.05, "N": 1, "mc": 1, + "Z": 1, "delta": 0.05, "epsI": 2, "alpha": 0.5})), + (taylor, SteadyStateDict({"r": 0.05, "pi": 0.01, "phi": 1.5}))]) def test_block_consistency(block, ss): """Make sure ss, td, and jac methods are all consistent with each other. Requires that all inputs of simple block allow calculating Jacobians""" # get ss output - ss_results = dict(zip(block.output_list, utilities.misc.make_tuple(block.ss(*ss)))) + ss_results = block.steady_state(ss) # now if we put in constant inputs, td should give us the same! - ss = dict(zip(block.input_list, ss)) - td_results = block.td(ss, **{k: np.full(20, v) for k, v in ss.items()}) - for k, v in td_results.items(): - assert np.all(v == ss_results[k]) + td_results = block.impulse_nonlinear(ss_results, {k: np.zeros(20) for k in ss.keys()}) + for v in td_results.values(): + assert np.all(v == 0) # now get the Jacobian - J = block.jac(ss, shock_list=block.input_list) + J = block.jacobian(ss, inputs=block.inputs) # now perturb the steady state by small random vectors # and verify that the second-order numerical derivative implied by .td # is equivalent to what we get from jac h = 1E-5 - all_shocks = {i: np.random.rand(10) for i in block.input_list} - td_up = block.td(ss, **{i: ss[i] + h*shock for i, shock in all_shocks.items()}) - td_dn = block.td(ss, **{i: ss[i] - h*shock for i, shock in all_shocks.items()}) + all_shocks = {i: np.random.rand(10) for i in block.inputs} + td_up = block.impulse_nonlinear(ss_results, {i: h*shock for i, shock in all_shocks.items()}) + td_dn = block.impulse_nonlinear(ss_results, {i: -h*shock for i, shock in all_shocks.items()}) - linear_impulses = {o: (td_up[o] - td_dn[o])/(2*h) for o in td_up} - linear_impulses_from_jac = {o: sum(J[o][i] @ all_shocks[i] for i in all_shocks if i in J[o]) for o in td_up} + linear_impulses = {o: (td_up[o] - td_dn[o])/(2*h) for o in block.outputs} + linear_impulses_from_jac = {o: sum(J[o][i] @ all_shocks[i] for i in all_shocks if i in J[o]) for o in block.outputs} for o in linear_impulses: assert np.all(np.abs(linear_impulses[o] - linear_impulses_from_jac[o]) < 1E-5) diff --git a/tests/base/test_solved_block.py b/tests/base/test_solved_block.py new file mode 100644 index 0000000..71273f9 --- /dev/null +++ b/tests/base/test_solved_block.py @@ -0,0 +1,31 @@ +import numpy as np +from sequence_jacobian import simple, solved +from sequence_jacobian.classes.steady_state_dict import SteadyStateDict +from sequence_jacobian.classes.jacobian_dict import FactoredJacobianDict + + +@simple +def myblock(u, i): + res = 0.5 * i(1) - u**2 - u(1) + return res + + +@solved(unknowns={'u': (-10.0, 10.0)}, targets=['res'], solver='brentq') +def myblock_solved(u, i): + res = 0.5 * i(1) - u**2 - u(1) + return res + +def test_solved_block(): + ss = SteadyStateDict({'u': 5, 'i': 10, 'res': 0.0}) + + # Compute jacobian of myblock_solved from scratch + J1 = myblock_solved.jacobian(ss, inputs=['i'], T=20) + + # Compute jacobian of SolvedBlock using a pre-computed FactoredJacobian + J_u = myblock.jacobian(ss, inputs=['u'], T=20) # square jac of underlying simple block + J_factored = FactoredJacobianDict(J_u, T=20) + J_i = myblock.jacobian(ss, inputs=['i'], T=20) # jac of underlying simple block wrt inputs that are NOT unknowns + J2 = J_factored.compose(J_i) # obtain jac of unknown wrt to non-unknown inputs using factored jac + + assert np.allclose(J1['u']['i'], J2['u']['i']) + diff --git a/tests/base/test_steady_state.py b/tests/base/test_steady_state.py index 7131a5c..8fa19aa 100644 --- a/tests/base/test_steady_state.py +++ b/tests/base/test_steady_state.py @@ -2,37 +2,42 @@ import numpy as np -import sequence_jacobian as sj -from sequence_jacobian.models import rbc, krusell_smith, hank, two_asset +from sequence_jacobian.examples import rbc, krusell_smith, hank, two_asset -def test_rbc_steady_state(rbc_model): - _, _, _, _, ss = rbc_model - ss_ref = rbc.rbc_ss() - assert set(ss.keys()) == set(ss_ref.keys()) - for k in ss.keys(): - assert np.all(np.isclose(ss[k], ss_ref[k])) +# def test_rbc_steady_state(rbc_dag): +# _, ss, *_ = rbc_dag +# ss_ref = rbc.rbc_ss() +# assert set(ss.keys()) == set(ss_ref.keys()) +# for k in ss.keys(): +# assert np.all(np.isclose(ss[k], ss_ref[k])) -def test_ks_steady_state(krusell_smith_model): - _, _, _, _, ss = krusell_smith_model - ss_ref = krusell_smith.ks_ss(nS=2, nA=10, amax=200) - assert set(ss.keys()) == set(ss_ref.keys()) - for k in ss.keys(): - assert np.all(np.isclose(ss[k], ss_ref[k])) +# def test_ks_steady_state(krusell_smith_dag): +# _, ss, *_ = krusell_smith_dag +# ss_ref = krusell_smith.ks_ss(nS=2, nA=10, amax=200) +# assert set(ss.keys()) == set(ss_ref.keys()) +# for k in ss.keys(): +# assert np.all(np.isclose(ss[k], ss_ref[k])) -def test_hank_steady_state(one_asset_hank_model): - _, _, _, _, ss = one_asset_hank_model - ss_ref = hank.hank_ss(nS=2, nA=10, amax=150) - assert set(ss.keys()) == set(ss_ref.keys()) - for k in ss.keys(): - assert np.all(np.isclose(ss[k], ss_ref[k])) +# def test_hank_steady_state(one_asset_hank_dag): +# _, ss, *_ = one_asset_hank_dag +# ss_ref = hank.hank_ss(nS=2, nA=10, amax=150) +# assert set(ss.keys()) == set(ss_ref.keys()) +# for k in ss.keys(): +# assert np.all(np.isclose(ss[k], ss_ref[k])) -def test_two_asset_steady_state(two_asset_hank_model): - _, _, _, _, ss = two_asset_hank_model - ss_ref = two_asset.two_asset_ss(nZ=3, nB=10, nA=16, nK=4, verbose=False) - assert set(ss.keys()) == set(ss_ref.keys()) - for k in ss.keys(): - assert np.all(np.isclose(ss[k], ss_ref[k])) +# def test_two_asset_steady_state(two_asset_hank_dag): +# _, ss, *_ = two_asset_hank_dag +# ss_ref = two_asset.two_asset_ss(nZ=3, nB=10, nA=16, nK=4, verbose=False) +# assert set(ss.keys()) == set(ss_ref.keys()) +# for k in ss.keys(): +# assert np.all(np.isclose(ss[k], ss_ref[k])) + + +# def test_remap_steady_state(ks_remapped_dag): +# _, _, _, _, ss = ks_remapped_dag +# assert ss['beta_impatient'] < ss['beta_patient'] +# assert ss['A_impatient'] < ss['A_patient'] diff --git a/tests/base/test_transitional_dynamics.py b/tests/base/test_transitional_dynamics.py index df22b15..c7439c1 100644 --- a/tests/base/test_transitional_dynamics.py +++ b/tests/base/test_transitional_dynamics.py @@ -1,126 +1,124 @@ """Test all models' non-linear transitional dynamics computations""" import numpy as np -import copy -from sequence_jacobian import two_asset, nonlinear, jacobian -from sequence_jacobian import utilities as utils +from sequence_jacobian import combine +from sequence_jacobian.examples import two_asset +from sequence_jacobian.examples.hetblocks import household_twoasset as hh # TODO: Figure out a more robust way to check similarity of the linear and non-linear solution. # As of now just checking that the tolerance for difference (by infinity norm) is below a manually checked threshold - -def test_rbc_td(rbc_model): - blocks, exogenous, unknowns, targets, ss = rbc_model +def test_rbc_td(rbc_dag): + rbc_model, ss, unknowns, targets, exogenous = rbc_dag T, impact, rho, news = 30, 0.01, 0.8, 10 - G = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss) + G = rbc_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T) dZ = np.empty((T, 2)) dZ[:, 0] = impact * ss['Z'] * rho**np.arange(T) dZ[:, 1] = np.concatenate((np.zeros(news), dZ[:-news, 0])) dC = 100 * G['C']['Z'] @ dZ / ss['C'] - td_nonlin = nonlinear.td_solve(ss=ss, block_list=blocks, unknowns=unknowns, targets=targets, - Z=ss["Z"]+dZ[:, 0], verbose=False) - td_nonlin_news = nonlinear.td_solve(ss=ss, block_list=blocks, unknowns=unknowns, targets=targets, - Z=ss["Z"]+dZ[:, 1], verbose=False) - dC_nonlin = 100 * (td_nonlin['C'] / ss['C'] - 1) - dC_nonlin_news = 100 * (td_nonlin_news['C'] / ss['C'] - 1) + td_nonlin = rbc_model.solve_impulse_nonlinear(ss, unknowns, targets, inputs={"Z": dZ[:, 0]}, outputs=['C']) + td_nonlin_news = rbc_model.solve_impulse_nonlinear(ss, unknowns, targets, inputs={"Z": dZ[:, 1]}, outputs=['C']) + + dC_nonlin = 100 * td_nonlin['C'] / ss['C'] + dC_nonlin_news = 100 * td_nonlin_news['C'] / ss['C'] assert np.linalg.norm(dC[:, 0] - dC_nonlin, np.inf) < 3e-2 assert np.linalg.norm(dC[:, 1] - dC_nonlin_news, np.inf) < 7e-2 -def test_ks_td(krusell_smith_model): - blocks, exogenous, unknowns, targets, ss = krusell_smith_model +def test_ks_td(krusell_smith_dag): + _, ss, ks_model, unknowns, targets, exogenous = krusell_smith_dag T = 30 - G = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss) + G = ks_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T) for shock_size, tol in [(0.01, 7e-3), (0.1, 0.6)]: - Z = ss['Z'] + shock_size * 0.8 ** np.arange(T) + dZ = shock_size * 0.8 ** np.arange(T) - td_nonlin = nonlinear.td_solve(ss=ss, block_list=blocks, unknowns=unknowns, - targets=targets, monotonic=True, Z=Z, verbose=False) - dr_nonlin = 10000 * (td_nonlin['r'] - ss['r']) - dr_lin = 10000 * G['r']['Z'] @ (Z - ss['Z']) + td_nonlin = ks_model.solve_impulse_nonlinear(ss, unknowns, targets, {"Z": dZ}) + dr_nonlin = 10000 * td_nonlin['r'] + dr_lin = 10000 * G['r']['Z'] @ dZ assert np.linalg.norm(dr_nonlin - dr_lin, np.inf) < tol -def test_hank_td(one_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = one_asset_hank_model +def test_hank_td(one_asset_hank_dag): + _, ss, hank_model, unknowns, targets, exogenous = one_asset_hank_dag T = 30 - G = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss, save=True) + household = hank_model['household'] + J_ha = household.jacobian(ss=ss, T=T, inputs=['Div', 'Tax', 'r', 'w']) + G = hank_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T, Js={'household': J_ha}) rho_r, sig_r = 0.61, -0.01/4 drstar = sig_r * rho_r ** (np.arange(T)) - rstar = ss['r'] + drstar - - H_U = jacobian.get_H_U(blocks, unknowns, targets, T, ss, use_saved=True) - H_U_factored = utils.misc.factor(H_U) - td_nonlin = nonlinear.td_solve(ss, blocks, unknowns, targets, H_U_factored=H_U_factored, rstar=rstar, verbose=False) + td_nonlin = hank_model.solve_impulse_nonlinear(ss, unknowns, targets, {"rstar": drstar}, Js={'household': J_ha}) - dC_nonlin = 100 * (td_nonlin['C'] / ss['C'] - 1) + dC_nonlin = 100 * td_nonlin['C'] / ss['C'] dC_lin = 100 * G['C']['rstar'] @ drstar / ss['C'] assert np.linalg.norm(dC_nonlin - dC_lin, np.inf) < 3e-3 -def test_two_asset_td(two_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = two_asset_hank_model +# TODO: needs to compute Jacobian of hetoutput `Chi` +def test_two_asset_td(two_asset_hank_dag): + _, ss, two_asset_model, unknowns, targets, exogenous = two_asset_hank_dag T = 30 - G = jacobian.get_G(block_list=blocks, exogenous=exogenous, unknowns=unknowns, - targets=targets, T=T, ss=ss, save=True) + household = two_asset_model['household'] + J_ha = household.jacobian(ss=ss, T=T, inputs=['N', 'r', 'ra', 'rb', 'tax', 'w']) + G = two_asset_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T, Js={'household': J_ha}) for shock_size, tol in [(0.1, 3e-4), (1, 2e-2)]: - drstar = -0.0025 * 0.6 ** np.arange(T) - rstar = ss["r"] + shock_size * drstar + drstar = shock_size * -0.0025 * 0.6 ** np.arange(T) - td_nonlin = nonlinear.td_solve(ss, blocks, unknowns, targets, rstar=rstar, use_saved=True, verbose=False) + td_nonlin = two_asset_model.solve_impulse_nonlinear(ss, unknowns, targets, {"rstar": drstar}, + Js={'household': J_ha}) - dY_nonlin = 100 * (td_nonlin['Y'] - 1) - dY_lin = shock_size * 100 * G['Y']['rstar'] @ drstar + dY_nonlin = 100 * td_nonlin['Y'] + dY_lin = 100 * G['Y']['rstar'] @ drstar assert np.linalg.norm(dY_nonlin - dY_lin, np.inf) < tol -def test_two_asset_solved_v_simple_td(two_asset_hank_model): - blocks, exogenous, unknowns, targets, ss = two_asset_hank_model +def test_two_asset_solved_v_simple_td(two_asset_hank_dag): + _, ss, two_asset_model, unknowns, targets, exogenous = two_asset_hank_dag - household = copy.deepcopy(two_asset.household) - household.add_hetoutput(two_asset.adjustment_costs, verbose=False) - blocks_simple = [household, two_asset.make_grids, - two_asset.pricing, two_asset.arbitrage, two_asset.labor, two_asset.investment, - two_asset.dividend, two_asset.taylor, two_asset.fiscal, - two_asset.finance, two_asset.wage, two_asset.union, two_asset.mkt_clearing, - two_asset.partial_steady_state_solution] + household = hh.household.add_hetinputs([two_asset.income, two_asset.make_grids]) + blocks_simple = [household, two_asset.pricing, two_asset.arbitrage, + two_asset.labor, two_asset.investment, two_asset.dividend, + two_asset.taylor, two_asset.fiscal, two_asset.share_value, + two_asset.finance, two_asset.wage, two_asset.union, + two_asset.mkt_clearing] + two_asset_model_simple = combine(blocks_simple, name="Two-Asset HANK w/ SimpleBlocks") unknowns_simple = ["r", "w", "Y", "pi", "p", "Q", "K"] targets_simple = ["asset_mkt", "fisher", "wnkpc", "nkpc", "equity", "inv", "val"] T = 30 - G = jacobian.get_G(blocks, exogenous, unknowns, targets, T, ss=ss, save=True) - G_simple = jacobian.get_G(blocks_simple, exogenous, unknowns_simple, targets_simple, T, ss=ss, save=True) + household = two_asset_model['household'] + J_ha = household.jacobian(ss=ss, T=T, inputs=['N', 'r', 'ra', 'rb', 'tax', 'w']) + G = two_asset_model.solve_jacobian(ss, unknowns, targets, exogenous, T=T, Js={'household': J_ha}) + G_simple = two_asset_model_simple.solve_jacobian(ss, unknowns_simple, targets_simple, exogenous, T=T, + Js={'household': J_ha}) drstar = -0.0025 * 0.6 ** np.arange(T) dY = 100 * G['Y']['rstar'] @ drstar - td_nonlin = nonlinear.td_solve(ss, blocks, unknowns, targets, - rstar=ss['r']+drstar, use_saved=True, verbose=False) + td_nonlin = two_asset_model.solve_impulse_nonlinear(ss, unknowns, targets, {"rstar": drstar}, + Js={'household': J_ha}) dY_nonlin = 100 * (td_nonlin['Y'] - 1) dY_simple = 100 * G_simple['Y']['rstar'] @ drstar - td_nonlin_simple = nonlinear.td_solve(ss, blocks_simple, unknowns_simple, targets_simple, - rstar=ss['r']+drstar, use_saved=True, verbose=False) + td_nonlin_simple = two_asset_model_simple.solve_impulse_nonlinear(ss, + unknowns_simple, targets_simple, + {"rstar": drstar}, Js={'household': J_ha}) dY_nonlin_simple = 100 * (td_nonlin_simple['Y'] - 1) assert np.linalg.norm(dY_nonlin - dY_nonlin_simple, np.inf) < 2e-7 - assert np.linalg.norm(dY - dY_simple, np.inf) < 0.02 \ No newline at end of file + assert np.linalg.norm(dY - dY_simple, np.inf) < 0.02 diff --git a/tests/base/test_two_asset.py b/tests/base/test_two_asset.py index ca99897..0c512d8 100644 --- a/tests/base/test_two_asset.py +++ b/tests/base/test_two_asset.py @@ -2,21 +2,20 @@ import numpy as np -from sequence_jacobian.models import two_asset +from sequence_jacobian.examples.hetblocks import household_twoasset as hh from sequence_jacobian import utilities as utils def test_hank_ss(): - A, B, U = hank_ss_singlerun() + A, B, UCE = hank_ss_singlerun() assert np.isclose(A, 12.526539492650361) assert np.isclose(B, 1.0840860793350566) - assert np.isclose(U, 4.5102870939550055) + assert np.isclose(UCE, 4.5102870939550055) -def hank_ss_singlerun(beta=0.976, vphi=2.07, r=0.0125, tot_wealth=14, K=10, delta=0.02, kappap=0.1, - muw=1.1, Bh=1.04, Bg=2.8, G=0.2, eis=0.5, frisch=1, chi0=0.25, chi1=6.5, chi2=2, - epsI=4, omega=0.005, kappaw=0.1, phi=1.5, nZ=3, nB=50, nA=70, nK=50, - bmax=50, amax=4000, kmax=1, rho_z=0.966, sigma_z=0.92, verbose=True): +def hank_ss_singlerun(beta=0.976, r=0.0125, tot_wealth=14, K=10, delta=0.02, Bg=2.8, G=0.2, + eis=0.5, chi0=0.25, chi1=6.5, chi2=2, omega=0.005, nZ=3, nB=50, + nA=70, nK=50, bmax=50, amax=4000, kmax=1, rho_z=0.966, sigma_z=0.92): """Mostly cribbed from two_asset.hank_ss(), but just does backward iteration to get a partial equilibrium household steady state given parameters, not solving for equilibrium. Convenient for testing.""" @@ -25,27 +24,26 @@ def hank_ss_singlerun(beta=0.976, vphi=2.07, r=0.0125, tot_wealth=14, K=10, delt b_grid = utils.discretize.agrid(amax=bmax, n=nB) a_grid = utils.discretize.agrid(amax=amax, n=nA) k_grid = utils.discretize.agrid(amax=kmax, n=nK)[::-1].copy() - e_grid, pi, Pi = utils.discretize.markov_rouwenhorst(rho=rho_z, sigma=sigma_z, N=nZ) + e_grid, _, Pi = utils.discretize.markov_rouwenhorst(rho=rho_z, sigma=sigma_z, N=nZ) # solve analytically what we can - I = delta * K mc = 1 - r * (tot_wealth - Bg - K) alpha = (r + delta) * K / mc w = (1 - alpha) * mc tax = (r * Bg + G) / w ra = r rb = r - omega + z_grid = (1 - tax) * w * e_grid # figure out initializer - z_grid = two_asset.income(e_grid, tax, w, 1) - Va = (0.6 + 1.1 * b_grid[:, np.newaxis] + a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) - Vb = (0.5 + b_grid[:, np.newaxis] + 1.2 * a_grid) ** (-1 / eis) * np.ones((z_grid.shape[0], 1, 1)) + calibration = {'Pi': Pi, 'a_grid': a_grid, 'b_grid': b_grid, 'e_grid': e_grid, + 'z_grid': z_grid, 'k_grid': k_grid, 'beta': beta, 'N': 1.0, + 'tax': tax, 'w': w, 'eis': eis, 'rb': rb, 'ra': ra, + 'chi0': chi0, 'chi1': chi1, 'chi2': chi2} - out = two_asset.household.ss(Va=Va, Vb=Vb, Pi=Pi, a_grid=a_grid, b_grid=b_grid, - N=1, tax=tax, w=w, e_grid=e_grid, k_grid=k_grid, beta=beta, - eis=eis, rb=rb, ra=ra, chi0=chi0, chi1=chi1, chi2=chi2) + out = hh.household.steady_state(calibration) - return out['A'], out['B'], out['U'] + return out['A'], out['B'], out['UCE'] def test_Psi(): @@ -56,7 +54,7 @@ def test_Psi(): a = np.random.rand(50) + 1 ap = np.random.rand(50) + 1 - oPsi, oPsi1, oPsi2 = two_asset.get_Psi_and_deriv(ap, a, ra, chi0, chi1, chi2) + oPsi, oPsi1, oPsi2 = hh.get_Psi_and_deriv(ap, a, ra, chi0, chi1, chi2) Psi = Psi_correct(ap, a, ra, chi0, chi1, chi2) assert np.allclose(oPsi, Psi) diff --git a/tests/base/test_workflow.py b/tests/base/test_workflow.py new file mode 100644 index 0000000..fd042bb --- /dev/null +++ b/tests/base/test_workflow.py @@ -0,0 +1,166 @@ +import numpy as np +from sequence_jacobian import simple, solved, create_model, markov_rouwenhorst, agrid +from sequence_jacobian.classes.impulse_dict import ImpulseDict +from sequence_jacobian.examples.hetblocks import household_sim as hh + + +'''Part 1: Household block''' + +def make_grids(rho_e, sd_e, nE, amin, amax, nA): + e_grid, e_dist, Pi = markov_rouwenhorst(rho=rho_e, sigma=sd_e, N=nE) + a_grid = agrid(amin=amin, amax=amax, n=nA) + return e_grid, e_dist, Pi, a_grid + + +def income(atw, N, e_grid, transfer): + y = atw * N * e_grid + transfer + return y + + +def get_mpcs(c, a, a_grid, r): + mpcs_ = np.empty_like(c) + post_return = (1 + r) * a_grid + mpcs_[:, 1:-1] = (c[:, 2:] - c[:, 0:-2]) / (post_return[2:] - post_return[:-2]) + mpcs_[:, 0] = (c[:, 1] - c[:, 0]) / (post_return[1] - post_return[0]) + mpcs_[:, -1] = (c[:, -1] - c[:, -2]) / (post_return[-1] - post_return[-2]) + mpcs_[a == a_grid[0]] = 1 + return mpcs_ + + +def mpcs(c, a, a_grid, r): + mpc = get_mpcs(c, a, a_grid, r) + return mpc + + +def weighted_uc(c, e_grid, eis): + uce = c ** (-1 / eis) * e_grid[:, np.newaxis] + return uce + + +'''Part 2: rest of the model''' + +@solved(unknowns={'C': 1.0, 'A': 1.0}, targets=['euler', 'budget_constraint'], solver='broyden_custom') +def household_ra(C, A, r, atw, N, transfer, beta, eis): + euler = beta * (1 + r(1)) * C(1) ** (-1 / eis) - C ** (-1 / eis) + budget_constraint = (1 + r) * A(-1) + atw * N + transfer - C - A + UCE = C ** (-1 / eis) + return euler, budget_constraint, UCE + + +@simple +def firm(N, Z): + Y = Z * N + w = Z + return Y, w + + +@simple +def union(UCE, tau, w, N, pi, muw, kappaw, nu, vphi, beta): + wnkpc = kappaw * N * (vphi * N ** nu - (1 - tau) * w * UCE / muw) + \ + beta * (1 + pi(+1)).apply(np.log) - (1 + pi).apply(np.log) + return wnkpc + + +@solved(unknowns={'B': (0.0, 10.0)}, targets=['B_rule'], solver='brentq') +def fiscal(B, G, r, w, N, transfer, rho_B): + B_rule = B.ss + rho_B * (B(-1) - B.ss + G - G.ss) - B + rev = (1 + r) * B(-1) + G + transfer - B # revenue to be raised + tau = rev / (w * N) + atw = (1 - tau) * w + return B_rule, rev, tau, atw + + +# Use this to test zero impulse once we have it +# @simple +# def real_bonds(r): +# rb = r +# return rb + + +@simple +def mkt_clearing(A, B, C, G, Y): + asset_mkt = A - B + goods_mkt = C + G - Y + return asset_mkt, goods_mkt + + +'''Part 3: Helper blocks''' + +@simple +def household_ra_ss(r, B, tau, w, N, transfer, eis): + beta = 1 / (1 + r) + A = B + C = r * A + (1 - tau) * w * N + transfer + UCE = C ** (-1 / eis) + return beta, A, C, UCE + + +@simple +def union_ss(atw, UCE, muw, N, nu, kappaw, beta, pi): + vphi = atw * UCE / (muw * N ** nu) + wnkpc = kappaw * N * (vphi * N ** nu - atw * UCE / muw) + \ + beta * (1 + pi(+1)).apply(np.log) - (1 + pi).apply(np.log) + return wnkpc, vphi + + +'''Tests''' + +def test_all(): + # Assemble HA block (want to test nesting) + household_ha = hh.household.add_hetinputs([make_grids, income]) + household_ha = household_ha.add_hetoutputs([mpcs, weighted_uc]).rename('household_ha') + + # Assemble DAG (for transition dynamics) + dag = {} + common_blocks = [firm, union, fiscal, mkt_clearing] + dag['ha'] = create_model([household_ha] + common_blocks, name='HANK') + dag['ra'] = create_model([household_ra] + common_blocks, name='RANK') + unknowns = ['N', 'pi'] + targets = ['asset_mkt', 'wnkpc'] + + # Solve steady state + calibration = {'N': 1.0, 'Z': 1.0, 'r': 0.005, 'pi': 0.0, 'eis': 0.5, 'nu': 0.5, + 'rho_e': 0.91, 'sd_e': 0.92, 'nE': 3, 'amin': 0.0, 'amax': 200, + 'nA': 100, 'kappaw': 0.1, 'muw': 1.2, 'transfer': 0.143, 'rho_B': 0.9} + + ss = {} + # Constructing ss-dag manually works just fine + dag_ss = {} + dag_ss['ha'] = create_model([household_ha, union_ss, firm, fiscal, mkt_clearing]) + ss['ha'] = dag_ss['ha'].solve_steady_state(calibration, dissolve=['fiscal'], solver='hybr', + unknowns={'beta': 0.96, 'B': 3.0, 'G': 0.2}, + targets={'asset_mkt': 0.0, 'MPC': 0.25, 'tau': 0.334}) + assert np.isclose(ss['ha']['goods_mkt'], 0.0) + assert np.isclose(ss['ha']['asset_mkt'], 0.0) + assert np.isclose(ss['ha']['wnkpc'], 0.0) + + dag_ss['ra'] = create_model([household_ra_ss, union_ss, firm, fiscal, mkt_clearing]) + ss['ra'] = dag_ss['ra'].steady_state(ss['ha'], dissolve=['fiscal']) + assert np.isclose(ss['ra']['goods_mkt'], 0.0) + assert np.isclose(ss['ra']['asset_mkt'], 0.0) + assert np.isclose(ss['ra']['wnkpc'], 0.0) + + # Precompute HA Jacobian + Js = {'ra': {}, 'ha': {}} + Js['ha']['household_ha'] = household_ha.jacobian(ss['ha'], + inputs=['N', 'atw', 'r', 'transfer'], outputs=['C', 'A', 'UCE'], T=300) + + # Linear impulse responses from Jacobian vs directly + shock = ImpulseDict({'G': 0.9 ** np.arange(300)}) + G, td_lin1, td_lin2 = dict(), dict(), dict() + for k in ['ra', 'ha']: + G[k] = dag[k].solve_jacobian(ss[k], unknowns, targets, inputs=['G'], T=300, Js=Js[k]) + td_lin1[k] = G[k] @ shock + td_lin2[k] = dag[k].solve_impulse_linear(ss[k], unknowns, targets, shock, Js=Js[k]) + assert all(np.allclose(td_lin1[k][i], td_lin2[k][i]) for i in td_lin1[k]) + + # Nonlinear vs linear impulses + td_nonlin = dag['ha'].solve_impulse_nonlinear(ss['ha'], unknowns, targets, inputs=shock*1E-2, + Js=Js, internals=['household_ha']) + assert np.max(np.abs(td_nonlin['goods_mkt'])) < 1E-8 + + # See if D change matches up with aggregate assets + td_nonlin_lvl = td_nonlin + ss['ha'] + td_A = np.sum(td_nonlin_lvl.internals['household_ha']['a'] * td_nonlin_lvl.internals['household_ha']['D'], axis=(1, 2)) + assert np.allclose(td_A - ss['ha']['A'], td_nonlin['A']) + \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index e4ff791..9e0de9f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,97 +1,30 @@ """Fixtures used by tests.""" import pytest -import copy -from sequence_jacobian import steady_state -from sequence_jacobian.models import rbc, krusell_smith, hank, two_asset +from sequence_jacobian.examples import rbc, krusell_smith, hank, two_asset @pytest.fixture(scope='session') -def rbc_model(): - blocks = [rbc.household, rbc.mkt_clearing, rbc.firm, rbc.steady_state_solution] - - # Steady State - calibration = {"eis": 1, "delta": 0.025, "alpha": 0.11, "frisch": 1., "L": 1.0, "r": 0.01} - ss_unknowns = {"beta": None, "vphi": None} - ss_targets = {"goods_mkt": 0, "euler": 0} - ss = steady_state(blocks, calibration, ss_unknowns, ss_targets, solver="solved", consistency_check=True) - - # Transitional Dynamics/Jacobian Calculation - exogenous = ["Z"] - dynamic_unknowns = ["K", "L"] - dynamic_targets = ["goods_mkt", "euler"] - - return blocks, exogenous, dynamic_unknowns, dynamic_targets, ss +def rbc_dag(): + return rbc.dag() @pytest.fixture(scope='session') -def krusell_smith_model(): - blocks = [krusell_smith.household, krusell_smith.firm, krusell_smith.mkt_clearing, krusell_smith.income_state_vars, - krusell_smith.asset_state_vars, krusell_smith.firm_steady_state_solution] - - # Steady State - calibration = {"eis": 1, "delta": 0.025, "alpha": 0.11, "rho": 0.966, "sigma": 0.5, "L": 1.0, - "nS": 2, "nA": 10, "amax": 200, "r": 0.01} - ss_unknowns = {"beta": (0.98/1.01, 0.999/1.01)} - ss_targets = {"K": "A"} - ss = steady_state(blocks, calibration, ss_unknowns, ss_targets, - solver="brentq", consistency_check=True) - - # Transitional Dynamics/Jacobian Calculation - exogenous = ["Z"] - dynamic_unknowns = ["K"] - dynamic_targets = ["asset_mkt"] - - return blocks, exogenous, dynamic_unknowns, dynamic_targets, ss +def krusell_smith_dag(): + return krusell_smith.dag() @pytest.fixture(scope='session') -def one_asset_hank_model(): - blocks = [hank.household, hank.firm, hank.monetary, hank.fiscal, hank.mkt_clearing, hank.nkpc, - hank.income_state_vars, hank.asset_state_vars, hank.partial_steady_state_solution] - - # Steady State - calibration = {"r": 0.005, "rstar": 0.005, "eis": 0.5, "frisch": 0.5, "mu": 1.2, "B_Y": 5.6, - "rho_s": 0.966, "sigma_s": 0.5, "kappa": 0.1, "phi": 1.5, "Y": 1, "Z": 1, "L": 1, - "pi": 0, "nS": 2, "amax": 150, "nA": 10} - ss_unknowns = {"beta": 0.986, "vphi": 0.8} - ss_targets = {"asset_mkt": 0, "labor_mkt": 0} - ss = steady_state(blocks, calibration, ss_unknowns, ss_targets, - solver="broyden_custom", consistency_check=True, verbose=False) - - # Transitional Dynamics/Jacobian Calculation - exogenous= ["rstar", "Z"] - dynamic_unknowns = ["pi", "w", "Y"] - dynamic_targets = ["nkpc_res", "asset_mkt", "labor_mkt"] - - return blocks, exogenous, dynamic_unknowns, dynamic_targets, ss +def one_asset_hank_dag(): + return hank.dag() @pytest.fixture(scope='session') -def two_asset_hank_model(): - household = copy.deepcopy(two_asset.household) - household.add_hetoutput(two_asset.adjustment_costs, verbose=False) - blocks = [household, two_asset.make_grids, - two_asset.pricing_solved, two_asset.arbitrage_solved, two_asset.production_solved, - two_asset.dividend, two_asset.taylor, two_asset.fiscal, - two_asset.finance, two_asset.wage, two_asset.union, two_asset.mkt_clearing, - two_asset.partial_steady_state_solution] +def two_asset_hank_dag(): + return two_asset.dag() - # Steady State - calibration = {"pi": 0, "piw": 0, "Q": 1, "Y": 1, "N": 1, "r": 0.0125, "rstar": 0.0125, "i": 0.0125, - "tot_wealth": 14, "K": 10, "delta": 0.02, "kappap": 0.1, "muw": 1.1, "Bh": 1.04, - "Bg": 2.8, "G": 0.2, "eis": 0.5, "frisch": 1, "chi0": 0.25, "chi2": 2, "epsI": 4, - "omega": 0.005, "kappaw": 0.1, "phi": 1.5, "nZ": 3, "nB": 10, "nA": 16, "nK": 4, - "bmax": 50, "amax": 4000, "kmax": 1, "rho_z": 0.966, "sigma_z": 0.92} - ss_unknowns = {"beta": 0.976, "vphi": 2.07, "chi1": 6.5} - ss_targets = {"asset_mkt": 0, "labor_mkt": 0, "B": "Bh"} - ss = steady_state(blocks, calibration, ss_unknowns, ss_targets, - solver="broyden_custom", consistency_check=True, verbose=False) - # Transitional Dynamics/Jacobian Calculation - exogenous = ["rstar", "Z", "G"] - dynamic_unknowns = ["r", "w", "Y"] - dynamic_targets = ["asset_mkt", "fisher", "wnkpc"] - - return blocks, exogenous, dynamic_unknowns, dynamic_targets, ss +@pytest.fixture(scope='session') +def ks_remapped_dag(): + return krusell_smith.remapped_dag() diff --git a/tests/robustness/test_steady_state.py b/tests/robustness/test_steady_state.py index 37a662e..b2b5d85 100644 --- a/tests/robustness/test_steady_state.py +++ b/tests/robustness/test_steady_state.py @@ -1,39 +1,41 @@ +"""Tests for steady_state with worse initial guesses, making use of the constrained solution functionality""" + import pytest import numpy as np -import sequence_jacobian as sj - # Filter out warnings when the solver is trying to search in bad regions @pytest.mark.filterwarnings("ignore:.*invalid value encountered in.*:RuntimeWarning") -def test_hank_steady_state_w_bad_init_guesses_and_bounds(one_asset_hank_model): - blocks, _, _, _, ss = one_asset_hank_model +def test_hank_steady_state_w_bad_init_guesses_and_bounds(one_asset_hank_dag): + dag_ss, ss, dag, *_ = one_asset_hank_dag - calibration = {"r": 0.005, "rstar": 0.005, "eis": 0.5, "frisch": 0.5, "mu": 1.2, "B_Y": 5.6, + calibration = {"r": 0.005, "rstar": 0.005, "eis": 0.5, "frisch": 0.5, "B": 5.6, "mu": 1.2, "rho_s": 0.966, "sigma_s": 0.5, "kappa": 0.1, "phi": 1.5, "Y": 1, "Z": 1, "L": 1, "pi": 0, "nS": 2, "amax": 150, "nA": 10} - unknowns = {"beta": (0.95, 0.97, 0.999/(1 + 0.005)), "vphi": (0.001, 1.0, 10)} - targets = {"asset_mkt": 0, "labor_mkt": 0} - ss_ref = sj.steady_state(blocks, calibration, unknowns, targets, - solver="broyden1", consistency_check=True, verbose=False, - solver_kwargs={"options": {"maxiter": 250}}, - constrained_kwargs={"boundary_epsilon": 5e-3, "penalty_scale": 100}) + unknowns_ss = {"beta": (0.95, 0.97, 0.999 / (1 + 0.005)), "vphi": (0.001, 1.0, 10.)} + targets_ss = {"asset_mkt": 0, "labor_mkt": 0} + cali = dag_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, solver="hybr", + constrained_kwargs={"boundary_epsilon": 5e-3, "penalty_scale": 100}) + ss_ref = dag.steady_state(cali) + for k in ss.keys(): assert np.all(np.isclose(ss[k], ss_ref[k])) @pytest.mark.filterwarnings("ignore:.*invalid value encountered in.*:RuntimeWarning") -def test_two_asset_steady_state_w_bad_init_guesses_and_bounds(two_asset_hank_model): - blocks, _, _, _, ss = two_asset_hank_model - - calibration = {"pi": 0, "piw": 0, "Q": 1, "Y": 1, "N": 1, "r": 0.0125, "rstar": 0.0125, "i": 0.0125, - "tot_wealth": 14, "K": 10, "delta": 0.02, "kappap": 0.1, "muw": 1.1, "Bh": 1.04, - "Bg": 2.8, "G": 0.2, "eis": 0.5, "frisch": 1, "chi0": 0.25, "chi2": 2, "epsI": 4, - "omega": 0.005, "kappaw": 0.1, "phi": 1.5, "nZ": 3, "nB": 10, "nA": 16, "nK": 4, - "bmax": 50, "amax": 4000, "kmax": 1, "rho_z": 0.966, "sigma_z": 0.92} - unknowns = {"beta": (0.5, 0.9, 0.999 / (1 + 0.0125)), "vphi": (0.001, 1.0, 10.), "chi1": (0.5, 5.5, 10.)} - targets = {"asset_mkt": 0, "labor_mkt": 0, "B": "Bh"} - ss_ref = sj.steady_state(blocks, calibration, unknowns, targets, - solver="broyden_custom", consistency_check=True, verbose=False) +def test_two_asset_steady_state_w_bad_init_guesses_and_bounds(two_asset_hank_dag): + dag_ss, ss, dag, *_ = two_asset_hank_dag + + # Steady State + calibration = {"Y": 1., "r": 0.0125, "rstar": 0.0125, "tot_wealth": 14, "delta": 0.02, + "kappap": 0.1, "muw": 1.1, 'N': 1.0, 'K': 10., 'pi': 0.0, + "Bh": 1.04, "Bg": 2.8, "G": 0.2, "eis": 0.5, "frisch": 1, "chi0": 0.25, "chi2": 2, + "epsI": 4, "omega": 0.005, "kappaw": 0.1, "phi": 1.5, "nZ": 3, "nB": 10, "nA": 16, + "nK": 4, "bmax": 50, "amax": 4000, "kmax": 1, "rho_z": 0.966, "sigma_z": 0.92} + unknowns_ss = {"beta": 0.976, "chi1": 6.5} + targets_ss = {"asset_mkt": 0., "B": "Bh"} + cali = dag_ss.solve_steady_state(calibration, unknowns_ss, targets_ss, + solver="broyden_custom") + ss_ref = dag.steady_state(cali) for k in ss.keys(): assert np.all(np.isclose(ss[k], ss_ref[k])) diff --git a/tests/utils/test_DAG.py b/tests/utils/test_DAG.py new file mode 100644 index 0000000..8a8bbb0 --- /dev/null +++ b/tests/utils/test_DAG.py @@ -0,0 +1,41 @@ +from sequence_jacobian.utilities.graph import DAG +from sequence_jacobian.utilities.ordered_set import OrderedSet + + +class Block: + def __init__(self, inputs, outputs): + self.inputs = OrderedSet(inputs) + self.outputs = OrderedSet(outputs) + + +test_dag = DAG([Block(inputs=['a', 'b', 'z'], outputs=['c', 'd']), + Block(inputs=['a', 'e'], outputs=['b']), + Block(inputs = ['d'], outputs=['f'])]) + + +def test_dag_constructor(): + # the blocks should be ordered 1, 0, 2 + assert list(test_dag.blocks[0].inputs) == ['a', 'e'] + assert list(test_dag.blocks[1].inputs) == ['a', 'b', 'z'] + assert list(test_dag.blocks[2].inputs) == ['d'] + + assert set(test_dag.inmap['a']) == {0, 1} + assert set(test_dag.inmap['b']) == {1} + + assert test_dag.outmap['c'] == 1 + assert test_dag.outmap['f'] == 2 + assert test_dag.outmap['d'] == 1 + + assert set(test_dag.adj[0]) == {1} + assert set(test_dag.adj[1]) == {2} + assert set(test_dag.revadj[2]) == {1} + assert set(test_dag.revadj[1]) == {0} + + +def test_visited(): + test_dag.visit_from_outputs(['f']) == OrderedSet([0, 1, 2]) + test_dag.visit_from_outputs(['b']) == OrderedSet([0]) + test_dag.visit_from_outputs(['d']) == OrderedSet([0, 1]) + + test_dag.visit_from_inputs(['e']) == OrderedSet([0, 1, 2]) + test_dag.visit_from_inputs(['z']) == OrderedSet([1, 2]) diff --git a/tests/utils/test_function.py b/tests/utils/test_function.py new file mode 100644 index 0000000..b1afb58 --- /dev/null +++ b/tests/utils/test_function.py @@ -0,0 +1,79 @@ +from sequence_jacobian.utilities.ordered_set import OrderedSet +from sequence_jacobian.utilities.function import (DifferentiableExtendedFunction, ExtendedFunction, + CombinedExtendedFunction, metadata) +import numpy as np + +def f1(a, b, c): + k = a + 1 + l = b - c + return k, l + +def f2(b): + k = b + 4 + return k + + +def test_metadata(): + assert metadata(f1) == ('f1', OrderedSet(['a', 'b', 'c']), OrderedSet(['k', 'l'])) + assert metadata(f2) == ('f2', OrderedSet(['b']), OrderedSet(['k'])) + + +def test_extended_function(): + inputs = {'a': 1, 'b': 2, 'c': 3} + assert ExtendedFunction(f1)(inputs) == {'k': 2, 'l': -1} + assert ExtendedFunction(f2)(inputs) == {'k': 6} + + +def f3(a, b): + c = a*b - 5*a + d = 3*b**2 + return c, d + + +def test_differentiable_extended_function(): + extf3 = ExtendedFunction(f3) + + ss1 = {'a': 1, 'b': 2} + inputs1 = {'a': 0.5} + + diff = extf3.differentiable(ss1).diff(inputs1) + assert np.isclose(diff['c'], -1.5) + assert np.isclose(diff['d'], 0) + + +def f4(a, c, e): + f = a / c + a * e - c + return f + + +def test_differentiable_combined_extended_function(): + # swapping in combined extended function to see if it works! + fs = CombinedExtendedFunction([f3, f4]) + + ss1 = {'a': 1, 'b': 2, 'e': 4} + ss1.update(fs(ss1)) + + inputs1 = {'a': 0.5, 'e': 1} + + diff = fs.differentiable(ss1).diff(inputs1) + assert np.isclose(diff['c'], -1.5) + assert np.isclose(diff['d'], 0) + assert np.isclose(diff['f'], 4.5) + + # test narrowing down outputs + diff = fs.differentiable(ss1).diff(inputs1, outputs=['c','d']) + assert np.isclose(diff['c'], -1.5) + assert np.isclose(diff['d'], 0) + assert list(diff) == ['c', 'd'] + + # if no shocks to first function, hide first function + inputs2 = {'e': -2} + diff = fs.differentiable(ss1).diff2(inputs2) + assert list(diff) == ['f'] + assert np.isclose(diff['f'], -2) + + # if we ask for output from first function but no inputs shocked, shouldn't be there! + diff = fs.differentiable(ss1).diff(inputs2, outputs=['c', 'f']) + assert list(diff) == ['f'] + assert np.isclose(diff['f'], -2) + diff --git a/tests/utils/test_multidim.py b/tests/utils/test_multidim.py new file mode 100644 index 0000000..8b87ada --- /dev/null +++ b/tests/utils/test_multidim.py @@ -0,0 +1,19 @@ +from sequence_jacobian.utilities.multidim import outer +import numpy as np + +def test_2d(): + a = np.random.rand(10) + b = np.random.rand(12) + assert np.allclose(np.outer(a,b), outer([a,b])) + +def test_3d(): + a = np.array([1., 2]) + b = np.array([1., 7]) + small = np.outer(a, b) + + c = np.array([2., 4]) + product = np.empty((2,2,2)) + product[..., 0] = 2*small + product[..., 1] = 4*small + + assert np.array_equal(product, outer([a,b,c])) diff --git a/tests/utils/test_ordered_set.py b/tests/utils/test_ordered_set.py new file mode 100644 index 0000000..1de3ea8 --- /dev/null +++ b/tests/utils/test_ordered_set.py @@ -0,0 +1,57 @@ +from sequence_jacobian.utilities.ordered_set import OrderedSet + +def test_ordered_set(): + # order matters + assert OrderedSet([1,2,3]) != OrderedSet([3,2,1]) + + # first insertion determines order + assert OrderedSet([5,1,6,5]) == OrderedSet([5,1,6]) + + # union preserves first and second order + assert (OrderedSet([6,1,3]) | OrderedSet([3,1,7,9])) == OrderedSet([6,1,3,7,9]) + + # intersection preserves first order + assert (OrderedSet([6,1,3]) & OrderedSet([3,1,7])) == OrderedSet([1,3]) + + # difference works + assert (OrderedSet([6,1,3,2]) - OrderedSet([3,1,7])) == OrderedSet([6,2]) + + # symmetric difference: first then second + assert (OrderedSet([6,1,3,8]) ^ OrderedSet([3,1,7,9])) == OrderedSet([6,8,7,9]) + + # in-place versions of these + s = OrderedSet([6,1,3]) + s2 = s + s2 |= OrderedSet([3,1,7,9]) + assert s == OrderedSet([6,1,3,7,9]) + + s = OrderedSet([6,1,3]) + s2 = s + s2 &= OrderedSet([3,1,7]) + assert s == OrderedSet([1,3]) + + s = OrderedSet([6,1,3,2]) + s2 = s + s2 -= OrderedSet([3,1,7]) + assert s == OrderedSet([6,2]) + + s = OrderedSet([6,1,3,8]) + s2 = s + s2 ^= OrderedSet([3,1,7,9]) + assert s == OrderedSet([6,8,7,9]) + + # comparisons (order not used for these) + assert OrderedSet([4,3,2,1]) <= OrderedSet([1,2,3,4]) + assert not (OrderedSet([4,3,2,1]) < OrderedSet([1,2,3,4])) + assert OrderedSet([3,2,1]) < OrderedSet([1,2,3,4]) + + # allow second argument (but ONLY second argument) to be any iterable, not just ordered set + # we use the order from the iterable... + assert (OrderedSet([6,1,3]) | [3,1,7,9]) == OrderedSet([6,1,3,7,9]) + assert (OrderedSet([6,1,3]) & [3,1,7]) == OrderedSet([1,3]) + assert (OrderedSet([6,1,3,2]) - [3,1,7]) == OrderedSet([6,2]) + assert (OrderedSet([6,1,3,8]) ^ [3,1,7,9]) == OrderedSet([6,8,7,9]) + + +def test_ordered_set_dict_from(): + assert OrderedSet(['a','b','c']).dict_from([1, 2, 3]) == {'a': 1, 'b': 2, 'c': 3} \ No newline at end of file