Files
CelerisLab/research/wall_seam_pulse_20261014/analyze.py
T

232 lines
10 KiB
Python

"""One-interval, collision-free population-basis analysis; NOT an LBM solver.
No production imports/maps, CUDA, collision, force, or time-stepping benchmark.
The hypotheses below extend the current geometric transfer to a wall crossing;
they are not claims about the shipped backend or any external implementation.
Run: python research/wall_seam_pulse_20261014/analyze.py
"""
from dataclasses import dataclass
from pathlib import Path
import csv
import hashlib
import json
import resource
resource.setrlimit(resource.RLIMIT_FSIZE, (2*1024**2, 2*1024**2))
OUT = Path(__file__).resolve().parent
ROOT = OUT.parents[1]
N, NF = 16, 24
LO, HI, HALO = 4, 12, 4
CX = (0, 1, -1, 0, 0, 1, -1, 1, -1)
CY = (0, 0, 0, 1, -1, 1, -1, -1, 1)
OPP = (0, 2, 1, 4, 3, 6, 5, 8, 7)
def covered(x, y):
return LO <= x < HI and LO <= y < HI
def coarse_fluid(x, y):
return 0 <= x < N and 8 <= y < 15
def fine_fluid(x, y):
return 0 <= x < NF and 0 <= y < NF and 8 < 2+(y+.5)/2 < 15
def active(x, y):
return HALO <= x < NF-HALO and HALO <= y < NF-HALO and fine_fluid(x, y)
def parent(x, y):
return LO+(x-HALO)//2, LO+(y-HALO)//2
def fine_targets():
return [(x, y) for y in range(2, NF-2) for x in range(2, NF-2) if fine_fluid(x, y)]
@dataclass(frozen=True)
class Packet:
phase: str
slot: tuple # (q, x, y)
value: float
operation: str
parents: tuple = () # (Packet, coefficient); includes zero-valued inputs
def derive(phase, slot, operation, parents):
return Packet(phase, slot, sum(p.value*w for p, w in parents), operation, tuple(parents))
def initialize(pulse):
level, q0, x0, y0 = pulse
coarse = {(q, x, y): Packet('Gc0', (q, x, y), float(('coarse', q, x, y) == pulse), 'input')
for y in range(N) for x in range(N) if coarse_fluid(x, y) and not covered(x, y)
for q in range(9)}
fine = {}
for y in range(NF):
for x in range(NF):
# Full fine footprint: a shared parent and its four children must
# agree, including outer halo and solid cells (which hold no data).
px, py = parent(x, y)
if fine_fluid(x, y) != coarse_fluid(px, py):
raise AssertionError('parent/child physical classifications disagree')
if not fine_fluid(x, y):
continue
for q in range(9):
key = q, x, y
if active(x, y):
fine[key] = Packet('Gf0', key, float(('fine', q, x, y) == pulse), 'input')
else:
fine[key] = derive('Gf0', key, 'explode', [(coarse[q, px, py], 1)])
if level == 'coarse':
assert sum(p.value for p in coarse.values()) == 1
else:
assert active(x0, y0) and sum(p.value for p in fine.values() if p.operation == 'input') == 1
return coarse, fine
def fine_hop(inp, hop, prestream=False):
phase = ('pre_' if prestream else '')+f'F{hop}'
staged = dict(inp)
if prestream:
written = set()
# Independent prestream form: write the missing solid-source slots.
# Each (q,source) has exactly one fluid destination in a pull scheme.
for x, y in fine_targets():
for q in range(9):
sx, sy = x-CX[q], y-CY[q]
if not fine_fluid(sx, sy) and (OPP[q], x, y) in inp:
key = q, sx, sy
assert key not in written
written.add(key)
staged[key] = derive(phase+'_BCscratch', key, 'prestream_BB', [(inp[OPP[q], x, y], 1)])
out = {}
for x, y in fine_targets():
for q in range(9):
key, source = (q, x, y), (q, x-CX[q], y-CY[q])
if prestream or fine_fluid(source[1], source[2]):
donor, op = staged.get(source), 'pull'
else:
donor, op = inp.get((OPP[q], x, y)), 'local_BB'
if donor is not None:
assert key not in out
out[key] = derive(phase, key, op, [(donor, 1)])
# Missing slots are explicitly invalid, not zero; outer halo is not output.
return out
def links():
result = {}
for y in range(N):
for x in range(N):
if not coarse_fluid(x, y) or covered(x, y):
continue
for q in range(1, 9):
if LO <= x+.5-CX[q]/2 <= HI and LO <= y+.5-CY[q]/2 <= HI:
a, b = HALO+2*(x-LO), HALO+2*(y-LO)
result[q, x, y] = [(q, a+dx, b+dy) for dy in (0, 1) for dx in (0, 1)]
return result
LINKS = links()
HYPOTHESES = ('coarse_BB_then_coalesce', 'coalesce_then_coarse_BB', 'omit_solid_source_links')
def coarse_arrivals(gc, f2, hypothesis):
out = {}
for q, x, y in gc:
key = q, x, y
sx, sy = (x-CX[q]) % N, (y-CY[q]) % N
wall = not coarse_fluid(sx, sy)
transfer = key in LINKS
if hypothesis == 'omit_solid_source_links' and wall:
transfer = False
# Functional final assignment models the specified ordered overrides.
# Every destination has one final writer; this alone does not prove
# that one initial packet has only one physical owner globally.
if transfer and (not wall or hypothesis == 'coarse_BB_then_coalesce'):
parents = [(f2[child], .25) for child in LINKS[key]]
node = derive('Fc1', key, 'coalescence', parents)
elif wall:
node = derive('Fc1', key, 'coarse_BB', [(gc[OPP[q], x, y], 1)])
else:
node = derive('Fc1', key, 'coarse_pull', [(gc[q, sx, sy], 1)])
assert key not in out
out[key] = node
return out
def trace(p, contribution=1.):
return dict(phase=p.phase, slot=list(p.slot), operation=p.operation,
value=p.value, downstream_weight=contribution,
parents=[trace(parent, contribution*w) for parent, w in p.parents if parent.value])
def main():
cases = [('outer_coarse_q7', ('coarse', 7, 2, 8)),
('adjacent_coarse_q7', ('coarse', 7, 3, 8)),
('active_fine_q7', ('fine', 7, 4, 12))]
rows, results = [], []
for name, pulse in cases:
gc, gf = initialize(pulse)
f1 = fine_hop(gf, 1)
f2 = fine_hop(f1, 2)
pre1 = fine_hop(gf, 1, prestream=True)
pre2 = fine_hop(pre1, 2, prestream=True)
assert {k: p.value for k, p in f1.items()} == {k: p.value for k, p in pre1.items()}
assert {k: p.value for k, p in f2.items()} == {k: p.value for k, p in pre2.items()}
assert all(child in f2 for children in LINKS.values() for child in children)
initial = 1. if pulse[0] == 'coarse' else .25
fine_mass = .25*sum(p.value for (q, x, y), p in f2.items() if active(x, y))
for phase in (gc, gf, f1, f2):
for node in phase.values():
if node.value:
rows.append((name, 'shared', node.phase, *node.slot, node.operation, node.value,
';'.join(f'{p.phase}:{p.slot}*{w}' for p, w in node.parents)))
for hypothesis in HYPOTHESES:
out = coarse_arrivals(gc, f2, hypothesis)
coarse_mass = sum(p.value for p in out.values())
selected = out[8, 3, 8]
item = dict(case=name, pulse=pulse, hypothesis=hypothesis,
initial_leaf_weight=initial, coarse_leaf_weight=coarse_mass,
fine_leaf_weight=fine_mass, total_leaf_weight=coarse_mass+fine_mass,
residual=coarse_mass+fine_mass-initial,
inspected_slot=[8, 3, 8], selected_writer=selected.operation,
inspected_value=selected.value,
final_nonzero_traces=[trace(p) for p in out.values() if p.value])
results.append(item)
for node in out.values():
if node.value:
rows.append((name, hypothesis, node.phase, *node.slot, node.operation, node.value,
';'.join(f'{p.phase}:{p.slot}*{w}' for p, w in node.parents)))
print(f'{name:20s} {hypothesis:27s} initial={initial:g} coarse={coarse_mass:g} '
f'fine={fine_mass:g} residual={item["residual"]:+g}; '
f'K(3,8),q8={selected.value:g} ({selected.operation})')
sources = ['src/CelerisLab/lbm/refinement.py', 'src/CelerisLab/lbm/refinement_stepper.py',
'src/CelerisLab/lbm/kernels/refinement.cu',
'src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh']
report = dict(scope='Independent single-interval linear packet/basis analysis; no collision or solver execution',
geometry=dict(coarse=[16, 16], patch=[4, 4, 12, 12], fine=[24, 24],
fine_origin=[2, 2], coarse_dx_dt=[1, 1], fine_dx_dt=[.5, .5], walls_Y=[8, 15]),
assumptions=['fluid active populations pass unchanged between fine hops (identity transfer probe)',
'fine fluid inner ghosts reflect, never collide; outer halo output is invalid',
'solid destinations omitted; no filtering by coarse source in geometric LINKS',
'coalescence is equal-weight average of four children of each coarse target',
'results test these proposed extensions, not current production support or external libraries'],
prestream_vs_postpull='Exact values and validity match on both hops for all three probes',
source_files_read_only={p: hashlib.sha256((ROOT/p).read_bytes()).hexdigest() for p in sources},
script_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), results=results)
(OUT/'results.json').write_text(json.dumps(report, indent=2)+'\n')
with (OUT/'sources.csv').open('w', newline='') as stream:
writer = csv.writer(stream)
writer.writerow(('case', 'hypothesis', 'phase', 'q', 'x', 'y', 'operation', 'value', 'immediate_sources'))
writer.writerows(rows)
print('Prestream/postpull equivalence, valid coalescence reads, and unique destination writes: PASS')
print(f'peak_rss_MiB={resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024:.3f}')
if __name__ == '__main__':
main()