"""Property-domain diagnostics must be nonfatal and independent of trial states.""" from dataclasses import replace import json from pathlib import Path import subprocess import tempfile import unittest from app.simulation.components.amesim.media.mediums import AmesimIdealAirMedium from app.simulation.config import SolveIVPConfig from app.simulation.native_codegen.build import build_native, toolchain, _command from app.simulation.native_codegen.compiler import _compile_storage_anchored_program from app.simulation.native_codegen.extended import compile_extended_program from app.simulation.native_codegen.runner import execute_native from tests.test_native_catalog import Circuit ROOT = Path(__file__).resolve().parents[1] class NativePropertyWarningTests(unittest.TestCase): def run_program(self, program, method='BDF', **kwargs): build = build_native(program) self.addCleanup(build.close) with tempfile.TemporaryDirectory() as directory: events = [] result = execute_native(build, SolveIVPConfig(t_stop=.01, max_step=.001, method=method), .001, run_dir=Path(directory), record_samples=False, warning_callback=events.append, **kwargs) self.assertTrue(result['success'], result['message']) self.assertEqual(len(events), len(result['propertyWarnings'])) for warning in result['propertyWarnings']: self.assertNotIn('component', warning) self.assertIn('氦气', warning['message']) self.assertIn('K', warning['message']) return result def test_both_generators_boundaries_and_no_viscosity_when_unused(self): for compiler in (_compile_storage_anchored_program, compile_extended_program): for T, expected in ((200, []), (1500, []), (1500.01, ['helium-equation-of-state-high']), (100, ['helium-ideal-caloric-low']), (20000, ['helium-equation-of-state-high']), (20000.01, ['helium-equation-of-state-high', 'helium-ideal-caloric-high'])): with self.subTest(compiler=compiler.__name__, T=T): b = Circuit(); b.chamber('gas', p0=1e5, T0=T, kth=0) result = self.run_program(compiler(b.seal())) self.assertEqual([w['code'] for w in result['propertyWarnings']], expected) self.assertTrue(all(w['time'] == 0 for w in result['propertyWarnings'])) def test_live_dedup_extrema_and_unchanged_numerical_results(self): b = Circuit() b.chamber('gas', p0=1e5, T0=1400, kth=1000, sth=1, extemp=26000) program = compile_extended_program(b.seal()) disabled = replace(program, header=program.header.replace( '#define MODEL_PROPERTY_TEMPERATURES 1', '#define MODEL_PROPERTY_TEMPERATURES 0')) for method in ('RK45', 'BDF'): with self.subTest(method=method): enabled_result = self.run_program(program, method) disabled_result = self.run_program(disabled, method) warnings = enabled_result['propertyWarnings'] self.assertEqual(len(warnings), 2) for warning in warnings: self.assertGreater(warning['time'], 0) self.assertGreater(warning['extremeTemperature'], warning['temperature']) self.assertGreaterEqual(warning['extremeTime'], warning['time']) for key in ('finalState', 'final', 'nfev', 'njev', 'nlu', 'acceptedSteps', 'rejectedSteps'): self.assertEqual(enabled_result[key], disabled_result[key], key) def test_pipe_viscosity_and_air_domain_not_invented(self): for medium, expected in ((None, {'equation-of-state', 'ideal-caloric', 'dynamic-viscosity'}), (AmesimIdealAirMedium(), set())): b = Circuit(medium) left = b.chamber('left', p0=2e5, T0=25000, kth=0) pipe = b.add('amesim_pnl0001', 'pipe', p0=1e5, T0=25000, kth=0, diam=.001, le=1) b.connect(left, 'port_1', pipe, 'port_1') result = self.run_program(compile_extended_program(b.seal())) self.assertEqual({w['property'] for w in result['propertyWarnings']}, expected) def test_observer_survives_full_cache_and_ignores_unobserved_trials(self): source = '#include "%s"\n' % (ROOT/'native/components/modules/properties.c').as_posix() + r''' #include #define CHECK(x) do {if(!(x)){fprintf(stderr,"line %d\n",__LINE__);return 1;}} while(0) int main(void) { NativePropertyCache cache;NativePropertyState scratch; NativePropertyTemperatures report={0}; native_properties_init(&cache,NULL,0);cache.temperatures=&report; NativePropertyState *s=property_pt(&cache,&helium_medium,1e5,100,&scratch); property_density(s);property_viscosity(s);local_isentropic(s); CHECK(report.seen==7 && report.minimum[0]==100 && report.minimum[2]==100); s=property_pt(&cache,&helium_medium,1e5,25000,&scratch); property_density(s);property_viscosity(s);local_isentropic(s); CHECK(report.maximum[0]==25000 && report.maximum[1]==25000 && report.maximum[2]==25000); /* Ordinary RHS-style calls have no observer and cannot affect reports. */ native_properties_init(&cache,NULL,0); native_density_context(&cache,&helium_medium,1e5,50000); CHECK(report.maximum[0]==25000); NativePropertyTemperatures next={0};cache.temperatures=&next; native_density_context(&cache,&helium_medium,1e5,300); CHECK(next.seen==1 && next.minimum[0]==300 && next.maximum[0]==300); return 0; } ''' with tempfile.TemporaryDirectory() as directory: root = Path(directory); path = root/'test.c'; exe = root/'test.exe' path.write_text(source) _command([toolchain()[0], '-std=c11', '-O2', '-Wall', '-Wextra', '-Werror', '-I', str(ROOT/'native/include'), str(path), '-lm', '-o', str(exe)], log=[]) result = subprocess.run([str(exe)], capture_output=True, text=True, timeout=20) self.assertEqual(result.returncode, 0, result.stderr) if __name__ == '__main__': unittest.main()