Riskyasset (portfolio-choice) now much faster with refine

I overhauled the codes for riskyasset which is typically used for portfolio choice, but is any endogenous state in which next period value aprime cannot be chosen directly but instead is a function of decision variables d and a between-period i.i.d. shock u, that is, aprime(d,u).

The overhaul was about ‘refining’ out the d1 and d3 decisions, before putting the expectations term together with the return function and then solving for the d2 decision (you don’t need to understand this sentence). The effect is anything from a 2x to a 10x improvement in runtimes.

The overhaul is about setting up (following is copy-paste from Life-Cycle Model 31):
To make things run faster we will also use vfoptions.refine\_{}d, which is always a 1-by-3 vector. The three elements are respectively the number of d1, d2 and d3 decision variables, where d1 are decision variables that appear in the return function but not the aprime function, d2 are decision variables that appear in the aprime function but not the return function, and d3 are decision variables that appear in both the aprime function and the return function. As a result the first inputs to the return function are (d1,d3,a,z,...), while the first inputs to the aprime function are (d2,d3,u,...). Our current model has two decisions, riskyshare and savings: riskyshare is only needed in the aprime function so it is a d2 variable, and savings is needed for both the return function and the aprime function so it is a d3 variable, and this model has no d1 variables. Hence we set vfoptions.refine\_{}d=[0,1,1], and we have to make sure that the ordering used for the decision variables in n\_{}d and d\_{}grid fit with the same ordering (we don’t have a d1 in this model, so first is our d2 which is riskyshare, and then comes our d3 which is savings). Note, the codes allow for more than one of each of d1, d2 and d3 variables; you can also have no d1 variables, but you cannot have no d2 nor no d3 variables. Note, functions to evaluate still take all decision variables. You also need to set simoptions.refine\_{}d=vfoptions.refine\_{}d.

What this means in the codes is you need one line of code where you set, e.g., vfoptions.refine_d=[0,1,1] and then other than this is just about being careful with the order in which you put your decision variables in n_d and d_grid, and how you send them as inputs to return fn and aprime fn (and fns to evaluate which will use all decision variables).

So for example in Life-Cycle Model 31 codes:


%% To speed up the use of riskyasset we use 'refine_d', which requires us to set the decision variables in a specific order
vfoptions.refine_d=[0,1,1]; % tell the code how many d1, d2, and d3 there are
% Idea is to distinguish three categories of decision variable:
%  d1: decision is in the ReturnFn but not in aprimeFn
%  d2: decision is in the aprimeFn but not in ReturnFn
%  d3: decision is in both ReturnFn and in aprimeFn
% Note: ReturnFn must use inputs (d1,d3,..) 
%       aprimeFn must use inputs (d2,d3,..)
% n_d must be set up as n_d=[n_d1, n_d2, n_d3]
% d_grid must be set up as d_grid=[d1_grid; d2_grid; d3_grid];
% It is possible to solve models without any d1, as is the case here.
simoptions.refine_d=vfoptions.refine_d;

I have updated all the Life-Cycle Models 31-35 to use vfoptions.refine_d. Other than the line setting the option itself, it was a trivial matter of rearranging the decision variables to have the right order, and then minor changes to the inputs for return fn and aprime fn. I also updated the example based on Cocco, Gomes & Maenhout (2005). An example of a d1 decision (which is in return fn but not aprime fn) is the endogenous labor supply decision in Life-Cycle Model 34.

This refine is intended as the standard setup for riskyasset from now on. You can still use the old approach which is no longer documented anywhere, it works fine just now prints a warning that you should switch over to using refine.

2 Likes

Works with and without Epstein-Zin preferences.

I had thought of doing refine for riskyasset way back, finally got around to it. Would have done it earlier if I’d realised just how big an improvement it is (and that it is pretty simple for user).

Other bonus is that it slashes the resource requirements, for example Life-Cycle Model 34 used not to be able to run on my desktop’s GPU, now it is easy.

If you have codes using riskyasset, it is 100% worth your time to overhaul them to use refine. Once you understand what to do (which might take 10 minutes to an hour) it will take less than 5 minutes to overhaul any code you have.

