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

同步远端 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

+160
View File
@@ -1,5 +1,6 @@
import math
import sys
import threading
import types
import unittest
from unittest.mock import patch
@@ -8,6 +9,7 @@ from app.simulation.core.errors import RecoverableTrialStateError
from app.simulation.solvers.solver import (
IntegrationCancelled,
SolveIVPConfig,
SolverActivityTracker,
StateTransition,
integrate_ode,
)
@@ -44,6 +46,164 @@ def _counting_fixed_step_solver(
class IntegrateOdeTests(unittest.TestCase):
def test_activity_tracking_preserves_the_exact_trajectory(self) -> None:
options = {
"rhs": lambda time, state: [
-0.5 * float(state[0]) + 0.1 * float(time)
],
"initial_state": [1.0],
"config": SolveIVPConfig(
t_start=0.0,
t_stop=0.1,
method="BDF",
max_step=0.01,
),
"t_eval": [0.0, 0.025, 0.05, 0.075, 0.1],
"cancel_check": lambda: False,
}
baseline = integrate_ode(**options)
tracked = integrate_ode(
**options,
activity_tracker=SolverActivityTracker(),
)
self.assertTrue(baseline.success, baseline.message)
self.assertTrue(tracked.success, tracked.message)
self.assertEqual(tracked.t, baseline.t)
self.assertEqual(tracked.y, baseline.y)
self.assertEqual(tracked.solver_segments, baseline.solver_segments)
def test_activity_advances_while_accepted_time_is_unchanged(self) -> None:
import numpy as np
rhs_started = threading.Event()
release_rhs = threading.Event()
tracker = SolverActivityTracker()
results = []
errors: list[BaseException] = []
class BlockingSolver:
def __init__(self, fun, t0, y0, t_bound, **_kwargs):
self.fun = fun
self.t = float(t0)
self.y = np.asarray(y0, dtype=float)
self.t_bound = float(t_bound)
self.status = "running"
self.nfev = 0
self.njev = 0
self.nlu = 0
def step(self):
self.fun(0.25, self.y)
self.nfev += 1
self.t = self.t_bound
self.y = np.asarray([self.t], dtype=float)
self.status = "finished"
return None
def rhs(_time, _state):
rhs_started.set()
if not release_rhs.wait(timeout=2.0):
raise TimeoutError("test did not release the blocking RHS")
return [1.0]
def run_integration() -> None:
try:
results.append(
integrate_ode(
rhs=rhs,
initial_state=[0.0],
config=SolveIVPConfig(t_stop=1.0, method="BDF"),
cancel_check=lambda: False,
activity_tracker=tracker,
)
)
except BaseException as exc: # pragma: no cover - test handoff
errors.append(exc)
with patch("scipy.integrate.BDF", BlockingSolver):
worker = threading.Thread(target=run_integration)
worker.start()
self.assertTrue(rhs_started.wait(timeout=2.0))
during_rhs = tracker.snapshot()
self.assertEqual(during_rhs.activity_kind, "rhs")
self.assertGreater(during_rhs.activity_sequence, 0)
self.assertEqual(during_rhs.current_trial_time, 0.25)
self.assertEqual(during_rhs.rhs_call_count, 1)
self.assertEqual(during_rhs.accepted_step_sequence, 0)
self.assertEqual(during_rhs.accepted_time, 0.0)
release_rhs.set()
worker.join(timeout=2.0)
self.assertFalse(worker.is_alive())
self.assertEqual(errors, [])
self.assertTrue(results[0].success, results[0].message)
completed = tracker.snapshot()
self.assertEqual(completed.accepted_step_sequence, 1)
self.assertEqual(completed.accepted_time, 1.0)
self.assertEqual(completed.solver_step_sequence, 1)
def test_activity_distinguishes_blocking_jacobian_evaluation(self) -> None:
import numpy as np
jacobian_started = threading.Event()
release_jacobian = threading.Event()
tracker = SolverActivityTracker()
results = []
class BlockingSolver:
def __init__(self, _fun, t0, y0, t_bound, **options):
self.jac = options["jac"]
self.t = float(t0)
self.y = np.asarray(y0, dtype=float)
self.t_bound = float(t_bound)
self.status = "running"
self.nfev = 0
self.njev = 0
self.nlu = 0
def step(self):
self.jac(0.5, self.y)
self.njev += 1
self.t = self.t_bound
self.status = "finished"
return None
class BlockingJacobian:
def __call__(self, _time, _state):
jacobian_started.set()
if not release_jacobian.wait(timeout=2.0):
raise TimeoutError("test did not release the Jacobian")
return np.asarray([[0.0]])
def run_integration() -> None:
results.append(
integrate_ode(
rhs=lambda _time, _state: [0.0],
initial_state=[0.0],
config=SolveIVPConfig(t_stop=1.0, method="BDF"),
cancel_check=lambda: False,
jac=BlockingJacobian(),
activity_tracker=tracker,
)
)
with patch("scipy.integrate.BDF", BlockingSolver):
worker = threading.Thread(target=run_integration)
worker.start()
self.assertTrue(jacobian_started.wait(timeout=2.0))
during_jacobian = tracker.snapshot()
self.assertEqual(during_jacobian.activity_kind, "jacobian")
self.assertEqual(during_jacobian.jacobian_evaluation_count, 1)
self.assertEqual(during_jacobian.rhs_call_count, 0)
self.assertEqual(during_jacobian.accepted_step_sequence, 0)
release_jacobian.set()
worker.join(timeout=2.0)
self.assertFalse(worker.is_alive())
self.assertTrue(results[0].success, results[0].message)
def test_generic_solver_keeps_canonical_default_tolerance(self) -> None:
self.assertEqual(SolveIVPConfig().atol, 1.0e-8)