完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本
This commit is contained in:
1 parent
6bb0591d32
commit
16a7eb2d6c
48 files changed
+8172
-217
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user