Skip to content

Commit 3381432

Browse files
committed
updated branch with master and resolved merge conflicts
2 parents 84da43c + 48fd635 commit 3381432

File tree

12 files changed

+921
-121
lines changed

12 files changed

+921
-121
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ References
306306
(2024): 15501 - 15516.
307307
`[DOI] <https://doi.org/10.1109/JSEN.2024.3368875>`__
308308

309-
.. |Build| image:: https://github.com/dynamicslab/pysensors/workflows/main.yml/badge.svg
309+
.. |Build| image:: https://github.com/dynamicslab/pysensors/actions/workflows/main.yml/badge.svg?branch=master
310310
:target: https://github.com/dynamicslab/pysensors/actions?query=workflow%3ACI
311311

312312
.. |RTD| image:: https://readthedocs.org/projects/python-sensors/badge/?version=latest

examples/cost_constrained_qr.ipynb

Lines changed: 9 additions & 9 deletions
Large diffs are not rendered by default.

examples/cross_validation.ipynb

Lines changed: 16 additions & 38 deletions
Large diffs are not rendered by default.

examples/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ PySensors Examples
1111
cost_constrained_qr
1212
cross_validation
1313
sea_surface_temperature
14+
reconstruction_comparison
15+
two_point_greedy
1416
polynomial_curve_fitting
1517
spatial_constrained_qr
1618
Olivetti_constrained_sensing

