Skip to content

Commit d9c99e7

Browse files
committed
Massive test update
1 parent 140ac03 commit d9c99e7

File tree

3 files changed

+268
-233
lines changed

3 files changed

+268
-233
lines changed

pydmd/havok.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ def hankel(self, X):
300300
)
301301

302302
Hm = m - ((self._delays - 1) * self._lag)
303-
H = np.empty((n * self._delays, Hm))
303+
H = np.empty((n * self._delays, Hm), dtype="complex")
304304
for i in range(self._delays):
305305
H[i * n : (i + 1) * n] = X[:, i * self._lag : i * self._lag + Hm]
306306

@@ -321,7 +321,7 @@ def dehankel(self, H):
321321
Hn, Hm = H.shape
322322
n = int(Hn / self._delays)
323323
m = int(Hm + ((self._delays - 1) * self._lag))
324-
X = np.empty((n, m))
324+
X = np.empty((n, m), dtype="complex")
325325
for i in range(self._delays):
326326
X[:, i * self._lag : i * self._lag + Hm] = H[i * n : (i + 1) * n]
327327
return X

tests/test_havok.py

Lines changed: 231 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import numpy as np
22
from pytest import raises
33
from scipy.integrate import solve_ivp
4-
54
from numpy.testing import assert_equal
65

6+
from pydmd import DMD
7+
from pydmd import PiDMD
78
from pydmd import HAVOK
89

910

