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

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

+334
View File
@@ -0,0 +1,334 @@
from pathlib import Path
import os
import unittest
from unittest.mock import patch
import numpy as np
from app.main import compile_system_xml_network
from app.simulation.solvers.jacobian import ExactColumnsUnavailable
from app.simulation.solvers.mechanical import MechanicalConstraintGroup
from app.simulation.solvers.solver import ODESolution, SolveIVPConfig
from app.simulation.solvers.tangent import compile_three_piston_tangent_provider
from app.simulation.systems.generic import (
ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE,
GenericFluidSystem,
_requested_ode_jacobian_mode,
)
from app.system_xml import validate_system_xml_document
TARGET_XML = Path("tests/data/test_mql-full-branches-01-04.xml")
TARGET_MASS_NAMES = (
"mass_friction_endstops_10",
"mass_friction_endstops_11",
"mass_friction_endstops_12",
)
class ThreePistonTangentCompilerTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
report = validate_system_xml_document(TARGET_XML.read_bytes())
assert report.valid
with patch.dict(os.environ, {"SIMULATION_CAUSAL_FAST_PATH": "1"}):
cls.system = GenericFluidSystem(
compile_system_xml_network(report.document)
)
cls.initial_state = np.asarray(
cls.system.consistent_initial_state_vector(0.0),
dtype=float,
)
def setUp(self) -> None:
self.system.mechanical_state_reducer.reset_constraint_modes()
self.system.apply_state_vector(self.initial_state.tolist())
def _closed_primal(
self,
state: np.ndarray,
) -> tuple[dict[str, dict[str, float]], np.ndarray]:
self.system.apply_state_vector(state.tolist())
connected_h = self.system._close_current_state(0.0)
derivative = np.asarray(
self.system._state_derivatives(connected_h),
dtype=float,
)
return connected_h, derivative
def test_target_layout_is_resolved_from_owner_and_slot(self) -> None:
compilation = compile_three_piston_tangent_provider(self.system)
self.assertTrue(compilation.eligible, compilation.reason)
owner_slots: dict[str, tuple[int, int]] = {}
cursor = 0
for entry in self.system.mechanical_state_reducer.state_entries:
if isinstance(entry, MechanicalConstraintGroup):
owner_slots[entry.representative.name] = (cursor, cursor + 1)
cursor += 2
else:
cursor += entry.state_size
resolved = tuple(
sorted(
index
for name in TARGET_MASS_NAMES
for index in owner_slots[name]
)
)
self.assertEqual(compilation.columns, resolved)
# This is a fixture drift guard, not the provider's lookup mechanism.
self.assertEqual(resolved, (20, 21, 38, 39, 54, 55))
self.assertEqual(compilation.reached_assignment_count, 34)
def test_stale_primal_context_requests_typed_fallback(self) -> None:
compilation = compile_three_piston_tangent_provider(self.system)
self.assertTrue(compilation.eligible, compilation.reason)
assert compilation.provider is not None
with self.assertRaises(ExactColumnsUnavailable) as captured:
compilation.provider(
0.0,
self.initial_state.copy(),
compilation.columns,
)
self.assertEqual(captured.exception.reason, "stalePrimalContext")
def test_initial_contact_boundary_requests_numerical_columns(self) -> None:
compilation = compile_three_piston_tangent_provider(self.system)
self.assertTrue(compilation.eligible, compilation.reason)
assert compilation.provider is not None
connected_h, _derivative = self._closed_primal(
self.initial_state.copy()
)
compilation.provider.request_primal_capture()
compilation.provider.record_primal(
0.0,
self.initial_state,
connected_h,
)
with self.assertRaises(ExactColumnsUnavailable) as captured:
compilation.provider(
0.0,
self.initial_state.copy(),
compilation.columns,
)
self.assertEqual(
captured.exception.reason,
"contactMode:contact_mode_boundary",
)
def test_smooth_six_columns_match_full_rhs_centered_difference(self) -> None:
compilation = compile_three_piston_tangent_provider(self.system)
self.assertTrue(compilation.eligible, compilation.reason)
provider = compilation.provider
assert provider is not None
state = self.initial_state.copy()
# Move all three contacts away from gap==0 and all three PNL0001 laws
# away from equal-pressure/zero-flow. The selected columns themselves
# remain the six mechanical [v, x] seeds.
for branch in provider.branches:
chamber_offset, chamber_size = provider.state_offsets[
branch.chamber.name
]
self.assertEqual(chamber_size, 2)
state[chamber_offset] *= 1.01
state[branch.position_index] -= 1.0e-3
connected_h, _base = self._closed_primal(state)
provider.request_primal_capture()
provider.record_primal(0.0, state, connected_h)
exact = provider(0.0, state.copy(), compilation.columns)
numerical = np.empty_like(exact)
for local_column, state_index in enumerate(compilation.columns):
step = 1.0e-7 * max(abs(state[state_index]), 1.0)
lower = state.copy()
upper = state.copy()
lower[state_index] -= step
upper[state_index] += step
lower_rhs = np.asarray(
self.system.rhs(0.0, lower.tolist()),
dtype=float,
)
upper_rhs = np.asarray(
self.system.rhs(0.0, upper.tolist()),
dtype=float,
)
numerical[:, local_column] = (
upper_rhs - lower_rhs
) / (2.0 * step)
np.testing.assert_allclose(
exact,
numerical,
rtol=2.0e-6,
atol=1.0e-5,
)
for downstream_mass in (
"mass_friction_endstops_18",
"mass_friction_endstops_19",
):
offset, size = provider.state_offsets[downstream_mass]
self.assertEqual(size, 2)
# Causal force reach includes both remote masses, but this fixture
# holds their discrete constraints fixed, so final ODE rows are zero.
np.testing.assert_allclose(
exact[offset : offset + size, :],
numerical[offset : offset + size, :],
rtol=0.0,
atol=1.0e-12,
)
np.testing.assert_allclose(
exact[offset : offset + size, :],
0.0,
rtol=0.0,
atol=1.0e-12,
)
def test_disabled_causal_path_is_not_eligible(self) -> None:
solver = self.system.pressure_flow_solver
original = solver._causal_fast_path_environment_enabled
try:
solver._causal_fast_path_environment_enabled = False
compilation = compile_three_piston_tangent_provider(self.system)
finally:
solver._causal_fast_path_environment_enabled = original
self.assertFalse(compilation.eligible)
self.assertEqual(compilation.reason, "causalFastPathDisabled")
def test_semi_analytic_mode_is_explicit_opt_in(self) -> None:
with patch.dict(
os.environ,
{ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE: "semi-analytic"},
):
self.assertEqual(
_requested_ode_jacobian_mode(),
"semi-analytic",
)
with patch.dict(os.environ, {}, clear=True):
self.assertEqual(_requested_ode_jacobian_mode(), "scipy")
def test_generic_simulation_wires_exact_columns_and_cleans_provider(
self,
) -> None:
report = validate_system_xml_document(TARGET_XML.read_bytes())
assert report.valid and report.document is not None
with patch.dict(os.environ, {"SIMULATION_CAUSAL_FAST_PATH": "1"}):
system = GenericFluidSystem(
compile_system_xml_network(report.document)
)
captured: dict[str, object] = {}
def fake_integrate_ode(**options) -> ODESolution:
captured.update(options)
captured["activeProvider"] = system._ode_tangent_provider
initial = [float(value) for value in options["initial_state"]]
stop = float(options["config"].t_stop)
return ODESolution(
t=[0.0, stop],
y=[[value, value] for value in initial],
success=True,
message="test",
)
with patch.dict(
os.environ,
{ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE: "semi-analytic"},
), patch(
"app.simulation.systems.generic.integrate_ode",
side_effect=fake_integrate_ode,
):
result = system.simulate(
SolveIVPConfig(t_stop=1.0e-3, method="BDF"),
sample_step=1.0e-3,
)
jacobian = captured["jac"]
self.assertIsNotNone(jacobian)
self.assertIsNotNone(captured["activeProvider"])
self.assertIsNone(system._ode_tangent_provider)
runtime = result.diagnostics["integration"]["jacobian"]
self.assertEqual(runtime["mode"], "semiAnalyticExactColumns")
self.assertEqual(runtime["effectiveMode"], "notEvaluated")
self.assertEqual(runtime["originalColorGroupCount"], 31)
self.assertEqual(runtime["remainingColorGroupCount"], 25)
self.assertEqual(runtime["exactColumnCount"], 6)
def test_generic_simulation_ineligible_path_uses_native_scipy(self) -> None:
report = validate_system_xml_document(TARGET_XML.read_bytes())
assert report.valid and report.document is not None
with patch.dict(os.environ, {"SIMULATION_CAUSAL_FAST_PATH": "1"}):
system = GenericFluidSystem(
compile_system_xml_network(report.document)
)
system.pressure_flow_solver._causal_fast_path_environment_enabled = False
captured: dict[str, object] = {}
def fake_integrate_ode(**options) -> ODESolution:
captured.update(options)
captured["activeProvider"] = system._ode_tangent_provider
initial = [float(value) for value in options["initial_state"]]
stop = float(options["config"].t_stop)
return ODESolution(
t=[0.0, stop],
y=[[value, value] for value in initial],
success=True,
message="test",
)
with patch.dict(
os.environ,
{ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE: "semi-analytic"},
), patch(
"app.simulation.systems.generic.integrate_ode",
side_effect=fake_integrate_ode,
):
result = system.simulate(
SolveIVPConfig(t_stop=1.0e-3, method="BDF"),
sample_step=1.0e-3,
)
self.assertIsNone(captured["jac"])
self.assertIsNone(captured["activeProvider"])
self.assertIsNone(system._ode_tangent_provider)
runtime = result.diagnostics["integration"]["jacobian"]
self.assertEqual(runtime["mode"], "scipySparseFiniteDifference")
self.assertEqual(
runtime["fallbackReason"],
"semiAnalytic:causalFastPathDisabled",
)
def test_generic_simulation_cleans_provider_after_integration_error(
self,
) -> None:
report = validate_system_xml_document(TARGET_XML.read_bytes())
assert report.valid and report.document is not None
with patch.dict(os.environ, {"SIMULATION_CAUSAL_FAST_PATH": "1"}):
system = GenericFluidSystem(
compile_system_xml_network(report.document)
)
def fail_integration(**_options):
self.assertIsNotNone(system._ode_tangent_provider)
raise RuntimeError("injected integration failure")
with patch.dict(
os.environ,
{ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE: "semi-analytic"},
), patch(
"app.simulation.systems.generic.integrate_ode",
side_effect=fail_integration,
), self.assertRaisesRegex(RuntimeError, "injected integration failure"):
system.simulate(
SolveIVPConfig(t_stop=1.0e-3, method="BDF"),
sample_step=1.0e-3,
)
self.assertIsNone(system._ode_tangent_provider)
if __name__ == "__main__":
unittest.main()