Skip to content

Commit b44ade8

Browse files
authored
Merge pull request #13 from EconForge/sl/complete_poly
move complete_polynomial stuff from dolo:
2 parents e943b92 + 6dc5486 commit b44ade8

File tree

3 files changed

+335
-0
lines changed

3 files changed

+335
-0
lines changed

interpolation/complete_poly.py

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
"""
2+
Function approximation using complete polynomials
3+
4+
@author : Spencer Lyon
5+
@date : 2016-01-21 17:06
6+
7+
"""
8+
import numpy as np
9+
from scipy.linalg import lstsq
10+
from numba import jit
11+
12+
# does not list monomials in the right order
13+
#
14+
# def complete_inds(n, d):
15+
# """
16+
# Return all combinations of powers in an n dimensional d degree
17+
# complete polynomial. This will include a term for all 0th order
18+
# variables (i.e. a constant)
19+
#
20+
# Parameters
21+
# ----------
22+
# n : int
23+
# The number of parameters in the polynomial
24+
#
25+
# d : int
26+
# The degree of the complete polynomials
27+
#
28+
# Returns
29+
# -------
30+
# inds : filter
31+
# A python filter object that contains all the indices inside a
32+
# generator
33+
#
34+
# """
35+
# i = itertools.product(*[range(d + 1) for i in range(n)])
36+
# return filter(lambda x: sum(x) <= d, i)
37+
38+
39+
@jit(nopython=True)
40+
def n_complete(n, d):
41+
"""
42+
Return the number of terms in a complete polynomial of degree d in n
43+
variables
44+
45+
Parameters
46+
----------
47+
n : int
48+
The number of parameters in the polynomial
49+
50+
d : int
51+
The degree of the complete polynomials
52+
53+
Returns
54+
-------
55+
m : int
56+
The number of terms in the polynomial
57+
58+
See Also
59+
--------
60+
See `complete_inds`
61+
62+
"""
63+
out = 1
64+
denom = 1
65+
for i in range(d):
66+
tmp = 1
67+
denom *= i + 1
68+
for j in range(i + 1):
69+
tmp *= (n + j)
70+
71+
# out += tmp // math.factorial(i + 1)
72+
out += tmp // denom
73+
return out
74+
75+
76+
def complete_polynomial(z, d):
77+
"""
78+
Construct basis matrix for complete polynomial of degree `d`, given
79+
input data `z`.
80+
81+
Parameters
82+
----------
83+
z : np.ndarray(size=(nvariables, nobservations))
84+
The degree 1 realization of each variable. For example, if
85+
variables are `q`, `r`, and `s`, then `z` should be
86+
`z = np.row_stack([q, r, s])`
87+
88+
d : int
89+
An integer specifying the degree of the complete polynomial
90+
91+
Returns
92+
-------
93+
out : np.ndarray(size=(ncomplete(nvariables, d), nobservations))
94+
The basis matrix for a complete polynomial of degree d
95+
96+
"""
97+
# check inputs
98+
assert d >= 0, "d must be non-negative"
99+
z = np.asarray(z)
100+
101+
# compute inds allocate space for output
102+
nvar, nobs = z.shape
103+
out = np.zeros((n_complete(nvar, d), nobs))
104+
105+
if d > 5:
106+
raise ValueError("Complete polynomial only implemeted up to degree 5")
107+
108+
# populate out with jitted function
109+
_complete_poly_impl(z, d, out)
110+
111+
return out
112+
113+
114+
@jit(nopython=True, cache=True)
115+
def _complete_poly_impl_vec(z, d, out):
116+
"out and z should be vectors"
117+
nvar = z.shape[0]
118+
119+
out[0] = 1.0
120+
121+
# fill first order stuff
122+
if d >= 1:
123+
for i in range(1, nvar + 1):
124+
out[i] = z[i - 1]
125+
126+
if d == 1:
127+
return
128+
129+
# now we need to fill in row nvar and beyond
130+
ix = nvar
131+
if d == 2:
132+
for i1 in range(nvar):
133+
for i2 in range(i1, nvar):
134+
ix += 1
135+
out[ix] = z[i1] * z[i2]
136+
137+
return
138+
139+
if d == 3:
140+
for i1 in range(nvar):
141+
for i2 in range(i1, nvar):
142+
ix += 1
143+
out[ix] = z[i1] * z[i2]
144+
145+
for i3 in range(i2, nvar):
146+
ix += 1
147+
out[ix] = z[i1] * z[i2] * z[i3]
148+
149+
return
150+
151+
if d == 4:
152+
for i1 in range(nvar):
153+
for i2 in range(i1, nvar):
154+
ix += 1
155+
out[ix] = z[i1] * z[i2]
156+
157+
for i3 in range(i2, nvar):
158+
ix += 1
159+
out[ix] = z[i1] * z[i2] * z[i3]
160+
161+
for i4 in range(i3, nvar):
162+
ix += 1
163+
out[ix] = z[i1] * z[i2] * z[i3] * z[i4]
164+
165+
return
166+
167+
if d == 5:
168+
for i1 in range(nvar):
169+
for i2 in range(i1, nvar):
170+
ix += 1
171+
out[ix] = z[i1] * z[i2]
172+
173+
for i3 in range(i2, nvar):
174+
ix += 1
175+
out[ix] = z[i1] * z[i2] * z[i3]
176+
177+
for i4 in range(i3, nvar):
178+
ix += 1
179+
out[ix] = z[i1] * z[i2] * z[i3] * z[i4]
180+
181+
for i5 in range(i4, nvar):
182+
ix += 1
183+
out[ix] = z[i1] * z[i2] * z[i3] * z[i4] * z[i5]
184+
185+
return
186+
187+
188+
@jit(nopython=True, cache=True)
189+
def _complete_poly_impl(z, d, out):
190+
nvar = z.shape[0]
191+
nobs = z.shape[1]
192+
193+
for k in range(nobs):
194+
out[0, k] = 1.0
195+
196+
# fill first order stuff
197+
if d >= 1:
198+
for i in range(1, nvar + 1):
199+
for k in range(nobs):
200+
out[i, k] = z[i - 1, k]
201+
202+
if d == 1:
203+
return
204+
205+
# now we need to fill in row nvar and beyond
206+
ix = nvar
207+
if d == 2:
208+
for i1 in range(nvar):
209+
for i2 in range(i1, nvar):
210+
ix += 1
211+
for k in range(nobs):
212+
out[ix, k] = z[i1, k] * z[i2, k]
213+
214+
return
215+
216+
if d == 3:
217+
for i1 in range(nvar):
218+
for i2 in range(i1, nvar):
219+
ix += 1
220+
for k in range(nobs):
221+
out[ix, k] = z[i1, k] * z[i2, k]
222+
223+
for i3 in range(i2, nvar):
224+
ix += 1
225+
for k in range(nobs):
226+
out[ix, k] = z[i1, k] * z[i2, k] * z[i3, k]
227+
228+
return
229+
230+
if d == 4:
231+
for i1 in range(nvar):
232+
for i2 in range(i1, nvar):
233+
ix += 1
234+
for k in range(nobs):
235+
out[ix, k] = z[i1, k] * z[i2, k]
236+
237+
for i3 in range(i2, nvar):
238+
ix += 1
239+
for k in range(nobs):
240+
out[ix, k] = z[i1, k] * z[i2, k] * z[i3, k]
241+
242+
for i4 in range(i3, nvar):
243+
ix += 1
244+
for k in range(nobs):
245+
out[ix, k] = (z[i1, k] * z[i2, k] * z[i3, k] *
246+
z[i4, k])
247+
248+
return
249+
250+
if d == 5:
251+
for i1 in range(nvar):
252+
for i2 in range(i1, nvar):
253+
ix += 1
254+
for k in range(nobs):
255+
out[ix, k] = z[i1, k] * z[i2, k]
256+
257+
for i3 in range(i2, nvar):
258+
ix += 1
259+
for k in range(nobs):
260+
out[ix, k] = z[i1, k] * z[i2, k] * z[i3, k]
261+
262+
for i4 in range(i3, nvar):
263+
ix += 1
264+
for k in range(nobs):
265+
out[ix, k] = (z[i1, k] * z[i2, k] * z[i3, k] *
266+
z[i4, k])
267+
268+
for i5 in range(i4, nvar):
269+
ix += 1
270+
for k in range(nobs):
271+
out[ix, k] = (z[i1, k] * z[i2, k] * z[i3, k] *
272+
z[i4, k] * z[i5, k])
273+
274+
return
275+
276+
277+
class CompletePolynomial:
278+
279+
def __init__(self, n, d):
280+
self.n = n
281+
self.d = d
282+
283+
def fit_values(self, s, x):
284+
Phi = complete_polynomial(s.T, self.d).T
285+
self.Phi = Phi
286+
self.coefs = np.ascontiguousarray(lstsq(Phi, x)[0])
287+
288+
def __call__(self, s):
289+
290+
Phi = complete_polynomial(s.T, self.d).T
291+
return np.dot(Phi, self.coefs)

