Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
run.py
Go to the documentation of this file.
1"""Run the unmodified spatial-CD physical setup and retain an auditable report."""
2import argparse
3import datetime
4import hashlib
5import importlib.util
6import json
7import os
8from pathlib import Path
9import re
10import shutil
11import subprocess
12import sys
13import time
14
15import numpy as np
16
17HERE = Path(__file__).resolve().parent
18ROOT = HERE.parents[1]
19RTOL = 1.e-4
20ATOL = 1.e-10
21
22
23def scalar(path, name):
24 match = re.search(r'^\s*' + re.escape(name) + r'\s*=\s*([^;]+);', path.read_text(), re.M)
25 if match is None:
26 raise ValueError(f'Missing {name} in {path}')
27 return float(match.group(1))
28
29
30def compare(actual, reference):
31 if actual.shape != reference.shape or actual.shape != (120, 7):
32 raise ValueError(f'Expected a complete 120-step history, got {actual.shape}')
33 if not np.isfinite(actual).all():
34 raise ValueError('Nonfinite output')
35 if not np.allclose(actual[:, :3], reference[:, :3], rtol=1.e-12, atol=1.e-12):
36 raise ValueError('Time/dose sampling changed')
37 error = np.abs(actual[:, 3:] - reference[:, 3:])
38 limits = ATOL + RTOL * np.abs(reference[:, 3:])
39 if np.any(error > limits):
40 step, component = np.unravel_index(np.argmax(error / limits), error.shape)
41 raise ValueError(f'Strain/swelling regression at step {step}, component {component}: '
42 f'{actual[step, component+3]} versus {reference[step, component+3]}')
43 if not np.allclose(actual[:, 3], actual[:, 4], rtol=1.e-9, atol=1.e-12):
44 raise ValueError('In-plane strain symmetry lost')
45 return dict(max_absolute_error=error.max(axis=0).tolist(), rtol=RTOL, atol=ATOL)
46
47
48def history(report):
49 values = np.atleast_2d(np.loadtxt(report / 'F/F_0.txt'))
50 labels = (report / 'F/F_labels.txt').read_text().splitlines()
51 if not np.array_equal(values[:, 0], np.arange(120)):
52 raise ValueError('Missing, repeated, or unexpected run IDs')
53 if not np.isfinite(values).all():
54 raise ValueError('Nonfinite simulation output')
55 material = report / 'inputFiles/Zr4_Fitted.txt'
56 t = values[:, labels.index('time [b/cs]')]
57 conversion = scalar(material, 'b_SI') / np.sqrt(scalar(material, 'mu0_SI') / scalar(material, 'rho_SI'))
58 dose = t * conversion * scalar(material, 'doseRate_dpaPerSec')
59 strain = np.column_stack([values[:, labels.index(f'betaP_{ij}')] for ij in ['11', '22', '33']])
60 for ij in ['12', '13', '23', '21', '31', '32']:
61 if np.max(np.abs(values[:, labels.index(f'betaP_{ij}')])) > 1.e-10:
62 raise ValueError('Unexpected shear plastic distortion')
63 result = np.column_stack([t, dose, dose + .5, strain, strain.mean(axis=1)])
64 np.savetxt(report / 'growth-swelling-data.csv', result, delimiter=',',
65 header='time_b_over_cs,dose_dpa,plotted_dose_dpa,beta11,beta22,beta33,trace_beta_over_3', comments='')
66 return result
67
68
69def main():
70 parser = argparse.ArgumentParser(description=__doc__)
71 parser.add_argument('--executable', required=True, type=Path)
72 parser.add_argument('--reports', type=Path, default=HERE / 'reports')
73 args = parser.parse_args()
74 if importlib.util.find_spec("matplotlib") is None:
75 parser.error("Matplotlib is required to generate the test plots.")
76 stamp = datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%dT%H%M%S.%fZ')
77 report = args.reports.resolve() / stamp
78 report.mkdir(parents=True)
79 print(f'Report: {report}', flush=True)
80 shutil.copytree(HERE / 'inputFiles', report / 'inputFiles')
81 shutil.copy2(ROOT / 'Library/Meshes/unitCube_15K.msh', report / 'inputFiles/unitCube_15K.msh')
82 for name in ['experimental-data.csv', 'reference.csv', 'stored-tutorial-result.pdf']:
83 shutil.copy2(HERE / name, report / name)
84 for name in ['F', 'config', '.cache']:
85 (report / name).mkdir()
86 settings = {
87 'physics': 'ClusterDynamics', 'useFEM': True, 'temperature_K': 553,
88 'steps': 120, 'dt_b_over_cs': 3.4e18, 'output_frequency': 1,
89 'mesh': 'unitCube_15K.msh', 'boxScaling': [3093, 3093, 3093],
90 'active_periodic_faces': [], 'discrete_microstructure': [],
91 'experimental_series': {'EXPIa': 'Iodide purity, a', 'EXPIc': 'Iodide purity, c',
92 'EXPZa': 'Zone-refined purity, a', 'EXPZc': 'Zone-refined purity, c'},
93 'experimental_strain_unit': '1e-4', 'plot_dose_offset_dpa': .5,
94 'plot_convention': 'Original script: beta components in percent; swelling trace(beta)/3 unscaled.',
95 'reference': 'Successful WeakProblem tutorial run from 2026-10-19, not a fit to experiment.',
96 'input_sha256': {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
97 for p in sorted((report / 'inputFiles').iterdir())},
98 }
99 (report / 'settings.json').write_text(json.dumps(settings, indent=2) + '\n')
100 env = dict(os.environ, OMP_NUM_THREADS='12', MPLBACKEND='Agg',
101 MPLCONFIGDIR=str(report / '.cache'), XDG_CACHE_HOME=str(report / '.cache'))
102 started = time.monotonic()
103 result = {'passed': False, 'report': str(report)}
104 try:
105 with (report / 'simulation.log').open('w') as log:
106 subprocess.run([str(args.executable.resolve()), str(report)], cwd=report,
107 env=env, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=1650)
108 actual = history(report)
109 # Keep plots even when a numerical regression is detected.
110 with (report / 'plot.log').open('w') as log:
111 subprocess.run([sys.executable, str(HERE / 'plotGrowthSwellingDensity.py'), str(report)],
112 env=env, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=60)
113 result.update(compare(actual, np.loadtxt(report / 'reference.csv', delimiter=',', skiprows=1)))
114 result.update(passed=True, outputs=len(actual), final_values=actual[-1].tolist())
115 except Exception as error:
116 result['error'] = str(error)
117 print(f'FAIL: {error}\nInspect {report}', file=sys.stderr)
118 finally:
119 result['elapsed_seconds'] = time.monotonic() - started
120 (report / 'result.json').write_text(json.dumps(result, indent=2) + '\n')
121 print(('PASS' if result['passed'] else 'FAIL') + f': spatialCDtest; report={report}', flush=True)
122 return 0 if result['passed'] else 1
123
124
125if __name__ == '__main__':
126 sys.exit(main())
compare(actual, reference)
Definition run.py:30
scalar(path, name)
Definition run.py:23
main()
Definition run.py:69
history(report)
Definition run.py:48