Skip to content

Commit dbf35e6

Browse files
Florian WilhelmFlorian Wilhelm
Florian Wilhelm
authored and
Florian Wilhelm
committed
Moved files from byts
1 parent 9dde191 commit dbf35e6

File tree

7 files changed

+539
-31
lines changed

7 files changed

+539
-31
lines changed

.gitignore

+16-5
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
1-
# Temporary files
1+
# Temporary and binary files
22
*~
3-
*.py[co]
3+
*.py[cod]
4+
*.so
45
*.cfg
56
*.orig
6-
.coverage
7-
.tox
7+
*.log
8+
*.pot
9+
__pycache__/*
810
.cache/*
911

1012
# Project files
1113
.ropeproject
1214
.project
1315
.pydevproject
1416
.settings
15-
.ideagit stat
17+
.idea
1618

1719
# Package files
1820
*.egg
21+
.installed.cfg
1922
*.egg-info
2023

24+
# Unittest and coverage
25+
htmlcov/*
26+
.coverage
27+
.tox
28+
junit.xml
29+
coverage.xml
30+
2131
# Build and docs folder/files
2232
build/*
2333
dist/*
34+
sdist/*
2435
docs/_rst/*
2536
docs/_build/*
2637
cover/*

COPYING

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2014, Florian Wilhelm.
1+
Copyright (c) 2014, Blue Yonder.
22
All rights reserved.
33

44

@@ -10,7 +10,7 @@ modification, are permitted provided that the following conditions are met:
1010
b. Redistributions in binary form must reproduce the above copyright
1111
notice, this list of conditions and the following disclaimer in the
1212
documentation and/or other materials provided with the distribution.
13-
c. Neither the name of the pydse developers nor the names of
13+
c. Neither the name of the PyDSE developers nor the names of
1414
its contributors may be used to endorse or promote products
1515
derived from this software without specific prior written
1616
permission.

README.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
=====
2-
pydse
2+
PyDSE
33
=====
44

5-
Your project was successfully set up with PyScaffold 0.4.1.
5+
Your project was successfully set up with PyScaffold 0.5.
66
Following features are available:
77

88
Packaging

pydse/arma.py

+260
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
4+
from __future__ import division, print_function, absolute_import
5+
6+
import logging
7+
import numpy as np
8+
from numpy import linalg
9+
from scipy import optimize
10+
11+
12+
_logger = logging.getLogger(__name__)
13+
14+
15+
class ARMAError(Exception):
16+
pass
17+
18+
19+
class ARMA(object):
20+
"""
21+
A(L)y(t) = B(L)e(t) + C(L)u(t) - TREND(t)
22+
23+
L: Shift operator
24+
A: (axpxp) tensor to define auto-regression
25+
B: (bxpxp) tensor to define moving-average
26+
C: (cxpxm) tensor for external input
27+
e: (pxt) matrix of unobserved disturbance (white noise)
28+
y: (pxt) matrix of observed output variables
29+
u: (mxt) matrix of input variables
30+
TREND: (pxt) matrix like y or a p-dim vector
31+
"""
32+
def __init__(self, A=None, B=None, C=None, TREND=None, rand_state=None):
33+
self.A = np.asarray(A[0]).reshape(A[1], order='F')
34+
self.B = np.asarray(B[0]).reshape(B[1], order='F') if B else np.zeros(shape=A[1])
35+
self.C = np.asarray(C[0]).reshape(C[1], order='F') if C else np.empty((0,))
36+
self.TREND = np.asarray(TREND) if TREND is not None else None
37+
self._check_consistency(self.A, self.B, self.C, self.TREND)
38+
39+
self.Aconst = np.zeros(self.A.shape, dtype=np.bool)
40+
self.Bconst = np.zeros(self.B.shape, dtype=np.bool)
41+
self.Cconst = np.zeros(self.C.shape, dtype=np.bool)
42+
43+
self.rand = rand_state if rand_state is not None else np.random.RandomState()
44+
45+
def _set_array_by_mask(self, arr, mask, values):
46+
mask = np.where(mask == False)
47+
arr[mask] = values
48+
49+
def _get_array_by_mask(self, arr, mask):
50+
mask = np.where(mask == False)
51+
return arr[mask]
52+
53+
def _get_num_non_consts(self):
54+
a = np.sum(self.Aconst == False)
55+
b = np.sum(self.Bconst == False)
56+
c = np.sum(self.Cconst == False)
57+
return (a, b, c)
58+
59+
@property
60+
def non_consts(self):
61+
a, b, c = self._get_num_non_consts()
62+
A = self._get_array_by_mask(self.A, self.Aconst)
63+
B = self._get_array_by_mask(self.B, self.Bconst)
64+
C = self._get_array_by_mask(self.C, self.Cconst)
65+
return np.hstack([A, B, C])
66+
67+
@non_consts.setter
68+
def non_consts(self, values):
69+
a, b, c = self._get_num_non_consts()
70+
if values.size != a + b + c:
71+
raise ARMAError("Number of values does not equal number of non-constants")
72+
A_values = values[:a]
73+
B_values = values[a:a + b]
74+
C_values = values[a + b:a + b + c]
75+
self._set_array_by_mask(self.A, self.Aconst, A_values)
76+
self._set_array_by_mask(self.B, self.Bconst, B_values)
77+
self._set_array_by_mask(self.C, self.Cconst, C_values)
78+
79+
def _check_consistency(self, A, B, C, TREND):
80+
if A is None:
81+
raise ARMAError("A needs to be set for an ARMA model")
82+
n = A.shape[1]
83+
if n != A.shape[2] or len(A.shape) > 3:
84+
raise ARMAError("A needs to be of shape (a, p, p)")
85+
if n != B.shape[1] or (n != B.shape[2] or len(B.shape) > 3):
86+
raise ARMAError("B needs to be of shape (b, p, p) with A being of shape (a, p, p)")
87+
if C.size != 0 and (n != C.shape[1] or len(C.shape) > 3):
88+
raise ARMAError("C needs to be of shape (c, p, m) with A being of shape (a, p, p)")
89+
if TREND is not None:
90+
if len(TREND.shape) > 2:
91+
raise ARMAError("TREND needs to of shape (p, t) with A being of shape (a, p, p)")
92+
elif len(TREND.shape) == 2 and n != TREND.shape[0]:
93+
raise ARMAError("TREND needs to of shape (p, t) with A being of shape (a, p, p)")
94+
elif len(TREND.shape) == 1 and n != TREND.shape[0]:
95+
raise ARMAError("TREND needs to of shape (p, t) with A being of shape (a, p, p)")
96+
97+
def _get_noise(self, samples, p, lags):
98+
w0 = self.rand.normal(size=lags * p).reshape((lags, p))
99+
w = self.rand.normal(size=samples * p).reshape((samples, p))
100+
return (w0, w)
101+
102+
def _prep_y(self, trend, dim_t, dim_p):
103+
if trend is not None:
104+
if len(trend.shape) == 2:
105+
assert trend.shape[1] == dim_t
106+
y = np.copy(trend)
107+
else:
108+
y = np.tile(trend, (dim_t, 1))
109+
else:
110+
y = np.zeros((dim_t, dim_p))
111+
return y
112+
113+
def simulate(self, y0=None, u0=None, sampleT=100, noise=None):
114+
p = self.A.shape[1]
115+
a, b = self.A.shape[0], self.B.shape[0]
116+
c = self.C.shape[0] if self.C else 0
117+
m = self.C.shape[2] if self.C else 0
118+
y0 = y0 if y0 else np.zeros((a, p))
119+
u0 = u0 if u0 else np.zeros((c, m))
120+
121+
# generate white noise if necessary
122+
if not noise:
123+
noise = self._get_noise(sampleT, p, b)
124+
w0, w = noise
125+
126+
# diagonalize with respect to matrix of leading coefficients
127+
A0inv = linalg.inv(self.A[0, :, :])
128+
A = np.tensordot(self.A, A0inv, axes=1)
129+
B = np.tensordot(self.B, A0inv, axes=1)
130+
if self.C:
131+
C = np.tensordot(self.C, A0inv, axes=1)
132+
133+
# perform simulation
134+
y = self._prep_y(self.TREND, sampleT, p)
135+
for t in xrange(sampleT):
136+
for l in xrange(1, a):
137+
if t - l <= -1:
138+
y[t, :] = y[t, :] - np.dot(A[l, :, :], y0[l - t - 1, :])
139+
else:
140+
y[t, :] = y[t, :] - np.dot(A[l, :, :], y[t - l, :])
141+
142+
for l in xrange(b):
143+
if t - l <= -1:
144+
y[t, :] = y[t, :] + np.dot(B[l, :, :], w0[l - t - 1, :])
145+
else:
146+
y[t, :] = y[t, :] + np.dot(B[l, :, :], w[t - l, :])
147+
148+
for l in xrange(c):
149+
if t - l <= -1:
150+
y[t, :] = y[t, :] + np.dot(C[l, :, :], u0[l - t - 1, :])
151+
else:
152+
y[t, :] = y[t, :] + np.dot(C[l, :, :], u[t - l, :])
153+
154+
return y
155+
156+
def forecast(self, y, u=None):
157+
p = self.A.shape[1]
158+
a, b = self.A.shape[0], self.B.shape[0]
159+
c = self.C.shape[0] if self.C else 0
160+
m = self.C.shape[2] if self.C else 0
161+
TREND = self.TREND
162+
163+
# ToDo: Let these be parameters and do consistensy check
164+
sampleT = predictT = y.shape[0]
165+
pred_err = np.zeros((sampleT, p))
166+
167+
if TREND is not None:
168+
if len(TREND.shape) == 2:
169+
assert TREND.shape[1] == sampleT
170+
else:
171+
TREND = np.tile(self.TREND, (sampleT, 1))
172+
173+
# diagonalize with respect to matrix of leading coefficients
174+
B0inv = linalg.inv(self.B[0, :, :])
175+
A = np.tensordot(self.A, B0inv, axes=1)
176+
B = np.tensordot(self.B, B0inv, axes=1)
177+
if self.C:
178+
C = np.tensordot(self.C, B0inv, axes=1)
179+
if TREND is not None:
180+
TREND = np.dot(TREND, B0inv)
181+
182+
# perform prediction
183+
for t in xrange(sampleT):
184+
if TREND is not None:
185+
vt = -TREND[t, :]
186+
else:
187+
vt = np.zeros((p,))
188+
189+
for l in xrange(a):
190+
if l <= t:
191+
vt = vt + np.dot(A[l, :, :], y[t - l, :])
192+
193+
for l in xrange(1, b):
194+
if l <= t:
195+
vt = vt - np.dot(B[l, :, :], pred_err[t - l, :])
196+
197+
for l in xrange(c):
198+
if l <= t:
199+
vt = vt - np.dot(C[l, :, :], u[t - l, :])
200+
201+
pred_err[t, :] = vt
202+
203+
pred = np.zeros((predictT, p))
204+
pred[:sampleT, :] = y[:sampleT, :] - np.dot(pred_err, B[0, :, :])
205+
206+
# ToDo: Implement this!
207+
if predictT > sampleT:
208+
pass
209+
210+
return pred
211+
212+
def fix_constants(self, fuzz=1e-5, prec=1):
213+
@np.vectorize
214+
def is_const(x):
215+
return abs(x - round(x, prec)) < fuzz
216+
217+
def set_const(M, Mconst):
218+
M_mask = is_const(M)
219+
Mconst[M_mask] = True
220+
Mconst[~M_mask] = False
221+
222+
set_const(self.A, self.Aconst)
223+
set_const(self.B, self.Bconst)
224+
if self.C.size != 0:
225+
set_const(self.C, self.Cconst)
226+
227+
@staticmethod
228+
def negloglike(pred, y):
229+
sampleT = pred.shape[0]
230+
res = pred[:sampleT, :] - y[:sampleT, :]
231+
p = res.shape[1]
232+
233+
Om = np.dot(res.T, res) / sampleT
234+
235+
if np.any(np.isnan(Om)) or np.any(Om > 1e100):
236+
like1 = like2 = 1e100
237+
else:
238+
_, s, _ = linalg.svd(Om)
239+
240+
# Check for degeneracy
241+
non_degen_mask = s > s[0] * np.sqrt(np.finfo(np.float).eps)
242+
if not np.all(non_degen_mask):
243+
_logger.warn("Covariance matrix is singular. Working on subspace")
244+
s = s[non_degen_mask]
245+
246+
like1 = 0.5 * sampleT * np.log(np.prod(s))
247+
like2 = 0.5 * sampleT * len(s)
248+
249+
const = 0.5 * sampleT * p * np.log(2 * np.pi)
250+
return like1 + like2 + const
251+
252+
def est_params(self, y):
253+
def cost_function(x):
254+
self.non_consts = x
255+
pred = self.forecast(y=y)
256+
return self.negloglike(pred, y)
257+
258+
x0 = self.non_consts
259+
return optimize.minimize(cost_function, x0)
260+

0 commit comments

Comments
 (0)