Life-cycle portfolio choice models are now easy fast enough that you could GMM estimate them.

3 Likes

Sounds great! I’ll update my working codes.

3 Likes

Hi Robert, I trying to update the portfolio-choice model with two markov (persistent labor shock z1 + permanent retirement shock, not age-dependent, z2) and 2 iid shocks (transitory labor shock e1 + transitory retirement shock e2). I receive error after I run value iteration problem for this model. (I try model 31 and it work fine.)

I modify the model to introduce refine as follows:

  • I add vfoptions.refine_d=[0,1,1]; and simoptions.refine_d=vfoptions.refine_d;

  • I change order in aprime function and in places when I called it (riskyshare,savings, … into savings, riskyshare, …)

  • Remove riskyshare from return function and in places when I called it.

    % riskyasset: aprime_val=aprimeFn(d,u)
    aprimeFn=@(riskyshare,savings, u, r) PortfolioCoiceModel4_aprimeFn(riskyshare,savings, u, r);
    
    ***
    
    ReturnFn =
    
    function_handle with value:
    
        @(savings,a,z1,z2,e1,e2,w,sigma,agej,Jr,pension,kappa_j)PortfolioCoiceModel4_ReturnFn(savings,a,z1,z2,e1,e2,w,sigma,agej,Jr,pension,kappa_j)
    
    ***
    
    >> %% Value function iteration problem
    disp('Test ValueFnIter')
    tic;
    [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);
    toc
    Test ValueFnIter
    Unrecognized function or variable 'zind'.
    
    Error in ValueFnIter_FHorz_RiskyAsset_Refine_nod1_noa1_e_raw (line 274)
            Policy2(1,:,:,:,jj)=shiftdim(d2index(maxindex+N_d3*zind++N_d3*N_z*eind),1);
    
    Error in ValueFnIter_Case1_FHorz_RiskyAsset (line 127)
                        [VKron, PolicyKron]=ValueFnIter_FHorz_RiskyAsset_Refine_nod1_noa1_e_raw(n_d2,n_d3,n_a2,n_z,vfoptions.n_e,n_u, N_j, d2_grid, d3_grid, a2_grid, z_gridvals_J, vfoptions.e_gridvals_J, u_grid, pi_z_J, vfoptions.pi_e_J, pi_u, ReturnFn, aprimeFn, Parameters, DiscountFactorParamNames, ReturnFnParamNames, aprimeFnParamNames, vfoptions);
    
    Error in ValueFnIter_Case1_FHorz (line 535)
        [V,Policy]=ValueFnIter_Case1_FHorz_RiskyAsset(n_d,n_a1,n_a2,n_z,vfoptions.n_u, N_j, d_grid, a1_grid, a2_grid, z_gridvals_J, vfoptions.u_grid, pi_z_J, vfoptions.pi_u, ReturnFn, vfoptions.aprimeFn, Parameters, DiscountFactorParamNames, ReturnFnParamNames, vfoptions);
    

Thank you.

Could you please send me a copy of your codes (email or github)? I will debug it Monday morning (shouldn’t take long).

1 Like

Thank you, Robert! I pasted two models on GitHub. One is without refine (model 4_0), and the other is with refine (model 4_1). Both models are otherwise the same. They include two Markov and two iid shocks. One Markov shock is age-dependent. You will see. Link: Codes

1 Like

Very interesting! This setup seems useful also for medels with human capital like Huggett Ventura and Yaron. Given the endogenous varible today a (human capital), a value for the decision variable d (effort) and a value for an iid shock u, I get the next-period value for the endogenous state (human capital tomorrow), a’. This a’ will not lie on the a grid, so the code uses interpolation.

Is my description correct or should we use some other algorithm for a model with human capital?

Thanks!

1 Like

For refine to work I need something that we want the max with respect to (like decision variables) and which is in one of the return function and the expectations term, but not the other.

