初版:实现 AMESim 机械因果化与事件求解
初步支持 MECMAS21 刚性质量状态归并、端止事件、恢复系数,以及 LSTP 接触和压力流量显式因果化。 已知问题:显式传播仍会重复扫描全网方程,长时刚性仿真性能待优化;自适应积分器遇到越出物理域的试探状态时,尚未实现恢复并缩步重试。
This commit is contained in:
1 parent
de265cdde6
commit
971e8f2336
9 files changed
+2808
-169
No files matched your search
+245
-1
@@ -4,7 +4,11 @@ import types
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.simulation.solvers.solver import SolveIVPConfig, integrate_ode
|
||||
from app.simulation.solvers.solver import (
|
||||
SolveIVPConfig,
|
||||
StateTransition,
|
||||
integrate_ode,
|
||||
)
|
||||
|
||||
|
||||
class IntegrateOdeTests(unittest.TestCase):
|
||||
@@ -197,6 +201,246 @@ class IntegrateOdeTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(result.t, sorted(set(result.t)))
|
||||
|
||||
def test_state_transition_resets_at_root_and_discards_step_overshoot(self) -> None:
|
||||
event_time = 0.35
|
||||
event_enabled = True
|
||||
|
||||
def transition_handler(
|
||||
previous_time,
|
||||
previous_state,
|
||||
current_time,
|
||||
current_state,
|
||||
dense_state,
|
||||
):
|
||||
nonlocal event_enabled
|
||||
if (
|
||||
not event_enabled
|
||||
or previous_state[0] >= event_time
|
||||
or current_state[0] < event_time
|
||||
):
|
||||
return None
|
||||
lower = previous_time
|
||||
upper = current_time
|
||||
for _iteration in range(60):
|
||||
middle = 0.5 * (lower + upper)
|
||||
if dense_state(middle)[0] >= event_time:
|
||||
upper = middle
|
||||
else:
|
||||
lower = middle
|
||||
event_enabled = False
|
||||
return StateTransition(time=upper, state=[0.0])
|
||||
|
||||
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=0.5,
|
||||
),
|
||||
t_eval=[0.0, event_time, 0.4, 1.0],
|
||||
state_transition_handler=transition_handler,
|
||||
)
|
||||
|
||||
self.assertTrue(result.success, result.message)
|
||||
self.assertEqual(result.t, [0.0, event_time, 0.4, 1.0])
|
||||
self.assertAlmostEqual(result.y[0][1], 0.0, places=12)
|
||||
self.assertAlmostEqual(result.y[0][2], 0.05, places=8)
|
||||
self.assertAlmostEqual(result.y[0][-1], 0.65, places=8)
|
||||
|
||||
def test_state_transitions_chain_at_same_time_until_state_repeats(self) -> None:
|
||||
event_time = 0.25
|
||||
stage = 0
|
||||
returned_reset_states: list[float] = []
|
||||
|
||||
def transition_handler(
|
||||
previous_time,
|
||||
_previous_state,
|
||||
current_time,
|
||||
_current_state,
|
||||
_dense_state,
|
||||
):
|
||||
nonlocal stage
|
||||
if stage == 0 and previous_time <= event_time <= current_time:
|
||||
stage = 1
|
||||
returned_reset_states.append(10.0)
|
||||
return StateTransition(time=event_time, state=[10.0])
|
||||
if stage == 1 and previous_time == event_time:
|
||||
stage = 2
|
||||
returned_reset_states.append(20.0)
|
||||
return StateTransition(time=event_time, state=[20.0])
|
||||
if stage == 2 and previous_time == event_time:
|
||||
returned_reset_states.append(20.0)
|
||||
return StateTransition(time=event_time, state=[20.0])
|
||||
return None
|
||||
|
||||
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=0.4,
|
||||
),
|
||||
t_eval=[0.0, event_time, 1.0],
|
||||
state_transition_handler=transition_handler,
|
||||
)
|
||||
|
||||
self.assertTrue(result.success, result.message)
|
||||
self.assertEqual(returned_reset_states, [10.0, 20.0, 20.0])
|
||||
self.assertEqual(result.t, [0.0, event_time, 1.0])
|
||||
self.assertEqual(result.y[0][1], 20.0)
|
||||
self.assertAlmostEqual(result.y[0][-1], 20.75, places=8)
|
||||
|
||||
def test_state_transition_chain_has_a_finite_guard(self) -> None:
|
||||
event_time = 0.25
|
||||
reset_count = 0
|
||||
|
||||
def transition_handler(
|
||||
previous_time,
|
||||
_previous_state,
|
||||
current_time,
|
||||
_current_state,
|
||||
_dense_state,
|
||||
):
|
||||
nonlocal reset_count
|
||||
if previous_time <= event_time <= current_time:
|
||||
reset_count += 1
|
||||
return StateTransition(
|
||||
time=event_time,
|
||||
state=[float(reset_count)],
|
||||
)
|
||||
return None
|
||||
|
||||
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=0.4,
|
||||
),
|
||||
state_transition_handler=transition_handler,
|
||||
)
|
||||
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.status, "failed")
|
||||
self.assertIn("64 chained resets", result.message)
|
||||
|
||||
def test_stepwise_solver_preserves_adjacent_float_samples(self) -> None:
|
||||
adjacent_time = math.nextafter(0.5, math.inf)
|
||||
|
||||
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=0.4,
|
||||
),
|
||||
t_eval=[0.0, 0.5, adjacent_time, 1.0],
|
||||
state_transition_handler=lambda *_args: None,
|
||||
)
|
||||
|
||||
self.assertTrue(result.success, result.message)
|
||||
self.assertEqual(result.t, [0.0, 0.5, adjacent_time, 1.0])
|
||||
|
||||
def test_state_transition_at_breakpoint_uses_exact_breakpoint_sample(self) -> None:
|
||||
event_time = 0.5
|
||||
integration_left_limit = math.nextafter(event_time, -math.inf)
|
||||
event_enabled = True
|
||||
|
||||
def transition_handler(
|
||||
previous_time,
|
||||
_previous_state,
|
||||
current_time,
|
||||
_current_state,
|
||||
_dense_state,
|
||||
):
|
||||
nonlocal event_enabled
|
||||
if (
|
||||
event_enabled
|
||||
and previous_time <= integration_left_limit <= current_time
|
||||
):
|
||||
event_enabled = False
|
||||
return StateTransition(time=event_time, state=[7.0])
|
||||
return None
|
||||
|
||||
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=0.2,
|
||||
),
|
||||
t_eval=[0.0, event_time, 1.0],
|
||||
breakpoints=[event_time],
|
||||
state_transition_handler=transition_handler,
|
||||
)
|
||||
|
||||
self.assertTrue(result.success, result.message)
|
||||
self.assertEqual(result.t, [0.0, event_time, 1.0])
|
||||
self.assertEqual(result.y[0][1], 7.0)
|
||||
self.assertAlmostEqual(result.y[0][-1], 7.5, places=8)
|
||||
|
||||
def test_cancellation_after_state_transition_reports_partial_progress(self) -> None:
|
||||
event_time = 0.25
|
||||
cancellation_requested = False
|
||||
event_enabled = True
|
||||
|
||||
def transition_handler(
|
||||
previous_time,
|
||||
_previous_state,
|
||||
current_time,
|
||||
_current_state,
|
||||
_dense_state,
|
||||
):
|
||||
nonlocal cancellation_requested, event_enabled
|
||||
if event_enabled and previous_time <= event_time <= current_time:
|
||||
event_enabled = False
|
||||
cancellation_requested = True
|
||||
return StateTransition(time=event_time, state=[0.0])
|
||||
return None
|
||||
|
||||
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=0.4,
|
||||
),
|
||||
cancel_check=lambda: cancellation_requested,
|
||||
state_transition_handler=transition_handler,
|
||||
)
|
||||
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.status, "cancelled")
|
||||
self.assertEqual(
|
||||
result.message,
|
||||
"Simulation was stopped before reaching the requested end time.",
|
||||
)
|
||||
self.assertEqual(result.t[-1], event_time)
|
||||
|
||||
def test_state_transition_handler_rejects_reverse_integration(self) -> None:
|
||||
with self.assertRaisesRegex(
|
||||
ValueError,
|
||||
"does not support reverse integration",
|
||||
):
|
||||
integrate_ode(
|
||||
rhs=lambda _time, _state: [1.0],
|
||||
initial_state=[0.0],
|
||||
config=SolveIVPConfig(t_start=1.0, t_stop=0.0),
|
||||
state_transition_handler=lambda *_args: None,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user