Example #1
0
    def make_EndOfPrdvFunc(self, EndOfPrdvP):
        """
        Construct the end-of-period value function for this period, storing it
        as an attribute of self for use by other methods.

        Parameters
        ----------
        EndOfPrdvP : np.array
            Array of end-of-period marginal value of assets corresponding to the
            asset values in self.aLvlNow x self.pLvlGrid.

        Returns
        -------
        none
        """
        def v_lvl_next(shocks, a_lvl, p_lvl):
            pLvlNext = self.p_lvl_next(shocks, p_lvl)
            mLvlNext = self.m_lvl_next(shocks, a_lvl, pLvlNext)
            return self.vFuncNext(mLvlNext, pLvlNext)

        # value in many possible future states
        vLvlNext = calc_expectation(self.IncShkDstn, v_lvl_next, self.aLvlNow,
                                    self.pLvlNow)
        vLvlNext = vLvlNext[:, :, 0]
        # expected value, averaging across states
        EndOfPrdv = self.DiscFacEff * vLvlNext
        # value transformed through inverse utility
        EndOfPrdvNvrs = self.uinv(EndOfPrdv)
        EndOfPrdvNvrsP = EndOfPrdvP * self.uinvP(EndOfPrdv)

        # Add points at mLvl=zero
        EndOfPrdvNvrs = np.concatenate((np.zeros(
            (self.pLvlGrid.size, 1)), EndOfPrdvNvrs),
                                       axis=1)
        if hasattr(self, "MedShkDstn"):
            EndOfPrdvNvrsP = np.concatenate((np.zeros(
                (self.pLvlGrid.size, 1)), EndOfPrdvNvrsP),
                                            axis=1)
        else:
            EndOfPrdvNvrsP = np.concatenate(
                (
                    np.reshape(EndOfPrdvNvrsP[:, 0], (self.pLvlGrid.size, 1)),
                    EndOfPrdvNvrsP,
                ),
                axis=1,
            )
            # This is a very good approximation, vNvrsPP = 0 at the asset minimum
        aLvl_temp = np.concatenate(
            (
                np.reshape(self.BoroCnstNat(self.pLvlGrid),
                           (self.pLvlGrid.size, 1)),
                self.aLvlNow,
            ),
            axis=1,
        )

        # Make an end-of-period value function for each persistent income level in the grid
        EndOfPrdvNvrsFunc_list = []
        for p in range(self.pLvlGrid.size):
            EndOfPrdvNvrsFunc_list.append(
                CubicInterp(
                    aLvl_temp[p, :] - self.BoroCnstNat(self.pLvlGrid[p]),
                    EndOfPrdvNvrs[p, :],
                    EndOfPrdvNvrsP[p, :],
                ))
        EndOfPrdvNvrsFuncBase = LinearInterpOnInterp1D(EndOfPrdvNvrsFunc_list,
                                                       self.pLvlGrid)

        # Re-adjust the combined end-of-period value function to account for the natural borrowing constraint shifter
        EndOfPrdvNvrsFunc = VariableLowerBoundFunc2D(EndOfPrdvNvrsFuncBase,
                                                     self.BoroCnstNat)
        self.EndOfPrdvFunc = ValueFuncCRRA(EndOfPrdvNvrsFunc, self.CRRA)
