66from sklearn .utils .validation import check_is_fitted
77
88from ..basis import Identity
9- from ..optimizers import CCQR , QR
9+ from ..optimizers import CCQR , QR , TPGR
1010from ..utils import validate_input
1111
1212INT_DTYPES = (int , np .int64 , np .int32 , np .int16 , np .int8 )
@@ -150,11 +150,19 @@ def fit(self, x, quiet=False, prefit_basis=False, seed=None, **optimizer_kws):
150150 # Check that n_sensors doesn't exceed dimension of basis vectors and
151151 # that it doesn't exceed the number of samples when using the CCQR optimizer.
152152 self ._validate_n_sensors ()
153-
153+ # Calculate the singular values
154+ X_proj = x @ self .basis_matrix_
155+ # Normalized singular values
156+ self .singular_values = np .linalg .norm (X_proj , axis = 0 ) / np .sqrt (x .shape [0 ])
154157 # Find sparse sensor locations
155- self .ranked_sensors_ = self .optimizer .fit (
156- self .basis_matrix_ , ** optimizer_kws
157- ).get_sensors ()
158+ if isinstance (self .optimizer , TPGR ):
159+ self .ranked_sensors_ = self .optimizer .fit (
160+ self .basis_matrix_ , self .singular_values , ** optimizer_kws
161+ ).get_sensors ()
162+ else :
163+ self .ranked_sensors_ = self .optimizer .fit (
164+ self .basis_matrix_ , ** optimizer_kws
165+ ).get_sensors ()
158166
159167 # Randomly shuffle sensors after self.basis.n_basis_modes
160168 rng = np .random .default_rng (seed )
@@ -165,7 +173,7 @@ def fit(self, x, quiet=False, prefit_basis=False, seed=None, **optimizer_kws):
165173
166174 return self
167175
168- def predict (self , x , method = None , noise = None , prior = None , ** solve_kws ):
176+ def predict (self , x , method = None , noise = None , prior = "decreasing" , ** solve_kws ):
169177 """
170178 Predict values at all positions given measurements at sensor locations.
171179
@@ -200,6 +208,25 @@ def predict(self, x, method=None, noise=None, prior=None, **solve_kws):
200208 )
201209
202210 if method is None :
211+ if isinstance (prior , str ) and prior == "decreasing" :
212+ computed_prior = self .singular_values
213+ elif isinstance (prior , np .ndarray ):
214+ if prior .ndim != 1 :
215+ raise ValueError ("prior must be a 1D array" )
216+ if prior .shape [0 ] != self .basis_matrix_ .shape [1 ]:
217+ raise ValueError (
218+ f"prior must be of shape { (self .basis_matrix_ .shape [1 ],)} ,"
219+ f" but got { prior .shape } "
220+ )
221+ computed_prior = prior
222+ if noise is None :
223+ warnings .warn (
224+ "noise is None. noise will be set to the "
225+ "average of the normalized prior"
226+ )
227+ noise = computed_prior .mean ()
228+ return self ._regularized_reconstruction (x , computed_prior , noise )
229+ elif method == "unregularized" :
203230 # Square matrix
204231 if self .n_sensors == self .basis_matrix_ .shape [1 ]:
205232 return self ._square_predict (
@@ -210,12 +237,12 @@ def predict(self, x, method=None, noise=None, prior=None, **solve_kws):
210237 return self ._rectangular_predict (
211238 x , self .ranked_sensors_ [: self .n_sensors ], ** solve_kws
212239 )
213- if method == "mle" :
214- return self . _max_likelihood_reconstruction ( x , prior , noise )
240+ else :
241+ raise NotImplementedError ( "Method not implemented" )
215242
216- def _max_likelihood_reconstruction (self , x , prior , noise ):
243+ def _regularized_reconstruction (self , x , prior , noise ):
217244 """
218- Reconstruct the state using maximum likelihood reconstruction
245+ Reconstruct the state using regularized reconstruction
219246
220247 See the following reference for more information
221248
@@ -234,15 +261,11 @@ def _max_likelihood_reconstruction(self, x, prior, noise):
234261 """
235262 if noise is None :
236263 noise = 1
237- prior_cov = np .diag (prior ** 2 )
238- sensor_selection_matrix = np .zeros (
239- (len (self .selected_sensors ), self .basis_matrix_ .shape [0 ])
240- )
241- sensor_selection_matrix [
242- np .arange (len (self .selected_sensors )), self .selected_sensors
243- ] = 1
244- low_rank_selection_matrix = sensor_selection_matrix @ self .basis_matrix_
245- composite_matrix = np .linalg .inv (prior_cov ) + (
264+ if not isinstance (prior , np .ndarray ):
265+ raise ValueError ("prior must be a numpy array" )
266+ prior_cov = 1 / (prior ** 2 )
267+ low_rank_selection_matrix = self .basis_matrix_ [self .selected_sensors , :]
268+ composite_matrix = np .diag (prior_cov ) + (
246269 low_rank_selection_matrix .T @ low_rank_selection_matrix
247270 ) / (noise ** 2 )
248271 rhs = low_rank_selection_matrix .T @ x
@@ -583,15 +606,20 @@ def std(self, prior, noise=None):
583606 noise = 1
584607 if noise <= 0 :
585608 raise ValueError ("Noise must be positive" )
586- prior_sq = prior ** 2
587- sensor_selection_matrix = np .zeros (
588- (len (self .selected_sensors ), self .basis_matrix_ .shape [0 ])
589- )
590- sensor_selection_matrix [
591- np .arange (len (self .selected_sensors )), self .selected_sensors
592- ] = 1
593- low_rank_selection_matrix = sensor_selection_matrix @ self .basis_matrix_
594- composite_matrix = np .diag (prior_sq ) + (
609+ if isinstance (prior , str ) and prior == "decreasing" :
610+ computed_prior = self .singular_values
611+ elif isinstance (prior , np .ndarray ):
612+ if prior .ndim != 1 :
613+ raise ValueError ("prior must be a 1D array" )
614+ if prior .shape [0 ] != self .basis_matrix_ .shape [1 ]:
615+ raise ValueError (
616+ f"prior must be of shape { (self .basis_matrix_ .shape [1 ],)} ,"
617+ f" but got { prior .shape } "
618+ )
619+ computed_prior = prior
620+ sq_inv_prior = 1.0 / (computed_prior ** 2 )
621+ low_rank_selection_matrix = self .basis_matrix_ [self .selected_sensors , :]
622+ composite_matrix = np .diag (sq_inv_prior ) + (
595623 low_rank_selection_matrix .T @ low_rank_selection_matrix
596624 ) / (noise ** 2 )
597625 diag_cov_matrix = (
@@ -603,14 +631,48 @@ def std(self, prior, noise=None):
603631 sigma = noise * np .sqrt (np .sum (diag_cov_matrix ** 2 , axis = 1 ))
604632 return sigma
605633
606- def one_pt_energy_landscape (self , prior , noise ):
634+ def one_pt_energy_landscape (self , prior = "decreasing" , noise = None ):
607635 check_is_fitted (self , "optimizer" )
608- G = self .basis_matrix_ @ np .diag (prior )
636+ if isinstance (prior , str ) and prior == "decreasing" :
637+ computed_prior = self .singular_values
638+ elif isinstance (prior , np .ndarray ):
639+ if prior .ndim != 1 :
640+ raise ValueError ("prior must be a 1D array" )
641+ if prior .shape [0 ] != self .basis_matrix_ .shape [1 ]:
642+ raise ValueError (
643+ f"prior must be of shape { (self .basis_matrix_ .shape [1 ],)} ,"
644+ f" but got { prior .shape } "
645+ )
646+ computed_prior = prior
647+ if noise is None :
648+ warnings .warn (
649+ "noise is None. noise will be set to the "
650+ "average of the normalized prior"
651+ )
652+ noise = computed_prior .mean ()
653+ G = self .basis_matrix_ @ np .diag (computed_prior )
609654 return - np .log (1 + np .einsum ("ij,ij->i" , G , G ) / noise ** 2 )
610655
611- def two_pt_energy_landscape (self , prior , noise , selected_sensors ):
656+ def two_pt_energy_landscape (self , selected_sensors , prior = "decreasing" , noise = None ):
612657 check_is_fitted (self , "optimizer" )
613- G = self .basis_matrix_ @ np .diag (prior )
658+ if isinstance (prior , str ) and prior == "decreasing" :
659+ computed_prior = self .singular_values
660+ elif isinstance (prior , np .ndarray ):
661+ if prior .ndim != 1 :
662+ raise ValueError ("prior must be a 1D array" )
663+ if prior .shape [0 ] != self .basis_matrix_ .shape [1 ]:
664+ raise ValueError (
665+ f"prior must be of shape { (self .basis_matrix_ .shape [1 ],)} ,"
666+ f" but got { prior .shape } "
667+ )
668+ computed_prior = prior
669+ if noise is None :
670+ warnings .warn (
671+ "noise is None. noise will be set to the "
672+ "average of the normalized prior"
673+ )
674+ noise = computed_prior .mean ()
675+ G = self .basis_matrix_ @ np .diag (computed_prior )
614676 mask = np .ones (G .shape [0 ], dtype = bool )
615677 mask [selected_sensors ] = False
616678 G_selected = G [selected_sensors , :]
0 commit comments