Skip to content

Commit 1837cc7

Browse files
authored
Merge pull request #174 from underworldcode/bugfix/integrals-revert
Revert PR #172 (integrals regression) + xfail CellWiseIntegral parity tests
2 parents fe05c63 + d65fe9a commit 1837cc7

2 files changed

Lines changed: 57 additions & 28 deletions

File tree

src/underworld3/cython/petsc_maths.pyx

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -102,46 +102,31 @@ class Integral:
102102
raise RuntimeError("Integral evaluation for Dyadic integrands not supported.")
103103

104104

105-
# BUGFIX(#171): clone the mesh DM and create a fresh DS per call,
106-
# rather than mutating the shared mesh DS. The previous code path
107-
# was:
108-
# ds = self.mesh.dm.getDS() # shared DS
109-
# PetscDSSetObjective(ds, 0, fn) # mutate it
110-
# DMPlexComputeIntegralFEM(...)
111-
# PetscDSSetObjective on the shared DS makes
112-
# DMPlexComputeIntegralFEM cost grow linearly with repeated
113-
# evaluate() calls in long simulations — observed in convection
114-
# diagnostics where t_vol grew from 5 s to 833 s over ~3000 calls.
115-
# The sister class CellWiseIntegral already takes the
116-
# clone-DM + createDS path; this brings Integral in line.
117-
# Explicit destroy of the clone at the end keeps long runs bounded.
118-
mesh = self.mesh
105+
self.dm = self.mesh.dm # .clone()
106+
mesh=self.mesh
119107

120108
_getext_result = getext(self.mesh, JITCallbackSet(residual=(self.fn,)),
121109
self.mesh.vars.values(), verbose=verbose)
122110
cdef PtrContainer ext = _getext_result.ptrobj
123111

124112
# Pull out vec for variables, and go ahead with the integral
113+
125114
self.mesh.update_lvec()
126-
a_global = self.mesh.dm.getGlobalVec()
127-
self.mesh.dm.localToGlobal(self.mesh.lvec, a_global)
115+
a_global = self.dm.getGlobalVec()
116+
self.dm.localToGlobal(self.mesh.lvec, a_global)
128117

129118
cdef Vec cgvec
130119
cgvec = a_global
131120

132-
cdef DM dmc = self.mesh.dm.clone()
133-
cdef FE fec = FE().createDefault(self.mesh.dim, 1, False, -1)
134-
dmc.setField(0, fec)
135-
dmc.createDS()
136-
137-
cdef DS ds = dmc.getDS()
121+
cdef DM dm = self.dm
122+
cdef DS ds = self.dm.getDS()
138123
cdef PetscScalar val_array[256]
139124

125+
# Now set callback...
140126
ierr = PetscDSSetObjective(ds.ds, 0, ext.fns_residual[0]); CHKERRQ(ierr)
141-
ierr = DMPlexComputeIntegralFEM(dmc.dm, cgvec.vec, &(val_array[0]), NULL); CHKERRQ(ierr)
127+
ierr = DMPlexComputeIntegralFEM(dm.dm, cgvec.vec, &(val_array[0]), NULL); CHKERRQ(ierr)
142128

143-
self.mesh.dm.restoreGlobalVec(a_global)
144-
dmc.destroy()
129+
self.dm.restoreGlobalVec(a_global)
145130

146131
# We're making an assumption here that PetscScalar is same as double.
147132
# Need to check where this may not be the case.
@@ -315,10 +300,15 @@ class CellWiseIntegral:
315300
cdef Vec cgvec
316301
cgvec = a_global
317302

318-
## Does this need to be consistent with everything else ?
319-
303+
# TODO(BUG): clone+createDefault+createDS gives dmc a single P1 field,
304+
# but cgvec is packed for mesh.dm's multi-field layout — the integral
305+
# reads the wrong DOFs and over-counts by ~2x on the unit square.
306+
# Same bug as PR #172 (reverted in PR #173-followup). Tests in
307+
# tests/test_0501_integrals.py::test_cellwise_integrate_* are xfail
308+
# until this is rewritten to integrate against mesh.dm + getDS()
309+
# directly (the pre-PR-172 Integral pattern).
320310
cdef DM dmc = self.mesh.dm.clone()
321-
cdef FE fec = FE().createDefault(self.dim, 1, False, -1)
311+
cdef FE fec = FE().createDefault(self.mesh.dim, 1, False, -1)
322312
dmc.setField(0, fec)
323313
dmc.createDS()
324314

tests/test_0501_integrals.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,42 @@ def test_integrate_swarmvar_deriv_00():
166166
assert abs(value + 2) < 0.0001
167167

168168
return
169+
170+
171+
# CellWiseIntegral parity tests — the sister class of Integral that returns a
172+
# per-cell array. These are marked xfail because CellWiseIntegral.evaluate()
173+
# clones mesh.dm and attaches a fresh single-field discretisation
174+
# (FE.createDefault + setField + createDS), but then integrates against
175+
# mesh.dm.getGlobalVec() which is packed for the original multi-field layout.
176+
# The mismatch produces a ~2× over-count on simple cases — the same bug that
177+
# motivated the revert of PR #172 for Integral.
178+
#
179+
# These tests will start passing once CellWiseIntegral.evaluate() is rewritten
180+
# to integrate against mesh.dm + mesh.dm.getDS() directly (matching the
181+
# pre-PR-172 Integral pattern).
182+
@pytest.mark.xfail(reason="CellWiseIntegral has the same field-cloning bug "
183+
"that motivated the PR #172 revert")
184+
def test_cellwise_integrate_constants():
185+
186+
calculator = uw.maths.CellWiseIntegral(mesh, fn=1.0)
187+
cell_values = calculator.evaluate()
188+
189+
# Sum of per-cell volumes equals the total mesh volume (unit square = 1.0).
190+
assert abs(np.sum(cell_values) - 1.0) < 0.001
191+
192+
return
193+
194+
195+
@pytest.mark.xfail(reason="CellWiseIntegral has the same field-cloning bug "
196+
"that motivated the PR #172 revert")
197+
def test_cellwise_integrate_meshvar():
198+
199+
s_soln.data[:, 0] = np.sin(np.pi * s_soln.coords[:, 0])
200+
201+
calculator = uw.maths.CellWiseIntegral(mesh, fn=s_soln.sym[0])
202+
cell_values = calculator.evaluate()
203+
204+
# Sum matches the global Integral: ∫∫ sin(πx) dx dy = 2/π over the unit square.
205+
assert abs(np.sum(cell_values) - 2 / np.pi) < 0.001
206+
207+
return

0 commit comments

Comments
 (0)