完善通用求解器回归与前端交互

- 引入因果坐标内核、热流体恢复和递进长时回归\n- 完善正交连线、线桥、视图保持与结果曲线缩放\n- 补充依赖约束、CI、测试基线和北京时间更新日志
This commit is contained in:
lujingze committed 2026-08-18 06:42:07 +00:00
1 parent 143e8dd309
commit b435daecf2
65 files changed
+172271 -701

No files matched your search

+149 -1
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import os
from types import SimpleNamespace
import unittest
from unittest.mock import patch
@@ -13,6 +14,7 @@ from app.simulation.components.experimental.storage.cylinder import Cylinder
from app.simulation.components.experimental.storage.tank import Tank
from app.simulation.core.medium import IdealGasMedium
from app.simulation.solvers.algebraic import (
CAUSAL_EXECUTOR_V2_ENVIRONMENT_VARIABLE,
AlgebraicSolveDiagnostics,
PressureFlowSolver,
)
@@ -26,6 +28,7 @@ from app.simulation.systems.network import SimulationNetwork
def _pnl0002_solver(
*,
include_unselected_island: bool = False,
executor_v2: bool = False,
) -> tuple[PressureFlowSolver, AmesimPnl0002]:
medium = IdealGasMedium()
left = Cylinder("left", medium, V=0.1, p0=500_000.0)
@@ -51,12 +54,157 @@ def _pnl0002_solver(
for component in network.dynamic_components():
component.refresh_thermodynamic_ports()
solver = PressureFlowSolver(network)
with patch.dict(
os.environ,
{
CAUSAL_EXECUTOR_V2_ENVIRONMENT_VARIABLE: (
"1" if executor_v2 else "0"
)
},
):
solver = PressureFlowSolver(network)
solver.solve()
return solver, pipe
class StreamPressureBlockSolverTests(unittest.TestCase):
def test_compiled_v2_secondary_matches_v1_and_skips_seed_sets(self) -> None:
compiled_solver, _compiled_pipe = _pnl0002_solver(executor_v2=True)
v1_solver, _v1_pipe = _pnl0002_solver()
compiled = StreamPressureBlockSolver(compiled_solver, ("pipe",))
v1 = StreamPressureBlockSolver(v1_solver, ("pipe",))
compiled.solve(scale_context=compiled_solver.scale_context())
v1.solve(scale_context=v1_solver.scale_context())
with patch.object(
compiled,
"_seed_selected_blocks",
wraps=compiled._seed_selected_blocks,
) as legacy_seed, patch.object(
compiled_solver,
"_evaluate_explicit_flow_stage",
wraps=compiled_solver._evaluate_explicit_flow_stage,
) as allocating_stage:
compiled_result = compiled.solve(
scale_context=compiled_solver.scale_context()
)
v1.solve(scale_context=v1_solver.scale_context())
legacy_seed.assert_not_called()
allocating_stage.assert_not_called()
self.assertEqual(
tuple(unknown.read() for unknown in compiled_solver.unknowns),
tuple(unknown.read() for unknown in v1_solver.unknowns),
)
self.assertIs(
compiled_result.diagnostics[0],
compiled._causal_cached_fast_diagnostics,
)
execution = compiled.causal_execution_diagnostics()
self.assertEqual(execution["executorV2FastSolveCount"], 1)
self.assertEqual(execution["coordinateKernelFastSolveCount"], 1)
self.assertEqual(
execution["compiledAssignmentCount"],
len(compiled._selected_unknowns),
)
self.assertEqual(execution["executorV2RuntimeValidationFailureCount"], 0)
def test_compiled_v2_secondary_failure_restores_before_legacy_seed(
self,
) -> None:
solver, _pipe = _pnl0002_solver(executor_v2=True)
block_solver = StreamPressureBlockSolver(solver, ("pipe",))
block_solver.solve(scale_context=solver.scale_context())
expected = tuple(
unknown.read() for unknown in block_solver._selected_flow_unknowns
)
original_seed = block_solver._seed_selected_blocks
def one_nonfinite_assignment():
block_solver._selected_flow_unknowns[0].state.m_flow = float("nan")
return "nonFiniteCausalSecondaryFlowAssignment"
def checked_legacy_seed(entry_values):
self.assertEqual(
tuple(
unknown.read()
for unknown in block_solver._selected_flow_unknowns
),
expected,
)
return original_seed(entry_values)
with patch.object(
block_solver,
"_execute_compiled_secondary_flow_plan",
side_effect=one_nonfinite_assignment,
), patch.object(
block_solver,
"_seed_selected_blocks",
side_effect=checked_legacy_seed,
):
result = block_solver.solve(scale_context=solver.scale_context())
self.assertFalse(result.used_global_fallback)
execution = block_solver.causal_execution_diagnostics()
self.assertFalse(execution["enabled"])
self.assertEqual(
execution["disabledReason"],
"nonFiniteCausalSecondaryFlowAssignment",
)
self.assertEqual(execution["executorV2RuntimeValidationFailureCount"], 1)
self.assertEqual(execution["legacyFallbackCount"], 1)
def test_compiled_v2_secondary_base_error_restores_transaction(self) -> None:
class ForcedFatalError(BaseException):
pass
solver, _pipe = _pnl0002_solver(executor_v2=True)
block_solver = StreamPressureBlockSolver(solver, ("pipe",))
block_solver.solve(scale_context=solver.scale_context())
expected = tuple(
unknown.read() for unknown in block_solver._selected_flow_unknowns
)
def broken_execution() -> None:
block_solver._selected_flow_unknowns[0].write(123_456.0)
raise ForcedFatalError("forced v2 interruption")
with patch.object(
block_solver,
"_execute_compiled_secondary_flow_plan",
side_effect=broken_execution,
):
with self.assertRaises(ForcedFatalError):
block_solver.solve(scale_context=solver.scale_context())
self.assertEqual(
tuple(
unknown.read()
for unknown in block_solver._selected_flow_unknowns
),
expected,
)
def test_compiled_v2_secondary_assignment_count_drift_falls_back(self) -> None:
solver, _pipe = _pnl0002_solver(executor_v2=True)
block_solver = StreamPressureBlockSolver(solver, ("pipe",))
block_solver.solve(scale_context=solver.scale_context())
with patch.object(
block_solver,
"_execute_compiled_secondary_flow_plan",
return_value="causalSecondaryFlowAssignmentCountMismatch",
):
result = block_solver.solve(scale_context=solver.scale_context())
self.assertFalse(result.used_global_fallback)
execution = block_solver.causal_execution_diagnostics()
self.assertEqual(
execution["disabledReason"],
"causalSecondaryFlowAssignmentCountMismatch",
)
self.assertEqual(execution["executorV2RuntimeValidationFailureCount"], 1)
def test_pnl0002_uses_two_blocks_with_one_shared_component(self) -> None:
solver, pipe = _pnl0002_solver()
block_solver = StreamPressureBlockSolver(solver, ("pipe",))