Example #2
0
def solveConsLaborIntMarg(solution_next, PermShkDstn, TranShkDstn, LivPrb,
                          DiscFac, CRRA, Rfree, PermGroFac, BoroCnstArt,
                          aXtraGrid, TranShkGrid, vFuncBool, CubicBool,
                          WageRte, LbrCost):
    '''
    Solves one period of the consumption-saving model with endogenous labor supply
    on the intensive margin by using the endogenous grid method to invert the first
    order conditions for optimal composite consumption and between consumption and
    leisure, obviating any search for optimal controls.
    
    Parameters 
    ----------
    solution_next : ConsumerLaborSolution
        The solution to the next period's problem; must have the attributes
        vPfunc and bNrmMinFunc representing marginal value of bank balances and
        minimum (normalized) bank balances as a function of the transitory shock.
    PermShkDstn: [np.array]
        Discrete distribution of permanent productivity shocks. 
    TranShkDstn: [np.array]
        Discrete distribution of transitory productivity shocks.       
    LivPrb : float
        Survival probability; likelihood of being alive at the beginning of
        the succeeding period. 
    DiscFac : float
        Intertemporal discount factor.
    CRRA : float
        Coefficient of relative risk aversion over the composite good.  
    Rfree : float
        Risk free interest rate on assets retained at the end of the period.
    PermGroFac : float                                                         
        Expected permanent income growth factor for next period.
    BoroCnstArt: float or None
        Borrowing constraint for the minimum allowable assets to end the
        period with.  Currently not handled, must be None.
    aXtraGrid: np.array
        Array of "extra" end-of-period asset values-- assets above the
        absolute minimum acceptable level.
    TranShkGrid: np.array
        Grid of transitory shock values to use as a state grid for interpolation.
    vFuncBool: boolean
        An indicator for whether the value function should be computed and
        included in the reported solution.  Not yet handled, must be False.
    CubicBool: boolean
        An indicator for whether the solver should use cubic or linear interpolation.
        Cubic interpolation is not yet handled, must be False.
    WageRte: float
        Wage rate per unit of labor supplied.
    LbrCost: float
        Cost parameter for supplying labor: u_t = U(x_t), x_t = c_t*z_t^LbrCost,
        where z_t is leisure = 1 - Lbr_t.
        
    Returns
    -------
    solution_now : ConsumerLaborSolution
        The solution to this period's problem, including a consumption function
        cFunc, a labor supply function LbrFunc, and a marginal value function vPfunc;
        each are defined over normalized bank balances and transitory prod shock.
        Also includes bNrmMinNow, the minimum permissible bank balances as a function
        of the transitory productivity shock.
    '''
    # Make sure the inputs for this period are valid: CRRA > LbrCost/(1+LbrCost)
    # and CubicBool = False.  CRRA condition is met automatically when CRRA >= 1.
    frac = 1. / (1. + LbrCost)
    if CRRA <= frac * LbrCost:
        print(
            'Error: make sure CRRA coefficient is strictly greater than alpha/(1+alpha).'
        )
        sys.exit()
    if BoroCnstArt is not None:
        print(
            'Error: Model cannot handle artificial borrowing constraint yet. ')
        sys.exit()
    if vFuncBool or CubicBool is True:
        print('Error: Model cannot handle cubic interpolation yet.')
        sys.exit()

    # Unpack next period's solution and the productivity shock distribution, and define the inverse (marginal) utilty function
    vPfunc_next = solution_next.vPfunc
    TranShkPrbs = TranShkDstn[0]
    TranShkVals = TranShkDstn[1]
    PermShkPrbs = PermShkDstn[0]
    PermShkVals = PermShkDstn[1]
    TranShkCount = TranShkPrbs.size
    PermShkCount = PermShkPrbs.size
    uPinv = lambda X: CRRAutilityP_inv(X, gam=CRRA)

    # Make tiled versions of the grid of a_t values and the components of the shock distribution
    aXtraCount = aXtraGrid.size
    bNrmGrid = aXtraGrid  # Next period's bank balances before labor income
    bNrmGrid_rep = np.tile(
        np.reshape(bNrmGrid, (aXtraCount, 1)), (1, TranShkCount)
    )  # Replicated axtraGrid of b_t values (bNowGrid) for each transitory (productivity) shock
    TranShkVals_rep = np.tile(
        np.reshape(TranShkVals, (1, TranShkCount)),
        (aXtraCount,
         1))  # Replicated transitory shock values for each a_t state
    TranShkPrbs_rep = np.tile(
        np.reshape(TranShkPrbs, (1, TranShkCount)),
        (aXtraCount,
         1))  # Replicated transitory shock probabilities for each a_t state

    # Construct a function that gives marginal value of next period's bank balances *just before* the transitory shock arrives
    vPNext = vPfunc_next(
        bNrmGrid_rep, TranShkVals_rep
    )  # Next period's marginal value at every transitory shock and every bank balances gridpoint
    vPbarNext = np.sum(
        vPNext * TranShkPrbs_rep, axis=1
    )  # Integrate out the transitory shocks (in TranShkVals direction) to get expected vP just before the transitory shock
    vPbarNvrsNext = uPinv(
        vPbarNext
    )  # Transformed marginal value through the inverse marginal utility function to "decurve" it
    vPbarNvrsFuncNext = LinearInterp(
        np.insert(bNrmGrid, 0, 0.0), np.insert(vPbarNvrsNext, 0, 0.0)
    )  # Linear interpolation over b_{t+1}, adding a point at minimal value of b = 0.
    vPbarFuncNext = MargValueFunc(
        vPbarNvrsFuncNext, CRRA
    )  # "Recurve" the intermediate marginal value function through the marginal utility function

    # Get next period's bank balances at each permanent shock from each end-of-period asset values
    aNrmGrid_rep = np.tile(
        np.reshape(aXtraGrid, (aXtraCount, 1)), (1, PermShkCount)
    )  # Replicated grid of a_t values for each permanent (productivity) shock
    PermShkVals_rep = np.tile(
        np.reshape(PermShkVals, (1, PermShkCount)),
        (aXtraCount,
         1))  # Replicated permanent shock values for each a_t value
    PermShkPrbs_rep = np.tile(
        np.reshape(PermShkPrbs, (1, PermShkCount)),
        (aXtraCount,
         1))  # Replicated permanent shock probabilities for each a_t value
    bNrmNext = (Rfree / (PermGroFac * PermShkVals_rep)) * aNrmGrid_rep

    # Calculate marginal value of end-of-period assets at each a_t gridpoint
    vPbarNext = (PermGroFac * PermShkVals_rep)**(-CRRA) * vPbarFuncNext(
        bNrmNext
    )  # Get marginal value of bank balances next period at each shock
    EndOfPrdvP = DiscFac * Rfree * LivPrb * np.sum(
        vPbarNext * PermShkPrbs_rep, axis=1,
        keepdims=True)  # Take expectation across permanent income shocks

    # Compute scaling factor for each transitory shock
    TranShkScaleFac_temp = frac * (WageRte * TranShkGrid)**(LbrCost * frac) * (
        LbrCost**(-LbrCost * frac) + LbrCost**(frac))
    TranShkScaleFac = np.reshape(
        TranShkScaleFac_temp,
        (1, TranShkGrid.size))  # Flip it to be a row vector

    # Use the first order condition to compute an array of "composite good" x_t values corresponding to (a_t,theta_t) values
    xNow = (np.dot(EndOfPrdvP,
                   TranShkScaleFac))**(-1. / (CRRA - LbrCost * frac))

    # Transform the composite good x_t values into consumption c_t and leisure z_t values
    TranShkGrid_rep = np.tile(np.reshape(TranShkGrid, (1, TranShkGrid.size)),
                              (aXtraCount, 1))
    xNowPow = xNow**frac  # Will use this object multiple times in math below
    cNrmNow = (
        ((WageRte * TranShkGrid_rep) / LbrCost)**(LbrCost * frac)
    ) * xNowPow  # Find optimal consumption from optimal composite good
    LsrNow = (
        LbrCost / (WageRte * TranShkGrid_rep)
    )**frac * xNowPow  # Find optimal leisure from optimal composite good

    # The zero-th transitory shock is TranShk=0, and the solution is to not work: Lsr = 1, Lbr = 0.
    cNrmNow[:, 0] = uPinv(EndOfPrdvP.flatten())
    LsrNow[:, 0] = 1.0

    # Agent cannot choose to work a negative amount of time. When this occurs, set
    # leisure to one and recompute consumption using simplified first order condition.
    violates_labor_constraint = LsrNow > 1.  # Find where labor would be negative if unconstrained
    EndOfPrdvP_temp = np.tile(np.reshape(EndOfPrdvP, (aXtraCount, 1)),
                              (1, TranShkCount))
    cNrmNow[violates_labor_constraint] = uPinv(
        EndOfPrdvP_temp[violates_labor_constraint])
    LsrNow[violates_labor_constraint] = 1.  # Set up z =1, upper limit

    # Calculate the endogenous bNrm states by inverting the within-period transition
    aNrmNow_rep = np.tile(np.reshape(aXtraGrid, (aXtraCount, 1)),
                          (1, TranShkGrid.size))
    bNrmNow = aNrmNow_rep - WageRte * TranShkGrid_rep + cNrmNow + WageRte * TranShkGrid_rep * LsrNow

    # Add an extra gridpoint at the absolute minimal valid value for b_t for each TranShk;
    # this corresponds to working 100% of the time and consuming nothing.
    bNowArray = np.concatenate((np.reshape(-WageRte * TranShkGrid,
                                           (1, TranShkGrid.size)), bNrmNow),
                               axis=0)
    cNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), cNrmNow),
                               axis=0)  # Consume nothing
    LsrNowArray = np.concatenate((np.zeros((1, TranShkGrid.size)), LsrNow),
                                 axis=0)  # And no leisure!
    LsrNowArray[0, 0] = 1.0  # Don't work at all if TranShk=0, even if bNrm=0
    LbrNowArray = 1.0 - LsrNowArray  # Labor is the complement of leisure

    # Get (pseudo-inverse) marginal value of bank balances using end of period
    # marginal value of assets (envelope condition), adding a column of zeros
    # zeros on the left edge, representing the limit at the minimum value of b_t.
    vPnvrsNowArray = np.concatenate((np.zeros(
        (1, TranShkGrid.size)), uPinv(EndOfPrdvP_temp)))  # Concatenate

    # Construct consumption and marginal value functions for this period
    bNrmMinNow = LinearInterp(TranShkGrid, bNowArray[0, :])

    # Loop over each transitory shock and make a linear interpolation to get lists
    # of optimal consumption, labor and (pseudo-inverse) marginal value by TranShk
    cFuncNow_list = []
    LbrFuncNow_list = []
    vPnvrsFuncNow_list = []
    for j in range(TranShkGrid.size):
        bNrmNow_temp = bNowArray[:, j] - bNowArray[
            0,
            j]  # Adjust bNrmNow for this transitory shock, so bNrmNow_temp[0] = 0
        cFuncNow_list.append(
            LinearInterp(bNrmNow_temp, cNowArray[:, j])
        )  # Make consumption function for this transitory shock
        LbrFuncNow_list.append(LinearInterp(
            bNrmNow_temp,
            LbrNowArray[:,
                        j]))  # Make labor function for this transitory shock
        vPnvrsFuncNow_list.append(
            LinearInterp(bNrmNow_temp, vPnvrsNowArray[:, j])
        )  # Make pseudo-inverse marginal value function for this transitory shock

    # Make linear interpolation by combining the lists of consumption, labor and marginal value functions
    cFuncNowBase = LinearInterpOnInterp1D(cFuncNow_list, TranShkGrid)
    LbrFuncNowBase = LinearInterpOnInterp1D(LbrFuncNow_list, TranShkGrid)
    vPnvrsFuncNowBase = LinearInterpOnInterp1D(vPnvrsFuncNow_list, TranShkGrid)

    # Construct consumption, labor, pseudo-inverse marginal value functions with
    # bNrmMinNow as the lower bound.  This removes the adjustment in the loop above.
    cFuncNow = VariableLowerBoundFunc2D(cFuncNowBase, bNrmMinNow)
    LbrFuncNow = VariableLowerBoundFunc2D(LbrFuncNowBase, bNrmMinNow)
    vPnvrsFuncNow = VariableLowerBoundFunc2D(vPnvrsFuncNowBase, bNrmMinNow)

    # Construct the marginal value function by "recurving" its pseudo-inverse
    vPfuncNow = MargValueFunc2D(vPnvrsFuncNow, CRRA)

    # Make a solution object for this period and return it
    solution = ConsumerLaborSolution(cFunc=cFuncNow,
                                     LbrFunc=LbrFuncNow,
                                     vPfunc=vPfuncNow,
                                     bNrmMin=bNrmMinNow)
    return solution