interpolation/tests/__init__.py

Whitespace-only changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import numpy as np
2+
import numpy as np
3+
4+
from interpolation.complete_poly import CompletePolynomial
5+
6+
def test_complete_scalar():
7+
8+
def f(x, y): return x
9+
10+
points = np.random.random((1000, 2))
11+
vals = f(points[:, 0], points[:, 1])
12+
13+
cp = CompletePolynomial(2, 3)
14+
cp.fit_values(points, vals)
15+
16+
evals = cp(points)
17+
18+
assert(evals.shape == vals.shape)
19+
assert(abs(evals - vals).max() < 1e-10)
20+
21+
22+
def test_complete_vector():
23+
24+
def f(x, y): return x
25+
def f2(x, y): return x**3 - y
26+
27+
points = np.random.random((1000, 2))
28+
vals = np.column_stack([
29+
f(points[:, 0], points[:, 1]),
30+
f2(points[:, 0], points[:, 1])
31+
])
32+
33+
cp = CompletePolynomial(2, 3)
34+
cp.fit_values(points, vals)
35+
36+
evals = cp(points)
37+
38+
assert(evals.shape == vals.shape)
39+
assert(abs(evals - vals).max() < 1e-10)
40+
41+
42+
if __name__ == '__main__':
43+
test_complete_scalar()
44+
test_complete_vector()

0 commit comments

Comments
 (0)