So for riskyasset, I might have d1 in the return function but not in the expectations term [note, for riskyasset, the things in aprimeFn will be in the expectations]. Or I might have d2 in the expectations term (as it is in aprimeFn) but not in the return fn. Refine works by ‘removing’ this before putting the return fn and the expectations term together, which makes the problem smaller and faster (but only if the joint thing after refining is smaller than if I put them together without refining). The codes also ask you for “d3”, but this is just to know what the ‘other ones’ are, nothing is actually meaningfully done with these when you use refine.

Most of the gains here come from “d2”, things in the aprime but not the return fn.

In human capital models, at least the simpler ones, most decisions are in both the return fn and the aprime fn (so they are “d3”). There are no gains to be had from such variables. So at least for most human capital models there is little to nothing to gain by refining.

Note, some human capital models have “d1” things in return fn but not aprime fn. I don’t think refining “d1” is getting much of a speed boost in the riskyasset. But really this requires some runtime tests to find out. My impression/intuition is that most of the gain is from refining “d2”.

[What you describe is an accurate description of how VFI Toolkit handles ‘experienceassetu’, which is used for uncertain human capital.]

[In infinite horizon the “d1” are refined out, but there we do return function once, and then combine with expectations terms lots of times, the ‘lots’ means there are meaningful gains coming from refining “d1”. But this doesn’t have same impact in finite horizon as we just would use it once.]

2 Likes

@bob_rb should be fixed now (update to latest github). You do need to make one more change to your 4_1 (with refine) code. Your return fn and aprime fn are good. But you need to put n_d in the order d2,d3. So n_d=[51,101];) and same for d_grid=[riskyshare_grid; a_grid]; (relatedly, on line 136, change it to use n_d(1) when creating riskyshare_grid).

The run time for value fn problem was cut from 38s to 2s with refine on my desktop!
(I think the e shocks are massively increasing the gains from refining out d2.)

Note, if you compare the solutions to 4_0 and 4_1, you can easily compare V, but be careful comparing Policy as the decision variables are in the opposite orders.

1 Like

Thanks for the explanation! So basically if I would like to do Huggett Ventura and Yaron (2011) (with two endogenous states, financial assets and human capital), the recommended algorithm is experienceassetu, right?

Thank you for such a clear guide, Robert. It works like a rocket now. :slight_smile:

2 Likes

HVY2006 is certain human capital, which is done in toolkit as experienceasset.

HVY2011 is savings asset and uncertain human capital, which is done in toolkit as two endogenous states, one standard and the other experienceassetu.

Additionally, HVY2006 is a life-cycle model while HVY2011 is an OLG (so add general eqm).

If you want, I have codes implementing HVY2011 I can email you (they will become public sometime later this year, once I do a few more exercises and clean them up).

1 Like

Thanks Rob! I don’t need the codes for the moment, I just wanted to understand the different features provided by the toolkit

2 Likes

Rough schema:
riskyasset: aprime(d,u)
experienceasset: aprime(d,a)
experienceassetu: aprime(d,a,u)
Because human capital tomorrow should depend on what it is today plus some kind of investment you want the experienceasset (and if you want it uncertain, then experienceassetu).

2 Likes

I’ve eliminated the ability to use riskyasset without refine (you will get an error message if you don’t set vfoptions.refine_d). Was no reason to use it and codes were getting to messy under-the-hood. Needed to reorganise everything as part of getting semiz to work with riskyasset. Much saner internally now :sweat_smile:

1 Like

I tried to run the example based on Cocco, Gomes and Maenhout (2005) available here:LifeCycleOLGReadingList/CoccoGomesMaenhout2005 at main · robertdkirkby/LifeCycleOLGReadingList · GitHub

Unfortunately the code gives an error:

Permanent type: 1 of 3 

