| 1 import numpy as np |
1 import numpy as np |
| 2 from dolfinx import fem, geometry |
2 from dolfinx import fem, geometry |
| |
3 from dolfinx_access import cell_FunctionSpace_f64 |
| 3 from mpi4py import MPI |
4 from mpi4py import MPI |
| 4 from petsc4py import PETSc |
5 from petsc4py import PETSc |
| 5 from pointsource_pde.dolfinx_extras import get_mass_matrix |
6 from pointsource_pde.dolfinx_extras import get_mass_matrix |
| 6 from scipy.sparse.linalg import svds |
7 from scipy.sparse.linalg import svds |
| 7 from slepc4py import SLEPc |
8 from slepc4py import SLEPc |
| 8 |
|
| 9 from dolfinx_access import cell_FunctionSpace_f64 |
|
| 10 |
9 |
| 11 |
10 |
| 12 class LaserSampling: |
11 class LaserSampling: |
| 13 """ |
12 """ |
| 14 A linear operator class for laser sampling of a concentration (fem.Function) |
13 A linear operator class for laser sampling of a concentration (fem.Function) |
| 50 E.setWhichEigenpairs(SLEPc.EPS.Which.SMALLEST_MAGNITUDE) |
49 E.setWhichEigenpairs(SLEPc.EPS.Which.SMALLEST_MAGNITUDE) |
| 51 # E.setWhichEigenpairs(SLEPc.EPS.Which.LARGEST_MAGNITUDE) |
50 # E.setWhichEigenpairs(SLEPc.EPS.Which.LARGEST_MAGNITUDE) |
| 52 E.setFromOptions() |
51 E.setFromOptions() |
| 53 E.solve() |
52 E.solve() |
| 54 |
53 |
| 55 sigma = abs(E.getEigenvalue(0)) |
54 # sigma = abs(E.getEigenvalue(0)) |
| 56 # self._opnorm = 1.0 / sigma |
55 # self._opnorm = 1.0 / sigma |
| 57 # print("Full sampling opnorm = %f" % self._opnorm) |
56 # print("Full sampling opnorm = %f" % self._opnorm) |
| 58 |
57 |
| 59 # SVD operator norm |
58 # SVD operator norm |
| 60 _u, s, _v = svds(self.R, k=1) |
59 _u, s, _v = svds(self.R, k=1) |
| 61 self._opnorm = s[0] # / sigma |
60 self._opnorm = s[0] # / sigma |
| 62 print(f"Laser sampling opnorm = {self._opnorm:.6f}") |
61 # print(f"Laser sampling opnorm = {self._opnorm:.6f}") |
| 63 |
62 |
| 64 def matrix(self, beams): |
63 def matrix(self, beams): |
| 65 """Combined: generate beams + segment + cell collision + dof weighting""" |
64 """Combined: generate beams + segment + cell collision + dof weighting""" |
| 66 dofmap = self.V.dofmap |
65 dofmap = self.V.dofmap |
| 67 tdim = self.domain.topology.dim |
66 tdim = self.domain.topology.dim |
| 159 return self._opnorm |
158 return self._opnorm |
| 160 |
159 |
| 161 def lipschitz_factor(self, *args): |
160 def lipschitz_factor(self, *args): |
| 162 return self.opnorm(*args) |
161 return self.opnorm(*args) |
| 163 |
162 |
| 164 def diff_adj_lipschitz_factor(self, *args): |
163 def diff_chain_lipschitz_factor(self, *args): |
| 165 return self.opnorm(*args) |
164 return self.opnorm(*args) |
| 166 |
165 |
| 167 def diff_bound(self, *args): |
166 def diff_bound(self, *args): |
| 168 return self.opnorm(*args) |
167 return self.opnorm(*args) |
| 169 |
168 |
| 170 def codomain_bound(self, xbound=None): |
|
| 171 if xbound is None: |
|
| 172 raise Exception("Linear operators have unbounded range") |
|
| 173 else: |
|
| 174 return self.opnorm() * xbound |
|
| 175 |
|
| 176 # Construct observation noise |
169 # Construct observation noise |
| 177 def noise(self, noise_level): |
170 def noise(self, noise_level, rng=None): |
| |
171 if rng is None: |
| |
172 rng = np.random.default_rng() |
| 178 M = self.M |
173 M = self.M |
| 179 # Generate noise with shape (M, 1) using scalar standard deviation |
174 # Generate noise with shape (M, 1) using scalar standard deviation |
| 180 noise = np.random.normal(0, noise_level, size=(M,)) |
175 noise = rng.normal(0, noise_level, size=(M,)) |
| 181 return noise |
176 return noise |