Skip to content

Commit 2d76243

Browse files
authored
Merge pull request #1020 from alanlujan91/CHI_new_feats
Additional Features for CubicHermiteInterp
2 parents ed2eeff + 4d57fb2 commit 2d76243

File tree

6 files changed

+324
-81
lines changed

6 files changed

+324
-81
lines changed

Documentation/CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ Release Date: TBD
2828
- Removes a specific way of accounting for ``employment'' in the idiosyncratic-shocks income process. [#1473](https://github.com/econ-ark/HARK/pull/1473)
2929
- Adds income process constructor for the discrete Markov state consumption-saving model. [#1484](https://github.com/econ-ark/HARK/pull/1484)
3030
- Changes the behavior of make_lognormal_RiskyDstn so that the standard deviation represents the standard deviation of log(returns)
31-
- Adds detailed parameter and latex documentation to most models.
31+
- Adds detailed parameter and LaTeX documentation to most models.
3232
- Add PermGroFac constructor that explicitly combines idiosyncratic and aggregate sources of growth. [#1489](https://github.com/econ-ark/HARK/pull/1489)
3333
- Suppress warning from calc_stable_points when it would be raised by inapplicable AgentType subclasses. [#1493](https://github.com/econ-ark/HARK/pull/1493)
3434
- Fixes notation errors in IndShockConsumerType.make_euler_error_func from prior changes. [#1495](https://github.com/econ-ark/HARK/pull/1495)
3535
- Fixes typos in IdentityFunction interpolator class. [#1492](https://github.com/econ-ark/HARK/pull/1492)
3636
- Expands functionality of Cobb-Douglas aggregator for CRRA utility. [#1363](https://github.com/econ-ark/HARK/pull/1363)
3737
- Adds a new function for using Tauchen's method to approximate an AR1 process. [#1521](https://github.com/econ-ark/HARK/pull/1521)
38+
- Adds additional functionality to the CubicHermiteInterp class, imported from scipy.interpolate. [#1020](https://github.com/econ-ark/HARK/pull/1020/)
3839

3940
### 0.15.1
4041

HARK/ConsumptionSaving/ConsIndShockModel.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@
3939
expected,
4040
)
4141
from HARK.interpolation import (
42-
CubicInterp,
4342
LinearInterp,
4443
LowerEnvelope,
4544
MargMargValueFuncCRRA,
4645
MargValueFuncCRRA,
4746
ValueFuncCRRA,
4847
)
48+
from HARK.interpolation import CubicHermiteInterp as CubicInterp
4949
from HARK.metric import MetricObject
5050
from HARK.rewards import (
5151
CRRAutility,
@@ -863,14 +863,13 @@ def solve_one_period_ConsKinkedR(
863863

864864
# Construct the assets grid by adjusting aXtra by the natural borrowing constraint
865865
aNrmNow = np.sort(
866-
np.hstack((np.asarray(aXtraGrid) + mNrmMinNow, np.array([0.0, 0.0]))),
866+
np.hstack((np.asarray(aXtraGrid) + mNrmMinNow, np.array([0.0, 1e-15]))),
867867
)
868868

869869
# Make a 1D array of the interest factor at each asset gridpoint
870870
Rfree = Rsave * np.ones_like(aNrmNow)
871-
Rfree[aNrmNow < 0] = Rboro
871+
Rfree[aNrmNow <= 0] = Rboro
872872
i_kink = np.argwhere(aNrmNow == 0.0)[0][0]
873-
Rfree[i_kink] = Rboro
874873

875874
# Calculate end-of-period marginal value of assets at each gridpoint
876875
vPfacEff = DiscFacEff * Rfree * PermGroFac ** (-CRRA)

HARK/interpolation.py

+48-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import numpy as np
1414
from scipy.interpolate import CubicHermiteSpline
15-
1615
from HARK.metric import MetricObject
1716
from HARK.rewards import CRRAutility, CRRAutilityP, CRRAutilityPP
1817

@@ -1231,7 +1230,7 @@ def __init__(
12311230
_check_grid_dimensions(1, self.y_list, self.x_list)
12321231
_check_grid_dimensions(1, self.dydx_list, self.x_list)
12331232

1234-
self.n = self.x_list.size
1233+
self.n = len(x_list)
12351234

12361235
self._chs = CubicHermiteSpline(
12371236
self.x_list, self.y_list, self.dydx_list, extrapolate=None
@@ -1330,6 +1329,53 @@ def _evalAndDer(self, x):
13301329
dydx = self._der_helper(x, out_bot, out_top)
13311330
return y, dydx
13321331

1332+
def der_interp(self, nu=1):
1333+
"""
1334+
Construct a new piecewise polynomial representing the derivative.
1335+
See `scipy` for additional documentation:
1336+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicHermiteSpline.html
1337+
"""
1338+
return self._chs.derivative(nu)
1339+
1340+
def antider_interp(self, nu=1):
1341+
"""
1342+
Construct a new piecewise polynomial representing the antiderivative.
1343+
1344+
Antiderivative is also the indefinite integral of the function,
1345+
and derivative is its inverse operation.
1346+
1347+
See `scipy` for additional documentation:
1348+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicHermiteSpline.html
1349+
"""
1350+
return self._chs.antiderivative(nu)
1351+
1352+
def integrate(self, a, b, extrapolate=False):
1353+
"""
1354+
Compute a definite integral over a piecewise polynomial.
1355+
1356+
See `scipy` for additional documentation:
1357+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicHermiteSpline.html
1358+
"""
1359+
return self._chs.integrate(a, b, extrapolate)
1360+
1361+
def roots(self, discontinuity=True, extrapolate=False):
1362+
"""
1363+
Find real roots of the the piecewise polynomial.
1364+
1365+
See `scipy` for additional documentation:
1366+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicHermiteSpline.html
1367+
"""
1368+
return self._chs.roots(discontinuity, extrapolate)
1369+
1370+
def solve(self, y=0.0, discontinuity=True, extrapolate=False):
1371+
"""
1372+
Find real solutions of the the equation ``pp(x) == y``.
1373+
1374+
See `scipy` for additional documentation:
1375+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicHermiteSpline.html
1376+
"""
1377+
return self._chs.solve(y, discontinuity, extrapolate)
1378+
13331379

13341380
class BilinearInterp(HARKinterpolator2D):
13351381
"""
@@ -4716,5 +4762,4 @@ def __call__(self, *cFuncArgs):
47164762
"cFunc does not have a 'derivativeX' attribute. Can't compute"
47174763
+ "marginal marginal value."
47184764
)
4719-
47204765
return MPC * CRRAutilityPP(c, rho=self.CRRA)

HARK/tests/test_interpolation.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
This file implements unit tests for interpolation methods
33
"""
44

5-
import unittest
5+
from HARK.interpolation import (
6+
IdentityFunction,
7+
LinearInterp,
8+
BilinearInterp,
9+
TrilinearInterp,
10+
QuadlinearInterp,
11+
)
12+
from HARK.interpolation import CubicHermiteInterp as CubicInterp
613

714
import numpy as np
8-
9-
from HARK.interpolation import BilinearInterp
10-
from HARK.interpolation import CubicHermiteInterp as CubicInterp
11-
from HARK.interpolation import LinearInterp, QuadlinearInterp, TrilinearInterp
12-
from HARK.interpolation import IdentityFunction
15+
import unittest
16+
import numpy as np
1317

1418

1519
class testsLinearInterp(unittest.TestCase):

examples/Interpolation/CubicInterp.ipynb

+129-67
Large diffs are not rendered by default.

examples/Interpolation/CubicInterp.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# formats: ipynb,py:percent
5+
# text_representation:
6+
# extension: .py
7+
# format_name: percent
8+
# format_version: '1.3'
9+
# jupytext_version: 1.11.2
10+
# kernelspec:
11+
# display_name: Python 3
12+
# language: python
13+
# name: python3
14+
# ---
15+
16+
# %% [markdown]
17+
# # Cubic Interpolation with Scipy
18+
19+
# %% pycharm={"name": "#%%\n"}
20+
import matplotlib.pyplot as plt
21+
import numpy as np
22+
from scipy.interpolate import CubicHermiteSpline
23+
24+
from HARK.interpolation import CubicInterp, CubicHermiteInterp
25+
26+
# %% [markdown]
27+
# ### Creating a HARK wrapper for scipy's CubicHermiteSpline
28+
#
29+
# The class CubicHermiteInterp in HARK.interpolation implements a HARK wrapper for scipy's CubicHermiteSpline. A HARK wrapper is needed due to the way interpolators are used in solution methods accross HARK, and in particular due to the `distance_criteria` attribute used for VFI convergence.
30+
31+
# %% pycharm={"name": "#%%\n"}
32+
x = np.linspace(0, 10, num=11, endpoint=True)
33+
y = np.cos(-(x**2) / 9.0)
34+
dydx = 2.0 * x / 9.0 * np.sin(-(x**2) / 9.0)
35+
36+
f = CubicInterp(x, y, dydx, lower_extrap=True)
37+
f2 = CubicHermiteSpline(x, y, dydx)
38+
f3 = CubicHermiteInterp(x, y, dydx, lower_extrap=True)
39+
40+
# %% [markdown]
41+
# Above are 3 interpolators, which are:
42+
# 1. **CubicInterp** from HARK.interpolation
43+
# 2. **CubicHermiteSpline** from scipy.interpolate
44+
# 3. **CubicHermiteInterp** hybrid newly implemented in HARK.interpolation
45+
#
46+
# Below we see that they behave in much the same way.
47+
48+
# %% pycharm={"name": "#%%\n"}
49+
xnew = np.linspace(0, 10, num=41, endpoint=True)
50+
51+
plt.plot(x, y, "o", xnew, f(xnew), "-", xnew, f2(xnew), "--", xnew, f3(xnew), "-.")
52+
plt.legend(["data", "hark", "scipy", "hark_new"], loc="best")
53+
plt.show()
54+
55+
# %% [markdown]
56+
# We can also verify that **CubicHermiteInterp** works as intended when extrapolating. Scipy's **CubicHermiteSpline** behaves differently when extrapolating, as it extrapolates using the last polynomial, whereas HARK implements linear decay extrapolation, so it is not shown below.
57+
58+
# %% pycharm={"name": "#%%\n"}
59+
x_out = np.linspace(-1, 11, num=41, endpoint=True)
60+
61+
plt.plot(x, y, "o", x_out, f(x_out), "-", x_out, f3(x_out), "-.")
62+
plt.legend(["data", "hark", "hark_new"], loc="best")
63+
plt.show()
64+
65+
# %% [markdown]
66+
# ### Timings
67+
#
68+
# Below we can compare timings for interpolation and extrapolation among the 3 interpolators. As expected, `scipy`'s CubicHermiteInterpolator (`f2` below) is the fastest, but it's not HARK compatible. `HARK.interpolation`'s CubicInterp (`f`) is the slowest, and `HARK.interpolation`'s new CubicHermiteInterp (`f3`) is somewhere in between.
69+
70+
# %% pycharm={"name": "#%%\n"}
71+
# %timeit f(xnew)
72+
# %timeit f(x_out)
73+
74+
# %% pycharm={"name": "#%%\n"}
75+
# %timeit f2(xnew)
76+
# %timeit f2(x_out)
77+
78+
# %% pycharm={"name": "#%%\n"}
79+
# %timeit f3(xnew)
80+
# %timeit f3(x_out)
81+
82+
# %% [markdown] pycharm={"name": "#%%\n"}
83+
# Notice in particular the difference between interpolating and extrapolating for the new ** CubicHermiteInterp **.The difference comes from having to calculate the extrapolation "by hand", since `HARK` uses linear decay extrapolation, whereas for interpolation it returns `scipy`'s result directly.
84+
85+
# %% [markdown]
86+
# ### Additional features from `scipy`
87+
#
88+
# Since we are using `scipy`'s **CubicHermiteSpline** already, we can add a few new features to `HARK.interpolation`'s new **CubicHermiteInterp** without much effort. These include:
89+
#
90+
# 1. `der_interp(self[, nu])` Construct a new piecewise polynomial representing the derivative.
91+
# 2. `antider_interp(self[, nu])` Construct a new piecewise polynomial representing the antiderivative.
92+
# 3. `integrate(self, a, b[, extrapolate])` Compute a definite integral over a piecewise polynomial.
93+
# 4. `roots(self[, discontinuity, extrapolate])` Find real roots of the the piecewise polynomial.
94+
# 5. `solve(self[, y, discontinuity, extrapolate])` Find real solutions of the the equation pp(x) == y.
95+
96+
# %%
97+
int_0_10 = f3.integrate(a=0, b=10)
98+
int_2_8 = f3.integrate(a=2, b=8)
99+
antiderivative = f3.antider_interp()
100+
int_0_10_calc = antiderivative(10) - antiderivative(0)
101+
int_2_8_calc = antiderivative(8) - antiderivative(2)
102+
103+
# %% [markdown]
104+
# First, we evaluate integration and the antiderivative. Below, we see the numerical integral between 0 and 10 using `integrate` or the `antiderivative` directly. The actual solution is `~1.43325`.
105+
106+
# %%
107+
int_0_10, int_0_10_calc
108+
109+
# %% [markdown]
110+
# The value of the integral between 2 and 8 is `~0.302871`.
111+
112+
# %%
113+
int_2_8, int_2_8_calc
114+
115+
# %% [markdown]
116+
# ### `roots` and `solve`
117+
#
118+
# We evaluate these graphically, by finding zeros, and by finding where the function equals `0.5`.
119+
120+
# %%
121+
roots = f3.roots()
122+
intercept = f3.solve(y=0.5)
123+
124+
plt.plot(roots, np.zeros(roots.size), "o")
125+
plt.plot(intercept, np.repeat(0.5, intercept.size), "o")
126+
plt.plot(xnew, f3(xnew), "-.")
127+
plt.legend(["roots", "intercept", "cubic"], loc="best")
128+
plt.plot(xnew, np.zeros(xnew.size), ".")
129+
plt.plot(xnew, np.ones(xnew.size) * 0.5, ".")
130+
plt.show()
131+
132+
# %%

0 commit comments

Comments
 (0)