dsa-metric 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ from DSA.dsa import DSA
2
+ from DSA.dmd import DMD
3
+ from DSA.kerneldmd import KernelDMD
4
+ from DSA.simdist import SimilarityTransformDist
5
+ from DSA.stats import *
@@ -0,0 +1,596 @@
1
+ """This module computes the Havok DMD model for a given dataset."""
2
+ import numpy as np
3
+ import torch
4
+
5
+ def embed_signal_torch(data, n_delays, delay_interval=1):
6
+ """
7
+ Create a delay embedding from the provided tensor data.
8
+
9
+ Parameters
10
+ ----------
11
+ data : torch.tensor
12
+ The data from which to create the delay embedding. Must be either: (1) a
13
+ 2-dimensional array/tensor of shape T x N where T is the number
14
+ of time points and N is the number of observed dimensions
15
+ at each time point, or (2) a 3-dimensional array/tensor of shape
16
+ K x T x N where K is the number of "trials" and T and N are
17
+ as defined above.
18
+
19
+ n_delays : int
20
+ Parameter that controls the size of the delay embedding. Explicitly,
21
+ the number of delays to include.
22
+
23
+ delay_interval : int
24
+ The number of time steps between each delay in the delay embedding. Defaults
25
+ to 1 time step.
26
+ """
27
+ if isinstance(data, np.ndarray):
28
+ data = torch.from_numpy(data)
29
+ device = data.device
30
+
31
+ if data.shape[int(data.ndim==3)] - (n_delays - 1)*delay_interval < 1:
32
+ raise ValueError("The number of delays is too large for the number of time points in the data!")
33
+
34
+ # initialize the embedding
35
+ if data.ndim == 3:
36
+ embedding = torch.zeros((data.shape[0], data.shape[1] - (n_delays - 1)*delay_interval, data.shape[2]*n_delays)).to(device)
37
+ else:
38
+ embedding = torch.zeros((data.shape[0] - (n_delays - 1)*delay_interval, data.shape[1]*n_delays)).to(device)
39
+
40
+ for d in range(n_delays):
41
+ index = (n_delays - 1 - d)*delay_interval
42
+ ddelay = d*delay_interval
43
+
44
+ if data.ndim == 3:
45
+ ddata = d*data.shape[2]
46
+ embedding[:,:, ddata: ddata + data.shape[2]] = data[:,index:data.shape[1] - ddelay]
47
+ else:
48
+ ddata = d*data.shape[1]
49
+ embedding[:, ddata:ddata + data.shape[1]] = data[index:data.shape[0] - ddelay]
50
+
51
+ return embedding
52
+
53
+ class DMD:
54
+ """DMD class for computing and predicting with DMD models.
55
+ """
56
+ def __init__(
57
+ self,
58
+ data,
59
+ n_delays,
60
+ delay_interval=1,
61
+ rank=None,
62
+ rank_thresh=None,
63
+ rank_explained_variance=None,
64
+ reduced_rank_reg=False,
65
+ lamb=0,
66
+ device='cpu',
67
+ verbose=False,
68
+ send_to_cpu=False,
69
+ steps_ahead=1
70
+ ):
71
+ """
72
+ Parameters
73
+ ----------
74
+ data : np.ndarray or torch.tensor
75
+ The data to fit the DMD model to. Must be either: (1) a
76
+ 2-dimensional array/tensor of shape T x N where T is the number
77
+ of time points and N is the number of observed dimensions
78
+ at each time point, or (2) a 3-dimensional array/tensor of shape
79
+ K x T x N where K is the number of "trials" and T and N are
80
+ as defined above.
81
+
82
+ n_delays : int
83
+ Parameter that controls the size of the delay embedding. Explicitly,
84
+ the number of delays to include.
85
+
86
+ delay_interval : int
87
+ The number of time steps between each delay in the delay embedding. Defaults
88
+ to 1 time step.
89
+
90
+ rank : int
91
+ The rank of V in fitting HAVOK DMD - i.e., the number of columns of V to
92
+ use to fit the DMD model. Defaults to None, in which case all columns of V
93
+ will be used.
94
+
95
+ rank_thresh : float
96
+ Parameter that controls the rank of V in fitting HAVOK DMD by dictating a threshold
97
+ of singular values to use. Explicitly, the rank of V will be the number of singular
98
+ values greater than rank_thresh. Defaults to None.
99
+
100
+ rank_explained_variance : float
101
+ Parameter that controls the rank of V in fitting HAVOK DMD by indicating the percentage of
102
+ cumulative explained variance that should be explained by the columns of V. Defaults to None.
103
+
104
+ reduced_rank_reg : bool
105
+ Determines whether to use reduced rank regression (True) or principal component regression (False)
106
+
107
+ lamb : float
108
+ Regularization parameter for ridge regression. Defaults to 0.
109
+
110
+ device: string, int, or torch.device
111
+ A string, int or torch.device object to indicate the device to torch.
112
+
113
+ verbose: bool
114
+ If True, print statements will be provided about the progress of the fitting procedure.
115
+
116
+ send_to_cpu: bool
117
+ If True, will send all tensors in the object back to the cpu after everything is computed.
118
+ This is implemented to prevent gpu memory overload when computing multiple DMDs.
119
+
120
+ steps_ahead: int
121
+ The number of time steps ahead to predict. Defaults to 1.
122
+ """
123
+
124
+ self.device = device
125
+ self._init_data(data)
126
+
127
+ self.n_delays = n_delays
128
+ self.delay_interval = delay_interval
129
+ self.rank = rank
130
+ self.rank_thresh = rank_thresh
131
+ self.rank_explained_variance = rank_explained_variance
132
+ self.reduced_rank_reg = reduced_rank_reg
133
+ self.lamb = lamb
134
+ self.verbose = verbose
135
+ self.send_to_cpu = send_to_cpu
136
+ self.steps_ahead = steps_ahead
137
+
138
+ # Hankel matrix
139
+ self.H = None
140
+
141
+ # SVD attributes
142
+ self.U = None
143
+ self.S = None
144
+ self.V = None
145
+ self.S_mat = None
146
+ self.S_mat_inv = None
147
+
148
+ # DMD attributes
149
+ self.A_v = None
150
+ self.A_havok_dmd = None
151
+
152
+ def _init_data(self, data):
153
+ # check if the data is an np.ndarry - if so, convert it to Torch
154
+ if isinstance(data, np.ndarray):
155
+ data = torch.from_numpy(data)
156
+ self.data = data
157
+ if self.data.ndim == 2:
158
+ self.data = self.data.unsqueeze(0)
159
+ # create attributes for the data dimensions
160
+ if self.data.ndim == 3:
161
+ self.ntrials = self.data.shape[0]
162
+ self.window = self.data.shape[1]
163
+ self.n = self.data.shape[2]
164
+ else:
165
+ self.window = self.data.shape[0]
166
+ self.n = self.data.shape[1]
167
+ self.ntrials = 1
168
+
169
+ return data
170
+
171
+ def compute_hankel(
172
+ self,
173
+ data=None,
174
+ n_delays=None,
175
+ delay_interval=None,
176
+ ):
177
+ """
178
+ Computes the Hankel matrix from the provided data.
179
+
180
+ Parameters
181
+ ----------
182
+ data : np.ndarray or torch.tensor
183
+ The data to fit the DMD model to. Must be either: (1) a
184
+ 2-dimensional array/tensor of shape T x N where T is the number
185
+ of time points and N is the number of observed dimensions
186
+ at each time point, or (2) a 3-dimensional array/tensor of shape
187
+ K x T x N where K is the number of "trials" and T and N are
188
+ as defined above.
189
+
190
+ n_delays : int
191
+ Parameter that controls the size of the delay embedding. Explicitly,
192
+ the number of delays to include. Defaults to None - provide only if you want
193
+ to override the value of n_delays from the init.
194
+
195
+ delay_interval : int
196
+ The number of time steps between each delay in the delay embedding. Defaults
197
+ to 1 time step. Defaults to None - provide only if you want
198
+ to override the value of n_delays from the init.
199
+ """
200
+ if self.verbose:
201
+ print("Computing Hankel matrix ...")
202
+
203
+ # if parameters are provided, overwrite them from the init
204
+ self.data = self.data if data is None else self._init_data(data)
205
+ self.n_delays = self.n_delays if n_delays is None else n_delays
206
+ self.delay_interval = self.delay_interval if delay_interval is None else delay_interval
207
+ self.data = self.data.to(self.device)
208
+
209
+ self.H = embed_signal_torch(self.data, self.n_delays, self.delay_interval)
210
+
211
+ if self.verbose:
212
+ print("Hankel matrix computed!")
213
+
214
+ def compute_svd(self):
215
+ """
216
+ Computes the SVD of the Hankel matrix.
217
+ """
218
+
219
+ if self.verbose:
220
+ print("Computing SVD on Hankel matrix ...")
221
+ if self.H.ndim == 3: #flatten across trials for 3d
222
+ H = self.H.reshape(self.H.shape[0] * self.H.shape[1], self.H.shape[2])
223
+ else:
224
+ H = self.H
225
+ # compute the SVD
226
+ U, S, Vh = torch.linalg.svd(H.T, full_matrices=False)
227
+
228
+ # update attributes
229
+ V = Vh.T
230
+ self.U = U
231
+ self.S = S
232
+ self.V = V
233
+
234
+ # construct the singuar value matrix and its inverse
235
+ # dim = self.n_delays * self.n
236
+ # s = len(S)
237
+ # self.S_mat = torch.zeros(dim, dim,dtype=torch.float32).to(self.device)
238
+ # self.S_mat_inv = torch.zeros(dim, dim,dtype=torch.float32).to(self.device)
239
+ self.S_mat = torch.diag(S).to(self.device)
240
+ self.S_mat_inv= torch.diag(1 / S).to(self.device)
241
+
242
+ # compute explained variance
243
+ exp_variance_inds = self.S**2 / ((self.S**2).sum())
244
+ cumulative_explained = torch.cumsum(exp_variance_inds, 0)
245
+ self.cumulative_explained_variance = cumulative_explained
246
+
247
+ #make the X and Y components of the regression by staggering the hankel eigen-time delay coordinates by time
248
+ if self.reduced_rank_reg:
249
+ V = self.V
250
+ else:
251
+ V = self.V
252
+
253
+ if self.ntrials > 1:
254
+ if V.numel() < self.H.numel():
255
+ raise ValueError("The dimension of the SVD of the Hankel matrix is smaller than the dimension of the Hankel matrix itself. \n \
256
+ This is likely due to the number of time points being smaller than the number of dimensions. \n \
257
+ Please reduce the number of delays.")
258
+
259
+ V = V.reshape(self.H.shape)
260
+
261
+ #first reshape back into Hankel shape, separated by trials
262
+ newshape = (self.H.shape[0]*(self.H.shape[1]-self.steps_ahead),self.H.shape[2])
263
+ self.Vt_minus = V[:,:-self.steps_ahead].reshape(newshape)
264
+ self.Vt_plus = V[:,self.steps_ahead:].reshape(newshape)
265
+ else:
266
+ self.Vt_minus = V[:-self.steps_ahead]
267
+ self.Vt_plus = V[self.steps_ahead:]
268
+
269
+
270
+ if self.verbose:
271
+ print("SVD complete!")
272
+
273
+ def recalc_rank(self,rank,rank_thresh,rank_explained_variance):
274
+ '''
275
+ Parameters
276
+ ----------
277
+ rank : int
278
+ The rank of V in fitting HAVOK DMD - i.e., the number of columns of V to
279
+ use to fit the DMD model. Defaults to None, in which case all columns of V
280
+ will be used. Provide only if you want to override the value from the init.
281
+
282
+ rank_thresh : float
283
+ Parameter that controls the rank of V in fitting HAVOK DMD by dictating a threshold
284
+ of singular values to use. Explicitly, the rank of V will be the number of singular
285
+ values greater than rank_thresh. Defaults to None - provide only if you want
286
+ to override the value from the init.
287
+
288
+ rank_explained_variance : float
289
+ Parameter that controls the rank of V in fitting HAVOK DMD by indicating the percentage of
290
+ cumulative explained variance that should be explained by the columns of V. Defaults to None -
291
+ provide only if you want to overried the value from the init.
292
+ '''
293
+ # if an argument was provided, overwrite the stored rank information
294
+ none_vars = (rank is None) + (rank_thresh is None) + (rank_explained_variance is None)
295
+ if none_vars != 3:
296
+ self.rank = None
297
+ self.rank_thresh = None
298
+ self.rank_explained_variance = None
299
+
300
+ self.rank = self.rank if rank is None else rank
301
+ self.rank_thresh = self.rank_thresh if rank_thresh is None else rank_thresh
302
+ self.rank_explained_variance = self.rank_explained_variance if rank_explained_variance is None else rank_explained_variance
303
+
304
+ none_vars = (self.rank is None) + (self.rank_thresh is None) + (self.rank_explained_variance is None)
305
+ if none_vars < 2:
306
+ raise ValueError("More than one value was provided between rank, rank_thresh, and rank_explained_variance. Please provide only one of these, and ensure the others are None!")
307
+ elif none_vars == 3:
308
+ self.rank = len(self.S)
309
+
310
+ if self.reduced_rank_reg:
311
+ S = self.proj_mat_S
312
+ else:
313
+ S = self.S
314
+
315
+ if rank_thresh is not None:
316
+ if S[-1] > rank_thresh:
317
+ self.rank = len(S)
318
+ else:
319
+ self.rank = torch.argmax(torch.arange(len(S), 0, -1).to(self.device)*(S < rank_thresh))
320
+
321
+ if rank_explained_variance is not None:
322
+ self.rank = int(torch.argmax((self.cumulative_explained_variance > rank_explained_variance).type(torch.int)).cpu().numpy())
323
+
324
+ if self.rank > self.H.shape[-1]:
325
+ self.rank = self.H.shape[-1]
326
+
327
+ if self.rank is None:
328
+ if S[-1] > self.rank_thresh:
329
+ self.rank = len(S)
330
+ else:
331
+ self.rank = torch.argmax(torch.arange(len(S), 0, -1).to(self.device)*(S < self.rank_thresh))
332
+
333
+ def compute_havok_dmd(self,lamb=None):
334
+ """
335
+ Computes the Havok DMD matrix (Principal Component Regression)
336
+
337
+ Parameters
338
+ ----------
339
+ lamb : float
340
+ Regularization parameter for ridge regression. Defaults to 0 - provide only if you want
341
+ to override the value of n_delays from the init.
342
+
343
+ """
344
+ if self.verbose:
345
+ print("Computing least squares fits to HAVOK DMD ...")
346
+
347
+ self.lamb = self.lamb if lamb is None else lamb
348
+
349
+ A_v = (torch.linalg.inv(self.Vt_minus[:, :self.rank].T @ self.Vt_minus[:, :self.rank] + self.lamb*torch.eye(self.rank).to(self.device)) \
350
+ @ self.Vt_minus[:, :self.rank].T @ self.Vt_plus[:, :self.rank]).T
351
+ self.A_v = A_v
352
+ self.A_havok_dmd = self.U @ self.S_mat[:self.U.shape[1], :self.rank] @ self.A_v @ self.S_mat_inv[:self.rank, :self.U.shape[1]] @ self.U.T
353
+
354
+ if self.verbose:
355
+ print("Least squares complete! \n")
356
+
357
+ def get_projections_onto_modes(self):
358
+ """
359
+ Returns the projection of each time point onto each mode
360
+ """
361
+ assert self.A_v is not None, "DMD must be fit before projecting onto modes"
362
+ eigvals, eigvecs = torch.linalg.eigh(self.A_v)
363
+ #project Vt_minus onto the eigenvectors
364
+ projections = self.V[:,:self.rank] @ eigvecs
365
+ projections = projections.reshape(self.data.shape[0],self.data.shape[1]-self.n_delays+1,-1)
366
+
367
+ #get the data that matches the shape of the original data
368
+ return projections, self.data[:,:self.n_delays-1]
369
+
370
+
371
+ def compute_proj_mat(self,lamb=None):
372
+ if self.verbose:
373
+ print("Computing Projector Matrix for Reduced Rank Regression")
374
+
375
+ self.lamb = self.lamb if lamb is None else lamb
376
+
377
+ self.proj_mat = self.Vt_plus.T @ self.Vt_minus @ torch.linalg.inv(self.Vt_minus.T @ self.Vt_minus +
378
+ self.lamb*torch.eye(self.Vt_minus.shape[1]).to(self.device)) @ \
379
+ self.Vt_minus.T @ self.Vt_plus
380
+
381
+ self.proj_mat_S, self.proj_mat_V = torch.linalg.eigh(self.proj_mat)
382
+ #todo: more efficient to flip ranks (negative index) in compute_reduced_rank_regression but also less interpretable
383
+ self.proj_mat_S = torch.flip(self.proj_mat_S, dims=(0,))
384
+ self.proj_mat_V = torch.flip(self.proj_mat_V, dims=(1,))
385
+
386
+ if self.verbose:
387
+ print("Projector Matrix computed! \n")
388
+
389
+ def compute_reduced_rank_regression(self,lamb=None):
390
+ if self.verbose:
391
+ print("Computing Reduced Rank Regression ...")
392
+
393
+ self.lamb = self.lamb if lamb is None else lamb
394
+ proj_mat = self.proj_mat_V[:,:self.rank] @ self.proj_mat_V[:,:self.rank].T
395
+ B_ols = torch.linalg.inv(self.Vt_minus.T @ self.Vt_minus + self.lamb*torch.eye(self.Vt_minus.shape[1]).to(self.device)) @ self.Vt_minus.T @ self.Vt_plus
396
+
397
+ self.A_v = B_ols @ proj_mat
398
+ self.A_havok_dmd = self.U @ self.S_mat[:self.U.shape[1],:self.A_v.shape[1]] @ self.A_v.T @ self.S_mat_inv[:self.A_v.shape[0], :self.U.shape[1]] @ self.U.T
399
+
400
+
401
+ if self.verbose:
402
+ print("Reduced Rank Regression complete! \n")
403
+
404
+ def substitute_shift_operator(self):
405
+ '''
406
+ the shift operator is a rectangular matrix of shape [dim*(n_delays-nshift),dim*n_delays]
407
+ where the first dim*(n_delays-nshift) rows are the identity matrix
408
+ and the last dim*nshift rows are zeros
409
+ this can be substituted for the bottom of the Havok matrix to predict nshift steps ahead
410
+ why? it can reduce noise
411
+ '''
412
+ if self.A_havok_dmd is None:
413
+ if self.verbose:
414
+ print("Havok DMD must be computed before substituting the shift operator")
415
+ return
416
+ if self.steps_ahead // self.delay_interval != self.steps_ahead / self.delay_interval:
417
+ if self.verbose:
418
+ print("steps_ahead / delay_interval must be an integer to substitute the shift operator")
419
+ return
420
+
421
+ nshift = self.steps_ahead // self.delay_interval
422
+
423
+ if self.n*(self.n_delays - nshift) <= 0 :
424
+ if self.verbose:
425
+ print("n*(n_delays - nshift) must be greater than 0 to substitute the shift operator")
426
+ return
427
+ # create the shift operator
428
+
429
+ shift_operator = torch.eye(self.n*(self.n_delays - nshift)).to(self.device)
430
+ shift_operator = torch.vstack([shift_operator, torch.zeros(self.n*nshift,self.n*(self.n_delays - nshift)).to(self.device)]).T
431
+
432
+ self.A_havok_dmd[self.n*nshift:,:] = shift_operator
433
+
434
+ def fit(
435
+ self,
436
+ data=None,
437
+ n_delays=None,
438
+ delay_interval=None,
439
+ rank=None,
440
+ rank_thresh=None,
441
+ rank_explained_variance=None,
442
+ lamb=None,
443
+ device=None,
444
+ verbose=None,
445
+ steps_ahead=None,
446
+ substitute_shift_operator=False
447
+ ):
448
+ """
449
+ Parameters
450
+ ----------
451
+ data : np.ndarray or torch.tensor
452
+ The data to fit the DMD model to. Must be either: (1) a
453
+ 2-dimensional array/tensor of shape T x N where T is the number
454
+ of time points and N is the number of observed dimensions
455
+ at each time point, or (2) a 3-dimensional array/tensor of shape
456
+ K x T x N where K is the number of "trials" and T and N are
457
+ as defined above. Defaults to None - provide only if you want to
458
+ override the value from the init.
459
+
460
+ n_delays : int
461
+ Parameter that controls the size of the delay embedding. Explicitly,
462
+ the number of delays to include. Defaults to None - provide only if you want to
463
+ override the value from the init.
464
+
465
+ delay_interval : int
466
+ The number of time steps between each delay in the delay embedding. Defaults to None -
467
+ provide only if you want to override the value from the init.
468
+
469
+ rank : int
470
+ The rank of V in fitting HAVOK DMD - i.e., the number of columns of V to
471
+ use to fit the DMD model. Defaults to None, in which case all columns of V
472
+ will be used - provide only if you want to
473
+ override the value from the init.
474
+
475
+ rank_thresh : int
476
+ Parameter that controls the rank of V in fitting HAVOK DMD by dictating a threshold
477
+ of singular values to use. Explicitly, the rank of V will be the number of singular
478
+ values greater than rank_thresh. Defaults to None - provide only if you want to
479
+ override the value from the init.
480
+
481
+ rank_explained_variance : float
482
+ Parameter that controls the rank of V in fitting HAVOK DMD by indicating the percentage of
483
+ cumulative explained variance that should be explained by the columns of V. Defaults to None -
484
+ provide only if you want to overried the value from the init.
485
+
486
+ lamb : float
487
+ Regularization parameter for ridge regression. Defaults to None - provide only if you want to
488
+ override the value from the init.
489
+
490
+ device: string or int
491
+ A string or int to indicate the device to torch. For example, can be 'cpu' or 'cuda',
492
+ or alternatively 0 if the intenion is to use GPU device 0. Defaults to None - provide only
493
+ if you want to override the value from the init.
494
+
495
+ verbose: bool
496
+ If True, print statements will be provided about the progress of the fitting procedure.
497
+ Defaults to None - provide only if you want to override the value from the init.
498
+
499
+ steps_ahead: int
500
+ The number of time steps ahead to predict. Defaults to 1.
501
+
502
+ substitute_shift_operator: bool
503
+ If true, will substitute the bottom of the Havok matrix with the shift operator
504
+ Note that this will only work if steps_ahead / delay_interval is an integer
505
+
506
+ """
507
+ # if parameters are provided, overwrite them from the init
508
+ self.steps_ahead = self.steps_ahead if steps_ahead is None else steps_ahead
509
+ self.device = self.device if device is None else device
510
+ self.verbose = self.verbose if verbose is None else verbose
511
+ rank = self.rank if rank is None else rank
512
+ rank_thresh = self.rank_thresh if rank_thresh is None else rank_thresh
513
+ rank_explained_variance = self.rank_explained_variance if rank_explained_variance is None else rank_explained_variance
514
+
515
+ self.compute_hankel(data, n_delays, delay_interval)
516
+ self.compute_svd()
517
+
518
+ if self.reduced_rank_reg:
519
+ self.compute_proj_mat(lamb)
520
+ self.recalc_rank(rank,rank_thresh,rank_explained_variance)
521
+ self.compute_reduced_rank_regression(lamb)
522
+ else:
523
+ self.recalc_rank(rank,rank_thresh,rank_explained_variance)
524
+ self.compute_havok_dmd(lamb)
525
+ if substitute_shift_operator:
526
+ self.substitute_shift_operator()
527
+
528
+ if self.send_to_cpu:
529
+ self.all_to_device('cpu') #send back to the cpu to save memory
530
+
531
+ def predict(
532
+ self,
533
+ test_data=None,
534
+ reseed=None,
535
+ full_return=False
536
+ ):
537
+ """
538
+ Returns
539
+ -------
540
+ pred_data : torch.tensor
541
+ The predictions generated by the HAVOK model. Of the same shape as test_data. Note that the first
542
+ (self.n_delays - 1)*self.delay_interval + 1 time steps of the generated predictions are by construction
543
+ identical to the test_data.
544
+
545
+ H_test_havok_dmd : torch.tensor (Optional)
546
+ Returned if full_return=True. The predicted Hankel matrix generated by the HAVOK model.
547
+ H_test : torch.tensor (Optional)
548
+ Returned if full_return=True. The true Hankel matrix
549
+ """
550
+ # initialize test_data
551
+ if test_data is None:
552
+ test_data = self.data
553
+ if isinstance(test_data, np.ndarray):
554
+ test_data = torch.from_numpy(test_data).to(self.device)
555
+ ndim = test_data.ndim
556
+ if ndim == 2:
557
+ test_data = test_data.unsqueeze(0)
558
+ H_test = embed_signal_torch(test_data, self.n_delays, self.delay_interval)
559
+ steps_ahead = self.steps_ahead if self.steps_ahead is not None else 1
560
+
561
+ if reseed is None:
562
+ reseed = 1
563
+
564
+ H_test_havok_dmd = torch.zeros(H_test.shape).to(self.device)
565
+ H_test_havok_dmd[:, :steps_ahead] = H_test[:, :steps_ahead]
566
+
567
+ A = self.A_havok_dmd.unsqueeze(0)
568
+ for t in range(steps_ahead, H_test.shape[1]):
569
+ if t % reseed == 0:
570
+ H_test_havok_dmd[:, t] = (A @ H_test[:, t - steps_ahead].transpose(-2, -1)).transpose(-2, -1)
571
+ else:
572
+ H_test_havok_dmd[:, t] = (A @ H_test_havok_dmd[:, t - steps_ahead].transpose(-2, -1)).transpose(-2, -1)
573
+ pred_data = torch.hstack([test_data[:, :(self.n_delays - 1)*self.delay_interval + steps_ahead], H_test_havok_dmd[:, steps_ahead:, :self.n]])
574
+
575
+ if ndim == 2:
576
+ pred_data = pred_data[0]
577
+
578
+ if full_return:
579
+ return pred_data, H_test_havok_dmd, H_test
580
+ else:
581
+ return pred_data
582
+
583
+ def all_to_device(self,device='cpu'):
584
+ for k,v in self.__dict__.items():
585
+ if isinstance(v, torch.Tensor):
586
+ self.__dict__[k] = v.to(device)
587
+
588
+
589
+ def project_onto_modes(self):
590
+ eigvals, eigvecs = torch.linalg.eigh(self.A_v)
591
+ #project Vt_minus onto the eigenvectors
592
+ projections = self.V[:,:self.rank] @ eigvecs
593
+ projections = projections.reshape(self.data.shape[0],self.data.shape[1]-self.n_delays+1,-1)
594
+
595
+ #get the data that matches the shape of the original data
596
+ return projections, self.data[:,:-self.n_delays+1]