def post_solve(self):
        self.solution_fast = deepcopy(self.solution)

        if self.cycles == 0:
            terminal = 1
        else:
            terminal = self.cycles
            self.solution[terminal] = self.solution_terminal_cs

        for i in range(terminal):
            solution = self.solution[i]

            # Construct the consumption function as a linear interpolation.
            cFunc = LinearInterp(solution.mNrm, solution.cNrm)

            """
            Defines the value and marginal value functions for this period.
            Uses the fact that for a perfect foresight CRRA utility problem,
            if the MPC in period t is :math:`\kappa_{t}`, and relative risk
            aversion :math:`\rho`, then the inverse value vFuncNvrs has a
            constant slope of :math:`\kappa_{t}^{-\rho/(1-\rho)}` and
            vFuncNvrs has value of zero at the lower bound of market resources
            mNrmMin.  See PerfForesightConsumerType.ipynb documentation notebook
            for a brief explanation and the links below for a fuller treatment.

            https://www.econ2.jhu.edu/people/ccarroll/public/lecturenotes/consumption/PerfForesightCRRA/#vFuncAnalytical
            https://www.econ2.jhu.edu/people/ccarroll/SolvingMicroDSOPs/#vFuncPF
            """

            vFuncNvrs = LinearInterp(
                np.array([solution.mNrmMin, solution.mNrmMin + 1.0]),
                np.array([0.0, solution.vFuncNvrsSlope]),
            )
            vFunc = ValueFuncCRRA(vFuncNvrs, self.CRRA)
            vPfunc = MargValueFuncCRRA(cFunc, self.CRRA)

            consumer_solution = ConsumerSolution(
                cFunc=cFunc,
                vFunc=vFunc,
                vPfunc=vPfunc,
                mNrmMin=solution.mNrmMin,
                hNrm=solution.hNrm,
                MPCmin=solution.MPCmin,
                MPCmax=solution.MPCmax,
            )

            Ex_IncNext = 1.0  # Perfect foresight income of 1

            # Add mNrmStE to the solution and return it
            consumer_solution.mNrmStE = _add_mNrmStENumba(
                self.Rfree,
                self.PermGroFac[i],
                solution.mNrm,
                solution.cNrm,
                solution.mNrmMin,
                Ex_IncNext,
                _find_mNrmStE,
            )

            self.solution[i] = consumer_solution
    def use_points_for_interpolation(self, cNrm, mNrm, interpolator):
        """
        Make a basic solution object with a consumption function and marginal
        value function (unconditional on the preference shock).

        Parameters
        ----------
        cNrm : np.array
            Consumption points for interpolation.
        mNrm : np.array
            Corresponding market resource points for interpolation.
        interpolator : function
            A function that constructs and returns a consumption function.

        Returns
        -------
        solution_now : ConsumerSolution
            The solution to this period's consumption-saving problem, with a
            consumption function, marginal value function, and minimum m.
        """
        # Make the preference-shock specific consumption functions
        PrefShkCount = self.PrefShkVals.size
        cFunc_list = []
        for j in range(PrefShkCount):
            MPCmin_j = self.MPCminNow * self.PrefShkVals[j]**(1.0 / self.CRRA)
            cFunc_this_shock = LowerEnvelope(
                LinearInterp(
                    mNrm[j, :],
                    cNrm[j, :],
                    intercept_limit=self.hNrmNow * MPCmin_j,
                    slope_limit=MPCmin_j,
                ),
                self.cFuncNowCnst,
            )
            cFunc_list.append(cFunc_this_shock)

        # Combine the list of consumption functions into a single interpolation
        cFuncNow = LinearInterpOnInterp1D(cFunc_list, self.PrefShkVals)

        # Make the ex ante marginal value function (before the preference shock)
        m_grid = self.aXtraGrid + self.mNrmMinNow
        vP_vec = np.zeros_like(m_grid)
        for j in range(
                PrefShkCount):  # numeric integration over the preference shock
            vP_vec += (self.uP(cFunc_list[j](m_grid)) * self.PrefShkPrbs[j] *
                       self.PrefShkVals[j])
        vPnvrs_vec = self.uPinv(vP_vec)
        vPfuncNow = MargValueFuncCRRA(LinearInterp(m_grid, vPnvrs_vec),
                                      self.CRRA)

        # Store the results in a solution object and return it
        solution_now = ConsumerSolution(cFunc=cFuncNow,
                                        vPfunc=vPfuncNow,
                                        mNrmMin=self.mNrmMinNow)
        return solution_now
Example #3
0
    def update_solution_terminal(self):
        """
        Update the terminal period solution.  This method should be run when a
        new AgentType is created or when CRRA changes.
        """

        self.solution_terminal_cs = ConsumerSolution(
            cFunc=self.cFunc_terminal_,
            vFunc=ValueFuncCRRA(self.cFunc_terminal_, self.CRRA),
            vPfunc=MargValueFuncCRRA(self.cFunc_terminal_, self.CRRA),
            vPPfunc=MargMargValueFuncCRRA(self.cFunc_terminal_, self.CRRA),
            mNrmMin=0.0,
            hNrm=0.0,
            MPCmin=1.0,
            MPCmax=1.0,
        )
    def use_points_for_interpolation(self, cLvl, mLvl, pLvl, interpolator):
        """
        Constructs a basic solution for this period, including the consumption
        function and marginal value function.

        Parameters
        ----------
        cLvl : np.array
            Consumption points for interpolation.
        mLvl : np.array
            Corresponding market resource points for interpolation.
        pLvl : np.array
            Corresponding persistent income level points for interpolation.
        interpolator : function
            A function that constructs and returns a consumption function.

        Returns
        -------
        solution_now : ConsumerSolution
            The solution to this period's consumption-saving problem, with a
            consumption function, marginal value function, and minimum m.
        """
        # Construct the unconstrained consumption function
        cFuncNowUnc = interpolator(mLvl, pLvl, cLvl)

        # Combine the constrained and unconstrained functions into the true consumption function
        cFuncNow = LowerEnvelope2D(cFuncNowUnc, self.cFuncNowCnst)

        # Make the marginal value function
        vPfuncNow = self.make_vPfunc(cFuncNow)

        # Pack up the solution and return it
        solution_now = ConsumerSolution(cFunc=cFuncNow,
                                        vPfunc=vPfuncNow,
                                        mNrmMin=0.0)
        return solution_now
