完善通用求解器回归与前端交互

- 引入因果坐标内核、热流体恢复和递进长时回归\n- 完善正交连线、线桥、视图保持与结果曲线缩放\n- 补充依赖约束、CI、测试基线和北京时间更新日志
This commit is contained in:
lujingze committed 2026-08-18 06:42:07 +00:00
1 parent 143e8dd309
commit b435daecf2
65 files changed
+172271 -701

No files matched your search

+177 -2
View File
@@ -9,6 +9,7 @@ from app.simulation.solvers.algebraic import (
PRESSURE_LOWER_BOUND_PA,
AlgebraicSolveDiagnostics,
AlgebraicUnknown,
CausalFlowKernelStage,
ExplicitFlowStage,
PressureFlowSolver,
)
@@ -237,6 +238,27 @@ class StreamPressureBlockSolver:
)
for stage in pressure_flow_solver._explicit_flow_plan
)
secondary_coordinate = 0
secondary_kernel_plan: list[CausalFlowKernelStage] = []
for stage in self._selected_explicit_flow_plan:
coordinate_indices = tuple(
range(
secondary_coordinate,
secondary_coordinate + len(stage.assignments),
)
)
secondary_coordinate += len(stage.assignments)
secondary_kernel_plan.append(
CausalFlowKernelStage(
stage=stage,
coordinate_indices=coordinate_indices,
)
)
self._causal_secondary_flow_kernel_plan = tuple(secondary_kernel_plan)
self._causal_secondary_coordinate_values = [0.0] * secondary_coordinate
self._causal_v2_entry_values = [0.0] * len(
self._selected_flow_unknowns
)
self._selected_equation_evaluation = (
self._compile_selected_equation_evaluation()
if self.blocks
@@ -259,7 +281,11 @@ class StreamPressureBlockSolver:
self._causal_full_residual_audit_count = 0
self._causal_audit_failure_count = 0
self._causal_legacy_fallback_count = 0
self._causal_v2_fast_solve_count = 0
self._causal_v2_runtime_validation_failure_count = 0
self._causal_coordinate_fast_solve_count = 0
self._causal_last_verified_diagnostics: AlgebraicSolveDiagnostics | None = None
self._causal_cached_fast_diagnostics: AlgebraicSolveDiagnostics | None = None
@property
def available(self) -> bool:
@@ -273,6 +299,13 @@ class StreamPressureBlockSolver:
and self.pressure_flow_solver.causal_fast_path_enabled
)
@property
def causal_executor_v2_enabled(self) -> bool:
return (
self.causal_fast_path_enabled
and self.pressure_flow_solver._causal_executor_v2_environment_enabled
)
def request_causal_audit(self) -> None:
self._causal_audit_required = True
@@ -280,7 +313,9 @@ class StreamPressureBlockSolver:
parent = self.pressure_flow_solver.causal_execution_diagnostics()
disabled_reason = self._causal_runtime_disabled_reason
if not bool(parent["enabled"]):
disabled_reason = str(parent["disabledReason"] or "parentCausalPathDisabled")
disabled_reason = str(
parent["disabledReason"] or "parentCausalPathDisabled"
)
elif not self._causal_fast_path_eligible:
disabled_reason = self._causal_fast_path_fallback_reason
verified = self._causal_last_verified_diagnostics
@@ -298,6 +333,34 @@ class StreamPressureBlockSolver:
"lastVerifiedMaxScaledResidual": (
verified.max_scaled_residual if verified is not None else None
),
"executorV2Configured": (
self.pressure_flow_solver._causal_executor_v2_environment_enabled
),
"executorV2Enabled": self.causal_executor_v2_enabled,
"executorV2FastSolveCount": self._causal_v2_fast_solve_count,
"executorV2RuntimeValidationFailureCount": (
self._causal_v2_runtime_validation_failure_count
),
"coordinateKernelConfigured": (
self.pressure_flow_solver._causal_coordinate_kernel_environment_enabled
),
"coordinateKernelEnabled": (
self.causal_executor_v2_enabled
and self.pressure_flow_solver.causal_coordinate_kernel_enabled
),
"coordinateKernelFastSolveCount": (
self._causal_coordinate_fast_solve_count
),
"compiledEffortUnknownCount": len(
self._causal_effort_entry_positions
),
"compiledFlowAssignmentCount": len(
self._causal_expected_flow_equation_ids
),
"compiledAssignmentCount": (
len(self._causal_effort_entry_positions)
+ len(self._causal_expected_flow_equation_ids)
),
}
def _disable_causal_fast_path(self, reason: str) -> None:
@@ -405,6 +468,28 @@ class StreamPressureBlockSolver:
self._causal_solves_since_audit = 0
self._causal_audit_required = False
self._causal_last_verified_diagnostics = diagnostics
self._causal_cached_fast_diagnostics = replace(
diagnostics,
message=(
"Compiled causal stream-pressure block completed; residuals "
"reuse the latest full audit."
),
evaluations=0,
residual_evaluations=0,
dense_fallback_used=False,
nonlinear_block_count=0,
nonlinear_block_unknown_count=0,
block_fallback_used=False,
block_fallback_reason=None,
residual_verified_this_solve=False,
causal_fast_path_used=True,
)
def _causal_v2_fast_diagnostics(self) -> AlgebraicSolveDiagnostics:
cached = self._causal_cached_fast_diagnostics
if cached is None:
raise RuntimeError("Stream causal execution has no residual audit.")
return cached
def _causal_fast_diagnostics(
self,
@@ -788,6 +873,45 @@ class StreamPressureBlockSolver:
unknown.write(entry_values[position])
return frozenset(seeded_equation_ids)
def _execute_compiled_secondary_flow_plan(self) -> str | None:
"""Execute selected flow assignments without equation-id sets."""
if self.pressure_flow_solver.causal_coordinate_kernel_enabled:
failure = (
self.pressure_flow_solver._execute_causal_coordinate_flow_stages(
self._causal_secondary_flow_kernel_plan,
self._causal_secondary_coordinate_values,
)
)
if failure == "causalFlowAssignmentCountMismatch":
return "causalSecondaryFlowAssignmentCountMismatch"
if failure == "nonFiniteCausalFlowAssignment":
return "nonFiniteCausalSecondaryFlowAssignment"
if failure and failure.startswith("causalFlowEvaluationFailed:"):
return "causalSecondaryFlowEvaluationFailed:" + failure.rsplit(
":", 1
)[-1]
return failure
for unknown in self._selected_flow_unknowns:
unknown.write(0.0)
for stage in self._selected_explicit_flow_plan:
try:
values = self.pressure_flow_solver._evaluate_explicit_flow_stage(
stage
)
except MemoryError:
raise
except (ArithmeticError, RuntimeError, ValueError) as exc:
return f"causalSecondaryFlowEvaluationFailed:{type(exc).__name__}"
if len(values) != len(stage.assignments):
return "causalSecondaryFlowAssignmentCountMismatch"
for assignment, target_value in zip(stage.assignments, values):
if not isfinite(target_value):
return "nonFiniteCausalSecondaryFlowAssignment"
assignment.unknown.write(target_value)
return None
@staticmethod
def _equation_scales_from_specs(
specs: tuple[_EquationScaleSpec, ...],
@@ -1083,11 +1207,62 @@ class StreamPressureBlockSolver:
scale_context: Mapping[str, float] | None = None,
) -> StreamBlockSolveResult:
solver = self.pressure_flow_solver
context = dict(scale_context or solver.scale_context())
causal_candidate = self.causal_fast_path_enabled
causal_audit_due = (
self._causal_audit_is_due() if causal_candidate else False
)
causal_v2_candidate = (
causal_candidate
and solver._causal_executor_v2_environment_enabled
and not causal_audit_due
)
if causal_v2_candidate:
# The secondary causal proof rejects every special pressure seed,
# so this executor mutates selected flow coordinates only. Keep
# the minimal transactional snapshot for the rare fallback path.
v2_entry_values = self._causal_v2_entry_values
for position, unknown in enumerate(self._selected_flow_unknowns):
v2_entry_values[position] = unknown.state.m_flow
def restore_v2_entry_mutations() -> None:
for unknown, value in zip(
self._selected_flow_unknowns,
v2_entry_values,
):
unknown.state.m_flow = value
try:
v2_failure_reason = (
self._execute_compiled_secondary_flow_plan()
)
except BaseException:
restore_v2_entry_mutations()
raise
if v2_failure_reason is None:
diagnostics = self._causal_v2_fast_diagnostics()
self._causal_fast_solve_count += 1
self._causal_v2_fast_solve_count += 1
if solver.causal_coordinate_kernel_enabled:
self._causal_coordinate_fast_solve_count += 1
self._causal_solves_since_audit += 1
selected = self._selected_equation_evaluation
assert selected is not None
return StreamBlockSolveResult(
diagnostics=(diagnostics,),
scopes=(selected.scope_components,),
used_global_fallback=False,
)
restore_v2_entry_mutations()
self._causal_v2_runtime_validation_failure_count += 1
self._causal_legacy_fallback_count += 1
self._disable_causal_fast_path(v2_failure_reason)
causal_candidate = False
causal_audit_due = False
# Keep scale construction and the full mutation snapshot off the v2
# success path. Callers may still precompute a shared scale mapping;
# avoiding that producer requires a later Generic-system API change.
context = dict(scale_context or solver.scale_context())
entry_values = tuple(
unknown.read() for unknown in self._entry_mutated_unknowns
)