完成求解器雅可比矩阵首轮优化,增加更新目录,整理了文档文件夹,增加了服务启动脚本
This commit is contained in:
1 parent
6bb0591d32
commit
16a7eb2d6c
48 files changed
+8172
-217
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user