@@ -611,55 +611,50 @@ When employed, the agent faces job separation with probability $\alpha$ each per
611611612612Now 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))
663658664659665660def 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