Files
SystemSimulationApp/tests/test_native_property_warnings.py
T

113 lines
6.2 KiB
Python

"""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 <stdio.h>
#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()