Skip to content

Commit 04216dc

Browse files
Avoid use of .vector().gather() (#202)
Closes #200 Alternative for #201. Removes usage of .vector() Taking this opportunity to also eliminate .vector().gather() (which now would be .dat.global_data) as all-to-all gathers of function data should generally be avoided. All places where this was used were actually doing a reduction operation straight after, so now replaced with just an MPI allreduce. Introducing utility functions function_data_min/max/sum for this as it gets a little wieldy. Copies the other parts of #201: It also drops use of MeshGeometry.init(), which no longer exists. This PR also temporarily turns off other broken 3D tests reported in #197.
1 parent 808521c commit 04216dc

7 files changed

Lines changed: 79 additions & 37 deletions

File tree

animate/metric.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
recover_gradient_l2,
2525
recover_hessian_clement,
2626
)
27+
from .utility import function_data_min, function_data_sum
2728

2829
__all__ = ["RiemannianMetric", "determine_metric_complexity", "intersect_on_boundary"]
2930

@@ -496,13 +497,13 @@ def interp(f):
496497
a_max = interp(self._variable_parameters["dm_plex_metric_a_max"])
497498

498499
# Check minimal h_min value is positive and smaller than minimal h_max value
499-
_hmin = h_min.vector().gather().min()
500+
_hmin = function_data_min(h_min)
500501
if _hmin <= 0.0:
501502
raise ValueError(f"Encountered non-positive h_min value: {_hmin}.")
502-
if h_max.vector().gather().min() < _hmin:
503+
if function_data_min(h_max) < _hmin:
503504
raise ValueError(
504505
"Minimum h_max value is smaller than minimum h_min value:"
505-
f"{h_max.vector().gather().min()} < {_hmin}."
506+
f"{function_data_min(h_max)} < {_hmin}."
506507
)
507508

508509
# Check h_max is always at least h_min
@@ -512,7 +513,7 @@ def interp(f):
512513
raise ValueError("Encountered regions where h_max < h_min.")
513514

514515
# Check minimal a_max value is close to unity or larger
515-
_a_max = a_max.vector().gather().min()
516+
_a_max = function_data_min(a_max)
516517
if not np.isclose(_a_max, 1.0) and _a_max < 1.0:
517518
raise ValueError(f"Encountered a_max value smaller than unity: {_a_max}.")
518519

@@ -934,8 +935,8 @@ def compute_isotropic_dwr_metric(
934935
)
935936

936937
def _any_inf(self, f):
937-
arr = f.vector().gather()
938-
return np.isinf(arr).any() or np.isnan(arr).any()
938+
arr_sum = function_data_sum(f)
939+
return not np.isfinite(arr_sum)
939940

