modulo-vki 2.0.7__py3-none-any.whl → 2.1.0__py3-none-any.whl
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.
- modulo_vki/__init__.py +0 -22
- modulo_vki/core/_dft.py +92 -21
- modulo_vki/core/_dmd_s.py +48 -39
- modulo_vki/core/_k_matrix.py +145 -17
- modulo_vki/core/_mpod_time.py +46 -25
- modulo_vki/core/_pod_space.py +3 -2
- modulo_vki/core/_pod_time.py +2 -1
- modulo_vki/core/spatial_structures.py +367 -0
- modulo_vki/core/temporal_structures.py +241 -0
- modulo_vki/core/utils.py +474 -0
- modulo_vki/modulo.py +751 -682
- modulo_vki/modulo_old.py +1368 -0
- modulo_vki/utils/_utils.py +19 -2
- modulo_vki/utils/others.py +18 -9
- {modulo_vki-2.0.7.dist-info → modulo_vki-2.1.0.dist-info}/METADATA +123 -30
- modulo_vki-2.1.0.dist-info/RECORD +26 -0
- {modulo_vki-2.0.7.dist-info → modulo_vki-2.1.0.dist-info}/WHEEL +1 -1
- modulo_vki-2.0.7.dist-info/RECORD +0 -22
- {modulo_vki-2.0.7.dist-info → modulo_vki-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {modulo_vki-2.0.7.dist-info → modulo_vki-2.1.0.dist-info}/top_level.txt +0 -0
modulo_vki/__init__.py
CHANGED
|
@@ -1,23 +1 @@
|
|
|
1
|
-
|
|
2
|
-
#from ._version import get_versions
|
|
3
|
-
#__version__ = get_versions()['version']
|
|
4
|
-
#del get_versions
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# from .utils.read_db import *
|
|
8
|
-
# from .utils._utils import *
|
|
9
|
-
# from .utils._plots import *
|
|
10
|
-
# from .utils.others import *
|
|
11
|
-
|
|
12
|
-
# from .core._k_matrix import *
|
|
13
|
-
# from .core._dft import *
|
|
14
|
-
# from .core._dmd_s import *
|
|
15
|
-
# from .core._k_matrix import *
|
|
16
|
-
# from .core._mpod_time import *
|
|
17
|
-
# from .core._mpod_space import *
|
|
18
|
-
# from .core._pod_time import *
|
|
19
|
-
# from .core._pod_space import *
|
|
20
|
-
# from .core._spod_s import *
|
|
21
|
-
# from .core._spod_t import *
|
|
22
|
-
|
|
23
1
|
from modulo_vki.modulo import ModuloVKI
|
modulo_vki/core/_dft.py
CHANGED
|
@@ -6,27 +6,39 @@ from tqdm import tqdm
|
|
|
6
6
|
|
|
7
7
|
def dft_fit(N_T, F_S, D, FOLDER_OUT, SAVE_DFT=False):
|
|
8
8
|
"""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
Computes the Discrete Fourier Transform (DFT) from the provided dataset.
|
|
10
|
+
|
|
11
|
+
Note
|
|
12
|
+
----
|
|
13
|
+
Memory saving feature is currently not supported by this function.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
N_T : int
|
|
18
|
+
Number of temporal snapshots.
|
|
19
|
+
|
|
20
|
+
F_S : float
|
|
21
|
+
Sampling frequency in Hz.
|
|
22
|
+
|
|
23
|
+
D : np.ndarray
|
|
24
|
+
Snapshot matrix.
|
|
25
|
+
|
|
26
|
+
FOLDER_OUT : str
|
|
27
|
+
Directory path where results are saved if `SAVE_DFT` is True.
|
|
28
|
+
|
|
29
|
+
SAVE_DFT : bool, default=False
|
|
30
|
+
If True, computed results are saved to disk and released from memory.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
Sorted_Freqs : np.ndarray
|
|
35
|
+
Frequency bins in Hz, sorted in ascending order.
|
|
36
|
+
|
|
37
|
+
Phi_F : np.ndarray
|
|
38
|
+
Complex spatial structures corresponding to each frequency mode.
|
|
39
|
+
|
|
40
|
+
SIGMA_F : np.ndarray
|
|
41
|
+
Real amplitudes associated with each frequency mode.
|
|
30
42
|
"""
|
|
31
43
|
n_t = int(N_T)
|
|
32
44
|
Freqs = np.fft.fftfreq(n_t) * F_S # Compute the frequency bins
|
|
@@ -59,3 +71,62 @@ def dft_fit(N_T, F_S, D, FOLDER_OUT, SAVE_DFT=False):
|
|
|
59
71
|
np.savez(FOLDER_OUT + 'DFT/dft_fitted', Freqs=Sorted_Freqs, Phis=Phi_F, Sigmas=SIGMA_F)
|
|
60
72
|
|
|
61
73
|
return Sorted_Freqs, Phi_F, SIGMA_F
|
|
74
|
+
|
|
75
|
+
def dft(N_T, F_S, D, FOLDER_OUT, SAVE_DFT=False):
|
|
76
|
+
"""
|
|
77
|
+
Computes the Discrete Fourier Transform (DFT) from the provided dataset.
|
|
78
|
+
|
|
79
|
+
Note
|
|
80
|
+
----
|
|
81
|
+
Memory saving feature is currently not supported by this function.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
N_T : int
|
|
86
|
+
Number of temporal snapshots.
|
|
87
|
+
|
|
88
|
+
F_S : float
|
|
89
|
+
Sampling frequency in Hz.
|
|
90
|
+
|
|
91
|
+
D : np.ndarray
|
|
92
|
+
Snapshot matrix.
|
|
93
|
+
|
|
94
|
+
FOLDER_OUT : str
|
|
95
|
+
Directory path where results are saved if `SAVE_DFT` is True.
|
|
96
|
+
|
|
97
|
+
SAVE_DFT : bool, default=False
|
|
98
|
+
If True, computed results are saved to disk and released from memory.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
Phi_F : np.ndarray
|
|
103
|
+
Complex spatial structures corresponding to each frequency mode.
|
|
104
|
+
|
|
105
|
+
Sorted_Freqs : np.ndarray
|
|
106
|
+
Frequency bins in Hz, sorted in ascending order.
|
|
107
|
+
|
|
108
|
+
SIGMA_F : np.ndarray
|
|
109
|
+
Real amplitudes associated with each frequency mode.
|
|
110
|
+
"""
|
|
111
|
+
n_t = int(N_T)
|
|
112
|
+
Freqs = np.fft.fftfreq(n_t) * F_S # Compute the frequency bins
|
|
113
|
+
|
|
114
|
+
# FFT along the snapshot axis
|
|
115
|
+
PHI_SIGMA = np.fft.fft(D, axis=1) / np.sqrt(n_t)
|
|
116
|
+
sigma_F = np.linalg.norm(PHI_SIGMA, axis=0) # Compute the norm of each column
|
|
117
|
+
|
|
118
|
+
# make phi_F orthonormal
|
|
119
|
+
Phi_F = PHI_SIGMA / sigma_F
|
|
120
|
+
|
|
121
|
+
# Sort
|
|
122
|
+
Indices = np.flipud(np.argsort(sigma_F)) # find indices for sorting in decreasing order
|
|
123
|
+
Sorted_Sigmas = sigma_F[Indices] # Sort all the sigmas
|
|
124
|
+
Sorted_Freqs = Freqs[Indices] # Sort all the frequencies accordingly.
|
|
125
|
+
Phi_F = Phi_F[:, Indices] # Sorted Spatial Structures Matrix
|
|
126
|
+
sigma_F = Sorted_Sigmas # Sorted Amplitude Matrix (vector)
|
|
127
|
+
|
|
128
|
+
if SAVE_DFT:
|
|
129
|
+
os.makedirs(FOLDER_OUT + 'DFT', exist_ok=True)
|
|
130
|
+
np.savez(FOLDER_OUT + 'DFT/dft_fitted', Freqs=Sorted_Freqs, Phis=Phi_F, Sigmas=SIGMA_F)
|
|
131
|
+
|
|
132
|
+
return Phi_F, Sorted_Freqs, sigma_F
|
modulo_vki/core/_dmd_s.py
CHANGED
|
@@ -5,63 +5,72 @@ from ..utils._utils import switch_svds
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def dmd_s(D_1, D_2, n_Modes, F_S,
|
|
8
|
-
SAVE_T_DMD=False,
|
|
9
|
-
FOLDER_OUT='./',
|
|
10
|
-
svd_solver: str = 'svd_sklearn_truncated'
|
|
8
|
+
SAVE_T_DMD: bool = False,
|
|
9
|
+
FOLDER_OUT: str = './',
|
|
10
|
+
svd_solver: str = 'svd_sklearn_truncated',
|
|
11
|
+
verbose=True):
|
|
11
12
|
"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
Compute the Dynamic Mode Decomposition (DMD) using the PIP algorithm.
|
|
14
|
+
|
|
15
|
+
This implementation follows the Penland & Sardeshmukh PIP approach and
|
|
16
|
+
recovers the same modes as the exact DMD of Tu et al. (2014).
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
D_1 : ndarray, shape (n_features, n_time-1)
|
|
21
|
+
First snapshot matrix (columns 0 to n_t-2 of the full data).
|
|
22
|
+
D_2 : ndarray, shape (n_features, n_time-1)
|
|
23
|
+
Second snapshot matrix (columns 1 to n_t-1 of the full data).
|
|
24
|
+
n_Modes : int
|
|
25
|
+
Number of DMD modes to compute.
|
|
26
|
+
F_S : float
|
|
27
|
+
Sampling frequency in Hz.
|
|
28
|
+
SAVE_T_DMD : bool, optional
|
|
29
|
+
If True, save time‐series DMD results to disk. Default is False.
|
|
30
|
+
FOLDER_OUT : str, optional
|
|
31
|
+
Directory in which to save outputs when SAVE_T_DMD is True. Default is './'.
|
|
32
|
+
svd_solver : str, optional
|
|
33
|
+
SVD solver to use for the low‐rank approximation. Default is
|
|
34
|
+
'svd_sklearn_truncated'.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
Phi_D : ndarray, shape (n_features, n_Modes)
|
|
39
|
+
Complex spatial DMD modes.
|
|
40
|
+
Lambda_D : ndarray, shape (n_Modes,)
|
|
41
|
+
Complex eigenvalues of the reduced propagator.
|
|
42
|
+
freqs : ndarray, shape (n_Modes,)
|
|
43
|
+
Frequencies (Hz) associated with each DMD mode.
|
|
44
|
+
a0s : ndarray, shape (n_Modes,)
|
|
45
|
+
Initial amplitudes (coefficients) of the DMD modes.
|
|
46
|
+
"""
|
|
43
47
|
|
|
44
48
|
Phi_P, Psi_P, Sigma_P = switch_svds(D_1, n_Modes, svd_solver)
|
|
45
|
-
|
|
49
|
+
if verbose:
|
|
50
|
+
print('SVD of D1 rdy')
|
|
46
51
|
Sigma_inv = np.diag(1 / Sigma_P)
|
|
47
52
|
dt = 1 / F_S
|
|
48
53
|
# %% Step 3: Compute approximated propagator
|
|
49
54
|
P_A = LA.multi_dot([np.transpose(Phi_P), D_2, Psi_P, Sigma_inv])
|
|
50
|
-
|
|
55
|
+
if verbose:
|
|
56
|
+
print('reduced propagator rdy')
|
|
51
57
|
|
|
52
58
|
# %% Step 4: Compute eigenvalues of the system
|
|
53
59
|
Lambda, Q = LA.eig(P_A) # not necessarily symmetric def pos! Avoid eigsh, eigh
|
|
54
60
|
freqs = np.imag(np.log(Lambda)) / (2 * np.pi * dt)
|
|
55
|
-
|
|
61
|
+
if verbose:
|
|
62
|
+
print(' lambdas and freqs rdy')
|
|
56
63
|
|
|
57
64
|
# %% Step 5: Spatial structures of the DMD in the PIP style
|
|
58
65
|
Phi_D = LA.multi_dot([D_2, Psi_P, Sigma_inv, Q])
|
|
59
|
-
|
|
66
|
+
if verbose:
|
|
67
|
+
print('Phi_D rdy')
|
|
60
68
|
|
|
61
69
|
# %% Step 6: Compute the initial coefficients
|
|
62
70
|
# a0s=LA.lstsq(Phi_D, D_1[:,0],rcond=None)
|
|
63
71
|
a0s = LA.pinv(Phi_D).dot(D_1[:, 0])
|
|
64
|
-
|
|
72
|
+
if verbose:
|
|
73
|
+
print('Sigma_D rdy')
|
|
65
74
|
|
|
66
75
|
if SAVE_T_DMD:
|
|
67
76
|
os.makedirs(FOLDER_OUT + "/DMD/", exist_ok=True)
|
modulo_vki/core/_k_matrix.py
CHANGED
|
@@ -2,32 +2,70 @@ import os
|
|
|
2
2
|
from tqdm import tqdm
|
|
3
3
|
import numpy as np
|
|
4
4
|
import math
|
|
5
|
+
from scipy.signal import firwin
|
|
6
|
+
from scipy import signal
|
|
7
|
+
from sklearn.metrics.pairwise import pairwise_kernels
|
|
8
|
+
|
|
9
|
+
def CorrelationMatrix(N_T,
|
|
10
|
+
N_PARTITIONS=1,
|
|
11
|
+
MEMORY_SAVING=False,
|
|
12
|
+
FOLDER_OUT='./',
|
|
13
|
+
SAVE_K=False,
|
|
14
|
+
D=None,
|
|
15
|
+
weights=np.array([]),
|
|
16
|
+
verbose=True):
|
|
17
|
+
"""
|
|
18
|
+
Computes the temporal correlation matrix from the provided data matrix.
|
|
5
19
|
|
|
20
|
+
If MEMORY_SAVING is active, computation is split into partitions to reduce memory load.
|
|
21
|
+
If data matrix D has been computed using MODULO, parameters like dimensions and number of partitions
|
|
22
|
+
will automatically be inferred.
|
|
6
23
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
N_T : int
|
|
27
|
+
Number of temporal snapshots.
|
|
28
|
+
|
|
29
|
+
N_PARTITIONS : int, default=1
|
|
30
|
+
Number of partitions for memory-saving computation.
|
|
31
|
+
Inherited automatically if using MODULO's partitioning.
|
|
32
|
+
|
|
33
|
+
MEMORY_SAVING : bool, default=False
|
|
34
|
+
Activates partitioned computation of the correlation matrix to reduce memory usage.
|
|
35
|
+
Requires pre-partitioned data according to MODULO's `_data_processing`.
|
|
36
|
+
|
|
37
|
+
FOLDER_OUT : str, default='./'
|
|
38
|
+
Output directory where the temporal correlation matrix will be saved if required.
|
|
39
|
+
|
|
40
|
+
SAVE_K : bool, default=False
|
|
41
|
+
Flag to save the computed correlation matrix K to disk. Automatically enforced
|
|
42
|
+
if MEMORY_SAVING is active.
|
|
43
|
+
|
|
44
|
+
D : np.ndarray, optional
|
|
45
|
+
Data matrix used to compute the correlation matrix. Required if MEMORY_SAVING is False.
|
|
46
|
+
|
|
47
|
+
weights : np.ndarray, default=np.array([])
|
|
48
|
+
Weight vector `[w_1, w_2, ..., w_Ns]` defined as `w_i = area_cell_i / area_grid`.
|
|
49
|
+
Needed only for non-uniform grids when MEMORY_SAVING is True.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
K : np.ndarray or None
|
|
54
|
+
Temporal correlation matrix if MEMORY_SAVING is False; otherwise returns None,
|
|
55
|
+
as matrix is managed via disk storage in partitioned computations.
|
|
21
56
|
"""
|
|
22
57
|
|
|
23
58
|
if not MEMORY_SAVING:
|
|
24
|
-
|
|
59
|
+
if verbose:
|
|
60
|
+
print("Computing Temporal correlation matrix K ...")
|
|
25
61
|
K = np.dot(D.T, D)
|
|
26
|
-
|
|
62
|
+
if verbose:
|
|
63
|
+
print("Done.")
|
|
27
64
|
|
|
28
65
|
else:
|
|
29
66
|
SAVE_K = True
|
|
30
|
-
|
|
67
|
+
if verbose:
|
|
68
|
+
print("\n Using Memory Saving feature...")
|
|
31
69
|
K = np.zeros((N_T, N_T))
|
|
32
70
|
dim_col = math.floor(N_T / N_PARTITIONS)
|
|
33
71
|
|
|
@@ -79,3 +117,93 @@ def CorrelationMatrix(N_T, N_PARTITIONS=1, MEMORY_SAVING=False, FOLDER_OUT='./',
|
|
|
79
117
|
np.savez(FOLDER_OUT + "/correlation_matrix/k_matrix", K=K)
|
|
80
118
|
|
|
81
119
|
return K if not MEMORY_SAVING else None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def spectral_filter(K: np.ndarray, N_o:int, f_c: float) -> np.ndarray:
|
|
123
|
+
"""
|
|
124
|
+
Zero‐phase band‐pass filter of the correlation matrix K along its diagonals.
|
|
125
|
+
Used for the SPOD proposed by Sieber et al.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
K : (n_t, n_t) array
|
|
130
|
+
Original temporal correlation matrix.
|
|
131
|
+
N_o : int
|
|
132
|
+
Semi‐order of the FIR filter (true filter length = 2*N_o+1).
|
|
133
|
+
f_c : float
|
|
134
|
+
Normalized cutoff frequency (0 < f_c < 0.5).
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
K_F : (n_t, n_t) array
|
|
139
|
+
The filtered correlation matrix.
|
|
140
|
+
"""
|
|
141
|
+
n_t = K.shape[0]
|
|
142
|
+
|
|
143
|
+
# extend K for edge-padding
|
|
144
|
+
K_ext = np.pad(
|
|
145
|
+
K,
|
|
146
|
+
pad_width=((N_o, N_o), (N_o, N_o)),
|
|
147
|
+
mode='constant', # or 'edge', 'reflect', etc.
|
|
148
|
+
constant_values=0
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Fill the edges ( a bit of repetition but ok.. )
|
|
152
|
+
# Row-wise, Upper part
|
|
153
|
+
for i in range(0, N_o):
|
|
154
|
+
K_ext[i, i:i + n_t] = K[0, :]
|
|
155
|
+
|
|
156
|
+
# Row-wise, bottom part
|
|
157
|
+
for i in range(N_o + n_t, n_t + 2 * N_o):
|
|
158
|
+
K_ext[i, i - n_t + 1:i + 1] = K[-1, :]
|
|
159
|
+
|
|
160
|
+
# Column-wise, left part
|
|
161
|
+
for j in range(0, N_o):
|
|
162
|
+
K_ext[j:j + n_t, j] = K[:, 0]
|
|
163
|
+
|
|
164
|
+
# Column-wise, right part
|
|
165
|
+
for j in range(N_o + n_t, 2 * N_o + n_t):
|
|
166
|
+
K_ext[j - n_t + 1:j + 1, j] = K[:, -1]
|
|
167
|
+
|
|
168
|
+
# K_e = np.zeros((n_t + 2 * N_o, n_t + 2 * N_o))
|
|
169
|
+
# From which we clearly know that:
|
|
170
|
+
# K_e[N_o:n_t + N_o, N_o:n_t + N_o] = K
|
|
171
|
+
|
|
172
|
+
# create 2D kernel for FIR
|
|
173
|
+
h1d = firwin(N_o, f_c)
|
|
174
|
+
L = K_ext.shape[0]
|
|
175
|
+
|
|
176
|
+
pad_l = (L - N_o) // 2
|
|
177
|
+
pad_r = L - N_o - pad_l
|
|
178
|
+
|
|
179
|
+
# symmetrically padded kernel in 1D
|
|
180
|
+
h1d_pad = np.pad(h1d, (pad_l, pad_r))
|
|
181
|
+
|
|
182
|
+
# we make it 2D diagonal
|
|
183
|
+
h2d = np.diag(h1d_pad)
|
|
184
|
+
|
|
185
|
+
# finally filter K_ext and return the trimmed filtered without boundaries
|
|
186
|
+
K_ext_filt = signal.fftconvolve(K_ext, h2d, mode='same')
|
|
187
|
+
K_F = K_ext_filt[N_o : N_o + n_t, N_o : N_o + n_t]
|
|
188
|
+
|
|
189
|
+
return K_F
|
|
190
|
+
|
|
191
|
+
def kernelized_K(D, M_ij, k_m, metric, cent, alpha):
|
|
192
|
+
|
|
193
|
+
n_s, n_t = D.shape
|
|
194
|
+
|
|
195
|
+
gamma = - np.log(k_m) / M_ij
|
|
196
|
+
K_zeta = pairwise_kernels(D.T, metric=metric, gamma=gamma) # kernel substitute of the inner product
|
|
197
|
+
|
|
198
|
+
# Center the Kernel Matrix (if cent is True):
|
|
199
|
+
if cent:
|
|
200
|
+
H = np.eye(n_t) - 1 / n_t * np.ones_like(K_zeta)
|
|
201
|
+
K_zeta = H @ K_zeta @ H.T
|
|
202
|
+
|
|
203
|
+
# add `Ridge term` to enforce strictly pos. def. eigs and well-conditioning
|
|
204
|
+
K_r = K_zeta + alpha * np.eye(n_t)
|
|
205
|
+
|
|
206
|
+
return K_r
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
modulo_vki/core/_mpod_time.py
CHANGED
|
@@ -4,8 +4,11 @@ from scipy.signal import firwin # To create FIR kernels
|
|
|
4
4
|
from tqdm import tqdm
|
|
5
5
|
from modulo_vki.utils._utils import conv_m, switch_eigs
|
|
6
6
|
|
|
7
|
+
# TODO: instead of computing the min of modes between SAT and the number of frequencies, we should compute the number of modes between SAT and the number of requested modes by the user.
|
|
7
8
|
|
|
8
|
-
def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced',
|
|
9
|
+
def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced',
|
|
10
|
+
dt=1,FOLDER_OUT: str = "./", MEMORY_SAVING: bool = False,SAT: int = 100,
|
|
11
|
+
n_Modes=10, eig_solver: str = 'svd_sklearn_randomized'):
|
|
9
12
|
'''
|
|
10
13
|
This function computes the PSIs for the mPOD. In this implementation, a "dft-trick" is proposed, in order to avoid
|
|
11
14
|
expansive SVDs. Randomized SVD is used by default for the diagonalization.
|
|
@@ -15,9 +18,11 @@ def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,F
|
|
|
15
18
|
:param dt: float.
|
|
16
19
|
1/fs, the dt between snapshots. Units in seconds.
|
|
17
20
|
:param Nf:
|
|
18
|
-
np.array. Vector collecting the order of the FIR filters used in each scale.
|
|
21
|
+
np.array. Vector collecting the order of the FIR filters used in each scale. It must be of size len(F_V) + 1, where the first element defines
|
|
22
|
+
the low pass filter, and the last one is the high pass filter. The rest are for the band-pass filters.
|
|
19
23
|
:param Ex: int.
|
|
20
|
-
Extension at the boundaries of K to impose the boundary conditions (see boundaries). It must be at least as Nf
|
|
24
|
+
Extension at the boundaries of K to impose the boundary conditions (see boundaries). It must be at least as Nf, and of size len(F_V) + 1, where the first
|
|
25
|
+
element is the low pass filter, and the last one is the high pass filter. The rest are band-pass filters.
|
|
21
26
|
:param F_V: np.array.
|
|
22
27
|
Frequency splitting vector, containing the frequencies of each scale (see article). If the time axis is in seconds, these frequencies are in Hz.
|
|
23
28
|
:param Keep: np.array.
|
|
@@ -51,6 +56,16 @@ def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,F
|
|
|
51
56
|
#Converting F_V in radiants and initialise number of scales M
|
|
52
57
|
Fs = 1 / dt
|
|
53
58
|
F_Bank_r = F_V * 2 / Fs
|
|
59
|
+
|
|
60
|
+
# repeat first entry to ensure all scales are covered
|
|
61
|
+
F_Bank_r = np.concatenate(([F_Bank_r[0]], F_Bank_r))
|
|
62
|
+
# TODO: update tutorials to reflect this change!
|
|
63
|
+
|
|
64
|
+
assert len(F_Bank_r) == len(Nf), "Frequencies and filter orders must have the same length. Remember that given M scales, Nf must have M+1 elements"
|
|
65
|
+
assert len(F_Bank_r) == len(Keep), "Frequencies and keep must have the same length. Remember that given M scales, keep must have M+1 elements"
|
|
66
|
+
# Nf = np.concatenate(([Nf[0]], Nf))
|
|
67
|
+
# Keep = np.concatenate(([Keep[0]], Keep))
|
|
68
|
+
|
|
54
69
|
M = len(F_Bank_r)
|
|
55
70
|
|
|
56
71
|
# Loop over the scales to show the transfer functions
|
|
@@ -72,26 +87,29 @@ def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,F
|
|
|
72
87
|
# Generate the 1d filter for this
|
|
73
88
|
if m < 1:
|
|
74
89
|
if Keep[m] == 1:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
# Low Pass Filter
|
|
91
|
+
h_A = firwin(Nf[m], F_Bank_r[m], window='hamming')
|
|
92
|
+
# Filter K_LP
|
|
93
|
+
print('\n Filtering Largest Scale')
|
|
94
|
+
K_L = conv_m(K=K, h=h_A, Ex=Ex, boundaries=boundaries)
|
|
95
|
+
# R_K = np.linalg.matrix_rank(K_L, tol=None, hermitian=True)
|
|
96
|
+
'''We replace it with an estimation based on the non-zero freqs the cut off frequency of the scale is '''
|
|
97
|
+
F_CUT = F_Bank_r[m] * Fs / 2
|
|
98
|
+
Indices = np.argwhere(np.abs(Freqs) < F_CUT)
|
|
99
|
+
|
|
100
|
+
# R_K = np.min([len(Indices), SAT])
|
|
101
|
+
# R_K = np.min(R_K, n_Modes) # we take the min between the number of frequencies and the number of modes requested
|
|
102
|
+
R_K = min(len(Indices), SAT, n_Modes)
|
|
103
|
+
|
|
104
|
+
print(str(len(Indices)) + ' Modes Estimated')
|
|
105
|
+
print('\n Diagonalizing Largest Scale')
|
|
106
|
+
|
|
107
|
+
Psi_P, Lambda_P = switch_eigs(K_L, R_K, eig_solver) #svds_RND(K_L, R_K)
|
|
108
|
+
Psi_M=Psi_P
|
|
109
|
+
Lambda_M=Lambda_P
|
|
89
110
|
else:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# Ks[:, :, m] = K_L # First large scale
|
|
93
|
-
|
|
94
|
-
# method = signal.choose_conv_method(K, h2d, mode='same')
|
|
111
|
+
print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
|
|
112
|
+
|
|
95
113
|
elif m > 0 and m < M - 1:
|
|
96
114
|
if Keep[m] == 1:
|
|
97
115
|
# print(m)
|
|
@@ -109,11 +127,14 @@ def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,F
|
|
|
109
127
|
print('Diagonalizing H Scale ' + str(m + 1) + '/' + str(M))
|
|
110
128
|
# R_K = np.linalg.matrix_rank(K_H, tol=None, hermitian=True)
|
|
111
129
|
Psi_P, Lambda_P = switch_eigs(K_H, R_K, eig_solver) #svds_RND(K_H, R_K) # Diagonalize scale
|
|
130
|
+
|
|
112
131
|
if np.shape(Psi_M)[0]==0: # if this is the first contribute to the basis
|
|
113
|
-
|
|
132
|
+
Psi_M=Psi_P
|
|
133
|
+
Lambda_M=Lambda_P
|
|
114
134
|
else:
|
|
115
|
-
|
|
116
|
-
|
|
135
|
+
Psi_M = np.concatenate((Psi_M, Psi_P), axis=1) # append to the previous
|
|
136
|
+
Lambda_M = np.concatenate((Lambda_M, Lambda_P), axis=0)
|
|
137
|
+
|
|
117
138
|
else:
|
|
118
139
|
print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
|
|
119
140
|
|
modulo_vki/core/_pod_space.py
CHANGED
|
@@ -4,7 +4,8 @@ from tqdm import tqdm
|
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def Spatial_basis_POD(D, PSI_P, Sigma_P, MEMORY_SAVING,
|
|
7
|
+
def Spatial_basis_POD(D, PSI_P, Sigma_P, MEMORY_SAVING,
|
|
8
|
+
N_T, FOLDER_OUT='./', N_PARTITIONS=1, SAVE_SPATIAL_POD=False,
|
|
8
9
|
rescale=False):
|
|
9
10
|
"""
|
|
10
11
|
This function computs the POD spatial basis from the temporal basis,
|
|
@@ -55,7 +56,7 @@ def Spatial_basis_POD(D, PSI_P, Sigma_P, MEMORY_SAVING, N_T, FOLDER_OUT='./', N_
|
|
|
55
56
|
|
|
56
57
|
else:
|
|
57
58
|
# We take only the first R modes.
|
|
58
|
-
Sigma_P_t = Sigma_P[0:R]
|
|
59
|
+
Sigma_P_t = Sigma_P[0:R]
|
|
59
60
|
Sigma_P_Inv_V = 1 / Sigma_P_t
|
|
60
61
|
# So we have the inverse
|
|
61
62
|
Sigma_P_Inv = np.diag(Sigma_P_Inv_V)
|
modulo_vki/core/_pod_time.py
CHANGED
|
@@ -3,7 +3,8 @@ import numpy as np
|
|
|
3
3
|
from ..utils._utils import switch_eigs
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def Temporal_basis_POD(K, SAVE_T_POD=False, FOLDER_OUT='./',
|
|
6
|
+
def Temporal_basis_POD(K, SAVE_T_POD=False, FOLDER_OUT='./',
|
|
7
|
+
n_Modes=10,eig_solver: str = 'eigh'):
|
|
7
8
|
"""
|
|
8
9
|
This method computes the POD basis. For some theoretical insights, you can find the theoretical background of the proper orthogonal decomposition in a nutshell here: https://youtu.be/8fhupzhAR_M
|
|
9
10
|
|