Replication: Laun & Wallenius (2015)

Laun & Wallenius (2015) - A life cycle model of health and retirement: The case of Swedish pension reform

Replication is available here: https://github.com/vfitoolkit/vfitoolkit-matlab-replication/tree/master/LaunWallenius2015

Everything replicates cleanly :smiley:

Shows off something not previously documented in VFI Toolkit. In LW2015 model you need to keep track of the age at which household “claims disability insurance”, “claims retirement pension”, and “stop work”. If you just took cross product it would be 40,000 combos. But only 1237 combos are actually relevant to the model. VFI Toolkit can set this up as an “experienceasset”, and you can easily have AI write the ‘encode/decode’ that converts the index which counts from 1-to-1237 into the ‘values of the three variables’ and vice-versa.

[Note: this is essentially a_gridvals, but with a decision variable that drives it that is way smaller. AI often suggests ‘lookup table’ approach, but this won’t work with gpu so you need the encode/decode to be done as functions.]

Whole thing runs on my desktop, full replication is easily done overnight, probably only takes an afternoon. I was able to use Claude + VFI Toolkit to write the entire replication in a single day.

4 Likes

Here is an example code for Keane & Wolpin (1997) that does the same encode/decode with an experienceasset trick:

Note: KW1997 used E-Max operator that heavily approximates the solution. This code does the full solution of the KW1997, but the full model is so big that the solution won’t even fit in the best GPU memory money can buy circa-2026, so you can only run a version that reduces the number of shocks. That said it is very fast, so once a gpu with 500gb-ish of memory is available a full replication of KW1997 should be easy.

2 Likes

Wow this is impressive. VFI-Toolkit becomes 100 times more powerful with AI

1 Like

This will become better and better: now that you shared these replications on github, the AI will learn from them and be able to recast these models in the vfi-toolkit format.

And AI becomes 100 times more powerful with VFI Toolkit :wink:

[Claude would struggle on these big models with no guidance, but the toolkit structure is all the guidance it needs.]

2 Likes

What does your AI make of this?

>> clear all; tic; KeaneWolpin1997; toc

cast2precision =

  function_handle with value:

    @(x)x


cast2index =

  function_handle with value:

    @(x)x


vfoptions = 

  struct with fields:

                  precision: 'double'
                     indexT: 'double'
                        n_e: [3 3 3 1 1]
                     e_grid: [11×1 gpuArray]
                       pi_e: [27×1 gpuArray]
            experienceasset: 1
                   aprimeFn: @(d,a,N_tuples,g_min,g_max,x_max,S)KeaneWolpin1997_aprimeFn(d,a,N_tuples,g_min,g_max,x_max,S)
                    verbose: 1
                  lowmemory: 1
           divideandconquer: 0
            gridinterplayer: 0
            incrementaltype: 0
          exoticpreferences: 'None'
                    dynasty: 0
           experienceassetu: 0
           experienceassete: 0
           experienceassetz: 0
          experienceassetze: 0
                 riskyasset: 0
              residualasset: 0
                n_ambiguity: 0
                    n_semiz: 0
                   parallel: 2
                 outputkron: 0
            alreadygridvals: 0
    alreadygridvals_semiexo: 0
               e_gridvals_J: [27×5×50 gpuArray]
                     pi_e_J: [27×50 gpuArray]

Finite horizon: 49 of 50 
Finite horizon: 48 of 50 
Finite horizon: 47 of 50 
Finite horizon: 46 of 50 
Finite horizon: 45 of 50 
Finite horizon: 44 of 50 
Finite horizon: 43 of 50 
Finite horizon: 42 of 50 
Finite horizon: 41 of 50 
Finite horizon: 40 of 50 
Finite horizon: 39 of 50 
Finite horizon: 38 of 50 
Finite horizon: 37 of 50 
Finite horizon: 36 of 50 
Finite horizon: 35 of 50 
Finite horizon: 34 of 50 
Finite horizon: 33 of 50 
Finite horizon: 32 of 50 
Finite horizon: 31 of 50 
Finite horizon: 30 of 50 
Finite horizon: 29 of 50 
Finite horizon: 28 of 50 
Finite horizon: 27 of 50 
Finite horizon: 26 of 50 
Finite horizon: 25 of 50 
Finite horizon: 24 of 50 
Finite horizon: 23 of 50 
Finite horizon: 22 of 50 
Finite horizon: 21 of 50 
Finite horizon: 20 of 50 
Finite horizon: 19 of 50 
Finite horizon: 18 of 50 
Finite horizon: 17 of 50 
Finite horizon: 16 of 50 
Finite horizon: 15 of 50 
Finite horizon: 14 of 50 
Finite horizon: 13 of 50 
Finite horizon: 12 of 50 
Finite horizon: 11 of 50 
Finite horizon: 10 of 50 
Finite horizon: 9 of 50 
Finite horizon: 8 of 50 
Finite horizon: 7 of 50 
Finite horizon: 6 of 50 
Finite horizon: 5 of 50 
Finite horizon: 4 of 50 
Finite horizon: 3 of 50 
Finite horizon: 2 of 50 
Finite horizon: 1 of 50 

vtime =

   10.2349

Elapsed time is 10.235112 seconds.
>> 
>> clear all; tic; KeaneWolpin1997; toc

cast2precision =

  function_handle with value:

    @(x)single(x)


cast2index =

  function_handle with value:

    @(x)int32(x)


