Asset Replacement Model

Randall Romero Aguilar, PhD

This demo is based on the original Matlab demo accompanying the Computational Economics and Finance 2001 textbook by Mario Miranda and Paul Fackler.

Original (Matlab) CompEcon file: demdp02.m

Running this file requires the Python version of CompEcon. This can be installed with pip by running

!pip install compecon --upgrade

Last updated: 2022-Oct-22


About

Profit-maximizing entrepreneur must decide when to replace an aging asset.

import numpy as np
import matplotlib.pyplot as plt
from compecon import BasisSpline, DPmodel, qnwnorm, NLP

Model Parameters

The maximum age of the asset is \(A=6\), production function coefficients are \(\alpha =[50.0, -2.5, -2.5]\), net replacement cost is \(\kappa = 40\), long-run mean unit profit is \(\bar{p} = 1\), the unit profit autoregression coefficient is \(\gamma = 0.5\), the standard deviation of unit profit shock is \(\sigma = 0.15\), and the discount factor is \(\delta = 0.9\).

A   = 6
ɑ   = np.array([50, -2.5, -2.5])
κ   = 40
pbar= 1.0
ɣ   = 0.5
σ   = 0.15
δ   = 0.9

State space

The state variables are the current unit profit \(p\) (continuous) and the age of asset \(a\in\{1,2,\dots,A\}\).

dstates = [f'a={age+1:d}' for age in range(A)]
print('Discrete states are :', dstates)
Discrete states are : ['a=1', 'a=2', 'a=3', 'a=4', 'a=5', 'a=6']

Here, we approximate the unit profit with a cubic spline basis of \(n=200\) nodes, over the interval \(p\in[0,2]\).

n  = 200
pmin, pmax = 0.0, 2.0  
basis = BasisSpline(n, pmin, pmax, labels=['unit profit'])

Action space

There is only one choice variable \(j\): whether to keep(0) or replace(1) the asset.

options = ['keep', 'replace']

Reward Function

The instant profit depends on the asset age and whether it is replaced. An asset of age \(a=A\) must be replaced. If the asset is replaced, the profit is \(50p\) minus the cost of replacement \(\kappa\); otherwise the profit depends on the age of the asset, \((\alpha_0 + \alpha_1 a + \alpha_2 a^2)p\).

def profit(p, x, i, j):
    a = i + 1
    if j or a == A:
        return p * 50 - κ
    else:
        return p * (ɑ[0] + ɑ[1]*a + ɑ[2]*a**2 )

State Transition Function

The unit profit \(p\) follows a Markov Process