class GenIncProcessConsumerType(IndShockConsumerType):
    """
    A consumer type with idiosyncratic shocks to persistent and transitory income.
    His problem is defined by a sequence of income distributions, survival prob-
    abilities, and persistent income growth functions, as well as time invariant
    values for risk aversion, discount factor, the interest rate, the grid of
    end-of-period assets, and an artificial borrowing constraint.

    See init_explicit_perm_inc for a dictionary of the
    keywords that should be passed to the constructor.

    Parameters
    ----------
    cycles : int
        Number of times the sequence of periods should be solved.
    """

    cFunc_terminal_ = BilinearInterp(np.array([[0.0, 0.0], [1.0, 1.0]]),
                                     np.array([0.0, 1.0]), np.array([0.0,
                                                                     1.0]))
    solution_terminal_ = ConsumerSolution(cFunc=cFunc_terminal_,
                                          mNrmMin=0.0,
                                          hNrm=0.0,
                                          MPCmin=1.0,
                                          MPCmax=1.0)

    state_vars = ['pLvl', "mLvl", 'aLvl']

    def __init__(self, **kwds):
        params = init_explicit_perm_inc.copy()
        params.update(kwds)

        # Initialize a basic ConsumerType
        IndShockConsumerType.__init__(self, **params)
        self.solve_one_period = make_one_period_oo_solver(
            ConsGenIncProcessSolver)

        # a poststate?
        self.state_now['aLvl'] = None
        self.state_prev['aLvl'] = None

        # better way to do this...
        self.state_now["mLvl"] = None
        self.state_prev["mLvl"] = None

    def pre_solve(self):
        #        AgentType.pre_solve()
        self.update_solution_terminal()

    def update(self):
        """
        Update the income process, the assets grid, the persistent income grid,
        and the terminal solution.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        IndShockConsumerType.update(self)
        self.update_pLvlNextFunc()
        self.update_pLvlGrid()

    def update_solution_terminal(self):
        """
        Update the terminal period solution.  This method should be run when a
        new AgentType is created or when CRRA changes.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        self.solution_terminal.vFunc = ValueFuncCRRA(self.cFunc_terminal_,
                                                     self.CRRA)
        self.solution_terminal.vPfunc = MargValueFuncCRRA(
            self.cFunc_terminal_, self.CRRA)
        self.solution_terminal.vPPfunc = MargMargValueFuncCRRA(
            self.cFunc_terminal_, self.CRRA)
        self.solution_terminal.hNrm = 0.0  # Don't track normalized human wealth
        self.solution_terminal.hLvl = lambda p: np.zeros_like(p)
        # But do track absolute human wealth by persistent income
        self.solution_terminal.mLvlMin = lambda p: np.zeros_like(p)
        # And minimum allowable market resources by perm inc

    def update_pLvlNextFunc(self):
        """
        A dummy method that creates a trivial pLvlNextFunc attribute that has
        no persistent income dynamics.  This method should be overwritten by
        subclasses in order to make (e.g.) an AR1 income process.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        pLvlNextFuncBasic = LinearInterp(np.array([0.0, 1.0]),
                                         np.array([0.0, 1.0]))
        self.pLvlNextFunc = self.T_cycle * [pLvlNextFuncBasic]
        self.add_to_time_vary("pLvlNextFunc")

    def install_retirement_func(self):
        """
        Installs a special pLvlNextFunc representing retirement in the correct
        element of self.pLvlNextFunc.  Draws on the attributes T_retire and
        pLvlNextFuncRet.  If T_retire is zero or pLvlNextFuncRet does not
        exist, this method does nothing.  Should only be called from within the
        method update_pLvlNextFunc, which ensures that time is flowing forward.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        if (not hasattr(self, "pLvlNextFuncRet")) or self.T_retire == 0:
            return
        t = self.T_retire
        self.pLvlNextFunc[t] = self.pLvlNextFuncRet

    def update_pLvlGrid(self):
        """
        Update the grid of persistent income levels.  Currently only works for
        infinite horizon models (cycles=0) and lifecycle models (cycles=1).  Not
        clear what to do about cycles>1 because the distribution of persistent
        income will be different within a period depending on how many cycles
        have elapsed.  This method uses a simulation approach to generate the
        pLvlGrid at each period of the cycle, drawing on the initial distribution
        of persistent income, the pLvlNextFuncs, and the attribute pLvlPctiles.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        LivPrbAll = np.array(self.LivPrb)

        # Simulate the distribution of persistent income levels by t_cycle in a lifecycle model
        if self.cycles == 1:
            pLvlNow = Lognormal(self.pLvlInitMean,
                                sigma=self.pLvlInitStd,
                                seed=31382).draw(self.AgentCount)
            pLvlGrid = []  # empty list of time-varying persistent income grids
            # Calculate distribution of persistent income in each period of lifecycle
            for t in range(len(self.PermShkStd)):
                if t > 0:
                    PermShkNow = self.PermShkDstn[t -
                                                  1].draw(N=self.AgentCount)
                    pLvlNow = self.pLvlNextFunc[t - 1](pLvlNow) * PermShkNow
                pLvlGrid.append(
                    get_percentiles(pLvlNow, percentiles=self.pLvlPctiles))

        # Calculate "stationary" distribution in infinite horizon (might vary across periods of cycle)
        elif self.cycles == 0:
            T_long = 1000  # Number of periods to simulate to get to "stationary" distribution
            pLvlNow = Lognormal(mu=self.pLvlInitMean,
                                sigma=self.pLvlInitStd,
                                seed=31382).draw(self.AgentCount)
            t_cycle = np.zeros(self.AgentCount, dtype=int)
            for t in range(T_long):
                LivPrb = LivPrbAll[
                    t_cycle]  # Determine who dies and replace them with newborns
                draws = Uniform(seed=t).draw(self.AgentCount)
                who_dies = draws > LivPrb
                pLvlNow[who_dies] = Lognormal(self.pLvlInitMean,
                                              self.pLvlInitStd,
                                              seed=t + 92615).draw(
                                                  np.sum(who_dies))
                t_cycle[who_dies] = 0

                for j in range(self.T_cycle):  # Update persistent income
                    these = t_cycle == j
                    PermShkTemp = self.PermShkDstn[j].draw(N=np.sum(these))
                    pLvlNow[these] = self.pLvlNextFunc[j](
                        pLvlNow[these]) * PermShkTemp
                t_cycle = t_cycle + 1
                t_cycle[t_cycle == self.T_cycle] = 0

            # We now have a "long run stationary distribution", extract percentiles
            pLvlGrid = []  # empty list of time-varying persistent income grids
            for t in range(self.T_cycle):
                these = t_cycle == t
                pLvlGrid.append(
                    get_percentiles(pLvlNow[these],
                                    percentiles=self.pLvlPctiles))

        # Throw an error if cycles>1
        else:
            assert False, "Can only handle cycles=0 or cycles=1!"

        # Store the result and add attribute to time_vary
        self.pLvlGrid = pLvlGrid
        self.add_to_time_vary("pLvlGrid")

    def sim_birth(self, which_agents):
        """
        Makes new consumers for the given indices.  Initialized variables include aNrm and pLvl, as
        well as time variables t_age and t_cycle.  Normalized assets and persistent income levels
        are drawn from lognormal distributions given by aNrmInitMean and aNrmInitStd (etc).

        Parameters
        ----------
        which_agents : np.array(Bool)
            Boolean array of size self.AgentCount indicating which agents should be "born".

        Returns
        -------
        None
        """
        # Get and store states for newly born agents
        N = np.sum(which_agents)  # Number of new consumers to make
        aNrmNow_new = Lognormal(self.aNrmInitMean,
                                self.aNrmInitStd,
                                seed=self.RNG.randint(0, 2**31 - 1)).draw(N)
        self.state_now['pLvl'][which_agents] = Lognormal(
            self.pLvlInitMean,
            self.pLvlInitStd,
            seed=self.RNG.randint(0, 2**31 - 1)).draw(N)
        self.state_now['aLvl'][
            which_agents] = aNrmNow_new * self.state_now['pLvl'][which_agents]
        self.t_age[
            which_agents] = 0  # How many periods since each agent was born
        self.t_cycle[
            which_agents] = 0  # Which period of the cycle each agent is currently in

    def transition(self):
        """
        Calculates updated values of normalized market resources
        and persistent income level for each
        agent.  Uses pLvlNow, aLvlNow, PermShkNow, TranShkNow.

        Parameters
        ----------
        None

        Returns
        -------
        pLvlNow
        mLvlNow
        """
        aLvlPrev = self.state_prev['aLvl']
        RfreeNow = self.get_Rfree()

        # Calculate new states: normalized market resources
        # and persistent income level
        pLvlNow = np.zeros_like(aLvlPrev)

        for t in range(self.T_cycle):
            these = t == self.t_cycle
            pLvlNow[these] = (
                self.pLvlNextFunc[t - 1](self.state_prev['pLvl'][these]) *
                self.shocks['PermShk'][these])

        #state value
        bLvlNow = RfreeNow * aLvlPrev  # Bank balances before labor income

        # Market resources after income - state value
        mLvlNow = bLvlNow + \
                  self.shocks['TranShk'] * \
                  pLvlNow

        return (pLvlNow, mLvlNow)

    def get_controls(self):
        """
        Calculates consumption for each consumer of this type using the consumption functions.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        cLvlNow = np.zeros(self.AgentCount) + np.nan
        MPCnow = np.zeros(self.AgentCount) + np.nan

        for t in range(self.T_cycle):
            these = t == self.t_cycle
            cLvlNow[these] = self.solution[t].cFunc(
                self.state_now["mLvl"][these], self.state_now['pLvl'][these])
            MPCnow[these] = self.solution[t].cFunc.derivativeX(
                self.state_now["mLvl"][these], self.state_now['pLvl'][these])
        self.controls["cLvl"] = cLvlNow
        self.MPCnow = MPCnow

    def get_poststates(self):
        """
        Calculates end-of-period assets for each consumer of this type.
        Identical to version in IndShockConsumerType but uses Lvl rather than Nrm variables.

        Parameters
        ----------
        None

        Returns
        -------
        None
        """
        self.state_now['aLvl'] = self.state_now["mLvl"] - self.controls["cLvl"]
        # moves now to prev
        AgentType.get_poststates(self)
