"""Host-only experimental union event contract; no solver or runtime admission. Consumes existing level-wide union geometry, never its legacy transfer tables. The frozen 707afe6 rectangle oracle remains independent and unchanged. Topology acceptance proves packet accounting, not interpolation accuracy or GPU support. """ from types import SimpleNamespace import numpy as np import spacetime_reference as ref class UnresolvedTopology(ValueError): """An experimental coupling limitation, not a production geometry rejection.""" def __init__(self, code, **detail): self.code, self.detail = code, detail super().__init__(f"experimental space-time topology unresolved: {code}: {detail}") def owned_slots(layout, directions, cells): """Map dense logical reads/writes to existing owner-core storage, not halos.""" cells = np.asarray(cells) if not np.issubdtype(cells.dtype, np.integer) or np.any((cells < 0) | (cells >= np.prod(layout.shape))): raise ValueError('invalid dense cell index') owner = layout.owner.ravel()[cells] if np.any(owner == layout.hole): raise UnresolvedTopology('missing_block_owner', cells=cells[owner == layout.hole].tolist()) return layout.slot(owner, directions) def _first_entry(covered, origin, child, c): """First continuous union entry of one characteristic, in exact quarter times. Probe the cells immediately before/after each crossed grid line using integer eighth-cell coordinates. Return only actual exterior faces of the UNION. """ start4 = 4*origin + 2*child + 1 for t4 in (1, 3): before = (2*start4 + c*(2*t4-1))//8 after = (2*start4 + c*(2*t4+1))//8 if not covered[tuple(before)] and covered[tuple(after)]: axes = [] for j in range(2): if before[j] != after[j]: neighbor = after.copy(); neighbor[j] = before[j] if not covered[tuple(neighbor)]: axes.append(j) return t4, axes return None def _crossing_time(covered, origin, position, c, axes): """Fig.11 convex T uses the last plane; Fig.13 concave T uses the first. Both use the same KKT formulation on different boundary geometry. DOI/primary PDF: https://www.osti.gov/servlets/purl/1226215, §3.5.1. """ times=[(origin[j]+int(c[j]>0)-position[j])/c[j] for j in axes] if len(axes)==1: return times[0] sides=[bool(covered[tuple(origin+np.eye(2,dtype=int)[j]*c[j])]) for j in axes] if sides[0]!=sides[1]: raise UnresolvedTopology('mixed_corner_support',source_yx=origin.tolist()) return min(times) if all(sides) else max(times) class UnionPlan: """Two-level, level-wide square geometry; all coordinates are level-local. Assumes a complete parent domain; does not validate nested parent coverage. ``pair`` is existing FixedPatchSet2D geometry, even for one rectangle. Scalar window origins and nested state coverage need their own contract; they are explicitly unadmitted here. No global rectangle-gap rule is imposed. """ phase = 'arrival_f' runtime_supported = False interpolation_accuracy = 'unqualified_for_new_unions' def __init__(self, pair): ny, nx = pair.coarse_shape self.origin = tuple(pair.fine_origin) if ny != nx or pair.fine_shape != (2*ny, 2*nx) or self.origin != (0., 0.): raise UnresolvedTopology('requires_level_wide_square_pair', coarse_shape=pair.coarse_shape, fine_shape=pair.fine_shape, origin=self.origin) self.n, self.nf = nx, 2*nx covered = np.array(pair.covered, dtype=bool, copy=True) active = np.kron(covered, np.ones((2, 2), bool)) if not np.array_equal(active, pair.fine_kind == 0): raise UnresolvedTopology('non_pair_active_coverage') self.maps = SimpleNamespace(coarse_active=~covered, coarse_covered=covered, fine_active=active) self.outgoing, transit = [[], []], [] # Guzik et al. §3.5.4, Fig.19 (https://www.osti.gov/servlets/purl/1226215): # retain substep-0 outgoing G at source, then fill the substep-1 ghost. # Rows are (q, fine source, next ghost); no covered coarse receiver credit. for q in range(1, 9): c = np.array((ref.cpu.CY[q], ref.cpu.CX[q])) boundary = active & ~np.roll(active, tuple(-c), axis=(0, 1)) for y, x in zip(*np.nonzero(boundary)): p = np.array((y, x)) for m in (0, 1): target = (p+(2-m)*c)//2 if covered[tuple(target)]: if m != 0 or not active[tuple(p+2*c)]: raise UnresolvedTopology('unsupported_transit', q=q, substep=m, source_yx=p.tolist()) ghost=p+c transit.append((q,int(y*self.nf+x),int(ghost[0]*self.nf+ghost[1]))) else: self.outgoing[m].append((q, int(y*self.nf+x), int(target[0]*nx+target[1]))) self.outgoing = [np.array(x, dtype=np.int64).reshape(-1, 3) for x in self.outgoing] self.transit = np.array(transit,dtype=np.int64).reshape(-1,3) self.groups = [] for iy, ix in zip(*np.nonzero(~covered)): origin = np.array((iy, ix)) # Two-cell production margins make all nearby probes in bounds. if not covered[max(0,iy-1):iy+2,max(0,ix-1):ix+2].any(): continue for q in range(1, 9): c = np.array((ref.cpu.CY[q], ref.cpu.CX[q])) children = [np.array((dy, dx)) for dy in (0, 1) for dx in (0, 1)] starts = [2*origin+child for child in children] events = [] for p in starts: event = None for m in (0, 1): old, new = p+m*c, p+(m+1)*c if not active[tuple(old)] and active[tuple(new)]: event = (m, int(old[0]*self.nf+old[1])); break events.append(event) if all(e is None for e in events): continue entries = [_first_entry(covered, origin, child, c) for child in children] axes = sorted({j for entry in entries if entry is not None for j in entry[1]}) if not axes: raise UnresolvedTopology('missing_exterior_support', source_yx=origin.tolist(), q=q) points = [] for p, entry, event in zip(starts, entries, events): position = (p+.5)/2 time = _crossing_time(covered,origin,position,c,axes) if entry is not None and (4*time != entry[0] or event is None): raise UnresolvedTopology('nonconvex_entry_support', source_yx=origin.tolist(), q=q, quarter_time=entry[0], support_quarter_time=float(4*time)) offset = position+c*time-(origin+.5+c*.5) points.append((float(offset[1]), float(offset[0]), float(time-.5)) if len(axes)==2 else (float(offset[1-axes[0]]), float(time-.5))) kind = 'corner' if len(axes)==2 else 'face'; points = tuple(points) samples, _, evaluation, coefficients = ref.interpolation(kind, points) indices = [] for row in samples: shift = np.array((row[1],row[0]), int) if kind=='corner' else np.eye(2,dtype=int)[1-axes[0]]*int(row[0]) pos = (origin+shift) % nx; indices.append(int(pos[0]*nx+pos[1])) target = (origin+c) % nx self.groups.append(ref.Group(q,int(iy*nx+ix),int(target[0]*nx+target[1]),kind, points,np.array(indices),evaluation@coefficients,tuple(events))) receivers = {(int(q),int(target)) for rows in self.outgoing for q,_,target in rows} receivers |= {(g.q,g.receiver) for g in self.groups if None in g.events} self.receivers = np.array(sorted(receivers),dtype=np.int64).reshape(-1,2) self.proof = validate_ownership(self) def validate_ownership(plan): """Independent mask-based event coverage and four-quarter receiver accounting.""" n, nf, active = plan.n, plan.nf, plan.maps.fine_active actual = [(m,g.q,slot) for g in plan.groups for e in g.events if e is not None for m,slot in [e]] expected_transit={(q,int(y*nf+x),int((y+ref.cpu.CY[q])*nf+x+ref.cpu.CX[q])) for q in range(1,9) for y,x in zip(*np.nonzero(active)) if not active[y+ref.cpu.CY[q],x+ref.cpu.CX[q]] and active[y+2*ref.cpu.CY[q],x+2*ref.cpu.CX[q]]} routes=[tuple(map(int,row)) for row in plan.transit] if len(routes)!=len(set(routes)) or set(routes)!=expected_transit: raise ValueError('transit provenance/uniqueness/coverage failed') actual += [(1,q,ghost) for q,source,ghost in routes] expected = {(m,q,int((y-ref.cpu.CY[q])*nf+x-ref.cpu.CX[q])) for m in (0,1) for q in range(1,9) for y,x in zip(*np.nonzero(active)) if not active[y-ref.cpu.CY[q],x-ref.cpu.CX[q]]} if len(actual)!=len(set(actual)) or set(actual)!=expected: raise ValueError('incoming writer uniqueness/coverage failed') packets = {tuple(map(int,row)):[] for row in plan.receivers} if len(packets)!=len(plan.receivers): raise ValueError('duplicate coarse receiver') anchors = set() for i,g in enumerate(plan.groups): if not 1<=g.q<9 or not 0<=g.source