(15)\[\begin{equation}p' = \bar{p} + \gamma(p-\bar{p}) + \epsilon \end{equation}\]

where \(\epsilon \sim N(0,\sigma^2)\).

def transition(p, x, i, j, in_, e):
    return pbar + ɣ * (p - pbar) + e

The continuous shock must be discretized. Here we use Gauss-Legendre quadrature to obtain nodes and weights defining a discrete distribution that matches the first 10 moments of the Normal distribution (this is achieved with \(m=5\) nodes and weights).

m = 5
e, w = qnwnorm(m,0,σ**2)

On the other hand, the age of the asset is deterministic: if it is replaced now it will be new (\(a=0\)) next period; otherwise its age will be \(a+1\) next period if current age is \(a\).

h = np.zeros((2, A),int)
h[0, :-1] = np.arange(1, A)

Model Structure

model = DPmodel(basis, profit, transition,
                i=dstates,
                j=options,
                discount=δ, e=e, w=w, h=h)

SOLUTION

The solve method returns a pandas DataFrame.

S = model.solve()
S.head()
Solving infinite-horizon model collocation equation by Newton's method
iter change       time    
------------------------------
   0       3.7e+02    0.1952
   1       4.3e+01    0.5473
   2       1.2e+01    1.1229
   3       5.7e-01    1.6491
   4       4.5e-13    2.0834
Elapsed Time =    2.08 Seconds
unit profit i value resid j* value[keep] value[replace]
unit profit
a=1 0.000000 0.000000 0 234.982583 -5.684342e-14 keep 234.982583 206.124123
0.001001 0.001001 0 235.058576 -6.159590e-07 keep 235.058576 206.209036
0.002001 0.002001 0 235.134563 -6.366804e-07 keep 235.134563 206.293949
0.003002 0.003002 0 235.210544 -2.317537e-07 keep 235.210544 206.378862
0.004002 0.004002 0 235.286520 4.292312e-07 keep 235.286520 206.463774

Analysis

Plot Action-Contingent Value Functions

# Compute and Plot Critical Unit Profit Contributions
pcrit = [NLP(lambda s: model.Value_j(s)[i].dot([1,-1])).broyden(0.0) for i in range(A-1)]
vcrit = [model.Value(s)[i] for i, s in enumerate(pcrit)]
fig1, ax = plt.subplots(figsize=[10,5])

ax.set(title='Action-Contingent Value Functions', xlabel='Net Unit Profit',ylabel='Value')

cc = np.linspace(0.3,0.9,model.dims.ni)



for a, i in enumerate(dstates[:-1]):
    ax.plot(S.loc[i,'value[keep]'] ,color=plt.cm.Blues(cc[a]), label=f'Keep {i}')

ax.plot(S.loc['a=1','value[replace]'], color=plt.cm.Oranges(0.5),label='Replace')

for a, i in enumerate(dstates[:-1]):
    if pmin < pcrit[a] < pmax:
        ax.plot(pcrit[a], vcrit[a], color='C2', marker='.', markersize=9)
        ax.annotate(f'$p^*_{a+1}$', (pcrit[a], vcrit[a]), xytext=(pcrit[a]-0.2, vcrit[a]+50), arrowprops=dict(width=1))
        print(f'Age {a+1:d}  Profit {pcrit[a]:5.2f}')

ax.legend(fontsize="xx-small")
Age 2  Profit  1.50
Age 3  Profit  0.66
Age 4  Profit  0.38
Age 5  Profit  0.25
<matplotlib.legend.Legend at 0x13462c93d00>
../../_images/02 Asset Replacement Model_25_2.png

Plot Residual

fig, ax = plt.subplots(1,1, figsize=[12,4])
S['resid2'] = 100*S['resid'] / S['value']
S['resid2'].unstack(level=0).plot(ax=ax)
ax.set(title='Bellman Equation Residual',
       xlabel='Net Unit Profit', 
       ylabel='Percent Residual')

ax.legend(fontsize="x-small")
<matplotlib.legend.Legend at 0x13462e22130>
../../_images/02 Asset Replacement Model_27_1.png

SIMULATION

T = 50
nrep = 10000
sinit = np.full(nrep, pbar)
iinit = 0
data = model.simulate(T,sinit,iinit, seed=945)
data['age'] = data.i.cat.codes + 1

Plot Simulated and Expected Continuous State Path

data.head()
time _rep i unit profit j* age
0 0 0 a=1 1.0 keep 1
1 0 1 a=1 1.0 keep 1
2 0 2 a=1 1.0 keep 1
3 0 3 a=1 1.0 keep 1
4 0 4 a=1 1.0 keep 1
subdata = data.query('_rep < 3').set_index(['time','_rep']).unstack()
subdata.head()
i unit profit j* age
_rep 0 1 2 0 1 2 0 1 2 0 1 2
time
0 a=1 a=1 a=1 1.000000 1.000000 1.000000 keep keep keep 1 1 1
1 a=2 a=2 a=2 1.000000 1.000000 1.000000 keep keep keep 2 2 2
2 a=3 a=3 a=3 1.000000 1.000000 1.000000 replace replace replace 3 3 3
3 a=1 a=1 a=1 1.203344 0.796656 1.203344 keep keep keep 1 1 1
4 a=2 a=2 a=2 1.101672 0.898328 1.101672 keep keep keep 2 2 2
fig3, ax = plt.subplots()

subdata['unit profit'].plot(ax=ax, legend=None)
ax.set(title='Simulated and Expected Price', xlabel='Period',ylabel='Net Unit Profit')

ax.plot(data[['time','unit profit']].groupby('time').mean(),'k--',label='mean')
ax.legend(fontsize="x-small")
<matplotlib.legend.Legend at 0x13462e96550>
../../_images/02 Asset Replacement Model_35_1.png

Plot Expected Discrete State Path

fig4, ax = plt.subplots()

ax.set(title='Expected Machine Age', xlabel='Period', ylabel='Age')


subdata['age'].plot(ax=ax, legend=None)
ax.plot(data[['time','age']].groupby('time').mean(),'k--',label='mean')
ax.legend(fontsize="x-small")
<matplotlib.legend.Legend at 0x13462f65be0>
../../_images/02 Asset Replacement Model_37_1.png