Note
Go to the end to download the full example code.
Deduplication: a scene has fewer atmospheres than pixels#
A retrieval product does not carry a different atmospheric state in every pixel. It comes out of a classifier or off a coarse grid, so neighbouring pixels repeat, and a sweep over the image calls the engine again and again with arguments it has already seen.
dedup=True collapses identical input rows before calling anything,
computes each distinct one once, and expands the result back onto every pixel
that shares it. It never changes a value or a shape, only how many times the
engine runs to produce them.
Run with:
pixi run -e dev python examples/04_dedup_on_a_scene.py
from __future__ import annotations
import shutil
import sys
import tempfile
import time
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
import xsweep
# sphinx-gallery executes examples without `__file__` set, so the gallery
# directory is located from the already-installed xsweep package instead.
sys.path.insert(0, str(Path(xsweep.__file__).resolve().parents[2] / "examples"))
from _scene import atmosphere_scene # noqa: E402
from _solvers import mc_reflectance # noqa: E402
from xsweep import SweepPolicy, sweep # noqa: E402
N_PHOTONS = 1500
CALLS = {"n": 0}
@sweep("loop(tau, ssa) -> reflectance(), transmittance()")
def layer(tau: float, ssa: float) -> dict[str, float]:
"""Reflectance and transmittance of a scattering layer, by Monte-Carlo."""
CALLS["n"] += 1
return mc_reflectance(tau, ssa, g=0.6, mu0=0.5, n_photons=N_PHOTONS, seed=0)
def counted() -> int:
"""Return the calls made since the last check, and reset the counter."""
made, CALLS["n"] = CALLS["n"], 0
return made
tau_map, ssa_map = atmosphere_scene(24, block=4)
scene = xr.Dataset({"tau": (("y", "x"), tau_map), "ssa": (("y", "x"), ssa_map)})
rows = np.stack([tau_map.ravel(), ssa_map.ravel()]).T
print(f"pixels: {tau_map.size}")
print(f"distinct atmospheres: {len(np.unique(rows, axis=0))}")
pixels: 576
distinct atmospheres: 6
The plan counts the distinct rows for you#
Turning dedup on adds a line to the SPACE block: how many unique
rows the space holds, and therefore how many calls a run will really make.
Nothing has been computed yet.
print(layer.explain(scene, policy=SweepPolicy(dedup=True)))
+- layer (v0) ------------------------------------------------+
| loop(tau, ssa) -> reflectance(), transmittance()
+------------------------------------------------------------+
SPACE
y tau, ssa 24
x tau, ssa 24
points 576
dedup 6 unique (570 duplicates)
CALLS
to compute 6
cached 0
skipped 0
total 6
ARGUMENTS
tau loop float64
ssa loop float64
RESULT
reflectance (y: 24, x: 24) float64
transmittance (y: 24, x: 24) float64
STORE none (in-memory: no cache, no resume)
EXECUTOR serial
Without deduplication#
One call per pixel, most of them recomputing an answer the engine has already given.
start = time.perf_counter()
plain = layer(scene)
plain_time = time.perf_counter() - start
plain_calls = counted()
print(f"{plain_calls} calls in {plain_time:.2f} s")
576 calls in 2.32 s
With deduplication#
start = time.perf_counter()
deduped = layer(scene, policy=SweepPolicy(dedup=True))
dedup_time = time.perf_counter() - start
dedup_calls = counted()
print(f"{dedup_calls} calls in {dedup_time:.2f} s")
print(f"\ncalls: {plain_calls / dedup_calls:.0f}x fewer")
print(f"time: {plain_time / dedup_time:.0f}x faster")
6 calls in 0.05 s
calls: 96x fewer
time: 50x faster
Same numbers, to the last bit#
Deduplication is a cost decision, not a numerical one. The engine is stochastic and the two runs still agree exactly, because the duplicated pixels were copied rather than recomputed.
np.testing.assert_array_equal(plain.reflectance.values, deduped.reflectance.values)
np.testing.assert_array_equal(plain.transmittance.values, deduped.transmittance.values)
print("identical on every pixel")
identical on every pixel
What it does not buy#
The duplicate positions still have to be filled. In memory that is a gather; into a store it is a batched write pass. Deduplication removes engine calls, which is nearly the whole story when a call is expensive and almost none of it when a call is free.
run_dir = Path(tempfile.mkdtemp())
try:
start = time.perf_counter()
persisted = layer(
scene, policy=SweepPolicy(dedup=True, store=run_dir / "scene.zarr")
)
print(f"{counted()} calls, persisted, in {time.perf_counter() - start:.2f} s")
np.testing.assert_array_equal(
plain.reflectance.values, persisted.reflectance.values
)
print("still identical once written to a store")
finally:
shutil.rmtree(run_dir)
6 calls, persisted, in 0.26 s
still identical once written to a store
The scaling that matters#
The number of distinct rows is a property of the scene, not of its size. Doubling the image doubles the pixels and leaves the call count where it was, so the saving grows with the image.
print(f"{'pixels':>8} {'distinct':>9} {'calls saved':>12}")
for size in (24, 48, 96, 192):
t_map, s_map = atmosphere_scene(size, block=size // 6)
unique = len(np.unique(np.stack([t_map.ravel(), s_map.ravel()]).T, axis=0))
print(f"{t_map.size:>8} {unique:>9} {t_map.size - unique:>12}")
pixels distinct calls saved
576 6 570
2304 6 2298
9216 6 9210
36864 6 36858
The result is still an image, with the pixels the duplicates were expanded onto in their proper places.
fig, axes = plt.subplots(1, 2, figsize=(8.0, 3.4))
for ax, (data, title) in zip(
axes,
((scene.tau, r"input $\tau$"), (deduped.reflectance, "reflectance, deduplicated")),
strict=True,
):
image = ax.imshow(data, origin="upper", cmap="viridis")
ax.set_title(title)
ax.set_xlabel("x")
ax.set_ylabel("y")
fig.colorbar(image, ax=ax, shrink=0.85)
fig.tight_layout()
plt.show()

Total running time of the script: (0 minutes 2.769 seconds)