整合求解器活动监控与步长回归证据

同步远端 PNL0003 诊断和大采样网格能力,语义合并活动感知的 60 秒真停滞判定与旧后端 15 分钟兼容兜底。

纳管热路径优化、15 单元运行证据、浏览器与 API 报告,并补充北京时间更新日志和遗留问题。
This commit is contained in:
lujingze committed 2026-08-19 16:24:31 +00:00
1 parent c19cf77aee
commit e18399c022
46 files changed
+181589 -170

No files matched your search

+83 -5
View File
@@ -26,7 +26,10 @@ from app.main import (
)
from app.simulation.components.experimental.flow.resistive_pipe import ResistivePipe
from app.simulation.registry import get_component_model_spec
from app.simulation.solvers.solver import SolveIVPConfig
from app.simulation.solvers.solver import (
SolveIVPConfig,
SolverActivityTracker,
)
from app.simulation.systems.generic import (
GenericFluidSystem,
SimulationPreparationError,
@@ -259,7 +262,9 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
def test_generic_chain_simulation_conserves_mass_and_moves_pressures(self) -> None:
network = compile_reactflow_network(chain_project())
progress: list[tuple[float, str]] = []
result = GenericFluidSystem(network).simulate(
activity_tracker = SolverActivityTracker()
system = GenericFluidSystem(network)
result = system.simulate(
SolveIVPConfig(
t_start=0.0,
t_stop=0.01,
@@ -268,6 +273,7 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
),
sample_step=0.005,
progress_callback=lambda value, phase: progress.append((value, phase)),
activity_tracker=activity_tracker,
)
self.assertTrue(result.success)
@@ -281,6 +287,23 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
)
self.assertIn("integrating", {phase for _, phase in progress})
self.assertIn("postprocessing", {phase for _, phase in progress})
activity = activity_tracker.snapshot()
self.assertEqual(activity.activity_kind, "complete")
self.assertGreater(activity.rhs_call_count, 0)
self.assertGreater(activity.accepted_step_sequence, 0)
self.assertEqual(activity.accepted_time, 0.01)
self.assertGreater(activity.thermofluid_closure_count, 0)
self.assertEqual(
result.diagnostics["activity"],
activity.as_dict(),
)
self.assertIsNone(system._activity_tracker)
self.assertEqual(
result.diagnostics["integration"][
"mechanicalAbsoluteTolerance"
]["mode"],
"legacy",
)
self.assertLess(result.series["cylinder_1.p"][-1], 500000.0)
self.assertGreater(result.series["tank_1.p"][-1], 100000.0)
total_mass = [
@@ -315,6 +338,33 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
pressure_flow["optimizerEvaluationCount"],
)
def test_activity_tracker_scope_is_restored_after_simulation_error(self) -> None:
system = GenericFluidSystem(compile_reactflow_network(chain_project()))
previous_tracker = SolverActivityTracker()
active_tracker = SolverActivityTracker()
system._activity_tracker = previous_tracker
with (
patch.object(
system,
"_simulate",
side_effect=RuntimeError("synthetic simulation failure"),
),
self.assertRaisesRegex(RuntimeError, "synthetic simulation failure"),
):
system.simulate(
SolveIVPConfig(
t_start=0.0,
t_stop=0.01,
method="BDF",
max_step=0.001,
),
sample_step=0.005,
activity_tracker=active_tracker,
)
self.assertIs(system._activity_tracker, previous_tracker)
def test_cancelled_simulation_returns_accepted_partial_samples(self) -> None:
cancel_event = threading.Event()
@@ -569,10 +619,24 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
self.assertEqual(snapshot["result"]["status"], "stopped")
def test_streaming_endpoint_keeps_quiet_solver_connection_alive(self) -> None:
def delayed_simulation(_xml_bytes, progress_callback, _cancel_check=None):
def delayed_simulation(
_xml_bytes,
progress_callback,
_cancel_check=None,
activity_tracker=None,
):
progress_callback(49, "integrating", "正在进行时间积分与压力流量求解")
time.sleep(0.03)
return {"success": True}
self.assertIsNotNone(activity_tracker)
activity_tracker.start_integration(0.0)
for trial_time in (0.0487, 0.0488, 0.0489, 0.0490):
activity_tracker.record_rhs(trial_time)
time.sleep(0.008)
return {
"success": True,
"status": "completed",
"simulatedUntil": 0.2,
"requestedStopTime": 0.2,
}
with (
patch("app.main.SIMULATION_STREAM_HEARTBEAT_SECONDS", 0.005),
@@ -590,7 +654,21 @@ class GenericSystemXmlSimulationTests(unittest.TestCase):
self.assertGreaterEqual(len(heartbeats), 1)
self.assertTrue(all(event["progress"] == 49 for event in heartbeats))
self.assertTrue(all(event["phase"] == "integrating" for event in heartbeats))
self.assertTrue(
all(event["acceptedStepSequence"] == 0 for event in heartbeats)
)
self.assertTrue(all(event["acceptedTime"] == 0.0 for event in heartbeats))
self.assertGreater(
len({event["activitySequence"] for event in heartbeats}),
1,
)
self.assertTrue(
all(event["activityKind"] == "rhs" for event in heartbeats)
)
self.assertEqual(events[-1]["event"], "result")
self.assertEqual(events[-1]["activityKind"], "complete")
self.assertEqual(events[-1]["acceptedStepSequence"], 0)
self.assertEqual(events[-1]["rhsCallCount"], 4)
def test_streaming_endpoint_returns_structured_validation_error(self) -> None:
events = [