优化仿真求解性能并修复流量闭合问题(初版)
This commit is contained in:
1 parent
57b459bc72
commit
5332a788f3
55 files changed
+8973
-549
No files matched your search
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user