Example #6
0
    def post_solve(self):
        self.solution_fast = deepcopy(self.solution)

        if self.cycles == 0:
            cycles = 1
        else:
            cycles = self.cycles
            self.solution[-1] = self.solution_terminal_cs

        for i in range(cycles):
            for j in range(self.T_cycle):
                solution = self.solution[i * self.T_cycle + j]

                # Define the borrowing constraint (limiting consumption function)
                cFuncNowCnst = LinearInterp(
                    np.array([solution.mNrmMin, solution.mNrmMin + 1]),
                    np.array([0.0, 1.0]),
                )

                """
                Constructs a basic solution for this period, including the consumption
                function and marginal value function.
                """

                if self.CubicBool:
                    # Makes a cubic spline interpolation of the unconstrained consumption
                    # function for this period.
                    cFuncNowUnc = CubicInterp(
                        solution.mNrm,
                        solution.cNrm,
                        solution.MPC,
                        solution.cFuncLimitIntercept,
                        solution.cFuncLimitSlope,
                    )
                else:
                    # Makes a linear interpolation to represent the (unconstrained) consumption function.
                    # Construct the unconstrained consumption function
                    cFuncNowUnc = LinearInterp(
                        solution.mNrm,
                        solution.cNrm,
                        solution.cFuncLimitIntercept,
                        solution.cFuncLimitSlope,
                    )

                # Combine the constrained and unconstrained functions into the true consumption function
                cFuncNow = LowerEnvelope(cFuncNowUnc, cFuncNowCnst)

                # Make the marginal value function and the marginal marginal value function
                vPfuncNow = MargValueFuncCRRA(cFuncNow, self.CRRA)

                # Pack up the solution and return it
                consumer_solution = ConsumerSolution(
                    cFunc=cFuncNow,
                    vPfunc=vPfuncNow,
                    mNrmMin=solution.mNrmMin,
                    hNrm=solution.hNrm,
                    MPCmin=solution.MPCmin,
                    MPCmax=solution.MPCmax,
                )

                if self.vFuncBool:
                    vNvrsFuncNow = CubicInterp(
                        solution.mNrmGrid,
                        solution.vNvrs,
                        solution.vNvrsP,
                        solution.MPCminNvrs * solution.hNrm,
                        solution.MPCminNvrs,
                    )
                    vFuncNow = ValueFuncCRRA(vNvrsFuncNow, self.CRRA)

                    consumer_solution.vFunc = vFuncNow

                if self.CubicBool or self.vFuncBool:
                    _searchFunc = (
                        _find_mNrmStECubic if self.CubicBool else _find_mNrmStELinear
                    )
                    # Add mNrmStE to the solution and return it
                    consumer_solution.mNrmStE = _add_mNrmStEIndNumba(
                        self.PermGroFac[j],
                        self.Rfree,
                        solution.Ex_IncNext,
                        solution.mNrmMin,
                        solution.mNrm,
                        solution.cNrm,
                        solution.MPC,
                        solution.MPCmin,
                        solution.hNrm,
                        _searchFunc,
                    )

                self.solution[i * self.T_cycle + j] = consumer_solution