vfoptions = 

  struct with fields:

                 riskyasset: 1
          exoticpreferences: 'EpsteinZin'
                    EZutils: 0
             EZriskaversion: 'gamma'
                      EZeis: 'psi'
                   refine_d: [0 1 1]
                        n_e: 3
                     e_grid: [3×79 gpuArray]
                       pi_e: [3×79 gpuArray]
                   aprimeFn: [function_handle]
                        n_u: 5
                     u_grid: [5×1 gpuArray]
                       pi_u: [5×1 gpuArray]
                    verbose: 1
              verboseparams: 0
              ptypestorecpu: 0
                   parallel: 2
               returnmatrix: 2
                  lowmemory: 0
                   paroverz: 1
           divideandconquer: 0
            incrementaltype: 0
                polindorval: 1
                 outputkron: 0
    policy_forceintegertype: 0
                    dynasty: 0
            experienceasset: 0
           experienceassetu: 0
              residualasset: 0
               e_gridvals_J: [3×1×79 gpuArray]
                     pi_e_J: [3×79 gpuArray]

Unrecognized function or variable 'n_u'.

Error in ValueFnIter_Case1_FHorz (line 403)
            [V, Policy]=ValueFnIter_Case1_FHorz_EpsteinZin_RiskyAsset(n_d,n_a1,n_a2,n_z,n_u,N_j,d_grid,a1_grid, a2_grid, z_gridvals_J, u_grid, pi_z_J, pi_u, ReturnFn, vfoptions.aprimeFn, Parameters, DiscountFactorParamNames, ReturnFnParamNames, vfoptions);
                                                                                        ^^^
Error in ValueFnIter_Case1_FHorz_PType (line 145)
        [V_ii, Policy_ii]=ValueFnIter_Case1_FHorz(n_d_temp,n_a_temp,n_z_temp,N_j_temp,d_grid_temp, a_grid_temp, z_grid_temp, pi_z_temp, ReturnFn_temp, Parameters_temp, DiscountFactorParamNames_temp, [], vfoptions_temp);
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error in CoccoGomesMaenhout2005 (line 300)
[V, Policy]=ValueFnIter_Case1_FHorz_PType(n_d,n_a,n_z,N_j,Names_i, d_grid, a_grid, z_grid_J, pi_z_J, ReturnFn, Params, DiscountFactorParamNames,vfoptions);
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 Like

Besides the error that I’ve shown above, I also got a warning before:

Warning: Trying to hit the 2nd moment with farmertodaoptions.nSigmas at 1 or less is odd. It will put lots of
probability near edges of grid as you are trying to get the std dev, but you max grid points are only about
plus/minus one std dev (warning shows for farmertodaoptions.nSigmas<1.2). 
> In discretizeAR1_FarmerToda (line 103)
In CoccoGomesMaenhout2005 (line 189)

should be fixed. thanks for spotting

1 Like

I don’t really want to change this warning (I added it a few months back, was not active when I originally solved CGM2005). You can easily get rid of it either by setting farmertodaoptions.nSigmas to a higher number (like 1.5), or by switching to use discretizeAR1_Tauchen() instead of discretizeAR1_FarmerToda(). But really it is just warning you of a choice CGM2005 made (which is very common) that is using ±1 std dev as max/min grid points while discretizing, and which means you cannot sensibly hit the std dev of the innovations. (Code will still all run and solve just fine)

1 Like

Hi Robert, I’m finally trying to migrate to your latest ‘VFIToolkit-matlab-master’.

[I didn’t do so for some time because I have two portfolio choice models—one with and one without refinement. (I still use the model without refinement on some of yours old ‘VFIToolkit-matlab-master’ because I can model stock market participation costs in the return function, since I can reduce income if the risky share > 0.)]

Can you please check why the toolkit returns an error when I try to compute the ‘stationary distribution’ of households? I receive the same error even when running your latest models 31–35. The error looks like:

Unable to resolve the name 'vfoptions.refine_d'.

Error in StationaryDist_FHorz_Case1_RiskyAsset (line 20)
n_d23=n_d(vfoptions.refine_d(1)+1:sum(vfoptions.refine_d(1:3))); % decision variables for riskyasset

Error in StationaryDist_FHorz_Case1 (line 147)
    StationaryDist=StationaryDist_FHorz_Case1_RiskyAsset(jequaloneDist,AgeWeightParamNames,Policy,n_d,n_a,n_z,N_j,pi_z_J,Parameters,simoptions);

2 Likes