完善通用求解器回归与前端交互
- 引入因果坐标内核、热流体恢复和递进长时回归\n- 完善正交连线、线桥、视图保持与结果曲线缩放\n- 补充依赖约束、CI、测试基线和北京时间更新日志
This commit is contained in:
1 parent
143e8dd309
commit
b435daecf2
65 files changed
+172271
-701
No files matched your search
@@ -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",))
|
||||
|
||||
Reference in new issue
Block a user