Acemoglu and Shimer (2000): Replication and Example

Implementing Acemoglu–Shimer (2000) with finite-horizon VFI Toolkit: semi-exogenous employment states, directed search choices, and voluntary quitting

Hi Robert and everyone,

I am trying to implement the dynamic model in Acemoglu and Shimer (2000), “Productivity Gains from Unemployment Insurance,” European Economic Review, Section 3, using the MATLAB VFI Toolkit. The paper is here: Productivity Gains from Unemployment Insurance | NBER

The one substantive change is to assume a finite horizon (t=1,\ldots,T) instead of infinitely lived workers. I am not adding the on-the-job-search extension from Section 5.4. In particular, I want to retain the baseline model’s voluntary quitting option: a worker in a bad job can quit, lose UI eligibility, and search while unemployed.

My proposed implementation uses:

  • One endogenous state: financial assets a; one endogenous-state choice: a'.
  • One semi-exogenous discrete state with four employment/UI statuses.
  • Two ordinary discrete decision variables: hours h and a three-valued action k (“stay employed,” “search good,” “search bad”).
  • A directly supplied, precomputed vfoptions.pi_semiz rather than vfoptions.SemiExoStateFn.

I would appreciate confirmation that this is the appropriate mapping, especially regarding the quitting option and the timing of a same-period quit followed by uninsured search.

1. Original model, with a finite horizon

Let the employment state be

e\in\{E_g,E_b,U_I,U_N\},

where E_g is a good job, E_b a bad job, U_I insured unemployment, and U_N uninsured unemployment. Both wages and job-finding rates are exogenous in this dynamic model: w_g>w_b and m_g<m_b. A worker devoting h hours to search in market j\in\{g,b\} finds a job with probability m_jh (restricted to [0,1]). Jobs separate exogenously with probabilities \delta_g,\delta_b. UI is b when eligible and expires with probability \chi after an unsuccessful search. Employed workers pay tax rate \tau. After an exogenous separation, workers are UI-eligible; voluntary quitters are not.

The paper uses CRRA preferences over a consumption–leisure composite. In the notation used below,

u(c,h)= \begin{cases} \dfrac{[c(\bar h-h)^\eta]^{1-\sigma}}{1-\sigma},&\sigma\ne1,\\[4pt] \log c+\eta\log(\bar h-h),&\sigma=1, \end{cases}

with c>0, 0\le h<\bar h. Hours are work hours when staying employed and search hours when unemployed or quitting. There is a borrowing constraint a'\ge0.

Growth normalization. To retain the deterministic productivity growth \Gamma>1 in the EER paper, use normalized assets a_t=A_t/\Gamma^t, consumption c_t=C_t/\Gamma^t, and constant normalized w_g,w_b,b. The normalized budget is

a'=\frac{R}{\Gamma}(a+y-c) \quad\Longleftrightarrow\quad c=a+y-\frac{\Gamma}{R}a'.

For \sigma\ne1, the normalized Bellman discount factor is \widetilde\beta=\beta\Gamma^{1-\sigma}. (Alternatively, one could solve in levels with age-dependent wages/benefits and ordinary discount factor \beta.) I would use the normalized formulation to stay close to the paper.

The four Bellman equations

Writing V_{g,t}(a), V_{b,t}(a), V_{I,t}(a) and V_{N,t}(a) for the four values, and using the normalized \widetilde\beta:

Good job (continue working):

