优化仿真求解性能并修复流量闭合问题(初版)

This commit is contained in:
ljz committed 2026-08-16 17:46:05 +08:00
1 parent 57b459bc72
commit 5332a788f3
55 files changed
+8973 -549

No files matched your search

+175
View File
@@ -12,6 +12,36 @@ from app.simulation.solvers.solver import (
)
def _counting_fixed_step_solver(
step_size: float,
dense_output_times: list[float],
):
import numpy as np
class FixedStepSolver:
def __init__(self, _fun, t0, y0, t_bound, **_kwargs):
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.t = min(self.t + step_size, self.t_bound)
self.y = np.asarray([self.t], dtype=float)
if self.t >= self.t_bound:
self.status = "finished"
return None
def dense_output(self):
dense_output_times.append(self.t)
return lambda time: np.asarray([float(time)], dtype=float)
return FixedStepSolver
class IntegrateOdeTests(unittest.TestCase):
def test_generic_solver_keeps_canonical_default_tolerance(self) -> None:
self.assertEqual(SolveIVPConfig().atol, 1.0e-8)
@@ -159,6 +189,151 @@ class IntegrateOdeTests(unittest.TestCase):
self.assertEqual(result.status, "cancelled")
self.assertEqual(result.t, [0.0])
def test_stepwise_solvers_build_dense_output_only_when_crossing_a_sample(
self,
) -> None:
import scipy.integrate
for method in ("BDF", "Radau", "RK45"):
with self.subTest(method=method):
dense_output_times: list[float] = []
fixed_step_solver = _counting_fixed_step_solver(
0.2,
dense_output_times,
)
with patch.object(scipy.integrate, method, fixed_step_solver):
result = integrate_ode(
rhs=lambda _time, _state: [1.0],
initial_state=[0.0],
config=SolveIVPConfig(
t_start=0.0,
t_stop=1.0,
method=method,
max_step=1.0,
),
t_eval=[0.0, 0.75, 1.0],
cancel_check=lambda: False,
)
self.assertTrue(result.success, result.message)
self.assertEqual(result.t, [0.0, 0.75, 1.0])
self.assertEqual(result.y, [[0.0, 0.75, 1.0]])
self.assertEqual(dense_output_times, [0.8, 1.0])
self.assertEqual(
result.solver_segments[0].accepted_step_count,
5,
)
def test_stepwise_solvers_keep_dense_output_for_state_event_detection(
self,
) -> None:
import scipy.integrate
for method in ("BDF", "Radau", "RK45"):
with self.subTest(method=method):
dense_output_times: list[float] = []
inspected_steps = 0
fixed_step_solver = _counting_fixed_step_solver(
0.25,
dense_output_times,
)
def inspect_state_event(
previous_time,
_previous_state,
current_time,
_current_state,
dense_state,
):
nonlocal inspected_steps
inspected_steps += 1
midpoint = 0.5 * (previous_time + current_time)
self.assertAlmostEqual(dense_state(midpoint)[0], midpoint)
return None
with patch.object(scipy.integrate, method, fixed_step_solver):
result = integrate_ode(
rhs=lambda _time, _state: [1.0],
initial_state=[0.0],
config=SolveIVPConfig(
t_start=0.0,
t_stop=1.0,
method=method,
max_step=1.0,
),
t_eval=[0.0, 1.0],
state_transition_handler=inspect_state_event,
)
self.assertTrue(result.success, result.message)
self.assertEqual(len(dense_output_times), 4)
self.assertEqual(inspected_steps, 4)
def test_stepwise_dense_output_preserves_adjacent_float_samples(self) -> None:
import scipy.integrate
dense_output_times: list[float] = []
adjacent_time = math.nextafter(0.5, math.inf)
half_interval_solver = _counting_fixed_step_solver(
0.5,
dense_output_times,
)
with patch.object(scipy.integrate, "RK45", half_interval_solver):
result = integrate_ode(
rhs=lambda _time, _state: [1.0],
initial_state=[0.0],
config=SolveIVPConfig(
t_start=0.0,
t_stop=1.0,
method="RK45",
max_step=1.0,
),
t_eval=[0.0, 0.5, adjacent_time, 1.0],
cancel_check=lambda: False,
)
self.assertTrue(result.success, result.message)
self.assertEqual(result.t, [0.0, 0.5, adjacent_time, 1.0])
self.assertEqual(result.y[0], result.t)
self.assertEqual(dense_output_times, [0.5, 1.0])
def test_dense_output_pruning_preserves_cancelled_partial_samples(self) -> None:
import scipy.integrate
cancellation_requested = False
dense_output_times: list[float] = []
fixed_step_solver = _counting_fixed_step_solver(
0.2,
dense_output_times,
)
def request_cancel(time: float) -> None:
nonlocal cancellation_requested
cancellation_requested = time >= 0.4
with patch.object(scipy.integrate, "BDF", fixed_step_solver):
result = integrate_ode(
rhs=lambda _time, _state: [1.0],
initial_state=[0.0],
config=SolveIVPConfig(
t_start=0.0,
t_stop=1.0,
method="BDF",
max_step=1.0,
),
t_eval=[0.0, 0.3, 0.8, 1.0],
cancel_check=lambda: cancellation_requested,
accepted_step_callback=request_cancel,
)
self.assertFalse(result.success)
self.assertEqual(result.status, "cancelled")
self.assertEqual(result.t, [0.0, 0.3, 0.4])
self.assertEqual(result.y, [[0.0, 0.3, 0.4]])
self.assertEqual(dense_output_times, [0.4])
def test_segmented_bdf_uses_left_limit_and_restarts_at_event(self) -> None:
import scipy.integrate