完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本

This commit is contained in:
lujingze committed 2026-08-17 07:33:31 +00:00
1 parent 6bb0591d32
commit 16a7eb2d6c
48 files changed
+8172 -217

No files matched your search

+173
View File
@@ -6,6 +6,7 @@ from unittest.mock import patch
from app.simulation.core.errors import RecoverableTrialStateError
from app.simulation.solvers.solver import (
IntegrationCancelled,
SolveIVPConfig,
StateTransition,
integrate_ode,
@@ -46,6 +47,55 @@ class IntegrateOdeTests(unittest.TestCase):
def test_generic_solver_keeps_canonical_default_tolerance(self) -> None:
self.assertEqual(SolveIVPConfig().atol, 1.0e-8)
def test_stepwise_jacobian_cancellation_returns_cancelled_solution(self) -> None:
class CancellingJacobian:
def start_segment(self) -> None:
pass
def observe(self, *_args) -> None:
pass
def __call__(self, _time, _state):
raise IntegrationCancelled
result = integrate_ode(
rhs=lambda _time, state: [-float(state[0])],
initial_state=[1.0],
config=SolveIVPConfig(t_stop=1.0, method="BDF"),
cancel_check=lambda: False,
jac=CancellingJacobian(),
)
self.assertFalse(result.success)
self.assertEqual(result.status, "cancelled")
self.assertEqual(result.t, [0.0])
def test_stepwise_explicit_solver_ignores_callable_jacobian(self) -> None:
class UnexpectedJacobian:
call_count = 0
def start_segment(self) -> None:
self.call_count += 1
def observe(self, *_args) -> None:
self.call_count += 1
def __call__(self, _time, _state):
self.call_count += 1
raise AssertionError("Explicit solver evaluated Jacobian")
jacobian = UnexpectedJacobian()
result = integrate_ode(
rhs=lambda _time, _state: [1.0],
initial_state=[0.0],
config=SolveIVPConfig(t_stop=0.01, method="RK45"),
state_transition_handler=lambda *_args: None,
jac=jacobian,
)
self.assertTrue(result.success, result.message)
self.assertEqual(jacobian.call_count, 0)
def test_scipy_solver_receives_step_size_controls(self) -> None:
calls: list[dict[str, object]] = []
@@ -77,6 +127,57 @@ class IntegrateOdeTests(unittest.TestCase):
self.assertEqual(calls[0]["max_step"], 1.0e-4)
self.assertEqual(calls[0]["first_step"], 1.0e-8)
def test_direct_implicit_solver_receives_callable_jacobian(self) -> None:
calls: list[dict[str, object]] = []
observed: list[tuple[float, list[float], list[float]]] = []
class RecordingJacobian:
def __init__(self) -> None:
self.segment_count = 0
def start_segment(self) -> None:
self.segment_count += 1
def observe(self, time, state, derivative) -> None:
observed.append(
(
float(time),
[float(value) for value in state],
[float(value) for value in derivative],
)
)
def __call__(self, _time, _state):
return [[1.0]]
def fake_solve_ivp(**kwargs):
calls.append(kwargs)
kwargs["fun"](0.0, [2.0])
return object()
scipy_module = types.ModuleType("scipy")
integrate_module = types.ModuleType("scipy.integrate")
integrate_module.solve_ivp = fake_solve_ivp
scipy_module.integrate = integrate_module
jacobian = RecordingJacobian()
with patch.dict(
sys.modules,
{"scipy": scipy_module, "scipy.integrate": integrate_module},
):
integrate_ode(
rhs=lambda _time, state: [2.0 * state[0]],
initial_state=[1.0],
config=SolveIVPConfig(t_start=0.0, t_stop=1.0, method="BDF"),
jac_sparsity=[[True]],
jac=jacobian,
)
self.assertIs(calls[0]["jac"], jacobian)
self.assertNotIn("jac_sparsity", calls[0])
self.assertEqual(jacobian.segment_count, 1)
self.assertEqual(observed, [(0.0, [2.0], [4.0])])
def test_scipy_solver_omits_unset_first_step(self) -> None:
calls: list[dict[str, object]] = []
@@ -416,6 +517,78 @@ class IntegrateOdeTests(unittest.TestCase):
)
)
def test_stepwise_implicit_solver_restarts_callable_jacobian(self) -> None:
import numpy as np
import scipy.integrate
for method in ("BDF", "Radau"):
with self.subTest(method=method):
received: list[dict[str, object]] = []
class RecordingJacobian:
def __init__(self) -> None:
self.segment_count = 0
self.observation_count = 0
def start_segment(self) -> None:
self.segment_count += 1
def observe(self, _time, _state, _derivative) -> None:
self.observation_count += 1
def __call__(self, _time, _state):
return [[0.0]]
class RecordingSolver:
def __init__(self, fun, t0, y0, t_bound, **kwargs):
received.append(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(self.t, self.y)
self.t = self.t_bound
self.status = "finished"
return None
def dense_output(self):
state = self.y.copy()
return lambda _time: state.copy()
jacobian = RecordingJacobian()
with patch.object(scipy.integrate, method, RecordingSolver):
result = integrate_ode(
rhs=lambda _time, _state: [0.0],
initial_state=[1.0],
config=SolveIVPConfig(
t_start=0.0,
t_stop=1.0,
method=method,
max_step=1.0,
),
t_eval=[0.0, 0.4, 1.0],
breakpoints=[0.4],
jac_sparsity=[[True]],
jac=jacobian,
)
self.assertTrue(result.success, result.message)
self.assertEqual(jacobian.segment_count, 2)
self.assertEqual(jacobian.observation_count, 2)
self.assertEqual(len(received), 2)
self.assertTrue(
all(options.get("jac") is jacobian for options in received)
)
self.assertTrue(
all("jac_sparsity" not in options for options in received)
)
def test_segmented_solver_reports_implicit_work_by_event_segment(self) -> None:
import numpy as np
import scipy.integrate