update filter, flow ema 0.5 + med 3

This commit is contained in:
ykcheungab
2026-09-12 19:36:36 +08:00
parent 5f78115429
commit 0d5d8ff70d
12 changed files with 697 additions and 62 deletions
+59 -10
View File
@@ -29,6 +29,39 @@ def compute_boundary_mask(active):
return active & ~fully_surrounded
def build_masked_factor(lap):
"""Factorize the masked Laplacian for fast repeated triangular solves.
The reduced system is a symmetric M-matrix (graph Laplacian of the active
pixels with an extra Dirichlet pin), so SuperLU can skip partial pivoting
(``SymmetricMode`` + ``diag_pivot_thresh=0``), which is ~2x faster than the
default pivoting factorization. Factoring in float32 halves the memory
traffic of the triangular solves; relative error against the float64
reference stays around 1e-7 for these systems.
``rhs_dtype`` is returned because ``SuperLU.solve`` refuses to cast a
float64 right-hand side down to a float32 factor.
lap: (N, N) symmetric sparse Laplacian (any scipy dtype)
returns: (solve_callable, rhs_dtype)
"""
lap_csc = lap.tocsc()
lap32 = lap_csc.astype(numpy.float32, copy=False)
for diag_pivot_thresh in (0.0, 1.0):
try:
lu = sparse_linalg.splu(
lap32,
permc_spec="MMD_AT_PLUS_A",
diag_pivot_thresh=diag_pivot_thresh,
options=dict(SymmetricMode=True),
)
return lu.solve, numpy.float32
except Exception:
continue
# Last resort: the original pivoting LU on the float64 matrix.
return sparse_linalg.factorized(lap_csc), numpy.float64
def sample_boundary_coords(boundary_coords, config: MaskedPoissonConfig):
"""Subsample boundary pixel coordinates for capacitance correction."""
if boundary_coords.size == 0:
@@ -53,6 +86,7 @@ class MaskedDomain:
"mask",
"index_map",
"factor",
"factor_dtype",
"lap",
"num_active",
"pinned",
@@ -75,11 +109,13 @@ class MaskedDomain:
pinned,
active_coords,
active_linear_indices,
factor_dtype=numpy.float32,
):
self.shape = shape
self.mask = mask
self.index_map = index_map
self.factor = factor
self.factor_dtype = numpy.dtype(factor_dtype)
self.lap = lap
self.num_active = num_active
self.pinned = pinned
@@ -156,7 +192,7 @@ class MaskedDomain:
data.append(diag)
lap = sparse.csc_matrix((data, (rows, cols)), shape=(num_active, num_active), dtype=numpy.float64)
factor = sparse_linalg.factorized(lap)
factor, factor_dtype = build_masked_factor(lap)
return cls(
shape=active.shape,
mask=active.copy(),
@@ -167,6 +203,7 @@ class MaskedDomain:
pinned=pinned,
active_coords=active_coords,
active_linear_indices=active_linear_indices,
factor_dtype=factor_dtype,
)
def matches(self, mask):
@@ -179,13 +216,20 @@ class MaskedDomain:
f: (H, W) scalar source field
returns: (N,) RHS on active pixels; empty if no active pixels
The returned dtype matches the factorization (see ``factor_dtype``):
``SuperLU.solve`` will not downcast a float64 RHS for a float32 factor.
"""
if self.num_active == 0:
return numpy.zeros(0, dtype=numpy.float64)
if self._rhs_buffer is None or self._rhs_buffer.shape != (self.num_active,):
self._rhs_buffer = numpy.empty(self.num_active, dtype=numpy.float64)
return numpy.zeros(0, dtype=self.factor_dtype)
if (
self._rhs_buffer is None
or self._rhs_buffer.shape != (self.num_active,)
or self._rhs_buffer.dtype != self.factor_dtype
):
self._rhs_buffer = numpy.empty(self.num_active, dtype=self.factor_dtype)
numpy.take(
numpy.asarray(f, dtype=numpy.float64).ravel(),
numpy.asarray(f, dtype=self.factor_dtype).ravel(),
self.active_linear_indices,
out=self._rhs_buffer,
)
@@ -197,11 +241,15 @@ class MaskedDomain:
def prepare_rhs_pair(self, f0, f1):
"""Extract two gauge-fixed RHS vectors into one reusable (N, 2) buffer."""
if self.num_active == 0:
return numpy.zeros((0, 2), dtype=numpy.float64)
if self._rhs_pair_buffer is None or self._rhs_pair_buffer.shape != (self.num_active, 2):
self._rhs_pair_buffer = numpy.empty((self.num_active, 2), dtype=numpy.float64)
flat0 = numpy.asarray(f0, dtype=numpy.float64).ravel()
flat1 = numpy.asarray(f1, dtype=numpy.float64).ravel()
return numpy.zeros((0, 2), dtype=self.factor_dtype)
if (
self._rhs_pair_buffer is None
or self._rhs_pair_buffer.shape != (self.num_active, 2)
or self._rhs_pair_buffer.dtype != self.factor_dtype
):
self._rhs_pair_buffer = numpy.empty((self.num_active, 2), dtype=self.factor_dtype)
flat0 = numpy.asarray(f0, dtype=self.factor_dtype).ravel()
flat1 = numpy.asarray(f1, dtype=self.factor_dtype).ravel()
numpy.take(flat0, self.active_linear_indices, out=self._rhs_pair_buffer[:, 0])
numpy.take(flat1, self.active_linear_indices, out=self._rhs_pair_buffer[:, 1])
self._rhs_pair_buffer -= self._rhs_pair_buffer.mean(axis=0, keepdims=True)
@@ -255,6 +303,7 @@ class MaskedDomain:
"mask": self.mask,
"index_map": self.index_map,
"factor": self.factor,
"factor_dtype": self.factor_dtype,
"lap": self.lap,
"num_active": self.num_active,
"pinned": self.pinned,