优化雅可比矩阵计算;端口转发情况下仿真结果传输方式优化

This commit is contained in:
lujingze committed 2026-09-12 03:57:31 +00:00
1 parent 3bc4be3c06
commit aa4951b14e
28 files changed
+8038 -23

No files matched your search

+257
View File
@@ -0,0 +1,257 @@
"""Compiler structure and fixed-state native probes; no integration is run."""
from collections import defaultdict
import ctypes
import hashlib
import json
import math
import os
from pathlib import Path
import re
import shlex
import shutil
import struct
import subprocess
import tempfile
import unittest
from app.main import compile_system_xml_network
from app.simulation.native_codegen.compiler import compile_native_program
from app.simulation.native_codegen.extended import compile_extended_program
from app.simulation.native_codegen.input import load_input
from app.simulation.native_codegen.jacobian import StateDependencies
from tests.native_reference import reference_data, reference_network
ROOT = Path(__file__).resolve().parents[1]
def compile_input(path):
_, document = load_input(ROOT / path)
return compile_native_program(compile_system_xml_network(document))
def array(program, name):
match = re.search(r'const int '+name+r'\[\d+\] = \{([^}]*)\};', program.source)
if not match:
raise AssertionError(f'Missing generated {name}')
return list(map(int, match[1].split(',')))
def rows_and_colors(program):
pointers = array(program, 'model_jacobian_col_ptr')
indices = array(program, 'model_jacobian_row_index')
colors = array(program, 'model_jacobian_column_color')
rows = [set() for _ in program.state_keys]
for column in range(len(colors)):
for row in indices[pointers[column]:pointers[column+1]]:
rows[row].add(column)
return rows, colors
class StructuralDependencyTests(unittest.TestCase):
def test_unknown_reachable_values_and_calls_disable_coloring(self):
for expression in ('mystery', 'q[900]', 'unreviewed_kernel(y[0])'):
deps = StateDependencies(2)
deps.expression('dy[0]', expression)
deps.expression('dy[1]', '0')
result = deps.build()
self.assertFalse(result.enabled)
self.assertIn('Unresolved', result.reason)
deps = StateDependencies(1)
deps.expression('w[9]', 'unused_diagnostic[0]')
deps.expression('dy[0]', '0')
self.assertTrue(deps.build().enabled)
self.assertEqual(deps.build().rows, ((0,),))
def test_scc_closure_keeps_independent_regions_separate(self):
deps = StateDependencies(6)
for target, inputs in {'h[0]': ('h[1]', 'y[0]'), 'h[1]': ('h[0]', 'y[1]'),
'h[2]': ('h[3]', 'y[3]'), 'h[3]': ('h[2]', 'y[4]')}.items():
deps.assign(target, inputs)
for i in range(6):
deps.expression(f'dy[{i}]', '0')
deps.assign('dy[2]', ('h[0]',))
deps.assign('dy[5]', ('h[2]',))
result = deps.build()
self.assertTrue(result.enabled)
self.assertEqual(result.rows[2], (0, 1, 2))
self.assertEqual(result.rows[5], (3, 4, 5))
self.assertEqual(result.color_count, 3)
def test_projection_and_stop_control_dependencies_keep_original_sources(self):
deps = StateDependencies(6)
deps.project_states([0, 2])
deps.project_states([1, 3])
for i in range(6):
deps.expression(f'dy[{i}]', '0')
deps.expression('g[0].p', 'y[0]+y[1]')
deps.expression('dy[4]', 'g[0].p')
deps.expression('dy[5]', 'y[4]')
deps.stop_motion(4, 5)
result = deps.build()
self.assertEqual(result.rows[4], tuple(range(6)))
self.assertEqual(result.rows[5], tuple(range(6)))
def test_catalog_patterns_have_valid_csc_and_colorings(self):
for index, case in enumerate(reference_data()['cases']):
with self.subTest(case=index):
program = compile_extended_program(reference_network(case))
info = program.jacobian_structure
self.assertTrue(info['enabled'], info['reason'])
self.assertTrue(info['canonicalRhs'])
self.assertEqual(info['defaultRuntimePolicy'], info['policy'])
self.assertEqual(info['policyScope'], 'default-runtime')
self.assertEqual(info['verification'], '--verify-jacobian')
self.assertNotIn('activation', info)
rows, colors = rows_and_colors(program)
for row, entries in enumerate(rows):
self.assertIn(row, entries)
self.assertEqual(len(entries), len({colors[col] for col in entries}))
self.assertEqual(sum(map(len, rows)), info['nonzeros'])
self.assertEqual(max(colors)+1, info['colorCount'])
eligible = info['colorCount'] < len(program.state_keys)
self.assertEqual(info['runtimeEligible'], eligible)
self.assertEqual(info['runtimeFallbackReason'] is None, eligible)
if eligible:
self.assertEqual(info['policy'], 'CVODE colored forward differences; canonical-property-cache RHS')
self.assertEqual(info['rhsPolicy'], 'canonical-property-cache finite differences')
else:
self.assertEqual(info['policy'], 'CVODE default dense differences')
self.assertEqual(info['rhsPolicy'], 'ordinary model_eval')
def test_eight_branch_pattern_covers_mechanical_volume_and_uses_fewer_groups(self):
program = compile_input('tests/data/test-mql-8-corrected.json')
rows, colors = rows_and_colors(program)
self.assertLess(max(colors)+1, len(program.state_keys)//2)
self.assertTrue(program.jacobian_structure['runtimeEligible'])
self.assertEqual(program.jacobian_structure['defaultRuntimePolicy'],
'CVODE colored forward differences; canonical-property-cache RHS')
self.assertIn('#define MODEL_JACOBIAN_CANONICAL_RHS 1', program.header)
self.assertEqual(len(colors), len(program.state_keys))
index = {key: i for i, key in enumerate(program.state_keys)}
# PNCH012 pressure/energy includes piston volume from two moving masses;
# a gas-only origin label would miss these position dependencies.
target = index['amesim_pnch012_11.U']
self.assertIn(index['amesim_mecmas21_5.x'], rows[target])
self.assertIn(index['amesim_mecmas21_9.x'], rows[target])
def test_compact_path_retains_dense_fallback(self):
program = compile_input('tests/fixtures/native-skill-test.xml')
self.assertFalse(program.jacobian_structure['enabled'])
self.assertFalse(program.jacobian_structure['runtimeEligible'])
self.assertTrue(program.jacobian_structure['runtimeFallbackReason'])
self.assertEqual(program.jacobian_structure['defaultRuntimePolicy'], 'CVODE default dense differences')
self.assertEqual(program.jacobian_structure['rhsPolicy'], 'ordinary model_eval')
self.assertIn('#define MODEL_JACOBIAN_COLORED 0', program.header)
self.assertIn('#define MODEL_JACOBIAN_CANONICAL_RHS 0', program.header)
self.assertNotIn('model_eval_jacobian', program.header)
class NativeJacobianProbeTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
command = shlex.split(os.environ.get('CC', ''))
if not command:
compiler = shutil.which('gcc') or shutil.which('clang')
if not compiler:
raise unittest.SkipTest('A C compiler is required for fixed-state probes')
command = [compiler]
cls.compiler = command
cls.directory = tempfile.TemporaryDirectory(prefix='native-jacobian-probe-')
cls.addClassCleanup(cls.directory.cleanup)
cls.root = Path(cls.directory.name)
cls.fixture = json.loads((ROOT/'tests/fixtures/native-jacobian-cache-state.json').read_text())
if hashlib.sha256((ROOT/cls.fixture['input']).read_bytes()).hexdigest() != cls.fixture['inputSha256']:
raise AssertionError('Update the fixed-state fixture deliberately when changing the physical input')
cls.program = compile_input(cls.fixture['input'])
cls.n = len(cls.program.state_keys)
cls.noutputs = len(cls.program.variables)
cls.library = cls.build_library('current', cls.program.source)
# Restore the pre-canonical gas-seeding policy for BOTH entrypoints in
# an isolated TU. This checks ordinary dispatch/cache behavior on each
# platform without requiring identical libm rounding across platforms.
old = 'gas_properties=canonical?NULL:properties;'
if cls.program.source.count(old) != 1:
raise AssertionError('Update the explicitly restored baseline policy when changing the generator')
baseline = cls.program.source.replace(old, 'gas_properties=properties;(void)canonical;')
cls.baseline = cls.build_library('seeded-baseline', baseline)
@classmethod
def build_library(cls, name, source):
directory = cls.root/name
directory.mkdir()
(directory/'model.c').write_text(source)
(directory/'model.h').write_text(cls.program.header)
output = directory/('probe.dll' if os.name == 'nt' else 'probe.so')
command = cls.compiler + ['-std=c11', '-O3', '-shared', '-Wall', '-Wextra', '-Werror',
'-ffp-contract=off', '-fno-fast-math']
if os.name != 'nt':
command.append('-fPIC')
command += ['-I', str(directory), '-I', str(ROOT/'native/include'),
str(directory/'model.c'), str(ROOT/'native/components/kernels.c'), '-lm', '-o', str(output)]
result = subprocess.run(command, capture_output=True, text=True, timeout=60)
if result.returncode:
raise AssertionError(result.stderr)
library = ctypes.CDLL(str(output))
if os.name == 'nt':
import _ctypes
cls.addClassCleanup(_ctypes.FreeLibrary, library._handle)
for name in ('model_eval', 'model_eval_jacobian'):
function = getattr(library, name)
function.argtypes = [ctypes.c_double, *([ctypes.POINTER(ctypes.c_double)]*3)]
function.restype = ctypes.c_int
return library
def evaluate(self, values, *, canonical=True, library=None):
state = (ctypes.c_double*self.n)(*values)
derivative = (ctypes.c_double*self.n)()
outputs = (ctypes.c_double*self.noutputs)()
function = getattr(library or self.library, 'model_eval_jacobian' if canonical else 'model_eval')
self.assertEqual(function(self.fixture['time'], state, derivative, outputs), 1)
self.assertEqual(list(state), values, 'RHS evaluation mutated its input state')
return list(derivative), list(outputs)
def test_normal_rhs_preserves_seeded_baseline_bits(self):
for column in (None, 71, 80, 81, 84):
state = self.fixture['state'][:]
if column is not None:
state[column] += math.sqrt(2**-52)*abs(state[column])
actual = self.evaluate(state, canonical=False)
expected = self.evaluate(state, canonical=False, library=self.baseline)
for got, want in zip(actual, expected):
self.assertEqual(struct.pack(f'={len(got)}d', *got), struct.pack(f'={len(want)}d', *want))
def test_canonical_full_dense_and_colored_differences_agree(self):
rows, colors = rows_and_colors(self.program)
state = self.fixture['state'][:]
base, _ = self.evaluate(state)
increments = [max(math.sqrt(2**-52)*abs(value), 1e-14) for value in state]
dense = []
for column in range(self.n):
trial = state[:]
trial[column] += increments[column]
value, _ = self.evaluate(trial)
differences = [value[row]-base[row] for row in range(self.n)]
for row in range(self.n):
if column not in rows[row]:
self.assertEqual(differences[row], 0, (row, column))
dense.append(differences)
groups = defaultdict(list)
for column, color in enumerate(colors):
groups[color].append(column)
for columns in groups.values():
trial = state[:]
for column in columns:
trial[column] += increments[column]
value, _ = self.evaluate(trial)
for column in columns:
for row in range(self.n):
if column in rows[row]:
self.assertEqual(value[row]-base[row], dense[column][row], (row, column))
# The historically offending cross-branch entries must be exact zeros.
for column in (80, 81):
for row in (68, 69, 70, 86):
self.assertEqual(dense[column][row], 0)
if __name__ == '__main__':
unittest.main()