Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa8f6aca1 | ||
|
|
70c83b7233 | ||
|
|
be8e6d691d | ||
|
|
1f64f7eb11 | ||
|
|
caa573f978 | ||
|
|
1e188e7419 | ||
|
|
02bde0f751 | ||
|
|
335624c27b | ||
|
|
9748ef91ef | ||
|
|
5fb95f5f4d | ||
|
|
46100e1d53 | ||
|
|
92b8afeeed |
No files matched your search
@@ -295,3 +295,256 @@ is not placement robustness. Two resolutions show a trend, not an order.
|
||||
- Merge note: native 92b8afe adds a `scaled_n256` receipt block. When the
|
||||
two are merged, keep both blocks and make `--flow shear_wave` and
|
||||
`scaled_n256` mutually exclusive. Not merged here.
|
||||
|
||||
### Scaled shear diagnostic (opt-in `scaled_shear_n256`)
|
||||
|
||||
Runner: `run_case.py --flow shear_wave --diagnostic scaled_shear_n256 --re 100`
|
||||
with one of these four cases:
|
||||
- `--n 256 --depth 1` (U256)
|
||||
- `--n 512 --depth 1` (U512)
|
||||
- `--n 256 --depth 2 --explosion constant` (constant R256)
|
||||
- `--n 256 --depth 2 --explosion projected_linear` (projected R256)
|
||||
|
||||
Everything physical is unchanged from N128:
|
||||
- `rectangles(256,2)` covers the same physical box;
|
||||
- same t* schedule (16N root steps);
|
||||
- `nu = U*N/100`;
|
||||
- same `eps(N)`, limits and floors.
|
||||
|
||||
`scaled_n256` (Taylor–Green, from native 92b8afe) and `scaled_shear_n256`
|
||||
each require their own flow, so they are mutually exclusive. N512 is
|
||||
accepted only through one of the two diagnostics. The shear diagnostic
|
||||
takes no early-pressure trace.
|
||||
|
||||
Assessment:
|
||||
|
||||
python assess_shear_wave.py U256.json U512.json R256_constant.json R256_projected.json \
|
||||
--diagnostic scaled_shear_n256 --reference-files <four de5b0cc N128 receipts> \
|
||||
--output report.json --expected-revision <sha>
|
||||
|
||||
- The scaled N256/512 case, build and workload checks come from the
|
||||
92b8afe assessor. Its TG-only diagnostic identity and early-sampling
|
||||
checks are not used. Instead every record must carry
|
||||
`diagnostic=scaled_shear_n256`, and N128 records must carry none.
|
||||
- The reference N128 receipts are hash-pinned to the audited de5b0cc
|
||||
evidence (commit cba6bca). They are never relabelled as fresh controls.
|
||||
- The report always includes, for each case:
|
||||
- the raw signed rate bias `rate/exact - 1` and the interval errors;
|
||||
- each refined run's signed excess over *each* uniform control (rate
|
||||
bias, residual, raw pressure rms);
|
||||
- the N128→N256 signed changes and ratios. The ratio is null when the
|
||||
N128 value is zero.
|
||||
- These are descriptive. Passing or failing another floor-controlled
|
||||
screen is not the conclusion, and a two-resolution ratio is not a
|
||||
convergence order.
|
||||
- Portability: the receipt's recomputed `eps = |k|^2` is compared within
|
||||
4 ULPs (`close_ulps`), not by exact equality. A cross-runtime replay
|
||||
differed by one ULP: 0.012047856934923534 on Horsea vs
|
||||
0.012047856934923532 elsewhere. Source and file hashes, case identity,
|
||||
the other metadata and every scientific threshold stay exact.
|
||||
|
||||
### Nested shear preset (opt-in `nested_shear_n128`)
|
||||
|
||||
A fixed three-level, two-region preset, defined in `shear_nested.py`. The
|
||||
wave, phase, Re100/U=0.03, initialization, five sample times, eps(root N),
|
||||
limits and floors are the same as before.
|
||||
|
||||
- Refinements, given as parent-frame `(x0, y0, x1, y1)` rectangles on
|
||||
level-wide, zero-origin unions:
|
||||
- L0→L1: `(16,16,60,112)`, `(68,24,112,120)`
|
||||
- L1→L2: `(44,44,108,212)`, `(148,60,212,228)`
|
||||
- Leaves `[7936, 12288, 86016]`, hops `[2048, 4096, 8192]`, work
|
||||
771227648 per refined case; 3723493376 for all four cases.
|
||||
- Level ν `[.0384, .0768, .1536]`, τ `[.6152, .7304, .9608]`.
|
||||
|
||||
Cases (`run_case.py --flow shear_wave --diagnostic nested_shear_n128 --re 100`):
|
||||
- `--n 128 --depth 1`
|
||||
- `--n 512 --depth 1` — the finest control is 4N, not 2N
|
||||
- `--n 128 --depth 3`
|
||||
- `--n 128 --depth 3 --explosion projected_linear [--final-snapshot]`
|
||||
|
||||
`--final-snapshot` is allowed only for the projected depth-3 case. It writes
|
||||
`<output>.final_macro.npz`, containing:
|
||||
- ACTIVE leaves packed as `level`/`i`/`j`;
|
||||
- `rho`, `ux`, `uy` from the FP64 physical populations at the final root
|
||||
synchronization;
|
||||
- embedded metadata: exact time, phase, mode and config.
|
||||
|
||||
It contains no populations, restart state or time series. Its SHA-256 and
|
||||
size go into the receipt. A file over 8 MiB is removed and reported, never
|
||||
kept. All old paths stay scalar-only.
|
||||
|
||||
Assessment: `assess_shear_wave.py U128.json U512.json R128c.json R128p.json
|
||||
--diagnostic nested_shear_n128 --output report.json --expected-revision <sha>`.
|
||||
- It reuses the case-agnostic provenance checks from the TG assessor, plus
|
||||
exact nested scales, builds, patch, workload, metadata and the pinned
|
||||
`shear_nested.py` hash.
|
||||
- The physics is `shear_wave.assess` generalized only in its depth-3 / 4N
|
||||
shape contract; the metrics are unchanged.
|
||||
- `shear_wave.py` is byte-identical (81d5c8ee…), so historical receipt
|
||||
identities and verdicts are untouched.
|
||||
- This preset changes placement and refined volume as well as depth. It
|
||||
gives no isolated cost of an extra level and no resolution trend.
|
||||
|
||||
### Depth-2 twin of the nested preset (opt-in `twin_shear_n128`)
|
||||
|
||||
`shear_twin.py` uses the same two L0→L1 boxes as `shear_nested.py`, with no
|
||||
L2. Leaves are `[7936, 33792]`, hops `[2048, 4096]`, and work is 154664960 per
|
||||
refined case (611319808 for all four).
|
||||
|
||||
The campaign is four fresh runs:
|
||||
`run_case.py --flow shear_wave --diagnostic twin_shear_n128 --re 100` with
|
||||
- `--n 128 --depth 1`
|
||||
- `--n 256 --depth 1` (the finest control is 2N)
|
||||
- `--n 128 --depth 2`
|
||||
- `--n 128 --depth 2 --explosion projected_linear`
|
||||
|
||||
There is no snapshot option. Older exact controls serve only as consistency
|
||||
checks; they are not reused as references.
|
||||
|
||||
Assessment uses the same preset contract as the nested preset, through
|
||||
`assess_shear_wave.py ... --diagnostic twin_shear_n128`:
|
||||
- the physics is the unchanged depth-2 / 2N `shear_wave.assess`;
|
||||
- receipts must pin `shear_twin.py`, `shear_nested.py` and `shear_wave.py`;
|
||||
- `shear_nested.py` stays byte-identical (fa6cfe87…), so the 335624c nested
|
||||
receipts still validate.
|
||||
|
||||
Labels:
|
||||
- *Twin vs nested* is the effect of adding this nested L2 refinement.
|
||||
Removing L2 also changes the finest spacing and refined volume, so it does
|
||||
not isolate abstract depth.
|
||||
- *Twin vs the single-box depth-2 runs* is a placement/geometry contrast at
|
||||
equal depth.
|
||||
- Neither contrast is additive by construction.
|
||||
- The thin-band/acoustic explanation for the nested L1 pressure is a
|
||||
hypothesis, not established.
|
||||
|
||||
### Signed interface-probe traces (opt-in `--probe-trace`, mechanism diagnostic)
|
||||
|
||||
`shear_probes.py` (pinned) uses native's validated 24-target layout through
|
||||
box 1, in root-128 physical coordinates:
|
||||
- a horizontal line y = 56, crossing the vertical faces x = 16 and 22;
|
||||
- a vertical line x = 38, crossing the horizontal faces y = 16 and 22;
|
||||
- normal coordinates 12.5, 14.5, 15.5, 16.25, 17.25, 19.25, 20.75, 21.75,
|
||||
22.125, 23.125, 24.125, 26.125.
|
||||
|
||||
Each target maps to the nearest ACTIVE leaf center. The deterministic tie
|
||||
rule is: distance, then finer level, then smaller j, then smaller i. This
|
||||
gives 24 unique cells in every case, and nested has 3 L0 + 5 L1 + 4 L2 per
|
||||
transect. Maximum displacement from target to center is 0.625 root cells in
|
||||
U128 (0.177 in U512).
|
||||
|
||||
Allowed cases:
|
||||
- nested U128, nested U512, nested projected depth 3;
|
||||
- twin projected depth 2.
|
||||
|
||||
The option can't be combined with `--final-snapshot`.
|
||||
|
||||
Sampling:
|
||||
- The receipt stores each probe's leaf level, `i`/`j`, actual cell center
|
||||
and displacement from its target, all in root-physical units (N=128).
|
||||
- Grids differ between cases, so centers are *not* collocated. Analytic
|
||||
fields are evaluated at each actual center (`shear_probes.residuals`), and
|
||||
comparisons across cases are between residuals at nearby but distinct
|
||||
centers.
|
||||
- Times are root-physical steps 0–128 and 1920–2048. U512 is sampled every 4
|
||||
lattice steps to match.
|
||||
- Each sample stores `rho`, `ux`, `uy` from the FP64 physical populations,
|
||||
POST-collision G at root synchronization only. No substep content is
|
||||
observed, and grid-scale alternation can alias.
|
||||
|
||||
What's kept and enforced:
|
||||
- The five qualification samples stay unchanged.
|
||||
- No field dumps. The trace is validated for exact schedule, finiteness and
|
||||
a 600 kB cap. If invalid it is dropped and an error is recorded, keeping
|
||||
the scalar receipt.
|
||||
- There are no new pass gates; this is a mechanism diagnostic only.
|
||||
|
||||
### Single x-gap-width contrast (`wide_x_shear_n128`)
|
||||
|
||||
This opt-in admits only Re100/N128/projected depth3 with `--probe-trace`.
|
||||
The outer boxes, three grid spacings, viscosities, initialization, five main
|
||||
sample times and trace windows are unchanged. L1→L2 parent-frame rectangles
|
||||
are `[(56,44,96,212),(160,60,200,228)]`: both x-normal middle gaps widen from
|
||||
6 to 12 root cells, while y gaps remain 6. Active leaves are
|
||||
`[7936,20352,53760]`, hops `[2048,4096,8192]`, work 540,016,640 active updates.
|
||||
This also moves the L2 x faces and reduces its volume; it is not a pure
|
||||
travel-distance experiment. No new qualification matrix, assessor, threshold
|
||||
or snapshot is added. Existing scalar screens and all failures stay visible.
|
||||
|
||||
The same 24 targets and actual-center metadata are retained. The horizontal
|
||||
probes stop before the new inner face x = 28: they do not bracket that interface
|
||||
or sample its fine side. The first 8 horizontal L0/L1 probes keep identical
|
||||
centers, and the y-normal x = 38 transect is unchanged. Their temporal residuals
|
||||
can support a descriptive timing contrast, but cannot establish a crossing
|
||||
or reflection coefficient. Changed-level probes must not be treated as
|
||||
collocated samples. `rho-1` remains raw density deviation; do not substitute
|
||||
the 24-probe mean for the global pressure gauge.
|
||||
|
||||
### POST-G initialization counterfactual (`postg_shear_n128`)
|
||||
|
||||
Exactly three fresh Re100 cases are admitted: U128, U512, and projected R128
|
||||
depth 3 with the original nested rectangles. Each requires `--flow shear_wave
|
||||
--diagnostic postg_shear_n128 --probe-trace`; snapshots and early-pressure mode
|
||||
are excluded. Geometry, physical velocity/density, viscosities, collisions,
|
||||
five main times and the 24 signed probes/windows are unchanged. Total work is
|
||||
2,952,265,728 active updates, with 15 main rows, 774 trace rows and five builds.
|
||||
|
||||
Only initialization changes. At level spacing h, k_level = h k and
|
||||
A_i = 3 w_i U cos(theta) (c_i dot e) (c_i dot k_level). Initialize physical
|
||||
FP64 **POST G = feq - (tau - 1) A**, then use the existing shifted FP32 codec.
|
||||
This is the first-gradient, low-Mach Chapman-Enskog approximation, not an
|
||||
exact discrete eigenstate. The PRE coefficient is -tau; the physical viscous
|
||||
stress coefficient tau - 1/2 must not replace the raw POST-G coefficient.
|
||||
The correction has zero mass/momentum before encoding; FP32 introduces normal
|
||||
roundoff. U512 uses its native k and tau, matching nested L2 physical spacing.
|
||||
|
||||
Receipts pin the new initializer module and identify `postg_first_gradient_v1`,
|
||||
with `qualification_eligible=false`. Existing scalar screens remain visible
|
||||
as descriptive outcomes, including failures; no new pass gate or assessor is
|
||||
introduced. Historical assessors reject the new diagnostic and initialization
|
||||
identities. Compare raw rows and signed traces with the retained equilibrium
|
||||
receipts as a startup-state counterfactual. The host first-interval pressure
|
||||
RMS reduction is not a prediction of long-time improvement, and individual
|
||||
probe errors can increase. All equilibrium paths and historical verdicts stay
|
||||
unchanged.
|
||||
|
||||
### Short square orientation diagnostic (`axis_square_shear_n128`)
|
||||
|
||||
Four fresh cases only: U128, U256, constant R128 and projected R128 depth 2.
|
||||
Use `--flow shear_wave --diagnostic axis_square_shear_n128 --probe-trace`.
|
||||
The supported union is `[[(32,32,96,96)]]`; a full-height periodic fine strip
|
||||
is not supported by the unchanged production geometry. All four cases use
|
||||
POST G = feq - (tau - 1) A, mode (2,0), polarization (0,1), phase pi/4,
|
||||
U=.03 and Re100. Root/fine nu are .0384/.0768 and tau are .6152/.7304.
|
||||
This first-gradient low-Mach initialization is not an exact discrete mode.
|
||||
|
||||
Stop at root step 8: two global rows at 0/8, nine local rows at 0..8;
|
||||
U256 advances two native steps per root step. Refined leaves are [12288,16384],
|
||||
hops [8,16], work 360,448 per refined case. Four-case work is 1,900,544 ACTIVE
|
||||
updates, six build sidecars and 720 native cell-time samples. Existing runtime,
|
||||
build/source guards and receipt/trace caps apply. No snapshots or late fit.
|
||||
`screening_pass=null`, `qualification_eligible=false`: exit zero means valid
|
||||
finite completion, with no physical pass threshold or legacy assessor.
|
||||
|
||||
Eight root-unit-cell groups (i,j): inner L/R/B/T = (32,64)/(95,63)/(64,32)/(63,95),
|
||||
outer L/R/B/T = (31,64)/(96,63)/(64,31)/(63,96). Refined inner groups contain four
|
||||
fine cells, outer groups one coarse cell; U128 uses one and U256 four per group.
|
||||
Retain raw rho/ux/uy, native indices, physical centers/areas and parity. Group
|
||||
residuals are signed means of rho-1 and rho*u-u_exact, evaluated at each actual
|
||||
center; normalization uses that group's mean analytic d(uy)/dx. Only inner
|
||||
L/B and R/T pairs have exactly opposite equal-magnitude gradients. Controls
|
||||
have their own native quadrature. Probe density is ungauged; global pressure
|
||||
RMS uses one domain gauge.
|
||||
|
||||
Actual-table local corridors match tangentially extended straight faces beyond
|
||||
22.5 root cells. The conservative paired-map radius is 2.5 root cells per root
|
||||
step, so these selected groups through step 8 are locally straight-face
|
||||
equivalent. This is not a claim about global corner errors. For projected
|
||||
explosion with actual missing-normal-gradient fallback, the stationary
|
||||
linearized affine reference after the first complete root interval predicts
|
||||
zero conserved-moment error on vertical faces. At the
|
||||
bottom/top inner fine quartets its mean delta jx / d(uy)/dx is
|
||||
-/+ 1/(576*tau_f), with opposite adjacent-coarse redistribution.
|
||||
Keep raw values, step-zero rounding and fresh controls: sinusoidal curvature
|
||||
and finite-Mach effects prevent treating this coefficient as an exact gate.
|
||||
@@ -1,6 +1,7 @@
|
||||
"""New-source four-case repeat of 11901a7; frozen oracle and gates are unchanged."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
@@ -8,16 +9,25 @@ from pathlib import Path
|
||||
from assess import BASELINE_FACTOR, FLOORS
|
||||
from oracle import LIMITS, TIMES, U, rectangles
|
||||
from run_case import (INITIALIZATION, sample_steps, NUMERICAL_REVISION, SOURCE_SHA256,
|
||||
BUILD_CAPTURE_SHA256, REFERENCE_HARNESS)
|
||||
BUILD_CAPTURE_SHA256, REFERENCE_HARNESS, SCALED_DIAGNOSTIC)
|
||||
|
||||
# Host-generated snapshots of a135252 at the frozen two local configurations.
|
||||
# Host-generated snapshots of a135252 at the frozen local configurations.
|
||||
# These describe expected inputs; actual sidecars are captured before cleanup.
|
||||
BUILD_SOURCES = {
|
||||
128: "803177416b7599ab89cc5afd35cf4a28a673b2f4bb8e8a6a1abc0b6d77610873",
|
||||
256: "17ec5e60f87b840d3d13b8778bc8a3aecf485469c5a061af46c5cc19b73396cf",
|
||||
512: "4e85e38098858ec95c137d1b1e96846a58d9cf3cc242b349a3ad7c8b8e3ce0aa",
|
||||
}
|
||||
CASES = ((100, 128, 1, "constant"), (100, 256, 1, "constant"),
|
||||
(100, 128, 2, "constant"), (100, 128, 2, "projected_linear"))
|
||||
REFERENCE_REVISION = "2cf9853952e52f59bea63cf5c1d3a1cd76e53eed"
|
||||
# Audited 2cf9853 scalar receipts, archived at evidence commit 90849a55cad4a0da9b72747da7bfe6eb75962a44.
|
||||
REFERENCE_RECEIPTS = {
|
||||
"uniform128": "724efafc267eaa59dcc59e8d4c16177b67f6c6a08fd2732a481833237bb19cee",
|
||||
"uniform256": "3c312a99ebf747a135674dd481b77455a3d2c9a406ca388f30524b37dad9bbe9",
|
||||
"constant128": "2ee28efd1e81b2e28b7902b6c4f95bb2c1ea4e5f0ec484638fa5ae83dca34bba",
|
||||
"projected128": "ee67b1d4d90a048c0d3d1a16447c6eab3d89c94d151f791306e373f96dd8b963",
|
||||
}
|
||||
NUMERICS = dict(lattice_model="D2Q9", collision="SRT", store_precision="FP32",
|
||||
ddf_shifting=True, streaming="double_buffer", les_enabled=False,
|
||||
boundary="periodic", block_shape=[8, 8],
|
||||
@@ -73,7 +83,11 @@ def valid_builds(record, n, depth):
|
||||
return True
|
||||
|
||||
|
||||
def assess(records, expected_revision=None):
|
||||
def assess(records, expected_revision=None, *, diagnostic=None):
|
||||
if diagnostic not in (None, SCALED_DIAGNOSTIC):
|
||||
raise ValueError("Unknown diagnostic")
|
||||
base_n = 256 if diagnostic else 128
|
||||
cases = tuple((re, n * base_n // 128, depth, scheme) for re, n, depth, scheme in CASES)
|
||||
indexed, duplicates = {}, []
|
||||
for record in records:
|
||||
key = tuple(record.get(k) for k in ("re_L", "n", "depth", "explosion"))
|
||||
@@ -81,9 +95,11 @@ def assess(records, expected_revision=None):
|
||||
duplicates.append(str(key))
|
||||
else:
|
||||
indexed[key] = record
|
||||
missing = [str(k) for k in CASES if k not in indexed]
|
||||
checks = dict(complete_matrix=not missing, no_extra_cases=set(indexed) <= set(CASES),
|
||||
missing = [str(k) for k in cases if k not in indexed]
|
||||
checks = dict(complete_matrix=not missing, no_extra_cases=set(indexed) <= set(cases),
|
||||
no_duplicate_cases=not duplicates, finite_receipts=finite_tree(records))
|
||||
if diagnostic or any("diagnostic" in r for r in records):
|
||||
checks["diagnostic_identity"] = all(r.get("diagnostic") == diagnostic for r in records)
|
||||
sources = [r.get("source", {}) for r in records]
|
||||
checks["same_source_and_harness"] = bool(sources) and len({
|
||||
json.dumps(s, sort_keys=True) for s in sources}) == 1
|
||||
@@ -106,9 +122,11 @@ def assess(records, expected_revision=None):
|
||||
) and len(set(devices)) == 1
|
||||
enabled = [r.get("early_pressure", {}).get("enabled") for r in records]
|
||||
checks["same_early_sampling"] = bool(enabled) and all(type(v) is bool for v in enabled) and len(set(enabled)) == 1
|
||||
if diagnostic:
|
||||
checks["diagnostic_early_sampling"] = all(v is True for v in enabled)
|
||||
shared_valid = all(checks.values())
|
||||
complete, case_valid, early = {}, {}, []
|
||||
for key in CASES:
|
||||
for key in cases:
|
||||
if key not in indexed:
|
||||
continue
|
||||
r = indexed[key]
|
||||
@@ -135,7 +153,7 @@ def assess(records, expected_revision=None):
|
||||
(("module", "__init__.py"), ("compiler", "cuda/compiler_v2.py"), ("kernel_root", "lbm/kernels")))
|
||||
expected_patch = [] if depth == 1 else rectangles(n, depth)
|
||||
checks[label + " patch"] = json.dumps(r.get("refinements")) == json.dumps(expected_patch)
|
||||
cells = [n*n] if depth == 1 else [8704, 30720]
|
||||
cells = [n*n] if depth == 1 else [17*n*n//32, 15*n*n//8]
|
||||
hops = [16*n * 2**l for l in range(depth)]
|
||||
work = r.get("workload", {})
|
||||
checks[label + " workload"] = (
|
||||
@@ -168,19 +186,22 @@ def assess(records, expected_revision=None):
|
||||
|
||||
comparisons, scheme_results = [], {}
|
||||
for scheme in ("constant", "projected_linear"):
|
||||
key = (100, 128, 2, scheme)
|
||||
key = (100, base_n, 2, scheme)
|
||||
for metric, floor in FLOORS.items():
|
||||
amr, coarse, fine = (worst(k, metric) for k in (key, CASES[0], CASES[1]))
|
||||
amr, coarse, fine = (worst(k, metric) for k in (key, cases[0], cases[1]))
|
||||
budget = None if coarse is None else max(BASELINE_FACTOR * coarse, floor)
|
||||
budget = budget if finite(budget) else None
|
||||
ratio = None if amr is None or fine is None else amr / max(fine, 1e-30)
|
||||
passed = amr is not None and budget is not None and amr <= budget
|
||||
checks[f"baseline {scheme}/{metric}"] = passed
|
||||
comparisons.append(dict(explosion=scheme, metric=metric, amr=amr,
|
||||
uniform128=coarse, baseline_budget=budget, passed=passed,
|
||||
uniform256=fine, amr_to_uniform256=ratio if finite(ratio) else None))
|
||||
baseline_budget=budget, passed=passed,
|
||||
**{f"uniform{base_n}": coarse, f"uniform{2*base_n}": fine,
|
||||
f"amr_to_uniform{2*base_n}": ratio if finite(ratio) else None}))
|
||||
if diagnostic:
|
||||
comparisons[-1]["floor_dominates"] = None if coarse is None else floor >= BASELINE_FACTOR * coarse
|
||||
baseline_passed = all(checks[f"baseline {scheme}/{metric}"] for metric in FLOORS)
|
||||
valid = shared_valid and all(case_valid.get(k, False) for k in (CASES[0], CASES[1], key))
|
||||
valid = shared_valid and all(case_valid.get(k, False) for k in (cases[0], cases[1], key))
|
||||
scheme_results[scheme] = dict(passed=valid and baseline_passed,
|
||||
valid_case_and_controls=valid, baseline_passed=baseline_passed)
|
||||
return dict(passed=all(checks.values()), missing=missing, duplicates=duplicates, checks=checks,
|
||||
@@ -190,19 +211,89 @@ def assess(records, expected_revision=None):
|
||||
floors=FLOORS, baseline_factor=BASELINE_FACTOR,
|
||||
main_statistic="maximum of four post-initial samples at t*=.12,.24,.36,.48",
|
||||
early_statistic="sampled pressure maximum only, t*=k*.001875, k=0..64; not an all-step or continuous maximum",
|
||||
finest_control="uniform256 and AMR/ uniform256 ratios are descriptive, not acceptance gates",
|
||||
finest_control=f"uniform{2*base_n} and AMR/ uniform{2*base_n} ratios are descriptive, not acceptance gates",
|
||||
scope="2D periodic Re100 smooth-decay diagnostic; no new algorithm, convergence-order or general AMR qualification")
|
||||
|
||||
|
||||
def load_reference(paths):
|
||||
"""Use the four audited historical files verbatim, never relabel them fresh controls."""
|
||||
blobs = [path.read_bytes() for path in paths]
|
||||
hashes = [hashlib.sha256(blob).hexdigest() for blob in blobs]
|
||||
if len(hashes) != 4 or set(hashes) != set(REFERENCE_RECEIPTS.values()):
|
||||
raise ValueError("Historical N128 receipt hashes differ from the audited 2cf9853 evidence")
|
||||
return [json.loads(blob) for blob in blobs]
|
||||
|
||||
|
||||
def resolution_trend(records, reference, expected_revision):
|
||||
"""Signed changes are descriptive; they never replace the frozen acceptance budgets."""
|
||||
old = assess(reference, REFERENCE_REVISION)
|
||||
new = assess(records, expected_revision, diagnostic=SCALED_DIAGNOSTIC)
|
||||
# Numerical screen failures still have useful raw errors; only malformed inputs block trends.
|
||||
valid = all(v for result in (old, new) for k, v in result["checks"].items()
|
||||
if not k.startswith("baseline ") and not k.endswith(" absolute"))
|
||||
valid = valid and all(maximum(r["rows"], m) is not None for r in reference + records for m in LIMITS)
|
||||
oracle = reference[0]["source"]["harness_sha256"].get("oracle.py") if valid else None
|
||||
valid = valid and bool(oracle) and records[0]["source"]["harness_sha256"].get("oracle.py") == oracle
|
||||
if not valid:
|
||||
return dict(valid=False, per_time=[], worst_post_initial=[], uniform256_bridge=[])
|
||||
key = lambda r: (r["re_L"], r["n"], r["depth"], r["explosion"])
|
||||
before, after = {key(r): r for r in reference}, {key(r): r for r in records}
|
||||
|
||||
def change(role, metric, low, high, **extra):
|
||||
ratio = high / low if low else None
|
||||
percent = 100*(ratio-1) if ratio is not None else None
|
||||
return dict(role=role, metric=metric, n128_campaign_error=low, n256_campaign_error=high,
|
||||
signed_change=high-low, new_to_old=ratio if finite(ratio) else None,
|
||||
ratio_unavailable_reason=None if finite(ratio) else "zero historical error" if not low else "overflow",
|
||||
change_percent=percent if finite(percent) else None, **extra)
|
||||
|
||||
per_time, worst, bridge = [], [], []
|
||||
for old_key, role in zip(CASES, ("uniform_root", "uniform_fine", "constant", "projected_linear")):
|
||||
re, n, depth, scheme = old_key
|
||||
low, high = before[old_key], after[(re, 2*n, depth, scheme)]
|
||||
for metric in LIMITS:
|
||||
per_time.extend(change(role, metric, a[metric], b[metric], tstar=a["tstar"])
|
||||
for a, b in zip(low["rows"], high["rows"]))
|
||||
worst.append(change(role, metric, maximum(low["rows"][1:], metric),
|
||||
maximum(high["rows"][1:], metric)))
|
||||
for a, b in zip(before[CASES[1]]["rows"], after[CASES[1]]["rows"]):
|
||||
bridge.extend(dict(metric=m, tstar=a["tstar"], historical=a[m], fresh=b[m], signed_change=b[m]-a[m])
|
||||
for m in LIMITS)
|
||||
low, high = before[CASES[1]], after[CASES[1]]
|
||||
bridge_identity = dict(main_rows_identical=low["rows"] == high["rows"],
|
||||
early_pressure_rows_identical=low["early_pressure"]["rows"] == high["early_pressure"]["rows"])
|
||||
same_runtime = reference[0]["runtime_identity"] == records[0]["runtime_identity"]
|
||||
return dict(valid=True, reference_revision=REFERENCE_REVISION, reference_receipt_sha256=REFERENCE_RECEIPTS,
|
||||
reference_scheme_results=old["scheme_results"], per_time=per_time, worst_post_initial=worst,
|
||||
uniform256_bridge=bridge, uniform256_bridge_identity=bridge_identity,
|
||||
same_recorded_runtime=same_runtime,
|
||||
trend_interpretation_ready=all(bridge_identity.values()) and same_runtime,
|
||||
scope="Historical same-production/oracle reference versus fresh controls; same-placement two-resolution trend only, not convergence order or phase robustness")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument("files", type=Path, nargs="+")
|
||||
p.add_argument("--output", type=Path, required=True)
|
||||
p.add_argument("--expected-revision", required=True)
|
||||
p.add_argument("--diagnostic", choices=[SCALED_DIAGNOSTIC])
|
||||
p.add_argument("--reference-files", type=Path, nargs=4)
|
||||
args = p.parse_args()
|
||||
if args.output.exists():
|
||||
p.error("Output already exists; choose a new path to retain prior evidence")
|
||||
result = assess([json.loads(path.read_text()) for path in args.files], args.expected_revision)
|
||||
if bool(args.diagnostic) != bool(args.reference_files):
|
||||
p.error("--diagnostic scaled_n256 requires --reference-files; references are diagnostic-only")
|
||||
records = [json.loads(path.read_text()) for path in args.files]
|
||||
result = assess(records, args.expected_revision, diagnostic=args.diagnostic)
|
||||
if args.diagnostic:
|
||||
try:
|
||||
reference = load_reference(args.reference_files)
|
||||
except ValueError as exc:
|
||||
p.error(str(exc))
|
||||
result["diagnostic"] = args.diagnostic
|
||||
result["resolution_trend"] = resolution_trend(records, reference, args.expected_revision)
|
||||
result["checks"]["valid_historical_reference_and_trend"] = result["resolution_trend"]["valid"]
|
||||
result["passed"] = all(result["checks"].values())
|
||||
args.output.write_text(json.dumps(result, indent=2, allow_nan=False) + "\n")
|
||||
print(json.dumps(result, indent=2, allow_nan=False))
|
||||
raise SystemExit(0 if result["passed"] else 1)
|
||||
@@ -1,4 +1,9 @@
|
||||
"""Fixed N128 four-case shear-wave contract: U128, U256, constant R128, projected R128.
|
||||
"""Fixed four-case shear-wave contracts.
|
||||
|
||||
Default: N128 (U128, U256, constant R128, projected R128). Opt-in
|
||||
--diagnostic scaled_shear_n256: N256 (U256, U512, constant R256, projected
|
||||
R256), same physical rectangle, t* schedule, nu = U N / 100, eps(N), limits
|
||||
and floors, plus a descriptive comparison with the hash-pinned N128 receipts.
|
||||
|
||||
Shared provenance, build, workload and sample-time checks are reused from
|
||||
assess_projected_pairs (same cases, source pins and geometry); only the
|
||||
@@ -6,12 +11,27 @@ Taylor-Green-specific checks are replaced by shear-wave metadata and physics.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import assess_projected_pairs as tg
|
||||
import shear_wave as sw
|
||||
from run_case import SHEAR_WAVE_SHA256
|
||||
import shear_nested as nested
|
||||
import shear_twin as twin
|
||||
from run_case import (NESTED_SHEAR, SCALED_DIAGNOSTIC, SCALED_SHEAR, SHEAR_NESTED_SHA256,
|
||||
SHEAR_TWIN_SHA256, SHEAR_WAVE_SHA256, TWIN_SHEAR, sample_steps)
|
||||
|
||||
# Audited de5b0cc N128 receipts (evidence commit cba6bca, shear-wave-de5b0cc zip).
|
||||
REFERENCE_REVISION = "de5b0cc080794d0ce44d37f845aa8e8dbf8c2016"
|
||||
REFERENCE_RECEIPTS = {
|
||||
"uniform128": "d4a60fb806d04bfa8098a862cceba51e00b49979a6a250fef17a5fc8e3ea3255",
|
||||
"uniform256": "e0b58e734724be5f82ee5d6df84395e3181b4e96e4246dd5a574ea23a8d12386",
|
||||
"constant128": "c70d7cd14b1257a01cc49f8edfcc7685d430e5301e2f2d60ed426936f8b96c51",
|
||||
"projected128": "980161458b9463af09a8e3178063fea0f4bbeb465329e1d3becdb0f224537b54",
|
||||
}
|
||||
ROLES = ("uniform_root", "uniform_fine", "constant", "projected_linear")
|
||||
|
||||
SHARED = ("complete_matrix", "no_extra_cases", "no_duplicate_cases", "finite_receipts",
|
||||
"same_source_and_harness", "clean_a135252_numerical_source",
|
||||
@@ -20,11 +40,26 @@ PER_CASE = ("complete_times", "scales", "actual_builds", "reference", "runtime_s
|
||||
"patch", "workload")
|
||||
|
||||
|
||||
METADATA_ULPS = 4
|
||||
|
||||
|
||||
def close_ulps(recorded, expected, ulps=METADATA_ULPS):
|
||||
"""Portable check for recomputed float metadata only (e.g. eps = |k|^2).
|
||||
|
||||
The recorded value comes from another runtime/libm, so exact equality can
|
||||
fail by one ULP (observed: 0.012047856934923534 Horsea vs ...532 here).
|
||||
A few ULPs is far below any meaningful change; hashes, case identity and
|
||||
scientific thresholds stay exact.
|
||||
"""
|
||||
return (isinstance(recorded, float) and math.isfinite(recorded)
|
||||
and abs(recorded - expected) <= ulps * math.ulp(max(abs(recorded), abs(expected))))
|
||||
|
||||
|
||||
def metadata_ok(record, n):
|
||||
return (record.get("flow") == "shear_wave" and record.get("mode") == list(sw.MODE)
|
||||
and record.get("polarization") == sw.E.tolist()
|
||||
and record.get("angle_deg") == sw.ANGLE_DEG and record.get("phase") == sw.PHI
|
||||
and record.get("eps") == sw.eps(n)
|
||||
and close_ulps(record.get("eps"), sw.eps(n))
|
||||
and record.get("numerics") == tg.NUMERICS
|
||||
and record.get("initialization") == sw.INITIALIZATION
|
||||
and record.get("limits") == sw.LIMITS
|
||||
@@ -33,15 +68,26 @@ def metadata_ok(record, n):
|
||||
and (record.get("source") or {}).get("shear_wave_sha256") == SHEAR_WAVE_SHA256)
|
||||
|
||||
|
||||
def assess(records, expected_revision=None):
|
||||
def cases_for(scaled):
|
||||
factor = 2 if scaled else 1
|
||||
return tuple((re, n * factor, depth, scheme) for re, n, depth, scheme in tg.CASES)
|
||||
|
||||
|
||||
def assess(records, expected_revision=None, *, scaled=False):
|
||||
if not isinstance(records, list) or not all(isinstance(r, dict) for r in records):
|
||||
raise ValueError("records must be a list of receipt objects")
|
||||
base_checks = tg.assess(records, expected_revision)["checks"]
|
||||
# The scaled TG assessor supplies N256/512 cases, builds and workload; its
|
||||
# TG-only diagnostic identity and early-sampling checks are not used here.
|
||||
base_checks = tg.assess(records, expected_revision,
|
||||
diagnostic=SCALED_DIAGNOSTIC if scaled else None)["checks"]
|
||||
checks = {name: base_checks.get(name, False) for name in SHARED}
|
||||
checks["shear_diagnostic_identity"] = all(
|
||||
r.get("diagnostic") == (SCALED_SHEAR if scaled else None) for r in records)
|
||||
cases = cases_for(scaled)
|
||||
indexed = {}
|
||||
for r in records:
|
||||
indexed.setdefault(tuple(r.get(k) for k in ("re_L", "n", "depth", "explosion")), r)
|
||||
for key in tg.CASES:
|
||||
for key in cases:
|
||||
_, n, depth, scheme = key
|
||||
label = f"{n}/{depth}/{scheme}"
|
||||
for name in PER_CASE:
|
||||
@@ -49,32 +95,241 @@ def assess(records, expected_revision=None):
|
||||
checks[f"{label} shear_metadata"] = key in indexed and metadata_ok(indexed[key], n)
|
||||
contract_valid = all(checks.values())
|
||||
result = dict(checks=checks, contract_valid=contract_valid,
|
||||
cases=[list(k) for k in tg.CASES], expected_revision=expected_revision,
|
||||
cases=[list(k) for k in cases], expected_revision=expected_revision,
|
||||
diagnostic=SCALED_SHEAR if scaled else None,
|
||||
scope="2D periodic Re100 oblique (2,1) shear wave; one phase and one "
|
||||
"rectangle; no placement robustness or convergence order")
|
||||
if not contract_valid:
|
||||
result.update(status="INVALID_SET", passed=False)
|
||||
return result
|
||||
try:
|
||||
physics = sw.assess(indexed[tg.CASES[0]], indexed[tg.CASES[1]],
|
||||
[indexed[tg.CASES[2]], indexed[tg.CASES[3]]])
|
||||
physics = sw.assess(indexed[cases[0]], indexed[cases[1]],
|
||||
[indexed[cases[2]], indexed[cases[3]]])
|
||||
except (KeyError, ValueError) as error:
|
||||
result.update(status="INVALID_SET", passed=False, reason=str(error))
|
||||
return result
|
||||
result.update(physics=physics, status=physics["status"],
|
||||
passed=physics["status"] == "PASS")
|
||||
try:
|
||||
result["signed"] = signed_summary([indexed[k] for k in cases])
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
result["signed_unavailable"] = str(error)
|
||||
return result
|
||||
|
||||
|
||||
CASE_AGNOSTIC = ("finite_receipts", "same_source_and_harness", "clean_a135252_numerical_source",
|
||||
"same_recorded_runtime", "same_explicit_device", "same_early_sampling")
|
||||
|
||||
|
||||
# Fixed multi-box presets sharing one contract; only geometry, controls, pins differ.
|
||||
PRESETS = {
|
||||
NESTED_SHEAR: dict(
|
||||
module=nested, physics=nested.assess, snapshot=True,
|
||||
pins={"shear_nested_sha256": SHEAR_NESTED_SHA256},
|
||||
finest_control="uniform 4N (U512)",
|
||||
scope="2D periodic Re100 oblique (2,1) shear wave; fixed three-level/two-region "
|
||||
"placement; changes placement and refined volume as well as depth, so no "
|
||||
"isolated-level cost or resolution trend"),
|
||||
TWIN_SHEAR: dict(
|
||||
module=twin, physics=sw.assess, snapshot=False,
|
||||
pins={"shear_nested_sha256": SHEAR_NESTED_SHA256,
|
||||
"shear_twin_sha256": SHEAR_TWIN_SHA256},
|
||||
finest_control="uniform 2N (U256)",
|
||||
scope="2D periodic Re100 oblique (2,1) shear wave; depth-2 twin of the nested "
|
||||
"preset (same two L0->L1 boxes, no L2). Twin vs nested is the effect of adding "
|
||||
"this nested refinement; it also changes finest spacing and refined volume, so it "
|
||||
"does not isolate abstract depth"),
|
||||
}
|
||||
|
||||
|
||||
def _nested_case_ok(r, n, depth, diagnostic=NESTED_SHEAR):
|
||||
"""Per-case identity of a fixed multi-box preset (scales, builds, patch, work, pins)."""
|
||||
preset = PRESETS[diagnostic]
|
||||
geometry = preset["module"]
|
||||
runtime = r.get("runtime_source", {})
|
||||
package = Path(runtime.get("checkout", "")) / "src/CelerisLab"
|
||||
cells = [n * n] if depth == 1 else geometry.LEAVES
|
||||
hops = [16 * n * 2**level for level in range(depth)]
|
||||
work = r.get("workload", {})
|
||||
snapshot = r.get("final_snapshot")
|
||||
expected_patch = [] if depth == 1 else geometry.REFINEMENTS
|
||||
return {
|
||||
"complete_times": r.get("completed") is True and tg.at_steps(
|
||||
r.get("rows", []), sample_steps(n)[0], n),
|
||||
"scales": (r.get("U") == sw.U and tg.finite(r.get("viscosity"))
|
||||
and abs(r["viscosity"] - sw.U * n / 100) < 1e-15
|
||||
and r.get("tau_by_level") == [0.5 + 3 * sw.U * n / 100 * 2**l
|
||||
for l in range(depth)]
|
||||
and r.get("mach") == sw.U * math.sqrt(3)),
|
||||
"actual_builds": tg.valid_builds(r, n, depth),
|
||||
"reference": (r.get("numerical_revision") == tg.NUMERICAL_REVISION
|
||||
and r.get("reference_harness") == tg.REFERENCE_HARNESS),
|
||||
"runtime_source": bool(runtime.get("checkout")) and all(
|
||||
runtime.get(k) == str(package / tail) for k, tail in
|
||||
(("module", "__init__.py"), ("compiler", "cuda/compiler_v2.py"),
|
||||
("kernel_root", "lbm/kernels"))),
|
||||
"patch": json.dumps(r.get("refinements")) == json.dumps(expected_patch),
|
||||
"workload": (work.get("active_cells_by_level") == cells and work.get("level_hops") == hops
|
||||
and work.get("root_intervals") == 16 * n
|
||||
and work.get("active_collision_updates_by_level") == [
|
||||
c * h for c, h in zip(cells, hops)]
|
||||
and work.get("active_collision_updates") == sum(
|
||||
c * h for c, h in zip(cells, hops))),
|
||||
"shear_metadata": metadata_ok(r, n),
|
||||
"nested_module_pin": all((r.get("source") or {}).get(key) == pin
|
||||
for key, pin in preset["pins"].items()),
|
||||
# Optional, nested projected depth-3 only; bounded and hashed when present.
|
||||
"snapshot": snapshot is None or (
|
||||
preset["snapshot"] and depth == nested.DEPTH and r.get("explosion") == "projected_linear"
|
||||
and snapshot.get("kept") is True and isinstance(snapshot.get("sha256"), str)
|
||||
and len(snapshot["sha256"]) == 64
|
||||
and 0 < snapshot.get("bytes", 0) <= nested.SNAPSHOT_MAX_BYTES
|
||||
and snapshot.get("leaf_counts") == nested.LEAVES
|
||||
and snapshot.get("step") == sample_steps(n)[0][-1]),
|
||||
}
|
||||
|
||||
|
||||
def assess_nested(records, expected_revision=None, diagnostic=NESTED_SHEAR):
|
||||
"""Fixed four-case preset contract: nested_shear_n128 (U128, U512, constant/projected
|
||||
R128 depth 3) or twin_shear_n128 (U128, U256, constant/projected R128 depth 2)."""
|
||||
preset = PRESETS[diagnostic]
|
||||
geometry = preset["module"]
|
||||
if not isinstance(records, list) or not all(isinstance(r, dict) for r in records):
|
||||
raise ValueError("records must be a list of receipt objects")
|
||||
base_checks = tg.assess(records, expected_revision)["checks"]
|
||||
checks = {name: base_checks.get(name, False) for name in CASE_AGNOSTIC}
|
||||
indexed, duplicates = {}, []
|
||||
for r in records:
|
||||
key = tuple(r.get(k) for k in ("re_L", "n", "depth", "explosion"))
|
||||
(duplicates.append(key) if key in indexed else indexed.__setitem__(key, r))
|
||||
checks.update(complete_matrix=all(k in indexed for k in geometry.CASES),
|
||||
no_extra_cases=set(indexed) <= set(geometry.CASES),
|
||||
no_duplicate_cases=not duplicates,
|
||||
shear_diagnostic_identity=all(r.get("diagnostic") == diagnostic
|
||||
for r in records))
|
||||
for key in geometry.CASES:
|
||||
_, n, depth, scheme = key
|
||||
label = f"{n}/{depth}/{scheme}"
|
||||
case = _nested_case_ok(indexed[key], n, depth, diagnostic) if key in indexed else {}
|
||||
for name in ("complete_times", "scales", "actual_builds", "reference", "runtime_source",
|
||||
"patch", "workload", "shear_metadata", "nested_module_pin", "snapshot"):
|
||||
checks[f"{label} {name}"] = bool(case.get(name, False))
|
||||
contract_valid = all(checks.values())
|
||||
result = dict(checks=checks, contract_valid=contract_valid, diagnostic=diagnostic,
|
||||
cases=[list(k) for k in geometry.CASES], expected_revision=expected_revision,
|
||||
finest_control=preset["finest_control"], scope=preset["scope"])
|
||||
if not contract_valid:
|
||||
result.update(status="INVALID_SET", passed=False)
|
||||
return result
|
||||
try:
|
||||
cases = geometry.CASES
|
||||
physics = preset["physics"](indexed[cases[0]], indexed[cases[1]],
|
||||
[indexed[cases[2]], indexed[cases[3]]])
|
||||
except (KeyError, ValueError) as error:
|
||||
result.update(status="INVALID_SET", passed=False, reason=str(error))
|
||||
return result
|
||||
result.update(physics=physics, status=physics["status"], passed=physics["status"] == "PASS")
|
||||
try:
|
||||
# Role "uniform_fine" is the preset's finest control (4N nested, 2N twin).
|
||||
result["signed"] = signed_summary([indexed[k] for k in geometry.CASES])
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
result["signed_unavailable"] = str(error)
|
||||
return result
|
||||
|
||||
|
||||
def _signed(record):
|
||||
"""Raw signed quantities of one valid record (None when the fit is unavailable)."""
|
||||
s = sw.summarize(record)
|
||||
bias = None if not s["rate_fit_valid"] else s["rate"] / s["exact_rate"] - 1
|
||||
return dict(n=record["n"], depth=record["depth"], explosion=record["explosion"],
|
||||
rate_fit_valid=s["rate_fit_valid"], signed_rate_bias=bias,
|
||||
rate_tstar=s["rate_tstar"], exact_rate_tstar=s["exact_rate_tstar"],
|
||||
interval_rate_errors=s["interval_rate_errors"],
|
||||
residual_l2=s["residual_l2"], spurious_pressure_scaled=s["spurious_pressure_scaled"],
|
||||
spurious_pressure_rms=s["spurious_pressure_rms"],
|
||||
final_amplitude_error=record["rows"][-1]["amplitude_error"])
|
||||
|
||||
|
||||
def signed_summary(records):
|
||||
"""Signed bias per case and refined excess over each uniform control."""
|
||||
rows = dict(zip(ROLES, (_signed(r) for r in records)))
|
||||
|
||||
def minus(a, b):
|
||||
return None if a is None or b is None else a - b
|
||||
|
||||
for role in ("constant", "projected_linear"):
|
||||
for control in ("uniform_root", "uniform_fine"):
|
||||
rows[role][f"excess_over_{control}"] = {
|
||||
key: minus(rows[role][key], rows[control][key])
|
||||
for key in ("signed_rate_bias", "residual_l2", "spurious_pressure_rms")}
|
||||
return rows
|
||||
|
||||
|
||||
def load_reference(paths):
|
||||
"""The four audited N128 receipts verbatim; never relabelled as fresh controls."""
|
||||
blobs = [Path(path).read_bytes() for path in paths]
|
||||
hashes = {hashlib.sha256(blob).hexdigest() for blob in blobs}
|
||||
if len(blobs) != 4 or hashes != set(REFERENCE_RECEIPTS.values()):
|
||||
raise ValueError("N128 receipt hashes differ from the audited de5b0cc evidence")
|
||||
return [json.loads(blob) for blob in blobs]
|
||||
|
||||
|
||||
def resolution_trend(new_result, reference, reference_revision=REFERENCE_REVISION):
|
||||
"""Descriptive N128 -> N256 signed changes; never an acceptance gate or an order."""
|
||||
old_result = assess(reference, reference_revision)
|
||||
if "signed" not in old_result or "signed" not in new_result:
|
||||
return dict(valid=False, reason="N128 reference or N256 set lacks a valid signed summary")
|
||||
out = {}
|
||||
for role in ROLES:
|
||||
old, new = old_result["signed"][role], new_result["signed"][role]
|
||||
out[role] = {}
|
||||
for key in ("signed_rate_bias", "residual_l2", "spurious_pressure_rms",
|
||||
*(f"excess_over_{c}" for c in ("uniform_root", "uniform_fine"))):
|
||||
if key not in old:
|
||||
continue
|
||||
pairs = old[key].items() if isinstance(old[key], dict) else [(None, old[key])]
|
||||
for sub, low in pairs:
|
||||
high = new[key][sub] if sub else new[key]
|
||||
ok = low is not None and high is not None
|
||||
out[role][key if sub is None else f"{key}.{sub}"] = dict(
|
||||
n128=low, n256=high, signed_change=high - low if ok else None,
|
||||
n256_to_n128=high / low if ok and low else None)
|
||||
return dict(valid=True, reference_revision=reference_revision,
|
||||
reference_receipt_sha256=REFERENCE_RECEIPTS,
|
||||
reference_status=old_result["status"], changes=out,
|
||||
scope="same-placement two-resolution trend; not a convergence order, "
|
||||
"placement robustness or redesign decision")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument("files", type=Path, nargs=4)
|
||||
p.add_argument("--output", type=Path, required=True)
|
||||
p.add_argument("--expected-revision", required=True)
|
||||
p.add_argument("--diagnostic", choices=[SCALED_SHEAR, NESTED_SHEAR, TWIN_SHEAR])
|
||||
p.add_argument("--reference-files", type=Path, nargs=4)
|
||||
args = p.parse_args()
|
||||
if args.output.exists():
|
||||
p.error("Output already exists; choose a new path to retain prior evidence")
|
||||
result = assess([json.loads(path.read_text()) for path in args.files], args.expected_revision)
|
||||
if args.diagnostic in PRESETS:
|
||||
if args.reference_files:
|
||||
p.error(f"{args.diagnostic} takes no reference files (no resolution trend)")
|
||||
result = assess_nested([json.loads(path.read_text()) for path in args.files],
|
||||
args.expected_revision, args.diagnostic)
|
||||
text = json.dumps(result, indent=2, allow_nan=False) + "\n"
|
||||
args.output.write_text(text)
|
||||
print(text, end="")
|
||||
raise SystemExit(0 if result["passed"] else 1)
|
||||
if bool(args.diagnostic) != bool(args.reference_files):
|
||||
p.error("--diagnostic scaled_shear_n256 requires --reference-files (N128 receipts)")
|
||||
result = assess([json.loads(path.read_text()) for path in args.files], args.expected_revision,
|
||||
scaled=bool(args.diagnostic))
|
||||
if args.diagnostic:
|
||||
try:
|
||||
reference = load_reference(args.reference_files)
|
||||
except ValueError as exc:
|
||||
p.error(str(exc))
|
||||
result["resolution_trend"] = resolution_trend(result, reference)
|
||||
text = json.dumps(result, indent=2, allow_nan=False) + "\n"
|
||||
args.output.write_text(text)
|
||||
print(text, end="")
|
||||
|
||||
@@ -19,6 +19,19 @@ BUILD_CAPTURE_SHA256 = "3dbaa6547cb0a4cb1541054f3b64ad8b6e18cc70527ac6166e56b944
|
||||
REFERENCE_HARNESS = "11901a7f77557a901e017a171eeae1758d584fa5"
|
||||
INITIALIZATION = "physical FP64 equilibrium G at actual cell centers; rho=1+3p"
|
||||
SHEAR_WAVE_SHA256 = "81d5c8ee60864372887cddf9239580c82fb6750d956038f1e3ee9de61aa4e877"
|
||||
SCALED_DIAGNOSTIC = "scaled_n256"
|
||||
SCALED_SHEAR = "scaled_shear_n256" # mutually exclusive with scaled_n256 (one flow each)
|
||||
NESTED_SHEAR = "nested_shear_n128" # fixed three-level/two-region shear preset
|
||||
SHEAR_NESTED_SHA256 = "fa6cfe87fb4df8c6d732c637c885276833c81973b950c7724d734f8fc350bcb0"
|
||||
TWIN_SHEAR = "twin_shear_n128" # depth-2 twin: nested L0->L1 boxes without L2
|
||||
SHEAR_TWIN_SHA256 = "1cbf37f18e89aea577581631c4565f51e68d22a3739b698816ef64acbf2b2bb4"
|
||||
WIDE_X_SHEAR = "wide_x_shear_n128" # one projected x-gap-width contrast
|
||||
SHEAR_WIDE_X_SHA256 = "13d16ba355117e2d074edccd9dc5d694c2fc13c673a0abdfca6401f6ed917733"
|
||||
AXIS_SQUARE = "axis_square_shear_n128" # short four-face orientation diagnostic
|
||||
SHEAR_AXIS_SQUARE_SHA256 = "0baffa6eb99c268ce998d90300c993d45bb3b7b27c524b636874d75808bd0a8c"
|
||||
POSTG_SHEAR = "postg_shear_n128" # initialization-only three-case counterfactual
|
||||
SHEAR_POSTG_SHA256 = "9c909fa013c092490ee21532e3125b2a78a4c5102c7a1bdc0e76986d3f0ef903"
|
||||
SHEAR_PROBES_SHA256 = "e2e6490d281cc275f448dc6d6ea7b9b4407cc74a51e9256b86c08c8626b89b76" # signed interface-probe mechanism diagnostic
|
||||
|
||||
|
||||
def flow_functions(flow="taylor_green"):
|
||||
@@ -88,7 +101,7 @@ def provenance(repo):
|
||||
)
|
||||
|
||||
|
||||
def verify_source(repo, revision, flow="taylor_green"):
|
||||
def verify_source(repo, revision, flow="taylor_green", probes=False):
|
||||
"""Exact clean harness commit, fixed production tree, and reviewed capture helper."""
|
||||
import oracle
|
||||
from tests.validation.multigrid import check_projected_pairs as gate
|
||||
@@ -107,7 +120,49 @@ def verify_source(repo, revision, flow="taylor_green"):
|
||||
if result["source_tree_sha256"] != SOURCE_SHA256:
|
||||
raise RuntimeError("Wrong numerical source fingerprint")
|
||||
result["build_capture_sha256"] = BUILD_CAPTURE_SHA256
|
||||
if flow == "shear_wave":
|
||||
if probes:
|
||||
import shear_probes
|
||||
module = harness / "shear_probes.py"
|
||||
if (Path(shear_probes.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_PROBES_SHA256):
|
||||
raise RuntimeError("Wrong shear_probes module checkout or content")
|
||||
result["shear_probes_sha256"] = SHEAR_PROBES_SHA256
|
||||
if flow == "shear_axis_square":
|
||||
import shear_axis_square
|
||||
module = harness / "shear_axis_square.py"
|
||||
if (Path(shear_axis_square.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_AXIS_SQUARE_SHA256):
|
||||
raise RuntimeError("Wrong shear_axis_square module checkout or content")
|
||||
result["shear_axis_square_sha256"] = SHEAR_AXIS_SQUARE_SHA256
|
||||
if flow == "shear_postg":
|
||||
import shear_postg
|
||||
module = harness / "shear_postg.py"
|
||||
if (Path(shear_postg.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_POSTG_SHA256):
|
||||
raise RuntimeError("Wrong shear_postg module checkout or content")
|
||||
result["shear_postg_sha256"] = SHEAR_POSTG_SHA256
|
||||
if flow == "shear_wide_x":
|
||||
import shear_wide_x
|
||||
module = harness / "shear_wide_x.py"
|
||||
if (Path(shear_wide_x.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_WIDE_X_SHA256):
|
||||
raise RuntimeError("Wrong shear_wide_x module checkout or content")
|
||||
result["shear_wide_x_sha256"] = SHEAR_WIDE_X_SHA256
|
||||
if flow == "shear_twin":
|
||||
import shear_twin
|
||||
module = harness / "shear_twin.py"
|
||||
if (Path(shear_twin.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_TWIN_SHA256):
|
||||
raise RuntimeError("Wrong shear_twin module checkout or content")
|
||||
result["shear_twin_sha256"] = SHEAR_TWIN_SHA256
|
||||
if flow in ("shear_nested", "shear_twin", "shear_wide_x", "shear_postg"):
|
||||
import shear_nested
|
||||
module = harness / "shear_nested.py"
|
||||
if (Path(shear_nested.__file__).resolve() != module
|
||||
or hashlib.sha256(module.read_bytes()).hexdigest() != SHEAR_NESTED_SHA256):
|
||||
raise RuntimeError("Wrong shear_nested module checkout or content")
|
||||
result["shear_nested_sha256"] = SHEAR_NESTED_SHA256
|
||||
if flow in ("shear_wave", "shear_nested", "shear_twin", "shear_wide_x", "shear_postg", "shear_axis_square"):
|
||||
import shear_wave
|
||||
module = harness / "shear_wave.py"
|
||||
if (Path(shear_wave.__file__).resolve() != module
|
||||
@@ -176,30 +231,107 @@ def main():
|
||||
p.add_argument("--run-gpu", action="store_true")
|
||||
p.add_argument("--dry-run", action="store_true")
|
||||
p.add_argument("--re", type=int, choices=[100, 1000], required=True)
|
||||
p.add_argument("--n", type=int, choices=[32, 64, 128, 256], required=True)
|
||||
p.add_argument("--n", type=int, choices=[32, 64, 128, 256, 512], required=True)
|
||||
p.add_argument("--depth", type=int, choices=[1, 2, 3], required=True)
|
||||
p.add_argument("--output", type=Path, required=True)
|
||||
p.add_argument("--explosion", choices=["constant", "projected_linear"], default="constant")
|
||||
p.add_argument("--diagnostic",
|
||||
choices=[SCALED_DIAGNOSTIC, SCALED_SHEAR, NESTED_SHEAR, TWIN_SHEAR,
|
||||
WIDE_X_SHEAR, POSTG_SHEAR, AXIS_SQUARE],
|
||||
help="explicit four-case N256/fine512 same-placement diagnostic "
|
||||
"(scaled_n256: Taylor-Green; scaled_shear_n256: shear wave) or the "
|
||||
"fixed three-level/two-region shear preset (nested_shear_n128) "
|
||||
"or its depth-2 twin (twin_shear_n128), or the single projected "
|
||||
"x-gap-width contrast (wide_x_shear_n128), or three-case POST-G "
|
||||
"initialization counterfactual (postg_shear_n128), or short square "
|
||||
"orientation diagnostic (axis_square_shear_n128)")
|
||||
p.add_argument("--probe-trace", action="store_true",
|
||||
help="signed interface-probe trace (nested U128/U512/projected d3, "
|
||||
"twin projected d2, wide-x projected d3, POST-G three-case set, "
|
||||
"axis-square four-case set); "
|
||||
"mechanism diagnostic, no gates")
|
||||
p.add_argument("--final-snapshot", action="store_true",
|
||||
help="nested_shear_n128 projected depth-3 only: final macro snapshot")
|
||||
p.add_argument("--early-pressure", action="store_true",
|
||||
help="also save 65 scalar pressure samples at t*=k*.001875, k=0..64")
|
||||
p.add_argument("--flow", choices=["taylor_green", "shear_wave"], default="taylor_green")
|
||||
args = p.parse_args()
|
||||
if args.flow == "shear_wave" and (args.re != 100 or args.depth == 3 or args.early_pressure):
|
||||
axis_square = args.diagnostic == AXIS_SQUARE
|
||||
nested = args.diagnostic == NESTED_SHEAR
|
||||
wide_x = args.diagnostic == WIDE_X_SHEAR
|
||||
postg = args.diagnostic == POSTG_SHEAR
|
||||
if (wide_x or postg or axis_square) and not args.probe_trace:
|
||||
p.error(f"{args.diagnostic} requires --probe-trace")
|
||||
if args.flow == "shear_wave" and (args.re != 100 or (args.depth == 3 and not (nested or wide_x or postg))
|
||||
or args.early_pressure):
|
||||
p.error("shear_wave requires --re 100, depth 1 or 2 and no --early-pressure")
|
||||
probe_cases = {(NESTED_SHEAR, 128, 1, "constant"), (NESTED_SHEAR, 512, 1, "constant"),
|
||||
(NESTED_SHEAR, 128, 3, "projected_linear"),
|
||||
(TWIN_SHEAR, 128, 2, "projected_linear"),
|
||||
(WIDE_X_SHEAR, 128, 3, "projected_linear"),
|
||||
(POSTG_SHEAR, 128, 1, "constant"), (POSTG_SHEAR, 512, 1, "constant"),
|
||||
(POSTG_SHEAR, 128, 3, "projected_linear")}
|
||||
if axis_square:
|
||||
import shear_axis_square
|
||||
probe_cases.update((AXIS_SQUARE, n, depth, scheme)
|
||||
for _, n, depth, scheme in shear_axis_square.CASES)
|
||||
if args.probe_trace and (args.final_snapshot or (
|
||||
args.diagnostic, args.n, args.depth, args.explosion) not in probe_cases):
|
||||
p.error("--probe-trace requires a fixed nested, twin, wide-x, POST-G or axis-square case, "
|
||||
"without --final-snapshot")
|
||||
if args.final_snapshot and not (nested and args.depth == 3
|
||||
and args.explosion == "projected_linear"):
|
||||
p.error("--final-snapshot requires nested_shear_n128 projected depth 3")
|
||||
initial, metrics, limits, initialization = flow_functions(args.flow)
|
||||
if args.output.exists():
|
||||
p.error("Output already exists; choose a new path to retain prior evidence")
|
||||
if args.explosion != "constant" and args.depth != 2:
|
||||
if args.explosion != "constant" and args.depth != 2 and not (nested or wide_x or postg):
|
||||
p.error("projected_linear requires depth 2")
|
||||
twin = args.diagnostic == TWIN_SHEAR
|
||||
if axis_square:
|
||||
preset = shear_axis_square
|
||||
if args.flow != "shear_wave" or (args.re, args.n, args.depth, args.explosion) not in preset.CASES:
|
||||
p.error("axis_square_shear_n128 requires one of its four Re100 shear cases")
|
||||
initial, metrics, limits, initialization = preset.initial, preset.metrics, {}, preset.INITIALIZATION
|
||||
elif nested or twin or wide_x or postg:
|
||||
import shear_nested
|
||||
import shear_twin
|
||||
import shear_wide_x
|
||||
preset = shear_wide_x if wide_x else shear_nested if nested else shear_twin
|
||||
if postg:
|
||||
import shear_postg
|
||||
preset = shear_postg
|
||||
initial, initialization = preset.initial, preset.INITIALIZATION
|
||||
if args.flow != "shear_wave":
|
||||
p.error(f"--diagnostic {args.diagnostic} requires --flow shear_wave")
|
||||
if (args.re, args.n, args.depth, args.explosion) not in preset.CASES:
|
||||
p.error(f"{args.diagnostic} requires one of its fixed Re100 cases")
|
||||
elif args.diagnostic:
|
||||
cases = {(256, 1, "constant"), (512, 1, "constant"),
|
||||
(256, 2, "constant"), (256, 2, "projected_linear")}
|
||||
flow = "shear_wave" if args.diagnostic == SCALED_SHEAR else "taylor_green"
|
||||
if args.flow != flow:
|
||||
p.error(f"--diagnostic {args.diagnostic} requires --flow {flow}")
|
||||
if args.diagnostic == SCALED_DIAGNOSTIC and (
|
||||
args.re != 100 or (args.n, args.depth, args.explosion) not in cases
|
||||
or not args.early_pressure):
|
||||
p.error("scaled_n256 requires one of its four Re100 cases and --early-pressure")
|
||||
if args.diagnostic == SCALED_SHEAR and (args.n, args.depth, args.explosion) not in cases:
|
||||
p.error("scaled_shear_n256 requires one of its four Re100 cases")
|
||||
elif args.n == 512:
|
||||
p.error("N512 requires --diagnostic scaled_n256, scaled_shear_n256 or nested_shear_n128")
|
||||
selected = os.environ.get("CUDA_VISIBLE_DEVICES", "")
|
||||
if not args.dry_run and (not args.run_gpu or not selected or "," in selected or selected == "-1"):
|
||||
p.error("--run-gpu and exactly one explicit CUDA_VISIBLE_DEVICES are required")
|
||||
repo = args.repo.resolve()
|
||||
sys.path[:0] = [str(repo / "src"), str(repo)]
|
||||
source = verify_source(repo, args.expected_revision, args.flow)
|
||||
source_flow = ("shear_axis_square" if axis_square else "shear_postg" if postg else "shear_wide_x" if wide_x else "shear_nested" if nested
|
||||
else "shear_twin" if twin else args.flow)
|
||||
source = verify_source(repo, args.expected_revision, source_flow, probes=args.probe_trace)
|
||||
runtime = runtime_source(repo)
|
||||
if args.dry_run:
|
||||
print(json.dumps(dict(source=source, runtime_source=runtime, dry_run=True)))
|
||||
print(json.dumps(dict(source=source, runtime_source=runtime, dry_run=True,
|
||||
**({"diagnostic": args.diagnostic} if args.diagnostic else {}))))
|
||||
return 0
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
from CelerisLab.lbm.blocks import BlockLayout2D
|
||||
@@ -209,11 +341,18 @@ def main():
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
||||
|
||||
cfg = config(args.n, args.re)
|
||||
refinements = [] if args.depth == 1 else rectangles(args.n, args.depth)
|
||||
if args.depth == 1:
|
||||
refinements = []
|
||||
elif nested or twin or wide_x or postg or axis_square:
|
||||
refinements = [list(level) for level in preset.REFINEMENTS]
|
||||
else:
|
||||
refinements = rectangles(args.n, args.depth)
|
||||
h = build_hierarchy(cfg, refinements)
|
||||
np.testing.assert_array_equal(leaf_partition(h), np.ones((args.n, args.n)))
|
||||
masks = [k == ACTIVE for k in h.kinds]
|
||||
main_steps, early_steps = sample_steps(args.n, args.early_pressure)
|
||||
if axis_square:
|
||||
main_steps = [0, preset.case_steps(args.n)[-1]]
|
||||
report = dict(
|
||||
schema=1,
|
||||
source=source,
|
||||
@@ -241,7 +380,7 @@ def main():
|
||||
cuda_visible_devices=selected,
|
||||
device_id=0,
|
||||
limits=limits,
|
||||
workload=workload(masks, round(TIMES[-1] * args.n / U)),
|
||||
workload=workload(masks, main_steps[-1]),
|
||||
rows=[],
|
||||
early_pressure=dict(enabled=args.early_pressure, rows=[],
|
||||
sampled_max_pressure_l2=None),
|
||||
@@ -249,9 +388,19 @@ def main():
|
||||
)
|
||||
if args.flow != "taylor_green":
|
||||
import shear_wave
|
||||
report.update(flow=args.flow, mode=list(shear_wave.MODE),
|
||||
polarization=shear_wave.E.tolist(), angle_deg=shear_wave.ANGLE_DEG,
|
||||
phase=shear_wave.PHI, eps=shear_wave.eps(args.n))
|
||||
wave = preset if axis_square else shear_wave
|
||||
report.update(flow=args.flow, mode=list(wave.MODE),
|
||||
polarization=wave.E.tolist(), angle_deg=wave.ANGLE_DEG,
|
||||
phase=wave.PHI, eps=wave.eps(args.n))
|
||||
if args.diagnostic:
|
||||
report["diagnostic"] = args.diagnostic
|
||||
if postg:
|
||||
report.update(initialization_id=preset.INITIALIZATION_ID, qualification_eligible=False,
|
||||
screening_scope="descriptive scalar screens only; no qualification assessor")
|
||||
if axis_square:
|
||||
report.update(initialization_id=preset.INITIALIZATION_ID, qualification_eligible=False,
|
||||
reference_harness=None, historical_baseline_revision=None,
|
||||
screening_scope="no numerical gates; short orientation diagnostic")
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
def save():
|
||||
temporary = args.output.with_suffix(args.output.suffix + ".tmp")
|
||||
@@ -286,8 +435,32 @@ def main():
|
||||
]
|
||||
)
|
||||
initial_mass = None
|
||||
for step in sorted(set(main_steps + early_steps)):
|
||||
probe_steps = set()
|
||||
if args.probe_trace:
|
||||
import shear_probes
|
||||
if axis_square:
|
||||
shear_probes = preset
|
||||
probe_root = dict(zip(shear_probes.case_steps(args.n), shear_probes.STEPS))
|
||||
probe_steps = set(probe_root)
|
||||
probes = shear_probes.select(masks, args.n)
|
||||
report["probe_trace"] = shear_probes.new_trace(
|
||||
probes, cfg.viscosity, [args.diagnostic, args.n, args.depth, args.explosion])
|
||||
for step in sorted(set(main_steps + early_steps) | probe_steps):
|
||||
run.step(step - run.step_count)
|
||||
if step in probe_steps:
|
||||
trace = report["probe_trace"]
|
||||
if "error" not in trace:
|
||||
arrays = (run.download(),) if args.depth == 1 else run.download()
|
||||
try:
|
||||
values = shear_probes.sample(arrays, probes)
|
||||
shear_probes.validate_row(values, len(probes))
|
||||
except (ValueError, ZeroDivisionError) as error:
|
||||
report["probe_trace"] = dict(error=str(error))
|
||||
else:
|
||||
trace["steps"].append(probe_root[step]) # root-physical step
|
||||
trace["values"].append(values)
|
||||
if step not in main_steps and step not in early_steps:
|
||||
continue # probe-only step: no qualification metrics
|
||||
row = sample(run, masks, step, args.n, cfg.viscosity, initial_mass,
|
||||
metrics, args.depth)
|
||||
row["step"] = step
|
||||
@@ -295,6 +468,13 @@ def main():
|
||||
initial_mass = row["mass"]
|
||||
if step in main_steps:
|
||||
report["rows"].append(row)
|
||||
if args.final_snapshot and step == main_steps[-1]:
|
||||
# Optional macro-only snapshot; scalar receipt stays authoritative.
|
||||
report["final_snapshot"] = shear_nested.write_snapshot(
|
||||
args.output.with_name(args.output.stem + ".final_macro.npz"),
|
||||
run.download(), masks,
|
||||
shear_nested.snapshot_meta(step, cfg.viscosity, args.explosion,
|
||||
source.get("revision")))
|
||||
if step in early_steps:
|
||||
trace = report["early_pressure"]
|
||||
trace["rows"].append({key: row[key] for key in ("step", "tstar", "pressure_l2")})
|
||||
@@ -333,10 +513,15 @@ def main():
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
if args.probe_trace and "error" not in report["probe_trace"]:
|
||||
try:
|
||||
report["probe_trace"]["bytes"] = shear_probes.validate(report["probe_trace"])
|
||||
except ValueError as error: # keep the scalar receipt; drop an invalid trace
|
||||
report["probe_trace"] = dict(error=str(error))
|
||||
report.update(
|
||||
completed=True,
|
||||
seconds=time.monotonic() - started,
|
||||
screening_pass=all(
|
||||
screening_pass=None if axis_square else all(
|
||||
row[key] <= limit for row in report["rows"] for key, limit in limits.items()
|
||||
),
|
||||
)
|
||||
@@ -347,6 +532,8 @@ def main():
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
if axis_square:
|
||||
return int("error" in report["probe_trace"])
|
||||
return 0 if report["screening_pass"] else 1
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Short axis-shear orientation diagnostic; no physical acceptance gates."""
|
||||
|
||||
import json
|
||||
import numpy as np
|
||||
|
||||
import shear_wave as sw
|
||||
from shear_probes import sample, validate_row, TRACE_MAX_BYTES
|
||||
from oracle import CX, CY, W, U, centers
|
||||
|
||||
ROOT_N = 128
|
||||
MODE, E, PHI, ANGLE_DEG = (2, 0), np.array([0., 1.]), np.pi / 4, 0.
|
||||
REFINEMENTS = [[(32, 32, 96, 96)]]
|
||||
CASES = ((100, 128, 1, "constant"), (100, 256, 1, "constant"),
|
||||
(100, 128, 2, "constant"), (100, 128, 2, "projected_linear"))
|
||||
STEPS = tuple(range(9))
|
||||
INITIALIZATION_ID = "postg_first_gradient_v1"
|
||||
INITIALIZATION = "physical FP64 axis-shear POST G=feq-(tau-1)A; first-gradient low-Mach CE"
|
||||
GROUPS = dict(inner_left=(32, 64), inner_right=(95, 63),
|
||||
inner_bottom=(64, 32), inner_top=(63, 95),
|
||||
outer_left=(31, 64), outer_right=(96, 63),
|
||||
outer_bottom=(64, 31), outer_top=(63, 96))
|
||||
|
||||
|
||||
def eps(n):
|
||||
return (4 * np.pi / n)**2
|
||||
|
||||
|
||||
def exact(x, t, length, viscosity):
|
||||
"""Transverse velocity and physical x derivative in the supplied length units."""
|
||||
k = 4 * np.pi / length
|
||||
amplitude = U * np.exp(-viscosity * k * k * t)
|
||||
return amplitude * np.sin(k * x + PHI), amplitude * k * np.cos(k * x + PHI)
|
||||
|
||||
|
||||
def initial(shape, level, length, viscosity):
|
||||
x, _ = centers(shape, level)
|
||||
uy, gradient = exact(x, 0, length, viscosity)
|
||||
tau = .5 + 3 * viscosity * 2**level
|
||||
a = (3 * W * CX * CY)[:, None, None] * gradient * 2.**-level
|
||||
return sw.equilibrium(np.ones_like(uy), np.zeros_like(uy), uy) - (tau - 1) * a
|
||||
|
||||
|
||||
def metrics(arrays, masks, t, length, viscosity, initial_mass=None):
|
||||
rho, ux, uy, x, _, w, _ = sw._leaves(arrays, masks)
|
||||
if not np.isclose(w.sum(), length**2, rtol=0, atol=1e-10):
|
||||
raise ValueError("leaf partition does not cover domain once")
|
||||
ey, _ = exact(x, t, length, viscosity)
|
||||
p = (rho - 1) / 3
|
||||
p -= w @ p / w.sum() # one domain gauge, never the probe mean
|
||||
mass = float(w @ rho)
|
||||
return dict(tstar=t * U / length, mass=mass,
|
||||
mass_relative=0. if initial_mass is None else abs(mass-initial_mass)/initial_mass,
|
||||
velocity_l2=float(np.sqrt(w @ (ux**2 + (uy-ey)**2) / (w @ ey**2))),
|
||||
spurious_pressure_rms=float(np.sqrt(w @ p**2 / w.sum())),
|
||||
rho_min=float(rho.min()), rho_max=float(rho.max()),
|
||||
max_speed=float(np.hypot(ux, uy).max()), leaf_cells=len(rho))
|
||||
|
||||
|
||||
def case_steps(n):
|
||||
return [r * (n // ROOT_N) for r in STEPS]
|
||||
|
||||
|
||||
def select(masks, n=ROOT_N):
|
||||
"""Exact native cells filling eight unit-area groups; no interpolated samples."""
|
||||
if (n, len(masks)) not in ((128, 1), (256, 1), (128, 2)):
|
||||
raise ValueError("unsupported square probe layout")
|
||||
probes = []
|
||||
for name, (x, y) in GROUPS.items():
|
||||
level = int(len(masks) == 2 and name.startswith("inner"))
|
||||
factor = (n // ROOT_N) * 2**level
|
||||
for dy in range(factor):
|
||||
for dx in range(factor):
|
||||
i, j = factor*x + dx, factor*y + dy
|
||||
if not masks[level][j, i]:
|
||||
raise ValueError("square probe is not ACTIVE")
|
||||
probes.append(dict(group=name, level=level, i=i, j=j,
|
||||
center=[(i+.5)/factor, (j+.5)/factor],
|
||||
width=1/factor, area=1/factor**2, parity=[i % 2, j % 2]))
|
||||
if len({(p["level"], p["i"], p["j"]) for p in probes}) != len(probes):
|
||||
raise ValueError("duplicate square probes")
|
||||
return probes
|
||||
|
||||
|
||||
def new_trace(probes, case_viscosity, case):
|
||||
return dict(schema=1, kind="axis-square orientation diagnostic; no pass gates", case=case,
|
||||
time_unit="root step (N=128)", sample_phase="postcollision G at root synchronization",
|
||||
root_n=ROOT_N, viscosity_root=U*ROOT_N/100, case_viscosity=case_viscosity,
|
||||
mode=list(MODE), phase=PHI, amplitude=U, fields=["rho", "ux", "uy"],
|
||||
probes=probes, steps=[], values=[])
|
||||
|
||||
|
||||
def group_moments(trace):
|
||||
"""Signed unit-area means; normalize by each group's mean actual-center derivative.
|
||||
|
||||
Momentum is rho*u. Raw density is ungauged. Per-cell parity stays in the trace.
|
||||
U128 samples one center, whereas U256/refined-inner use four different centers.
|
||||
"""
|
||||
out = []
|
||||
for step, row in zip(trace["steps"], trace["values"]):
|
||||
values, groups = np.asarray(row).reshape(-1, 3), {}
|
||||
for name in GROUPS:
|
||||
indices = [i for i, p in enumerate(trace["probes"]) if p["group"] == name]
|
||||
probes = [trace["probes"][i] for i in indices]
|
||||
weights = np.array([p["area"] for p in probes])
|
||||
if weights.sum() != 1:
|
||||
raise ValueError("probe group does not have unit area")
|
||||
rho, ux, uy = values[indices].T
|
||||
ey, gradient = exact(np.array([p["center"][0] for p in probes]), step,
|
||||
ROOT_N, trace["viscosity_root"])
|
||||
drho, djx, djy = (float(weights @ v) for v in (rho-1, rho*ux, rho*uy-ey))
|
||||
derivative = float(weights @ gradient)
|
||||
groups[name] = dict(drho=drho, djx=djx, djy=djy, mean_duy_dx=derivative,
|
||||
djx_over_duy_dx=djx/derivative, djy_over_duy_dx=djy/derivative)
|
||||
out.append(dict(root_step=step, groups=groups))
|
||||
return out
|
||||
|
||||
|
||||
def validate(trace):
|
||||
if trace["steps"] != list(STEPS) or len(trace["values"]) != len(STEPS):
|
||||
raise ValueError("square trace schedule mismatch")
|
||||
_, n, depth, scheme = trace["case"]
|
||||
if (100, n, depth, scheme) not in CASES or len(trace["probes"]) != (
|
||||
20 if depth == 2 else 8*(n//ROOT_N)**2):
|
||||
raise ValueError("square trace case/count mismatch")
|
||||
for row in trace["values"]:
|
||||
validate_row(row, len(trace["probes"]))
|
||||
trace["group_moments"] = group_moments(trace)
|
||||
size = 0
|
||||
while True:
|
||||
final = len(json.dumps(dict(trace, bytes=size), allow_nan=False).encode())
|
||||
if final == size:
|
||||
break
|
||||
size = final
|
||||
if size > TRACE_MAX_BYTES:
|
||||
raise ValueError("square trace exceeds byte cap")
|
||||
return size
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Fixed three-level/two-region shear preset (opt-in nested_shear_n128).
|
||||
|
||||
Four fresh cases: U128, U512 (finest control = 4N), constant R128 depth 3,
|
||||
projected R128 depth 3. Same wave, phase, Re100/U=0.03, initialization, five
|
||||
sample times, eps(root N), limits and floors as shear_wave. This changes
|
||||
placement and refined volume as well as depth: no isolated-level cost or
|
||||
resolution-trend label. shear_wave.py is reused unchanged (historical receipt
|
||||
identities stay valid); only the depth/finest-control contract is generalized
|
||||
here.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shear_wave as sw
|
||||
from oracle import CX, CY, U, centers
|
||||
|
||||
ROOT_N = 128
|
||||
DEPTH = 3
|
||||
FINEST_FACTOR = 2 ** (DEPTH - 1) # finest-spacing uniform control is 4N
|
||||
# Parent-frame (x0, y0, x1, y1) rectangles; level-wide, zero-origin unions.
|
||||
REFINEMENTS = [
|
||||
[(16, 16, 60, 112), (68, 24, 112, 120)], # L0 -> L1, root coordinates
|
||||
[(44, 44, 108, 212), (148, 60, 212, 228)], # L1 -> L2, L1 coordinates
|
||||
]
|
||||
CASES = ((100, ROOT_N, 1, "constant"), (100, FINEST_FACTOR * ROOT_N, 1, "constant"),
|
||||
(100, ROOT_N, DEPTH, "constant"), (100, ROOT_N, DEPTH, "projected_linear"))
|
||||
ROOT_STEPS = 16 * ROOT_N
|
||||
LEAVES = [7936, 12288, 86016]
|
||||
HOPS = [ROOT_STEPS * 2**level for level in range(DEPTH)] # [2048, 4096, 8192]
|
||||
WORK = sum(c * h for c, h in zip(LEAVES, HOPS)) # 771227648 per refined case
|
||||
NU = [U * ROOT_N / sw.RE * 2**level for level in range(DEPTH)] # [.0384, .0768, .1536]
|
||||
TAU = [0.5 + 3 * nu for nu in NU] # [.6152, .7304, .9608]
|
||||
SNAPSHOT_MAX_BYTES = 8 * 1024 * 1024
|
||||
SNAPSHOT_KEYS = ("level", "i", "j", "rho", "ux", "uy", "meta")
|
||||
|
||||
|
||||
def leaf_masks():
|
||||
"""Independent numpy leaf masks of the fixed preset (root, L1, L2 level-wide grids)."""
|
||||
masks, covered = [], None
|
||||
for level in range(DEPTH):
|
||||
size = ROOT_N * 2**level
|
||||
inside = np.ones((size, size), bool) if level == 0 else covered
|
||||
if level < DEPTH - 1:
|
||||
child = np.zeros((size, size), bool)
|
||||
for x0, y0, x1, y1 in REFINEMENTS[level]:
|
||||
child[y0:y1, x0:x1] = True
|
||||
masks.append(inside & ~child)
|
||||
covered = np.kron(child, np.ones((2, 2), bool))
|
||||
else:
|
||||
masks.append(inside)
|
||||
return masks
|
||||
|
||||
|
||||
def assess(base_control, finest_control, refined):
|
||||
"""shear_wave.assess generalized to depth 3 with a 4N finest control.
|
||||
|
||||
Same summaries, screens, budgets and outcome semantics; only the
|
||||
(depth, finest-control) shape contract differs.
|
||||
"""
|
||||
if base_control is None or finest_control is None or not refined:
|
||||
return dict(status="INVALID_CONTROL", reason="missing control or refined run")
|
||||
n = base_control["n"]
|
||||
if (base_control["depth"], finest_control["depth"], finest_control["n"]) != (
|
||||
1, 1, FINEST_FACTOR * n):
|
||||
raise ValueError("controls must be uniform N and uniform 4N")
|
||||
if any(r["depth"] != DEPTH or r["n"] != n for r in refined):
|
||||
raise ValueError("refined runs must be depth 3 at root N")
|
||||
if len({r.get("explosion") for r in refined}) != len(refined):
|
||||
raise ValueError("duplicate refined explosion scheme")
|
||||
sources = {repr(r.get("source")) for r in [base_control, finest_control, *refined]}
|
||||
base, fine = sw._usable(base_control), sw._usable(finest_control)
|
||||
if base is None or fine is None or len(sources) != 1:
|
||||
return dict(status="INVALID_CONTROL", reason="incomplete/invalid control or mixed source")
|
||||
controls_pass = base["own_screens_pass"] and fine["own_screens_pass"]
|
||||
report = dict(base_control=base, finest_control=fine, refined={},
|
||||
controls_pass=controls_pass)
|
||||
statuses = []
|
||||
for r in refined:
|
||||
try:
|
||||
s = sw.summarize(r)
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
report["refined"][r["explosion"]] = dict(valid=False, reason=str(error))
|
||||
statuses.append("INVALID_RUN")
|
||||
continue
|
||||
s["valid"] = True
|
||||
s["budget"] = {
|
||||
key: (None if base[key] is None else max(sw.BASELINE_FACTOR * base[key], floor))
|
||||
for key, floor in sw.FLOORS.items()
|
||||
}
|
||||
s["budget_pass"] = all(
|
||||
s[key] is not None and s["budget"][key] is not None and s[key] <= s["budget"][key]
|
||||
for key in sw.FLOORS)
|
||||
s["control_ratios"] = {
|
||||
name: {key: (s[key] / c[key] if s[key] is not None and c[key] else None)
|
||||
for key in (*sw.FLOORS, "spurious_pressure_rms")}
|
||||
for name, c in (("root", base), ("finest", fine))
|
||||
}
|
||||
s["controlled_pass"] = controls_pass and s["own_screens_pass"] and s["budget_pass"]
|
||||
report["refined"][r["explosion"]] = s
|
||||
statuses.append("PASS" if s["controlled_pass"] else "FAIL")
|
||||
if "INVALID_RUN" in statuses:
|
||||
report["status"] = "INVALID_RUN"
|
||||
elif not controls_pass:
|
||||
report["status"] = "CONTROL_SCREEN_FAIL"
|
||||
else:
|
||||
report["status"] = "PASS" if all(v == "PASS" for v in statuses) else "FAIL"
|
||||
return report
|
||||
|
||||
|
||||
def write_snapshot(path, arrays, masks, meta):
|
||||
"""Final macro snapshot: packed ACTIVE leaves (level, i, j), rho, ux, uy only.
|
||||
|
||||
No populations, no restart state, no time series. Returns the receipt block;
|
||||
a file over SNAPSHOT_MAX_BYTES is removed and reported, never kept.
|
||||
"""
|
||||
path = Path(path)
|
||||
if path.exists():
|
||||
raise FileExistsError(path)
|
||||
cols = {k: [] for k in ("level", "i", "j", "rho", "ux", "uy")}
|
||||
counts = []
|
||||
for level, (g, mask) in enumerate(zip(arrays, masks)):
|
||||
if g.shape != (9, *mask.shape):
|
||||
raise ValueError("population/mask shape mismatch")
|
||||
values = g[:, mask].astype(float)
|
||||
rho = values.sum(axis=0)
|
||||
j, i = np.nonzero(mask) # row = y index, column = x index
|
||||
cols["level"].append(np.full(rho.size, level, np.uint8))
|
||||
cols["i"].append(i.astype(np.uint16))
|
||||
cols["j"].append(j.astype(np.uint16))
|
||||
cols["rho"].append(rho)
|
||||
cols["ux"].append(CX @ values / rho)
|
||||
cols["uy"].append(CY @ values / rho)
|
||||
counts.append(int(rho.size))
|
||||
packed = {k: np.concatenate(v) for k, v in cols.items()}
|
||||
if not all(np.isfinite(packed[k]).all() for k in ("rho", "ux", "uy")):
|
||||
raise FloatingPointError("nonfinite snapshot macros")
|
||||
meta = dict(meta, leaf_counts=counts, cell_center="h*(index+0.5), h=2**-level",
|
||||
fields="rho, ux, uy from FP64 physical populations at ACTIVE leaves")
|
||||
with path.open("xb") as handle:
|
||||
np.savez_compressed(handle, meta=np.array(json.dumps(meta, sort_keys=True)), **packed)
|
||||
size = path.stat().st_size
|
||||
block = dict(file=path.name, bytes=size, leaf_counts=counts, max_bytes=SNAPSHOT_MAX_BYTES,
|
||||
step=meta.get("step"), tstar=meta.get("tstar"))
|
||||
if size > SNAPSHOT_MAX_BYTES:
|
||||
path.unlink()
|
||||
return dict(block, kept=False, sha256=None, reason="exceeds snapshot byte cap")
|
||||
block.update(kept=True, sha256=hashlib.sha256(path.read_bytes()).hexdigest())
|
||||
return block
|
||||
|
||||
|
||||
def read_snapshot(path):
|
||||
"""Validate a snapshot: exact keys, no populations, consistent packed arrays."""
|
||||
with np.load(path, allow_pickle=False) as data:
|
||||
if tuple(sorted(data.files)) != tuple(sorted(SNAPSHOT_KEYS)):
|
||||
raise ValueError("unexpected snapshot contents")
|
||||
out = {k: data[k] for k in data.files}
|
||||
meta = json.loads(str(out.pop("meta")))
|
||||
n = len(out["rho"])
|
||||
if any(len(v) != n for v in out.values()) or sum(meta["leaf_counts"]) != n:
|
||||
raise ValueError("inconsistent snapshot arrays")
|
||||
return meta, out
|
||||
|
||||
|
||||
def snapshot_meta(step, viscosity, explosion, source_revision):
|
||||
"""Exact time/phase/config identity stored inside the snapshot."""
|
||||
return dict(step=step, tstar=step * U / ROOT_N, phase=sw.PHI, mode=list(sw.MODE),
|
||||
polarization=sw.E.tolist(), amplitude=U, re_L=sw.RE, n=ROOT_N, depth=DEPTH,
|
||||
explosion=explosion, viscosity=viscosity, tau_by_level=TAU,
|
||||
refinements=REFINEMENTS, source_revision=source_revision,
|
||||
origin="level-wide zero-origin grids", time="root synchronization, postcollision")
|
||||
@@ -0,0 +1,34 @@
|
||||
"""First-gradient POST-G initialization counterfactual, not a qualification preset.
|
||||
|
||||
PRE F = feq - tau*A; POST G = feq - (tau - 1)*A. This low-Mach,
|
||||
first-order Chapman-Enskog approximation is not an exact discrete eigenstate.
|
||||
The physical viscous-stress factor tau - 1/2 is not the raw POST-G factor.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shear_nested as nested
|
||||
import shear_wave as sw
|
||||
from oracle import CX, CY, W, U, centers
|
||||
|
||||
REFINEMENTS = nested.REFINEMENTS
|
||||
CASES = ((100, 128, 1, "constant"), (100, 512, 1, "constant"),
|
||||
(100, 128, 3, "projected_linear"))
|
||||
INITIALIZATION_ID = "postg_first_gradient_v1"
|
||||
INITIALIZATION = (
|
||||
"physical FP64 POST G=feq-(tau-1)A at actual cell centers; "
|
||||
"A=3*w*U*cos(theta)*(c.e)*(c.k_level); rho=1; first-gradient low-Mach CE"
|
||||
)
|
||||
|
||||
|
||||
def initial(shape, level, length, viscosity):
|
||||
"""Return physical FP64 G; production initialize owns shifted FP32 encoding."""
|
||||
x, y = centers(shape, level)
|
||||
k = sw.wavevector(length)
|
||||
theta = k[0] * x + k[1] * y + sw.PHI
|
||||
k_level = k * 2.0**-level
|
||||
tau = 0.5 + 3 * viscosity * 2**level
|
||||
ce = CX * sw.E[0] + CY * sw.E[1]
|
||||
ck = CX * k_level[0] + CY * k_level[1]
|
||||
correction = (3 * W * ce * ck)[:, None, None] * U * np.cos(theta)
|
||||
return sw.initial(shape, level, length, viscosity) - (tau - 1) * correction
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Signed interface-probe traces for the nested/twin shear presets (mechanism diagnostic).
|
||||
|
||||
No pass gates. Native-validated 24-target layout through box 1, in root-128
|
||||
physical coordinates, crossing L0->L1 and (nested only) L1->L2 away from corners:
|
||||
horizontal line y = 56 across the vertical faces x = 16 and x = 22;
|
||||
vertical line x = 38 across the horizontal faces y = 16 and y = 22;
|
||||
normal coordinates NORMAL_COORDS on both (nested: 3 L0 + 5 L1 + 4 L2 each).
|
||||
Each target maps to the nearest ACTIVE leaf center (tie rule: distance, then
|
||||
finer level, then smaller j, then smaller i). Grids differ between cases
|
||||
(U128, U512, nested, twin), so actual centers, levels and displacements are
|
||||
stored and analytic fields are evaluated at the actual centers.
|
||||
Samples are POST-collision G at root synchronization only (root-step units);
|
||||
no substep content is observed, and grid-scale alternation can alias.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shear_wave as sw
|
||||
from oracle import CX, CY, U
|
||||
|
||||
ROOT_N = 128
|
||||
NORMAL_COORDS = (12.5, 14.5, 15.5, 16.25, 17.25, 19.25, 20.75, 21.75,
|
||||
22.125, 23.125, 24.125, 26.125)
|
||||
TRANSECTS = {"horizontal_y56": dict(axis=0, other=56.0), # x varies, y fixed
|
||||
"vertical_x38": dict(axis=1, other=38.0)} # y varies, x fixed
|
||||
STEPS = tuple(range(0, 129)) + tuple(range(1920, 2049)) # root steps, both inclusive
|
||||
TRACE_MAX_BYTES = 600_000
|
||||
|
||||
|
||||
def points():
|
||||
out = []
|
||||
for name, t in TRANSECTS.items():
|
||||
for along in NORMAL_COORDS:
|
||||
xy = (along, t["other"]) if t["axis"] == 0 else (t["other"], along)
|
||||
out.append((name, along, xy))
|
||||
return out
|
||||
|
||||
|
||||
def case_steps(n):
|
||||
"""Case lattice steps at the fixed root-physical sample times (U512: 4 per root step)."""
|
||||
return [s * (n // ROOT_N) for s in STEPS]
|
||||
|
||||
|
||||
def select(masks, n=ROOT_N):
|
||||
"""Nearest ACTIVE leaf center for each target (deterministic tie rule).
|
||||
|
||||
Targets and stored centers are in root-physical units (root cell = 1); a case
|
||||
with root N = n has cell size h = ROOT_N / (n * 2**level) there (U512: 0.25).
|
||||
"""
|
||||
if n % ROOT_N:
|
||||
raise ValueError("case root N must be a multiple of 128")
|
||||
probes = []
|
||||
for name, along, (x, y) in points():
|
||||
best = None
|
||||
for level, mask in enumerate(masks):
|
||||
h = ROOT_N / (n * 2 ** level)
|
||||
i0, j0 = int(math.floor(x / h)), int(math.floor(y / h))
|
||||
for j in range(j0 - 2, j0 + 3):
|
||||
for i in range(i0 - 2, i0 + 3):
|
||||
if 0 <= j < mask.shape[0] and 0 <= i < mask.shape[1] and mask[j, i]:
|
||||
cx, cy = h * (i + 0.5), h * (j + 0.5)
|
||||
key = (math.hypot(cx - x, cy - y), -level, j, i)
|
||||
if best is None or key < best[0]:
|
||||
best = (key, level, i, j, cx, cy)
|
||||
if best is None:
|
||||
raise ValueError(f"no ACTIVE leaf near target {(x, y)}")
|
||||
_, level, i, j, cx, cy = best
|
||||
probes.append(dict(transect=name, normal=along, point=[x, y], level=level, i=i, j=j,
|
||||
center=[cx, cy], displacement=[cx - x, cy - y]))
|
||||
if len({(p["level"], p["i"], p["j"]) for p in probes}) != len(probes):
|
||||
raise ValueError("probe targets map to duplicate cells")
|
||||
return probes
|
||||
|
||||
|
||||
def sample(arrays, probes):
|
||||
"""Flat [rho, ux, uy] per probe from FP64 physical populations."""
|
||||
row = []
|
||||
for p in probes:
|
||||
f = np.asarray(arrays[p["level"]][:, p["j"], p["i"]], dtype=float)
|
||||
rho = float(f.sum())
|
||||
row += [rho, float(CX @ f) / rho, float(CY @ f) / rho]
|
||||
return row
|
||||
|
||||
|
||||
def new_trace(probes, case_viscosity, case):
|
||||
"""Times/coordinates are root-physical (N=128); nu_root = U*128/Re for all cases."""
|
||||
return dict(schema=1, kind="mechanism diagnostic, no pass gates", case=case,
|
||||
time_unit="root step (N=128)", sample_phase="postcollision G at root synchronization",
|
||||
root_n=ROOT_N, viscosity_root=U * ROOT_N / sw.RE, case_viscosity=case_viscosity,
|
||||
mode=list(sw.MODE), phase=sw.PHI,
|
||||
amplitude=U, fields=["rho", "ux", "uy"], probes=probes, steps=[], values=[])
|
||||
|
||||
|
||||
def residuals(trace):
|
||||
"""Signed (drho, dux, duy) per step and probe, analytic field at each actual center."""
|
||||
out = []
|
||||
for step, row in zip(trace["steps"], trace["values"]):
|
||||
res = []
|
||||
for n, p in enumerate(trace["probes"]):
|
||||
x, y = p["center"]
|
||||
ux, uy, _ = sw.exact(x, y, step, trace["root_n"], trace["viscosity_root"])
|
||||
rho, vx, vy = row[3 * n: 3 * n + 3]
|
||||
res.append((rho - 1.0, vx - float(ux), vy - float(uy)))
|
||||
out.append(res)
|
||||
return out
|
||||
|
||||
|
||||
def validate_row(row, probe_count):
|
||||
"""Reject an invalid optional sample before it can enter a scalar receipt."""
|
||||
if len(row) != 3 * probe_count or not all(math.isfinite(v) for v in row):
|
||||
raise ValueError("malformed or nonfinite trace row")
|
||||
|
||||
|
||||
def validate(trace):
|
||||
"""Exact schedule, finite values, fixed probe count and byte cap."""
|
||||
import json
|
||||
if trace.get("steps") != list(STEPS) or len(trace["values"]) != len(STEPS):
|
||||
raise ValueError("trace steps differ from the fixed schedule")
|
||||
if len(trace["probes"]) != len(NORMAL_COORDS) * len(TRANSECTS):
|
||||
raise ValueError("unexpected probe count")
|
||||
for row in trace["values"]:
|
||||
validate_row(row, len(trace["probes"]))
|
||||
# Include the final byte-count field itself, including its number of digits.
|
||||
size = 0
|
||||
while True:
|
||||
final_size = len(json.dumps(dict(trace, bytes=size), allow_nan=False).encode())
|
||||
if final_size == size:
|
||||
break
|
||||
size = final_size
|
||||
if size > TRACE_MAX_BYTES:
|
||||
raise ValueError(f"trace exceeds {TRACE_MAX_BYTES} bytes ({size})")
|
||||
return size
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Depth-2 twin of the nested shear preset (opt-in twin_shear_n128).
|
||||
|
||||
Same two L0->L1 boxes as shear_nested, no L2. Four fresh cases: U128, U256
|
||||
(finest control = 2N), constant R128 depth 2, projected R128 depth 2; same
|
||||
wave, phase, Re100/U=0.03, initialization, times, eps(root N), limits, floors.
|
||||
|
||||
Twin vs nested measures the effect of adding this nested L2 refinement.
|
||||
Removing L2 also changes the finest spacing and refined volume, so the
|
||||
contrast does not isolate abstract depth. Physics is the unchanged depth-2,
|
||||
2N-control shear_wave.assess; shear_nested.py stays byte-identical.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import shear_nested as nested
|
||||
|
||||
ROOT_N = nested.ROOT_N
|
||||
DEPTH = 2
|
||||
FINEST_FACTOR = 2
|
||||
REFINEMENTS = [nested.REFINEMENTS[0]] # L0 -> L1 only, root coordinates
|
||||
CASES = ((100, ROOT_N, 1, "constant"), (100, FINEST_FACTOR * ROOT_N, 1, "constant"),
|
||||
(100, ROOT_N, DEPTH, "constant"), (100, ROOT_N, DEPTH, "projected_linear"))
|
||||
ROOT_STEPS = 16 * ROOT_N
|
||||
LEAVES = [7936, 33792]
|
||||
HOPS = [ROOT_STEPS * 2**level for level in range(DEPTH)] # [2048, 4096]
|
||||
WORK = sum(c * h for c, h in zip(LEAVES, HOPS)) # 154664960 per refined case
|
||||
NU = nested.NU[:DEPTH]
|
||||
TAU = nested.TAU[:DEPTH]
|
||||
|
||||
|
||||
def leaf_masks():
|
||||
"""Independent numpy leaf masks (root complement, full L1 boxes)."""
|
||||
child = np.zeros((ROOT_N, ROOT_N), bool)
|
||||
for x0, y0, x1, y1 in REFINEMENTS[0]:
|
||||
child[y0:y1, x0:x1] = True
|
||||
return [~child, np.kron(child, np.ones((2, 2), bool))]
|
||||
@@ -0,0 +1,20 @@
|
||||
"""One projected x-gap-width diagnostic (opt-in wide_x_shear_n128).
|
||||
|
||||
Keep the nested outer boxes and all three spacings. Shrink only L2 in x,
|
||||
so each x-normal middle gap grows from 6 to 12 root cells; y gaps stay 6.
|
||||
This also reduces L2 volume and moves its x faces. It is a descriptive
|
||||
geometry contrast with no new qualification matrix, assessor or pass gate.
|
||||
"""
|
||||
|
||||
import shear_nested as nested
|
||||
|
||||
ROOT_N = nested.ROOT_N
|
||||
DEPTH = nested.DEPTH
|
||||
REFINEMENTS = [nested.REFINEMENTS[0],
|
||||
[(56, 44, 96, 212), (160, 60, 200, 228)]] # parent L1 coordinates
|
||||
CASES = ((100, ROOT_N, DEPTH, "projected_linear"),)
|
||||
LEAVES = [7936, 20352, 53760]
|
||||
HOPS = nested.HOPS
|
||||
WORK = sum(c * h for c, h in zip(LEAVES, HOPS)) # 540016640 active updates
|
||||
NU = nested.NU
|
||||
TAU = nested.TAU
|
||||
@@ -0,0 +1,172 @@
|
||||
"""Fixed square geometry, native sampling, POST phase and runner admission; CPU only."""
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
import numpy as np
|
||||
import run_case as rc
|
||||
import shear_axis_square as axis
|
||||
import shear_wave as sw
|
||||
import test_shear_wave as legacy
|
||||
from oracle import CX, CY, W, U, centers
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
|
||||
|
||||
def masks(n, depth):
|
||||
h = build_hierarchy(rc.config(n, 100), [] if depth == 1 else axis.REFINEMENTS)
|
||||
np.testing.assert_array_equal(leaf_partition(h), np.ones((n, n)))
|
||||
return [k == ACTIVE for k in h.kinds]
|
||||
|
||||
|
||||
class AxisSquareTests(unittest.TestCase):
|
||||
def test_geometry_native_groups_and_work(self):
|
||||
work = 0
|
||||
for _, n, depth, _ in axis.CASES:
|
||||
m = masks(n, depth)
|
||||
p = axis.select(m, n)
|
||||
self.assertEqual(len(p), 20 if depth == 2 else 8*(n//128)**2)
|
||||
self.assertEqual(len({(v['level'], v['i'], v['j']) for v in p}), len(p))
|
||||
roots = [(32, 64), (95, 63), (64, 32), (63, 95),
|
||||
(31, 64), (96, 63), (64, 31), (63, 96)]
|
||||
expected = []
|
||||
for group, (x, y) in enumerate(roots):
|
||||
level = 1 if depth == 2 and group < 4 else 0
|
||||
factor = (n//128) * 2**level
|
||||
expected += [(level, factor*x+dx, factor*y+dy,
|
||||
[x+(dx+.5)/factor, y+(dy+.5)/factor])
|
||||
for dy in range(factor) for dx in range(factor)]
|
||||
self.assertEqual([(v['level'], v['i'], v['j'], v['center']) for v in p], expected)
|
||||
for name in axis.GROUPS:
|
||||
group = [v for v in p if v['group'] == name]
|
||||
self.assertEqual(sum(v['area'] for v in group), 1)
|
||||
np.testing.assert_array_equal(np.mean([v['center'] for v in group], axis=0),
|
||||
np.array(axis.GROUPS[name])+.5)
|
||||
for v in group:
|
||||
self.assertTrue(m[v['level']][v['j'], v['i']])
|
||||
w = rc.workload(m, axis.case_steps(n)[-1])
|
||||
work += w['active_collision_updates']
|
||||
if depth == 2:
|
||||
self.assertEqual(w['active_cells_by_level'], [12288, 16384])
|
||||
self.assertEqual(w['level_hops'], [8, 16])
|
||||
self.assertEqual(axis.case_steps(n), [r*(n//128) for r in range(9)])
|
||||
self.assertEqual(work, 1900544)
|
||||
|
||||
def test_postg_moments_stress_and_matching_fine_control(self):
|
||||
for level in (0, 1):
|
||||
x, _ = centers((33, 41), level)
|
||||
k, h, tau = 4*np.pi/128, 2.**-level, .5+3*.0384*2**level
|
||||
uy = U*np.sin(k*x+np.pi/4)
|
||||
eq = sw.equilibrium(np.ones_like(x), np.zeros_like(x), uy)
|
||||
g = axis.initial(x.shape, level, 128, .0384)
|
||||
a = 3*W[:, None, None]*CX[:, None, None]*CY[:, None, None]*h*k*U*np.cos(k*x+np.pi/4)
|
||||
np.testing.assert_allclose(g, eq-(tau-1)*a, rtol=0, atol=6e-17)
|
||||
for v in (np.ones(9), CX, CY):
|
||||
np.testing.assert_allclose(np.einsum('q,qyx->yx', v, g-eq), 0, rtol=0, atol=2e-16)
|
||||
np.testing.assert_allclose(np.einsum('q,qyx->yx', CX*CY, g-eq),
|
||||
(1-tau)*h*k*U*np.cos(k*x+np.pi/4)/3, atol=6e-17)
|
||||
self.assertGreater(g.min(), 0)
|
||||
np.testing.assert_array_equal(axis.initial((256, 256), 0, 256, .0768),
|
||||
axis.initial((256, 256), 1, 128, .0384))
|
||||
|
||||
def test_signed_momentum_and_actual_center_time(self):
|
||||
m = masks(128, 2)
|
||||
probes = axis.select(m)
|
||||
trace = axis.new_trace(probes, .0384, [rc.AXIS_SQUARE, 128, 2, 'projected_linear'])
|
||||
for step in axis.STEPS:
|
||||
arrays = []
|
||||
for level, mask in enumerate(m):
|
||||
x, _ = centers(mask.shape, level)
|
||||
uy, _ = axis.exact(x, step, 128, .0384)
|
||||
arrays.append(sw.equilibrium(np.full_like(x, 1.001), np.zeros_like(x), uy))
|
||||
trace['steps'].append(step)
|
||||
trace['values'].append(axis.sample(arrays, probes))
|
||||
trace['bytes'] = axis.validate(trace)
|
||||
self.assertEqual(trace['bytes'], len(json.dumps(trace, allow_nan=False).encode()))
|
||||
for row in trace['group_moments']:
|
||||
for name, v in row['groups'].items():
|
||||
self.assertAlmostEqual(v['drho'], .001, places=14)
|
||||
x = np.array([p['center'][0] for p in probes if p['group'] == name])
|
||||
uy, gradient = axis.exact(x, row['root_step'], 128, .0384)
|
||||
self.assertAlmostEqual(v['djy'], .001*uy.mean(), places=14)
|
||||
self.assertAlmostEqual(v['mean_duy_dx'], gradient.mean(), places=14)
|
||||
self.assertAlmostEqual(row['groups']['inner_left']['mean_duy_dx'],
|
||||
-row['groups']['inner_bottom']['mean_duy_dx'], places=14)
|
||||
with patch.object(axis, 'TRACE_MAX_BYTES', trace['bytes']-1), self.assertRaises(ValueError):
|
||||
axis.validate(trace)
|
||||
for mutate in (lambda t: t['steps'].pop(),
|
||||
lambda t: t['steps'].__setitem__(1, 0),
|
||||
lambda t: t['values'][1].__setitem__(0, float('nan')),
|
||||
lambda t: t['probes'].pop()):
|
||||
bad = deepcopy(trace); mutate(bad)
|
||||
with self.assertRaises(ValueError): axis.validate(bad)
|
||||
|
||||
def test_four_full_mock_schedules_and_old_rejection(self):
|
||||
helper = legacy.ProbeTraceTests()
|
||||
helper.setUp()
|
||||
def arrays(m, n, t):
|
||||
return [axis.initial(v.shape, level, n, U*n/100) for level, v in enumerate(m)]
|
||||
for _, n, depth, scheme in axis.CASES:
|
||||
with patch.object(helper, 'exact_arrays', side_effect=arrays):
|
||||
r, counts, files = helper.run_mocked(n, depth, rc.AXIS_SQUARE, scheme,
|
||||
masks(n, depth), initializer=axis.initial)
|
||||
self.assertEqual(counts[-1], 8*(n//128))
|
||||
self.assertEqual([v['step'] for v in r['rows']], [0, 8*(n//128)])
|
||||
self.assertEqual(r['probe_trace']['steps'], list(range(9)))
|
||||
self.assertEqual(r['probe_trace']['probes'], axis.select(masks(n, depth), n))
|
||||
self.assertEqual(r['mode'], [2, 0])
|
||||
self.assertEqual(r['phase'], np.pi/4)
|
||||
self.assertEqual(r['initialization'], axis.INITIALIZATION)
|
||||
self.assertEqual(r['limits'], {})
|
||||
self.assertIsNone(r['screening_pass'])
|
||||
self.assertFalse(r['qualification_eligible'])
|
||||
self.assertEqual(files, ['probe.json'])
|
||||
old = legacy.NestedPresetTests(); old.setUp()
|
||||
records = old.nested_set(); records[-1]['diagnostic'] = rc.AXIS_SQUARE
|
||||
self.assertEqual(old.mod.assess_nested(records, 'r')['status'], 'INVALID_SET')
|
||||
records = [legacy.receipt(128, 1), legacy.receipt(256, 1), legacy.receipt(128, 2),
|
||||
legacy.receipt(128, 2, 'projected_linear')]
|
||||
records[-1].update(diagnostic=rc.AXIS_SQUARE, initialization=axis.INITIALIZATION)
|
||||
self.assertEqual(old.mod.assess(records, 'r')['status'], 'INVALID_SET')
|
||||
self.assertNotIn(rc.AXIS_SQUARE, old.mod.PRESETS)
|
||||
with patch.object(axis, 'sample', return_value=[float('nan')]*24):
|
||||
r, _, _ = helper.run_mocked(128, 1, rc.AXIS_SQUARE, 'constant', masks(128, 1),
|
||||
initializer=axis.initial)
|
||||
self.assertIn('error', r['probe_trace'])
|
||||
self.assertTrue(r['completed'])
|
||||
self.assertEqual(len(r['rows']), 2)
|
||||
|
||||
def test_admission_and_module_pin(self):
|
||||
from tests.validation.multigrid import check_projected_pairs as gate
|
||||
self.assertEqual(hashlib.sha256(Path(axis.__file__).read_bytes()).hexdigest(), rc.SHEAR_AXIS_SQUARE_SHA256)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ['run_case.py', '--repo', str(REPO), '--expected-revision', 'f'*40,
|
||||
'--output', str(Path(tmp)/'case.json'), '--dry-run', '--re', '100',
|
||||
'--flow', 'shear_wave', '--diagnostic', rc.AXIS_SQUARE, '--probe-trace']
|
||||
for n, depth, scheme, valid in [(n, d, s, True) for _, n, d, s in axis.CASES] + [
|
||||
(512, 1, 'constant', False), (256, 2, 'projected_linear', False), (128, 3, 'constant', False)]:
|
||||
for extra in ([], ['--final-snapshot'], ['--early-pressure'], ['--flow', 'taylor_green'], ['--re', '1000']):
|
||||
argv = base + ['--n', str(n), '--depth', str(depth), '--explosion', scheme] + extra
|
||||
with patch.object(sys, 'argv', argv), redirect_stdout(io.StringIO()), patch('sys.stderr', io.StringIO()), \
|
||||
patch.object(rc, 'verify_source', return_value={}) as verify, patch.object(rc, 'runtime_source', return_value={}):
|
||||
if valid and not extra:
|
||||
self.assertEqual(rc.main(), 0)
|
||||
verify.assert_called_once_with(REPO, 'f'*40, 'shear_axis_square', probes=True)
|
||||
else:
|
||||
with self.assertRaises(SystemExit): rc.main()
|
||||
verify.assert_not_called()
|
||||
with patch.object(gate, 'preflight', return_value=rc.NUMERICAL_REVISION), \
|
||||
patch.object(rc, 'provenance', return_value=dict(source_tree_sha256=rc.SOURCE_SHA256)):
|
||||
rc.verify_source(REPO, 'r', 'shear_axis_square', probes=True)
|
||||
for module, key, value in ((axis, '__file__', '/wrong/helper.py'), (rc, 'SHEAR_AXIS_SQUARE_SHA256', 'wrong')):
|
||||
with patch.object(module, key, value), self.assertRaisesRegex(RuntimeError, 'shear_axis_square'):
|
||||
rc.verify_source(REPO, 'r', 'shear_axis_square', probes=True)
|
||||
@@ -17,6 +17,7 @@ from unittest.mock import patch
|
||||
import numpy as np
|
||||
|
||||
import run_case
|
||||
import assess_projected_pairs as assessor
|
||||
from assess_projected_pairs import (BUILD_SOURCES, CASES, NUMERICS, NUMERICAL_REVISION,
|
||||
SOURCE_SHA256, assess)
|
||||
from oracle import LIMITS, TIMES, U, rectangles
|
||||
@@ -31,7 +32,7 @@ RUNTIME = dict(device=dict(name="host mock", pci_bus_id="mock", capability=[7, 0
|
||||
|
||||
def source():
|
||||
return dict(revision=MOCK_REVISION, dirty=False, source_tree_sha256=SOURCE_SHA256,
|
||||
harness_sha256={"run_case.py": "a" * 64},
|
||||
harness_sha256={"run_case.py": "a" * 64, "oracle.py": "c" * 64},
|
||||
build_capture_sha256=run_case.BUILD_CAPTURE_SHA256)
|
||||
|
||||
|
||||
@@ -42,12 +43,14 @@ def build_metadata(size):
|
||||
arch="sm_70", flags=["--use_fast_math"], ptx_sha256="b" * 64)
|
||||
|
||||
|
||||
def records(early=False):
|
||||
def records(early=False, diagnostic=None):
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
|
||||
out = []
|
||||
for re, n, depth, scheme in CASES:
|
||||
if diagnostic:
|
||||
n *= 2
|
||||
cfg = run_case.config(n, re)
|
||||
refinements = [] if depth == 1 else rectangles(n, depth)
|
||||
h = build_hierarchy(cfg, refinements)
|
||||
@@ -66,6 +69,8 @@ def records(early=False):
|
||||
early_pressure=dict(enabled=early, rows=[
|
||||
dict(step=s, tstar=s*U/n, pressure_l2=.01 if s not in main_steps else 0.0) for s in early_steps],
|
||||
sampled_max_pressure_l2=.01 if early else None)))
|
||||
if diagnostic:
|
||||
out[-1]["diagnostic"] = diagnostic
|
||||
return out
|
||||
|
||||
|
||||
@@ -251,7 +256,7 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
def test_pinned_build_sources_match_host_generated_inputs(self):
|
||||
from CelerisLab.cuda import compiler_v2
|
||||
|
||||
for size in (128, 256):
|
||||
for size in (128, 256, 512):
|
||||
cfg = replace(run_case.config(size, 100), nx=10, ny=10)
|
||||
cfg.validate()
|
||||
root = compiler_v2.generate_config(cfg)
|
||||
@@ -263,6 +268,111 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
finally:
|
||||
compiler_v2.cleanup_build_dir(root)
|
||||
|
||||
def test_scaled_geometry_work_and_frozen_gates(self):
|
||||
data = records(True, run_case.SCALED_DIAGNOSTIC)
|
||||
self.assertEqual(sum(r["workload"]["active_collision_updates"] for r in data), 4714397696)
|
||||
self.assertEqual([r["viscosity"] for r in data], [.0768, .1536, .0768, .0768])
|
||||
self.assertEqual(data[-1]["tau_by_level"], [.7303999999999999, .9608])
|
||||
for r in data:
|
||||
self.assertEqual([row["tstar"] for row in r["rows"]], TIMES)
|
||||
self.assertEqual(len(r["early_pressure"]["rows"]), 65)
|
||||
if r["depth"] == 2:
|
||||
self.assertEqual(r["refinements"], [[(32, 32, 224, 192)]])
|
||||
self.assertEqual(r["workload"]["active_cells_by_level"], [34816, 122880])
|
||||
self.assertEqual(r["workload"]["level_hops"], [4096, 8192])
|
||||
result = assess(data, MOCK_REVISION, diagnostic=run_case.SCALED_DIAGNOSTIC)
|
||||
self.assertTrue(result["passed"])
|
||||
self.assertEqual(result["floors"], dict(velocity_l2=.002, pressure_l2=.02, energy_relative=.004))
|
||||
self.assertTrue(all(row["floor_dominates"] for row in result["comparisons"]))
|
||||
data[-1]["rows"][-1]["velocity_l2"] = .00200001
|
||||
self.assertFalse(assess(data, diagnostic=run_case.SCALED_DIAGNOSTIC)["passed"])
|
||||
self.assertFalse(assess(data)["passed"])
|
||||
data[-1]["rows"][-1]["velocity_l2"] = 0.0
|
||||
for mutate in (lambda r: r.pop("diagnostic"), lambda r: r.update(diagnostic="wrong"),
|
||||
lambda r: r["source"].update(revision="wrong"),
|
||||
lambda r: r["early_pressure"].update(enabled=False),
|
||||
lambda r: r.update(refinements=[[(32, 32, 224, 200)]])):
|
||||
bad = deepcopy(data)
|
||||
mutate(bad[-1])
|
||||
self.assertFalse(assess(bad, MOCK_REVISION, diagnostic=run_case.SCALED_DIAGNOSTIC)["passed"])
|
||||
|
||||
def test_raw_resolution_changes_and_reference_identity(self):
|
||||
old, new = records(True), records(True, run_case.SCALED_DIAGNOSTIC)
|
||||
for r in old:
|
||||
r["source"]["revision"] = assessor.REFERENCE_REVISION
|
||||
old[-2]["rows"][-1]["velocity_l2"] = .004 # historical constant failure remains usable
|
||||
old[-1]["rows"][-1]["velocity_l2"] = .001
|
||||
new[-1]["rows"][-1]["velocity_l2"] = .0015 # floor pass, but worse raw error
|
||||
result = assessor.resolution_trend(new, old, MOCK_REVISION)
|
||||
self.assertTrue(result["valid"])
|
||||
self.assertTrue(result["trend_interpretation_ready"])
|
||||
self.assertFalse(result["reference_scheme_results"]["constant"]["passed"])
|
||||
self.assertEqual((len(result["per_time"]), len(result["worst_post_initial"]),
|
||||
len(result["uniform256_bridge"])), (80, 16, 20))
|
||||
row = next(r for r in result["per_time"] if r["role"] == "projected_linear"
|
||||
and r["metric"] == "velocity_l2" and r["tstar"] == .48)
|
||||
self.assertEqual((row["signed_change"], row["new_to_old"], row["change_percent"]), (.0005, 1.5, 50.))
|
||||
self.assertTrue(assess(new, diagnostic=run_case.SCALED_DIAGNOSTIC)["passed"])
|
||||
self.assertIsNone(result["per_time"][0]["new_to_old"]) # zero denominator is explicit
|
||||
self.assertEqual(result["per_time"][0]["ratio_unavailable_reason"], "zero historical error")
|
||||
new[-1]["rows"][-1]["velocity_l2"] = .06 # retain raw errors even when absolute screen fails
|
||||
self.assertTrue(assessor.resolution_trend(new, old, MOCK_REVISION)["valid"])
|
||||
self.assertFalse(assess(new, diagnostic=run_case.SCALED_DIAGNOSTIC)["passed"])
|
||||
new[-1]["rows"][-1]["velocity_l2"] = .0015
|
||||
for sample in ("rows", "early_pressure"):
|
||||
bad = deepcopy(new)
|
||||
if sample == "rows":
|
||||
bad[0]["rows"][-1]["velocity_l2"] = .0001
|
||||
else:
|
||||
bad[0]["early_pressure"]["rows"][1]["pressure_l2"] = .009
|
||||
result = assessor.resolution_trend(bad, old, MOCK_REVISION)
|
||||
self.assertTrue(result["valid"])
|
||||
self.assertFalse(result["trend_interpretation_ready"])
|
||||
for mutate in (lambda r: r["source"].update(revision=MOCK_REVISION),
|
||||
lambda r: r["source"].update(source_tree_sha256="wrong"),
|
||||
lambda r: r["source"]["harness_sha256"].update(**{"oracle.py": "wrong"}),
|
||||
lambda r: r.update(diagnostic=run_case.SCALED_DIAGNOSTIC),
|
||||
lambda r: r.update(refinements=[]), lambda r: r["rows"].pop()):
|
||||
bad = deepcopy(old)
|
||||
mutate(bad[-1])
|
||||
self.assertFalse(assessor.resolution_trend(new, bad, MOCK_REVISION)["valid"])
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
paths = [Path(tmp) / f"{i}.json" for i in range(4)]
|
||||
for path, receipt in zip(paths, old):
|
||||
path.write_text(json.dumps(receipt))
|
||||
with self.assertRaisesRegex(ValueError, "receipt hashes"):
|
||||
assessor.load_reference(paths)
|
||||
witnesses = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in paths}
|
||||
with patch.object(assessor, "REFERENCE_RECEIPTS", witnesses):
|
||||
self.assertEqual(assessor.load_reference(paths), json.loads(json.dumps(old)))
|
||||
with self.assertRaises(ValueError):
|
||||
assessor.load_reference(paths[:-1] + paths[:1])
|
||||
paths[-1].write_text(paths[-1].read_text() + " ")
|
||||
with self.assertRaises(ValueError):
|
||||
assessor.load_reference(paths)
|
||||
|
||||
def test_scaled_runner_requires_explicit_bounded_diagnostic(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(REPO), "--expected-revision", MOCK_REVISION,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run"]
|
||||
diagnostic = ["--diagnostic", run_case.SCALED_DIAGNOSTIC, "--early-pressure"]
|
||||
for n, depth, re, extra, valid in (
|
||||
(512, 1, 100, [], False), (512, 1, 100, diagnostic, True),
|
||||
(256, 2, 100, diagnostic, True), (128, 2, 100, diagnostic, False),
|
||||
(256, 3, 100, diagnostic, False), (256, 2, 1000, diagnostic, False),
|
||||
(256, 2, 100, diagnostic[:2], False), (512, 2, 100, diagnostic, False)):
|
||||
argv = base + ["--n", str(n), "--depth", str(depth), "--re", str(re)] + extra
|
||||
with self.subTest(argv=argv), patch.object(sys, "argv", argv), \
|
||||
patch.object(run_case, "verify_source", return_value=source()) as verify, \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
else:
|
||||
with self.assertRaises(SystemExit) as caught:
|
||||
run_case.main()
|
||||
self.assertEqual(caught.exception.code, 2)
|
||||
verify.assert_not_called()
|
||||
|
||||
def test_source_preflight_requires_clean_exact_revision_and_tree(self):
|
||||
import oracle
|
||||
from tests.validation.multigrid import check_projected_pairs as gate
|
||||
@@ -319,7 +429,7 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
patch.object(run_case, "runtime_identity", side_effect=AssertionError("CUDA reached")), \
|
||||
redirect_stdout(stdout):
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green")
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green", probes=False)
|
||||
self.assertTrue(json.loads(stdout.getvalue())["dry_run"])
|
||||
self.assertFalse(output.exists())
|
||||
|
||||
@@ -349,9 +459,15 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
|
||||
def test_mocked_runner_default_early_and_failure(self):
|
||||
# Replace the GPU context and steppers before import. No CUDA module or flow is used.
|
||||
for early, depth, fail in ((False, 1, None), (False, 2, None), (True, 2, None),
|
||||
cases = [(early, depth, fail, 128, None, "projected_linear" if early else "constant")
|
||||
for early, depth, fail in
|
||||
((False, 1, None), (False, 2, None), (True, 2, None),
|
||||
(True, 2, "resource"), (True, 2, "nonfinite"),
|
||||
(True, 2, "probe_nonfinite")):
|
||||
(True, 2, "probe_nonfinite"))]
|
||||
cases += [(True, depth, None, n, run_case.SCALED_DIAGNOSTIC, scheme)
|
||||
for n, depth, scheme in ((256, 1, "constant"), (512, 1, "constant"),
|
||||
(256, 2, "constant"), (256, 2, "projected_linear"))]
|
||||
for early, depth, fail, n, diagnostic, scheme in cases:
|
||||
calls, selectors = [], []
|
||||
|
||||
class FakeRun:
|
||||
@@ -395,11 +511,14 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output = Path(tmp) / "case.json"
|
||||
argv = ["run_case.py", "--repo", str(REPO), "--re", "100", "--n", "128",
|
||||
argv = ["run_case.py", "--repo", str(REPO), "--re", "100", "--n", str(n),
|
||||
"--depth", str(depth), "--output", str(output),
|
||||
"--expected-revision", MOCK_REVISION, "--run-gpu"]
|
||||
argv += ["--explosion", scheme]
|
||||
if early:
|
||||
argv += ["--early-pressure", "--explosion", "projected_linear"]
|
||||
argv += ["--early-pressure"]
|
||||
if diagnostic:
|
||||
argv += ["--diagnostic", diagnostic]
|
||||
with patch.dict(sys.modules, modules), patch.object(sys, "argv", argv), \
|
||||
patch.dict("os.environ", CUDA_VISIBLE_DEVICES="mock"), \
|
||||
patch.object(run_case, "verify_source", return_value=source()) as verify, \
|
||||
@@ -412,16 +531,17 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
else:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
receipt = json.loads(output.read_text())
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green")
|
||||
self.assertEqual(selectors, ["projected_linear" if early else "constant"] if depth == 2 else [])
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green", probes=False)
|
||||
self.assertEqual(selectors, [scheme] if depth == 2 else [])
|
||||
self.assertEqual(receipt["source"], source())
|
||||
self.assertEqual(receipt["runtime_identity"], RUNTIME)
|
||||
self.assertEqual(receipt["completed"], not fail)
|
||||
self.assertEqual(receipt.get("diagnostic"), diagnostic)
|
||||
self.assertEqual(calls[:2], [0, 1]) # READY probe is part of this trajectory
|
||||
if not fail:
|
||||
self.assertEqual([r["tstar"] for r in receipt["rows"]], TIMES)
|
||||
self.assertEqual(len(receipt["early_pressure"]["rows"]), 65 if early else 0)
|
||||
self.assertEqual(calls[-1], 2048)
|
||||
self.assertEqual(calls[-1], 16*n)
|
||||
else:
|
||||
self.assertEqual(len(receipt["rows"]), 1)
|
||||
self.assertEqual(len(receipt["early_pressure"]["rows"]),
|
||||
|
||||
@@ -4,8 +4,13 @@ Run from this directory with PYTHONPATH=<checkout>/src (same as test_oracle.py).
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import tempfile
|
||||
from contextlib import redirect_stdout
|
||||
from unittest.mock import patch
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -361,7 +366,7 @@ class RunnerSampleTests(unittest.TestCase):
|
||||
self.assertEqual(row["leaf_cells"], N * N)
|
||||
|
||||
|
||||
def receipt(n, depth, explosion="constant"):
|
||||
def receipt(n, depth, explosion="constant", diagnostic=None):
|
||||
"""Synthetic receipt satisfying every contract field of the fixed four-case set."""
|
||||
import assess_projected_pairs as tg
|
||||
steps = run_case.sample_steps(n)[0]
|
||||
@@ -369,7 +374,7 @@ def receipt(n, depth, explosion="constant"):
|
||||
for row, step in zip(rows, steps):
|
||||
row.update(step=step, tstar=step * U / n,
|
||||
spurious_pressure_scaled_by_level=[row["spurious_pressure_scaled"]] * depth)
|
||||
cells = [n * n] if depth == 1 else [8704, 30720]
|
||||
cells = [n * n] if depth == 1 else [17 * n * n // 32, 15 * n * n // 8]
|
||||
hops = [16 * n * 2**level for level in range(depth)]
|
||||
builds = []
|
||||
for level in range(depth):
|
||||
@@ -406,7 +411,8 @@ def receipt(n, depth, explosion="constant"):
|
||||
active_collision_updates=sum(c * h for c, h in zip(cells, hops))),
|
||||
actual_build_sidecars=builds, rows=rows, completed=True,
|
||||
flow="shear_wave", mode=list(sw.MODE), polarization=sw.E.tolist(),
|
||||
angle_deg=sw.ANGLE_DEG, phase=sw.PHI, eps=sw.eps(n))
|
||||
angle_deg=sw.ANGLE_DEG, phase=sw.PHI, eps=sw.eps(n),
|
||||
**({"diagnostic": diagnostic} if diagnostic else {}))
|
||||
|
||||
|
||||
class ContractTests(unittest.TestCase):
|
||||
@@ -421,6 +427,19 @@ class ContractTests(unittest.TestCase):
|
||||
self.assertTrue(result["contract_valid"], [k for k, v in result["checks"].items() if not v])
|
||||
self.assertEqual(result["status"], "PASS")
|
||||
|
||||
def test_eps_metadata_is_portable_but_bounded(self):
|
||||
import assess_shear_wave
|
||||
self.assertTrue(assess_shear_wave.close_ulps(0.012047856934923534, 0.012047856934923532))
|
||||
exact = sw.eps(128)
|
||||
for value, ok in ((math.nextafter(exact, 1.0), True),
|
||||
(math.nextafter(math.nextafter(exact, 0.0), 0.0), True),
|
||||
(exact * (1 + 1e-12), False), (exact * 2, False),
|
||||
(str(exact), False), (float("nan"), False), (None, False)):
|
||||
records = [dict(r) for r in self.records]
|
||||
records[2]["eps"] = value
|
||||
status = self.assess(records, "r")["status"]
|
||||
self.assertEqual(status == "PASS", ok, (value, status))
|
||||
|
||||
def test_reproduced_false_passes_are_rejected(self):
|
||||
a = self.assess
|
||||
self.assertEqual(a(self.records[:2], "r")["status"], "INVALID_SET") # empty refined
|
||||
@@ -448,6 +467,89 @@ class ContractTests(unittest.TestCase):
|
||||
self.assertEqual(a(self.records, "wrong-revision")["status"], "INVALID_SET")
|
||||
|
||||
|
||||
def scaled_set(rate_error=0.0):
|
||||
d = run_case.SCALED_SHEAR
|
||||
out = [receipt(256, 1, diagnostic=d), receipt(512, 1, diagnostic=d),
|
||||
receipt(256, 2, diagnostic=d), receipt(256, 2, "projected_linear", diagnostic=d)]
|
||||
if rate_error:
|
||||
out[3]["rows"] = rows_for(256, rate_error=rate_error)
|
||||
for row, step in zip(out[3]["rows"], run_case.sample_steps(256)[0]):
|
||||
row.update(step=step, tstar=step * U / 256,
|
||||
spurious_pressure_scaled_by_level=[row["spurious_pressure_scaled"]] * 2)
|
||||
return out
|
||||
|
||||
|
||||
class ScaledContractTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
import assess_shear_wave
|
||||
self.mod = assess_shear_wave
|
||||
|
||||
def test_scaled_contract_and_signed_excess(self):
|
||||
result = self.mod.assess(scaled_set(1.5e-3), "r", scaled=True)
|
||||
self.assertTrue(result["contract_valid"], [k for k, v in result["checks"].items() if not v])
|
||||
self.assertEqual([c[1] for c in result["cases"]], [256, 512, 256, 256])
|
||||
projected = result["signed"]["projected_linear"]
|
||||
self.assertAlmostEqual(projected["signed_rate_bias"], 1.5e-3, delta=1e-9)
|
||||
self.assertAlmostEqual(projected["excess_over_uniform_root"]["signed_rate_bias"], 1.5e-3,
|
||||
delta=1e-9)
|
||||
self.assertIn("excess_over_uniform_fine", projected)
|
||||
|
||||
def test_scaled_and_default_sets_do_not_mix(self):
|
||||
n128 = [receipt(128, 1), receipt(256, 1), receipt(128, 2), receipt(128, 2, "projected_linear")]
|
||||
self.assertEqual(self.mod.assess(n128, "r", scaled=True)["status"], "INVALID_SET")
|
||||
self.assertEqual(self.mod.assess(scaled_set(), "r")["status"], "INVALID_SET")
|
||||
unlabeled = scaled_set()
|
||||
unlabeled[0].pop("diagnostic")
|
||||
self.assertEqual(self.mod.assess(unlabeled, "r", scaled=True)["status"], "INVALID_SET")
|
||||
tg_label = scaled_set()
|
||||
tg_label[2]["diagnostic"] = run_case.SCALED_DIAGNOSTIC
|
||||
self.assertEqual(self.mod.assess(tg_label, "r", scaled=True)["status"], "INVALID_SET")
|
||||
|
||||
def test_resolution_trend_and_reference_pin(self):
|
||||
reference = [receipt(128, 1), receipt(256, 1), receipt(128, 2),
|
||||
receipt(128, 2, "projected_linear")]
|
||||
new = self.mod.assess(scaled_set(1.5e-3), "r", scaled=True)
|
||||
trend = self.mod.resolution_trend(new, reference, "r")
|
||||
self.assertTrue(trend["valid"])
|
||||
change = trend["changes"]["projected_linear"]["signed_rate_bias"]
|
||||
self.assertAlmostEqual(change["signed_change"], 1.5e-3, delta=1e-9)
|
||||
self.assertIn("excess_over_uniform_fine.residual_l2", trend["changes"]["constant"])
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
paths = []
|
||||
for i, r in enumerate(reference):
|
||||
paths.append(Path(tmp) / f"{i}.json")
|
||||
paths[-1].write_text(json.dumps(r))
|
||||
with self.assertRaises(ValueError):
|
||||
self.mod.load_reference(paths)
|
||||
|
||||
def test_scaled_runner_options(self):
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100"]
|
||||
shear = ["--flow", "shear_wave"]
|
||||
scaled = ["--diagnostic", run_case.SCALED_SHEAR]
|
||||
for extra, valid in (
|
||||
(["--n", "512", "--depth", "1"] + shear + scaled, True),
|
||||
(["--n", "256", "--depth", "2", "--explosion", "projected_linear"] + shear + scaled, True),
|
||||
(["--n", "512", "--depth", "1"] + shear, False),
|
||||
(["--n", "128", "--depth", "2"] + shear + scaled, False),
|
||||
(["--n", "512", "--depth", "1"] + scaled, False), # needs shear flow
|
||||
(["--n", "512", "--depth", "1", "--early-pressure", "--diagnostic",
|
||||
run_case.SCALED_DIAGNOSTIC] + shear, False), # mutually exclusive
|
||||
(["--n", "256", "--depth", "2", "--early-pressure"] + shear + scaled, False)):
|
||||
argv = base + extra
|
||||
with self.subTest(argv=argv), patch.object(sys, "argv", argv), \
|
||||
patch.object(run_case, "verify_source", return_value={}), \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
|
||||
|
||||
class DefaultPathTests(unittest.TestCase):
|
||||
def test_frozen_oracle_is_byte_identical(self):
|
||||
digest = hashlib.sha256((HERE / "oracle.py").read_bytes()).hexdigest()
|
||||
@@ -469,3 +571,846 @@ class DefaultPathTests(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class NestedPresetTests(unittest.TestCase):
|
||||
"""Fixed three-level/two-region shear preset (nested_shear_n128)."""
|
||||
|
||||
def setUp(self):
|
||||
import assess_shear_wave
|
||||
import shear_nested
|
||||
self.mod, self.nested = assess_shear_wave, shear_nested
|
||||
|
||||
def test_exact_geometry_work_and_levels(self):
|
||||
nested = self.nested
|
||||
masks = nested.leaf_masks()
|
||||
self.assertEqual([int(m.sum()) for m in masks], [7936, 12288, 86016])
|
||||
self.assertEqual(nested.LEAVES, [7936, 12288, 86016])
|
||||
self.assertEqual(nested.HOPS, [2048, 4096, 8192])
|
||||
self.assertEqual(nested.WORK, 771227648)
|
||||
uniform = 128 * 128 * 2048 + 512 * 512 * 8192
|
||||
self.assertEqual(uniform + 2 * nested.WORK, 3723493376)
|
||||
np.testing.assert_allclose(nested.NU, [.0384, .0768, .1536], rtol=0, atol=1e-15)
|
||||
np.testing.assert_allclose(nested.TAU, [.6152, .7304, .9608], rtol=0, atol=1e-14)
|
||||
self.assertEqual(sum(float(m.sum()) * 0.25**l for l, m in enumerate(masks)), 128.0**2)
|
||||
# Production host hierarchy builds the same leaves from the same rectangles.
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
h = build_hierarchy(run_case.config(128, 100), nested.REFINEMENTS)
|
||||
np.testing.assert_array_equal(leaf_partition(h), np.ones((128, 128)))
|
||||
for mine, kinds in zip(masks, h.kinds):
|
||||
np.testing.assert_array_equal(mine, kinds == ACTIVE)
|
||||
|
||||
def test_historical_shear_identity_is_preserved(self):
|
||||
digest = hashlib.sha256((HERE / "shear_wave.py").read_bytes()).hexdigest()
|
||||
self.assertEqual(digest, "81d5c8ee60864372887cddf9239580c82fb6750d956038f1e3ee9de61aa4e877")
|
||||
self.assertEqual(run_case.SHEAR_WAVE_SHA256, digest)
|
||||
nested_digest = hashlib.sha256((HERE / "shear_nested.py").read_bytes()).hexdigest()
|
||||
self.assertEqual(run_case.SHEAR_NESTED_SHA256, nested_digest)
|
||||
|
||||
def nested_set(self):
|
||||
out = []
|
||||
for n, depth, scheme in ((128, 1, "constant"), (512, 1, "constant"),
|
||||
(128, 3, "constant"), (128, 3, "projected_linear")):
|
||||
r = receipt(n, depth, scheme, diagnostic=run_case.NESTED_SHEAR)
|
||||
r["source"]["shear_nested_sha256"] = run_case.SHEAR_NESTED_SHA256
|
||||
if depth == 3:
|
||||
cells, hops = self.nested.LEAVES, self.nested.HOPS
|
||||
r["refinements"] = [list(level) for level in self.nested.REFINEMENTS]
|
||||
r["workload"] = dict(active_cells_by_level=cells, level_hops=hops,
|
||||
root_intervals=2048,
|
||||
active_collision_updates_by_level=[c * h for c, h in zip(cells, hops)],
|
||||
active_collision_updates=self.nested.WORK)
|
||||
out.append(r)
|
||||
return out
|
||||
|
||||
def test_nested_contract_and_rejections(self):
|
||||
a = self.mod.assess_nested
|
||||
good = self.nested_set()
|
||||
result = a(good, "r")
|
||||
self.assertTrue(result["contract_valid"], [k for k, v in result["checks"].items() if not v])
|
||||
self.assertEqual(result["status"], "PASS")
|
||||
self.assertIn("finest", result["physics"]["refined"]["constant"]["control_ratios"])
|
||||
wrong_finest = self.nested_set()
|
||||
wrong_finest[1] = receipt(256, 1, diagnostic=run_case.NESTED_SHEAR)
|
||||
wrong_finest[1]["source"]["shear_nested_sha256"] = run_case.SHEAR_NESTED_SHA256
|
||||
self.assertEqual(a(wrong_finest, "r")["status"], "INVALID_SET")
|
||||
for mutate in (
|
||||
lambda s: s[2].update(refinements=[s[2]["refinements"][1], s[2]["refinements"][0]]),
|
||||
lambda s: s[3].update(refinements=rectangles(128, 3)),
|
||||
lambda s: s[2]["rows"][2].update(spurious_pressure_scaled_by_level=[0.0, 0.0]),
|
||||
lambda s: s[0].update(diagnostic=run_case.SCALED_SHEAR),
|
||||
lambda s: s[2]["source"].pop("shear_nested_sha256"),
|
||||
lambda s: s[2].update(final_snapshot=dict(kept=True, sha256="a" * 64, bytes=10,
|
||||
leaf_counts=self.nested.LEAVES, step=2048)),
|
||||
lambda s: s[3].update(final_snapshot=dict(kept=True, sha256="a" * 64,
|
||||
bytes=self.nested.SNAPSHOT_MAX_BYTES + 1,
|
||||
leaf_counts=self.nested.LEAVES, step=2048)),
|
||||
):
|
||||
records = self.nested_set()
|
||||
mutate(records)
|
||||
self.assertNotEqual(a(records, "r")["status"], "PASS")
|
||||
ok = self.nested_set()
|
||||
ok[3]["final_snapshot"] = dict(kept=True, sha256="a" * 64, bytes=1000,
|
||||
leaf_counts=self.nested.LEAVES, step=2048)
|
||||
self.assertEqual(a(ok, "r")["status"], "PASS")
|
||||
with self.assertRaises(ValueError):
|
||||
self.nested.assess(good[0], good[1], [good[2], dict(good[2])])
|
||||
|
||||
def test_nested_runner_options(self):
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100"]
|
||||
nest = ["--flow", "shear_wave", "--diagnostic", run_case.NESTED_SHEAR]
|
||||
proj = ["--explosion", "projected_linear"]
|
||||
for extra, valid in (
|
||||
(["--n", "128", "--depth", "1"] + nest, True),
|
||||
(["--n", "512", "--depth", "1"] + nest, True),
|
||||
(["--n", "128", "--depth", "3"] + nest, True),
|
||||
(["--n", "128", "--depth", "3"] + proj + nest + ["--final-snapshot"], True),
|
||||
(["--n", "256", "--depth", "1"] + nest, False), # finest control is 4N
|
||||
(["--n", "128", "--depth", "2"] + nest, False),
|
||||
(["--n", "128", "--depth", "3", "--flow", "shear_wave"], False),
|
||||
(["--n", "128", "--depth", "3", "--diagnostic", run_case.NESTED_SHEAR], False),
|
||||
(["--n", "128", "--depth", "3"] + nest + ["--final-snapshot"], False),
|
||||
(["--n", "128", "--depth", "3"] + proj + ["--flow", "shear_wave"], False),
|
||||
(["--n", "128", "--depth", "2", "--flow", "shear_wave", "--final-snapshot"], False)):
|
||||
argv = base + extra
|
||||
with self.subTest(argv=argv), patch.object(sys, "argv", argv), \
|
||||
patch.object(run_case, "verify_source", return_value={}) as verify, \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
self.assertEqual(verify.call_args.args[2], "shear_nested")
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
|
||||
def test_mocked_nested_execution_with_bounded_snapshot(self):
|
||||
from types import SimpleNamespace
|
||||
from contextlib import nullcontext
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy
|
||||
h = build_hierarchy(run_case.config(128, 100), self.nested.REFINEMENTS)
|
||||
nu = U * 128 / sw.RE
|
||||
arrays = [sw.initial(k.shape, level, 128, nu) for level, k in enumerate(h.kinds)]
|
||||
|
||||
class FakeRun:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.step_count = 0
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def initialize(self, *given):
|
||||
assert len(given) == 3
|
||||
|
||||
def step(self, count):
|
||||
assert count >= 0
|
||||
self.step_count += count
|
||||
|
||||
def download(self):
|
||||
return arrays
|
||||
|
||||
modules = {"CelerisLab.cuda.context": SimpleNamespace(CudaContext=FakeRun),
|
||||
"CelerisLab.lbm.block_stepper": SimpleNamespace(PeriodicBlockStepper=FakeRun),
|
||||
"CelerisLab.lbm.refinement_stepper": SimpleNamespace(FixedPatchStepper=FakeRun)}
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output = Path(tmp) / "projected_nested.json"
|
||||
argv = ["run_case.py", "--repo", str(repo), "--re", "100", "--n", "128", "--depth", "3",
|
||||
"--explosion", "projected_linear", "--flow", "shear_wave",
|
||||
"--diagnostic", run_case.NESTED_SHEAR, "--final-snapshot",
|
||||
"--output", str(output), "--expected-revision", "f" * 40, "--run-gpu"]
|
||||
with patch.dict(sys.modules, modules), patch.object(sys, "argv", argv), \
|
||||
patch.dict("os.environ", CUDA_VISIBLE_DEVICES="mock"), \
|
||||
patch.object(run_case, "verify_source", return_value=dict(revision="f" * 40)), \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
patch.object(run_case, "runtime_identity", return_value={}), \
|
||||
patch.object(run_case, "build_capture", return_value=nullcontext()), \
|
||||
redirect_stdout(io.StringIO()):
|
||||
run_case.main()
|
||||
rec = json.loads(output.read_text())
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["diagnostic"], run_case.NESTED_SHEAR)
|
||||
self.assertEqual(rec["workload"]["active_cells_by_level"], self.nested.LEAVES)
|
||||
self.assertEqual(rec["workload"]["active_collision_updates"], self.nested.WORK)
|
||||
self.assertEqual(rec["tau_by_level"], self.nested.TAU)
|
||||
self.assertTrue(all(len(r["spurious_pressure_scaled_by_level"]) == 3 for r in rec["rows"]))
|
||||
snap = rec["final_snapshot"]
|
||||
path = output.with_name(output.stem + ".final_macro.npz")
|
||||
self.assertTrue(snap["kept"])
|
||||
self.assertEqual(snap["sha256"], hashlib.sha256(path.read_bytes()).hexdigest())
|
||||
self.assertLessEqual(snap["bytes"], self.nested.SNAPSHOT_MAX_BYTES)
|
||||
meta, fields = self.nested.read_snapshot(path)
|
||||
self.assertEqual((meta["step"], meta["phase"]), (2048, sw.PHI))
|
||||
self.assertEqual(meta["tstar"], 2048 * U / 128)
|
||||
self.assertEqual(meta["leaf_counts"], self.nested.LEAVES)
|
||||
self.assertEqual(sorted(fields), ["i", "j", "level", "rho", "ux", "uy"])
|
||||
np.testing.assert_allclose(fields["rho"], 1.0, atol=1e-12)
|
||||
|
||||
def test_snapshot_contents_and_cap(self):
|
||||
nested = self.nested
|
||||
masks = nested.leaf_masks()
|
||||
arrays = [sw.initial(m.shape, level, 128, 0.0384) for level, m in enumerate(masks)]
|
||||
meta = nested.snapshot_meta(2048, 0.0384, "projected_linear", "r")
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "s.npz"
|
||||
block = nested.write_snapshot(path, arrays, masks, meta)
|
||||
self.assertTrue(block["kept"])
|
||||
got, fields = nested.read_snapshot(path)
|
||||
self.assertEqual(got["refinements"], [[list(r) for r in level] for level in nested.REFINEMENTS])
|
||||
self.assertEqual(int(fields["level"].max()), 2)
|
||||
with self.assertRaises(FileExistsError):
|
||||
nested.write_snapshot(path, arrays, masks, meta)
|
||||
bad = Path(tmp) / "bad.npz"
|
||||
np.savez_compressed(bad, populations=np.zeros(3), **{k: np.zeros(1) for k in ("level", "i", "j", "rho", "ux", "uy")}, meta=np.array("{}"))
|
||||
with self.assertRaises(ValueError):
|
||||
nested.read_snapshot(bad)
|
||||
with patch.object(nested, "SNAPSHOT_MAX_BYTES", 10):
|
||||
capped = nested.write_snapshot(Path(tmp) / "c.npz", arrays, masks, meta)
|
||||
self.assertFalse(capped["kept"])
|
||||
self.assertFalse((Path(tmp) / "c.npz").exists())
|
||||
|
||||
|
||||
class TwinPresetTests(unittest.TestCase):
|
||||
"""Depth-2 twin (twin_shear_n128): nested L0->L1 boxes, no L2, fresh U128/U256."""
|
||||
|
||||
def setUp(self):
|
||||
import assess_shear_wave
|
||||
import shear_nested
|
||||
import shear_twin
|
||||
self.mod, self.nested, self.twin = assess_shear_wave, shear_nested, shear_twin
|
||||
|
||||
def test_exact_geometry_work_and_nested_identity(self):
|
||||
twin = self.twin
|
||||
masks = twin.leaf_masks()
|
||||
self.assertEqual([int(m.sum()) for m in masks], [7936, 33792])
|
||||
self.assertEqual((twin.LEAVES, twin.HOPS, twin.WORK), ([7936, 33792], [2048, 4096], 154664960))
|
||||
self.assertEqual(128 * 128 * 2048 + 256 * 256 * 4096 + 2 * twin.WORK, 611319808)
|
||||
self.assertEqual(twin.REFINEMENTS, [self.nested.REFINEMENTS[0]])
|
||||
np.testing.assert_array_equal(masks[0], self.nested.leaf_masks()[0]) # same root leaves
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
h = build_hierarchy(run_case.config(128, 100), twin.REFINEMENTS)
|
||||
np.testing.assert_array_equal(leaf_partition(h), np.ones((128, 128)))
|
||||
for mine, kinds in zip(masks, h.kinds):
|
||||
np.testing.assert_array_equal(mine, kinds == ACTIVE)
|
||||
# Historical nested identity is unchanged; the twin is pinned separately.
|
||||
self.assertEqual(hashlib.sha256((HERE / "shear_nested.py").read_bytes()).hexdigest(),
|
||||
"fa6cfe87fb4df8c6d732c637c885276833c81973b950c7724d734f8fc350bcb0")
|
||||
self.assertEqual(run_case.SHEAR_TWIN_SHA256,
|
||||
hashlib.sha256((HERE / "shear_twin.py").read_bytes()).hexdigest())
|
||||
|
||||
def twin_set(self):
|
||||
out = []
|
||||
for n, depth, scheme in ((128, 1, "constant"), (256, 1, "constant"),
|
||||
(128, 2, "constant"), (128, 2, "projected_linear")):
|
||||
r = receipt(n, depth, scheme, diagnostic=run_case.TWIN_SHEAR)
|
||||
r["source"].update(shear_nested_sha256=run_case.SHEAR_NESTED_SHA256,
|
||||
shear_twin_sha256=run_case.SHEAR_TWIN_SHA256)
|
||||
if depth == 2:
|
||||
cells, hops = self.twin.LEAVES, self.twin.HOPS
|
||||
r["refinements"] = [list(level) for level in self.twin.REFINEMENTS]
|
||||
r["workload"] = dict(active_cells_by_level=cells, level_hops=hops, root_intervals=2048,
|
||||
active_collision_updates_by_level=[c * h for c, h in zip(cells, hops)],
|
||||
active_collision_updates=self.twin.WORK)
|
||||
out.append(r)
|
||||
return out
|
||||
|
||||
def test_twin_contract_and_rejections(self):
|
||||
a = lambda records: self.mod.assess_nested(records, "r", run_case.TWIN_SHEAR)
|
||||
result = a(self.twin_set())
|
||||
self.assertTrue(result["contract_valid"], [k for k, v in result["checks"].items() if not v])
|
||||
self.assertEqual(result["status"], "PASS")
|
||||
self.assertIn("does not isolate abstract depth", result["scope"])
|
||||
self.assertEqual(result["finest_control"], "uniform 2N (U256)")
|
||||
for mutate in (
|
||||
lambda s: s.__setitem__(1, receipt(512, 1, diagnostic=run_case.TWIN_SHEAR)), # 4N control
|
||||
lambda s: s[2].update(refinements=[list(level) for level in self.nested.REFINEMENTS]),
|
||||
lambda s: s[3].update(refinements=rectangles(128, 2)), # old single box
|
||||
lambda s: s[2]["rows"][1].update(spurious_pressure_scaled_by_level=[0.0, 0.0, 0.0]),
|
||||
lambda s: s[0].update(diagnostic=run_case.NESTED_SHEAR),
|
||||
lambda s: s[2]["source"].pop("shear_twin_sha256"),
|
||||
lambda s: s[3].update(final_snapshot=dict(kept=True, sha256="a" * 64, bytes=10,
|
||||
leaf_counts=self.twin.LEAVES, step=2048)),
|
||||
):
|
||||
records = self.twin_set()
|
||||
mutate(records)
|
||||
self.assertNotEqual(a(records)["status"], "PASS")
|
||||
# Twin and nested sets never validate as each other.
|
||||
self.assertEqual(self.mod.assess_nested(self.twin_set(), "r")["status"], "INVALID_SET")
|
||||
|
||||
def test_twin_runner_options(self):
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100"]
|
||||
tw = ["--flow", "shear_wave", "--diagnostic", run_case.TWIN_SHEAR]
|
||||
proj = ["--explosion", "projected_linear"]
|
||||
for extra, valid in (
|
||||
(["--n", "128", "--depth", "1"] + tw, True),
|
||||
(["--n", "256", "--depth", "1"] + tw, True),
|
||||
(["--n", "128", "--depth", "2"] + tw, True),
|
||||
(["--n", "128", "--depth", "2"] + proj + tw, True),
|
||||
(["--n", "512", "--depth", "1"] + tw, False),
|
||||
(["--n", "128", "--depth", "3"] + tw, False),
|
||||
(["--n", "128", "--depth", "2"] + proj + tw + ["--final-snapshot"], False),
|
||||
(["--n", "128", "--depth", "2", "--diagnostic", run_case.TWIN_SHEAR], False)):
|
||||
argv = base + extra
|
||||
with self.subTest(argv=argv), patch.object(sys, "argv", argv), \
|
||||
patch.object(run_case, "verify_source", return_value={}) as verify, \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
self.assertEqual(verify.call_args.args[2], "shear_twin")
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
|
||||
def test_mocked_twin_execution(self):
|
||||
from types import SimpleNamespace
|
||||
from contextlib import nullcontext
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy
|
||||
h = build_hierarchy(run_case.config(128, 100), self.twin.REFINEMENTS)
|
||||
arrays = [sw.initial(k.shape, level, 128, U * 128 / sw.RE) for level, k in enumerate(h.kinds)]
|
||||
selectors = []
|
||||
|
||||
class FakeRun:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.step_count = 0
|
||||
if "refinements" in kwargs:
|
||||
selectors.append((kwargs["explosion"], kwargs["refinements"]))
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def initialize(self, *given):
|
||||
assert len(given) == 2
|
||||
|
||||
def step(self, count):
|
||||
self.step_count += count
|
||||
|
||||
def download(self):
|
||||
return arrays
|
||||
|
||||
modules = {"CelerisLab.cuda.context": SimpleNamespace(CudaContext=FakeRun),
|
||||
"CelerisLab.lbm.block_stepper": SimpleNamespace(PeriodicBlockStepper=FakeRun),
|
||||
"CelerisLab.lbm.refinement_stepper": SimpleNamespace(FixedPatchStepper=FakeRun)}
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output = Path(tmp) / "projected_twin.json"
|
||||
argv = ["run_case.py", "--repo", str(repo), "--re", "100", "--n", "128", "--depth", "2",
|
||||
"--explosion", "projected_linear", "--flow", "shear_wave",
|
||||
"--diagnostic", run_case.TWIN_SHEAR, "--output", str(output),
|
||||
"--expected-revision", "f" * 40, "--run-gpu"]
|
||||
with patch.dict(sys.modules, modules), patch.object(sys, "argv", argv), \
|
||||
patch.dict("os.environ", CUDA_VISIBLE_DEVICES="mock"), \
|
||||
patch.object(run_case, "verify_source", return_value=dict(revision="f" * 40)), \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
patch.object(run_case, "runtime_identity", return_value={}), \
|
||||
patch.object(run_case, "build_capture", return_value=nullcontext()), \
|
||||
redirect_stdout(io.StringIO()):
|
||||
run_case.main()
|
||||
rec = json.loads(output.read_text())
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["diagnostic"], run_case.TWIN_SHEAR)
|
||||
self.assertEqual(rec["workload"]["active_cells_by_level"], self.twin.LEAVES)
|
||||
self.assertEqual(rec["workload"]["active_collision_updates"], self.twin.WORK)
|
||||
self.assertNotIn("final_snapshot", rec)
|
||||
self.assertTrue(all(len(r["spurious_pressure_scaled_by_level"]) == 2 for r in rec["rows"]))
|
||||
self.assertEqual(selectors, [("projected_linear", [list(self.twin.REFINEMENTS[0])])])
|
||||
self.assertEqual(sorted(p.name for p in Path(tmp).iterdir()), ["projected_twin.json"])
|
||||
|
||||
|
||||
class ProbeTraceTests(unittest.TestCase):
|
||||
"""Signed interface-probe mechanism diagnostic (--probe-trace); no pass gates."""
|
||||
|
||||
def setUp(self):
|
||||
import shear_nested
|
||||
import shear_probes
|
||||
import shear_twin
|
||||
self.nested, self.probes, self.twin = shear_nested, shear_probes, shear_twin
|
||||
|
||||
def exact_arrays(self, masks, n, t):
|
||||
"""Equilibrium of the exact wave at root-physical time t on a case grid of root N=n."""
|
||||
out = []
|
||||
for level, m in enumerate(masks):
|
||||
x, y = centers(m.shape, level)
|
||||
x, y = x * 128 / n, y * 128 / n
|
||||
ux, uy, _ = sw.exact(x, y, t, 128, U * 128 / sw.RE)
|
||||
out.append(sw.equilibrium(np.ones_like(ux), ux, uy))
|
||||
return out
|
||||
|
||||
def test_frozen_history_identities(self):
|
||||
for name, digest in (("shear_wave.py", "81d5c8ee60864372887cddf9239580c82fb6750d956038f1e3ee9de61aa4e877"),
|
||||
("shear_nested.py", "fa6cfe87fb4df8c6d732c637c885276833c81973b950c7724d734f8fc350bcb0"),
|
||||
("shear_twin.py", "1cbf37f18e89aea577581631c4565f51e68d22a3739b698816ef64acbf2b2bb4"),
|
||||
("oracle.py", ORACLE_SHA256_EEC4677)):
|
||||
self.assertEqual(hashlib.sha256((HERE / name).read_bytes()).hexdigest(), digest, name)
|
||||
self.assertEqual(run_case.SHEAR_PROBES_SHA256,
|
||||
hashlib.sha256((HERE / "shear_probes.py").read_bytes()).hexdigest())
|
||||
|
||||
def test_probe_selection_per_case(self):
|
||||
sp = self.probes
|
||||
self.assertEqual(len(sp.points()), 24)
|
||||
corners = [(x, y) for level in (self.nested.REFINEMENTS[0],
|
||||
[tuple(v / 2 for v in r) for r in self.nested.REFINEMENTS[1]])
|
||||
for x0, y0, x1, y1 in level for x in (x0, x1) for y in (y0, y1)]
|
||||
for _, _, (x, y) in sp.points():
|
||||
self.assertGreaterEqual(min(math.hypot(x - cx, y - cy) for cx, cy in corners), 8.0)
|
||||
pattern = [0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2]
|
||||
nested = sp.select(self.nested.leaf_masks())
|
||||
self.assertEqual([p["level"] for p in nested], pattern * 2)
|
||||
twin = sp.select(self.twin.leaf_masks())
|
||||
self.assertEqual([p["level"] for p in twin], [min(v, 1) for v in pattern] * 2)
|
||||
u128 = sp.select([np.ones((128, 128), bool)], 128)
|
||||
u512 = sp.select([np.ones((512, 512), bool)], 512)
|
||||
self.assertTrue(all(p["level"] == 0 for p in u128 + u512))
|
||||
for case in (nested, twin, u128, u512):
|
||||
self.assertEqual(len({(p["level"], p["i"], p["j"]) for p in case}), 24) # unique
|
||||
for p in case:
|
||||
dx, dy = p["displacement"]
|
||||
self.assertEqual([p["center"][0] - p["point"][0], p["center"][1] - p["point"][1]],
|
||||
[dx, dy])
|
||||
self.assertEqual(max(math.hypot(*p["displacement"]) for p in u128), 0.625)
|
||||
self.assertLessEqual(max(math.hypot(*p["displacement"]) for p in u512), 0.25 * 0.7072)
|
||||
# Nested: 3 L0 + 5 L1 + 4 L2 per transect, as checked by native review.
|
||||
for name in sp.TRANSECTS:
|
||||
levels = [p["level"] for p in nested if p["transect"] == name]
|
||||
self.assertEqual([levels.count(v) for v in (0, 1, 2)], [3, 5, 4])
|
||||
# Different grids give different centers: no pretended collocation.
|
||||
self.assertNotEqual(u128[3]["center"], u512[3]["center"])
|
||||
self.assertEqual(sp.case_steps(512)[:3], [0, 4, 8])
|
||||
self.assertEqual(sp.case_steps(128), list(sp.STEPS))
|
||||
|
||||
def test_residuals_vanish_for_the_exact_field(self):
|
||||
sp = self.probes
|
||||
for masks, n in ((self.nested.leaf_masks(), 128), ([np.ones((512, 512), bool)], 512)):
|
||||
probes = sp.select(masks, n)
|
||||
trace = sp.new_trace(probes, U * n / sw.RE, ["x", n, len(masks), "c"])
|
||||
for t in (0, 100, 2048):
|
||||
trace["steps"].append(t)
|
||||
trace["values"].append(sp.sample(self.exact_arrays(masks, n, t), probes))
|
||||
for res in sp.residuals(trace):
|
||||
self.assertLess(max(abs(v) for r in res for v in r), 1e-14)
|
||||
|
||||
def test_validate_schedule_values_and_cap(self):
|
||||
sp = self.probes
|
||||
probes = sp.select(self.nested.leaf_masks())
|
||||
trace = sp.new_trace(probes, 0.0384, ["n", 128, 3, "projected_linear"])
|
||||
trace["steps"] = list(sp.STEPS)
|
||||
trace["values"] = [[1.0, 0.01, -0.02] * 24 for _ in sp.STEPS]
|
||||
size = sp.validate(trace)
|
||||
self.assertLess(size, sp.TRACE_MAX_BYTES)
|
||||
final_trace = dict(trace, bytes=size)
|
||||
self.assertEqual(size, len(json.dumps(final_trace, allow_nan=False).encode()))
|
||||
self.assertEqual(sp.validate(final_trace), size)
|
||||
with patch.object(sp, "TRACE_MAX_BYTES", size):
|
||||
self.assertEqual(sp.validate(trace), size)
|
||||
with patch.object(sp, "TRACE_MAX_BYTES", size - 1):
|
||||
with self.assertRaises(ValueError):
|
||||
sp.validate(trace)
|
||||
realistic = dict(trace, values=[[1.0000012345678901, 0.012345678901234567,
|
||||
-0.023456789012345678] * 24 for _ in sp.STEPS])
|
||||
self.assertLess(sp.validate(realistic), sp.TRACE_MAX_BYTES)
|
||||
for bad in (dict(trace, steps=trace["steps"][:-1]),
|
||||
dict(trace, values=trace["values"][:-1] + [[float("nan")] * 72]),
|
||||
dict(trace, probes=trace["probes"][:-1])):
|
||||
with self.assertRaises(ValueError):
|
||||
sp.validate(bad)
|
||||
with patch.object(sp, "TRACE_MAX_BYTES", 1000):
|
||||
with self.assertRaises(ValueError):
|
||||
sp.validate(trace)
|
||||
|
||||
def test_probe_runner_options(self):
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100",
|
||||
"--flow", "shear_wave", "--probe-trace"]
|
||||
nest = ["--diagnostic", run_case.NESTED_SHEAR]
|
||||
tw = ["--diagnostic", run_case.TWIN_SHEAR]
|
||||
proj = ["--explosion", "projected_linear"]
|
||||
for extra, valid in (
|
||||
(["--n", "128", "--depth", "1"] + nest, True),
|
||||
(["--n", "512", "--depth", "1"] + nest, True),
|
||||
(["--n", "128", "--depth", "3"] + proj + nest, True),
|
||||
(["--n", "128", "--depth", "2"] + proj + tw, True),
|
||||
(["--n", "128", "--depth", "3"] + nest, False), # constant d3
|
||||
(["--n", "128", "--depth", "1"] + tw, False),
|
||||
(["--n", "128", "--depth", "3"] + proj + nest + ["--final-snapshot"], False),
|
||||
(["--n", "128", "--depth", "2"], False)):
|
||||
argv = base + extra
|
||||
with self.subTest(argv=argv), patch.object(sys, "argv", argv), \
|
||||
patch.object(run_case, "verify_source", return_value={}) as verify, \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
self.assertIs(verify.call_args.kwargs["probes"], True)
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
|
||||
def run_mocked(self, n, depth, diagnostic, scheme, masks, initializer=sw.initial):
|
||||
from types import SimpleNamespace
|
||||
from contextlib import nullcontext
|
||||
arrays = self.exact_arrays(masks, n, 0.0)
|
||||
counts, selectors = [], []
|
||||
|
||||
class FakeRun:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.step_count = 0
|
||||
if "refinements" in kwargs:
|
||||
selectors.append((kwargs["explosion"], kwargs["refinements"]))
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
pass
|
||||
|
||||
def initialize(self, *given):
|
||||
assert len(given) == depth
|
||||
for level, (values, mask) in enumerate(zip(given, masks)):
|
||||
np.testing.assert_array_equal(values, initializer(mask.shape, level, n, U * n / sw.RE))
|
||||
|
||||
def step(self, count):
|
||||
self.step_count += count
|
||||
counts.append(self.step_count)
|
||||
|
||||
def download(self):
|
||||
return arrays[0] if depth == 1 else arrays
|
||||
|
||||
modules = {"CelerisLab.cuda.context": SimpleNamespace(CudaContext=FakeRun),
|
||||
"CelerisLab.lbm.block_stepper": SimpleNamespace(PeriodicBlockStepper=FakeRun),
|
||||
"CelerisLab.lbm.refinement_stepper": SimpleNamespace(FixedPatchStepper=FakeRun)}
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
output = Path(tmp) / "probe.json"
|
||||
argv = ["run_case.py", "--repo", str(repo), "--re", "100", "--n", str(n),
|
||||
"--depth", str(depth), "--explosion", scheme, "--flow", "shear_wave",
|
||||
"--diagnostic", diagnostic, "--probe-trace", "--output", str(output),
|
||||
"--expected-revision", "f" * 40, "--run-gpu"]
|
||||
with patch.dict(sys.modules, modules), patch.object(sys, "argv", argv), \
|
||||
patch.dict("os.environ", CUDA_VISIBLE_DEVICES="mock"), \
|
||||
patch.object(run_case, "verify_source", return_value=dict(revision="f" * 40)), \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
patch.object(run_case, "runtime_identity", return_value={}), \
|
||||
patch.object(run_case, "build_capture", return_value=nullcontext()), \
|
||||
redirect_stdout(io.StringIO()):
|
||||
run_case.main()
|
||||
rec = json.loads(output.read_text())
|
||||
expected = ([(scheme, [[tuple(r) for r in level] for level in rec["refinements"]])]
|
||||
if depth > 1 else [])
|
||||
self.assertEqual(selectors, expected)
|
||||
return rec, counts, sorted(p.name for p in Path(tmp).iterdir())
|
||||
|
||||
def test_mocked_probe_traces(self):
|
||||
sp = self.probes
|
||||
rec, counts, files = self.run_mocked(128, 3, run_case.NESTED_SHEAR, "projected_linear",
|
||||
self.nested.leaf_masks())
|
||||
trace = rec["probe_trace"]
|
||||
self.assertEqual(trace["steps"], list(sp.STEPS))
|
||||
self.assertEqual(len(trace["values"]), len(sp.STEPS))
|
||||
self.assertLessEqual(trace["bytes"], sp.TRACE_MAX_BYTES)
|
||||
self.assertEqual(trace["bytes"], len(json.dumps(trace, allow_nan=False).encode()))
|
||||
self.assertEqual(trace["case"], [run_case.NESTED_SHEAR, 128, 3, "projected_linear"])
|
||||
self.assertEqual(len(rec["rows"]), 5) # qualification samples preserved
|
||||
self.assertEqual(files, ["probe.json"]) # no field dumps
|
||||
self.assertEqual(sp.validate(trace) <= sp.TRACE_MAX_BYTES, True)
|
||||
rec512, counts512, _ = self.run_mocked(512, 1, run_case.NESTED_SHEAR, "constant",
|
||||
[np.ones((512, 512), bool)])
|
||||
t512 = rec512["probe_trace"]
|
||||
self.assertEqual(t512["steps"], list(sp.STEPS)) # root-physical times
|
||||
self.assertEqual(t512["viscosity_root"], U * 128 / sw.RE)
|
||||
self.assertEqual(t512["case_viscosity"], U * 512 / sw.RE)
|
||||
self.assertIn(4 * 1920, counts512) # U512 advanced to 4x lattice steps
|
||||
self.assertTrue(all(p["level"] == 0 for p in t512["probes"]))
|
||||
res = sp.residuals(t512)
|
||||
self.assertLess(abs(res[0][0][1]), 1e-14) # exact field at t=0 at actual centers
|
||||
|
||||
def test_invalid_optional_trace_preserves_scalar_rows(self):
|
||||
sp = self.probes
|
||||
case = (128, 1, run_case.NESTED_SHEAR, "constant", [np.ones((128, 128), bool)])
|
||||
baseline, _, _ = self.run_mocked(*case)
|
||||
original = sp.sample
|
||||
for bad in ([float("nan")] * 72, [float("inf")] * 72, [1.0],
|
||||
ZeroDivisionError("zero probe density")):
|
||||
calls = []
|
||||
|
||||
def sample(arrays, probes):
|
||||
calls.append(True)
|
||||
if len(calls) == 2: # probe-only step 1, before the next scalar save
|
||||
if isinstance(bad, Exception):
|
||||
raise bad
|
||||
return bad
|
||||
return original(arrays, probes)
|
||||
|
||||
with self.subTest(bad=repr(bad)), patch.object(sp, "sample", side_effect=sample):
|
||||
rec, counts, files = self.run_mocked(*case)
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["rows"], baseline["rows"])
|
||||
self.assertEqual(set(rec["probe_trace"]), {"error"})
|
||||
self.assertEqual(len(calls), 2) # no further probe sampling after rejection
|
||||
self.assertEqual(counts[-1], 2048)
|
||||
self.assertEqual(files, ["probe.json"])
|
||||
with patch.object(sp, "TRACE_MAX_BYTES", 100):
|
||||
rec, _, _ = self.run_mocked(*case)
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["rows"], baseline["rows"])
|
||||
self.assertIn("exceeds", rec["probe_trace"]["error"])
|
||||
|
||||
|
||||
class WideXDiagnosticTests(unittest.TestCase):
|
||||
"""One width contrast; historical qualification matrices remain unchanged."""
|
||||
|
||||
def setUp(self):
|
||||
import shear_nested
|
||||
import shear_probes
|
||||
import shear_wide_x
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy
|
||||
self.wide, self.nested, self.probes = shear_wide_x, shear_nested, shear_probes
|
||||
self.h = build_hierarchy(run_case.config(128, 100), self.wide.REFINEMENTS)
|
||||
|
||||
def test_geometry_counts_and_legal_pair_tables(self):
|
||||
from CelerisLab.lbm.blocks import BlockLayout2D
|
||||
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
from CelerisLab.lbm.refinement_tables import projected_explosion_tables
|
||||
wide, h = self.wide, self.h
|
||||
self.assertEqual(wide.REFINEMENTS[0], self.nested.REFINEMENTS[0])
|
||||
self.assertEqual(wide.REFINEMENTS[1], [(56, 44, 96, 212), (160, 60, 200, 228)])
|
||||
for outer, inner in zip(wide.REFINEMENTS[0], wide.REFINEMENTS[1]):
|
||||
x0, y0, x1, y1 = (v / 2 for v in inner)
|
||||
self.assertEqual((x0 - outer[0], outer[2] - x1), (12, 12))
|
||||
self.assertEqual((y0 - outer[1], outer[3] - y1), (6, 6))
|
||||
# Independent physical rectangle masks at each native spacing.
|
||||
masks = []
|
||||
for level, kind in enumerate(h.kinds):
|
||||
x, y = centers(kind.shape, level)
|
||||
outer = ((x >= 16) & (x < 60) & (y >= 16) & (y < 112)
|
||||
| (x >= 68) & (x < 112) & (y >= 24) & (y < 120))
|
||||
inner = ((x >= 28) & (x < 48) & (y >= 22) & (y < 106)
|
||||
| (x >= 80) & (x < 100) & (y >= 30) & (y < 114))
|
||||
masks.append(~outer if level == 0 else outer & ~inner if level == 1 else inner)
|
||||
np.testing.assert_array_equal(kind == ACTIVE, masks[-1])
|
||||
self.assertEqual([int(m.sum()) for m in masks], [7936, 20352, 53760])
|
||||
np.testing.assert_array_equal(leaf_partition(h), np.ones((128, 128)))
|
||||
self.assertEqual(run_case.workload(masks, 2048)["active_collision_updates"], 540016640)
|
||||
self.assertEqual(wide.LEAVES, [7936, 20352, 53760])
|
||||
self.assertEqual(wide.HOPS, [2048, 4096, 8192])
|
||||
self.assertEqual(wide.WORK, 540016640)
|
||||
np.testing.assert_allclose([c.viscosity for c in h.configs], wide.NU, rtol=0, atol=0)
|
||||
np.testing.assert_allclose([1 / c.omega for c in h.configs], wide.TAU, rtol=1e-15)
|
||||
old = build_hierarchy(run_case.config(128, 100), self.nested.REFINEMENTS)
|
||||
for name in ("ghost_coarse", "ghost_fine", "coalesce_coarse", "coalesce_fine"):
|
||||
np.testing.assert_array_equal(getattr(h.pairs[0], name), getattr(old.pairs[0], name))
|
||||
root = BlockLayout2D.build(128, 128, 8, 8, periodic=(True, True))
|
||||
for level, pair in enumerate(h.pairs):
|
||||
layout = root if level == 0 else h.pairs[level - 1].fine_layout((8, 8))
|
||||
neighbors, offsets = projected_explosion_tables(pair, layout, h.kinds[level])
|
||||
legal = np.unique(pair.ghost_coarse)
|
||||
legal = legal[(h.kinds[level].ravel()[legal] == ACTIVE) & ~pair.covered.ravel()[legal]]
|
||||
self.assertTrue(np.isin(neighbors, layout.owner.ravel()[legal]).all())
|
||||
self.assertEqual(neighbors.shape, (len(pair.ghost_fine), 4))
|
||||
self.assertEqual(len(np.unique(pair.ghost_fine)), len(pair.ghost_fine))
|
||||
self.assertTrue((np.abs(offsets) == .25).all())
|
||||
if level == 0:
|
||||
expected = projected_explosion_tables(old.pairs[0], root, old.kinds[0])
|
||||
for a, b in zip((neighbors, offsets), expected):
|
||||
np.testing.assert_array_equal(a, b)
|
||||
|
||||
def test_fixed_probes_and_complete_runner_schedule(self):
|
||||
from CelerisLab.lbm.refinement import ACTIVE
|
||||
masks = [k == ACTIVE for k in self.h.kinds]
|
||||
probes = self.probes.select(masks)
|
||||
old = self.probes.select(self.nested.leaf_masks())
|
||||
self.assertEqual([p["level"] for p in probes[:12]], [0] * 3 + [1] * 9)
|
||||
self.assertLess(max(p["center"][0] for p in probes[:12]), 28)
|
||||
self.assertEqual(probes[:8], old[:8]) # identical-center old L0/L1 probes
|
||||
self.assertEqual(probes[12:], old[12:]) # unchanged y-normal transect
|
||||
helper = ProbeTraceTests()
|
||||
helper.setUp()
|
||||
rec, counts, files = helper.run_mocked(128, 3, run_case.WIDE_X_SHEAR,
|
||||
"projected_linear", masks)
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["refinements"], [[list(r) for r in level] for level in self.wide.REFINEMENTS])
|
||||
self.assertEqual(rec["workload"]["active_cells_by_level"], self.wide.LEAVES)
|
||||
self.assertEqual(rec["workload"]["active_collision_updates"], self.wide.WORK)
|
||||
self.assertEqual([r["step"] for r in rec["rows"]], [0, 512, 1024, 1536, 2048])
|
||||
self.assertEqual(rec["probe_trace"]["steps"], list(self.probes.STEPS))
|
||||
self.assertEqual(rec["probe_trace"]["probes"], probes)
|
||||
self.assertEqual(counts[-1], 2048)
|
||||
self.assertEqual(files, ["probe.json"])
|
||||
self.assertNotIn("final_snapshot", rec)
|
||||
|
||||
def test_one_case_admission_and_pin(self):
|
||||
self.assertEqual(run_case.SHEAR_WIDE_X_SHA256,
|
||||
hashlib.sha256((HERE / "shear_wide_x.py").read_bytes()).hexdigest())
|
||||
repo = HERE.parents[2]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100",
|
||||
"--flow", "shear_wave", "--diagnostic", run_case.WIDE_X_SHEAR,
|
||||
"--n", "128", "--depth", "3", "--explosion", "projected_linear", "--probe-trace"]
|
||||
for extra in ([], ["--n", "512"], ["--depth", "2"], ["--explosion", "constant"],
|
||||
["--re", "1000"], ["--flow", "taylor_green"], ["--final-snapshot"],
|
||||
["--early-pressure"]):
|
||||
with self.subTest(extra=extra), patch.object(sys, "argv", base + extra), \
|
||||
patch.object(run_case, "verify_source", return_value={}) as verify, \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if not extra:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
verify.assert_called_once_with(repo, "f" * 40, "shear_wide_x", probes=True)
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
verify.assert_not_called()
|
||||
with patch.object(sys, "argv", base[:-1]), patch("sys.stderr", io.StringIO()):
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
|
||||
def test_historical_matrix_rejects_width_case(self):
|
||||
helper = NestedPresetTests()
|
||||
helper.setUp()
|
||||
records = helper.nested_set()
|
||||
self.assertTrue(helper.mod.assess_nested(records, "r")["passed"])
|
||||
records[-1].update(diagnostic=run_case.WIDE_X_SHEAR,
|
||||
refinements=self.wide.REFINEMENTS)
|
||||
self.assertEqual(helper.mod.assess_nested(records, "r")["status"], "INVALID_SET")
|
||||
self.assertNotIn(run_case.WIDE_X_SHEAR, helper.mod.PRESETS)
|
||||
|
||||
|
||||
class PostGDiagnosticTests(unittest.TestCase):
|
||||
"""Three initialization-only cases, excluded from historical qualification."""
|
||||
|
||||
def setUp(self):
|
||||
import shear_postg
|
||||
self.postg = shear_postg
|
||||
|
||||
def test_moments_phase_and_physical_scaling(self):
|
||||
from CelerisLab.lbm.population_storage import _encode_physical, _decode_stored
|
||||
velocities = np.array([CX, CY], dtype=float).T
|
||||
for level in range(3):
|
||||
shape, nu = (31, 47), .0384
|
||||
h, tau = 2.0**-level, .5 + 3 * nu * 2**level
|
||||
eq = sw.initial(shape, level, 128, nu)
|
||||
g = self.postg.initial(shape, level, 128, nu)
|
||||
x, y = centers(shape, level)
|
||||
k = sw.wavevector(128)
|
||||
strain = (.5 * U * np.cos(k[0] * x + k[1] * y + sw.PHI)[None, None]
|
||||
* (np.outer(sw.E, h * k) + np.outer(h * k, sw.E))[:, :, None, None])
|
||||
tensor = np.einsum("qi,qj->qij", velocities, velocities) - np.eye(2)[None] / 3
|
||||
a = 3 * oracle.W[:, None, None] * np.einsum("qij,ijyx->qyx", tensor, strain)
|
||||
np.testing.assert_allclose(g, eq - (tau - 1) * a, rtol=0, atol=6e-17)
|
||||
for weights in (np.ones(9), CX, CY):
|
||||
np.testing.assert_allclose(np.einsum("q,qyx->yx", weights, g - eq),
|
||||
0, rtol=0, atol=2e-16)
|
||||
stress = np.einsum("qi,qj,qyx->ijyx", velocities, velocities, g - eq)
|
||||
np.testing.assert_allclose(stress, (1 - tau) * 2 / 3 * strain, rtol=0, atol=6e-17)
|
||||
pre = eq - tau * a
|
||||
rho = pre.sum(axis=0)
|
||||
pre_eq = sw.equilibrium(rho, np.einsum("q,qyx->yx", CX, pre) / rho,
|
||||
np.einsum("q,qyx->yx", CY, pre) / rho)
|
||||
np.testing.assert_allclose((1 - 1 / tau) * pre + pre_eq / tau, g, rtol=0, atol=4e-16)
|
||||
self.assertAlmostEqual((tau - .5) * h, .1152, places=16)
|
||||
self.assertGreater(1 - tau, 0)
|
||||
encoded = _decode_stored(_encode_physical(g, True), True)
|
||||
self.assertGreater(encoded.min(), 0)
|
||||
for weights in (np.ones(9), CX, CY):
|
||||
np.testing.assert_allclose(np.einsum("q,qyx->yx", weights, encoded - eq),
|
||||
0, rtol=0, atol=3e-9)
|
||||
# The U512 control is the same physical spacing/tau/phase as nested L2.
|
||||
np.testing.assert_array_equal(self.postg.initial((512, 512), 0, 512, .1536),
|
||||
self.postg.initial((512, 512), 2, 128, .0384))
|
||||
|
||||
def test_actual_initializers_and_three_complete_schedules(self):
|
||||
helper = ProbeTraceTests()
|
||||
helper.setUp()
|
||||
work = 0
|
||||
for _, n, depth, scheme in self.postg.CASES:
|
||||
masks = helper.nested.leaf_masks() if depth == 3 else [np.ones((n, n), bool)]
|
||||
rec, counts, files = helper.run_mocked(n, depth, run_case.POSTG_SHEAR, scheme, masks,
|
||||
initializer=self.postg.initial)
|
||||
self.assertEqual(rec["initialization"], self.postg.INITIALIZATION)
|
||||
self.assertEqual(rec["initialization_id"], "postg_first_gradient_v1")
|
||||
self.assertFalse(rec["qualification_eligible"])
|
||||
self.assertTrue(rec["completed"])
|
||||
self.assertEqual(rec["limits"], sw.LIMITS)
|
||||
self.assertEqual([r["step"] for r in rec["rows"]], run_case.sample_steps(n)[0])
|
||||
self.assertEqual(rec["probe_trace"]["steps"], list(helper.probes.STEPS))
|
||||
self.assertEqual(rec["probe_trace"]["probes"], helper.probes.select(masks, n))
|
||||
self.assertEqual(counts[-1], 16 * n)
|
||||
self.assertIn(1920 * (n // 128), counts)
|
||||
self.assertEqual(files, ["probe.json"])
|
||||
self.assertNotIn("final_snapshot", rec)
|
||||
work += rec["workload"]["active_collision_updates"]
|
||||
self.assertEqual(work, 2952265728)
|
||||
|
||||
def test_fixed_admission_and_source_pin(self):
|
||||
from tests.validation.multigrid import check_projected_pairs as gate
|
||||
repo = HERE.parents[2]
|
||||
self.assertEqual(run_case.SHEAR_POSTG_SHA256,
|
||||
hashlib.sha256((HERE / "shear_postg.py").read_bytes()).hexdigest())
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
base = ["run_case.py", "--repo", str(repo), "--expected-revision", "f" * 40,
|
||||
"--output", str(Path(tmp) / "case.json"), "--dry-run", "--re", "100",
|
||||
"--flow", "shear_wave", "--diagnostic", run_case.POSTG_SHEAR, "--probe-trace"]
|
||||
for n, depth, scheme, valid in ((128, 1, "constant", True), (512, 1, "constant", True),
|
||||
(128, 3, "projected_linear", True),
|
||||
(128, 3, "constant", False), (256, 1, "constant", False),
|
||||
(128, 2, "projected_linear", False)):
|
||||
argv = base + ["--n", str(n), "--depth", str(depth), "--explosion", scheme]
|
||||
extras = ([], ["--final-snapshot"], ["--early-pressure"], ["--re", "1000"],
|
||||
["--flow", "taylor_green"]) if valid else ([],)
|
||||
for extra in extras:
|
||||
with self.subTest(argv=argv + extra), patch.object(sys, "argv", argv + extra), \
|
||||
patch.object(run_case, "verify_source", return_value={}) as verify, \
|
||||
patch.object(run_case, "runtime_source", return_value={}), \
|
||||
redirect_stdout(io.StringIO()), patch("sys.stderr", io.StringIO()):
|
||||
if valid and not extra:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
verify.assert_called_once_with(repo, "f" * 40, "shear_postg", probes=True)
|
||||
else:
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
verify.assert_not_called()
|
||||
with patch.object(sys, "argv", base[:-1] + ["--n", "128", "--depth", "1"]), \
|
||||
patch("sys.stderr", io.StringIO()):
|
||||
with self.assertRaises(SystemExit):
|
||||
run_case.main()
|
||||
with patch.object(gate, "preflight", return_value=run_case.NUMERICAL_REVISION), \
|
||||
patch.object(run_case, "provenance", return_value=dict(source_tree_sha256=run_case.SOURCE_SHA256)):
|
||||
self.assertEqual(run_case.verify_source(repo, "r", "shear_postg")["shear_postg_sha256"],
|
||||
run_case.SHEAR_POSTG_SHA256)
|
||||
for module, attr, value in ((self.postg, "__file__", "/wrong/shear_postg.py"),
|
||||
(run_case, "SHEAR_POSTG_SHA256", "wrong")):
|
||||
with patch.object(module, attr, value), self.assertRaisesRegex(RuntimeError, "shear_postg"):
|
||||
run_case.verify_source(repo, "r", "shear_postg")
|
||||
|
||||
def test_old_assessors_reject_identity_or_initializer(self):
|
||||
from copy import deepcopy
|
||||
helper = NestedPresetTests()
|
||||
helper.setUp()
|
||||
for base, assessor in ((helper.nested_set(), helper.mod.assess_nested),
|
||||
([receipt(128, 1), receipt(256, 1), receipt(128, 2),
|
||||
receipt(128, 2, "projected_linear")], helper.mod.assess)):
|
||||
self.assertTrue(assessor(base, "r")["passed"])
|
||||
for change in (dict(diagnostic=run_case.POSTG_SHEAR),
|
||||
dict(initialization=self.postg.INITIALIZATION)):
|
||||
records = deepcopy(base)
|
||||
records[-1].update(change)
|
||||
self.assertEqual(assessor(records, "r")["status"], "INVALID_SET")
|
||||
self.assertNotIn(run_case.POSTG_SHEAR, helper.mod.PRESETS)
|
||||
Reference in new issue
Block a user