Skip to content

Commit 694ff9e

Browse files
Niharika KarnikNiharika Karnik
authored andcommitted
Merge branch 'cleaningGQR' of https://github.com/Jimmy-INL/pysensors into TestingPR
For cleaning
2 parents 8d054d2 + b66409e commit 694ff9e

File tree

12 files changed

+546
-1259
lines changed

12 files changed

+546
-1259
lines changed

examples/basis_comparison-Copy1.ipynb

Lines changed: 0 additions & 885 deletions
This file was deleted.

examples/cost_constrained_qr.ipynb

Lines changed: 0 additions & 370 deletions
This file was deleted.

pysensors/basis/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ._identity import Identity
22
from ._random_projection import RandomProjection
33
from ._svd import SVD
4+
from ._custom import Custom
45

5-
__all__ = ["Identity", "SVD", "RandomProjection"]
6+
__all__ = ["Identity", "SVD", "RandomProjection","Custom"]

pysensors/basis/_custom.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
custom mode basis class.
3+
"""
4+
from ._base import InvertibleBasis
5+
from ._base import MatrixMixin
6+
7+
class Custom(InvertibleBasis, MatrixMixin):
8+
"""
9+
Use a custom transformation to map input features to
10+
custom modes.
11+
12+
Assumes the data has already been centered (to have mean 0).
13+
14+
Parameters
15+
----------
16+
n_basis_modes : int, optional (default 10)
17+
Number of basis modes to retain. Cannot be larger than
18+
the number of features ``n_features``, or the number of examples
19+
``n_examples``.
20+
U: The custom basis matrix
21+
22+
Attributes
23+
----------
24+
basis_matrix_ : numpy ndarray, shape (n_features, n_basis_modes)
25+
The top n_basis_modes left singular vectors of the training data.
26+
27+
"""
28+
29+
def __init__(self, U, n_basis_modes=10, **kwargs):
30+
if isinstance(n_basis_modes, int) and n_basis_modes > 0:
31+
super(Custom, self).__init__()#n_components=n_basis_modes, **kwargs
32+
self._n_basis_modes = n_basis_modes
33+
self.custom_basis_ = U
34+
else:
35+
raise ValueError("n_basis_modes must be a positive integer.")
36+
37+
def fit(self, X):
38+
"""
39+
Parameters
40+
----------
41+
X : array-like, shape (n_samples, n_features)
42+
The training data.
43+
44+
Returns
45+
-------
46+
self : instance
47+
"""
48+
# self.basis_matrix_ = self.custom_basis_[:,: self.n_basis_modes] @ self.custom_basis_[:,: self.n_basis_modes].T @ X[: self.n_basis_modes, :].T.copy()
49+
# self.basis_matrix_ = self.custom_basis_ @ self.custom_basis_.T @ X[: self.n_basis_modes, :].T.copy()
50+
self.basis_matrix_ = self.custom_basis_[:,:self.n_basis_modes]
51+
# self.basis_matrix_ = (X @ self.custom_basis_[:,:self.n_basis_modes] @ self.custom_basis_[:,:self.n_basis_modes].T)[:self.n_basis_modes,:].T
52+
53+
# self.basis_matrix_ = ((X @ self.custom_basis_).T)[:,:self.n_basis_modes]
54+
# self.basis_matrix_ = ((X @ self.custom_basis_ @ self.custom_basis_.T).T)[:,:self.n_basis_modes]
55+
return self
56+
57+
def matrix_inverse(self, n_basis_modes=None):
58+
"""
59+
Get the inverse matrix mapping from measurement space to
60+
coordinates with respect to the basis.
61+
62+
Note that this is not the inverse of the matrix returned by
63+
``self.matrix_representation``. It is the (pseudo) inverse of
64+
the matrix whose columns are the basis modes.
65+
66+
Parameters
67+
----------
68+
n_basis_modes : positive int, optional (default None)
69+
Number of basis modes to be used to compute inverse.
70+
71+
Returns
72+
-------
73+
B : numpy ndarray, shape (n_basis_modes, n_features)
74+
The inverse matrix.
75+
"""
76+
n_basis_modes = self._validate_input(n_basis_modes)
77+
78+
return self.basis_matrix_[:, :n_basis_modes].T
79+
80+
@property
81+
def n_basis_modes(self):
82+
"""Number of basis modes."""
83+
return self._n_basis_modes
84+
85+
@n_basis_modes.setter
86+
def n_basis_modes(self, n_basis_modes):
87+
self._n_basis_modes = n_basis_modes
88+
self.n_components = n_basis_modes

pysensors/classification/_sspoc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def fit(
259259

260260
if self.threshold is None:
261261
# Chosen as in Brunton et al. (2016)
262-
threshold = np.sqrt(np.sum(s**2)) / (
262+
threshold = np.sqrt(np.sum(s ** 2)) / (
263263
2 * self.basis_matrix_inverse_.shape[0] * n_classes
264264
)
265265
else:

pysensors/optimizers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from ._ccqr import CCQR
22
from ._qr import QR
3-
3+
from ._gqr import GQR
44

55
__all__ = [
66
"CCQR",
77
"QR",
8+
"GQR"
89
]

pysensors/optimizers/_gqr.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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 performance. 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 constrained sensors in the constrained area.
22+
if n = 0 this converges to the CCQR results.
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+
@ authors: Niharika Karnik (@nkarnik2999), Mohammad Abdo (@Jimmy-INL), and Krithika Manohar (@kmanohar)
31+
"""
32+
def __init__(self):
33+
"""
34+
Attributes
35+
----------
36+
pivots_ : np.ndarray, shape [n_features]
37+
Ranked list of sensor locations.
38+
idx_constrained : np.ndarray, shape [No. of constrained locations]
39+
Column Indices of the sensors in the constrained locations.
40+
n_sensors : integer,
41+
Total number of sensors
42+
n_const_sensors : integer,
43+
Total number of sensors required by the user in the constrained region.
44+
all_sensors : np.ndarray, shape [n_features]
45+
Optimall placed list of sensors obtained from QR pivoting algorithm.
46+
constraint_option : string,
47+
max_n_const_sensors : The number of sensors in the constrained region should be less than or equal to n_const_sensors.
48+
exact_n_const_sensors : The number of sensors in the constrained region should be exactly equal to n_const_sensors.
49+
"""
50+
self.pivots_ = None
51+
self.idx_constrained = []
52+
self.n_sensors = 10
53+
self.n_const_sensors = 0
54+
self.all_sensors = []
55+
self.constraint_option = ''
56+
self.nx = 64
57+
self.ny = 64
58+
self.r = 1
59+
60+
def fit(self,basis_matrix=None,**optimizer_kws):
61+
"""
62+
Parameters
63+
----------
64+
basis_matrix: np.ndarray, shape [n_features, n_samples]
65+
Matrix whose columns are the basis vectors in which to
66+
represent the measurement data.
67+
optimizer_kws: dictionary, optional
68+
Keyword arguments to be passed to the qr method.
69+
70+
Returns
71+
-------
72+
self: a fitted :class:`pysensors.optimizers.GQR` instance
73+
"""
74+
[setattr(self,name,optimizer_kws.get(name,getattr(self,name))) for name in optimizer_kws.keys()]
75+
self._norm_calc_Instance = normCalcReturnInstance(self, self.constraint_option)
76+
n_features, n_samples = basis_matrix.shape # We transpose basis_matrix below
77+
max_const_sensors = len(self.idx_constrained) # Maximum number of sensors allowed in the constrained region
78+
79+
## Assertions and checks:
80+
# if self.n_sensors > n_features - max_const_sensors + self.nConstrainedSensors:
81+
# raise IOError ("n_sensors cannot be larger than n_features - all possible locations in the constrained area + allowed constrained sensors")
82+
# if self.n_sensors > n_samples + self.nConstrainedSensors: ## Handling zero constraint?
83+
# raise IOError ("Currently n_sensors should be less than min(number of samples, number of modes) + number of constrained sensors,\
84+
# got: n_sensors = {}, n_samples + const_sensors = {} + {} = {}".format(self.n_sensors,n_samples,self.nConstrainedSensors,n_samples+self.nConstrainedSensors))
85+
86+
# Initialize helper variables
87+
R = basis_matrix.conj().T.copy()
88+
p = np.arange(n_features)
89+
k = min(n_samples, n_features)
90+
91+
92+
for j in range(k):
93+
r = R[j:, j:]
94+
95+
# Norm of each column
96+
dlens = np.sqrt(np.sum(np.abs(r) ** 2, axis=0))
97+
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)
98+
i_piv = np.argmax(dlens_updated)
99+
dlen = dlens_updated[i_piv]
100+
101+
if dlen > 0:
102+
u = r[:, i_piv] / dlen
103+
u[0] += np.sign(u[0]) + (u[0] == 0)
104+
u /= np.sqrt(abs(u[0]))
105+
else:
106+
u = r[:, i_piv]
107+
u[0] = np.sqrt(2)
108+
109+
# Track column pivots
110+
i_piv += j # true permutation index is i_piv shifted by the iteration counter j
111+
p[[j, i_piv]] = p[[i_piv, j]]
112+
113+
# Switch columns
114+
R[:, [j, i_piv]] = R[:, [i_piv, j]]
115+
116+
# Apply reflector
117+
R[j:, j:] -= np.outer(u, np.dot(u, R[j:, j:]))
118+
R[j + 1 :, j] = 0
119+
self.pivots_ = p
120+
return self