Example #7
0
def solveConsMarkovALT(solution_next,IncomeDstn,LivPrb,DiscFac,CRRA,Rfree,PermGroFac,uPfac,
                                 MrkvArray,BoroCnstArt,aXtraGrid,vFuncBool,CubicBool):
    '''
    Solves a single period consumption-saving problem with risky income and
    stochastic transitions between discrete states, in a Markov fashion.  Has
    identical inputs as solveConsIndShock, except for a discrete
    Markov transitionrule MrkvArray.  Markov states can differ in their interest
    factor, permanent growth factor, and income distribution, so the inputs Rfree,
    PermGroFac, and IncomeDstn are arrays or lists specifying those values in each
    (succeeding) Markov state.

    Parameters
    ----------
    solution_next : ConsumerSolution
        The solution to next period's one period problem.
    IncomeDstn : DiscreteDistribution
        A representation of permanent and transitory income shocks that might
        arrive at the beginning of next period.
    LivPrb : float
        Survival probability; likelihood of being alive at the beginning of
        the succeeding period.
    DiscFac : float
        Intertemporal discount factor for future utility.
    CRRA : float
        Coefficient of relative risk aversion.
    Rfree : np.array
        Risk free interest factor on end-of-period assets for each Markov
        state in the succeeding period.
    PermGroFac : np.array
        Expected permanent income growth factor at the end of this period
        for each Markov state in the succeeding period.
    uPfac : np.array
        Scaling factor for (marginal) utility in each current Markov state.
    MrkvArray : np.array
        An NxN array representing a Markov transition matrix between discrete
        states.  The i,j-th element of MrkvArray is the probability of
        moving from state i in period t to state j in period t+1.
    BoroCnstArt: float or None
        Borrowing constraint for the minimum allowable assets to end the
        period with.  If it is less than the natural borrowing constraint,
        then it is irrelevant; BoroCnstArt=None indicates no artificial bor-
        rowing constraint.
    aXtraGrid: np.array
        Array of "extra" end-of-period asset values-- assets above the
        absolute minimum acceptable level.
    vFuncBool: boolean
        An indicator for whether the value function should be computed and
        included in the reported solution.  Not used.
    CubicBool: boolean
        An indicator for whether the solver should use cubic or linear inter-
        polation.  Not used.

    Returns
    -------
    solution : ConsumerSolution
        The solution to the single period consumption-saving problem. Includes
        a consumption function cFunc (using cubic or linear splines), a marg-
        inal value function vPfunc, a minimum acceptable level of normalized
        market resources mNrmMin.  All of these attributes are lists or arrays, 
        with elements corresponding to the current Markov state.  E.g.
        solution.cFunc[0] is the consumption function when in the i=0 Markov
        state this period.
    '''
    # Get sizes of grids
    aCount = aXtraGrid.size
    StateCount = MrkvArray.shape[0]

    # Loop through next period's states, assuming we reach each one at a time.
    # Construct EndOfPrdvP_cond functions for each state.
    BoroCnstNat_cond = []
    EndOfPrdvPfunc_cond = []
    for j in range(StateCount):
        # Unpack next period's solution
        vPfuncNext = solution_next.vPfunc[j]
        mNrmMinNext = solution_next.mNrmMin[j]

        # Unpack the income shocks
        ShkPrbsNext = IncomeDstn[j].pmf
        PermShkValsNext = IncomeDstn[j].X[0]
        TranShkValsNext = IncomeDstn[j].X[1]
        ShkCount = ShkPrbsNext.size
        aXtra_tiled = np.tile(np.reshape(aXtraGrid, (aCount, 1)), (1, ShkCount))

        # Make tiled versions of the income shocks
        # Dimension order: aNow, Shk
        ShkPrbsNext_tiled = np.tile(np.reshape(ShkPrbsNext, (1, ShkCount)), (aCount, 1))
        PermShkValsNext_tiled = np.tile(np.reshape(PermShkValsNext, (1, ShkCount)), (aCount, 1))
        TranShkValsNext_tiled = np.tile(np.reshape(TranShkValsNext, (1, ShkCount)), (aCount, 1))

        # Find the natural borrowing constraint
        aNrmMin_candidates = PermGroFac[j]*PermShkValsNext_tiled/Rfree[j]*(mNrmMinNext - TranShkValsNext_tiled[0, :])
        aNrmMin = np.max(aNrmMin_candidates)
        BoroCnstNat_cond.append(aNrmMin)

        # Calculate market resources next period (and a constant array of capital-to-labor ratio)
        aNrmNow_tiled = aNrmMin + aXtra_tiled
        mNrmNext_array = Rfree[j]*aNrmNow_tiled/PermShkValsNext_tiled + TranShkValsNext_tiled

        # Find marginal value next period at every income shock realization and every aggregate market resource gridpoint
        vPnext_array = Rfree[j]*PermShkValsNext_tiled**(-CRRA)*vPfuncNext(mNrmNext_array)

        # Calculate expectated marginal value at the end of the period at every asset gridpoint
        EndOfPrdvP = DiscFac*np.sum(vPnext_array*ShkPrbsNext_tiled, axis=1)

        # Make the conditional end-of-period marginal value function
        EndOfPrdvPnvrs = EndOfPrdvP**(-1./CRRA)
        EndOfPrdvPnvrsFunc = LinearInterp(np.insert(aNrmMin + aXtraGrid, 0, aNrmMin), np.insert(EndOfPrdvPnvrs, 0, 0.0))
        EndOfPrdvPfunc_cond.append(MargValueFunc(EndOfPrdvPnvrsFunc, CRRA))

    # Now loop through *this* period's discrete states, calculating end-of-period
    # marginal value (weighting across state transitions), then construct consumption
    # and marginal value function for each state.
    cFuncNow = []
    vPfuncNow = []
    mNrmMinNow = []
    for i in range(StateCount):
        # Find natural borrowing constraint for this state
        aNrmMin_candidates = np.zeros(StateCount) + np.nan
        for j in range(StateCount):
            if MrkvArray[i, j] > 0.:  # Irrelevant if transition is impossible
                aNrmMin_candidates[j] = BoroCnstNat_cond[j]
        aNrmMin = np.nanmax(aNrmMin_candidates)
        
        # Find the minimum allowable market resources
        if BoroCnstArt is not None:
            mNrmMin = np.maximum(BoroCnstArt, aNrmMin)
        else:
            mNrmMin = aNrmMin
        mNrmMinNow.append(mNrmMin)

        # Make tiled grid of aNrm
        aNrmNow = aNrmMin + aXtraGrid
        
        # Loop through feasible transitions and calculate end-of-period marginal value
        EndOfPrdvP = np.zeros(aCount)
        for j in range(StateCount):
            if MrkvArray[i, j] > 0.:
                temp = MrkvArray[i, j]*EndOfPrdvPfunc_cond[j](aNrmNow)
                EndOfPrdvP += temp
        EndOfPrdvP *= LivPrb[i] # Account for survival out of the current state

        # Calculate consumption and the endogenous mNrm gridpoints for this state
        cNrmNow = (EndOfPrdvP/uPfac[i])**(-1./CRRA)
        mNrmNow = aNrmNow + cNrmNow

        # Make a piecewise linear consumption function
        c_temp = np.insert(cNrmNow, 0, 0.0)  # Add point at bottom
        m_temp = np.insert(mNrmNow, 0, aNrmMin)
        cFuncUnc = LinearInterp(m_temp, c_temp)
        cFuncCnst = LinearInterp(np.array([mNrmMin, mNrmMin+1.0]), np.array([0.0, 1.0]))
        cFuncNow.append(LowerEnvelope(cFuncUnc,cFuncCnst))

        # Construct the marginal value function using the envelope condition
        m_temp = aXtraGrid + mNrmMin
        c_temp = cFuncNow[i](m_temp)
        uP = uPfac[i]*c_temp**(-CRRA)
        vPnvrs = uP**(-1./CRRA)
        vPnvrsFunc = LinearInterp(np.insert(m_temp, 0, mNrmMin), np.insert(vPnvrs, 0, 0.0))
        vPfuncNow.append(MargValueFunc(vPnvrsFunc, CRRA))
        
    # Pack up and return the solution
    solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow)
    return solution_now
