|
1 | 1 | import pytest |
2 | | -import numpy as np |
3 | | -from scipy.sparse import csr_matrix |
4 | | -import petsctools |
5 | 2 | from firedrake import * |
6 | 3 | from firedrake.adjoint import * |
7 | 4 |
|
8 | 5 |
|
9 | | -def petsc2numpy_vec(petsc_vec): |
10 | | - """Allgather a PETSc.Vec.""" |
11 | | - gvec = petsc_vec |
12 | | - gather, lvec = PETSc.Scatter().toAll(gvec) |
13 | | - gather(gvec, lvec, addv=PETSc.InsertMode.INSERT_VALUES) |
14 | | - return lvec.array_r.copy() |
15 | | - |
16 | | - |
17 | | -def petsc2numpy_mat(petsc_mat): |
18 | | - """Allgather a PETSc.Mat.""" |
19 | | - comm = petsc_mat.getComm() |
20 | | - local_mat = petsc_mat.getRedundantMatrix( |
21 | | - comm.size, PETSc.COMM_SELF) |
22 | | - return csr_matrix( |
23 | | - local_mat.getValuesCSR()[::-1], |
24 | | - shape=local_mat.getSize() |
25 | | - ).todense() |
26 | | - |
27 | | - |
28 | | -@pytest.fixture |
29 | | -def rng(): |
30 | | - return RandomGenerator(PCG64(seed=13)) |
31 | | - |
32 | | - |
33 | | -@pytest.mark.skipcomplex |
34 | | -@pytest.mark.parallel([1, 2]) |
35 | | -@pytest.mark.parametrize("degree", (1, 2), ids=["degree1", "degree2"]) |
36 | | -@pytest.mark.parametrize("dim", (0, 2, (2, 2)), ids=["scalar", "vec2", "tensor22"]) |
37 | | -@pytest.mark.parametrize("family", ("CG", "DG")) |
38 | | -@pytest.mark.parametrize("mesh_type", ("interval", "square")) |
39 | | -@pytest.mark.parametrize("backend_type", (PyOP2NoiseBackend, PetscNoiseBackend), ids=("pyop2", "petsc")) |
40 | | -def test_white_noise(family, degree, mesh_type, dim, backend_type, rng): |
41 | | - """Test that white noise generator converges to a mass matrix covariance. |
42 | | - """ |
43 | | - |
44 | | - nx = 10 |
45 | | - # Mesh dimension |
46 | | - if mesh_type == 'interval': |
47 | | - mesh = UnitIntervalMesh(nx) |
48 | | - elif mesh_type == 'square': |
49 | | - mesh = UnitSquareMesh(nx, nx) |
50 | | - |
51 | | - # Variable rank |
52 | | - if not isinstance(dim, int): |
53 | | - V = TensorFunctionSpace(mesh, family, degree, shape=dim) |
54 | | - elif dim > 0: |
55 | | - V = VectorFunctionSpace(mesh, family, degree, dim=dim) |
56 | | - else: |
57 | | - V = FunctionSpace(mesh, family, degree) |
58 | | - |
59 | | - # Finite element white noise has mass matrix covariance |
60 | | - M = inner(TrialFunction(V), TestFunction(V))*dx |
61 | | - covmat = petsc2numpy_mat( |
62 | | - assemble(M, mat_type='aij').petscmat) |
63 | | - |
64 | | - generator = WhiteNoiseGenerator( |
65 | | - V, backend=backend_type(V, rng=rng)) |
66 | | - |
67 | | - # Test convergence as sample size increases |
68 | | - nsamples = [50, 100, 200, 400, 800] |
69 | | - |
70 | | - samples = np.empty((V.dim(), nsamples[-1])) |
71 | | - for i in range(nsamples[-1]): |
72 | | - with generator.sample().dat.vec_ro as bv: |
73 | | - samples[:, i] = petsc2numpy_vec(bv) |
74 | | - |
75 | | - covariances = [np.cov(samples[:, :ns]) for ns in nsamples] |
76 | | - |
77 | | - # Measured covariance matrix should converge at a rate of sqrt(n). |
78 | | - # The number of samples is fairly small to keep the test cost |
79 | | - # lower so we only test the mean rate to a low tolerance. |
80 | | - |
81 | | - errors = [np.linalg.norm(cov-covmat) for cov in covariances] |
82 | | - rate = -np.diff(np.log(errors))/np.diff(np.log(nsamples)) |
83 | | - |
84 | | - assert np.mean(rate) > 0.4 |
85 | | - |
86 | | - |
87 | | -@pytest.mark.skipcomplex |
88 | | -@pytest.mark.parallel([1, 2]) |
89 | | -@pytest.mark.parametrize("dim", (0, 2, (2, 2)), ids=["scalar", "vec2", "tensor22"]) |
90 | | -@pytest.mark.parametrize("mesh_type", ("interval", "square")) |
91 | | -def test_vom_white_noise(dim, mesh_type, rng): |
92 | | - """Test that white noise generator converges to a mass matrix covariance. |
93 | | - """ |
94 | | - |
95 | | - nx = 10 |
96 | | - nv = 10 |
97 | | - np.random.seed(13) |
98 | | - # Mesh dimension |
99 | | - if mesh_type == 'interval': |
100 | | - mesh = UnitIntervalMesh(nx) |
101 | | - points = np.random.random_sample((nv, 1)) |
102 | | - elif mesh_type == 'square': |
103 | | - mesh = UnitSquareMesh(nx, nx) |
104 | | - points = np.random.random_sample((nv, 2)) |
105 | | - |
106 | | - vom = VertexOnlyMesh(mesh, points) |
107 | | - |
108 | | - # Variable rank |
109 | | - if not isinstance(dim, int): |
110 | | - V = TensorFunctionSpace(vom, "DG", 0, shape=dim) |
111 | | - elif dim > 0: |
112 | | - V = VectorFunctionSpace(vom, "DG", 0, dim=dim) |
113 | | - else: |
114 | | - V = FunctionSpace(vom, "DG", 0) |
115 | | - |
116 | | - # Finite element white noise has mass matrix covariance |
117 | | - M = inner(TrialFunction(V), TestFunction(V))*dx |
118 | | - covmat = petsc2numpy_mat( |
119 | | - assemble(M, mat_type='aij').petscmat) |
120 | | - |
121 | | - backend = VOMNoiseBackend(V, rng) |
122 | | - generator = WhiteNoiseGenerator(V, backend=backend) |
123 | | - |
124 | | - # Test convergence as sample size increases |
125 | | - nsamples = [50, 100, 200, 400, 800] |
126 | | - |
127 | | - samples = np.empty((V.dim(), nsamples[-1])) |
128 | | - for i in range(nsamples[-1]): |
129 | | - with generator.sample().dat.vec_ro as bv: |
130 | | - samples[:, i] = petsc2numpy_vec(bv) |
131 | | - |
132 | | - covariances = [np.cov(samples[:, :ns]) for ns in nsamples] |
133 | | - |
134 | | - # Measured covariance matrix should converge at a rate of sqrt(n). |
135 | | - # The number of samples is fairly small to keep the test cost |
136 | | - # lower so we only test the mean rate to a low tolerance. |
137 | | - |
138 | | - errors = [np.linalg.norm(cov-covmat) for cov in covariances] |
139 | | - rate = -np.diff(np.log(errors))/np.diff(np.log(nsamples)) |
140 | | - |
141 | | - assert np.mean(rate) > 0.4 |
142 | | - |
143 | | - |
144 | | -@pytest.mark.skipcomplex |
145 | | -@pytest.mark.parallel([1, 2]) |
146 | | -@pytest.mark.parametrize("m", (0, 2, 4)) |
147 | | -@pytest.mark.parametrize("dim", (0, 2), ids=["scalar", "vector2"]) |
148 | | -@pytest.mark.parametrize("family", ("CG", "DG")) |
149 | | -@pytest.mark.parametrize("mesh_type", ("interval", "square")) |
150 | | -def test_covariance_inverse_action(m, family, mesh_type, dim): |
151 | | - """Test that covariance operator action and inverse are opposites. |
152 | | - """ |
153 | | - |
154 | | - nx = 20 |
155 | | - if mesh_type == 'interval': |
156 | | - mesh = PeriodicUnitIntervalMesh(nx) |
157 | | - x, = SpatialCoordinate(mesh) |
158 | | - wexpr = cos(2*pi*x) |
159 | | - elif mesh_type == 'square': |
160 | | - mesh = PeriodicUnitSquareMesh(nx, nx) |
161 | | - x, y = SpatialCoordinate(mesh) |
162 | | - wexpr = cos(2*pi*x)*cos(4*pi*y) |
163 | | - elif mesh_type == 'cube': |
164 | | - mesh = PeriodicUnitCubeMesh(nx, nx, nx) |
165 | | - x, y, z = SpatialCoordinate(mesh) |
166 | | - wexpr = cos(2*pi*x)*cos(4*pi*y)*cos(pi*z) |
167 | | - if dim > 0: |
168 | | - V = VectorFunctionSpace(mesh, family, 1, dim=dim) |
169 | | - wexpr = as_vector([-1**(j+1)*wexpr for j in range(dim)]) |
170 | | - else: |
171 | | - V = FunctionSpace(mesh, family, 1) |
172 | | - |
173 | | - L = 0.1 |
174 | | - sigma = 0.9 |
175 | | - |
176 | | - solver_parameters = { |
177 | | - 'ksp_type': 'preonly', |
178 | | - 'pc_type': 'lu', |
179 | | - 'pc_factor_mat_solver_type': 'mumps' |
180 | | - } |
181 | | - |
182 | | - form = 'IP' if family == 'DG' else 'CG' |
183 | | - |
184 | | - B = AutoregressiveCovariance( |
185 | | - V, L, sigma, m, form=form, |
186 | | - solver_parameters=solver_parameters, |
187 | | - options_prefix="") |
188 | | - |
189 | | - w = Function(V).project(wexpr) |
190 | | - wcheck = B.apply_action(B.apply_inverse(w)) |
191 | | - |
192 | | - tol = 1e-10 |
193 | | - |
194 | | - assert errornorm(w, wcheck) < tol |
195 | | - |
196 | | - |
197 | | -@pytest.mark.skipcomplex |
198 | | -@pytest.mark.parallel([1, 2]) |
199 | | -@pytest.mark.parametrize("m", (0, 2, 4)) |
200 | | -def test_covariance_inverse_action_hdiv(m): |
201 | | - """Test that covariance operator action and inverse are opposites |
202 | | - for hdiv spaces. |
203 | | - """ |
204 | | - |
205 | | - nx = 20 |
206 | | - mesh = PeriodicUnitSquareMesh(nx, nx) |
207 | | - x, y = SpatialCoordinate(mesh) |
208 | | - wexpr = cos(2*pi*x)*cos(4*pi*x) |
209 | | - |
210 | | - V = FunctionSpace(mesh, "BDM", 1) |
211 | | - wexpr = as_vector([-1**(j+1)*wexpr for j in range(2)]) |
212 | | - |
213 | | - L = 0.1 |
214 | | - sigma = 0.9 |
215 | | - |
216 | | - solver_parameters = { |
217 | | - 'ksp_type': 'preonly', |
218 | | - 'pc_type': 'lu', |
219 | | - 'pc_factor_mat_solver_type': 'mumps' |
220 | | - } |
221 | | - |
222 | | - B = AutoregressiveCovariance( |
223 | | - V, L, sigma, m, form='IP', |
224 | | - solver_parameters=solver_parameters, |
225 | | - options_prefix="") |
226 | | - |
227 | | - w = Function(V).project(wexpr) |
228 | | - wcheck = B.apply_action(B.apply_inverse(w)) |
229 | | - |
230 | | - tol = 1e-8 |
231 | | - |
232 | | - assert errornorm(w, wcheck) < tol |
| 6 | +@pytest.fixture(autouse=True) |
| 7 | +def autouse_set_test_tape(set_test_tape): |
| 8 | + pass |
233 | 9 |
|
234 | 10 |
|
235 | 11 | @pytest.mark.skipcomplex |
@@ -269,133 +45,3 @@ def test_covariance_adjoint_norm(m, family): |
269 | 45 | assert min(taylor['R0']['Rate']) > 0.95, taylor['R0'] |
270 | 46 | assert min(taylor['R1']['Rate']) > 1.95, taylor['R1'] |
271 | 47 | assert min(taylor['R2']['Rate']) > 2.95, taylor['R2'] |
272 | | - |
273 | | - |
274 | | -@pytest.mark.skipcomplex |
275 | | -@pytest.mark.parallel([1, 2]) |
276 | | -@pytest.mark.parametrize("m", (0, 2, 4)) |
277 | | -@pytest.mark.parametrize("family", ("CG", "DG")) |
278 | | -@pytest.mark.parametrize("operation", ("action", "inverse")) |
279 | | -def test_covariance_mat(m, family, operation): |
280 | | - """Test that covariance mat and pc apply correct and opposite actions. |
281 | | - """ |
282 | | - nx = 20 |
283 | | - L = 0.2 |
284 | | - sigma = 0.9 |
285 | | - |
286 | | - mesh = UnitIntervalMesh(nx) |
287 | | - coords, = SpatialCoordinate(mesh) |
288 | | - |
289 | | - V = FunctionSpace(mesh, family, 1) |
290 | | - |
291 | | - form = 'IP' if family == 'DG' else 'CG' |
292 | | - |
293 | | - B = AutoregressiveCovariance(V, L, sigma, m, form=form) |
294 | | - |
295 | | - mat = CovarianceMat(B, operation=operation) |
296 | | - |
297 | | - expr = 2*pi*coords |
298 | | - |
299 | | - if operation == 'action': |
300 | | - x = Function(V).project(expr).riesz_representation() |
301 | | - y = Function(V) |
302 | | - xcheck = x.copy(deepcopy=True) |
303 | | - ycheck = y.copy(deepcopy=True) |
304 | | - |
305 | | - B.apply_action(xcheck, tensor=ycheck) |
306 | | - |
307 | | - elif operation == 'inverse': |
308 | | - x = Function(V).project(expr) |
309 | | - y = Function(V.dual()) |
310 | | - xcheck = x.copy(deepcopy=True) |
311 | | - ycheck = y.copy(deepcopy=True) |
312 | | - |
313 | | - B.apply_inverse(xcheck, tensor=ycheck) |
314 | | - |
315 | | - with x.dat.vec as xv, y.dat.vec as yv: |
316 | | - mat.mult(xv, yv) |
317 | | - |
318 | | - # flip to primal space to calculate norms |
319 | | - if operation == 'inverse': |
320 | | - y = y.riesz_representation() |
321 | | - ycheck = ycheck.riesz_representation() |
322 | | - |
323 | | - assert errornorm(ycheck, y)/norm(ycheck) < 1e-12 |
324 | | - |
325 | | - if operation == 'inverse': |
326 | | - y = y.riesz_representation() |
327 | | - ycheck = ycheck.riesz_representation() |
328 | | - |
329 | | - ksp = PETSc.KSP().create() |
330 | | - ksp.setOperators(mat) |
331 | | - |
332 | | - tol = 1e-8 |
333 | | - |
334 | | - petsctools.set_from_options( |
335 | | - ksp, options_prefix=str(operation), |
336 | | - parameters={ |
337 | | - 'ksp_monitor': None, |
338 | | - 'ksp_type': 'richardson', |
339 | | - 'ksp_max_it': 2, |
340 | | - 'ksp_rtol': tol, |
341 | | - 'pc_type': 'python', |
342 | | - 'pc_python_type': 'firedrake.adjoint.CovariancePC', |
343 | | - } |
344 | | - ) |
345 | | - x.zero() |
346 | | - |
347 | | - with x.dat.vec as xv, y.dat.vec as yv: |
348 | | - with petsctools.inserted_options(ksp): |
349 | | - ksp.solve(yv, xv) |
350 | | - |
351 | | - # CovarianceOperator operations should |
352 | | - # be exact inverses of each other. |
353 | | - assert ksp.its == 1 |
354 | | - |
355 | | - if operation == 'action': |
356 | | - x = x.riesz_representation() |
357 | | - xcheck = xcheck.riesz_representation() |
358 | | - |
359 | | - assert errornorm(xcheck, x)/norm(xcheck) < 10*tol |
360 | | - |
361 | | - |
362 | | -@pytest.mark.skipcomplex |
363 | | -@pytest.mark.parametrize("family", ("CG", "DG")) |
364 | | -def test_diffusion_form(family): |
365 | | - """Test that the provided diffusion forms converge to a known solution at the expected rate. |
366 | | - """ |
367 | | - from firedrake.adjoint.covariance_operator import diffusion_form |
368 | | - |
369 | | - def poisson_error(mesh, family): |
370 | | - V = FunctionSpace(mesh, family, 1) |
371 | | - x, y = SpatialCoordinate(mesh) |
372 | | - |
373 | | - f = Function(V).interpolate((1+8*pi*pi)*cos(x*pi*2)*cos(y*pi*2)) |
374 | | - uexact = cos(x*pi*2)*cos(y*pi*2) |
375 | | - |
376 | | - nu = Constant(1) |
377 | | - u = TrialFunction(V) |
378 | | - v = TestFunction(V) |
379 | | - |
380 | | - formulation = AutoregressiveCovariance.DiffusionForm( |
381 | | - "CG" if family == "CG" else "IP") |
382 | | - |
383 | | - a = diffusion_form(u, v, nu, formulation) |
384 | | - L = inner(f, v)*dx |
385 | | - |
386 | | - u = Function(V) |
387 | | - solve(a == L, u) |
388 | | - |
389 | | - return errornorm(uexact, u) |
390 | | - |
391 | | - base_nx = 16 |
392 | | - nrefs = 3 |
393 | | - base_mesh = UnitSquareMesh(base_nx, base_nx) |
394 | | - mh = MeshHierarchy(base_mesh, nrefs) |
395 | | - |
396 | | - errors = [poisson_error(m, family) for m in mh] |
397 | | - |
398 | | - # second order convergence |
399 | | - nxs = [base_nx*(2**n) for n in range(nrefs+1)] |
400 | | - rate = - np.diff(np.log(errors))/np.diff(np.log(nxs)) |
401 | | - assert (rate > 1.9).all() |
0 commit comments