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

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

+316 -22
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, replace
from math import floor, isfinite
import os
from typing import Literal
from app.simulation.core.base import Component, DynamicComponent
@@ -12,6 +13,10 @@ from app.simulation.performance import performance_span, profile_phase
from app.simulation.property_cache import with_property_cache
from app.simulation.solvers.algebraic import PressureFlowSolver
from app.simulation.solvers.algebraic_blocks import StreamPressureBlockSolver
from app.simulation.solvers.jacobian import (
SparseJacobianCompatibilityError,
SparseSecantJacobian,
)
from app.simulation.solvers.mechanical import (
MechanicalConstraintGroup,
MechanicalStateReducer,
@@ -21,15 +26,58 @@ from app.simulation.solvers.pneumatic_storage import (
ideal_storage_group_is_reducible,
)
from app.simulation.solvers.pneumatic_volume import PneumaticVolumeResolver
from app.simulation.solvers.solver import ODESolution, SolveIVPConfig, integrate_ode
from app.simulation.solvers.solver import (
IntegrationCancelled,
ODESolution,
SolveIVPConfig,
integrate_ode,
)
from app.simulation.solvers.signal import SignalResolver
from app.simulation.solvers.stream import StreamResolver
from app.simulation.solvers.tangent import (
ThreePistonTangentCompilation,
ThreePistonTangentProvider,
compile_three_piston_tangent_provider,
)
from app.simulation.systems.network import Endpoint, SimulationNetwork
SimulationProgressCallback = Callable[[float, str], None]
SimulationCancellationCheck = Callable[[], bool]
SimulationRunStatus = Literal["completed", "cancelled", "failed"]
ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE = "SIMULATION_ODE_JACOBIAN_MODE"
def _requested_ode_jacobian_mode() -> Literal[
"optimized",
"hybrid",
"semi-analytic",
"scipy",
]:
value = os.getenv(
ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE,
"scipy",
).strip().lower()
if value in {"optimized", "colored"}:
return "optimized"
if value in {"hybrid", "secant"}:
return "hybrid"
if value in {"semi-analytic", "semi_analytic", "analytic"}:
return "semi-analytic"
if value in {
"scipy",
"native",
"finite-difference",
"0",
"false",
"no",
"off",
}:
return "scipy"
raise ValueError(
f"{ODE_JACOBIAN_MODE_ENVIRONMENT_VARIABLE} must be "
"'optimized', 'hybrid', 'semi-analytic', or 'scipy'."
)
@dataclass(frozen=True)
@@ -398,6 +446,7 @@ class GenericFluidSystem:
self.signal_propagation_count = 0
self.pneumatic_volume_propagation_count = 0
self._jacobian_sparsity = None
self._ode_tangent_provider: ThreePistonTangentProvider | None = None
def _request_causal_residual_audit(self) -> None:
"""Make topology or mode boundaries verify the next causal closure."""
@@ -874,6 +923,23 @@ class GenericFluidSystem:
"colorGroupCount": group_count,
}
def _exact_ode_jacobian_rows(self) -> dict[int, dict[int, float]]:
"""Return mode-independent kinematic rows safe to evaluate exactly."""
rows: dict[int, dict[int, float]] = {}
cursor = 0
for entry in self.mechanical_state_reducer.state_entries:
if isinstance(entry, MechanicalConstraintGroup):
# A discrete endstop can replace x' = v with x' = 0 for the
# active constrained mode. Keep those rows numerical; free
# mechanical groups always have d(x')/d(v) = 1.
if not entry.discrete_endstop_components:
rows[cursor + 1] = {cursor: 1.0}
cursor += 2
else:
cursor += entry.state_size
return rows
@profile_phase(
"simulation.closure",
minimum_mode="audit",
@@ -1065,7 +1131,11 @@ class GenericFluidSystem:
def rhs(self, _time: float, state_vector: list[float]) -> list[float]:
self.apply_state_vector(state_vector)
connected_h = self._close_current_state(_time)
return self._state_derivatives(connected_h)
derivatives = self._state_derivatives(connected_h)
provider = self._ode_tangent_provider
if provider is not None:
provider.record_primal(_time, state_vector, connected_h)
return derivatives
def _append_current_state(self, series: dict[str, list[float]]) -> None:
for component in self.network.components.values():
@@ -1156,29 +1226,112 @@ class GenericFluidSystem:
report_solver_time(time)
return self.rhs(time, state_vector)
jacobian = None
jacobian_fallback_reason: str | None = None
tangent_compilation: ThreePistonTangentCompilation | None = None
selected_tangent_provider: ThreePistonTangentProvider | None = None
self._ode_tangent_provider = None
requested_jacobian_mode = (
_requested_ode_jacobian_mode()
if jac_sparsity is not None
else "scipy"
)
if (
jac_sparsity is not None
and requested_jacobian_mode
in {"optimized", "hybrid", "semi-analytic"}
):
state_count = int(jac_sparsity.shape[0])
if (
requested_jacobian_mode == "hybrid"
and not self.pressure_flow_solver.causal_fast_path_enabled
):
jacobian_fallback_reason = "causalAlgebraicExecutionUnavailable"
elif int(jac_sparsity.nnz) >= state_count * state_count:
jacobian_fallback_reason = "denseStateDependencyPattern"
else:
exact_columns = None
if requested_jacobian_mode == "semi-analytic":
tangent_compilation = (
compile_three_piston_tangent_provider(self)
)
if tangent_compilation.eligible:
provider = tangent_compilation.provider
if provider is None:
raise RuntimeError(
"An eligible tangent compilation has no provider."
)
selected_tangent_provider = provider
exact_columns = (
tangent_compilation.columns,
provider,
)
else:
jacobian_fallback_reason = (
f"semiAnalytic:{tangent_compilation.reason}"
)
if (
requested_jacobian_mode != "semi-analytic"
or tangent_compilation is not None
and tangent_compilation.eligible
):
def evaluate_jacobian_rhs(time, state):
if cancel_check is not None and cancel_check():
raise IntegrationCancelled
return monitored_rhs(
time,
[float(value) for value in state],
)
try:
jacobian = SparseSecantJacobian(
evaluate_jacobian_rhs,
jac_sparsity,
integration_config.atol,
exact_rows=self._exact_ode_jacobian_rows(),
exact_columns=exact_columns,
max_consecutive_reuses=(
1
if requested_jacobian_mode == "hybrid"
else 0
),
)
self._ode_tangent_provider = (
selected_tangent_provider
)
except SparseJacobianCompatibilityError as exc:
jacobian_fallback_reason = (
f"scipyCompatibility:{type(exc).__name__}"
)
def handle_state_transition(*args):
transition = self.mechanical_state_reducer.state_transition(*args)
if transition is not None:
self._request_causal_residual_audit()
return transition
solution = integrate_ode(
rhs=monitored_rhs,
initial_state=initial_state,
config=integration_config,
t_eval=t_eval,
cancel_check=cancel_check,
accepted_step_callback=(
report_solver_time if cancel_check is not None else None
),
breakpoints=signal_event_times,
state_transition_handler=(
handle_state_transition
if self.mechanical_state_reducer.has_state_events
else None
),
jac_sparsity=jac_sparsity,
)
try:
solution = integrate_ode(
rhs=monitored_rhs,
initial_state=initial_state,
config=integration_config,
t_eval=t_eval,
cancel_check=cancel_check,
accepted_step_callback=(
report_solver_time if cancel_check is not None else None
),
breakpoints=signal_event_times,
state_transition_handler=(
handle_state_transition
if self.mechanical_state_reducer.has_state_events
else None
),
jac_sparsity=jac_sparsity,
jac=jacobian,
)
finally:
self._ode_tangent_provider = None
if isinstance(solution, ODESolution):
run_status: SimulationRunStatus = solution.status
integration_error = solution.error
@@ -1212,6 +1365,44 @@ class GenericFluidSystem:
"recoverableRetryCount": 0,
}
]
if jacobian is not None:
direct_jacobian = jacobian.diagnostics()
solver_segment_diagnostics[0].update(
{
"jacobianEvaluationCount": int(
direct_jacobian["jacobianEvaluationCount"]
),
"jacobianFullBuildCount": int(
direct_jacobian["fullBuildCount"]
),
"jacobianSecantReuseCount": int(
direct_jacobian["secantReuseCount"]
),
"jacobianAuditFailureCount": int(
direct_jacobian["auditFailureCount"]
),
"finiteDifferenceRhsEvaluationCount": int(
direct_jacobian[
"finiteDifferenceRhsEvaluationCount"
]
),
"jacobianBaseRhsEvaluationCount": int(
direct_jacobian["baseRhsEvaluationCount"]
),
"jacobianJvAuditRhsEvaluationCount": int(
direct_jacobian["jvAuditEvaluationCount"]
),
"exactColumnBuildCount": int(
direct_jacobian["exactColumnBuildCount"]
),
"exactColumnFallbackCount": int(
direct_jacobian["exactColumnFallbackCount"]
),
"jacobianAssemblySeconds": float(
direct_jacobian["assemblySeconds"]
),
}
)
solver_total_keys = (
"nfev",
"njev",
@@ -1225,21 +1416,123 @@ class GenericFluidSystem:
key: sum(int(segment[key]) for segment in solver_segment_diagnostics)
for key in solver_total_keys
}
jacobian_work_keys = (
"jacobianEvaluationCount",
"jacobianFullBuildCount",
"jacobianSecantReuseCount",
"jacobianAuditFailureCount",
"finiteDifferenceRhsEvaluationCount",
"jacobianBaseRhsEvaluationCount",
"jacobianJvAuditRhsEvaluationCount",
"exactColumnBuildCount",
"exactColumnFallbackCount",
"jacobianAssemblySeconds",
)
for key in jacobian_work_keys:
if any(key in segment for segment in solver_segment_diagnostics):
solver_totals[key] = sum(
segment.get(key, 0)
for segment in solver_segment_diagnostics
)
jacobian_diagnostics = (
self.jacobian_sparsity_diagnostics()
if integration_config.method in {"BDF", "Radau"}
else None
)
runtime_jacobian_diagnostics: dict[str, object] | None = None
if jacobian_diagnostics is not None:
color_group_count = int(jacobian_diagnostics["colorGroupCount"])
for segment in solver_segment_diagnostics:
segment["finiteDifferenceRhsEstimate"] = (
int(segment["njev"]) * color_group_count
if jacobian is None:
for segment in solver_segment_diagnostics:
segment["finiteDifferenceRhsEstimate"] = (
int(segment["njev"]) * color_group_count
)
runtime_jacobian_diagnostics = {
"mode": "scipySparseFiniteDifference",
"fallbackReason": jacobian_fallback_reason,
"jacobianEvaluationCount": int(solver_totals["njev"]),
"fullBuildCount": int(solver_totals["njev"]),
"finiteDifferenceRhsEstimateIsExact": False,
}
else:
for segment in solver_segment_diagnostics:
segment["finiteDifferenceRhsEstimate"] = int(
segment.get("finiteDifferenceRhsEvaluationCount", 0)
) + int(
segment.get("jacobianJvAuditRhsEvaluationCount", 0)
)
runtime_jacobian_diagnostics = dict(jacobian.diagnostics())
runtime_jacobian_diagnostics.update(
{
"fallbackReason": jacobian_fallback_reason,
"finiteDifferenceRhsEstimateIsExact": True,
}
)
if tangent_compilation is not None:
runtime_jacobian_diagnostics["tangentCompilation"] = (
tangent_compilation.diagnostics()
)
if tangent_compilation.eligible and jacobian is not None:
runtime_jacobian_diagnostics["mode"] = (
"semiAnalyticExactColumns"
)
exact_builds = int(
runtime_jacobian_diagnostics[
"exactColumnBuildCount"
]
)
exact_fallbacks = int(
runtime_jacobian_diagnostics[
"exactColumnFallbackCount"
]
)
if exact_builds == 0 and exact_fallbacks == 0:
effective_mode = "notEvaluated"
elif exact_builds == 0:
effective_mode = "numericalFallbackOnly"
elif exact_fallbacks:
effective_mode = "mixedExactAndNumericalFallback"
else:
effective_mode = "exactColumns"
runtime_jacobian_diagnostics["effectiveMode"] = (
effective_mode
)
if exact_fallbacks:
runtime_jacobian_diagnostics[
"runtimeFallbackReason"
] = runtime_jacobian_diagnostics[
"lastExactColumnFallbackReason"
]
solver_totals["finiteDifferenceRhsEstimate"] = sum(
int(segment["finiteDifferenceRhsEstimate"])
for segment in solver_segment_diagnostics
)
if jacobian is None:
solver_totals["jacobianRhsEvaluationCountEstimate"] = (
int(solver_totals["finiteDifferenceRhsEstimate"])
+ int(solver_totals["njev"])
)
else:
solver_totals["jacobianRhsEvaluationCount"] = (
int(
solver_totals.get(
"finiteDifferenceRhsEvaluationCount",
0,
)
)
+ int(
solver_totals.get(
"jacobianBaseRhsEvaluationCount",
0,
)
)
+ int(
solver_totals.get(
"jacobianJvAuditRhsEvaluationCount",
0,
)
)
)
with performance_span("simulation.postprocessing"):
series: dict[str, list[float]] = {"time": []}
@@ -1286,6 +1579,7 @@ class GenericFluidSystem:
"integration": {
"method": integration_config.method,
"jacobianSparsity": jacobian_diagnostics,
"jacobian": runtime_jacobian_diagnostics,
"segmentCount": len(solver_segment_diagnostics),
"segments": solver_segment_diagnostics,
"totals": solver_totals,