Example #8
0
    def make_solution(self, cNrm, mNrm):
        """
        Construct an object representing the solution to this period's problem.

        Parameters
        ----------
        cNrm : np.array
            Array of normalized consumption values for interpolation.  Each row
            corresponds to a Markov state for this period.
        mNrm : np.array
            Array of normalized market resource values for interpolation.  Each
            row corresponds to a Markov state for this period.

        Returns
        -------
        solution : ConsumerSolution
            The solution to the single period consumption-saving problem. Includes
            a consumption function cFunc (using cubic or linear splines), a marg-
            inal value function vPfunc, a minimum acceptable level of normalized
            market resources mNrmMin, normalized human wealth hNrm, and bounding
            MPCs MPCmin and MPCmax.  It might also have a value function vFunc
            and marginal marginal value function vPPfunc.  All of these attributes
            are lists or arrays, with elements corresponding to the current
            Markov state.  E.g. solution.cFunc[0] is the consumption function
            when in the i=0 Markov state this period.
        """
        solution = (
            ConsumerSolution()
        )  # An empty solution to which we'll add state-conditional solutions
        # Calculate the MPC at each market resource gridpoint in each state (if desired)
        if self.CubicBool:
            dcda = self.EndOfPrdvPP / self.uPP(np.array(self.cNrmNow))
            MPC = dcda / (dcda + 1.0)
            self.MPC_temp = np.hstack(
                (np.reshape(self.MPCmaxNow, (self.StateCount, 1)), MPC)
            )
            interpfunc = self.make_cubic_cFunc
        else:
            interpfunc = self.make_linear_cFunc

        # Loop through each current period state and add its solution to the overall solution
        for i in range(self.StateCount):
            # Set current-period-conditional human wealth and MPC bounds
            self.hNrmNow_j = self.hNrmNow[i]
            self.MPCminNow_j = self.MPCminNow[i]
            if self.CubicBool:
                self.MPC_temp_j = self.MPC_temp[i, :]

            # Construct the consumption function by combining the constrained and unconstrained portions
            self.cFuncNowCnst = LinearInterp(
                [self.mNrmMin_list[i], self.mNrmMin_list[i] + 1.0], [0.0, 1.0]
            )
            cFuncNowUnc = interpfunc(mNrm[i, :], cNrm[i, :])
            cFuncNow = LowerEnvelope(cFuncNowUnc, self.cFuncNowCnst)

            # Make the marginal value function and pack up the current-state-conditional solution
            vPfuncNow = MargValueFuncCRRA(cFuncNow, self.CRRA)
            solution_cond = ConsumerSolution(
                cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=self.mNrmMinNow
            )
            if (
                self.CubicBool
            ):  # Add the state-conditional marginal marginal value function (if desired)
                solution_cond = self.add_vPPfunc(solution_cond)

            # Add the current-state-conditional solution to the overall period solution
            solution.append_solution(solution_cond)

        # Add the lower bounds of market resources, MPC limits, human resources,
        # and the value functions to the overall solution
        solution.mNrmMin = self.mNrmMin_list
        solution = self.add_MPC_and_human_wealth(solution)
        if self.vFuncBool:
            vFuncNow = self.make_vFunc(solution)
            solution.vFunc = vFuncNow

        # Return the overall solution to this period
        return solution
