GitHub

@@ -611,55 +611,50 @@ When employed, the agent faces job separation with probability $\alpha$ each per

611611612612

Now let's simulate many agents simultaneously to examine the cross-sectional unemployment rate.

613613614-

We first create a vectorized version of `update_agent` to efficiently update all agents in parallel:

614+

To do this efficiently, we need a different approach than `simulate_employment_path` defined above.

615615616-

```{code-cell} ipython3

617-

# Create vectorized version of update_agent

618-

update_agents_vmap = jax.vmap(

619-

update_agent, in_axes=(0, 0, 0, None, None)

620-

)

621-

```

616+

The key differences are:

622617623-

Next we define the core simulation function, which uses `lax.fori_loop` to efficiently iterate many agents forward in time:

624-625-

```{code-cell} ipython3

626-

@partial(jax.jit, static_argnums=(3, 4))

627-

def _simulate_cross_section_compiled(

628-

key: jnp.ndarray,

629-

model: Model,

630-

w_bar: float,

631-

n_agents: int,

632-

T: int

633-

):

634-

"""JIT-compiled core simulation loop using lax.fori_loop.

635-

Returns only the final employment state to save memory."""

636-

c, α, β, ρ, ν, γ, w_grid, z_draws = model

618+

- `simulate_employment_path` records the entire history (all T periods) for a single agent, which is useful for visualization but memory-intensive

619+

- The new function `sim_agent` below only tracks and returns the final state, which is all we need for cross-sectional statistics

620+

- `sim_agent` uses `lax.fori_loop` instead of a Python loop, making it JIT-compilable and suitable for vectorization across many agents

637621638-

# Initialize arrays

639-

init_key, subkey = jax.random.split(key)

640-

wages = jnp.exp(jax.random.normal(subkey, (n_agents,)) * ν)

641-

status = jnp.zeros(n_agents, dtype=jnp.int32)

622+

We first define a function that simulates a single agent forward T time steps:

642623643-

def update(t, loop_state):

644-

status, wages = loop_state

624+

```{code-cell} ipython3

625+

@jax.jit

626+

def sim_agent(key, initial_status, initial_wage, model, w_bar, T):

627+

"""

628+

Simulate a single agent forward T time steps using lax.fori_loop.

645629646-

# Shift loop state forwards

647-

step_key = jax.random.fold_in(init_key, t)

648-

agent_keys = jax.random.split(step_key, n_agents)

630+

Uses fold_in to generate a new key at each time step.

649631650-

status, wages = update_agents_vmap(

651-

agent_keys, status, wages, model, w_bar

652-

)

632+

Parameters:

633+

- key: JAX random key for this agent

634+

- initial_status: Initial employment status (0 or 1)

635+

- initial_wage: Initial wage

636+

- model: Model instance

637+

- w_bar: Reservation wage

638+

- T: Number of time periods to simulate

653639654-

return status, wages

640+

Returns:

641+

- final_status: Employment status after T periods

642+

- final_wage: Wage after T periods

643+

"""

644+

def update(t, loop_state):

645+

status, wage = loop_state

646+

step_key = jax.random.fold_in(key, t)

647+

status, wage = update_agent(step_key, status, wage, model, w_bar)

648+

return status, wage

655649656-

# Run simulation using fori_loop

657-

initial_loop_state = (status, wages)

650+

initial_loop_state = (initial_status, initial_wage)

658651

final_loop_state = lax.fori_loop(0, T, update, initial_loop_state)

652+

final_status, final_wage = final_loop_state

653+

return final_status, final_wage

654+659655660-

# Return only final employment state

661-

final_is_employed, _ = final_loop_state

662-

return final_is_employed

656+

# Create vectorized version of sim_agent to process multiple agents in parallel

657+

sim_agents_vmap = jax.vmap(sim_agent, in_axes=(0, 0, 0, None, None, None))

663658664659665660

def simulate_cross_section(

@@ -669,30 +664,36 @@ def simulate_cross_section(

669664

seed: int = 42

670665

) -> float:

671666

"""

672-

Simulate employment paths for many agents and return final unemployment rate.

667+

Simulate cross-section of agents and return unemployment rate.

673668674-

Parameters:

675-

- model: Model instance with parameters

676-

- n_agents: Number of agents to simulate

677-

- T: Number of periods to simulate

678-

- seed: Random seed for reproducibility

669+

This approach:

670+

1. Generates n_agents random keys

671+

2. Calls sim_agent for each agent (vectorized via vmap)

672+

3. Collects the final states to produce the cross-section

679673680-

Returns:

681-

- unemployment_rate: Fraction of agents unemployed at time T

674+

Returns the cross-sectional unemployment rate.

682675

"""

676+

c, α, β, ρ, ν, γ, w_grid, z_draws = model

677+683678

key = jax.random.PRNGKey(seed)

684679685680

# Solve for optimal reservation wage

686681

w_bar = get_reservation_wage(model)

687682688-

# Run JIT-compiled simulation

689-

final_status = _simulate_cross_section_compiled(

690-

key, model, w_bar, n_agents, T

683+

# Initialize arrays

684+

init_key, subkey = jax.random.split(key)

685+

initial_wages = jnp.exp(jax.random.normal(subkey, (n_agents,)) * ν)

686+

initial_status_vec = jnp.zeros(n_agents, dtype=jnp.int32)

687+688+

# Generate n_agents random keys

689+

agent_keys = jax.random.split(init_key, n_agents)

690+691+

# Simulate each agent forward T steps (vectorized)

692+

final_status, final_wages = sim_agents_vmap(

693+

agent_keys, initial_status_vec, initial_wages, model, w_bar, T

691694

)

692695693-

# Calculate unemployment rate at final period

694696

unemployment_rate = 1 - jnp.mean(final_status)

695-696697

return unemployment_rate

697698

```

698699

@@ -743,12 +744,23 @@ def plot_cross_sectional_unemployment(

743744

Generate histogram of cross-sectional unemployment at a specific time.

744745745746

"""

747+

c, α, β, ρ, ν, γ, w_grid, z_draws = model

746748747749

# Get final employment state directly

748750

key = jax.random.PRNGKey(42)

749751

w_bar = get_reservation_wage(model)

750-

final_status = _simulate_cross_section_compiled(

751-

key, model, w_bar, n_agents, t_snapshot

752+753+

# Initialize arrays

754+

init_key, subkey = jax.random.split(key)

755+

initial_wages = jnp.exp(jax.random.normal(subkey, (n_agents,)) * ν)

756+

initial_status_vec = jnp.zeros(n_agents, dtype=jnp.int32)

757+758+

# Generate n_agents random keys

759+

agent_keys = jax.random.split(init_key, n_agents)

760+761+

# Simulate each agent forward T steps (vectorized)

762+

final_status, _ = sim_agents_vmap(

763+

agent_keys, initial_status_vec, initial_wages, model, w_bar, t_snapshot

752764

)

753765754766

# Calculate unemployment rate

Read the original on github.com ↗