modulo-vki 2.0.5__py3-none-any.whl → 2.0.6__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.
@@ -1,154 +1,154 @@
1
- import os
2
- import numpy as np
3
- from scipy.signal import firwin # To create FIR kernels
4
- from tqdm import tqdm
5
- from modulo_vki.utils._utils import conv_m, switch_eigs
6
-
7
-
8
- def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,FOLDER_OUT: str = "./", MEMORY_SAVING: bool = False,SAT: int = 100,n_Modes=10, eig_solver: str = 'svd_sklearn_randomized'):
9
- '''
10
- This function computes the PSIs for the mPOD. In this implementation, a "dft-trick" is proposed, in order to avoid
11
- expansive SVDs. Randomized SVD is used by default for the diagonalization.
12
-
13
- :param K:
14
- np.array Temporal correlation matrix
15
- :param dt: float.
16
- 1/fs, the dt between snapshots. Units in seconds.
17
- :param Nf:
18
- np.array. Vector collecting the order of the FIR filters used in each scale.
19
- :param Ex: int.
20
- Extension at the boundaries of K to impose the boundary conditions (see boundaries). It must be at least as Nf.
21
- :param F_V: np.array.
22
- Frequency splitting vector, containing the frequencies of each scale (see article). If the time axis is in seconds, these frequencies are in Hz.
23
- :param Keep: np.array.
24
- Vector defining which scale to keep.
25
- :param boundaries: str -> {'nearest', 'reflect', 'wrap' or 'extrap'}.
26
- In order to avoid 'edge effects' if the time correlation matrix is not periodic, several boundary conditions can be used. Options are (from scipy.ndimage.convolve):
27
- ‘reflect’ (d c b a | a b c d | d c b a) The input is extended by reflecting about the edge of the last pixel.
28
- ‘nearest’ (a a a a | a b c d | d d d d) The input is extended by replicating the last pixel.
29
- ‘wrap’ (a b c d | a b c d | a b c d) The input is extended by wrapping around to the opposite edge.
30
- :param MODE: tr -> {‘reduced’, ‘complete’, ‘r’, ‘raw’}
31
- As a final step of this algorithm, the orthogonality is imposed via a QR-factorization. This parameterd define how to perform such factorization, according to numpy.
32
- Options: this is a wrapper to np.linalg.qr(_, mode=MODE). Check numpy's documentation.
33
- if ‘reduced’ The final basis will not necessarely be full. If ‘complete’ The final basis will always be full
34
- :param FOLDER_OUT: str.
35
- This is the directory where intermediate results will be stored if the memory saving is active.It will be ignored if MEMORY_SAVING=False.
36
- :param MEMORY_SAVING: Bool.
37
- If memory saving is active, the results will be saved locally. Nevertheless, since Psi_M is usually not expensive, it will be returned.
38
- :param SAT: int.
39
- Maximum number of modes per scale. The user can decide how many modes to compute; otherwise, modulo set the default SAT=100.
40
- :param n_Modes: int.
41
- Total number of modes that will be finally exported
42
- :param eig_solver: str.
43
- This is the eigenvalue solver that will be used. Refer to eigs_swith for the options.
44
- :return PSI_M: np.array.
45
- The mPOD PSIs. Yet to be sorted !
46
- '''
47
-
48
- if Ex < np.max(Nf):
49
- raise RuntimeError("For the mPOD temporal basis computation Ex must be larger than or equal to Nf")
50
-
51
- #Converting F_V in radiants and initialise number of scales M
52
- Fs = 1 / dt
53
- F_Bank_r = F_V * 2 / Fs
54
- M = len(F_Bank_r)
55
-
56
- # Loop over the scales to show the transfer functions
57
- Psi_M = np.array([])
58
- Lambda_M = np.array([])
59
- n_t = K.shape[1]
60
-
61
- # if K_S:
62
- # Ks = np.zeros((n_t, n_t, M + 1))
63
-
64
- #DFT-trick below: computing frequency bins.
65
- Freqs = np.fft.fftfreq(n_t) * Fs
66
-
67
- print("Filtering and Diagonalizing H scale: \n")
68
-
69
- #Filtering and computing eigenvectors
70
-
71
- for m in tqdm(range(0, M)):
72
- # Generate the 1d filter for this
73
- if m < 1:
74
- if Keep[m] == 1:
75
- # Low Pass Filter
76
- h_A = firwin(Nf[m], F_Bank_r[m], window='hamming')
77
- # Filter K_LP
78
- print('\n Filtering Largest Scale')
79
- K_L = conv_m(K=K, h=h_A, Ex=Ex, boundaries=boundaries)
80
- # R_K = np.linalg.matrix_rank(K_L, tol=None, hermitian=True)
81
- '''We replace it with an estimation based on the non-zero freqs the cut off frequency of the scale is '''
82
- F_CUT = F_Bank_r[m] * Fs / 2
83
- Indices = np.argwhere(np.abs(Freqs) < F_CUT)
84
- R_K = np.min([len(Indices), SAT])
85
- print(str(len(Indices)) + ' Modes Estimated')
86
- print('\n Diagonalizing Largest Scale')
87
- Psi_P, Lambda_P = switch_eigs(K_L, R_K, eig_solver) #svds_RND(K_L, R_K)
88
- Psi_M=Psi_P; Lambda_M=Lambda_P
89
- else:
90
- print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
91
- # if K_S:
92
- # Ks[:, :, m] = K_L # First large scale
93
-
94
- # method = signal.choose_conv_method(K, h2d, mode='same')
95
- elif m > 0 and m < M - 1:
96
- if Keep[m] == 1:
97
- # print(m)
98
- print('\n Working on Scale '+str(m)+'/'+str(M))
99
- # This is the 1d Kernel for Band pass
100
- h1d_H = firwin(Nf[m], [F_Bank_r[m], F_Bank_r[m + 1]], pass_zero=False) # Band-pass
101
- F_CUT1 = F_Bank_r[m] * Fs / 2
102
- F_CUT2 = F_Bank_r[m + 1] * Fs / 2
103
- Indices = np.argwhere((np.abs(Freqs) > F_CUT1) & (np.abs(Freqs) < F_CUT2))
104
- R_K = np.min([len(Indices), SAT]) # number of frequencies here
105
- print(str(len(Indices)) + ' Modes Estimated')
106
- # print('Filtering H Scale ' + str(m + 1) + '/' + str(M))
107
- K_H = conv_m(K, h1d_H, Ex, boundaries)
108
- # Ks[:, :, m + 1] = K_H # Intermediate band-pass
109
- print('Diagonalizing H Scale ' + str(m + 1) + '/' + str(M))
110
- # R_K = np.linalg.matrix_rank(K_H, tol=None, hermitian=True)
111
- Psi_P, Lambda_P = switch_eigs(K_H, R_K, eig_solver) #svds_RND(K_H, R_K) # Diagonalize scale
112
- if np.shape(Psi_M)[0]==0: # if this is the first contribute to the basis
113
- Psi_M=Psi_P; Lambda_M=Lambda_P
114
- else:
115
- Psi_M = np.concatenate((Psi_M, Psi_P), axis=1) # append to the previous
116
- Lambda_M = np.concatenate((Lambda_M, Lambda_P), axis=0)
117
- else:
118
- print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
119
-
120
- else: # this is the case m=M: this is a high pass
121
- if Keep[m] == 1:
122
- print('Working on Scale '+str(m)+'/'+str(M))
123
- # This is the 1d Kernel for High Pass (last scale)
124
- h1d_H = firwin(Nf[m], F_Bank_r[m], pass_zero=False)
125
- F_CUT1 = F_Bank_r[m] * Fs / 2
126
- Indices = np.argwhere((np.abs(Freqs) > F_CUT1))
127
- R_K = len(Indices)
128
- R_K = np.min([len(Indices), SAT]) # number of frequencies here
129
- print(str(len(Indices)) + ' Modes Estimated')
130
- print('Filtering H Scale ' + str(m + 1) + '/ ' + str(M))
131
- K_H = conv_m(K, h1d_H, Ex, boundaries)
132
- # Ks[:, :, m + 1] = K_H # Last (high pass) scale
133
- print('Diagonalizing H Scale ' + str(m + 1) + '/ ' + str(M))
134
- # R_K = np.linalg.matrix_rank(K_H, tol=None, hermitian=True)
135
- Psi_P, Lambda_P = switch_eigs(K_H, R_K, eig_solver) #svds_RND(K_H, R_K) # Diagonalize scale
136
- Psi_M = np.concatenate((Psi_M, Psi_P), axis=1) # append to the previous
137
- Lambda_M = np.concatenate((Lambda_M, Lambda_P), axis=0)
138
- else:
139
- print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
140
-
141
- # Now Order the Scales
142
- Indices = np.flip(np.argsort(Lambda_M)) # find indices for sorting in decreasing order
143
- Psi_M = Psi_M[:, Indices] # Sort the temporal structures
144
- #print(f"Size psis in mpodtime = {np.shape(Psi_M)}")
145
- # Now we complete the basis via re-orghotonalization
146
- print('\n QR Polishing...')
147
- PSI_M, R = np.linalg.qr(Psi_M, mode=MODE)
148
- print('Done!')
149
-
150
- if MEMORY_SAVING:
151
- os.makedirs(FOLDER_OUT + '/mPOD', exist_ok=True)
152
- np.savez(FOLDER_OUT + '/mPOD/Psis', Psis=PSI_M)
153
-
154
- return PSI_M[:,0:n_Modes]
1
+ import os
2
+ import numpy as np
3
+ from scipy.signal import firwin # To create FIR kernels
4
+ from tqdm import tqdm
5
+ from modulo_vki.utils._utils import conv_m, switch_eigs
6
+
7
+
8
+ def temporal_basis_mPOD(K, Nf, Ex, F_V, Keep, boundaries, MODE='reduced', dt=1,FOLDER_OUT: str = "./", MEMORY_SAVING: bool = False,SAT: int = 100,n_Modes=10, eig_solver: str = 'svd_sklearn_randomized'):
9
+ '''
10
+ This function computes the PSIs for the mPOD. In this implementation, a "dft-trick" is proposed, in order to avoid
11
+ expansive SVDs. Randomized SVD is used by default for the diagonalization.
12
+
13
+ :param K:
14
+ np.array Temporal correlation matrix
15
+ :param dt: float.
16
+ 1/fs, the dt between snapshots. Units in seconds.
17
+ :param Nf:
18
+ np.array. Vector collecting the order of the FIR filters used in each scale.
19
+ :param Ex: int.
20
+ Extension at the boundaries of K to impose the boundary conditions (see boundaries). It must be at least as Nf.
21
+ :param F_V: np.array.
22
+ Frequency splitting vector, containing the frequencies of each scale (see article). If the time axis is in seconds, these frequencies are in Hz.
23
+ :param Keep: np.array.
24
+ Vector defining which scale to keep.
25
+ :param boundaries: str -> {'nearest', 'reflect', 'wrap' or 'extrap'}.
26
+ In order to avoid 'edge effects' if the time correlation matrix is not periodic, several boundary conditions can be used. Options are (from scipy.ndimage.convolve):
27
+ ‘reflect’ (d c b a | a b c d | d c b a) The input is extended by reflecting about the edge of the last pixel.
28
+ ‘nearest’ (a a a a | a b c d | d d d d) The input is extended by replicating the last pixel.
29
+ ‘wrap’ (a b c d | a b c d | a b c d) The input is extended by wrapping around to the opposite edge.
30
+ :param MODE: tr -> {‘reduced’, ‘complete’, ‘r’, ‘raw’}
31
+ As a final step of this algorithm, the orthogonality is imposed via a QR-factorization. This parameterd define how to perform such factorization, according to numpy.
32
+ Options: this is a wrapper to np.linalg.qr(_, mode=MODE). Check numpy's documentation.
33
+ if ‘reduced’ The final basis will not necessarely be full. If ‘complete’ The final basis will always be full
34
+ :param FOLDER_OUT: str.
35
+ This is the directory where intermediate results will be stored if the memory saving is active.It will be ignored if MEMORY_SAVING=False.
36
+ :param MEMORY_SAVING: Bool.
37
+ If memory saving is active, the results will be saved locally. Nevertheless, since Psi_M is usually not expensive, it will be returned.
38
+ :param SAT: int.
39
+ Maximum number of modes per scale. The user can decide how many modes to compute; otherwise, modulo set the default SAT=100.
40
+ :param n_Modes: int.
41
+ Total number of modes that will be finally exported
42
+ :param eig_solver: str.
43
+ This is the eigenvalue solver that will be used. Refer to eigs_swith for the options.
44
+ :return PSI_M: np.array.
45
+ The mPOD PSIs. Yet to be sorted !
46
+ '''
47
+
48
+ if Ex < np.max(Nf):
49
+ raise RuntimeError("For the mPOD temporal basis computation Ex must be larger than or equal to Nf")
50
+
51
+ #Converting F_V in radiants and initialise number of scales M
52
+ Fs = 1 / dt
53
+ F_Bank_r = F_V * 2 / Fs
54
+ M = len(F_Bank_r)
55
+
56
+ # Loop over the scales to show the transfer functions
57
+ Psi_M = np.array([])
58
+ Lambda_M = np.array([])
59
+ n_t = K.shape[1]
60
+
61
+ # if K_S:
62
+ # Ks = np.zeros((n_t, n_t, M + 1))
63
+
64
+ #DFT-trick below: computing frequency bins.
65
+ Freqs = np.fft.fftfreq(n_t) * Fs
66
+
67
+ print("Filtering and Diagonalizing H scale: \n")
68
+
69
+ #Filtering and computing eigenvectors
70
+
71
+ for m in tqdm(range(0, M)):
72
+ # Generate the 1d filter for this
73
+ if m < 1:
74
+ if Keep[m] == 1:
75
+ # Low Pass Filter
76
+ h_A = firwin(Nf[m], F_Bank_r[m], window='hamming')
77
+ # Filter K_LP
78
+ print('\n Filtering Largest Scale')
79
+ K_L = conv_m(K=K, h=h_A, Ex=Ex, boundaries=boundaries)
80
+ # R_K = np.linalg.matrix_rank(K_L, tol=None, hermitian=True)
81
+ '''We replace it with an estimation based on the non-zero freqs the cut off frequency of the scale is '''
82
+ F_CUT = F_Bank_r[m] * Fs / 2
83
+ Indices = np.argwhere(np.abs(Freqs) < F_CUT)
84
+ R_K = np.min([len(Indices), SAT])
85
+ print(str(len(Indices)) + ' Modes Estimated')
86
+ print('\n Diagonalizing Largest Scale')
87
+ Psi_P, Lambda_P = switch_eigs(K_L, R_K, eig_solver) #svds_RND(K_L, R_K)
88
+ Psi_M=Psi_P; Lambda_M=Lambda_P
89
+ else:
90
+ print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
91
+ # if K_S:
92
+ # Ks[:, :, m] = K_L # First large scale
93
+
94
+ # method = signal.choose_conv_method(K, h2d, mode='same')
95
+ elif m > 0 and m < M - 1:
96
+ if Keep[m] == 1:
97
+ # print(m)
98
+ print('\n Working on Scale '+str(m)+'/'+str(M))
99
+ # This is the 1d Kernel for Band pass
100
+ h1d_H = firwin(Nf[m], [F_Bank_r[m], F_Bank_r[m + 1]], pass_zero=False) # Band-pass
101
+ F_CUT1 = F_Bank_r[m] * Fs / 2
102
+ F_CUT2 = F_Bank_r[m + 1] * Fs / 2
103
+ Indices = np.argwhere((np.abs(Freqs) > F_CUT1) & (np.abs(Freqs) < F_CUT2))
104
+ R_K = np.min([len(Indices), SAT]) # number of frequencies here
105
+ print(str(len(Indices)) + ' Modes Estimated')
106
+ # print('Filtering H Scale ' + str(m + 1) + '/' + str(M))
107
+ K_H = conv_m(K, h1d_H, Ex, boundaries)
108
+ # Ks[:, :, m + 1] = K_H # Intermediate band-pass
109
+ print('Diagonalizing H Scale ' + str(m + 1) + '/' + str(M))
110
+ # R_K = np.linalg.matrix_rank(K_H, tol=None, hermitian=True)
111
+ Psi_P, Lambda_P = switch_eigs(K_H, R_K, eig_solver) #svds_RND(K_H, R_K) # Diagonalize scale
112
+ if np.shape(Psi_M)[0]==0: # if this is the first contribute to the basis
113
+ Psi_M=Psi_P; Lambda_M=Lambda_P
114
+ else:
115
+ Psi_M = np.concatenate((Psi_M, Psi_P), axis=1) # append to the previous
116
+ Lambda_M = np.concatenate((Lambda_M, Lambda_P), axis=0)
117
+ else:
118
+ print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
119
+
120
+ else: # this is the case m=M: this is a high pass
121
+ if Keep[m] == 1:
122
+ print('Working on Scale '+str(m)+'/'+str(M))
123
+ # This is the 1d Kernel for High Pass (last scale)
124
+ h1d_H = firwin(Nf[m], F_Bank_r[m], pass_zero=False)
125
+ F_CUT1 = F_Bank_r[m] * Fs / 2
126
+ Indices = np.argwhere((np.abs(Freqs) > F_CUT1))
127
+ R_K = len(Indices)
128
+ R_K = np.min([len(Indices), SAT]) # number of frequencies here
129
+ print(str(len(Indices)) + ' Modes Estimated')
130
+ print('Filtering H Scale ' + str(m + 1) + '/ ' + str(M))
131
+ K_H = conv_m(K, h1d_H, Ex, boundaries)
132
+ # Ks[:, :, m + 1] = K_H # Last (high pass) scale
133
+ print('Diagonalizing H Scale ' + str(m + 1) + '/ ' + str(M))
134
+ # R_K = np.linalg.matrix_rank(K_H, tol=None, hermitian=True)
135
+ Psi_P, Lambda_P = switch_eigs(K_H, R_K, eig_solver) #svds_RND(K_H, R_K) # Diagonalize scale
136
+ Psi_M = np.concatenate((Psi_M, Psi_P), axis=1) # append to the previous
137
+ Lambda_M = np.concatenate((Lambda_M, Lambda_P), axis=0)
138
+ else:
139
+ print('\n Scale '+str(m)+' jumped (keep['+str(m)+']=0)')
140
+
141
+ # Now Order the Scales
142
+ Indices = np.flip(np.argsort(Lambda_M)) # find indices for sorting in decreasing order
143
+ Psi_M = Psi_M[:, Indices] # Sort the temporal structures
144
+ #print(f"Size psis in mpodtime = {np.shape(Psi_M)}")
145
+ # Now we complete the basis via re-orghotonalization
146
+ print('\n QR Polishing...')
147
+ PSI_M, R = np.linalg.qr(Psi_M, mode=MODE)
148
+ print('Done!')
149
+
150
+ if MEMORY_SAVING:
151
+ os.makedirs(FOLDER_OUT + '/mPOD', exist_ok=True)
152
+ np.savez(FOLDER_OUT + '/mPOD/Psis', Psis=PSI_M)
153
+
154
+ return PSI_M[:,0:n_Modes]