Example #9
0
def solveConsRepAgentMarkov(solution_next,MrkvArray,DiscFac,CRRA,IncomeDstn,CapShare,DeprFac,PermGroFac,aXtraGrid):
    '''
    Solve one period of the simple representative agent consumption-saving model.
    This version supports a discrete Markov process.

    Parameters
    ----------
    solution_next : ConsumerSolution
        Solution to the next period's problem (i.e. previous iteration).
    MrkvArray : np.array
        Markov transition array between this period and next period.
    DiscFac : float
        Intertemporal discount factor for future utility.
    CRRA : float
        Coefficient of relative risk aversion.
    IncomeDstn : [[np.array]]
        A list of lists containing three arrays of floats, representing a discrete
        approximation to the income process between the period being solved
        and the one immediately following (in solution_next). Order: event
        probabilities, permanent shocks, transitory shocks.
    CapShare : float
        Capital's share of income in Cobb-Douglas production function.
    DeprFac : float
        Depreciation rate of capital.
    PermGroFac : [float]
        Expected permanent income growth factor for each state we could be in
        next period.
    aXtraGrid : np.array
        Array of "extra" end-of-period asset values-- assets above the
        absolute minimum acceptable level.  In this model, the minimum acceptable
        level is always zero.

    Returns
    -------
    solution_now : ConsumerSolution
        Solution to this period's problem (new iteration).
    '''
    # Define basic objects
    StateCount  = MrkvArray.shape[0]
    aNrmNow     = aXtraGrid
    aNrmCount   = aNrmNow.size
    EndOfPrdvP_cond = np.zeros((StateCount,aNrmCount)) + np.nan

    # Loop over *next period* states, calculating conditional EndOfPrdvP
    for j in range(StateCount):
        # Define next-period-state conditional objects
        vPfuncNext  = solution_next.vPfunc[j]
        ShkPrbsNext     = IncomeDstn[j].pmf
        PermShkValsNext = IncomeDstn[j].X[0]
        TranShkValsNext = IncomeDstn[j].X[1]

        # Make tiled versions of end-of-period assets, shocks, and probabilities
        ShkCount    = ShkPrbsNext.size
        aNrm_tiled  = np.tile(np.reshape(aNrmNow,(aNrmCount,1)),(1,ShkCount))

        # Tile arrays of the income shocks and put them into useful shapes
        PermShkVals_tiled = np.tile(np.reshape(PermShkValsNext,(1,ShkCount)),(aNrmCount,1))
        TranShkVals_tiled = np.tile(np.reshape(TranShkValsNext,(1,ShkCount)),(aNrmCount,1))
        ShkPrbs_tiled     = np.tile(np.reshape(ShkPrbsNext,(1,ShkCount)),(aNrmCount,1))

        # Calculate next period's capital-to-permanent-labor ratio under each combination
        # of end-of-period assets and shock realization
        kNrmNext = aNrm_tiled/(PermGroFac[j]*PermShkVals_tiled)

        # Calculate next period's market resources
        KtoLnext  = kNrmNext/TranShkVals_tiled
        RfreeNext = 1. - DeprFac + CapShare*KtoLnext**(CapShare-1.)
        wRteNext  = (1.-CapShare)*KtoLnext**CapShare
        mNrmNext  = RfreeNext*kNrmNext + wRteNext*TranShkVals_tiled

        # Calculate end-of-period marginal value of assets for the RA
        vPnext = vPfuncNext(mNrmNext)
        EndOfPrdvP_cond[j,:] = DiscFac*np.sum(RfreeNext*(PermGroFac[j]*PermShkVals_tiled)**(-CRRA)*vPnext*ShkPrbs_tiled,axis=1)

    # Apply the Markov transition matrix to get unconditional end-of-period marginal value
    EndOfPrdvP = np.dot(MrkvArray,EndOfPrdvP_cond)

    # Construct the consumption function and marginal value function for each discrete state
    cFuncNow_list = []
    vPfuncNow_list = []
    for i in range(StateCount):
        # Invert the first order condition to get consumption, then find endogenous gridpoints
        cNrmNow = EndOfPrdvP[i,:]**(-1./CRRA)
        mNrmNow = aNrmNow + cNrmNow

        # Construct the consumption function and the marginal value function
        cFuncNow_list.append(LinearInterp(np.insert(mNrmNow,0,0.0),np.insert(cNrmNow,0,0.0)))
        vPfuncNow_list.append(MargValueFunc(cFuncNow_list[-1],CRRA))

    # Construct and return the solution for this period
    solution_now = ConsumerSolution(cFunc=cFuncNow_list,vPfunc=vPfuncNow_list)
    return solution_now
Example #10
0
def solveConsRepAgent(solution_next,DiscFac,CRRA,IncomeDstn,CapShare,DeprFac,PermGroFac,aXtraGrid):
    '''
    Solve one period of the simple representative agent consumption-saving model.

    Parameters
    ----------
    solution_next : ConsumerSolution
        Solution to the next period's problem (i.e. previous iteration).
    DiscFac : float
        Intertemporal discount factor for future utility.
    CRRA : float
        Coefficient of relative risk aversion.
    IncomeDstn : [np.array]
        A list containing three arrays of floats, representing a discrete
        approximation to the income process between the period being solved
        and the one immediately following (in solution_next). Order: event
        probabilities, permanent shocks, transitory shocks.
    CapShare : float
        Capital's share of income in Cobb-Douglas production function.
    DeprFac : float
        Depreciation rate of capital.
    PermGroFac : float
        Expected permanent income growth factor at the end of this period.
    aXtraGrid : np.array
        Array of "extra" end-of-period asset values-- assets above the
        absolute minimum acceptable level.  In this model, the minimum acceptable
        level is always zero.

    Returns
    -------
    solution_now : ConsumerSolution
        Solution to this period's problem (new iteration).
    '''
    # Unpack next period's solution and the income distribution
    vPfuncNext      = solution_next.vPfunc
    ShkPrbsNext     = IncomeDstn.pmf
    PermShkValsNext = IncomeDstn.X[0]
    TranShkValsNext = IncomeDstn.X[1]

    # Make tiled versions of end-of-period assets, shocks, and probabilities
    aNrmNow     = aXtraGrid
    aNrmCount   = aNrmNow.size
    ShkCount    = ShkPrbsNext.size
    aNrm_tiled  = np.tile(np.reshape(aNrmNow,(aNrmCount,1)),(1,ShkCount))

    # Tile arrays of the income shocks and put them into useful shapes
    PermShkVals_tiled = np.tile(np.reshape(PermShkValsNext,(1,ShkCount)),(aNrmCount,1))
    TranShkVals_tiled = np.tile(np.reshape(TranShkValsNext,(1,ShkCount)),(aNrmCount,1))
    ShkPrbs_tiled     = np.tile(np.reshape(ShkPrbsNext,(1,ShkCount)),(aNrmCount,1))

    # Calculate next period's capital-to-permanent-labor ratio under each combination
    # of end-of-period assets and shock realization
    kNrmNext = aNrm_tiled/(PermGroFac*PermShkVals_tiled)

    # Calculate next period's market resources
    KtoLnext  = kNrmNext/TranShkVals_tiled
    RfreeNext = 1. - DeprFac + CapShare*KtoLnext**(CapShare-1.)
    wRteNext  = (1.-CapShare)*KtoLnext**CapShare
    mNrmNext  = RfreeNext*kNrmNext + wRteNext*TranShkVals_tiled

    # Calculate end-of-period marginal value of assets for the RA
    vPnext = vPfuncNext(mNrmNext)
    EndOfPrdvP = DiscFac*np.sum(RfreeNext*(PermGroFac*PermShkVals_tiled)**(-CRRA)*vPnext*ShkPrbs_tiled,axis=1)

    # Invert the first order condition to get consumption, then find endogenous gridpoints
    cNrmNow = EndOfPrdvP**(-1./CRRA)
    mNrmNow = aNrmNow + cNrmNow

    # Construct the consumption function and the marginal value function
    cFuncNow  = LinearInterp(np.insert(mNrmNow,0,0.0),np.insert(cNrmNow,0,0.0))
    vPfuncNow = MargValueFunc(cFuncNow,CRRA)

    # Construct and return the solution for this period
    solution_now = ConsumerSolution(cFunc=cFuncNow,vPfunc=vPfuncNow)
    return solution_now
