modify documents

This commit is contained in:
KOKO\Mimi
2026-08-18 23:24:35 +09:00
parent 3cd5a21892
commit d3e41b4c93
353 changed files with 23702 additions and 4289 deletions
@@ -1,317 +0,0 @@
import unittest
from pathlib import Path
try:
import tomllib
except ModuleNotFoundError: # pragma: no cover
import tomli as tomllib
ROOT = Path(__file__).resolve().parents[1]
AGENTS_ROOT = ROOT / ".codex" / "agents"
SKILLS_ROOT = ROOT / ".codex" / "skills"
DESIGN_DOC = ROOT / "docs" / "ABAQUS_SUBROUTINE_AGENT_DESIGN.md"
COMMON_SKILL_SECTIONS = (
"## Inputs",
"## Workflow",
"## Output Contract",
"## Boundaries",
"## Quality Gate",
"## Handoff",
)
SKILLS = {
"abaqus-subroutine-requirements": (
"Subroutine requirements",
"ABAQUS-USUB-REQ-<FEATURE>-###",
"Requirement Verification Matrix",
"UMAT | VUMAT | UEL",
"Do not implement Fortran code.",
),
"abaqus-subroutine-research": (
"Research evidence",
"official Abaqus documentation",
"books, papers, and benchmark sources",
"Separate verified facts from inference.",
"Abaqus User Subroutines Reference Guide",
),
"abaqus-subroutine-formulation": (
"Finite element formulation",
"stress update",
"consistent tangent",
"state variables",
"Do not design Fortran source layout.",
),
"abaqus-subroutine-numerical-review": (
"Numerical review",
"finite-difference tangent check",
"state variable update",
"stability risks",
"pass-for-interface-definition",
),
"abaqus-subroutine-interface": (
"Abaqus ABI Contract",
"UMAT",
"VUMAT",
"UEL",
"DDSDDE",
"STATEV",
),
"abaqus-subroutine-test-models": (
"TDD test model",
"no-Abaqus",
"tests/fortran/manifest.json",
"references/<feature-id>/<model-id>/",
"source hash",
"msg/dat/log tail",
),
"abaqus-fortran-tdd": (
"Fortran TDD",
"RED -> GREEN -> VERIFY",
"python scripts/validate_fortran.py",
"Intel oneAPI",
"ifx",
"ifort",
),
"abaqus-subroutine-validation": (
"Subroutine validation",
"externally generated",
"ready-for-comparison",
"source hash",
"msg/dat/log",
"Do not change tolerance policies.",
),
"abaqus-subroutine-physics-sanity": (
"Physics sanity",
"global equilibrium",
"reaction consistency",
"stress/strain",
"state variable",
"energy/residual",
),
"abaqus-subroutine-readiness": (
"Readiness audit",
"Gate Evidence Inventory",
"Known Limitations",
"Validation Evidence",
"Do not publish, deploy, package, tag, commit, or externally release anything unless the user explicitly asks.",
),
}
AGENT_SKILL_REFERENCES = {
"coordinator-agent.toml": (
"abaqus-subroutine-requirements",
"abaqus-subroutine-test-models",
"abaqus-subroutine-readiness",
),
"requirement-agent.toml": ("abaqus-subroutine-requirements",),
"research-agent.toml": ("abaqus-subroutine-research", "fem-theory-query"),
"formulation-agent.toml": ("abaqus-subroutine-formulation", "fem-theory-query"),
"numerical-review-agent.toml": (
"abaqus-subroutine-numerical-review",
"fem-theory-query",
),
"io-definition-agent.toml": (
"abaqus-subroutine-interface",
"fem-theory-query",
),
"reference-model-agent.toml": (
"abaqus-subroutine-test-models",
"fem-theory-query",
),
"implementation-planning-agent.toml": (
"abaqus-subroutine-formulation",
"abaqus-subroutine-test-models",
"abaqus-fortran-tdd",
"fem-theory-query",
),
"implementation-agent.toml": ("abaqus-fortran-tdd",),
"build-test-executor-agent.toml": (
"abaqus-fortran-tdd",
"abaqus-subroutine-validation",
),
"correction-agent.toml": ("abaqus-fortran-tdd",),
"reference-verification-agent.toml": (
"abaqus-subroutine-validation",
"abaqus-subroutine-interface",
),
"physics-evaluation-agent.toml": (
"abaqus-subroutine-physics-sanity",
"fem-theory-query",
),
"release-agent.toml": ("abaqus-subroutine-readiness",),
}
AGENT_REQUIRED_TERMS = {
"coordinator-agent.toml": (
"1. Subroutine requirements analysis",
"2. Research evidence",
"3. Finite element formulation",
"4. Abaqus subroutine interface",
"5. TDD test models",
"6. Fortran implementation",
"7. Subroutine validation",
),
"requirement-agent.toml": ("Subroutine requirements analysis", "ABAQUS-USUB-REQ"),
"research-agent.toml": ("books, papers, official Abaqus manuals", "source reliability"),
"formulation-agent.toml": ("stress update", "consistent tangent", "state variables"),
"numerical-review-agent.toml": (
"finite-difference tangent check",
"algorithmic consistency",
),
"io-definition-agent.toml": ("Abaqus ABI Contract", "STRESS", "DDSDDE", "STATEV"),
"reference-model-agent.toml": (
"tests/fortran/manifest.json",
"references/<feature-id>/<model-id>/",
),
"implementation-planning-agent.toml": (
"Fortran source",
"no-Abaqus driver",
"RED -> GREEN -> VERIFY",
),
"implementation-agent.toml": ("Fortran source", "Intel oneAPI", "RED -> GREEN -> VERIFY"),
"build-test-executor-agent.toml": (
"python scripts/validate_fortran.py",
"externally generated",
),
"correction-agent.toml": ("Fortran compile", "minimal correction"),
"reference-verification-agent.toml": (
"metadata.json",
"source hash",
"Abaqus version",
"compiler version",
),
"physics-evaluation-agent.toml": ("global equilibrium", "stress/strain", "state variable"),
"release-agent.toml": ("Gate Evidence Inventory", "Known Limitations"),
}
def parse_frontmatter(text):
lines = text.splitlines()
if not lines or lines[0] != "---":
raise AssertionError("SKILL.md must start with YAML frontmatter")
fields = {}
for line in lines[1:]:
if line == "---":
return fields
key, sep, value = line.partition(":")
if not sep:
raise AssertionError(f"Invalid frontmatter line: {line}")
fields[key.strip()] = value.strip()
raise AssertionError("SKILL.md frontmatter must be closed")
class AbaqusSubroutineCodexConfigTests(unittest.TestCase):
def test_abaqus_subroutine_skill_files_exist_with_metadata(self):
for skill_name, body_terms in SKILLS.items():
with self.subTest(skill=skill_name):
skill_path = SKILLS_ROOT / skill_name / "SKILL.md"
self.assertTrue(skill_path.exists(), f"{skill_name} SKILL.md is missing")
text = skill_path.read_text(encoding="utf-8")
fields = parse_frontmatter(text)
self.assertEqual(set(fields), {"name", "description"})
self.assertEqual(fields["name"], skill_name)
self.assertIn("Use when", fields["description"])
self.assertIn("Abaqus", fields["description"])
self.assertIn("User Subroutine", fields["description"])
for section in COMMON_SKILL_SECTIONS:
self.assertIn(section, text)
self.assertIn("AGENTS.md", text)
self.assertIn("docs/ABAQUS_SUBROUTINE_AGENT_DESIGN.md", text)
for term in body_terms:
self.assertIn(term, text)
def test_abaqus_subroutine_skills_have_ui_metadata(self):
for skill_name in SKILLS:
with self.subTest(skill=skill_name):
metadata = SKILLS_ROOT / skill_name / "agents" / "openai.yaml"
self.assertTrue(metadata.exists(), f"{skill_name} openai.yaml is missing")
text = metadata.read_text(encoding="utf-8")
self.assertIn("interface:", text)
self.assertIn("display_name:", text)
self.assertIn("short_description:", text)
self.assertIn("default_prompt:", text)
self.assertIn(f"${skill_name}", text)
def test_deprecated_fesa_skill_directories_are_removed(self):
deprecated = sorted(p.name for p in SKILLS_ROOT.iterdir() if p.name.startswith("fesa-"))
self.assertEqual([], deprecated)
def test_agents_reference_abaqus_subroutine_skills(self):
for agent_file, skill_names in AGENT_SKILL_REFERENCES.items():
with self.subTest(agent=agent_file):
text = (AGENTS_ROOT / agent_file).read_text(encoding="utf-8")
data = tomllib.loads(text)
self.assertEqual(data["name"], agent_file.removesuffix(".toml"))
self.assertEqual(data["model_reasoning_effort"], "extra high")
self.assertIn("Abaqus User Subroutine", data["description"])
instructions = data["developer_instructions"]
self.assertIn("Skill references:", instructions)
self.assertIn("Abaqus User Subroutine", instructions)
for skill_name in skill_names:
self.assertIn(f"${skill_name}", instructions)
for term in AGENT_REQUIRED_TERMS[agent_file]:
self.assertIn(term, instructions)
def test_agent_and_skill_text_no_longer_targets_fesa_cpp_solver_work(self):
checked_paths = list(AGENTS_ROOT.glob("*.toml"))
checked_paths += [SKILLS_ROOT / name / "SKILL.md" for name in SKILLS]
checked_paths += list((SKILLS_ROOT / "harness-workflow").glob("SKILL.md"))
checked_paths += list((SKILLS_ROOT / "harness-review").glob("SKILL.md"))
checked_paths += list((ROOT / "docs").glob("**/README.md"))
forbidden_terms = (
"FESA",
"FESA solver",
"FESA FEM",
"FESA C++",
"Nastran",
"C++17/MSVC",
"C++/MSVC",
"HARNESS_ABAQUS_VALIDATION=run",
"opt-in Abaqus",
"Abaqus opt-in",
"Abaqus execution is valid",
"If explicitly configured, run",
)
for path in checked_paths:
with self.subTest(path=path):
text = path.read_text(encoding="utf-8")
for forbidden in forbidden_terms:
self.assertNotIn(forbidden, text)
def test_harness_skills_target_fortran_subroutine_workflow(self):
for skill_name in ("harness-workflow", "harness-review"):
with self.subTest(skill=skill_name):
text = (SKILLS_ROOT / skill_name / "SKILL.md").read_text(encoding="utf-8")
for term in (
"Abaqus User Subroutine",
"Fortran",
"python scripts/validate_fortran.py",
"python scripts/validate_reference_artifacts.py",
"externally generated",
):
self.assertIn(term, text)
def test_design_doc_captures_user_subroutine_process(self):
text = DESIGN_DOC.read_text(encoding="utf-8")
for term in (
"Abaqus User Subroutine development process",
"1. Subroutine requirements analysis",
"2. Books, papers, and research evidence",
"3. Finite element formulation for implementation",
"4. Subroutine input/output parameter definition",
"5. TDD test model design",
"6. Fortran code implementation",
"7. Subroutine validation",
):
self.assertIn(term, text)
if __name__ == "__main__":
unittest.main()
-434
View File
@@ -1,434 +0,0 @@
import csv
import contextlib
import hashlib
import importlib.util
import io
import json
import tempfile
import unittest
from pathlib import Path
def load_compare_extracted_csv():
module_path = Path(__file__).resolve().parent / "compare_extracted_csv.py"
spec = importlib.util.spec_from_file_location("compare_extracted_csv", module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def write_json(path: Path, payload: dict):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def write_csv(path: Path, rows: list[dict[str, str]]):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
def write_text(path: Path, text: str):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def metadata_payload() -> dict:
return {
"schema_version": "abaqus-user-subroutine-artifact-v1",
"feature_id": "umat",
"model_id": "single-element",
"artifact_status": "ready-for-comparison",
"abaqus": {"version": "2024", "precision": "double"},
"compiler": {"vendor": "Intel oneAPI", "name": "ifx", "version": "2024"},
"subroutine": {"entry_points": ["UMAT"], "source_files": []},
"input_file": "model.inp",
"outputs": {
"tails": {
"msg": "job.msg.tail.txt",
"dat": "job.dat.tail.txt",
"log": "job.log.tail.txt",
"sta": "job.sta.tail.txt",
},
"csv": {"stresses": "extracted/stresses.csv"},
},
"extraction": {
"source_odb": "job.odb",
"tool": "Abaqus Python",
"extracted_at": "2026-06-10T00:00:00+09:00",
"csv_directory": "extracted",
},
"comparisons": {
"stresses": {
"reference_csv": "extracted/stresses.csv",
"actual_csv": "extracted/stresses.csv",
"required_columns": [
"step",
"frame",
"instance",
"element_label",
"integration_point",
"section_point",
"output_position",
"component",
"coordinate_system",
"unit",
"value",
],
"key_columns": [
"step",
"frame",
"instance",
"element_label",
"integration_point",
"section_point",
"output_position",
"component",
],
"value_column": "value",
"unit_column": "unit",
"coordinate_system_column": "coordinate_system",
"tolerance": {"absolute": 1.0e-8, "relative": 1.0e-6, "relative_floor": 1.0e-12},
}
},
}
def stress_rows(value: str = "100.0") -> list[dict[str, str]]:
return [
{
"step": "Step-1",
"frame": "1",
"instance": "PART-1-1",
"element_label": "1",
"integration_point": "1",
"section_point": "",
"output_position": "INTEGRATION_POINT",
"component": "S11",
"coordinate_system": "GLOBAL",
"unit": "MPa",
"value": value,
}
]
def make_metadata_valid_for_artifact_validation(root: Path, model_dir: Path, payload: dict) -> dict:
source = root / "src" / "fortran" / "abaqus" / "UMAT.for"
source.parent.mkdir(parents=True, exist_ok=True)
source.write_text(" subroutine umat()\n end\n", encoding="utf-8")
source_hash = hashlib.sha256(source.read_bytes()).hexdigest()
payload["subroutine"]["source_files"] = [
{
"path": "src/fortran/abaqus/UMAT.for",
"language": "Fortran",
"sha256": source_hash,
}
]
for name in [
"model.inp",
"job.msg.tail.txt",
"job.dat.tail.txt",
"job.log.tail.txt",
"job.sta.tail.txt",
]:
path = model_dir / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("ok\n", encoding="utf-8")
return payload
def compare_stresses(reference_rows: list[dict[str, str]], actual_rows: list[dict[str, str]]) -> dict:
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
write_csv(reference / "extracted" / "stresses.csv", reference_rows)
write_csv(actual / "extracted" / "stresses.csv", actual_rows)
return compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
def call_main_silently(compare, argv: list[str]) -> int:
with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
return compare.main(argv)
class CompareExtractedCsvTests(unittest.TestCase):
def test_quantity_passes_when_schema_keys_units_and_values_match_within_tolerance(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
write_csv(actual / "extracted" / "stresses.csv", stress_rows("100.00000001"))
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "pass")
self.assertEqual(report["quantities"][0]["result"], "pass")
self.assertEqual(report["quantities"][0]["classification"], "N/A")
self.assertEqual(report["quantities"][0]["compared_rows"], 1)
def test_missing_actual_csv_is_missing_generated_output(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "missing-generated-output")
def test_missing_required_column_is_schema_mismatch(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
row = stress_rows("100.0")[0]
write_csv(reference / "extracted" / "stresses.csv", [row])
actual_row = dict(row)
actual_row.pop("coordinate_system")
write_csv(actual / "extracted" / "stresses.csv", [actual_row])
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "schema-mismatch")
def test_missing_quantity_contract_is_upstream_contract(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
payload = metadata_payload()
payload["comparisons"].pop("stresses")
write_json(reference / "metadata.json", payload)
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "upstream-contract")
def test_missing_comparisons_block_is_upstream_contract(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
payload = metadata_payload()
payload.pop("comparisons")
write_json(reference / "metadata.json", payload)
report = compare.compare_metadata(reference / "metadata.json", actual, validate_artifacts=False)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "upstream-contract")
def test_incomplete_quantity_contract_is_upstream_contract(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
payload = metadata_payload()
payload["comparisons"]["stresses"].pop("value_column")
write_json(reference / "metadata.json", payload)
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "upstream-contract")
def test_duplicate_header_is_schema_mismatch(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
header = ",".join(list(stress_rows("100.0")[0]) + ["value"])
row = ",".join(stress_rows("100.0")[0].values()) + ",100.0"
write_text(actual / "extracted" / "stresses.csv", f"{header}\n{row}\n")
report = compare.compare_metadata(
reference / "metadata.json",
actual,
quantities=["stresses"],
validate_artifacts=False,
)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "schema-mismatch")
def test_changed_row_key_is_id_mismatch(self):
actual_row = dict(stress_rows("100.0")[0])
actual_row["element_label"] = "2"
report = compare_stresses(stress_rows("100.0"), [actual_row])
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "id-mismatch")
self.assertEqual(report["quantities"][0]["missing_rows"], 1)
self.assertEqual(report["quantities"][0]["extra_rows"], 1)
def test_unit_mismatch_is_unit_or_coordinate_mismatch(self):
actual_row = dict(stress_rows("100.0")[0])
actual_row["unit"] = "Pa"
report = compare_stresses(stress_rows("100.0"), [actual_row])
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "unit-or-coordinate-mismatch")
def test_coordinate_system_mismatch_is_unit_or_coordinate_mismatch(self):
actual_row = dict(stress_rows("100.0")[0])
actual_row["coordinate_system"] = "LOCAL-1"
report = compare_stresses(stress_rows("100.0"), [actual_row])
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "unit-or-coordinate-mismatch")
def test_nonfinite_value_is_nonfinite_result(self):
report = compare_stresses(stress_rows("100.0"), stress_rows("nan"))
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "nonfinite-result")
def test_value_outside_tolerance_is_tolerance_failure(self):
report = compare_stresses(stress_rows("100.0"), stress_rows("101.0"))
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "tolerance-failure")
self.assertEqual(report["quantities"][0]["max_abs_error"], 1.0)
self.assertEqual(report["quantities"][0]["result"], "fail")
def test_cli_writes_json_report_and_returns_zero_when_all_quantities_pass(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
report_json = root / "build" / "reference-verification" / "umat-single-element.json"
payload = make_metadata_valid_for_artifact_validation(root, reference, metadata_payload())
write_json(reference / "metadata.json", payload)
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
write_csv(actual / "extracted" / "stresses.csv", stress_rows("100.00000001"))
exit_code = call_main_silently(
compare,
[
"--metadata",
str(reference / "metadata.json"),
"--actual-root",
str(actual),
"--quantity",
"stresses",
"--report-json",
str(report_json),
],
)
report = json.loads(report_json.read_text(encoding="utf-8"))
self.assertEqual(exit_code, 0)
self.assertEqual(report["overall_result"], "pass")
def test_cli_writes_json_report_and_returns_one_when_a_quantity_fails(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
report_json = root / "build" / "reference-verification" / "umat-single-element.json"
payload = make_metadata_valid_for_artifact_validation(root, reference, metadata_payload())
write_json(reference / "metadata.json", payload)
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
write_csv(actual / "extracted" / "stresses.csv", stress_rows("101.0"))
exit_code = call_main_silently(
compare,
[
"--metadata",
str(reference / "metadata.json"),
"--actual-root",
str(actual),
"--quantity",
"stresses",
"--report-json",
str(report_json),
],
)
report = json.loads(report_json.read_text(encoding="utf-8"))
self.assertEqual(exit_code, 1)
self.assertEqual(report["overall_result"], "fail")
def test_cli_returns_two_for_invalid_arguments(self):
compare = load_compare_extracted_csv()
self.assertEqual(call_main_silently(compare, []), 2)
def test_default_comparison_validates_reference_artifact_metadata(self):
compare = load_compare_extracted_csv()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
reference = root / "references" / "umat" / "single-element"
actual = root / "external-results" / "umat" / "single-element"
write_json(reference / "metadata.json", metadata_payload())
write_csv(reference / "extracted" / "stresses.csv", stress_rows("100.0"))
write_csv(actual / "extracted" / "stresses.csv", stress_rows("100.0"))
report = compare.compare_metadata(reference / "metadata.json", actual)
self.assertEqual(report["overall_result"], "fail")
self.assertEqual(report["quantities"][0]["classification"], "missing-reference-artifact")
if __name__ == "__main__":
unittest.main()
-82
View File
@@ -1,82 +0,0 @@
import importlib.util
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
def load_fortran_toolchain():
module_path = Path(__file__).resolve().parent / "fortran_toolchain.py"
spec = importlib.util.spec_from_file_location("fortran_toolchain", module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class FortranToolchainTests(unittest.TestCase):
def test_auto_prefers_ifx_over_ifort_when_both_are_on_path(self):
fortran_toolchain = load_fortran_toolchain()
def fake_which(name):
return f"C:\\oneapi\\{name}.exe" if name in {"ifx", "ifort"} else None
with patch.object(fortran_toolchain.shutil, "which", side_effect=fake_which):
with patch.dict(os.environ, {}, clear=True):
toolchain = fortran_toolchain.resolve_toolchain()
self.assertEqual(toolchain.name, "ifx")
self.assertEqual(toolchain.executable, "C:\\oneapi\\ifx.exe")
self.assertIsNone(toolchain.env_script)
def test_auto_uses_oneapi_env_script_when_compiler_is_not_on_path(self):
fortran_toolchain = load_fortran_toolchain()
with tempfile.TemporaryDirectory() as tmp:
vars_bat = Path(tmp) / "setvars.bat"
vars_bat.write_text("@echo off\n", encoding="utf-8")
with patch.object(fortran_toolchain.shutil, "which", return_value=None):
with patch.object(fortran_toolchain, "ONEAPI_VARS_CANDIDATES", [vars_bat]):
with patch.dict(os.environ, {}, clear=True):
toolchain = fortran_toolchain.resolve_toolchain()
self.assertEqual(toolchain.name, "ifx")
self.assertEqual(toolchain.executable, "ifx")
self.assertEqual(toolchain.env_script, vars_bat)
def test_explicit_compiler_preference_is_honored(self):
fortran_toolchain = load_fortran_toolchain()
def fake_which(name):
return f"C:\\oneapi\\{name}.exe"
with patch.object(fortran_toolchain.shutil, "which", side_effect=fake_which):
with patch.dict(os.environ, {"HARNESS_FORTRAN_COMPILER": "ifort"}, clear=True):
toolchain = fortran_toolchain.resolve_toolchain()
self.assertEqual(toolchain.name, "ifort")
self.assertEqual(toolchain.executable, "C:\\oneapi\\ifort.exe")
def test_wrap_command_calls_oneapi_env_before_compiler(self):
fortran_toolchain = load_fortran_toolchain()
toolchain = fortran_toolchain.FortranToolchain(
name="ifx",
executable="ifx",
env_script=Path(r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat"),
)
with patch.dict(
os.environ,
{"ComSpec": r"C:\Windows\System32\cmd.exe", "SystemRoot": r"C:\Windows"},
clear=True,
):
command = fortran_toolchain.wrap_command(toolchain, ["ifx", "/nologo", "test.f90"])
self.assertIn(r"C:\Windows\System32\cmd.exe /d /s /c", command)
self.assertIn('set "PATH=C:\\Windows\\System32;C:\\Windows"', command)
self.assertIn('call "C:\\Program Files (x86)\\Intel\\oneAPI\\setvars.bat" intel64', command)
self.assertIn("ifx /nologo test.f90", command)
if __name__ == "__main__":
unittest.main()
@@ -1,191 +0,0 @@
import hashlib
import importlib.util
import json
import tempfile
import unittest
from pathlib import Path
def load_validate_reference_artifacts():
module_path = Path(__file__).resolve().parent / "validate_reference_artifacts.py"
spec = importlib.util.spec_from_file_location("validate_reference_artifacts", module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def write_json(path: Path, payload: dict):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
def write_text(path: Path, text: str = "ok\n"):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def ready_metadata(source_hash: str) -> dict:
return {
"schema_version": "abaqus-user-subroutine-artifact-v1",
"feature_id": "umat",
"model_id": "single-element",
"artifact_status": "ready-for-comparison",
"abaqus": {"version": "2024", "precision": "double"},
"compiler": {"vendor": "Intel oneAPI", "name": "ifx", "version": "2024"},
"subroutine": {
"entry_points": ["UMAT"],
"source_files": [
{
"path": "src/fortran/abaqus/UMAT.for",
"language": "Fortran",
"sha256": source_hash,
}
],
},
"input_file": "model.inp",
"outputs": {
"tails": {
"msg": "job.msg.tail.txt",
"dat": "job.dat.tail.txt",
"log": "job.log.tail.txt",
"sta": "job.sta.tail.txt",
},
"csv": {"stresses": "extracted/stresses.csv"},
},
"extraction": {
"source_odb": "job.odb",
"tool": "Abaqus Python",
"extracted_at": "2026-06-10T00:00:00+09:00",
"csv_directory": "extracted",
"script": "extraction/extract_odb_to_csv.py",
},
}
def create_ready_bundle(root: Path) -> tuple[Path, dict]:
source = root / "src" / "fortran" / "abaqus" / "UMAT.for"
write_text(source, " subroutine umat()\n end\n")
source_hash = hashlib.sha256(source.read_bytes()).hexdigest()
model_dir = root / "references" / "umat" / "single-element"
for name in [
"model.inp",
"job.msg.tail.txt",
"job.dat.tail.txt",
"job.log.tail.txt",
"job.sta.tail.txt",
"extracted/stresses.csv",
"extraction/extract_odb_to_csv.py",
]:
write_text(model_dir / name)
return model_dir, ready_metadata(source_hash)
class ValidateReferenceArtifactsTests(unittest.TestCase):
def test_missing_references_directory_is_valid(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
self.assertEqual(validator.validate_root(Path(tmp)), [])
def test_draft_metadata_with_minimal_provenance_is_valid(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
write_json(
root / "references" / "umat" / "single-element" / "metadata.json",
{
"schema_version": "abaqus-user-subroutine-artifact-v1",
"feature_id": "umat",
"model_id": "single-element",
"artifact_status": "draft",
},
)
self.assertEqual(validator.validate_root(root), [])
def test_ready_for_comparison_requires_declared_files(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
write_json(
root / "references" / "umat" / "single-element" / "metadata.json",
{
"schema_version": "abaqus-user-subroutine-artifact-v1",
"feature_id": "umat",
"model_id": "single-element",
"artifact_status": "ready-for-comparison",
"abaqus": {"version": "2024", "precision": "double"},
"compiler": {"vendor": "Intel oneAPI", "name": "ifx", "version": "2024"},
"subroutine": {
"entry_points": ["UMAT"],
"source_files": [{"path": "src/fortran/abaqus/UMAT.for", "language": "Fortran", "sha256": "abc"}],
},
"input_file": "model.inp",
"outputs": {
"tails": {"msg": "job.msg.tail.txt", "dat": "job.dat.tail.txt", "log": "job.log.tail.txt"},
"csv": {"stresses": "extracted/stresses.csv"},
},
},
)
errors = validator.validate_root(root)
self.assertTrue(any("missing input_file" in error for error in errors))
self.assertTrue(any("missing output tail sta" in error for error in errors))
self.assertTrue(any("missing ready-for-comparison key extraction" in error for error in errors))
self.assertTrue(any("missing csv output" in error for error in errors))
self.assertTrue(any("missing source file" in error for error in errors))
def test_ready_for_comparison_accepts_external_bundle_without_abaqus_command(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
model_dir, payload = create_ready_bundle(root)
payload["extraction"]["odb_sha256_file"] = "result.odb.sha256"
write_text(model_dir / "result.odb.sha256", "0" * 64 + " job.odb\n")
write_json(model_dir / "metadata.json", payload)
self.assertEqual(validator.validate_root(root), [])
def test_ready_for_comparison_checks_source_sha256(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
model_dir, payload = create_ready_bundle(root)
payload["subroutine"]["source_files"][0]["sha256"] = "0" * 64
write_json(model_dir / "metadata.json", payload)
errors = validator.validate_root(root)
self.assertTrue(any("sha256 mismatch" in error for error in errors))
def test_ready_for_comparison_rejects_csv_outside_extracted_directory(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
model_dir, payload = create_ready_bundle(root)
payload["outputs"]["csv"] = {"stresses": "stresses.csv"}
write_text(model_dir / "stresses.csv")
write_json(model_dir / "metadata.json", payload)
errors = validator.validate_root(root)
self.assertTrue(any("csv output stresses must match extracted/*.csv" in error for error in errors))
def test_ready_for_comparison_checks_optional_odb_sha256_file(self):
validator = load_validate_reference_artifacts()
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
model_dir, payload = create_ready_bundle(root)
payload["extraction"]["odb_sha256_file"] = "result.odb.sha256"
write_text(model_dir / "result.odb.sha256", "not-a-sha\n")
write_json(model_dir / "metadata.json", payload)
errors = validator.validate_root(root)
self.assertTrue(any("invalid odb_sha256_file" in error for error in errors))
if __name__ == "__main__":
unittest.main()