@@ -12,8 +13,9 @@ def generate_lorenz_data(t_eval):
1213
Given a time vector t_eval = t1, t2, ..., evaluates and returns
1314
the snapshots of the Lorenz system as columns of the matrix X.
1415
"""
16+
1517
def lorenz_system(t, state):
16-
sigma, rho, beta = 10, 28, 8/3 # chaotic parameters
18+
sigma, rho, beta = 10, 28, 8 / 3 # chaotic parameters
1719
x, y, z = state
1820
x_dot = sigma * (y - x)
1921
y_dot = (x * (rho - z)) - y
@@ -38,7 +40,7 @@ def lorenz_system(t, state):
3840

3941

4042
# Generate Lorenz system data.
41-
dt = 0.001 # time step
43+
dt = 0.001 # time step
4244
m = 50000 # number of data samples
4345
t = np.arange(m) * dt
4446
X = generate_lorenz_data(t)
@@ -47,17 +49,11 @@ def lorenz_system(t, state):
4749

4850
def test_error_fitted():
4951
"""
50-
Ensure that attempting to get attributes results
52+
Ensure that attempting to get HAVOK attributes results
5153
in an error if fit() has not yet been called.
5254
"""
5355
havok = HAVOK()
5456

55-
with raises(ValueError):
56-
_ = havok.snapshots
57-
with raises(ValueError):
58-
_ = havok.ho_snapshots
59-
with raises(ValueError):
60-
_ = havok.time
6157
with raises(ValueError):
6258
_ = havok.modes
6359
with raises(ValueError):
@@ -82,12 +78,20 @@ def test_error_fitted():
8278

8379
def test_hankel_1():
8480
"""
85-
Test
81+
Test that the hankel and dehankel functions work as intended.
82+
Use 1-D data, lag = 1, and various delay values.
8683
"""
8784
dummy_data = np.array([[1, 2, 3, 4]])
8885

8986
havok = HAVOK(delays=1)
90-
assert_equal(havok.hankel(dummy_data), np.array([[1, 2, 3, 4],]))
87+
assert_equal(
88+
havok.hankel(dummy_data),
89+
np.array(
90+
[
91+
[1, 2, 3, 4],
92+
]
93+
),
94+
)
9195
assert_equal(havok.dehankel(havok.hankel(dummy_data)), dummy_data)
9296

9397
havok = HAVOK(delays=2)
@@ -99,7 +103,25 @@ def test_hankel_1():
99103
assert_equal(havok.dehankel(havok.hankel(dummy_data)), dummy_data)
100104

101105
havok = HAVOK(delays=4)
102-
assert_equal(havok.hankel(dummy_data), np.array([[1,], [2,], [3,], [4,]]))
106+
assert_equal(
107+
havok.hankel(dummy_data),
108+
np.array(
109+
[
110+
[
111+
1,
112+
],
113+
[
114+
2,
115+
],
116+
[
117+
3,
118+
],
119+
[
120+
4,
121+
],
122+
]
123+
),
124+
)
103125
assert_equal(havok.dehankel(havok.hankel(dummy_data)), dummy_data)
104126

105127
with raises(ValueError):
@@ -109,7 +131,8 @@ def test_hankel_1():
109131

110132
def test_hankel_2():
111133
"""
112-
Test
134+
Test that the hankel and dehankel functions work as intended.
135+
Use 2-D data, lag = 1, and various delay values.
113136
"""
114137
dummy_data = np.array([[1, 2, 3], [4, 5, 6]])
115138
H2 = np.array([[1, 2], [4, 5], [2, 3], [5, 6]])
@@ -134,7 +157,8 @@ def test_hankel_2():
134157

135158
def test_hankel_3():
136159
"""
137-
Test
160+
Test that the hankel and dehankel functions work as intended.
161+
Use 1-D data, lag = 2, and various delay values.
138162
"""
139163
dummy_data = np.array([[1, 2, 3, 4, 5, 6]])
140164
H2 = np.array([[1, 2, 3, 4], [3, 4, 5, 6]])
@@ -149,12 +173,12 @@ def test_hankel_3():
149173
assert_equal(havok.hankel(dummy_data), H2)
150174
assert_equal(havok.dehankel(havok.hankel(dummy_data)), dummy_data)
151175

152-
havok = HAVOK(delays=3, lag=3)
176+
havok = HAVOK(delays=3, lag=2)
153177
assert_equal(havok.hankel(dummy_data), H3)
154178
assert_equal(havok.dehankel(havok.hankel(dummy_data)), dummy_data)
155179

156180
with raises(ValueError):
157-
havok = HAVOK(delays=2, lag=2)
181+
havok = HAVOK(delays=4, lag=2)
158182
havok.hankel(dummy_data)
159183

160184

@@ -186,43 +210,215 @@ def test_shape_2():
186210
assert havok.B.shape == (havok.r - 2, 2)
187211

188212

189-
def test_r():
213+
def test_snapshots_1():
190214
"""
191-
Ensure the accuracy of the r property in the following situations:
215+
Test the stored snapshots and ho_snapshots.
216+
Ensure that they are accurate in the default case with 1-D data.
192217
"""
193-
# If no svd truncation, r is the min of the dimensions of the hankel matrix
194-
havok = HAVOK(svd_rank=-1)
218+
havok = HAVOK()
219+
with raises(ValueError):
220+
_ = havok.snapshots
221+
with raises(ValueError):
222+
_ = havok.ho_snapshots
195223
havok.fit(x, t)
196-
assert havok.r == min(havok.delays, len(t) - havok.delays + 1)
224+
assert_equal(havok.snapshots, x)
225+
assert_equal(havok.ho_snapshots, havok.hankel(x))
226+
227+
228+
def test_snapshots_2():
229+
"""
230+
Test the stored snapshots and ho_snapshots.
231+
Ensure that they are accurate in the default case with 2-D data.
232+
"""
233+
havok = HAVOK().fit(X, t)
234+
assert_equal(havok.snapshots, X)
235+
assert_equal(havok.ho_snapshots, havok.hankel(X))
236+
197237

198-
# Test the above case, but for a larger d value
199-
havok = HAVOK(svd_rank=-1, delays=500)
238+
def test_time_1():
239+
"""
240+
Test the stored time attribute.
241+
Ensure that it is accurate in the default case.
242+
"""
243+
havok = HAVOK()
244+
with raises(ValueError):
245+
_ = havok.time
200246
havok.fit(x, t)
201-
assert havok.r == min(havok.delays, len(t) - havok.delays + 1)
247+
assert_equal(havok.time, t)
248+
249+
250+
def test_time_2():
251+
"""
252+
Test that a HAVOK model fitted with a time vector is essentially
253+
the same as a HAVOK model fitted with the time-step dt. Check the
254+
stored time vector and the computed HAVOK operator.
255+
"""
256+
havok_1 = HAVOK().fit(x, t)
257+
havok_2 = HAVOK().fit(x, dt)
258+
assert_equal(havok_1.time, havok_2.time)
259+
assert_equal(havok_1.operator, havok_2.operator)
260+
202261

203-
# Test the above case, but for an even larger d value
204-
havok = HAVOK(svd_rank=-1, delays=len(t) - 20)
262+
def test_plot_summary_1():
263+
"""
264+
Test that everything is fine if we fit a HAVOK
265+
model and ask for the default summary plot.
266+
"""
267+
havok = HAVOK(svd_rank=16, delays=100)
205268
havok.fit(x, t)
206-
assert havok.r == min(havok.delays, len(t) - havok.delays + 1)
269+
havok.plot_summary()
207270

208271

209-
def test_reconstruction():
272+
def test_plot_summary_2():
210273
"""
211-
Test the accuracy of the HAVOK reconstruction.
274+
Test that everything is fine if we fit a HAVOK model and
275+
ask for various (but still valid) plot modifications.
212276
"""
213277
havok = HAVOK(svd_rank=16, delays=100)
214278
havok.fit(x, t)
215-
error = x - havok.reconstructed_data.real
216-
assert np.linalg.norm(error) / np.linalg.norm(x) < 0.2
279+
havok.plot_summary(
280+
num_plot=15000,
281+
index_linear=(0, 1),
282+
forcing_threshold=0.005,
283+
min_jump_dist=200,
284+
figsize=(15, 4),
285+
dpi=100,
286+
)
287+
288+
289+
def test_plot_summary_3():
290+
"""
291+
Test that an error is thrown if we ask for
292+
a summary from an unfitted HAVOK model.
293+
"""
294+
havok = HAVOK()
295+
with raises(ValueError):
296+
havok.plot_summary()
297+
298+
299+
def test_reconstruction_1():
300+
"""
301+
Test the accuracy of the HAVOK reconstruction.
302+
"""
303+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
304+
error = x - havok.reconstructed_data
305+
assert np.linalg.norm(error) / np.linalg.norm(x) < 0.05
306+
307+
308+
def test_reconstruction_2():
309+
"""
310+
Test the accuracy of the sHAVOK reconstruction.
311+
"""
312+
havok = HAVOK(svd_rank=4, delays=100, structured=True).fit(x, t)
313+
error = x[:-1] - havok.reconstructed_data
314+
assert np.linalg.norm(error) / np.linalg.norm(x[:-1]) < 0.07
315+
316+
317+
def test_predict_1():
318+
"""
319+
Test the accuracy of the HAVOK prediction.
320+
Ensure that prediction with the HAVOK forcing term and the times
321+
of fitting simply yields the computed data reconstruction.
322+
"""
323+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
324+
assert_equal(
325+
havok.predict(havok.forcing, havok.time),
326+
havok.reconstructed_data,
327+
)
328+
329+
330+
def test_predict_2():
331+
"""
332+
Test the accuracy of the HAVOK prediction.
333+
Test that predicting beyond the training set isn't absurdly inaccurate.
334+
"""
335+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
336+
337+
# Build a longer data set and fit a HAVOK model to it.
338+
t_long = np.arange(2 * m) * dt
339+
x_long = generate_lorenz_data(t_long)[0]
340+
havok_long = HAVOK(svd_rank=16, delays=100).fit(x_long, t_long)
341+
342+
# We only use the long HAVOK model to obtain a long forcing signal.
343+
forcing_long = havok_long.forcing
344+
time_long = t_long[: len(forcing_long)]
217345

346+
# Get the error of the full prediction.
347+
error = x_long - havok.predict(forcing_long, time_long)
348+
assert np.linalg.norm(error) / np.linalg.norm(x_long) < 0.45
218349

219-
def test_predict():
350+
351+
def test_predict_3():
220352
"""
221353
Test the accuracy of the HAVOK prediction.
354+
Test that predicting with V0 indices is functionally
355+
the same as predicting with array-valued V0 inputs.
356+
"""
357+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
358+
assert_equal(
359+
havok.predict(havok.forcing, havok.time, V0=0),
360+
havok.predict(havok.forcing, havok.time, V0=havok.linear_dynamics[0]),
361+
)
362+
assert_equal(
363+
havok.predict(havok.forcing, havok.time, V0=1),
364+
havok.predict(havok.forcing, havok.time, V0=havok.linear_dynamics[1]),
365+
)
366+
assert_equal(
367+
havok.predict(havok.forcing, havok.time, V0=-1),
368+
havok.predict(havok.forcing, havok.time, V0=havok.linear_dynamics[-1]),
369+
)
370+
371+
372+
def test_threshold_1():
222373
"""
374+
Test compute_threshold function.
375+
Test that threshold computation works as expected, whether you plot or not.
376+
"""
377+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
378+
thres_1 = havok.compute_threshold(p=0.1, bins=100, plot=False)
379+
thres_2 = havok.compute_threshold(p=0.1, bins=100, plot=True)
380+
assert thres_1 == thres_2
381+
382+
383+
def test_threshold_2():
384+
"""
385+
Test compute_threshold function.
386+
Test that threshold computation works as expected, whether you index
387+
the stored forcing term, or provide a custom array-valued forcing term.
388+
"""
389+
havok = HAVOK(svd_rank=16, delays=100).fit(x, t)
390+
vr = havok.forcing.flatten()
391+
thres_1 = havok.compute_threshold(p=0.1, bins=100)
392+
thres_2 = havok.compute_threshold(forcing=vr, p=0.1, bins=100)
393+
assert thres_1 == thres_2
223394

224395

225-
def test_time():
396+
def test_dmd_1():
226397
"""
227-
Test that
398+
Test that HAVOK works when used with an externally-defined DMD model.
399+
Test the basic exact DMD model - ensure that plot_summary works just
400+
fine and that the data reconstruction is still accurate.
228401
"""
402+
dmd = DMD(svd_rank=-1)
403+
havok = HAVOK(svd_rank=16, delays=100, dmd=dmd).fit(x, t)
404+
havok.plot_summary()
405+
error = x - havok.reconstructed_data
406+
assert np.linalg.norm(error) / np.linalg.norm(x) < 0.05
407+
408+
409+
def test_dmd_2():
410+
"""
411+
Test that HAVOK works when used with an externally-defined DMD model.
412+
Test a physics-informed DMD model - ensure that plot_summary works just
413+
fine and that the data reconstruction is still accurate.
414+
"""
415+
pidmd = PiDMD(
416+
svd_rank=-1,
417+
manifold="diagonal",
418+
manifold_opt=2,
419+
compute_A=True,
420+
)
421+
havok = HAVOK(svd_rank=4, delays=100, dmd=pidmd).fit(x, t)
422+
havok.plot_summary()
423+
error = x - havok.reconstructed_data
424+
assert np.linalg.norm(error) / np.linalg.norm(x) < 0.1

0 commit comments

Comments
 (0)