Example #11
0
class PrefLaborConsumerType(IndShockConsumerType):
    '''
    A consumer type with idiosyncratic shocks to permanent and transitory income,
    as well as shocks to preferences
    '''
    # Define some universal values for all consumer types
    cFunc_terminal_ = BilinearInterpOnInterp1D(
        [[
            LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])),
            LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0]))
        ],
         [
             LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])),
             LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0]))
         ]], np.array([0.0, 1.0]), np.array([0.0,
                                             1.0]))  # c=m in terminal period
    vFunc_terminal_ = BilinearInterpOnInterp1D(
        [[
            LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])),
            LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0]))
        ],
         [
             LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0])),
             LinearInterp(np.array([0.0, 1.0]), np.array([0.0, 1.0]))
         ]], np.array([0.0, 1.0]), np.array([0.0, 1.0]))  # This is overwritten
    solution_terminal_ = ConsumerSolution(cFunc=cFunc_terminal_,
                                          vFunc=vFunc_terminal_)
    time_inv_ = PerfForesightConsumerType.time_inv_ + [
        'LaborElas', 'PrefShkVals', 'WageShkVals'
    ]
    shock_vars_ = ['PermShkNow', 'TranShkNow', 'PrefShkNow']

    def __init__(self, cycles=1, time_flow=True, **kwds):
        '''
        Instantiate a new ConsumerType with given data.
        See ConsumerParameters.init_idiosyncratic_shocks for a dictionary of
        the keywords that should be passed to the constructor.
        
        Parameters
        ----------
        cycles : int
            Number of times the sequence of periods should be solved.
        time_flow : boolean
            Whether time is currently "flowing" forward for this instance.
        
        Returns
        -------
        None
        '''
        # Initialize a basic AgentType
        IndShockConsumerType.__init__(self,
                                      cycles=cycles,
                                      time_flow=time_flow,
                                      **kwds)

        # Add consumer-type specific objects, copying to create independent versions
        self.solveOnePeriod = solvePrefLaborShock  # idiosyncratic shocks solver
        self.update()  # Make assets grid, income process, terminal solution

    def reset(self):
        self.initializeSim()
        self.t_age = drawDiscrete(self.AgentCount,
                                  P=self.AgeDstn,
                                  X=np.arange(self.AgeDstn.size),
                                  exact_match=False,
                                  seed=self.RNG.randint(0,
                                                        2**31 - 1)).astype(int)
        self.t_cycle = copy(self.t_age)

    def marketAction(self):
        self.simulate(1)

    def updateIncomeProcess(self):
        '''
        Updates this agent's income process based on his own attributes.  The
        function that generates the discrete income process can be swapped out
        for a different process.
        
        Parameters
        ----------
        none
        
        Returns:
        -----------
        none
        '''
        original_time = self.time_flow
        self.timeFwd()
        IncomeAndPrefDstn, PermShkDstn, TranShkDstn, PrefShkDstn = constructLognormalIncomeAndPreferenceProcess(
            self)
        self.IncomeAndPrefDstn = IncomeAndPrefDstn
        self.PermShkDstn = PermShkDstn
        self.TranShkDstn = TranShkDstn
        self.PrefShkDstn = PrefShkDstn
        self.PrefShkVals = PrefShkDstn[0][1]
        self.WageShkVals = TranShkDstn[0][1]
        self.addToTimeVary('IncomeAndPrefDstn', 'PermShkDstn', 'TranShkDstn',
                           'PrefShkDstn')
        if not original_time:
            self.timeRev()

    def updateSolutionTerminal(self):
        '''
        Update the terminal period solution.  
        
        Parameters
        ----------
        none
        
        Returns
        -------
        none
        '''
        #self.solution_terminal.vFunc   = ValueFunc(self.cFunc_terminal_,self.CRRA)
        terminal_instance = LinearInterp(np.array([0.0, 100.0]),
                                         np.array([0.01, 0.0]),
                                         lower_extrap=True)
        self.solution_terminal.vPfunc = BilinearInterpOnInterp1D(
            [[terminal_instance, terminal_instance],
             [terminal_instance, terminal_instance]], np.array([0.0, 2.0]),
            np.array([0.0, 2.0]))
        #self.solution_terminal.vPPfunc = MargMargValueFunc(self.cFunc_terminal_,self.CRRA)

    def getShocks(self):
        '''
        Gets permanent and transitory income shocks for this period.  Samples from IncomeDstn for
        each period in the cycle.
        
        Parameters
        ----------
        None
        
        Returns
        -------
        None
        '''
        PermShkNow = np.zeros(self.AgentCount)  # Initialize shock arrays
        TranShkNow = np.zeros(self.AgentCount)
        PrefShkNow = np.zeros(self.AgentCount)
        newborn = self.t_age == 0
        for t in range(self.T_cycle):
            these = t == self.t_cycle
            N = np.sum(these)
            if N > 0:
                IncomeDstnNow = self.IncomeAndPrefDstn[
                    t - 1]  # set current income distribution
                PermGroFacNow = self.PermGroFac[
                    t - 1]  # and permanent growth factor
                Indices = np.arange(
                    IncomeDstnNow[0].size)  # just a list of integers
                # Get random draws of income shocks from the discrete distribution
                EventDraws = drawDiscrete(N,
                                          X=Indices,
                                          P=IncomeDstnNow[0],
                                          exact_match=False,
                                          seed=self.RNG.randint(0, 2**31 - 1))
                PermShkNow[these] = IncomeDstnNow[1][
                    EventDraws] * PermGroFacNow  # permanent "shock" includes expected growth
                TranShkNow[these] = IncomeDstnNow[2][EventDraws]
                PrefShkNow[these] = IncomeDstnNow[3][EventDraws]

        # That procedure used the *last* period in the sequence for newborns, but that's not right
        # Redraw shocks for newborns, using the *first* period in the sequence.  Approximation.
        N = np.sum(newborn)
        if N > 0:
            these = newborn
            IncomeDstnNow = self.IncomeAndPrefDstn[
                0]  # set current income distribution
            PermGroFacNow = self.PermGroFac[0]  # and permanent growth factor
            Indices = np.arange(
                IncomeDstnNow[0].size)  # just a list of integers
            # Get random draws of income shocks from the discrete distribution
            EventDraws = drawDiscrete(N,
                                      X=Indices,
                                      P=IncomeDstnNow[0],
                                      exact_match=False,
                                      seed=self.RNG.randint(0, 2**31 - 1))
            PermShkNow[these] = IncomeDstnNow[1][
                EventDraws] * PermGroFacNow  # permanent "shock" includes expected growth
            TranShkNow[these] = IncomeDstnNow[2][EventDraws]
            PrefShkNow[these] = IncomeDstnNow[3][EventDraws]


