1+ import numpy as np
2+ import pysensors
3+
4+ from pysensors .optimizers ._qr import QR
5+
6+ import matplotlib .pyplot as plt
7+ from sklearn import datasets
8+ from sklearn import metrics
9+ from mpl_toolkits .axes_grid1 import make_axes_locatable
10+
11+ import pysensors as ps
12+ from matplotlib .patches import Circle
13+ from pysensors .utils ._norm_calc import returnInstance as normCalcReturnInstance
14+
15+ class GQR (QR ):
16+ """
17+ General QR optimizer for sensor selection.
18+ Ranks sensors in descending order of "importance" based on
19+ reconstruction accuracy. This is an extension that requires a more intrusive
20+ access to the QR optimizer to facilitate a more adaptive optimization. This is a generalized version of cost constraints
21+ in the sense that users can allow `n_const_sensors` in the constrained area.
22+ if n = 0 this converges to the CCQR results. and if no constrained region it should converge to the results from QR optimizer.
23+
24+ See the following reference for more information
25+ Manohar, Krithika, et al.
26+ "Data-driven sparse sensor placement for reconstruction:
27+ Demonstrating the benefits of exploiting known patterns."
28+ IEEE Control Systems Magazine 38.3 (2018): 63-86.
29+
30+ Niharika Karnik, Mohammad G. Abdo, Carlos E. Estrada Perez, Jun Soo Yoo, Joshua J. Cogliati, Richard S. Skifton,
31+ Pattrick Calderoni, Steven L. Brunton, and Krithika Manohar.
32+ Optimal Sensor Placement with Adaptive Constraints for Nuclear Digital Twins. 2023. arXiv: 2306 . 13637 [math.OC].
33+
34+ @ authors: Niharika Karnik (@nkarnik2999), Mohammad Abdo (@Jimmy-INL), and Krithika Manohar (@kmanohar)
35+ """
36+ def __init__ (self ):
37+ """
38+ Attributes
39+ ----------
40+ pivots_ : np.ndarray, shape [n_features]
41+ Ranked list of sensor locations.
42+ idx_constrained : np.ndarray, shape [No. of constrained locations]
43+ Column Indices of the sensors in the constrained locations.
44+ n_sensors : integer,
45+ Total number of sensors
46+ n_const_sensors : integer,
47+ Total number of sensors required by the user in the constrained region.
48+ all_sensors : np.ndarray, shape [n_features]
49+ Optimally placed list of sensors obtained from QR pivoting algorithm.
50+ constraint_option : string,
51+ max_n_const_sensors : The number of sensors in the constrained region should be less than or equal to n_const_sensors.
52+ exact_n_const_sensors : The number of sensors in the constrained region should be exactly equal to n_const_sensors.
53+ """
54+ self .pivots_ = None
55+ self .idx_constrained = []
56+ self .n_sensors = None
57+ self .n_const_sensors = 0
58+ self .all_sensors = []
59+ self .constraint_option = ''
60+ self .nx = None
61+ self .ny = None
62+ self .r = 1
63+
64+ def fit (self ,basis_matrix ,** optimizer_kws ):
65+ """
66+ Parameters
67+ ----------
68+ basis_matrix: np.ndarray, shape [n_features, n_samples]
69+ Matrix whose columns are the basis vectors in which to
70+ represent the measurement data.
71+ optimizer_kws: dictionary, optional
72+ Keyword arguments to be passed to the qr method.
73+
74+ Returns
75+ -------
76+ self: a fitted :class:`pysensors.optimizers.GQR` instance
77+ """
78+ [setattr (self ,name ,optimizer_kws .get (name ,getattr (self ,name ))) for name in optimizer_kws .keys ()]
79+ self ._norm_calc_Instance = normCalcReturnInstance (self , self .constraint_option )
80+ n_features , n_samples = basis_matrix .shape # We transpose basis_matrix below
81+ max_const_sensors = len (self .idx_constrained ) # Maximum number of sensors allowed in the constrained region
82+
83+ # Initialize helper variables
84+ R = basis_matrix .conj ().T .copy ()
85+ p = np .arange (n_features )
86+ k = min (n_samples , n_features )
87+
88+
89+ for j in range (k ):
90+ r = R [j :, j :]
91+
92+ # Norm of each column
93+ dlens = np .sqrt (np .sum (np .abs (r ) ** 2 , axis = 0 ))
94+ dlens_updated = self ._norm_calc_Instance (self .idx_constrained , dlens , p , j , self .n_const_sensors , dlens_old = dlens , all_sensors = self .all_sensors , n_sensors = self .n_sensors , nx = self .nx , ny = self .ny , r = self .r )
95+ i_piv = np .argmax (dlens_updated )
96+ dlen = dlens_updated [i_piv ]
97+
98+ if dlen > 0 :
99+ u = r [:, i_piv ] / dlen
100+ u [0 ] += np .sign (u [0 ]) + (u [0 ] == 0 )
101+ u /= np .sqrt (abs (u [0 ]))
102+ else :
103+ u = r [:, i_piv ]
104+ u [0 ] = np .sqrt (2 )
105+
106+ # Track column pivots
107+ i_piv += j # true permutation index is i_piv shifted by the iteration counter j
108+ p [[j , i_piv ]] = p [[i_piv , j ]]
109+
110+ # Switch columns
111+ R [:, [j , i_piv ]] = R [:, [i_piv , j ]]
112+
113+ # Apply reflector
114+ R [j :, j :] -= np .outer (u , np .dot (u , R [j :, j :]))
115+ R [j + 1 :, j ] = 0
116+ self .pivots_ = p
117+ return self
0 commit comments