2026-04-07
The economy is populated by a large number of ex-ante identical but ex-post heterogeneous households.
Households trade in a single security to insure against risks.
There is no aggregate uncertainty, implying that the aggregate variables will be constant in the stationary equilibrium.
Individual income at period \(t\), denoted by \(s_t\), follows an \(m\)-state Markov chain with transition matrix \(\pi\).
We have that:
If the realization of the process at period \(t\) is \(s_t\), a household receives labor income equal to \(w s_t\), where \(w\) is the wage rate.
If the asset holdings of the household at the beginning of the period are equal to \(a_t\), she receives a gross return of \((1 + r)a_t\).
For given \((w, r)\) and \((a_0, s_0)\), the household solves:
\[ \begin{aligned} \max_{\{c_t, a_{t+1}\}_{t=0}^{\infty}} \quad & \mathbb{E}_0 \left[ \sum_{t=0}^{\infty} \beta^t u(c_t) \right] \\ \text{s.t.} \quad & c_t + a_{t+1} = (1 + r)a_t + w s_t \\ & a_{t+1} \in A \end{aligned} \]
We assume that \(\beta \in (0,1)\) and \(u(c)\) satisfies \(\lim_{c \to 0} u'(c) = \infty\). This last assumption ensures that \(c > 0\). In addition, \(u(c)\) is strictly increasing, strictly concave, and twice continuously differentiable.
An important assumption is \(\beta(1+r) < 1\). This prevents assets from growing to infinity.
Regarding the factor prices \((w, r)\), we distinguish between two cases:
If we discretize the state space, the problem of the household can be solved using standard numerical dynamic programming techniques.
The dynamic programming problem of the household is then given by:
\[ v(a, s) = \max_{a' \in A} \left\{ u\big[(1 + r)a + ws - a'\big] + \beta \sum_j \pi_{ij} v(a', s_j) \right\} \\ \text{subject to} \quad c + a' = (1 + r)a + ws \]
Given \((w, r)\), the solution consists of the optimal value and policy functions \(v(a, s)\), \(a' = g_a(a, s)\) and \(c = g_c(a, s)\).
The policy functions have to satisfy market clearing when aggregated over households.
To aggregate, however, we need the asset-employment distribution, making this type of model more complicated than the standard representative household environment.
We use the fact that a stationary employment-wealth distribution can be obtained by defining a Markov process for the distribution and by calculating the stationary distribution of this Markov chain.
We approximate the process for \((a, s)\) with a Markov chain, and define a stationary probability vector \(\lambda(a_n, s_m)\), where
\[ \lambda(a_n, s_m) = \Pr(a_t = a_n, s_t = s_m) \]
\(\lambda(a, s)\) represents the fraction of time that a household with state \((a, s)\) spends in that state.
Alternatively, \(\lambda(a, s)\) can be interpreted as the fraction of households with asset holdings \(a\) and labor endowment \(s\). There is an equivalence between the cross-sectional and time-series properties of this distribution.
Let the individual state of the economy be represented by the vector
\[ x = \begin{pmatrix} (s_1, a_1) & \cdots & (s_1, a_n) & (s_2, a_1) & \cdots & (s_2, a_n) & \cdots & (s_m, a_1) & \cdots & (s_m, a_n) \end{pmatrix}^\top \]
where each \((s_i, a_j)\) is a possible state.
The policy function \(a' = g_a(a, s)\) and the shock transition matrix \(\Pi\) induce a Markov chain on \(x\) via the following formula: \[ \begin{aligned} \Pi &= \Pr[(a_{t+1} = a',\, s_{t+1} = s') \mid (a_t = a,\, s_t = s)] \\ &= \Pr[a_{t+1} = a' \mid s_t = s,\, a_t = a] \cdot \Pr[s_{t+1} = s' \mid s_t = s] \\ &= Y(a', a, s)\, \pi \end{aligned} \] where \(Y(a', a, s)\) is an indicator function equal to one if \(a' = g(a, s)\) and zero otherwise.
The stationary probability vector of this Markov chain is given by:
\[ p_\infty = \begin{pmatrix} \lambda(s_1, a_1) & \cdots & \lambda(s_1, a_n) & \lambda(s_2, a_1) & \cdots & \lambda(s_2, a_n) & \cdots & \lambda(s_m, a_1) & \cdots & \lambda(s_m, a_n) \end{pmatrix}^\top \]
The aggregate asset holdings are equal to \(p_\infty^\top x\).
In the model of Aiyagari (1994), the asset holdings \(a_{t+1}\) represent holdings of capital \(K_{t+1}\), which evolves according to \[ K_{t+1} = (1 - \delta) K_t + x_t, \] where \(x_t\) is gross investment and \(\delta\) is the depreciation rate.
The consumption of a household is therefore equal to \[ c_t + a_{t+1} = (1 + r) a_t + w s_t, \] where \(a_{t+1} = K_{t+1}\).
The factor prices \(r = \tilde{r} - \delta\) and \(w\) are determined from the marginal conditions of the firm.
The author assumed that there is an aggregate production function whose arguments are the average levels of capital and employment: \[ F(K, N) = A K^{\alpha} N^{1 - \alpha}. \]
Therefore, \[ w = \frac{\partial F(K, N)}{\partial N} \] and \[ \tilde{r} = \frac{\partial F(K, N)}{\partial K}. \]
A stationary equilibrium consists of a value function \(v(a, s)\), policy functions \(g_k(k, s)\) and \(g_c(k, s)\), a probability distribution \(\lambda(k, s)\), factor prices \((w, \tilde{r})\), and aggregate capital and labor \((K, N)\), such that:
Factor prices satisfy the conditions for profit maximization.
Value and policy functions solve the household problem given factor prices.
Probability distribution \(\lambda(k,s)\) is the stationary distribution associated with \(g_k(k, s)\) and \(\pi\).
Markets clear:
\[ K = \sum_{k,s} \lambda(k, s) \, g_k(k, s) \] \[ N = p_\infty^\top s \]
We start with the imports.
let’s use 64 bit floats here.
for a helper function, we need to compute stationary distributions of stochastic matrices.
Consider \[ Y = A K^{\alpha} N^{1 - \alpha}\] the firm solves: \[ \max_{K, N} \left\{ A K^{\alpha} N^{1 - \alpha} - (r + \delta) K - w N \right\} \]
we use a namedtuple to store the parameters of the firm.
it’s easy to see that
\[ r = A \alpha \left( \frac{N}{K} \right)^{1 - \alpha} - \delta \]
then for labor: \[ w(r) = A (1 - \alpha) \left( \frac{A \alpha}{r + \delta} \right)^{\alpha / (1 - \alpha)} \]
Infinitely lived households / consumers face idiosyncratic income shocks. The savings problem faced by a typical household is: \[ \max \mathbb{E} \sum_{t=0}^{\infty} \beta^t u(c_t) \] subject to: \[ a_{t+1} + c_t \leq w z_t + (1 + r) a_t, \\ c_t \geq 0,\\ a_t \geq -B \]
we setup the household using namedtuple, too.
we use a CRRA utility function here.
finally we define the prices as a namedtuple.
now we can define the Bellman equation. Consider the value of being in state \((a,z)\) with choosing \(a'\) as the next period’s asset. The value is:
\[ \begin{aligned} B(a,z,a') &= u(w z + (1 + r) a - a') + \beta \sum_{z'} v(a',z') \Pi(z,z')\\ &= u(w z + (1 + r) a - a') + \beta \mathbb{E}[v(a',z') | z] \end{aligned} \]
We use vmap to vectorize the B function.
Then it’s adviced to use jax.jit to jit compile the B function.
Warning
Note that we need to use jax.arange to create the indices. And we need to use jax.jit to jit compile the whole B function (rather than the B_scalar or B_vmap alone).
Then it’s easy to get the greedy policy.
Before we start the VFI, we need to define the Bellman operator \(T\) as follows:
Let’s then first do the VFI version of this HH problem:
@jax.jit
def VFI(hh, prices, max_iter=10000, tol=1e-8):
def body_fun(k_v_err):
k, v, err = k_v_err
vp = T(v, hh, prices)
err = jnp.max(jnp.abs(vp - v))
return k + 1, vp, err
def cond_fun(k_v_err):
k, v, err = k_v_err
return jnp.logical_and(k < max_iter, err > tol)
sizes = (hh.a_grid.size, hh.z_grid.size)
k, v, err = jax.lax.while_loop(cond_fun, body_fun, (0, jnp.zeros(sizes), jnp.inf))
return v, get_greedy(v, hh, prices)Let’s then test the VFI function.
Let’s re-run to see the time used:
Time used: 0.043359994888305664 seconds
It’s surprising the VFI is so fast!
Let’s then try something faster, that is, the HPI.
Algorithm: Howard Policy Iteration
The HPI alternates between two steps until convergence:
Compared to VFI which applies \(T\) one step at a time, HPI jumps directly to the exact value of the current policy in each iteration — this is why it typically converges in far fewer iterations.
To do this, we first need to define the reward function \(r_\sigma\) given a policy \(\sigma\). \[ r_\sigma(a,z) = u(w z + (1 + r) a - \sigma(a,z)) \]
Given the reward function \(r_\sigma\), we can get the value function \(v_\sigma\) by solving the following equation: \[ v_\sigma = r_\sigma + \beta \mathbb{E}[v_\sigma | z] = r_\sigma + \beta P_\sigma v_\sigma \tag{1}\]
where \(P_\sigma\) is the transition matrix of the policy \(\sigma\). Let \(n=|a| \times |z|\) be the total number of the states. Then we have \(v_\sigma,r_\sigma \in \mathbb{R}^n,P_\sigma \in \mathbb{R}^{n \times n}\).
Transform Equation 1 into a matrix equation: \[ v_\sigma = r_\sigma + \beta P_\sigma v_\sigma \]
we have \(v_\sigma = (I - \beta P_\sigma)^{-1} r_\sigma\). Then we can define \[ R_\sigma = I - \beta P_\sigma \]
So that \(r_\sigma = R_\sigma v_\sigma\)
Note that for state \((a,z)\), we have \[ R_\sigma v_\sigma(a,z) = v_\sigma(a,z) - \beta \sum_{z'} v_\sigma(\sigma(a,z),z') \Pi(z,z') = r_\sigma(a,z) \]
Then we have the code:
def R_sigma_scalar(v, hh, prices, sigma, i, j):
beta, a_grid, z_grid, Pi = hh
EV = jnp.sum(v[sigma[i, j], :] * Pi[j, :])
return v[i, j] - beta * EV
R_sigma_1 = jax.vmap(R_sigma_scalar, in_axes=(None, None, None, None, None, 0))
R_sigma_vmap = jax.vmap(R_sigma_1, in_axes=(None, None, None, None, 0, None))
@jax.jit
def R_sigma(v, hh, prices, sigma):
a_size, z_size = hh.a_grid.size, hh.z_grid.size
a_indices = jnp.arange(a_size)
z_indices = jnp.arange(z_size)
return R_sigma_vmap(v, hh, prices, sigma, a_indices, z_indices)so from \(v_\sigma = R_\sigma^{-1} r_\sigma\), we can then solve for the value function \(v_\sigma\). But first, we need to compute the reward function \(r_\sigma\).
def r_sigma_scalar(hh, prices, sigma, i, j):
beta, a_grid, z_grid, Pi = hh
r, w = prices
a = a_grid[i]
z = z_grid[j]
ap = a_grid[sigma[i, j]]
c = w * z + (1 + r) * a - ap
return jnp.where(c > 0, u(c), -jnp.inf)
r_sigma_1 = jax.vmap(r_sigma_scalar, in_axes=(None, None, None, None, 0))
r_sigma_vmap = jax.vmap(r_sigma_1, in_axes=(None, None, None, 0, None))
@jax.jit
def r_sigma(hh, prices, sigma):
a_size, z_size = hh.a_grid.size, hh.z_grid.size
a_indices = jnp.arange(a_size)
z_indices = jnp.arange(z_size)
return r_sigma_vmap(hh, prices, sigma, a_indices, z_indices)All prepared, let’s solve for the value function \(v_\sigma\).
Why bicgstab instead of direct inversion?
\(R_\sigma\) is an \(n \times n\) matrix where \(n = |a| \times |z|\). With \(|a|=400\) and \(|z|=2\), we have \(n=800\), so \(R_\sigma \in \mathbb{R}^{800 \times 800}\) — direct inversion is feasible here.
However, in general \(n\) can be very large, so we use an iterative solver (bicgstab) instead. Crucially, we never need to materialize \(R_\sigma\) as a matrix — we only need the linear map \(v \mapsto R_\sigma v\), which is what _R_sigma provides. This makes the approach memory-friendly for larger state spaces.
So here is the Howard policy iteration.
from jax import debug
@jax.jit
def HPI_loop(hh, prices, max_iter=10000, tol=1e-8):
def body_fun(k_v_err):
k, v, err = k_v_err
sigma_p = get_greedy(v, hh, prices)
v_sigma_p = get_v_sigma(hh, prices, sigma_p)
err = jnp.max(jnp.abs(v_sigma_p - v))
return k + 1, v_sigma_p, err
def cond_fun(k_v_err):
k, v_sigma, err = k_v_err
return jnp.logical_and(k < max_iter, err > tol)
sizes = (hh.a_grid.size, hh.z_grid.size)
k, v_sigma, err = jax.lax.while_loop(cond_fun, body_fun, (0, jnp.zeros(sizes), jnp.inf))
return v_sigma, get_greedy(v_sigma, hh, prices)Let’s then test the HPI function.
Let’s re-run to see the time used:
Time used: 0.006806135177612305 seconds
Speedup: 6.3707219672820266
Let’s look at the result of the VFI and HPI.
hh = create_HH()
fig, ax = plt.subplots()
ax.plot(hh.a_grid, hh.a_grid, "k--", label="45 degrees")
for j, z in enumerate(hh.z_grid):
lb = f"$z = {z:.2}, HPI$"
policy_vals = hh.a_grid[sigma_hpi[:, j]]
ax.plot(hh.a_grid, policy_vals, lw=2, alpha=0.6, label=lb)
ax.set_xlabel("current assets")
ax.set_ylabel("next period assets")
for j, z in enumerate(hh.z_grid):
lb = f"$z = {z:.2}, VFI$"
policy_vals = hh.a_grid[sigma_vfi[:, j]]
ax.plot(hh.a_grid, policy_vals, lw=2, alpha=0.6, label=lb)
ax.legend(loc="upper left")
plt.show()Check if they are the same:
Let’s then try a third way to solve the household problem: the endogenous grid method (EGM).
The main idea is different from both VFI and HPI. Instead of iterating on the Bellman equation and repeatedly maximizing over \(a'\), we iterate directly on the consumption policy using the Euler equation.
For the household problem \[ a' + c = w z + (1 + r) a, \qquad a' \geq a_{\min}, \] the Euler equation on the interior is \[ u'(c(a, z)) = \beta (1 + r) \sum_{z'} u'(c(a', z')) \Pi(z, z'). \]
Suppose we already have an approximation \(c_n(a, z)\) to the consumption policy.
Fix an exogenous grid for next period assets \(a' \in A\). For each pair \((a', z)\), define \[
\mathcal{M}_n(a', z) := \sum_{z'} u'(c_n(a', z')) \Pi(z, z').
\] Then the Euler equation implies \[
c_{n+1}^{\text{endo}}(a', z)
= (u')^{-1} \left( \beta (1 + r) \mathcal{M}_n(a', z) \right).
\]
Given this consumption choice and the budget constraint, the associated current asset level must satisfy \[ a_{n+1}^{\text{endo}}(a', z) = \frac{c_{n+1}^{\text{endo}}(a', z) + a' - w z}{1 + r}. \]
So for each \(z\), the pairs \[ \left( a_{n+1}^{\text{endo}}(a', z), a' \right) \] form an endogenous grid. Once we have these points, we interpolate back to the original asset grid and recover the policy \(a' = g(a, z)\).
If \(a\) is below the first endogenous grid point, then the borrowing / saving constraint binds and we set \(a' = a_{\min}\). In the code below, this is handled automatically by the left endpoint of jnp.interp.
Algorithm: EGM
Compared to VFI and HPI, EGM avoids the argmax step entirely, which is why it is often much faster in one-asset incomplete-markets problems.
We keep the same CRRA specification as before, so the marginal utility and its inverse are:
For each productivity state, we need to interpolate from the endogenous current-asset grid back to the original exogenous grid.
vmap in interp_policyinterp_policy_col handles a single column only, i.e., interpolation for a single \(z\) (productivity) state:
a_vals: the original asset grid, shape (n,)a_endo_col: the endogenous asset grid (current \(z\) column), shape (n,)sigma_a_col: the corresponding savings policy (current \(z\) column), shape (n,)All three input matrices have shape (n_a, n_z). in_axes=(1, 1, 1) means that we slice along axis 1 (columns), i.e., for each \(z\) state, we pass one column at a time to interp_policy_col. out_axes=1 then stacks all column results along the columns, so the final output is again shape (n_a, n_z).
This is equivalent to interpolating separately for each \(z_j\): \[
g(a, z_j) = \text{interp}\!\left(a,\; a^{\text{endo}}(\cdot, z_j),\; a'(\cdot, z_j)\right), \quad j = 1, \ldots, n_z
\] But vmap replaces a Python for loop with a single vectorized operation for efficiency and much better performance on GPU/TPU.
Now we can write one EGM update.
@jax.jit
def EGM_step(sigma_c, hh, prices):
"""
Perform one EGM (Endogenous Grid Method) iteration step for the consumption-savings policy.
"""
beta, a_grid, z_grid, Pi = hh
r, w = prices
R = 1 + r # Gross interest rate
# Compute expected marginal utility for next period
Emu = u_prime(sigma_c) @ Pi.T # (n_a, n_z) @ (n_z, n_z) -> (n_a, n_z)
# Back out consumption on the endogenous grid using inverse marginal utility
c_endo = u_prime_inv(beta * R * Emu)
# a_prime: Future asset grid, shape (n_a, n_z)
a_prime = jnp.broadcast_to(a_grid[:, jnp.newaxis], c_endo.shape)
# Map to endogenous current-asset grid
a_endo = (c_endo + a_prime - w * z_grid[jnp.newaxis, :]) / R
# Current asset grid (used for interpolation)
a_now = jnp.broadcast_to(a_grid[:, jnp.newaxis], c_endo.shape)
# Interpolate savings policy from endogenous to standard grid
sigma_a = interp_policy(a_now, a_endo, a_prime)
# Ensure no borrowing: impose lower bound on assets
sigma_a = jnp.maximum(sigma_a, a_grid[0])
# Update the consumption policy using the budget constraint
sigma_c_new = w * z_grid[jnp.newaxis, :] + R * a_now - sigma_a
return sigma_a, sigma_c_newThen we iterate on the consumption policy until convergence.
@jax.jit
def EGM(hh, prices, max_iter=10_000, tol=1e-8):
beta, a_grid, z_grid, Pi = hh
r, w = prices
R = 1 + r
a_min = a_grid[0]
a_now = a_grid[:, jnp.newaxis]
sigma_c_init = w * z_grid[jnp.newaxis, :] + R * a_now - a_min
def body_fun(k_c_err):
k, sigma_c, err = k_c_err
sigma_a_new, sigma_c_new = EGM_step(sigma_c, hh, prices)
err = jnp.max(jnp.abs(sigma_c_new - sigma_c))
return k + 1, sigma_c_new, err
def cond_fun(k_c_err):
k, sigma_c, err = k_c_err
return jnp.logical_and(k < max_iter, err > tol)
k, sigma_c, err = jax.lax.while_loop(
cond_fun,
body_fun,
(0, sigma_c_init, jnp.inf),
)
sigma_a, sigma_c = EGM_step(sigma_c, hh, prices)
return sigma_a, sigma_cLet’s test the EGM solver.
And let’s check the runtime:
Time used: 0.00323486328125 seconds
Speedup vs VFI: 13.40396521226415
Speedup vs HPI: 2.1039946933962264
Because EGM returns a continuous asset policy rather than an index-valued policy, it is natural to compare it to the HPI asset policy hh.a_grid[sigma_hpi].
Let’s plot the two policy functions together.
sigma_a_hpi = hh.a_grid[sigma_hpi]
fig, ax = plt.subplots()
ax.plot(hh.a_grid, hh.a_grid, "k--", label="45 degrees")
for j, z in enumerate(hh.z_grid):
lb = f"$z = {z:.2}, EGM$"
ax.plot(hh.a_grid, sigma_a_egm[:, j], lw=2, alpha=0.7, label=lb)
for j, z in enumerate(hh.z_grid):
lb = f"$z = {z:.2}, HPI$"
ax.plot(hh.a_grid, sigma_a_hpi[:, j], "--", lw=2, alpha=0.7, label=lb)
ax.set_xlabel("current assets")
ax.set_ylabel("next period assets")
ax.legend(loc="upper left")
plt.show()So in this Aiyagari household problem:
For the equilibrium section below, we keep using HPI because the distribution code is currently written for an index-valued policy \(\sigma(a, z) \in \{0,1,\ldots,|A|-1\}\). To use EGM all the way through, we would need to modify the law of motion so that probability mass is split across neighboring grid points after interpolation.
We need to know how much capital households supply at a given interest rate \(r\).
After we have the policy \(\sigma\), we now know the transition matrix \(P_\sigma\) where
\[ P_\sigma(i,j) = \Pr \{a_{t+1} = a_j, z_{t+1} = z_j | a_t = a_i, z_t = z_i\} \]
In quantecon’s tutorial here, they use this following code:
@jax.jit
def compute_asset_stationary(σ, household):
# Unpack
β, a_grid, z_grid, Π = household
a_size, z_size = len(a_grid), len(z_grid)
# Construct P_σ as an array of the form P_σ[i, j, ip, jp]
ap_idx = jnp.arange(a_size)
ap_idx = jnp.reshape(ap_idx, (1, 1, a_size, 1))
σ = jnp.reshape(σ, (a_size, z_size, 1, 1))
A = jnp.where(σ == ap_idx, 1, 0)
Π = jnp.reshape(Π, (1, z_size, 1, z_size))
P_σ = A * Π
# Reshape P_σ into a matrix
n = a_size * z_size
P_σ = jnp.reshape(P_σ, (n, n))
# Get stationary distribution and reshape back onto [i, j] grid
ψ = compute_stationary(P_σ)
ψ = jnp.reshape(ψ, (a_size, z_size))
# Sum along the rows to get the marginal distribution of assets
ψ_a = jnp.sum(ψ, axis=1)
return ψ_aBut let’s do a slightly different version.
That is, we’ll do the power iteration to get the stationary distribution instead of solving the transition matrix first, this is memeory-friendly cuz if we have a large number of states, the transition matrix will be too large to store in memory.
@jax.jit
def push_forward(psi, sigma, hh):
I, J = hh.a_grid.size, hh.z_grid.size
psi_next = jnp.zeros_like(psi)
def body_fun(k, psi_next):
i, j = jnp.unravel_index(k, (I, J))
ip = sigma[i, j]
psi_next = psi_next.at[ip, :].add(psi[i, j] * hh.Pi[j, :])
return psi_next
psi_next = jax.lax.fori_loop(0, I * J, body_fun, psi_next)
return psi_nextNow we can get the stationary distribution by iterating the push_forward function until it converges.
@jax.jit
def get_stat_asset(sigma, hh, tol=1e-12, max_iter=10_000):
I, J = hh.a_grid.size, hh.z_grid.size
# we make it a uniform distribution initially
psi = jnp.ones((I, J)) / (I * J)
def body_fun(k_psi_err):
k, psi, err = k_psi_err
psi_next = push_forward(psi, sigma, hh)
err = jnp.max(jnp.abs(psi_next - psi))
return k + 1, psi_next, err
def cond_fun(k_psi_err):
k, psi, err = k_psi_err
return jnp.logical_and(k < max_iter, err > tol)
init_val = (0, psi, jnp.inf)
k, psi, err = jax.lax.while_loop(cond_fun, body_fun, init_val)
return psiNow it’s time to test the power iteration method.
True
2.5466139613739003e-11
Let’s check the distribution now:
Check if it sums to one:
Now we can get the aggregate capital supply by households.
Let’s test the capital supply function:
Alright, then remeber what we have in r_given_k and w_given_r, we can now write the equilibrium condition.
Let’s consider the mapping \(K_{n+1} = G(K_n)\), where \(G(K)\) is the capital supply function.
That is
Let’s test the G function:
Array(8.65889621, dtype=float64)
Let’s plot the G function:
So a natural idea is to use the damped iteration scheme \[K_{n+1} = \alpha K_n + (1-\alpha) G(K_n)\] where \(\alpha\) is a damping factor.
@jax.jit
def equilibrium_damped(K0, firm, hh, alpha=0.6, max_iter=100, tol=1e-8):
def body_fun(k_K_err):
k, K, err = k_K_err
K_next = alpha * K + (1 - alpha) * G(K, firm, hh)
err = jnp.abs(K_next - K)
return k + 1, K_next, err
def cond_fun(k_K_err):
k, K, err = k_K_err
return jnp.logical_and(k < max_iter, err > tol)
init_val = (0, K0, jnp.inf)
k, K, err = jax.lax.while_loop(cond_fun, body_fun, init_val)
return KLet’s test the equilibrium_damped function:
K0 = 6
firm = create_firm()
hh = create_HH()
K_star = equilibrium_damped(K0, firm, hh)
print(f"Equilibrium capital stock: {K_star}")
start_time = time.time()
K_star = equilibrium_damped(K0, firm, hh).block_until_ready()
end_time = time.time()
damped_time = end_time - start_time
print(f"Time used: {damped_time} seconds")Equilibrium capital stock: 9.988106877194669
Time used: 0.2039480209350586 seconds
Let’s plot to check the result!