mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-09-04 00:30:42 +02:00
Slide 30 reported an 8/11 cluster mismatch between ClusterFinder and ClusterFinderFrozen without naming its cause. Two experiments now do. Instrumented: 974 divergent pixels partition onto the three decisions with no remainder -- Test1 619 flips / 0 clusters, Test3 347 / 11, local-max gate 8 / 8. Ablated: the 11 go to exactly zero. Corrects a claim the data refuted -- Test1 initiates independently, ~7x slower, and never creates a cluster. AARE_BRANCH_TRACE defaults to 0 and folds away at compile time, so the CPU baseline the deck quotes is unaffected. AARE_TEST3_ENABLED defaults to 1. New annex A7 (2 slides): part 1 restores the third test slide 4 omits and derives c3 from variance addition; part 2 carries the measurement. fig_test3 painted a completed branch map with a "scan is here" cursor over it -- now the finished frame, arrows meaning raster order, shadow given its own fill so a photon stops looking 3x4 wide, frozen in red so amber keeps one meaning. fig_overlap_9x9: row spacing now clears the tag, not the lane. New docs/deck/QA.md: the code and algorithm questions this raised, with where each is settled. Also retired 3 figures placed on no slide; annex count in report and README was stale (6/53, now 7/55).
149 lines
6.1 KiB
Python
149 lines
6.1 KiB
Python
"""Which test is responsible for the serial-vs-frozen disagreement?
|
|
|
|
ClusterFinder and ClusterFinderFrozen differ in exactly one thing: WHEN the
|
|
pedestal is pushed. `push_fast` touches only the pixel's own accumulators and
|
|
never reads the stencil, so the update itself is order-independent: given the
|
|
same set of updated pixels, both models end a frame with a bit-identical
|
|
pedestal. The only channel for divergence is therefore a differing DECISION.
|
|
|
|
There are three places a decision can differ, and they are not equally exposed:
|
|
|
|
Test1 max > nSigma*rms reads the stencil MAX.
|
|
local-max gate v == m compares two stencil values.
|
|
Test3 total > c3*nSigma*rms reads the stencil SUM, so it collects the
|
|
shift of every already-scanned neighbour --
|
|
three above and one left -- where Test1 feels
|
|
at most the one that happens to be the argmax.
|
|
|
|
WHAT THIS ACTUALLY FOUND (the prior stated here before running it was only half
|
|
right, so it is recorded as measured rather than as predicted):
|
|
|
|
* Every cluster frozen finds and serial does not -- 11 of them -- comes from
|
|
Test3. Compiling Test3 out sends that count to exactly zero.
|
|
* Every cluster serial finds and frozen does not -- 8 -- comes from the
|
|
local-max gate, downstream of accumulated pedestal drift.
|
|
* Test1's threshold flips MOST often, 619 of 974 divergent pixels, and yields
|
|
ZERO clusters: it only moves a pixel between QUIET_UPDATE and SHADOW, and
|
|
neither of those stores.
|
|
* Test1 is not merely downstream of Test3. With Test3 ablated the first
|
|
divergence is still a Test1 flip, on a frame where the two pedestals were
|
|
provably identical. It initiates on its own, just ~7x slower (frame 30
|
|
against frame 4), and cannot create a cluster by itself.
|
|
|
|
This does not ablate anything -- both finders run their shipped logic. Each
|
|
records a per-pixel branch code (see branch_map in either header) and we diff
|
|
the two maps frame by frame, which names the guilty test on the real algorithm
|
|
and localises it for the annex figure.
|
|
|
|
Writes branch_trace.json next to itself.
|
|
|
|
REQUIRES a build with branch tracing on, which is OFF by default so the
|
|
shipped path carries no per-pixel store:
|
|
|
|
sed -i 's/#define AARE_BRANCH_TRACE 0/#define AARE_BRANCH_TRACE 1/' \
|
|
include/aare/ClusterFinder.hpp include/aare/ClusterFinderFrozen.hpp
|
|
cmake --build build -j8
|
|
|
|
Set it back to 0 afterwards. Without it branch_map is all-5 and this
|
|
script reports no divergence at all.
|
|
"""
|
|
import sys, json, time
|
|
sys.path.append('/home/ferjao_k/aare/build')
|
|
sys.path.append('/home/ferjao_k/aare/python/tests')
|
|
|
|
from pathlib import Path
|
|
from collections import Counter
|
|
import numpy as np
|
|
|
|
from aare import File, ClusterFinder, ClusterFinderFrozen
|
|
from helper import centers, only_sets
|
|
|
|
OUT = Path(__file__).resolve().parent
|
|
BASE = Path('/mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/'
|
|
'process/xrf/')
|
|
|
|
N_PED, N, N_SIGMA = 1000, 10000, 5
|
|
CLUSTER = (3, 3)
|
|
IMG = (400, 400)
|
|
CAP = 50_000
|
|
|
|
CODE = {0: "NEG", 1: "SHADOW", 2: "TEST1_STORE", 3: "TEST3_STORE",
|
|
6: "TEST3_SKIP", 4: "QUIET_UPDATE", 5: "UNTOUCHED"}
|
|
|
|
f = File(BASE / 'Cu_factor_10_data_master_0.json')
|
|
pd = File(BASE / 'Cu_factor_10_pedestal_master_0.json')
|
|
|
|
cf_cpu = ClusterFinder(IMG, CLUSTER, n_sigma=N_SIGMA, capacity=CAP)
|
|
cf_frz = ClusterFinderFrozen(IMG, CLUSTER, n_sigma=N_SIGMA, capacity=CAP)
|
|
|
|
t0 = time.perf_counter()
|
|
pd.seek(0)
|
|
for _ in range(N_PED):
|
|
img = pd.read_frame().copy()
|
|
cf_cpu.push_pedestal_frame(img)
|
|
cf_frz.push_pedestal_frame(img)
|
|
print(f'pedestal train: {time.perf_counter()-t0:.1f}s', flush=True)
|
|
|
|
f.seek(0)
|
|
data = f.read_n(N)
|
|
print('data:', data.shape, data.dtype, flush=True)
|
|
|
|
# (cpu_code, frozen_code) -> count, over every pixel where the two maps differ
|
|
transitions = Counter()
|
|
# the first frame at which the branch maps diverge at all
|
|
first_div = None
|
|
# per-frame centre-set difference, to reproduce the 8/11 headline
|
|
cpu_only_tot = frz_only_tot = 0
|
|
n_cpu = n_frz = 0
|
|
# every divergent pixel, kept for the figure (there should be very few)
|
|
sites = []
|
|
|
|
t0 = time.perf_counter()
|
|
for fid in range(N):
|
|
cf_cpu.find_clusters(data[fid])
|
|
cf_frz.find_clusters(data[fid])
|
|
b_cpu = np.asarray(cf_cpu.branch_map)
|
|
b_frz = np.asarray(cf_frz.branch_map)
|
|
|
|
cv_cpu = cf_cpu.steal_clusters(realloc_same_capacity=True)
|
|
cv_frz = cf_frz.steal_clusters(realloc_same_capacity=True)
|
|
c_cpu, c_frz = centers(cv_cpu), centers(cv_frz)
|
|
n_cpu += len(c_cpu); n_frz += len(c_frz)
|
|
a_only, b_only = only_sets(c_cpu, c_frz, tol=0)
|
|
cpu_only_tot += len(a_only); frz_only_tot += len(b_only)
|
|
|
|
diff = np.argwhere(b_cpu != b_frz)
|
|
if diff.size:
|
|
if first_div is None:
|
|
first_div = int(fid)
|
|
for iy, ix in diff:
|
|
pair = (int(b_cpu[iy, ix]), int(b_frz[iy, ix]))
|
|
transitions[pair] += 1
|
|
if len(sites) < 400:
|
|
sites.append(dict(frame=int(fid), iy=int(iy), ix=int(ix),
|
|
cpu=CODE[pair[0]], frozen=CODE[pair[1]]))
|
|
if (fid + 1) % 2000 == 0:
|
|
print(f' {fid+1}/{N} divergent pixels so far: '
|
|
f'{sum(transitions.values())}', flush=True)
|
|
|
|
dt = time.perf_counter() - t0
|
|
print(f'\nscan: {dt:.1f}s')
|
|
print(f'clusters: cpu {n_cpu} frozen {n_frz}')
|
|
print(f'centre-set difference (the 8/11 headline): '
|
|
f'cpu-only {cpu_only_tot} frozen-only {frz_only_tot}')
|
|
print(f'first frame with any branch divergence: {first_div}')
|
|
print(f'\ndivergent pixels, by (cpu branch -> frozen branch):')
|
|
for (a, b), n in transitions.most_common():
|
|
print(f' {CODE[a]:>12} -> {CODE[b]:<12} {n:>8}')
|
|
|
|
json.dump(dict(n_frames=N, n_ped=N_PED, n_sigma=N_SIGMA,
|
|
clusters=dict(cpu=n_cpu, frozen=n_frz),
|
|
centre_diff=dict(cpu_only=cpu_only_tot,
|
|
frozen_only=frz_only_tot),
|
|
first_divergent_frame=first_div,
|
|
transitions={f'{CODE[a]}->{CODE[b]}': n
|
|
for (a, b), n in transitions.items()},
|
|
sites=sites),
|
|
open(OUT / 'branch_trace.json', 'w'), indent=1)
|
|
print(f'\nwrote {OUT / "branch_trace.json"}')
|