Skip to content

Commit 3eebf4c

Browse files
authored
Merge pull request PyDMD#515 from nmank/lagged_dmdc
Lagged dmdc
2 parents 7ac9f3f + 837ffbd commit 3eebf4c

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

pydmd/dmdc.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,14 @@ class DMDc(DMDBase):
191191
equal than `svd_rank`. For the possible values please refer to the
192192
`svd_rank` parameter description above.
193193
:type svd_rank_omega: int or float
194+
:param lag: the time lag for the snapshots. Used in fit method to generate
195+
X and Y.
196+
:type lag: int
194197
"""
195198

196-
def __init__(self, svd_rank=0, tlsq_rank=0, opt=False, svd_rank_omega=-1):
199+
def __init__(
200+
self, svd_rank=0, tlsq_rank=0, opt=False, svd_rank_omega=-1, lag=1
201+
):
197202
# we're going to initialize Atilde when we know if B is known
198203
self._Atilde = None
199204
# remember the arguments for when we'll need them
@@ -210,6 +215,7 @@ def __init__(self, svd_rank=0, tlsq_rank=0, opt=False, svd_rank_omega=-1):
210215
self._snapshots_holder = None
211216
self._controlin = None
212217
self._basis = None
218+
self._lag = lag
213219

214220
self._modes_activation_bitmask_proxy = None
215221

@@ -255,7 +261,7 @@ def reconstructed_data(self, control_input=None):
255261
else self._controlin
256262
)
257263

258-
if controlin.shape[-1] != self.dynamics.shape[-1] - 1:
264+
if controlin.shape[-1] != self.dynamics.shape[-1] - self._lag:
259265
raise RuntimeError(
260266
"The number of control inputs and the number of snapshots to "
261267
"reconstruct has to be the same"
@@ -268,7 +274,8 @@ def reconstructed_data(self, control_input=None):
268274
[self.modes, np.diag(eigs), np.linalg.pinv(self.modes)]
269275
)
270276

271-
data = [self.snapshots[:, 0]]
277+
data = [self.snapshots[:, i] for i in range(self._lag)]
278+
272279
expected_shape = data[0].shape
273280

274281
for i, u in enumerate(controlin.T):
@@ -298,13 +305,16 @@ def fit(self, X, I, B=None):
298305
:type B: numpy.ndarray or iterable
299306
"""
300307
self._reset()
308+
self._controlin = np.atleast_2d(np.asarray(I))
301309

302310
self._snapshots_holder = Snapshots(X)
303-
self._controlin = np.atleast_2d(np.asarray(I))
311+
n_samples = self.snapshots.shape[-1]
312+
313+
if self._lag < 1:
314+
raise ValueError("Time lag must be positive.")
304315

305-
n_samples = self.snapshots.shape[1]
306-
X = self.snapshots[:, :-1]
307-
Y = self.snapshots[:, 1:]
316+
X = self.snapshots[:, : -self._lag]
317+
Y = self.snapshots[:, self._lag :]
308318

309319
self._set_initial_time_dictionary(
310320
{"t0": 0, "tend": n_samples - 1, "dt": 1}

tests/test_dmdc.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ def create_system_without_B():
2626
return {"snapshots": snapshots, "u": u, "B": B, "A": A}
2727

2828

29+
def create_system_without_B_lag(lag=1):
30+
n = 5 # dimension snapshots
31+
m = 15 # number snapshots
32+
A = scipy.linalg.helmert(n, True)
33+
B = np.random.rand(n, n) - 0.5
34+
snapshots = []
35+
for _ in range(lag):
36+
snapshots.append(np.array([0.25] * n))
37+
u = np.random.rand(n, m - lag) - 0.5
38+
for i in range(m - lag):
39+
snapshots.append(A.dot(snapshots[i]) + B.dot(u[:, i]))
40+
snapshots = np.array(snapshots).T
41+
return {"snapshots": snapshots, "u": u, "B": B, "A": A}
42+
43+
2944
def test_eigs_b_known():
3045
system = create_system_with_B()
3146
dmdc = DMDc(svd_rank=-1)
@@ -41,13 +56,29 @@ def test_eigs_b_unknown():
4156
assert dmdc.eigs.shape[0] == 3
4257

4358

59+
def test_eigs_b_unknown_lag():
60+
lag = 3
61+
system = create_system_without_B_lag(lag=lag)
62+
dmdc = DMDc(svd_rank=3, opt=False, svd_rank_omega=4, lag=lag)
63+
dmdc.fit(system["snapshots"], system["u"])
64+
assert dmdc.eigs.shape[0] == 3
65+
66+
4467
def test_modes_b_unknown():
4568
system = create_system_without_B()
4669
dmdc = DMDc(svd_rank=3, opt=False, svd_rank_omega=4)
4770
dmdc.fit(system["snapshots"], system["u"])
4871
assert dmdc.modes.shape[1] == 3
4972

5073

74+
def test_modes_b_unknown_lag():
75+
lag = 3
76+
system = create_system_without_B_lag(lag=lag)
77+
dmdc = DMDc(svd_rank=3, opt=False, svd_rank_omega=4, lag=lag)
78+
dmdc.fit(system["snapshots"], system["u"])
79+
assert dmdc.modes.shape[1] == 3
80+
81+
5182
def test_reconstruct_b_known():
5283
system = create_system_with_B()
5384
dmdc = DMDc(svd_rank=-1)
@@ -73,6 +104,16 @@ def test_reconstruct_b_unknown():
73104
)
74105

75106

107+
def test_reconstruct_b_unknown_lag():
108+
lag = 3
109+
system = create_system_without_B_lag(lag=lag)
110+
dmdc = DMDc(svd_rank=-1, opt=True, lag=lag)
111+
dmdc.fit(system["snapshots"], system["u"])
112+
np.testing.assert_array_almost_equal(
113+
dmdc.reconstructed_data(), system["snapshots"], decimal=6
114+
)
115+
116+
76117
def test_atilde_b_unknown():
77118
system = create_system_without_B()
78119
dmdc = DMDc(svd_rank=-1, opt=True)
@@ -232,3 +273,10 @@ def test_correct_amplitudes():
232273
dmd = DMDc(svd_rank=-1)
233274
dmd.fit(system["snapshots"], system["u"], system["B"])
234275
np.testing.assert_array_almost_equal(dmd.amplitudes, dmd._b)
276+
277+
278+
def test_lag_param_b_unknown_raises():
279+
system = create_system_without_B_lag(lag=3)
280+
dmdc = DMDc(svd_rank=-1, opt=True, lag=0)
281+
with raises(ValueError):
282+
dmdc.fit(system["snapshots"], system["u"])

0 commit comments

Comments
 (0)