vfoptions = 

  struct with fields:

                  precision: 'single'
                     indexT: 'int32'
                        n_e: [3 3 3 1 1]
                     e_grid: [11×1 gpuArray]
                       pi_e: [27×1 gpuArray]
            experienceasset: 1
                   aprimeFn: @(d,a,N_tuples,g_min,g_max,x_max,S)KeaneWolpin1997_aprimeFn_single(d,a,N_tuples,g_min,g_max,x_max,S)
                    verbose: 1
                  lowmemory: 1
           divideandconquer: 0
            gridinterplayer: 0
            incrementaltype: 0
          exoticpreferences: 'None'
                    dynasty: 0
           experienceassetu: 0
           experienceassete: 0
           experienceassetz: 0
          experienceassetze: 0
                 riskyasset: 0
              residualasset: 0
                n_ambiguity: 0
                    n_semiz: 0
                   parallel: 2
                 outputkron: 0
            alreadygridvals: 0
    alreadygridvals_semiexo: 0
               e_gridvals_J: [27×5×50 gpuArray]
                     pi_e_J: [27×50 gpuArray]

Finite horizon: 49 of 50 
Finite horizon: 48 of 50 
Finite horizon: 47 of 50 
Finite horizon: 46 of 50 
Finite horizon: 45 of 50 
Finite horizon: 44 of 50 
Finite horizon: 43 of 50 
Finite horizon: 42 of 50 
Finite horizon: 41 of 50 
Finite horizon: 40 of 50 
Finite horizon: 39 of 50 
Finite horizon: 38 of 50 
Finite horizon: 37 of 50 
Finite horizon: 36 of 50 
Finite horizon: 35 of 50 
Finite horizon: 34 of 50 
Finite horizon: 33 of 50 
Finite horizon: 32 of 50 
Finite horizon: 31 of 50 
Finite horizon: 30 of 50 
Finite horizon: 29 of 50 
Finite horizon: 28 of 50 
Finite horizon: 27 of 50 
Finite horizon: 26 of 50 
Finite horizon: 25 of 50 
Finite horizon: 24 of 50 
Finite horizon: 23 of 50 
Finite horizon: 22 of 50 
Finite horizon: 21 of 50 
Finite horizon: 20 of 50 
Finite horizon: 19 of 50 
Finite horizon: 18 of 50 
Finite horizon: 17 of 50 
Finite horizon: 16 of 50 
Finite horizon: 15 of 50 
Finite horizon: 14 of 50 
Finite horizon: 13 of 50 
Finite horizon: 12 of 50 
Finite horizon: 11 of 50 
Finite horizon: 10 of 50 
Finite horizon: 9 of 50 
Finite horizon: 8 of 50 
Finite horizon: 7 of 50 
Finite horizon: 6 of 50 
Finite horizon: 5 of 50 
Finite horizon: 4 of 50 
Finite horizon: 3 of 50 
Finite horizon: 2 of 50 
Finite horizon: 1 of 50 

vtime =

    1.5754

Elapsed time is 1.575596 seconds.

Too long? Couldn’t read?

double/double: Elapsed time is 10.235112 seconds.
single/int32: Elapsed time is 1.575596 seconds.

I can see you are saying that you can solve it 10x faster using single/in32 instead of double/double. Which is cool.

Are you also saying that it solves with the full n_e?

[My AI has been judged too dangerous by the Trump administration so I am back to thinking for myself :stuck_out_tongue: ]

2 Likes

Alas the 1B parameter limit stops the naive approach. I was able to complete

n_e = [3, 3, 3, 1, 1];

But when I move to

n_e = [3, 3, 3, 3, 1];

I get this:

Error using zeros
Maximum variable size allowed on the device is exceeded.

Error in ValueFnIter_FHorz_ExpAsset_nod1_noa1_noz_e_raw (line 8)
V=zeros(N_a,N_e,N_j,vfoptions.precision,'gpuArray');
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error in ValueFnIter_FHorz_ExpAsset (line 71)
                [VKron, PolicyKron]=ValueFnIter_FHorz_ExpAsset_nod1_noa1_noz_e_raw(n_d2,n_a2, vfoptions.n_e, N_j, d2_gridvals, a2_grid, vfoptions.e_gridvals_J, vfoptions.pi_e_J, ReturnFn, aprimeFn, Parameters, DiscountFactorParamNames, ReturnFnParamNames, aprimeFnParamNames, vfoptions);
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error in ValueFnIter_Case1_FHorz (line 407)
            [V,Policy]=ValueFnIter_FHorz_ExpAsset(n_d1,n_d2,n_a1,n_a2,n_z, N_j, d1_grid , d2_grid, a1_grid, a2_grid, z_gridvals_J, pi_z_J, ReturnFn, Parameters, DiscountFactorParamNames, ReturnFnParamNames, vfoptions);
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error in KeaneWolpin1997 (line 177)
[V, Policy] = ValueFnIter_Case1_FHorz(n_d, n_a, n_z, N_j, d_grid, a_grid, z_grid, pi_z, ReturnFn, Params, DiscountFactorParamNames, [], vfoptions);

So it’s going to take a Frankenstein solution to break V up and put it back together. The [3,3,3,1,1] version only needs 24GB of VRAM on my card with singlefp (I’m measuring off the dashboard, not carefully). With doublefp that grows to 31.5GB VRAM, and no spills to shared memory. So the real bottleneck is just a large value array.

1 Like

This replication of Luan and Wallenius is what made the Pentagon suspicious :sweat_smile:

1 Like