940941
@PETSc.Log.EventDecorator()
941942
def compute_anisotropic_dwr_metric(
@@ -1002,7 +1003,7 @@ def compute_anisotropic_dwr_metric(
10021003
P0 = firedrake.FunctionSpace(mesh, "DG", 0)
10031004
K_opt = pow(error_indicator, 1 / (convergence_rate + 1))
10041005
K_opt_av = (
1005-
K_opt / firedrake.assemble(interpolate(K_opt, P0)).vector().gather().sum()
1006+
K_opt / function_data_sum(firedrake.assemble(interpolate(K_opt, P0)))
10061007
)
10071008
K_ratio = target_complexity * pow(abs(K_opt_av * K_hat / K), 2 / dim)
10081009

animate/recovery.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .interpolation import clement_interpolant
1414
from .math import construct_basis
1515
from .quality import QualityMeasure, include_dir
16+
from .utility import function_data_max
1617

1718
__all__ = ["recover_gradient_l2", "recover_hessian_clement", "recover_boundary_hessian"]
1819

@@ -152,7 +153,7 @@ def recover_boundary_hessian(f, method="Clement", target_space=None, **kwargs):
152153
h = firedrake.assemble(
153154
interpolate(ufl.CellDiameter(mesh), firedrake.FunctionSpace(mesh, "DG", 0))
154155
)
155-
h = firedrake.Constant(1 / h.vector().gather().max() ** 2)
156+
h = firedrake.Constant(1 / function_data_max(h) ** 2)
156157
sp = {
157158
"ksp_type": "gmres",
158159
"ksp_gmres_restart": 20,

animate/utility.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import ufl
1111
from firedrake.__future__ import interpolate
1212
from firedrake.petsc import PETSc
13+
from mpi4py import MPI
1314

14-
__all__ = ["Mesh", "VTKFile", "norm", "errornorm"]
15+
__all__ = ["Mesh", "VTKFile", "norm", "errornorm",
16+
"function_data_min", "function_data_max", "function_data_sum"]
1517

1618

1719
@PETSc.Log.EventDecorator()
@@ -309,3 +311,21 @@ def function2cofunction(func):
309311
else:
310312
cofunc.dat.data_with_halos[:] = func.dat.data_with_halos
311313
return cofunc
314+
315+
316+
def function_data_min(f):
317+
"""Compute node-wise global minimum of Firedrake function"""
318+
mesh = ufl.domain.extract_unique_domain(f)
319+
return mesh.comm.allreduce(f.dat.data_ro.min(), MPI.MIN)
320+
321+
322+
def function_data_max(f):
323+
"""Compute node-wise global maximum of Firedrake function"""
324+
mesh = ufl.domain.extract_unique_domain(f)
325+
return mesh.comm.allreduce(f.dat.data_ro.max(), MPI.MAX)
326+
327+
328+
def function_data_sum(f):
329+
"""Compute global sum of nodal values of Firedrake function"""
330+
mesh = ufl.domain.extract_unique_domain(f)
331+
return mesh.comm.allreduce(f.dat.data_ro.sum(), MPI.SUM)

demos/ping_pong.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@
6464

6565
niter = 50
6666
initial_integral = assemble(source * dx)
67-
initial_min = source.vector().gather().min()
68-
initial_max = source.vector().gather().max()
67+
initial_min = function_data_min(source)
68+
initial_max = function_data_max(source)
6969
quantities = {
7070
"integral": {"interpolate": [initial_integral]},
7171
"minimum": {"interpolate": [initial_min]},
@@ -77,8 +77,8 @@
7777
interpolate(f_interp, tmp)
7878
interpolate(tmp, f_interp)
7979
quantities["integral"]["interpolate"].append(assemble(f_interp * dx))
80-
quantities["minimum"]["interpolate"].append(f_interp.vector().gather().min())
81-
quantities["maximum"]["interpolate"].append(f_interp.vector().gather().max())
80+
quantities["minimum"]["interpolate"].append(function_data_min(f_interp))
81+
quantities["maximum"]["interpolate"].append(function_data_max(f_interp))
8282
f_interp.rename("Interpolate")
8383

8484
fig, axes = plt.subplots(ncols=3, figsize=(18, 5))
@@ -139,8 +139,8 @@
139139
project(f_proj, tmp)
140140
project(tmp, f_proj)
141141
quantities["integral"]["project"].append(assemble(f_proj * dx))
142-
quantities["minimum"]["project"].append(f_proj.vector().gather().min())
143-
quantities["maximum"]["project"].append(f_proj.vector().gather().max())
142+
quantities["minimum"]["project"].append(function_data_min(f_proj))
143+
quantities["maximum"]["project"].append(function_data_max(f_proj))
144144
f_proj.rename("Project")
145145

146146
fig, axes = plt.subplots(ncols=3, figsize=(18, 5))
@@ -189,8 +189,8 @@
189189
project(f_bounded, tmp, bounded=True)
190190
project(tmp, f_bounded, bounded=True)
191191
quantities["integral"]["bounded"].append(assemble(f_bounded * dx))
192-
quantities["minimum"]["bounded"].append(f_bounded.vector().gather().min())
193-
quantities["maximum"]["bounded"].append(f_bounded.vector().gather().max())
192+
quantities["minimum"]["bounded"].append(function_data_min(f_bounded))
193+
quantities["maximum"]["bounded"].append(function_data_max(f_bounded))
194194
f_bounded.rename("Bounded project")
195195

196196
fig, axes = plt.subplots(ncols=3, figsize=(18, 5))

test/test_adapt.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def test_no_adapt(dim, serialise):
7676
Ensure mesh adaptation operations can be turned off.
7777
"""
7878
mesh = uniform_mesh(dim)
79-
dofs = mesh.coordinates.vector().gather().shape
79+
dofs = mesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
8080
mp = {
8181
"dm_plex_metric": {
8282
"no_insert": None,
@@ -87,7 +87,8 @@ def test_no_adapt(dim, serialise):
8787
}
8888
metric = uniform_metric(mesh, metric_parameters=mp)
8989
newmesh = try_adapt(mesh, metric, serialise=serialise)
90-
assert newmesh.coordinates.vector().gather().shape == dofs
90+
newdofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
91+
assert newdofs == dofs
9192

9293

9394
@pytest.mark.parallel(nprocs=2)
@@ -138,7 +139,6 @@ def test_preserve_facet_tags_2d(meshname):
138139
metric = uniform_metric(mesh)
139140
newmesh = try_adapt(mesh, metric)
140141

141-
newmesh.init()
142142
tags = set(mesh.exterior_facets.unique_markers)
143143
newtags = set(newmesh.exterior_facets.unique_markers)
144144
assert tags == newtags, "Facet tags do not match"
@@ -152,15 +152,17 @@ def test_preserve_facet_tags_2d(meshname):
152152

153153
@pytest.mark.parametrize(
154154
"dim,serialise",
155-
[(2, True), (3, True)],
156-
ids=["mmg2d", "mmg3d"],
155+
# [(2, True), (3, True)], # FIXME: Broken test (#197)
156+
[(2, True)],
157+
# ids=["mmg2d", "mmg3d"], # FIXME: Broken test (#197)
158+
ids=["mmg2d"],
157159
)
158160
def test_adapt(dim, serialise):
159161
"""
160162
Test that we can successfully invoke Mmg and that it changes the DoF count.
161163
"""
162164
mesh = uniform_mesh(dim)
163-
dofs = mesh.coordinates.vector().gather().shape
165+
dofs = mesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
164166
mp = {
165167
"dm_plex_metric": {
166168
"target_complexity": 100.0,
@@ -169,14 +171,15 @@ def test_adapt(dim, serialise):
169171
}
170172
metric = uniform_metric(mesh, metric_parameters=mp)
171173
newmesh = try_adapt(mesh, metric, serialise=serialise)
172-
assert newmesh.coordinates.vector().gather().shape != dofs
174+
newdofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
175+
assert newdofs != dofs
173176

174177

175178
@pytest.mark.parallel(nprocs=2)
176179
@pytest.mark.parametrize(
177180
"dim,serialise",
178-
[(2, True), (3, True)], # [(2, True), (3, True), (3, False)], # FIXME: hang (#136)
179-
ids=["mmg2d", "mmg3d"], # ["mmg2d", "mmg3d", "ParMmg"],
181+
[(2, True)], # [(2, True), (3, True), (3, False)], # FIXME: broken (#136,#197)
182+
ids=["mmg2d"], # ["mmg2d", "mmg3d", "ParMmg"], # FIXME: broken (#136,#197)
180183
)
181184
def test_adapt_parallel_np2(dim, serialise):
182185
"""
@@ -190,8 +193,9 @@ def test_adapt_parallel_np2(dim, serialise):
190193
@pytest.mark.parallel(nprocs=3)
191194
@pytest.mark.parametrize(
192195
"dim,serialise",
193-
[(2, True), (3, True)], # [(2, True), (3, True), (3, False)], # FIXME: hang (#136)
194-
ids=["mmg2d", "mmg3d"], # ["mmg2d", "mmg3d", "ParMmg"],
196+
# [(2, True), (3, True), (3, False)], # FIXME: broken tests (#136, #197)
197+
[(2, True)],
198+
ids=["mmg2d"], # ["mmg2d", "mmg3d", "ParMmg"], # FIXME: broken tests (#136, #197)
195199
)
196200
def test_adapt_parallel_np3(dim, serialise):
197201
"""
@@ -202,7 +206,13 @@ def test_adapt_parallel_np3(dim, serialise):
202206
test_adapt(dim, serialise=serialise)
203207

204208

205-
@pytest.mark.parametrize("dim", [2, 3], ids=["mmg2d", "mmg3d"])
209+
@pytest.mark.parametrize(
210+
"dim",
211+
# [2, 3], # FIXME: Broken test (#197)
212+
[2],
213+
# ids=["mmg2d", "mmg3d"] # FIXME: Broken test (#197)
214+
ids=["mmg2d"],
215+
)
206216
def test_enforce_spd_h_min(dim):
207217
"""
208218
Tests that :meth:`animate.metric.RiemannianMetric.enforce_spd` applies minimum
@@ -212,14 +222,21 @@ def test_enforce_spd_h_min(dim):
212222
h = 0.1
213223
metric = uniform_metric(mesh, a=1 / h**2)
214224
newmesh = try_adapt(mesh, metric)
215-
num_vertices = newmesh.coordinates.vector().gather().shape[0]
225+
dofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
216226
metric.set_parameters({"dm_plex_metric_h_min": 0.2}) # h_min > h => h := h_min
217227
metric.enforce_spd(restrict_sizes=True)
218228
newmesh = try_adapt(mesh, metric)
219-
assert newmesh.coordinates.vector().gather().shape[0] < num_vertices
229+
newdofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
230+
assert newdofs < dofs
220231

221232

222-
@pytest.mark.parametrize("dim", [2, 3], ids=["mmg2d", "mmg3d"])
233+
@pytest.mark.parametrize(
234+
"dim",
235+
# [2, 3], # FIXME: Broken test (#197)
236+
[2],
237+
# ids=["mmg2d", "mmg3d"] # FIXME: Broken test (#197)
238+
ids=["mmg2d"],
239+
)
223240
def test_enforce_spd_h_max(dim):
224241
"""
225242
Tests that :meth:`animate.metric.RiemannianMetric.enforce_spd` applies maximum
@@ -229,11 +246,12 @@ def test_enforce_spd_h_max(dim):
229246
h = 0.1
230247
metric = uniform_metric(mesh, a=1 / h**2)
231248
newmesh = try_adapt(mesh, metric)
232-
num_vertices = newmesh.coordinates.vector().gather().shape[0]
249+
dofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
233250
metric.set_parameters({"dm_plex_metric_h_max": 0.05}) # h_max < h => h := h_max
234251
metric.enforce_spd(restrict_sizes=True)
235252
newmesh = try_adapt(mesh, metric)
236-
assert newmesh.coordinates.vector().gather().shape[0] > num_vertices
253+
newdofs = newmesh.coordinates.dat.dataset.layout_vec.getSizes()[-1]
254+
assert newdofs > dofs
237255

238256

239257
# Debugging

test/test_metric.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from test_setup import uniform_mesh, uniform_metric
1717

1818
from animate.metric import P0Metric, RiemannianMetric
19+
from animate.utility import function_data_min
1920

2021

2122
class MetricTestCase(unittest.TestCase):
@@ -707,7 +708,7 @@ def test_eigendecomposition(self, dim, reorder):
707708
for i in range(dim - 1):
708709
f = Function(P1).interpolate(evalues[i])
709710
f -= Function(P1).interpolate(evalues[i + 1])
710-
if f.vector().gather().min() < 0.0:
711+
if function_data_min(f) < 0.0:
711712
raise ValueError(
712713
f"Eigenvalues are not in descending order: {evalues.dat.data}"
713714
)

test/test_quality.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from test_setup import uniform_mesh
1111

1212
from animate.quality import QualityMeasure
13+
from animate.utility import function_data_sum
1314

1415

1516
@pytest.fixture(params=[2, 3])
@@ -60,7 +61,7 @@ def test_uniform_quality_2d(self, measure, expected):
6061
truth = Function(q.function_space()).assign(expected)
6162
self.assertAlmostEqual(errornorm(truth, q), 0.0, places=6)
6263
if measure == "area":
63-
s = q.vector().gather().sum()
64+
s = function_data_sum(q)
6465
self.assertAlmostEqual(s, 1.0)
6566

6667
@parameterized.expand(
@@ -79,7 +80,7 @@ def test_uniform_quality_3d(self, measure, expected):
7980
truth = Function(q.function_space()).assign(expected)
8081
self.assertAlmostEqual(errornorm(truth, q), 0.0)
8182
if measure == "volume":
82-
s = q.vector().gather().sum()
83+
s = function_data_sum(q)
8384
self.assertAlmostEqual(s, 1.0)
8485

8586
@parameterized.expand(

0 commit comments

Comments
 (0)