pysensors/utils/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
from ._base import validate_input
22
from ._optimizers import constrained_binary_solve
33
from ._optimizers import constrained_multiclass_solve
4-
4+
from ._constraints import get_constraind_sensors_indices
5+
from ._constraints import get_constrained_sensors_indices_linear
6+
from ._validation import determinant
7+
from ._validation import relative_reconstruction_error
8+
from ._norm_calc import exact_n
9+
from ._norm_calc import max_n
10+
from ._norm_calc import predetermined
511

612
__all__ = [
713
"constrained_binary_solve",
814
"constrained_multiclass_solve",
915
"validate_input",
16+
"get_constraind_sensors_indices",
17+
"get_constrained_sensors_indices_linear",
18+
"box_constraints",
19+
"functional_constraints",
20+
"determinant",
21+
"relative_reconstruction_error",
22+
"exact_n",
23+
"max_n",
24+
"predetermined"
1025
]

pysensors/utils/_constraints.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
2+
"""
3+
Various utility functions for mapping constrained sensors locations with the column indices for class GQR.
4+
"""
5+
6+
import numpy as np
7+
8+
9+
def get_constraind_sensors_indices(x_min, x_max, y_min, y_max, nx, ny, all_sensors):
10+
"""
11+
Function for mapping constrained sensor locations on the grid with the column indices of the basis_matrix.
12+
13+
Parameters
14+
----------
15+
x_min: int, lower bound for the x-axis constraint
16+
x_max : int, upper bound for the x-axis constraint
17+
y_min : int, lower bound for the y-axis constraint
18+
y_max : int, upper bound for the y-axis constraint
19+
nx : int, image pixel (x dimensions of the grid)
20+
ny : int, image pixel (y dimensions of the grid)
21+
all_sensors : np.ndarray, shape [n_features], ranked list of sensor locations.
22+
23+
Returns
24+
-------
25+
idx_constrained : np.darray, shape [No. of constrained locations], array which contains the constrained
26+
locations of the grid in terms of column indices of basis_matrix.
27+
"""
28+
n_features = len(all_sensors)
29+
image_size = int(np.sqrt(n_features))
30+
a = np.unravel_index(all_sensors, (nx,ny))
31+
constrained_sensorsx = []
32+
constrained_sensorsy = []
33+
for i in range(n_features):
34+
if (a[0][i] >= x_min and a[0][i] <= x_max) and (a[1][i] >= y_min and a[1][i] <= y_max):
35+
constrained_sensorsx.append(a[0][i])
36+
constrained_sensorsy.append(a[1][i])
37+
38+
constrained_sensorsx = np.array(constrained_sensorsx)
39+
constrained_sensorsy = np.array(constrained_sensorsy)
40+
constrained_sensors_array = np.stack((constrained_sensorsy, constrained_sensorsx), axis=1)
41+
constrained_sensors_tuple = np.transpose(constrained_sensors_array)
42+
if len(constrained_sensorsx) == 0: ##Check to handle condition when number of sensors in the constrained region = 0
43+
idx_constrained = []
44+
else:
45+
idx_constrained = np.ravel_multi_index(constrained_sensors_tuple, (nx,ny))
46+
return idx_constrained
47+
48+
def get_constrained_sensors_indices_linear(x_min, x_max, y_min, y_max,df):
49+
"""
50+
Function for obtaining constrained column indices from already existing linear sensor locations on the grid.
51+
52+
Parameters
53+
----------
54+
x_min: int, lower bound for the x-axis constraint
55+
x_max : int, upper bound for the x-axis constraint
56+
y_min : int, lower bound for the y-axis constraint
57+
y_max : int, upper bound for the y-axis constraint
58+
df : pandas.DataFrame, a dataframe containing the features and samples
59+
60+
Returns
61+
-------
62+
idx_constrained : np.darray, shape [No. of constrained locations], array which contains the constrained
63+
locations of the grid in terms of column indices of basis_matrix.
64+
"""
65+
x = df['X (m)'].to_numpy()
66+
n_features = x.shape[0]
67+
y = df['Y (m)'].to_numpy()
68+
idx_constrained = []
69+
for i in range(n_features):
70+
if (x[i] >= x_min and x[i] <= x_max) and (y[i] >= y_min and y[i] <= y_max):
71+
idx_constrained.append(i)
72+
return idx_constrained

0 commit comments

Comments
 (0)