-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding region constraints to pysensors PR #17
Merged
kmanohar
merged 67 commits into
dynamicslab:master
from
niharika2999:niharika-krithika-mohammad-regionConstraints
Jul 27, 2023
Merged
Changes from all commits
Commits
Show all changes
67 commits
Select commit
Hold shift + click to select a range
b7800c5
adding custom basis for quick testing
Jimmy-INL 62b79a2
retrieving previous version
Jimmy-INL ceb456a
adding the custom basis
Jimmy-INL 1ccd23f
adding the tutorial
Jimmy-INL 20c2460
readding custom basis
Jimmy-INL 1cbcc13
resolving conflicts
Jimmy-INL 960d971
trying to have the basis matrix as the identity matrix
Jimmy-INL db3c6cb
Adding region constraint notebook
dbbb980
Tried to fix the const_sensors = 0 error
4fb1085
Fixing n_const_sensssors = 0, and edding checks for limitations
Jimmy-INL c011daf
pushing the notebook to compare changes
Jimmy-INL fb5ea4e
starting the conversion from notebook to script, and fixing the zero …
Jimmy-INL 0e5f9ca
Resolving conflicts
daee413
Adding Script for gqr
bb9a251
Script updated with Linear indices function for Twist prototype
f71f77d
Adding gqr with new function for optimal constraint sensor placement …
9cb0a4a
minor changes to the examples/region_optimal.ipynb and _gqr
Jimmy-INL 2fdf2f0
Merge branch 'master' of https://github.com/Jimmy-INL/pysensors into …
59e05ab
Merge branch 'niharika-krithika-mohammad-regionConstraints' of https:…
0a3477d
Adding different cases in f_region
7d75143
Merge branch 'custom-Basis' into niharika-krithika-mohammad-regionCon…
16b8427
Merge branch 'Jimmy-adding-customBasis' into niharika-krithika-mohamm…
Jimmy-INL 876d6fb
Adding the Trace(R) calculation
ea74f8f
Adding updated gqr with constraints moved to utils and region_optimal…
772b525
Small corrections in _constraints
434903b
Merge branch 'niharika-krithika-mohammad-regionConstraints' of https:…
Jimmy-INL c0ab251
Separating constraint processing function from the function for dlens…
3484e08
Adding options to call different type of constraints and renaming fun…
db7d378
Merge branch 'niharika-krithika-mohammad-regionConstraints' of https:…
Jimmy-INL b8b3717
Few changes to gqr and region_optimal which has the radius constraints
8788790
Fixing dlens issues
885add4
Adding _validation in utils with determinant and relative reconstruct…
69a5511
Adding predtermined_norm_calc to gqr
ab4bdbd
Moving norm_calculation functions to utilities
67d3bb8
Adding all updates
f434583
Merge branch 'niharika-krithika-mohammad-regionConstraints' of https:…
5671ebb
Modifying radii_constraints code and gqr now works as a script for ra…
55a1005
Merge branch 'niharika-krithika-mohammad-regionConstraints' of https:…
Jimmy-INL 284a752
stating to clear _gqr
Jimmy-INL e011bf8
undo old changes to _identity.py
Jimmy-INL 9f1d5e6
more cleaning, first two constraints fixed
Jimmy-INL 5e8664b
Merge pull request #2 from Jimmy-INL/cleaningGQR
niharika2999 720290f
more cleaning
Jimmy-INL 80b68b4
fixing tests
Jimmy-INL 3408c9b
removing notebooks and unnecessary mods
Jimmy-INL 3254b89
Delete basis_comparison-Copy1.ipynb
Jimmy-INL acf2f36
Delete cost_constrained_qr.ipynb
Jimmy-INL 1f6ec68
Delete region_optimal.ipynb
Jimmy-INL 9346b79
Delete region_qrModified.ipynb
Jimmy-INL b412708
Delete region_qrModified.py
Jimmy-INL 616ecf1
removing notebooks and unnecessary mods
Jimmy-INL 9c01b1b
Merge branch 'cleaningGQR' of https://github.com/Jimmy-INL/pysensors …
Jimmy-INL 0313052
removing notebooks and unnecessary mods
Jimmy-INL 023dcb2
removing notebooks and unnecessary mods
Jimmy-INL c5b1903
removing notebooks and unnecessary mods
Jimmy-INL b66409e
removing notebooks and unnecessary mods
Jimmy-INL 8d054d2
Merge https://github.com/Jimmy-INL/pysensors into TestingPR
694ff9e
Merge branch 'cleaningGQR' of https://github.com/Jimmy-INL/pysensors …
7fb6978
Revised PR
43ee27d
Revised PR by Niharika
1c16f67
Removed a validation
Jimmy-INL 0a1787f
removing validation from __init__
Jimmy-INL f558ff5
fixing and adding validation
Jimmy-INL 38db2a2
Merge pull request #3 from Jimmy-INL/cleaningGQR
niharika2999 370e9e4
Adding example notebook for spatial constraints
11d5bc7
Adding max_n and predetermined tests to test_optimizers.py, deleting …
niharika2999 082a963
Adding the cost_constrained_qr.ipynb
niharika2999 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
from ._identity import Identity | ||
from ._random_projection import RandomProjection | ||
from ._svd import SVD | ||
from ._custom import Custom | ||
|
||
|
||
__all__ = ["Identity", "SVD", "RandomProjection"] | ||
__all__ = ["Identity", "SVD", "RandomProjection","Custom"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
""" | ||
custom mode basis class. | ||
""" | ||
from ._base import InvertibleBasis | ||
from ._base import MatrixMixin | ||
|
||
class Custom(InvertibleBasis, MatrixMixin): | ||
""" | ||
Use a custom transformation to map input features to | ||
custom modes. | ||
|
||
Assumes the data has already been centered (to have mean 0). | ||
|
||
Parameters | ||
---------- | ||
n_basis_modes : int, optional (default 10) | ||
Number of basis modes to retain. Cannot be larger than | ||
the number of features ``n_features``, or the number of examples | ||
``n_examples``. | ||
U: The custom basis matrix | ||
|
||
Attributes | ||
---------- | ||
basis_matrix_ : numpy ndarray, shape (n_features, n_basis_modes) | ||
The top n_basis_modes left singular vectors of the training data. | ||
|
||
""" | ||
|
||
def __init__(self, U, n_basis_modes=10, **kwargs): | ||
''' | ||
kwargs : Not defined but added to remain consistent with prior basis functions. | ||
''' | ||
if isinstance(n_basis_modes, int) and n_basis_modes > 0: | ||
super(Custom, self).__init__()#n_components=n_basis_modes, **kwargs | ||
self._n_basis_modes = n_basis_modes | ||
self.custom_basis_ = U | ||
else: | ||
raise ValueError("n_basis_modes must be a positive integer.") | ||
|
||
def fit(self, X): | ||
""" | ||
Parameters | ||
---------- | ||
X : array-like, shape (n_samples, n_features) | ||
The training data. | ||
|
||
Returns | ||
------- | ||
self : instance | ||
""" | ||
# 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() | ||
# self.basis_matrix_ = self.custom_basis_ @ self.custom_basis_.T @ X[: self.n_basis_modes, :].T.copy() | ||
self.basis_matrix_ = self.custom_basis_[:,:self.n_basis_modes] | ||
# self.basis_matrix_ = (X @ self.custom_basis_[:,:self.n_basis_modes] @ self.custom_basis_[:,:self.n_basis_modes].T)[:self.n_basis_modes,:].T | ||
|
||
# self.basis_matrix_ = ((X @ self.custom_basis_).T)[:,:self.n_basis_modes] | ||
# self.basis_matrix_ = ((X @ self.custom_basis_ @ self.custom_basis_.T).T)[:,:self.n_basis_modes] | ||
return self | ||
|
||
def matrix_inverse(self, n_basis_modes=None): | ||
""" | ||
Get the inverse matrix mapping from measurement space to | ||
coordinates with respect to the basis. | ||
|
||
Note that this is not the inverse of the matrix returned by | ||
``self.matrix_representation``. It is the (pseudo) inverse of | ||
the matrix whose columns are the basis modes. | ||
|
||
Parameters | ||
---------- | ||
n_basis_modes : positive int, optional (default None) | ||
Number of basis modes to be used to compute inverse. | ||
|
||
Returns | ||
------- | ||
B : numpy ndarray, shape (n_basis_modes, n_features) | ||
The inverse matrix. | ||
""" | ||
n_basis_modes = self._validate_input(n_basis_modes) | ||
|
||
return self.basis_matrix_[:, :n_basis_modes].T | ||
|
||
@property | ||
def n_basis_modes(self): | ||
"""Number of basis modes.""" | ||
return self._n_basis_modes | ||
|
||
@n_basis_modes.setter | ||
def n_basis_modes(self, n_basis_modes): | ||
self._n_basis_modes = n_basis_modes | ||
self.n_components = n_basis_modes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
from ._ccqr import CCQR | ||
from ._qr import QR | ||
|
||
from ._gqr import GQR | ||
|
||
__all__ = [ | ||
"CCQR", | ||
"QR", | ||
"GQR" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import numpy as np | ||
import pysensors | ||
|
||
from pysensors.optimizers._qr import QR | ||
|
||
import matplotlib.pyplot as plt | ||
from sklearn import datasets | ||
from sklearn import metrics | ||
from mpl_toolkits.axes_grid1 import make_axes_locatable | ||
|
||
import pysensors as ps | ||
from matplotlib.patches import Circle | ||
from pysensors.utils._norm_calc import returnInstance as normCalcReturnInstance | ||
|
||
class GQR(QR): | ||
""" | ||
General QR optimizer for sensor selection. | ||
Ranks sensors in descending order of "importance" based on | ||
reconstruction accuracy. This is an extension that requires a more intrusive | ||
access to the QR optimizer to facilitate a more adaptive optimization. This is a generalized version of cost constraints | ||
in the sense that users can allow `n_const_sensors` in the constrained area. | ||
if n = 0 this converges to the CCQR results. and if no constrained region it should converge to the results from QR optimizer. | ||
|
||
See the following reference for more information | ||
Manohar, Krithika, et al. | ||
"Data-driven sparse sensor placement for reconstruction: | ||
Demonstrating the benefits of exploiting known patterns." | ||
IEEE Control Systems Magazine 38.3 (2018): 63-86. | ||
|
||
Niharika Karnik, Mohammad G. Abdo, Carlos E. Estrada Perez, Jun Soo Yoo, Joshua J. Cogliati, Richard S. Skifton, | ||
Pattrick Calderoni, Steven L. Brunton, and Krithika Manohar. | ||
Optimal Sensor Placement with Adaptive Constraints for Nuclear Digital Twins. 2023. arXiv: 2306 . 13637 [math.OC]. | ||
|
||
@ authors: Niharika Karnik (@nkarnik2999), Mohammad Abdo (@Jimmy-INL), and Krithika Manohar (@kmanohar) | ||
""" | ||
def __init__(self): | ||
""" | ||
Attributes | ||
---------- | ||
pivots_ : np.ndarray, shape [n_features] | ||
Ranked list of sensor locations. | ||
idx_constrained : np.ndarray, shape [No. of constrained locations] | ||
Column Indices of the sensors in the constrained locations. | ||
n_sensors : integer, | ||
Total number of sensors | ||
n_const_sensors : integer, | ||
Total number of sensors required by the user in the constrained region. | ||
all_sensors : np.ndarray, shape [n_features] | ||
Optimally placed list of sensors obtained from QR pivoting algorithm. | ||
constraint_option : string, | ||
max_n_const_sensors : The number of sensors in the constrained region should be less than or equal to n_const_sensors. | ||
exact_n_const_sensors : The number of sensors in the constrained region should be exactly equal to n_const_sensors. | ||
""" | ||
self.pivots_ = None | ||
self.idx_constrained = [] | ||
self.n_sensors = None | ||
self.n_const_sensors = 0 | ||
self.all_sensors = [] | ||
self.constraint_option = '' | ||
self.nx = None | ||
self.ny = None | ||
self.r = 1 | ||
|
||
def fit(self,basis_matrix,**optimizer_kws): | ||
""" | ||
Parameters | ||
---------- | ||
basis_matrix: np.ndarray, shape [n_features, n_samples] | ||
Matrix whose columns are the basis vectors in which to | ||
represent the measurement data. | ||
optimizer_kws: dictionary, optional | ||
Keyword arguments to be passed to the qr method. | ||
|
||
Returns | ||
------- | ||
self: a fitted :class:`pysensors.optimizers.GQR` instance | ||
""" | ||
[setattr(self,name,optimizer_kws.get(name,getattr(self,name))) for name in optimizer_kws.keys()] | ||
self._norm_calc_Instance = normCalcReturnInstance(self, self.constraint_option) | ||
n_features, n_samples = basis_matrix.shape # We transpose basis_matrix below | ||
max_const_sensors = len(self.idx_constrained) # Maximum number of sensors allowed in the constrained region | ||
|
||
# Initialize helper variables | ||
R = basis_matrix.conj().T.copy() | ||
p = np.arange(n_features) | ||
k = min(n_samples, n_features) | ||
|
||
|
||
for j in range(k): | ||
r = R[j:, j:] | ||
|
||
# Norm of each column | ||
dlens = np.sqrt(np.sum(np.abs(r) ** 2, axis=0)) | ||
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) | ||
i_piv = np.argmax(dlens_updated) | ||
dlen = dlens_updated[i_piv] | ||
|
||
if dlen > 0: | ||
u = r[:, i_piv] / dlen | ||
u[0] += np.sign(u[0]) + (u[0] == 0) | ||
u /= np.sqrt(abs(u[0])) | ||
else: | ||
u = r[:, i_piv] | ||
u[0] = np.sqrt(2) | ||
|
||
# Track column pivots | ||
i_piv += j # true permutation index is i_piv shifted by the iteration counter j | ||
p[[j, i_piv]] = p[[i_piv, j]] | ||
|
||
# Switch columns | ||
R[:, [j, i_piv]] = R[:, [i_piv, j]] | ||
|
||
# Apply reflector | ||
R[j:, j:] -= np.outer(u, np.dot(u, R[j:, j:])) | ||
R[j + 1 :, j] = 0 | ||
self.pivots_ = p | ||
return self |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,25 @@ | ||
from ._base import validate_input | ||
from ._optimizers import constrained_binary_solve | ||
from ._optimizers import constrained_multiclass_solve | ||
|
||
from ._constraints import get_constraind_sensors_indices | ||
from ._constraints import get_constrained_sensors_indices_linear | ||
from ._norm_calc import exact_n | ||
from ._norm_calc import max_n | ||
from ._norm_calc import predetermined | ||
from ._validation import determinant | ||
from ._validation import relative_reconstruction_error | ||
|
||
__all__ = [ | ||
"constrained_binary_solve", | ||
"constrained_multiclass_solve", | ||
"validate_input", | ||
"get_constraind_sensors_indices", | ||
"get_constrained_sensors_indices_linear", | ||
"box_constraints", | ||
"functional_constraints", | ||
"exact_n", | ||
"max_n", | ||
"predetermined", | ||
"determinant", | ||
"relative_reconstruction_error" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
|
||
""" | ||
Various utility functions for mapping constrained sensors locations with the column indices for class GQR. | ||
""" | ||
|
||
import numpy as np | ||
|
||
|
||
def get_constraind_sensors_indices(x_min, x_max, y_min, y_max, nx, ny, all_sensors): | ||
""" | ||
Function for mapping constrained sensor locations on the grid with the column indices of the basis_matrix. | ||
|
||
Parameters | ||
---------- | ||
x_min: int, lower bound for the x-axis constraint | ||
x_max : int, upper bound for the x-axis constraint | ||
y_min : int, lower bound for the y-axis constraint | ||
y_max : int, upper bound for the y-axis constraint | ||
nx : int, image pixel (x dimensions of the grid) | ||
ny : int, image pixel (y dimensions of the grid) | ||
all_sensors : np.ndarray, shape [n_features], ranked list of sensor locations. | ||
|
||
Returns | ||
------- | ||
idx_constrained : np.darray, shape [No. of constrained locations], array which contains the constrained | ||
locations of the grid in terms of column indices of basis_matrix. | ||
""" | ||
n_features = len(all_sensors) | ||
image_size = int(np.sqrt(n_features)) | ||
a = np.unravel_index(all_sensors, (nx,ny)) | ||
constrained_sensorsx = [] | ||
constrained_sensorsy = [] | ||
for i in range(n_features): | ||
if (a[0][i] >= x_min and a[0][i] <= x_max) and (a[1][i] >= y_min and a[1][i] <= y_max): | ||
constrained_sensorsx.append(a[0][i]) | ||
constrained_sensorsy.append(a[1][i]) | ||
|
||
constrained_sensorsx = np.array(constrained_sensorsx) | ||
constrained_sensorsy = np.array(constrained_sensorsy) | ||
constrained_sensors_array = np.stack((constrained_sensorsy, constrained_sensorsx), axis=1) | ||
constrained_sensors_tuple = np.transpose(constrained_sensors_array) | ||
if len(constrained_sensorsx) == 0: ##Check to handle condition when number of sensors in the constrained region = 0 | ||
idx_constrained = [] | ||
else: | ||
idx_constrained = np.ravel_multi_index(constrained_sensors_tuple, (nx,ny)) | ||
return idx_constrained | ||
|
||
def get_constrained_sensors_indices_linear(x_min, x_max, y_min, y_max,df): | ||
""" | ||
Function for obtaining constrained column indices from already existing linear sensor locations on the grid. | ||
|
||
Parameters | ||
---------- | ||
x_min: int, lower bound for the x-axis constraint | ||
x_max : int, upper bound for the x-axis constraint | ||
y_min : int, lower bound for the y-axis constraint | ||
y_max : int, upper bound for the y-axis constraint | ||
df : pandas.DataFrame, a dataframe containing the features and samples | ||
|
||
Returns | ||
------- | ||
idx_constrained : np.darray, shape [No. of constrained locations], array which contains the constrained | ||
locations of the grid in terms of column indices of basis_matrix. | ||
""" | ||
x = df['X (m)'].to_numpy() | ||
n_features = x.shape[0] | ||
y = df['Y (m)'].to_numpy() | ||
idx_constrained = [] | ||
for i in range(n_features): | ||
if (x[i] >= x_min and x[i] <= x_max) and (y[i] >= y_min and y[i] <= y_max): | ||
idx_constrained.append(i) | ||
return idx_constrained |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is kwargs needed for compatibility, or can it be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not necessarily needed but we have included it for uniformity. Adding documentation for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.