feat: adopt lumped mass and reduced-coordinate shell HHT

Integrate Hex8 mass verification and fixed-null-basis shell dynamics with numerical and physics handoff evidence. Preserve interrupted reference and full-validation status; full integration validation is deferred until after performance optimization.
This commit is contained in:
KOKO\Mimi
2026-10-13 16:57:19 +09:00
parent ede137c6b4
commit eaa5cbeb21
110 changed files with 15274 additions and 971 deletions
+85 -3
View File
@@ -138,7 +138,7 @@ def make_result(path, model):
"hexa_dynlinear": "hht-linear-dynamics", "hexa_dynnonlinear": "hht-nonlinear-dynamics",
"hexa_freq": "linear-frequency"}[case],
"element_formulation": "FESA-Hex8", "integration_rule": "gauss-2x2x2",
"mass_formulation": "consistent-reference", "source_step_name": "Step-1",
"mass_formulation": "lumped-reference", "source_step_name": "Step-1",
"source_input_identity": f"path={model['source_path']};content_identity={model['content_identity']}",
"unit_system_label": "user-consistent-unspecified",
"coordinate_convention": "global-cartesian; reference-strain; current-cauchy-stress",
@@ -430,6 +430,7 @@ def test_wrong_result_node_beats_nonfinite_displacement(tmp_path, result_fixture
@pytest.mark.parametrize("attribute,value,code", [
("source_input_identity", "path=wrong;content_identity=fnv1a64:0000000000000000", "wrong-result-attribute"),
("analysis_procedure", "hht-linear-dynamics", "wrong-result-attribute"),
("mass_formulation", "consistent-reference", "wrong-result-attribute"),
("component_order", "UX,UY,UZ,URX,URY,URZ", "wrong-result-attribute"),
])
def test_metadata_identity_rejects_other_model(tmp_path, result_fixtures, attribute, value, code):
@@ -535,13 +536,94 @@ def test_report_is_deterministic_and_keeps_family_boundaries(result_fixtures):
assert len({row["reference_sha256"] for row in first["families"]}) == 3
def test_modal_original_absence_is_not_pass(result_fixtures):
report = compare_fixture(result_fixtures["hexa_freq"], "hexa_freq")
def test_modal_original_absence_is_not_pass(tmp_path, result_fixtures):
report = COMPARE.compare("hexa_freq", REFERENCE / "hexa_freq.inp",
result_fixtures["hexa_freq"], tmp_path)
assert report["precheck"]["passed"], report["precheck"]
assert report["verdict"] == "not-run" and not report["blocking_passed"]
assert report["families"] == []
def test_supplied_modal_values_are_compared_not_reported_missing(result_fixtures):
report = compare_fixture(result_fixtures["hexa_freq"], "hexa_freq")
assert report["precheck"]["passed"], report["precheck"]
# The artificial eigenvalues 1..10 must fail against genuine source Values.
assert report["verdict"] == "failed"
assert len(report["blocking_failures"]) == 10
assert report["precheck"]["reference_row_counts"]["Value"] == 10
assert [f["mode"] for f in report["families"]] == list(range(1, 11))
assert all(f["quantity"] == "eigenvalue" for f in report["families"])
def test_modal_eigenvalue_matches_pass_with_ten_independent_families(tmp_path, result_fixtures):
path = copy_result(tmp_path, result_fixtures, "hexa_freq")
reference = COMPARE.read_modal_reference(REFERENCE / "hexa_freq.txt")
with h5py.File(path, "r+") as file:
eigen = file["steps/Step-1/eigen"]
for name, field in (("modes", "eigenvalue"), ("verification/pairs", "eigenvalue"),
("frequencies", "frequency")):
rows = eigen[name][:]
rows[field] = [math.sqrt(row["value"]) / (2 * math.pi) if field == "frequency"
else row["value"] for row in reference]
eigen[name][:] = rows
first = compare_fixture(path, "hexa_freq")
assert first["verdict"] == "passed", first
assert first["blocking_passed"] and first["blocking_failures"] == []
assert len({f["identity"] for f in first["families"]}) == 10
assert all(f["relative_rms"] == 0 and f["row_count"] == 1 for f in first["families"])
assert first == compare_fixture(path, "hexa_freq")
def test_modal_text_reads_value_not_cycles_per_time(tmp_path):
original = REFERENCE / "hexa_freq.txt"
rows = COMPARE.read_modal_reference(original)
assert rows[0] == {"step": "Step-1", "mode": 1, "quantity": "eigenvalue",
"unit_dimensions": "1/time^2", "value": 496.28}
assert rows[-1]["value"] == 449260.0
reversed_path = tmp_path / "reversed.txt"
reversed_path.write_text("\n".join(reversed(original.read_text().splitlines())), encoding="utf-8")
assert COMPARE.read_modal_reference(reversed_path) == rows
@pytest.mark.parametrize("mutation,code", [
("missing", "modal-mode-inventory"), ("duplicate", "duplicate-modal-mode"),
("extra", "wrong-modal-mode"), ("unit", "wrong-modal-reference-format"),
("quantity", "wrong-modal-reference-format"), ("nan", "nonfinite-modal-reference"),
("negative", "invalid-modal-reference-value"), ("order", "wrong-modal-order"),
])
def test_modal_text_rejects_invalid_source(tmp_path, mutation, code):
lines = [line for line in (REFERENCE / "hexa_freq.txt").read_text().splitlines() if line.strip()]
if mutation == "missing":
lines.pop()
elif mutation == "duplicate":
lines.append(lines[0])
elif mutation == "extra":
lines.append("Mode 11: Value = 500000 Freq = 112.54 (cycles/time)")
elif mutation == "unit":
lines[0] = lines[0].replace("cycles/time", "Hz")
elif mutation == "quantity":
lines[0] = lines[0].replace("Value", "Frequency")
elif mutation == "nan":
lines[0] = lines[0].replace("496.28", "NaN")
elif mutation == "negative":
lines[0] = lines[0].replace("3.5456", "-3.5456")
else:
lines[0] = lines[0].replace("496.28", "900000")
path = tmp_path / "invalid.txt"
path.write_text("\n".join(lines), encoding="utf-8")
with pytest.raises(COMPARE.ComparisonError) as error:
COMPARE.read_modal_reference(path)
assert error.value.code == code
def test_modified_modal_reference_is_precheck_failure(tmp_path, result_fixtures):
(tmp_path / "hexa_freq.txt").write_bytes((REFERENCE / "hexa_freq.txt").read_bytes() + b"\n")
report = COMPARE.compare("hexa_freq", REFERENCE / "hexa_freq.inp",
result_fixtures["hexa_freq"], tmp_path)
assert report["verdict"] == "failed"
assert report["precheck"]["code"] == "reference-hash-mismatch"
@pytest.mark.parametrize("mutation,code", [("duplicate", "duplicate-modal-mode"), ("missing", "modal-mode-inventory"),
("extra", "wrong-modal-mode"), ("unit", "wrong-modal-unit"),
("step", "wrong-modal-step")])