examples/polynomial_curve_fitting.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
"outputs": [],
125125
"source": [
126126
"# Interpolation using the points selected by the SSPOR\n",
127-
"pysense_interp = model.predict(f[sensors])"
127+
"pysense_interp = model.predict(f[sensors], method='unregularized')"
128128
]
129129
},
130130
{
@@ -210,7 +210,7 @@
210210
"model.set_number_of_sensors(5)\n",
211211
"sensors = model.get_selected_sensors()\n",
212212
"\n",
213-
"pysense_interp = model.predict(f[sensors])\n",
213+
"pysense_interp = model.predict(f[sensors], method='unregularized')\n",
214214
"\n",
215215
"fig, ax = plt.subplots(1, 1, figsize=(10, 4))\n",
216216
"ax.plot(x[sensors], f[sensors], 'bo')\n",
@@ -278,7 +278,7 @@
278278
"name": "python",
279279
"nbconvert_exporter": "python",
280280
"pygments_lexer": "ipython3",
281-
"version": "3.10.11"
281+
"version": "3.10.16"
282282
},
283283
"toc": {
284284
"base_numbering": 1,

examples/pysensors_overview.ipynb

Lines changed: 27 additions & 33 deletions
Large diffs are not rendered by default.

examples/spatial_constrained_qr.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@
864864
},
865865
{
866866
"cell_type": "code",
867-
"execution_count": 16,
867+
"execution_count": null,
868868
"metadata": {},
869869
"outputs": [
870870
{
@@ -901,7 +901,7 @@
901901
" # Plot reconstructed faces\n",
902902
" for j in range(n_faces):\n",
903903
" idx = 10 * j\n",
904-
" img = model_exact.predict(X_test[idx, top_sensors_exact])\n",
904+
" img = model_exact.predict(X_test[idx, top_sensors_exact], method='unregularized')\n",
905905
" vmax = max(img.max(), img.min())\n",
906906
" axs[j + 1, k].imshow(\n",
907907
" img.reshape(image_shape),\n",
@@ -1742,7 +1742,7 @@
17421742
},
17431743
{
17441744
"cell_type": "code",
1745-
"execution_count": 32,
1745+
"execution_count": null,
17461746
"metadata": {},
17471747
"outputs": [
17481748
{
@@ -1776,7 +1776,7 @@
17761776
" # Plot reconstructed faces\n",
17771777
" for j in range(n_faces):\n",
17781778
" idx = 10 * j\n",
1779-
" img = model_pre.predict(X_test[idx, top_sensors_pre])\n",
1779+
" img = model_pre.predict(X_test[idx, top_sensors_pre], method='unregularized')\n",
17801780
" vmax = max(img.max(), img.min())\n",
17811781
" axs[j + 1, k].imshow(\n",
17821782
" img.reshape(image_shape),\n",
@@ -2259,7 +2259,7 @@
22592259
" # Plot reconstructed faces\n",
22602260
" for j in range(n_faces):\n",
22612261
" idx = 10 * j\n",
2262-
" img = model_pre.predict(X_test[idx, top_sensors_distance])\n",
2262+
" img = model_pre.predict(X_test[idx, top_sensors_distance], method='unregularized')\n",
22632263
" vmax = max(img.max(), img.min())\n",
22642264
" axs[j + 1, k].imshow(\n",
22652265
" img.reshape(image_shape),\n",

pysensors/optimizers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ._ccqr import CCQR, qr_reflector
22
from ._gqr import GQR
33
from ._qr import QR
4+
from ._tpgr import TPGR
45

5-
__all__ = ["CCQR", "QR", "GQR"]
6+
__all__ = ["CCQR", "QR", "GQR", "TPGR"]

pysensors/optimizers/_tpgr.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import warnings
2+
3+
import numpy as np
4+
from sklearn.base import BaseEstimator
5+
from sklearn.utils.validation import check_is_fitted
6+
7+
8+
class TPGR(BaseEstimator):
9+
"""
10+
Two-Point Greedy Algorithm for Sensor Selection.
11+
12+
See the following reference for more information
13+
14+
Klishin, Andrei A., et. al.
15+
Data-Induced Interactions of Sparse Sensors. 2023.
16+
arXiv:2307.11838 [cond-mat.stat-mech]
17+
18+
Parameters
19+
----------
20+
n_sensors : int
21+
The number of sensors to select.
22+
23+
prior: str or np.ndarray shape (n_basis_modes,), optional (default='decreasing')
24+
Prior Covariance Vector, typically a scaled identity vector or a vector
25+
containing normalized singular values. If 'decreasing', normalized singular
26+
values are used.
27+
28+
noise: float (default None)
29+
Magnitude of the gaussian uncorrelated sensor measurement noise.
30+
31+
Attributes
32+
----------
33+
sensors_ : list of int
34+
Indices of the selected sensors (rows from the basis matrix).
35+
36+
"""
37+
38+
def __init__(self, n_sensors, prior="decreasing", noise=None):
39+
self.n_sensors = n_sensors
40+
self.noise = noise
41+
self.sensors_ = None
42+
self.prior = prior
43+
44+
def fit(self, basis_matrix, singular_values):
45+
"""
46+
Parameters
47+
----------
48+
basis_matrix: np.ndarray, shape (n_features, n_basis_modes)
49+
Matrix whose columns are the basis vectors in which to
50+
represent the measurement data.
51+
52+
singular_values : np.ndarray, shape (n_basis_modes,)
53+
Normalized singular values to be used if `prior="decreasing"`.
54+
55+
Returns
56+
-------
57+
self: a fitted :class:`pysensors.optimizers.TPGR` instance
58+
"""
59+
if isinstance(self.prior, str) and self.prior == "decreasing":
60+
computed_prior = singular_values
61+
elif isinstance(self.prior, np.ndarray):
62+
if self.prior.ndim != 1:
63+
raise ValueError("prior must be a 1D array.")
64+
if self.prior.shape[0] != basis_matrix.shape[1]:
65+
raise ValueError(
66+
f"prior must be of shape {(basis_matrix.shape[1],)},"
67+
f" but got {self.prior.shape[0]}."
68+
)
69+
computed_prior = self.prior
70+
else:
71+
raise ValueError(
72+
"Invalid prior: must be 'decreasing' or a 1D "
73+
"ndarray of appropriate length."
74+
)
75+
if self.noise is None:
76+
warnings.warn(
77+
"noise is None. noise will be set to the average of the computed prior."
78+
)
79+
self.noise = computed_prior.mean()
80+
G = basis_matrix @ np.diag(computed_prior)
81+
n = G.shape[0]
82+
if self.n_sensors > G.shape[0]:
83+
raise ValueError("n_sensors cannot exceed the number of available sensors.")
84+
mask = np.ones(n, dtype=bool)
85+
one_pt_energies = self._one_pt_energy(G)
86+
i = np.argmin(one_pt_energies)
87+
self.sensors_ = [i]
88+
mask[i] = False
89+
G_selected = G[[i], :]
90+
while G_selected.shape[0] < self.n_sensors:
91+
G_remaining = G[mask]
92+
q = np.argmin(
93+
self._one_pt_energy(G_remaining)
94+
+ 2 * self._two_pt_energy(G_selected, G_remaining)
95+
)
96+
remaining_indices = np.where(mask)[0]
97+
selected_index = remaining_indices[q]
98+
self.sensors_.append(selected_index)
99+
mask[selected_index] = False
100+
G_selected = np.vstack(
101+
(G_selected, G[selected_index : selected_index + 1, :])
102+
)
103+
return self
104+
105+
def _one_pt_energy(self, G):
106+
"""
107+
Compute the one-pt energy of the sensors
108+
109+
Parameters
110+
----------
111+
G : np.ndarray, shape (n_features, n_basis_modes)
112+
Basis matrix weighted by the prior.
113+
114+
Returns
115+
-------
116+
np.ndarray, shape (n_features,)
117+
"""
118+
return -np.log(1 + np.einsum("ij,ij->i", G, G) / self.noise**2)
119+
120+
def _two_pt_energy(self, G_selected, G_remaining):
121+
"""
122+
Compute the two-pt energy interations of the selected
123+
sensors with the remaining sensors
124+
125+
Parameters
126+
----------
127+
G_selected : np.ndarray, shape (k, n_basis_modes)
128+
Matrix of currently selected k sensors.
129+
130+
G_remaining : np.ndarray, shape (n_features - k, n_basis_modes)
131+
Matrix of currently remaining sensors.
132+
133+
Returns
134+
-------
135+
np.ndarray, shape (n_features - k,)
136+
"""
137+
J = 0.5 * np.sum(
138+
((G_remaining @ G_selected.T) ** 2)
139+
/ (
140+
np.outer(
141+
1 + (np.sum(G_remaining**2, axis=1)) / self.noise**2,
142+
1 + (np.sum(G_selected**2, axis=1)) / self.noise**2,
143+
)
144+
* self.noise**4
145+
),
146+
axis=1,
147+
)
148+
return J
149+
150+
def get_sensors(self):
151+
check_is_fitted(self, "sensors_")
152+
return self.sensors_

0 commit comments

Comments
 (0)