#!/usr/bin/env python # coding: utf-8 # # Asset replacement model with maintenance # # **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: **demddp03.m** # # Running this file requires the Python version of CompEcon. This can be installed with pip by running # # !pip install compecon --upgrade # # Last updated: 2021-Oct-01 #
# ## About # # Consider the preceding example, but suppose that the productivity of the asset may be # enhanced by performing annual service maintenance. Specifically, at the beginning of each # year, a manufacturer must decide whether to replace the asset with a new one or, if he elects # to keep the asset, whether to service it. An asset that is $a$ years old and has been serviced $s$ times yields a profit contribution $p(a, s)$ up to an age of $n$ years, at which point the asset becomes unsafe and must be replaced by law. The cost of a new asset is $c$, and the cost of servicing an asset is $k$. What replacement-maintenance policy maximizes profits? # ## Initial tasks # In[1]: import numpy as np import pandas as pd import matplotlib.pyplot as plt from compecon import DDPmodel, gridmake, getindex # ## Model Parameters # Assume a maximum asset age of 5 years, asset replacement cost $c = 75$, cost of servicing $k = 10$, and annual discount factor $\delta = 0.9$. # In[2]: maxage = 5 repcost = 75 mancost = 10 delta = 0.9 # ### State Space # # This is an infinite horizon, deterministic model with time $t$ measured in years. The state # variables # # $a \in \{1, 2, 3, \dots, n\}$ # # $s \in \{0, 1, 2, \dots, n − 1\}$ # # are the age of the asset in years and the number of servicings it has undergone, respectively. # In[3]: s1 = np.arange(1, 1 + maxage) # asset age s2 = s1 - 1 # servicings S = gridmake(s1,s2) # combined state grid S1, S2 = S n = S1.size # total number of states # Here, the set of possible asset ages and servicings are generated individually, and then a two-dimensional state grid is constructed by forming their Cartesian product using the CompEcon routine gridmake. # ### Action Space # The action variable # # $x \in \{\text{no action}, \text{service}, \text{replace}\}$ # # is the hold-replacement-maintenance decision. # In[4]: X = np.array(['no action', 'service', 'replace']) # vector of actions m = len(X) # number of actions # ### Reward Function # The reward function is # \begin{equation} # f (a, s, x) = # \begin{cases} # p(a, s), &x = \text{no action}\\ # p(0, 0) − c, &x = \text{service}\\ # p(a, s + 1) − k, &x = \text{replace} # \end{cases} # \end{equation} # # Assuming a profit contribution $p(a) = # 50 − 2.5a −2.5a^2$ that is a function of the asset age $a$ in years. # # Here, the rows of the reward matrix, which # correspond to the three admissible decisions (no action, service, replace), are computed # individually. # In[5]: f = np.zeros((m, n)) q = 50 - 2.5 * S1 - 2.5 * S1 ** 2 f[0] = q * np.minimum(1, 1 - (S1 - S2) / maxage) f[1] = q * np.minimum(1, 1 - (S1 - S2 - 1) / maxage) - mancost f[2] = 50 - repcost # ### State Transition Function # The state transition function is # \begin{equation} # g(a, s, x) = # \begin{cases} # (a + 1, s), &x = \text{no action}\\ # (1, 0), &x = \text{service}\\ # (a + 1, s + 1), &x = \text{replace} # \end{cases} # \end{equation} # # Here, the routine ```getindex``` is used to find the index of the following period’s state. # In[6]: g = np.empty_like(f) g[0] = getindex(np.c_[S1 + 1, S2], S) g[1] = getindex(np.c_[S1 + 1, S2 + 1], S) g[2] = getindex(np.c_[1, 0], S) # # Model Structure # The value of asset of age $a$ that has undergone $s$ servicings satisfies the Bellman equation # \begin{equation} # V(a,s) = \max\{p(a,s) + \delta V(a+1,s),\quad p(a,s+1)−k + \delta V(a+1,s+1), # \quad p(0,0) − c + \delta V(1,0)\} # \end{equation} # # where we set $p(n, s) = −\infty$ for all $s$ to enforce replacement of an asset of age $n$. The # Bellman equation asserts that if the manufacturer replaces an asset of age $a$ with servicings # $s$, he earns $p(0,0) − c$ over the coming year and begins the subsequent year with an asset # worth $V(1,0)$; if he services the asset, he earns $p(a, s + 1) − k$ over the coming year # and begins the subsequent year with an asset worth $V(a + 1, s + 1)$. As with the previous # example, the value $V(a, s)$ measures not only the current and future net earnings of the # asset, but also the net earnings of all future assets that replace it. # To solve and simulate this model, use the CompEcon class ```DDPmodel```. # In[7]: model = DDPmodel(f, g, delta) model.solve() # ## Analysis # ### Simulate Model # The paths are computed by performing q deterministic simulation of 12 years in duration using the ```simulate()``` method. # In[8]: sinit = 0 nyrs = 12 t = np.arange(nyrs + 1) spath, xpath = model.simulate(sinit, nyrs) # In[9]: pd.Categorical(X[xpath], categories=X) # In[10]: simul = pd.DataFrame({ 'Year': t, 'Age of Asset': S1[spath], 'Number of Servicings': S2[spath]}).set_index('Year') simul['Action'] = pd.Categorical(X[xpath], categories=X) simul # ### Plot State Paths (Age and Servicings) and Action Path # The asset is replaced every four years, and is serviced twice, at the beginning of the second and third years of operation. # In[11]: fig, axs = plt.subplots(3, 1, sharex=True, figsize=[9,9]) simul['Age of Asset'].plot(marker='o', ax=axs[0]) axs[0].set(title='Age of Asset') simul['Number of Servicings'].plot(marker='o', ax=axs[1]) axs[1].set(title='Number of Servicings') simul['Action'].cat.codes.plot(marker='o', ax=axs[2]) axs[2].set(title='Action Path', yticks=range(3), yticklabels=X);