"""Focused host checks for the uniform experimental DFG pilot.""" from copy import deepcopy import importlib.util import json from pathlib import Path import subprocess import sys from tempfile import TemporaryDirectory import unittest sys.path.insert(0, str(Path(__file__).resolve().parent)) import run_dfg as r def test_factory_and_default(): cfg, body, p, manifest = r.configuration(r.ROOT) r.configuration(r.ROOT) import run_refined_dfg as runner h, _ = runner.helpers(r.ROOT) old, old_body, old_p = runner.config(h, 80) assert old['method']['inlet']['scheme'] == 'zou_he_local' old['method']['inlet']['scheme'] = r.VARIANT assert cfg == old and body == old_body and p == old_p assert len(body['objects']) == 1 and p['diameter'] == 80 assert cfg == runner.config(h, 80, r.VARIANT)[0] assert manifest['commit'] == '926ca27407c26ca11283282a6e53249925725376' def receipt(): _, _, _, m = r.configuration(r.ROOT) expected = r.identity(m) rows = [dict(step=i*4000, tstar=i*.5, tstar_force=i*.5-.25, rho_min=.99, rho_max=1.01, max_mach=.03, cd=3., cl=.1, dp=2., mass=.8, flux_in=.41, flux_out=.41) for i in range(1, 11)] return expected, dict(**expected, case='uniform', mode='pilot', status='pilot_complete', healthy=True, steps=40000, executed_steps=40000, history=rows, calibration_batches_seconds=[.001]*5) def test_receipts(): expected, result = receipt() r.verify_pilot(result, expected) for key, value in [('collision', 'MRT'), ('experimental_variant', 'zou_he_local'), ('observation_mode', 'first_health_pair'), ('control', 'without_cylinder'), ('numerical_source_commit', 'other'), ('case', 'refined'), ('steps', 39999), ('executed_steps', 39999), ('healthy', False)]: bad = deepcopy(result); bad[key] = value with unittest.TestCase().assertRaises(ValueError): r.verify_pilot(bad, expected) for change in ('nan', 'missing', 'time', 'count'): bad = deepcopy(result) if change == 'nan': bad['history'][-1]['tstar'] = float('nan') if change == 'missing': del bad['history'][0]['cd'] if change == 'time': bad['history'][0]['tstar_force'] = .5 if change == 'count': bad['history'].pop() with unittest.TestCase().assertRaises(ValueError): r.verify_pilot(bad, expected) def test_accuracy_requires_reference(): assert not r.accuracy_pass(dict(healthy=True, periodic=True)) assert r.accuracy_pass(dict(healthy=True, periodic=True, reference_comparison=dict(reference_agreement=True))) assert not r.accuracy_pass(dict(healthy=True, periodic=False, reference_comparison=dict(reference_agreement=True))) def test_preflight_and_source_guards(): with TemporaryDirectory() as temp: subprocess.run([sys.executable, str(Path(r.__file__)), '--output', temp], check=True) out = json.loads((Path(temp)/'preflight.json').read_text()) assert out['max_steps'] == 40000 and out['observation_width_tstar'] == .5 assert not out['executed'] and not out['accuracy_qualified'] assert out['wall_seconds'] == 180 and out['control'] == 'with_cylinder' with unittest.TestCase().assertRaises(ValueError): r.configuration(r.ROOT.parent) with unittest.TestCase().assertRaisesRegex(ValueError, 'Pinned source mismatch'): r.diagnostic.source_config(r.ROOT, Path(r.__file__).with_name('source_manifest.json')) def test_shared_source_semantics(): r.configuration(r.ROOT) import run_refined_dfg as runner text = Path(runner.__file__).read_text() assert 'sparse_pilot=False' in text assert '(a.mode == "pilot" and not sparse_pilot)' in text assert 'impulse += sim.read_force(0).astype(np.float64) * batch' in text assert 'tstar_force=(begin + n / 2) * SPEED / p["diameter"]' in text assert 'cd=float(impulse[0] / n / den)' in text assert 'mass = float(rho[fluid].sum()) * p["dx"] ** 2' in text assert 'executed_steps += batch' in text assert 'First unhealthy observation; onset between observations is unknown' in text def test_accounted_calibration_and_failure_progress(): import numpy as np import types from unittest.mock import patch r.configuration(r.ROOT) import run_refined_dfg as runner h, periodic = runner.helpers(r.ROOT) _, _, p = runner.config(h, 80) count = [0] fail = [False] class Simulation: def __init__(self, *args, **kwargs): self.field = types.SimpleNamespace(flag=np.ones(p['nx']*p['ny'], np.uint8)) def __enter__(self): return self def __exit__(self, *args): pass def initialize(self): count[0] = 0 def run(self, n): count[0] += n def read_force(self, body): return np.array([.004, .002]) def get_macroscopic(self): shape = (p['ny'], p['nx']) return dict(rho=np.full(shape, float('nan') if fail[0] else 1.), ux=np.full(shape, .01), uy=np.zeros(shape)) modules = { 'CelerisLab.simulation': types.SimpleNamespace(Simulation=Simulation), 'CelerisLab.cuda.context': types.SimpleNamespace(CudaContext=object), 'CelerisLab.lbm.refinement_stepper': types.SimpleNamespace(FixedPatchStepper=object), } with patch.dict(sys.modules, modules), patch.object(h, 'pressure_probes', return_value=dict(dp=2.)): with TemporaryDirectory() as temp: args = types.SimpleNamespace(source=r.ROOT, output=Path(temp), mode='pilot', deadline=float('inf')) progress = {} result = runner.run_case(args, h, periodic, 'uniform', inlet_scheme=r.VARIANT, sparse_pilot=True, progress=progress) assert count[0] == 40000 and result['executed_steps'] == 40000 assert len(result['history']) == 10 and len(result['calibration_batches_seconds']) == 5 assert abs(result['history'][0]['cd'] - 1.) < 1e-14 assert result['history'][0]['tstar_force'] == .25 fail[0] = True with TemporaryDirectory() as temp: args.output = Path(temp) result = runner.run_case(args, h, periodic, 'uniform', inlet_scheme=r.VARIANT, sparse_pilot=True, progress=progress) assert result['executed_steps'] == 4000 and result['steps'] == 0 assert result['first_observed_failure_step'] == 4000 and result['last_observed_step'] == 0 fail[0] = False with TemporaryDirectory() as temp: args.output = Path(temp) def deadline(_): if count[0] >= 500: raise TimeoutError('test deadline') count[0] = 0 with patch.object(runner, 'check_deadline', side_effect=deadline): with unittest.TestCase().assertRaises(TimeoutError): runner.run_case(args, h, periodic, 'uniform', inlet_scheme=r.VARIANT, sparse_pilot=True, progress=progress) assert progress['executed_steps'] == 500 and progress['last_observed_step'] == 0 assert len(progress['calibration_batches_seconds']) == 5 if __name__ == '__main__': result = unittest.TextTestRunner(verbosity=2).run(unittest.TestSuite( unittest.FunctionTestCase(fn) for name, fn in sorted(globals().items()) if name.startswith('test_'))) raise SystemExit(not result.wasSuccessful())