#        PermShkNow[newborn] = 1.0
        TranShkNow[newborn] = 1.0

        # Store the shocks in self
        self.EmpNow = np.ones(self.AgentCount, dtype=bool)
        self.EmpNow[TranShkNow == self.IncUnemp] = False
        self.PermShkNow = PermShkNow
        self.TranShkNow = TranShkNow
        self.PrefShkNow = PrefShkNow

    def getStates(self):
        '''
        Calculates updated values of normalized market resources and permanent income level for each
        agent.  Uses pLvlNow, aNrmNow, PermShkNow, TranShkNow.
        
        Parameters
        ----------
        None
        
        Returns
        -------
        None
        '''
        pLvlPrev = self.pLvlNow
        aNrmPrev = self.aNrmNow
        RfreeNow = self.getRfree()

        # Calculate new states: normalized market resources and permanent income level
        self.pLvlNow = pLvlPrev * self.PermShkNow  # Updated permanent income level
        ReffNow = RfreeNow / self.PermShkNow  # "Effective" interest factor on normalized assets
        self.bNrmNow = ReffNow * aNrmPrev  # Bank balances before labor income
        return None

    def getControls(self):
        '''
        Calculates consumption for each consumer of this type using the consumption functions.
        
        Parameters
        ----------
        None
        
        Returns
        -------
        None
        '''
        cNrmNow = np.zeros(self.AgentCount) + np.nan
        MPCnow = np.zeros(self.AgentCount) + np.nan
        lNow = np.zeros(self.AgentCount) + np.nan
        for t in range(self.T_cycle):
            these = t == self.t_cycle
            cNrmNow[these] = self.solution[t].cFunc(self.bNrmNow[these],
                                                    self.TranShkNow,
                                                    self.PrefShkNow)
            MPCnow[these] = self.solution[t].cFunc.derivativeX(
                self.bNrmNow[these], self.TranShkNow, self.PrefShkNow)
            lNow[these] = self.solution[t].lFunc(self.bNrmNow[these],
                                                 self.TranShkNow,
                                                 self.PrefShkNow)
        self.cNrmNow = cNrmNow
        self.MPCnow = MPCnow
        self.lNow = lNow
        self.lIncomeLvl = lNow * self.TranShkNow * self.pLvlNow
        self.cLvlNow = cNrmNow * self.pLvlNow
        return None

    def getPostStates(self):
        '''
        Calculates end-of-period assets for each consumer of this type.
        
        Parameters
        ----------
        None
        
        Returns
        -------
        None
        '''
        self.aNrmNow = self.bNrmNow + self.lNow * self.TranShkNow - self.cNrmNow
        self.aLvlNow = self.aNrmNow * self.pLvlNow  # Useful in some cases to precalculate asset level
        return None
Example #12
0
    def makeBasicSolution(self, EndOfPrdvP, aNrm, wageShkVals, prefShkVals):
        '''
        Given end of period assets and end of period marginal value, construct
        the basic solution for this period.
        
        Parameters
        ----------
        EndOfPrdvP : np.array
            Array of end-of-period marginal values.
        aNrm : np.array
            Array of end-of-period asset values that yield the marginal values
            in EndOfPrdvP.
        wageShkVals : np.array
            Array of this period transitory wage shock values.
        prefShkVals : np.array
            Array of this period preference shock values.
            
        Returns
        -------
        solution_now : ConsumerSolution
            The solution to this period's consumption-saving problem, with a
            consumption function, marginal value function.
        '''
        num_pref_shocks = len(prefShkVals)
        num_wage_shocks = len(wageShkVals)
        cFuncBaseByPref_list = []
        vPFuncBaseByPref_list = []
        lFuncBaseByPref_list = []
        for i in range(num_wage_shocks):
            cFuncBaseByPref_list.append([])
            vPFuncBaseByPref_list.append([])
            lFuncBaseByPref_list.append([])
            for j in range(num_pref_shocks):
                c_temp = self.uPinv(EndOfPrdvP / prefShkVals[j])
                l_temp = self.LabSupply(wageShkVals[i] * EndOfPrdvP)
                b_temp = c_temp + aNrm - l_temp * wageShkVals[i]

                if wageShkVals[i] == 0.0:
                    c_temp = np.insert(c_temp, 0, 0., axis=-1)
                    l_temp = np.insert(l_temp, 0, 0.0, axis=-1)
                    b_temp = np.insert(b_temp, 0, 0.0, axis=-1)

                lFuncBaseByPref_list[i].append(
                    LinearInterp(b_temp, l_temp, lower_extrap=True))
                cFunc1 = LinearInterp(b_temp, c_temp, lower_extrap=True)
                cFunc2 = LinearInterp(b_temp,
                                      l_temp * wageShkVals[i] + b_temp,
                                      lower_extrap=True)
                cFuncBaseByPref_list[i].append(LowerEnvelope(cFunc1, cFunc2))

                pseudo_inverse_vPfunc1 = LinearInterp(
                    b_temp,
                    prefShkVals[j]**(-1.0 / self.CRRA) * c_temp,
                    lower_extrap=True)
                pseudo_inverse_vPfunc2 = LinearInterp(
                    b_temp,
                    prefShkVals[j]**(-1.0 / self.CRRA) *
                    (l_temp * wageShkVals[i] + b_temp),
                    lower_extrap=True)
                pseudo_inverse_vPfunc = LowerEnvelope(pseudo_inverse_vPfunc1,
                                                      pseudo_inverse_vPfunc2)

                vPFuncBaseByPref_list[i].append(
                    MargValueFunc(pseudo_inverse_vPfunc, self.CRRA))

        cFuncNow = BilinearInterpOnInterp1D(cFuncBaseByPref_list, wageShkVals,
                                            prefShkVals)
        vPfuncNow = BilinearInterpOnInterp1D(vPFuncBaseByPref_list,
                                             wageShkVals, prefShkVals)
        lFuncNow = BilinearInterpOnInterp1D(lFuncBaseByPref_list, wageShkVals,
                                            prefShkVals)

        # Pack up and return the solution
        solution_now = ConsumerSolution(cFunc=cFuncNow, vPfunc=vPfuncNow)
        solution_now.lFunc = lFuncNow
        return solution_now