482 lines
27 KiB
Python
482 lines
27 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit root-split density artifacts against the original frozen campaign.
|
|
|
|
Saved polynomial algebra and aggregation are checked independently. Mapped
|
|
quadrature weights are not exported, so this is not an independent FEM integral
|
|
or a global positivity certificate. Tangential convergence remains an observation.
|
|
Run after the serial numerical campaign, never alongside a timed FEM run.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from collections import defaultdict
|
|
import csv
|
|
from decimal import Decimal, localcontext
|
|
import hashlib
|
|
import json
|
|
import math
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
EPS = 2.220446049250313e-16
|
|
PREFIX = "density_negative_regions_"
|
|
ORDERS = (14, 22, 30)
|
|
SUM_FIELDS = ("tangent_lines", "negative_intervals", "integration_points", "volume", "mass",
|
|
"negative_volume", "negative_mass")
|
|
NEGATIVE_BOUNDS = ("negative_radius_minimum", "negative_radius_maximum",
|
|
"negative_interval_t_minimum", "negative_interval_t_maximum")
|
|
CONSUMED = ("metadata.txt", "layout.csv", "observed_state.txt", "observed_density_coefficients.txt",
|
|
"density_invariants.csv", "density_physical.csv")
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise AssertionError(message)
|
|
|
|
|
|
def close(a, b, label, rtol=3e-11, atol=1e-25):
|
|
require(math.isfinite(a) and math.isfinite(b) and math.isclose(a, b, rel_tol=rtol, abs_tol=atol),
|
|
f"{label}: {a:.17g} != {b:.17g}")
|
|
|
|
|
|
def digest(path):
|
|
with path.open("rb") as stream:
|
|
return hashlib.file_digest(stream, "sha256").hexdigest()
|
|
|
|
|
|
def rows(path):
|
|
with path.open(newline="") as stream:
|
|
reader = csv.DictReader(stream)
|
|
require(reader.fieldnames is not None and len(set(reader.fieldnames)) == len(reader.fieldnames),
|
|
f"Missing or duplicate CSV columns: {path}")
|
|
result = list(reader)
|
|
require(all(None not in row and None not in row.values() for row in result), f"Malformed CSV: {path}")
|
|
return result
|
|
|
|
|
|
def metadata(path):
|
|
entries = [line.split("=", 1) for line in path.read_text().splitlines() if "=" in line]
|
|
require(len({key for key, _ in entries}) == len(entries), f"Duplicate metadata: {path}")
|
|
return dict(entries)
|
|
|
|
|
|
def keyed(table, key):
|
|
result = {key(row): row for row in table}
|
|
require(len(result) == len(table), "Duplicate artifact rows")
|
|
return result
|
|
|
|
|
|
def numeric(row):
|
|
return {key: float(value) for key, value in row.items()}
|
|
|
|
|
|
def finite(row, allowed_nan=()):
|
|
for key, value in row.items():
|
|
require(math.isfinite(value) or (key in allowed_nan and math.isnan(value)),
|
|
f"Unexpected nonfinite value in {key}")
|
|
|
|
|
|
class Campaign:
|
|
def __init__(self, path):
|
|
self.path = path.resolve()
|
|
self.ledger = {}
|
|
self.provenance = json.loads(self.record(self.path / "provenance.json").read_text())
|
|
p = self.provenance
|
|
require(digest(self.record(self.path / "plateau_diagnostic")) == p["binary_sha256"], "Producer binary changed")
|
|
require(digest(self.record(Path(p["mesh"]))) == p["mesh_sha256"], "Producer mesh changed")
|
|
require(json.loads(self.record(self.path / "cases.json").read_text()) == p["cases"], "Case manifest changed")
|
|
self.cases = keyed(p["cases"], lambda case: case["id"])
|
|
self.runs = {}
|
|
|
|
def record(self, path):
|
|
path = path.resolve()
|
|
self.ledger[str(path)] = digest(path)
|
|
return path
|
|
|
|
def run(self, name):
|
|
if name not in self.runs:
|
|
r = json.loads(self.record(self.path / f"{name}_run.json").read_text())
|
|
require(r["id"] == name and r["exit_code"] == 0 and r["inputs_unchanged"] is True,
|
|
f"Incomplete or changed case: {name}")
|
|
expected = [str(self.path / "plateau_diagnostic"), *self.cases[name]["arguments"],
|
|
"--mesh", self.provenance["mesh"], "--output", str(self.path / name)]
|
|
require(r["command"] == expected, f"Recorded command differs from manifest: {name}")
|
|
for path, expected_hash in r["input_state_hashes"].items():
|
|
require(self.ledger.get(str(self.record(Path(path)))) == expected_hash, f"Changed source input: {path}")
|
|
self.runs[name] = r
|
|
return self.runs[name]
|
|
|
|
def artifact(self, name, relative):
|
|
r = self.run(name)
|
|
require(relative in r["output_hashes"], f"Consumed artifact lacks producer hash: {name}/{relative}")
|
|
path = self.record(self.path / name / relative)
|
|
require(self.ledger[str(path)] == r["output_hashes"][relative], f"Changed output: {name}/{relative}")
|
|
return path
|
|
|
|
|
|
def vector_size(path):
|
|
with path.open() as stream:
|
|
size = int(next(stream).strip())
|
|
require(size > 0, "Empty saved vector")
|
|
count = 0
|
|
for line in stream:
|
|
require(math.isfinite(float(line)), "Nonfinite saved vector")
|
|
count += 1
|
|
require(size == count, "Saved vector size differs from its header")
|
|
return size
|
|
|
|
|
|
def gauss_rule(count):
|
|
"""Independent Legendre recurrence on [0,1], used only for node binding."""
|
|
result = []
|
|
for j in range(1, count + 1):
|
|
z = math.cos(math.pi * (j - .25) / (count + .5))
|
|
for _ in range(30):
|
|
previous, current = 1., z
|
|
for degree in range(2, count + 1):
|
|
previous, current = current, ((2 * degree - 1) * z * current - (degree - 1) * previous) / degree
|
|
derivative = count * (z * current - previous) / (z * z - 1)
|
|
update = current / derivative
|
|
z -= update
|
|
if abs(update) < 2e-16:
|
|
break
|
|
result.append(((1 + z) / 2, 1 / ((1 - z * z) * derivative * derivative)))
|
|
return sorted(result)
|
|
|
|
|
|
def polynomial_intervals(c, b, a):
|
|
"""Direct high-precision quadratic formula differs from producer q/a,c/q."""
|
|
with localcontext() as context:
|
|
context.prec = 70
|
|
c, b, a = [Decimal(str(x)) for x in (c, b, a)]
|
|
points = [Decimal(0), Decimal(1)]
|
|
if a == 0:
|
|
roots = [] if b == 0 else [-c / b]
|
|
else:
|
|
discriminant = b * b - 4 * a * c
|
|
roots = [] if discriminant < 0 else [(-b - discriminant.sqrt()) / (2 * a),
|
|
(-b + discriminant.sqrt()) / (2 * a)]
|
|
points = sorted(set(points + [t for t in roots if 0 < t < 1]))
|
|
value = lambda t: c + t * (b + t * a)
|
|
return [(float(left), float(right)) for left, right in zip(points, points[1:])
|
|
if value((left + right) / 2) < 0]
|
|
|
|
|
|
def interval_check(row, rule):
|
|
r = numeric(row)
|
|
finite(r)
|
|
left, right = r["t_left"], r["t_right"]
|
|
require(r["tangential_order"] == 30 and 0 <= left < right <= 1, "Invalid finest negative interval")
|
|
require(r["axis"] in (0, 1, 2) and r["orientation"] in (-1, 1), "Invalid interval axis")
|
|
a, b, c = (r[key] for key in ("coefficient_a", "coefficient_b", "coefficient_c"))
|
|
scale = max(abs(a), abs(b), abs(c))
|
|
require(scale > 0, "Zero polynomial cannot have a negative interval")
|
|
allowance = 4096 * EPS * scale
|
|
evaluate = lambda t: math.fsum((c, b * t, a * t * t))
|
|
for endpoint in (left, right):
|
|
if 0 < endpoint < 1:
|
|
require(abs(evaluate(endpoint)) <= allowance, "Interior interval endpoint is not a root")
|
|
for fraction in (.125, .25, .5, .75, .875):
|
|
require(evaluate(left + fraction * (right - left)) <= allowance, "Negative interval contains a positive value")
|
|
candidates = [left, right]
|
|
if a and left < -b / (2 * a) < right:
|
|
candidates.append(-b / (2 * a))
|
|
minimum = min(map(evaluate, candidates))
|
|
require(minimum <= allowance, "Claimed negative interval has positive minimum")
|
|
close(r["minimum_density"], minimum, "Direct FE and polynomial interval minimum", rtol=0, atol=allowance)
|
|
require(r["negative_volume"] > 0 and r["negative_mass"] > 0, "Nonpositive negative-region integral")
|
|
require(r["negative_mass"] / r["negative_volume"] <= -minimum + allowance,
|
|
"Negative mean density exceeds the polynomial's maximum magnitude")
|
|
weights = []
|
|
for coordinate in ("u", "v"):
|
|
nearest = min(rule, key=lambda pair: abs(pair[0] - r[coordinate]))
|
|
close(nearest[0], r[coordinate], "Tangential Gauss node", rtol=0, atol=4e-15)
|
|
weights.append(nearest[1])
|
|
close(weights[0] * weights[1], r["tangential_weight"], "Tangential Gauss weight", rtol=2e-12)
|
|
radii = [math.sqrt(math.fsum(r[f"{side}_{component}"] ** 2 for component in "xyz"))
|
|
for side in ("left", "right")]
|
|
require(0 <= r["radius_minimum"] <= r["radius_maximum"], "Invalid radius bounds")
|
|
tolerance = 2e-13 * max(1., r["radius_maximum"])
|
|
require(r["radius_minimum"] <= min(radii) + tolerance and r["radius_maximum"] >= max(radii) - tolerance,
|
|
"Sampled radius bounds exclude a saved endpoint")
|
|
return r, minimum < -allowance
|
|
|
|
|
|
def measure_check(r):
|
|
empty = r["negative_intervals"] == 0
|
|
finite(r, NEGATIVE_BOUNDS if empty else ())
|
|
require(r["volume"] > 0 and 0 <= r["negative_volume"] <= r["volume"], "Invalid measured volume")
|
|
require(r["negative_mass"] >= 0, "Invalid negative mass")
|
|
for key in SUM_FIELDS[:3]:
|
|
require(r[key] >= 0 and r[key].is_integer(), f"Noninteger count: {key}")
|
|
close(r["negative_volume_fraction"], r["negative_volume"] / r["volume"], "Negative volume fraction")
|
|
require(r["mass"] != 0, "Zero total mass requires an explicitly unsupported fraction convention")
|
|
close(r["negative_mass_fraction"], r["negative_mass"] / abs(r["mass"]), "Negative mass fraction")
|
|
require(0 <= r["maximum_reconstruction_error"] <= r["maximum_reconstruction_tolerance"], "Q2 reconstruction failed")
|
|
require(all(0 <= r[key] <= 1 for key in ("witness_t", "witness_u", "witness_v")), "Witness outside reference cell")
|
|
if empty:
|
|
require(r["negative_mass"] == 0 and r["negative_volume"] == 0 and
|
|
all(math.isnan(r[key]) for key in NEGATIVE_BOUNDS), "Empty negative region has nonempty observations")
|
|
else:
|
|
require(r["negative_mass"] > 0 and r["negative_volume"] > 0 and
|
|
0 <= r["negative_interval_t_minimum"] < r["negative_interval_t_maximum"] <= 1 and
|
|
0 <= r["negative_radius_minimum"] <= r["negative_radius_maximum"], "Invalid nonempty negative bounds")
|
|
|
|
|
|
def audit_selfchecks(table):
|
|
expected = {"positive_quadratic": (0., 0.), "negative_constant": (1., 2.),
|
|
"linear_crossing": (.25, .03125), "double_root_nonnegative": (0., 0.),
|
|
"near_linear_positive": (0., 0.), "near_double_negative": (2. ** -19, 4 * 2. ** -60 / 3),
|
|
"gauss_q2_t_power_1p5": (.04548403175047501373888, .0005262295634284655),
|
|
"gauss_q2_t_power_3": (math.sqrt(6) / 15, math.sqrt(6) / 2250)}
|
|
indexed = keyed(table, lambda r: r["case"])
|
|
require(indexed.keys() == expected.keys(), "Analytic selfcheck coverage changed")
|
|
for name, (length, mass) in expected.items():
|
|
r = indexed[name]
|
|
require(r["passed"] == "1", "Producer arithmetic selfcheck failed")
|
|
for key, target in (("negative_length", length), ("negative_mass", mass)):
|
|
close(float(r[key]), target, f"Independent selfcheck {name}/{key}", rtol=3e-12, atol=1e-27)
|
|
close(float(r["expected_" + key]), target, "Producer selfcheck expected value", rtol=3e-12, atol=1e-27)
|
|
|
|
|
|
def audit_case(current, original, name):
|
|
new = lambda filename: current.artifact(name, filename)
|
|
old = lambda filename: original.artifact(name, filename)
|
|
meta, prior_meta = metadata(new("metadata.txt")), metadata(old("metadata.txt"))
|
|
for key in ("n", "K", "J", "rho_c", "M", "R_reference", "density_source", "surface_prescription",
|
|
"surface_degree", "surface_maximum_order", "central_phase"):
|
|
require(key in meta and key in prior_meta and meta[key] == prior_meta[key], f"Changed model metadata: {key}")
|
|
require(meta["density_diagnostic"] == "1" and meta["density_region_integral"] == "1", "Wrong diagnostic mode")
|
|
require(current.run(name)["input_state_hashes"] == original.run(name)["input_state_hashes"], "Changed frozen inputs")
|
|
for filename in ("observed_state.txt", "observed_density_coefficients.txt", "layout.csv"):
|
|
require(digest(new(filename)) == digest(old(filename)), f"Source is not byte-identical: {filename}")
|
|
state_size = vector_size(new("observed_state.txt"))
|
|
density_size = vector_size(new("observed_density_coefficients.txt"))
|
|
layout = rows(new("layout.csv"))
|
|
for kind in ("value", "residual"):
|
|
blocks = sorted((int(r["offset"]), int(r["size"])) for r in layout if r["kind"] == kind)
|
|
end = 0
|
|
for offset, size in blocks:
|
|
require(offset == end and size > 0, "Invalid saved block layout")
|
|
end += size
|
|
require(end == state_size, "Layout does not cover the observed state")
|
|
require(density_size % 27 == 0, "Full density storage is not Q2 tensor sized")
|
|
invariants = {r["metric"]: float(r["value"]) for r in rows(new("density_invariants.csv"))}
|
|
previous_invariants = {r["metric"]: float(r["value"]) for r in rows(old("density_invariants.csv"))}
|
|
finite(invariants)
|
|
for key in ("accepted_physical_unchanged", "accepted_normalized_unchanged", "accepted_residual_unchanged"):
|
|
require(invariants[key] == previous_invariants[key] == 1, "Accepted-state observation/restoration failed")
|
|
close(invariants["prepared_residual_norm"], previous_invariants["prepared_residual_norm"], "Frozen residual", atol=1e-14)
|
|
scope_file = new(PREFIX + "scope.txt")
|
|
scope = metadata(scope_file)
|
|
require(scope["source_fields_bitwise_identical"] == "1" and scope["trial_preparations"] == "0" and
|
|
scope["mpi_ranks"] == "1", "Region observer invariants failed")
|
|
for key in ("n", "K"):
|
|
close(float(scope[key]), float(meta[key]), "Observer model binding")
|
|
text = scope_file.read_text()
|
|
for phrase in ("no density or EOS clipping", "All stellar cells are included", "tangential integration is sampled",
|
|
"not global extrema or polynomial sign certificates", "Near-double-root"):
|
|
require(phrase in text, "Missing observer scope limitation")
|
|
audit_selfchecks(rows(new(PREFIX + "selfchecks.csv")))
|
|
totals = keyed([numeric(r) for r in rows(new(PREFIX + "summary.csv"))], lambda r: int(r["tangential_order"]))
|
|
elements = keyed([numeric(r) for r in rows(new(PREFIX + "elements.csv"))],
|
|
lambda r: (int(r["tangential_order"]), int(r["element"])))
|
|
topology = keyed(rows(old("density_element_topology.csv")), lambda r: int(r["element"]))
|
|
require(totals.keys() == set(ORDERS), "Missing quadrature levels")
|
|
require(elements.keys() == {(order, element) for order in ORDERS for element in topology}, "Stellar support differs")
|
|
original_summary = keyed(rows(old("density_summary.csv")), lambda r: (int(r["quadrature"]), r["field"]))
|
|
physical = {r["metric"]: float(r["value"]) for r in rows(new("density_physical.csv")) if r["quadrature"] == "18"}
|
|
denominator_differences = {}
|
|
for order, total in totals.items():
|
|
require(total["interval_order"] == 14, "Changed interval quadrature")
|
|
measure_check(total)
|
|
subset = [elements[(order, element)] for element in topology]
|
|
for r in subset:
|
|
measure_check(r)
|
|
top = topology[int(r["element"])]
|
|
require(r["interval_order"] == 14 and r["tangent_lines"] == (order // 2 + 1) ** 2 and
|
|
r["attribute"] == int(top["attribute"]) and r["surface_face_count"] == int(top["surface_face_count"]),
|
|
"Element integration layout or topology differs")
|
|
require(r["witness_element"] == r["element"] and r["axis"] in (0, 1, 2) and
|
|
r["orientation"] in (-1, 1) and r["integration_points"] % 8 == 0 and
|
|
8 * r["tangent_lines"] <= r["integration_points"] <= 24 * r["tangent_lines"], "Element count/axis contradiction")
|
|
require((r["surface_face"] >= 0) == (r["surface_face_count"] > 0), "Surface axis metadata inconsistent")
|
|
if r["surface_face_count"] == 0:
|
|
require(r["axis"] == 0 and r["orientation"] == 1, "Non-surface slicing convention changed")
|
|
for key in SUM_FIELDS:
|
|
close(math.fsum(r[key] for r in subset), total[key], f"Global element sum {order}/{key}")
|
|
close(min(r["minimum_density"] for r in subset), total["minimum_density"], "Global minimum")
|
|
witness = elements[(order, int(total["witness_element"]))]
|
|
for key in ("minimum_density", "witness_h", "witness_t", "witness_u", "witness_v", "witness_x", "witness_y", "witness_z"):
|
|
close(witness[key], total[key], "Global witness binding", atol=1e-15)
|
|
nonempty = [r for r in subset if r["negative_intervals"]]
|
|
for key, function in (("negative_radius_minimum", min), ("negative_radius_maximum", max),
|
|
("negative_interval_t_minimum", min), ("negative_interval_t_maximum", max)):
|
|
if nonempty:
|
|
close(function(r[key] for r in nonempty), total[key], "Negative-region bounds")
|
|
for key in ("maximum_reconstruction_error", "maximum_reconstruction_tolerance"):
|
|
close(max(r[key] for r in subset), total[key], "Reconstruction maximum")
|
|
denominator_differences[str(order)] = {}
|
|
for key in ("mass", "volume"):
|
|
for old_order in (18, 26):
|
|
reference = float(original_summary[(old_order, "state")][key])
|
|
close(total[key], reference, f"Independent full {key}, q{order}/original q{old_order}", rtol=2e-9, atol=1e-13)
|
|
denominator_differences[str(order)][f"{key}_relative_to_q{old_order}"] = (total[key] - reference) / abs(reference)
|
|
close(physical[key], float(original_summary[(18, "state")][key]), "Repeated q18 physical measure")
|
|
|
|
interval_rows = rows(new(PREFIX + "intervals.csv"))
|
|
by_element, by_line = defaultdict(list), defaultdict(list)
|
|
unresolved = 0
|
|
rule = gauss_rule(16)
|
|
for row in interval_rows:
|
|
r, resolved = interval_check(row, rule)
|
|
unresolved += not resolved
|
|
element = int(r["element"])
|
|
require((30, element) in elements, "Interval on an unsupported element")
|
|
for key in ("attribute", "axis", "orientation", "surface_face"):
|
|
require(r[key] == elements[(30, element)][key], "Interval geometry metadata mismatch")
|
|
by_element[element].append(r)
|
|
by_line[(element, r["u"], r["v"])].append((row, r, resolved))
|
|
for line in by_line.values():
|
|
first = line[0][0]
|
|
for row, _, _ in line:
|
|
require(all(row[key] == first[key] for key in ("coefficient_c", "coefficient_b", "coefficient_a")), "One line has multiple polynomials")
|
|
expected = polynomial_intervals(first["coefficient_c"], first["coefficient_b"], first["coefficient_a"])
|
|
actual = sorted((r["t_left"], r["t_right"]) for _, r, _ in line)
|
|
require(all(actual[i][1] <= actual[i + 1][0] for i in range(len(actual) - 1)), "Overlapping negative intervals")
|
|
if all(resolved for _, _, resolved in line):
|
|
require(len(expected) == len(actual), "Missing or extra resolved negative interval")
|
|
a, b, c = [float(first[k]) for k in ("coefficient_a", "coefficient_b", "coefficient_c")]
|
|
uncertainty = 4096 * EPS * max(abs(a), abs(b), abs(c))
|
|
for got, wanted in zip(actual, expected):
|
|
for x, y in zip(got, wanted):
|
|
if y in (0., 1.):
|
|
require(x == y, "Negative interval misses the physical cell endpoint")
|
|
continue
|
|
# Near a double root, position uncertainty scales as the
|
|
# square root of coefficient uncertainty, not machine eps.
|
|
allowance = max(64 * EPS, uncertainty / max(abs(b + 2 * a * y), math.sqrt(abs(a) * uncertainty)))
|
|
close(x, y, "Independent Decimal root", rtol=0, atol=allowance)
|
|
for element in topology:
|
|
r = elements[(30, element)]
|
|
pieces = by_element[element]
|
|
require(len(pieces) == r["negative_intervals"], "Finest interval export coverage mismatch")
|
|
for key in ("negative_mass", "negative_volume"):
|
|
close(math.fsum(piece[key] for piece in pieces), r[key], "Finest interval-to-element sum")
|
|
if pieces:
|
|
allowance = r["maximum_reconstruction_tolerance"] + 128 * EPS * max(abs(r["minimum_density"]), 1e-30)
|
|
close(min(piece["minimum_density"] for piece in pieces), r["minimum_density"], "Finest negative minimum", rtol=0, atol=allowance)
|
|
for interval_key, element_key, function in (("t_left", "negative_interval_t_minimum", min),
|
|
("t_right", "negative_interval_t_maximum", max), ("radius_minimum", "negative_radius_minimum", min),
|
|
("radius_maximum", "negative_radius_maximum", max)):
|
|
close(function(p[interval_key] for p in pieces), r[element_key], "Finest interval bounds")
|
|
comparison = {}
|
|
for key in ("negative_mass", "negative_volume", "negative_mass_fraction", "negative_volume_fraction"):
|
|
comparison[key] = {"q14": totals[14][key], "q22": totals[22][key], "q30": totals[30][key],
|
|
"q30_minus_q22": totals[30][key] - totals[22][key],
|
|
"relative_q30_minus_q22": ((totals[30][key] - totals[22][key]) / totals[30][key]
|
|
if totals[30][key] else None)}
|
|
return {"artifact_passed": True, "n": float(meta["n"]), "state_size": state_size,
|
|
"density_storage_size": density_size, "stellar_elements": len(topology),
|
|
"finest_exported_negative_intervals": len(interval_rows), "finest_negative_lines": len(by_line),
|
|
"intervals_below_coefficient_uncertainty": unresolved,
|
|
"finest_affected_elements": len([v for v in by_element.values() if v]),
|
|
"finest_negative_elements_not_touching_surface": sum(bool(by_element[e]) and int(topology[e]["surface_face_count"]) == 0 for e in topology),
|
|
"denominator_comparisons": denominator_differences, "quadrature_comparison": comparison,
|
|
"summary": {str(k): v for k, v in totals.items()}}
|
|
|
|
|
|
class SelfTests(unittest.TestCase):
|
|
def test_decimal_roots(self):
|
|
self.assertEqual(polynomial_intervals("-.25", "1", "0"), [(0., .25)])
|
|
self.assertEqual(polynomial_intervals(".25", "-1", "1"), [])
|
|
roots = polynomial_intervals(".05", "-.6", "1.5")
|
|
close(roots[0][0], .2 - math.sqrt(6) / 30, "Cubic root")
|
|
close(roots[0][1], .2 + math.sqrt(6) / 30, "Cubic root")
|
|
|
|
def fixture(self):
|
|
rule = gauss_rule(16)
|
|
u, weight = rule[3]
|
|
r = dict(tangential_order=30, element=1, attribute=1, axis=0, orientation=1, surface_face=2,
|
|
u=u, v=u, tangential_weight=weight * weight, t_left=0, t_right=.25,
|
|
coefficient_c=-.25, coefficient_b=1, coefficient_a=0,
|
|
negative_volume=.25 * weight * weight, negative_mass=.03125 * weight * weight,
|
|
minimum_density=-.25, witness_h=1, radius_minimum=math.sqrt(2 * u * u),
|
|
radius_maximum=math.sqrt(.25 ** 2 + 2 * u * u), left_x=0, left_y=u, left_z=u,
|
|
right_x=.25, right_y=u, right_z=u)
|
|
return {k: str(v) for k, v in r.items()}, rule
|
|
|
|
def test_valid_interval(self):
|
|
r, rule = self.fixture()
|
|
self.assertTrue(interval_check(r, rule)[1])
|
|
|
|
def test_corrupt_interval(self):
|
|
for key, value in (("t_right", ".3"), ("negative_mass", "1"), ("u", ".5"),
|
|
("minimum_density", "-.5"), ("witness_h", "nan")):
|
|
with self.subTest(key=key):
|
|
r, rule = self.fixture()
|
|
r[key] = value
|
|
with self.assertRaises(AssertionError):
|
|
interval_check(r, rule)
|
|
|
|
def test_hash_coverage(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
folder = Path(directory)
|
|
(folder / "case").mkdir()
|
|
(folder / "case/file.txt").write_text("original")
|
|
campaign = object.__new__(Campaign)
|
|
campaign.path, campaign.ledger = folder, {}
|
|
campaign.runs = {"case": {"output_hashes": {"file.txt": digest(folder / "case/file.txt")}}}
|
|
campaign.artifact("case", "file.txt")
|
|
(folder / "case/file.txt").write_text("changed")
|
|
with self.assertRaises(AssertionError):
|
|
campaign.artifact("case", "file.txt")
|
|
with self.assertRaises(AssertionError):
|
|
campaign.artifact("case", "unrecorded.txt")
|
|
|
|
|
|
def clean(item):
|
|
if isinstance(item, dict):
|
|
return {str(k): clean(v) for k, v in item.items()}
|
|
if isinstance(item, list):
|
|
return [clean(v) for v in item]
|
|
return None if isinstance(item, float) and not math.isfinite(item) else item
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("campaign", type=Path, nargs="?")
|
|
parser.add_argument("--original", type=Path, help="Original negative-density campaign; defaults to sibling campaign")
|
|
parser.add_argument("--output", type=Path)
|
|
parser.add_argument("--self-test", action="store_true")
|
|
args = parser.parse_args()
|
|
if args.self_test:
|
|
result = unittest.TextTestRunner(verbosity=2).run(unittest.defaultTestLoader.loadTestsFromTestCase(SelfTests))
|
|
if not result.wasSuccessful():
|
|
raise SystemExit(1)
|
|
if args.campaign is None:
|
|
return
|
|
if args.campaign is None or args.output is None:
|
|
parser.error("campaign and --output are required for artifact auditing")
|
|
current = Campaign(args.campaign)
|
|
original = Campaign(args.original or args.campaign.resolve().parent / "campaign")
|
|
require(current.path != original.path, "A distinct source campaign is required")
|
|
require(current.provenance["mesh_sha256"] == original.provenance["mesh_sha256"], "Campaign meshes differ")
|
|
require(current.cases.keys() <= original.cases.keys(), "Region case has no original source case")
|
|
report = {"schema": "negative_density_regions_audit_v1", "artifact_passed": True,
|
|
"auditor_sha256": digest(Path(__file__)), "producer_commit": current.provenance["commit"],
|
|
"original_commit": original.provenance["commit"],
|
|
"scope": "Independent saved quadratic algebra, aggregation and source binding; not independent mapped FEM integration or global positivity certification.",
|
|
"tolerances": {"aggregation_relative": 3e-11, "aggregation_absolute": 1e-25,
|
|
"root_coefficient_roundoff_factor": 4096, "whole_measure_relative": 2e-9,
|
|
"whole_measure_absolute": 1e-13},
|
|
"cases": {name: audit_case(current, original, name) for name in current.cases}}
|
|
report["consumed_sha256"] = {**original.ledger, **current.ledger}
|
|
args.output.write_text(json.dumps(clean(report), indent=2, allow_nan=False) + "\n")
|
|
print("PASS", args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|