V_{g,t}(a)=\max_{a',h}\left\{ \nu(c,h)+\widetilde\beta\left[ (1-\delta_g)V_{g,t+1}(a')+\delta_gV_{I,t+1}(a') \right]\right\}, \quad c=a+(1-\tau)w_gh-\frac{\Gamma}{R}a'.

Bad job (stay or quit): define the value of staying as

S_{b,t}(a)=\max_{a',h}\left\{ \nu(c,h)+\widetilde\beta\left[ (1-\delta_b)V_{b,t+1}(a')+\delta_bV_{I,t+1}(a') \right]\right\}, \quad c=a+(1-\tau)w_bh-\frac{\Gamma}{R}a'.

The quitting option is the same-period uninsured-unemployment value:

V_{b,t}(a)=\max\{S_{b,t}(a),V_{N,t}(a)\}.

This implements quitting before current-period labour income/search: the quitter receives no current wage or UI but can search immediately. Is that also the correct interpretation of “workers may voluntarily quit a job at any time” in the paper? The paper states that the bad-job Bellman equation has this option but does not write the nested max explicitly.

Uninsured unemployment:

V_{N,t}(a)=\max_{a',h,j\in\{g,b\}}\left\{ \nu(c,h)+\widetilde\beta\left[ m_jh\,V_{j,t+1}(a')+(1-m_jh)V_{N,t+1}(a') \right]\right\}, \quad c=a-\frac{\Gamma}{R}a'.

Insured unemployment:

\begin{aligned} V_{I,t}(a)=\max_{a',h,j\in\{g,b\}}\Big\{ &\nu(c,h)+\widetilde\beta\big[ m_jh\,V_{j,t+1}(a')\\ &+(1-m_jh)\{(1-\chi)V_{I,t+1}(a')+\chi V_{N,t+1}(a')\} \big]\Big\},\\ &c=a+b-\frac{\Gamma}{R}a'. \end{aligned}

At the finite-horizon terminal date, assume no bequest motive and no value after T; provided 0 belongs to the asset grid, optimal a' should be zero at T when current consumption has positive marginal utility.

2. Proposed Toolkit state and decision layout

I would map the objects as follows:

Economic object VFI Toolkit object
Current assets a a
Chosen next assets a' aprime
Employment/UI state e semiz with grid [1;2;3;4]
Hours (work or search) h First discrete decision d1
Employment/search action k Second discrete decision d2
Calendar time Finite-horizon index j=1,...,N_j

Encode the four semiz states as 1=E_g, 2=E_b, 3=U_I, 4=U_N. Rather than adding a third binary “quit” decision, let the action be

k=\begin{cases} 0 & \text{stay in current job and work},\\ 1 & \text{search for a good job},\\ 2 & \text{search for a bad job}. \end{cases}

For E_b, k=1 or 2 means quit and search as uninsured. For U_I or U_N, k=1 or 2 is normal search. The action k=0 is infeasible for unemployed workers. (For E_g one can either rule out quitting because it is dominated in the baseline, or allow it explicitly with the same quitting treatment.)

This expands the choice set, but crucially turns the same-period max \max\{S_{b,t},V_{N,t}\} into an ordinary max over mutually exclusive actions. Thus the Toolkit can solve all four state values in one backward-induction call.

The relevant setup is:

n_h = numel(h_grid);
k_grid = [0; 1; 2];    % stay, search good, search bad

n_d = [n_h, 3];
d_grid = [h_grid; k_grid];

n_a = numel(a_grid);
n_z = 0;               % no additional exogenous Markov shock

vfoptions.n_semiz   = 4;
vfoptions.semiz_grid = (1:4)';
vfoptions.l_dsemiz  = 2;  % both h and k affect transitions

The order of the return-function arguments is important: all d variables, then aprime, then a, then semiz, then any ordinary exogenous shocks, then parameters. In our case (no z or e shocks):

ReturnFn = @(h,k,aprime,a,semiz,R,Gamma,wg,wb,tau,b,hbar,eta,sigma) ...
    AS_ReturnFn(h,k,aprime,a,semiz,R,Gamma,wg,wb,tau,b,hbar,eta,sigma);

3. Semi-exogenous transition probabilities

This is the part for which the Toolkit’s semi-exogenous feature seems particularly useful. Let

P(e'\mid e,h,k)

be the probability of next period’s employment/UI state. Its rows correspond to current states and columns to next states, both in order (E_g,E_b,U_I,U_N).

When a worker stays employed (k=0), the rows for good and bad jobs are:

P(\cdot\mid E_g,h,0)=(1-\delta_g,0,\delta_g,0),
P(\cdot\mid E_b,h,0)=(0,1-\delta_b,\delta_b,0).

When an insured unemployed worker searches in job type j, put p=m_jh:

P(\cdot\mid U_I,h,j)= \begin{cases} (p,0,(1-p)(1-\chi),(1-p)\chi),&j=g,\\ (0,p,(1-p)(1-\chi),(1-p)\chi),&j=b. \end{cases}

When an uninsured unemployed worker searches:

P(\cdot\mid U_N,h,j)= \begin{cases} (p,0,0,1-p),&j=g,\\ (0,p,0,1-p),&j=b. \end{cases}

Finally, if an employed worker chooses to quit and search, the corresponding row is exactly the uninsured search row, not the insured one. This is how we enforce loss of UI eligibility on quitting.

Option A: create pi_semiz directly (my preferred implementation)

From the Toolkit exogenous-shock documentation and SemiExogShockSetup_FHorz.m, vfoptions.pi_semiz can be a precomputed array with dimensions

[N_{\mathrm{semiz}},N_{\mathrm{semiz}},N_{d,\mathrm{semiz}}] =[4,4,3n_h]

when transitions do not vary with age. The Toolkit broadcasts it over t=1,\dots,T-1. Its third dimension uses the MATLAB column-major combination index

i_d=i_h+(i_k-1)n_h.

Here is my proposed construction:

function pi_semiz = AS_CreatePiSemiz(h_grid,delta_g,delta_b,mg,mb,chi)
% Four states (current and next): 1 Eg, 2 Eb, 3 UI, 4 UN.
% Actions: 0 stay; 1 search good; 2 search bad.
% The last two ordinary decisions are (h,k).

nh = numel(h_grid);
k_grid = [0;1;2];
pi_semiz = zeros(4,4,nh*numel(k_grid));

for ik = 1:numel(k_grid)
    k = k_grid(ik);
    for ih = 1:nh
        h = h_grid(ih);
        P = zeros(4,4);

        if k == 0
            % Valid choices for employed workers: work and stay.
            P(1,:) = [1-delta_g, 0, delta_g, 0];
            P(2,:) = [0, 1-delta_b, delta_b, 0];

            % Placeholder probability rows for forbidden U + stay action.
            % ReturnFn will assign -Inf to that action.
            P(3,:) = [0, 0, 1-chi, chi];
            P(4,:) = [0, 0, 0, 1];
        else
            if k == 1
                p = mg*h;
                target = 1;
            else
                p = mb*h;
                target = 2;
            end
            assert(p >= 0 && p <= 1, 'Search probability outside [0,1]');

            % A quitter from E_g or E_b searches without UI.
            quitter = zeros(1,4);
            quitter(target) = p;
            quitter(4) = 1-p;
            P(1,:) = quitter;    % optionally disallow E_g quitting in ReturnFn
            P(2,:) = quitter;

            % Insured unemployed: UI can expire if search fails.
            P(3,target) = p;
            P(3,3) = (1-p)*(1-chi);
            P(3,4) = (1-p)*chi;

            % Uninsured unemployed.
            P(4,:) = quitter;
        end

        idx = ih + (ik-1)*nh;
        pi_semiz(:,:,idx) = P;
    end
end

assert(all(pi_semiz(:) >= 0));
assert(max(abs(sum(pi_semiz,2)-1),[],'all') < 1e-12);
end

Even action/state combinations forbidden by the return function have valid transition probabilities so every slice is stochastic. The ReturnFn is responsible for ruling out economically infeasible actions.

Then simply:

vfoptions.pi_semiz = AS_CreatePiSemiz( ...
    h_grid, delta_g, delta_b, mg, mb, chi);

If matching/separation/UI-expiration probabilities depend on age, I understand that a four-dimensional array [4,4,3*n_h,N_j-1] is also allowed.

Option B: supply SemiExoStateFn

I understand that the alternative is a function returning a scalar probability:

vfoptions.SemiExoStateFn = ...
    @(semiz,semizprime,h,k,delta_g,delta_b,mg,mb,chi) ...
    AS_SemiExoProbability(semiz,semizprime,h,k, ...
                          delta_g,delta_b,mg,mb,chi);

The Toolkit would use the function to construct the same array internally. Because there are only four states and the probabilities are simple, I lean towards Option A, which is particularly easy to inspect/test. Are there any practical advantages to using SemiExoStateFn instead?

4. Return function and quitting

The same ReturnFn must handle staying, searching, and quitting. The key point is that a quitter must not receive current employed income or current UI. Instead, she consumes and searches as an uninsured unemployed worker in the same period.

The income entering the normalized budget is

y(e,h,k)= \begin{cases} (1-\tau)w_gh,& e=E_g,\ k=0,\\ (1-\tau)w_bh,& e=E_b,\ k=0,\\ b,& e=U_I,\ k\in\{1,2\},\\ 0,&\text{uninsured unemployment, or quitting and searching}. \end{cases}

Here is a schematic scalar return function, intended for the Toolkit’s discrete-return evaluation (without a separate z shock):

function F = AS_ReturnFn(h,k,aprime,a,semiz, ...
                         R,Gamma,wg,wb,tau,b,hbar,eta,sigma)
% semiz = 1 Eg, 2 Eb, 3 UI, 4 UN
% k = 0 stay; 1 search good; 2 search bad

F = -Inf;

% All employed states may quit. To restrict quitting to bad jobs,
% also require k==0 when semiz==1.
validAction = ((semiz==1 || semiz==2) && k==0) || ...
              ((k==1 || k==2) && semiz>=1 && semiz<=4);

if validAction && aprime>=0 && h>=0 && h<hbar
    income = 0;
    if semiz==1 && k==0
        income = (1-tau)*wg*h;
    elseif semiz==2 && k==0
        income = (1-tau)*wb*h;
    elseif semiz==3
        income = b;
    end
    % All other admissible search actions have zero current income.
    % For semiz 1 or 2 with k>0, this is an immediate uninsured quit.

    c = a + income - (Gamma/R)*aprime;
    if c>0
        if sigma==1
            F = log(c) + eta*log(hbar-h);
        else
            F = (c*(hbar-h)^eta)^(1-sigma)/(1-sigma);
        end
    end
end
end

The transition array accounts for the next employment state; ReturnFn supplies only current-period utility and feasibility. Importantly, the ReturnFn argument order is

(d1,d2,aprime,a,semiz,parameters...), not (a,semiz,aprime,d1,d2,...).

5. Schematic call to the finite-horizon solver

The following illustrates the overall setup, using paper-like parameters and illustrative grids. It is a proposed implementation, not yet a tested replication:

% --- Model parameters (weekly frequency, illustrative) ---
Parameters.R     = 1.0006;
Parameters.Gamma = 1.0003;
Parameters.beta  = 0.998;
Parameters.sigma = 4;
Parameters.eta   = 1/6;
Parameters.hbar  = 1;
Parameters.wg    = 1.3;
Parameters.wb    = 1.0;
Parameters.b     = 0.4;
Parameters.tau   = 0.022;   % fixed here; see budget note below

Parameters.betaeff = Parameters.beta * ...
    Parameters.Gamma^(1-Parameters.sigma);

mg = 0.05;
mb = 0.50;
delta_g = 0.005;
delta_b = 0.005;
chi = 0.04;

N_j = 120; % short finite-horizon test; use longer horizon later
h_grid = linspace(0,0.95,21)';
a_grid = (linspace(0,sqrt(40),151)').^2;

n_h = numel(h_grid);
n_d = [n_h, 3];
d_grid = [h_grid; 0; 1; 2];
n_a = numel(a_grid);
n_z = 0;
z_grid = [];
pi_z = [];

vfoptions = struct();
vfoptions.n_semiz = 4;
vfoptions.semiz_grid = (1:4)';
vfoptions.l_dsemiz = 2;
vfoptions.pi_semiz = AS_CreatePiSemiz( ...
    h_grid,delta_g,delta_b,mg,mb,chi);
vfoptions.gridinterplayer = 0;  % first validate plain VFI
vfoptions.parallel = 2;         % use GPU where available

ReturnFn = @(h,k,aprime,a,semiz,R,Gamma,wg,wb,tau,b,hbar,eta,sigma) ...
    AS_ReturnFn(h,k,aprime,a,semiz,R,Gamma,wg,wb,tau,b,hbar,eta,sigma);

ReturnFnParamNames = {'R','Gamma','wg','wb','tau','b', ...
                      'hbar','eta','sigma'};
DiscountFactorParamNames = {'betaeff'};

[V,Policy] = ValueFnIter_Case1_FHorz( ...
    n_d,n_a,n_z,N_j,d_grid,a_grid,z_grid,pi_z, ...
    ReturnFn,Parameters,DiscountFactorParamNames, ...
    ReturnFnParamNames,vfoptions);

The expected shape of V is [n_a,4,N_j], with the four columns corresponding to employment/UI states. Policy should identify chosen hours, the three-valued action, and aprime at each (a,e,t) (subject to the Toolkit’s policy-index representation).

The government-budget equation remains an outer equilibrium condition, not part of worker VFI. For this first implementation I would condition on \tau and b. With a finite horizon, a fully budget-balanced experiment would additionally require an initial cross-sectional distribution and a specified tax-financing rule over time. The original infinite-horizon paper balances the UI system using the stationary employment/wealth distribution.

6. Checks I would perform

  1. Stochastic transitions: each row of each pi_semiz(:,:,idx) sums to one; all probabilities are nonnegative. Check a few known (h,k) pairs by hand.
  2. Correct quitting: in state E_b with k=1 or 2, current wage and UI income are both zero, and an unsuccessful search leads to U_N, not U_I.
  3. Bellman identity: compare the Toolkit bad-job value to an independently computed \max\{S_{b,t}(a),V_{N,t}(a)\} on a small asset grid. They should agree numerically if the timing is correct.
  4. Dominance: V_{b,t}(a)\ge V_{N,t}(a) at every feasible (a,t) when quitting is allowed.
  5. Finite-horizon terminal period: check that a'_{T}=0 in the absence of bequests (and that search effort becomes irrelevant at T).
  6. Zero-income corner: U_N with a=0 has no feasible strictly positive consumption under the original no-borrowing, zero-uninsured-income assumptions. With CRRA utility, this can produce -Inf values; I do not want to silently introduce a consumption floor that changes the model. Is there a preferred Toolkit treatment for these infeasible boundary states?

7. Questions for the VFI Toolkit forum

  1. Is the expanded action-space approach the right way to implement voluntary quitting? In particular, does combining ReturnFn and pi_semiz in this way reproduce a worker’s ability to quit and enter uninsured search in the same period, without solving a separate nested Bellman equation?
  2. Is direct vfoptions.pi_semiz preferable here to vfoptions.SemiExoStateFn, given that the 4\times4\times(3n_h) array is small and independent of assets?
  3. Any implementation pitfalls with two decision variables controlling the semi-exogenous transition (vfoptions.l_dsemiz=2), forbidden actions assigned -Inf, or the terminal-period/zero-asset cases?
  4. Once the basic implementation is validated, would you recommend vfoptions.gridinterplayer for the asset choice in this particular semi-exogenous finite-horizon setup, or are there branch-specific considerations?

Thanks in advance for any feedback!

I wanted to replicate the Acemoglu-Shimer paper with the VFI toolkit and asked chatgpt to map their model to the toolkit layout. It was not entirely obvious to me and chatgpt did a very good job, I think. The post above serves as a documentation for myself and other users interested in replicating this type of search models.

The main novelty relative to search models already done with the toolkit is that workers can direct their search to either bad jobs or good jobs. The model is stylized because the matching probabilities are exogenous and wage heterogeneity is minimal (as I wrote above, only two wage levels) but still captures the important trade-off: less wealthy agents may be forced to accept bad jobs because bad jobs are easier to find (i.e. higher job finding rate). Higher unemployment benefits relax the liquidity constraints of unemployed workers and allow them to target better, but harder to find, jobs.

Whenever I have time, I will implement chatgpt recommendations and share the Acemoglu-Shimer replication here for the benefit of VFI-toolkit users :slight_smile:

Acemoglu & Shimer (2000) is an InfHorz value fn problem with one standard endogenous state (assets) and one semi-exogenous state (four values: good job, bad job, unemployed&insured, unemployed&uninsured). Two decision variables: search effort, and binary which is good/bad job to search for (when unemployed) and quit/stay (when employed); both influence the semi-exogenous state.

Model is essentially partial eqm, but a government budget constraint means that we have to solve general eqm to get the government budget to balance.

AS2000 assume that only people in bad jobs make a quit/stay decision, but is easier to just code that all people in jobs make a quit/stay decision as those in good job never quit anyway.

There is also a minor issue with the model that would be super irritating when solving. They set 0 as the income when unemployed&uninsured, but there is a non-zero probability of being in this state for any finite length of time. This gets ugly for solving the value fn problem, so in practice you are better setting something like an income floor of 1e-7, no meaningful change in the model but avoids what is otherwise highly problematic as it become incredibly hard to solve. [The use of income floors in models is standard for precisely this reason, you see it in many life-cycle and OLG models.]

PS. Economics of the article was interesting, but overall the article was rather irritating. They spend all this time on a static model that is supposed to give the main ideas, then quantify using a dynamic extension. But the static model is actually highly misleading as a guide to the dynamic model. The static model has households, firms and general eqm (free-entry condition). Took me a moment before I twigged that the dynamic model gets rid of most of this and just has households in partial eqm (technically it is not quite partial eqm because there is a government budget constraint that is a general eqm eqn; but none of the labour market eqm the static model spent a lot of time and effort illustrating). In the end the static model seems to be there mostly just to mislead readers as to the actual exercise they are doing.

PPS. I built the needed functionality: InfHorz: semiz and e

1 Like

Thanks for adding this functionality to infinite horizon!

Yes, the dynamic model throws away the general equilibrium. I still like the paper, but it has indeed a limitation: jobs and wages are fixed, so the increase in unemployment bemefits does not change firm behavior (there are no firms!).

I think back then people did not know how to model directed search with savings. This is indeed the methodological contribution of Chaumont and Shi (JME 2022) and Kaas, Lale and Siassi (Ecta 2026).

https://www.sciencedirect.com/science/article/abs/pii/S030439322200040X

https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4662181

Here is a full replication of Acemoglu & Shimer (2000). Largely all replicates, one or two model stats seem wrong (or just non-obvious definition) but everything else is very close.

Here is an example of the baseline model of Acemoglu & Shimer (2000). This example solves the general eqm once and then reports a few simple model statistics. Focus is on explaining the code and what is being set up.

Acemoglu & Shimer (2000) original paper.

Replication is largely just solving the example multiple times with different setups, except Table 6 which adds on-the-job search which requires an additional decision variable.

PS. This example uses “diary”, I use diary in all my codes nowadays as you can them point AI at the diary to diagnose how things went.

1 Like

Great, I will look at this!

One comment about “diary”. Why you need this? Usually I ask my AI to run matlab code in batch mode and the AI sees what’s going on
So for example codex or claude will run matlab from the command line using:

matlab -batch "filename"

where filename.m is the entry script.

No need to set MCP or other complicated interfaces.

Allows AI (Claude Code in my case) to read the diary txt file, which contains everything printed to the “Command Window” in Matlab. So, e.g., Claude can look at every iteration of the stationary general eqm (with heteroagentoptions.verbose=2) and see if it is converging, in how many steps, does it just oscillate at 2e-4 when tolerance is 1e-4, etc. Very useful for things like “would a larger grid help”, “would changing the convergence tolerance help”, “should shooting use a larger/smaller update step size”.

Note: some server queuing software (like Slurm) creates “.out” files that report exactly this kind of thing. But the diary works everywhere.

1 Like

Yes but when you run matlab on the command line with batch, it’s like running on the server and AI can read all the info printed on the command window as well.
Nowadays I do not even run matlab from the desktop app anymore :slight_smile: