整合求解器活动监控与步长回归证据
同步远端 PNL0003 诊断和大采样网格能力,语义合并活动感知的 60 秒真停滞判定与旧后端 15 分钟兼容兜底。 纳管热路径优化、15 单元运行证据、浏览器与 API 报告,并补充北京时间更新日志和遗留问题。
This commit is contained in:
1 parent
c19cf77aee
commit
e18399c022
46 files changed
+181589
-170
No files matched your search
@@ -35,6 +35,12 @@ CAUSAL_EXECUTOR_V2_ENVIRONMENT_VARIABLE = "SIMULATION_CAUSAL_EXECUTOR_V2"
|
||||
CAUSAL_COORDINATE_KERNEL_ENVIRONMENT_VARIABLE = (
|
||||
"SIMULATION_CAUSAL_COORDINATE_KERNEL"
|
||||
)
|
||||
CAUSAL_DIRECT_SUM_ASSIGNMENTS_ENVIRONMENT_VARIABLE = (
|
||||
"SIMULATION_CAUSAL_DIRECT_SUM_ASSIGNMENTS"
|
||||
)
|
||||
CAUSAL_DIRECT_EQUATION_READERS_ENVIRONMENT_VARIABLE = (
|
||||
"SIMULATION_CAUSAL_DIRECT_EQUATION_READERS"
|
||||
)
|
||||
CAUSAL_FAST_PATH_AUDIT_INTERVAL = 64
|
||||
|
||||
|
||||
@@ -57,6 +63,20 @@ def _causal_coordinate_kernel_environment_enabled() -> bool:
|
||||
return value.strip().lower() not in {"0", "false", "no", "off"}
|
||||
|
||||
|
||||
def _causal_direct_sum_assignments_environment_enabled() -> bool:
|
||||
"""Return whether exact sum-to-zero targets bypass component tuples."""
|
||||
|
||||
value = os.getenv(CAUSAL_DIRECT_SUM_ASSIGNMENTS_ENVIRONMENT_VARIABLE, "1")
|
||||
return value.strip().lower() not in {"0", "false", "no", "off"}
|
||||
|
||||
|
||||
def _causal_direct_equation_readers_environment_enabled() -> bool:
|
||||
"""Return whether exact-class scalar residual readers are enabled."""
|
||||
|
||||
value = os.getenv(CAUSAL_DIRECT_EQUATION_READERS_ENVIRONMENT_VARIABLE, "1")
|
||||
return value.strip().lower() not in {"0", "false", "no", "off"}
|
||||
|
||||
|
||||
class AlgebraicSolveError(RuntimeError):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -127,6 +147,7 @@ class EffortAnchor:
|
||||
unknown: AlgebraicUnknown
|
||||
evaluate: Callable[[], float]
|
||||
equation_id: str
|
||||
causal_evaluate: Callable[[], float] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -326,6 +347,14 @@ class PressureFlowSolver:
|
||||
self.residual_tolerance = residual_tolerance
|
||||
self.max_evaluations = max_evaluations
|
||||
self.scope_kind = scope_kind
|
||||
self._causal_direct_sum_assignments_environment_enabled = (
|
||||
_causal_direct_sum_assignments_environment_enabled()
|
||||
)
|
||||
self._causal_direct_sum_flow_assignment_count = 0
|
||||
self._causal_direct_equation_readers_environment_enabled = (
|
||||
_causal_direct_equation_readers_environment_enabled()
|
||||
)
|
||||
self._causal_direct_effort_anchor_count = 0
|
||||
self.unknowns = self._build_unknowns()
|
||||
self._unknowns_by_id = {unknown.id: unknown for unknown in self.unknowns}
|
||||
self._unknowns_by_variable = {
|
||||
@@ -371,6 +400,9 @@ class PressureFlowSolver:
|
||||
for item in self._component_equation_plan
|
||||
for equation in item.templates
|
||||
) + tuple(item.template for item in self._connection_equation_plan)
|
||||
self._causal_direct_equation_readers = (
|
||||
self._build_causal_direct_equation_readers()
|
||||
)
|
||||
self._effort_groups = {
|
||||
variable: self._build_effort_equality_groups(variable)
|
||||
for variable in ("p", "x", "v")
|
||||
@@ -418,6 +450,11 @@ class PressureFlowSolver:
|
||||
self._causal_fast_path_eligible,
|
||||
self._causal_fast_path_fallback_reason,
|
||||
) = self._build_causal_execution_plan()
|
||||
self._causal_direct_effort_anchor_count = sum(
|
||||
assignment.anchor.causal_evaluate is not None
|
||||
for assignments in self._causal_effort_plan_by_variable.values()
|
||||
for assignment in assignments
|
||||
)
|
||||
self._causal_fast_path_environment_enabled = (
|
||||
_causal_fast_path_environment_enabled()
|
||||
)
|
||||
@@ -599,6 +636,18 @@ class PressureFlowSolver:
|
||||
"coordinateKernelFastSolveCount": (
|
||||
self._causal_coordinate_fast_solve_count
|
||||
),
|
||||
"directSumAssignmentsConfigured": (
|
||||
self._causal_direct_sum_assignments_environment_enabled
|
||||
),
|
||||
"directSumFlowAssignmentCount": (
|
||||
self._causal_direct_sum_flow_assignment_count
|
||||
),
|
||||
"directEquationReadersConfigured": (
|
||||
self._causal_direct_equation_readers_environment_enabled
|
||||
),
|
||||
"directEffortAnchorCount": (
|
||||
self._causal_direct_effort_anchor_count
|
||||
),
|
||||
"compiledEffortUnknownCount": (
|
||||
self._causal_compiled_effort_unknown_count
|
||||
),
|
||||
@@ -850,6 +899,9 @@ class PressureFlowSolver:
|
||||
] = {}
|
||||
component_evaluators: dict[int, Callable[[], tuple[float, ...]]] = {}
|
||||
for coordinate_index, assignment in indexed_assignments:
|
||||
if assignment.anchor.causal_evaluate is not None:
|
||||
direct_targets.append((coordinate_index, assignment))
|
||||
continue
|
||||
equation_index = equation_index_by_id[
|
||||
assignment.anchor.equation_id
|
||||
]
|
||||
@@ -934,10 +986,22 @@ class PressureFlowSolver:
|
||||
if stage is None:
|
||||
return False
|
||||
for coordinate_index, assignment in stage.direct_targets:
|
||||
workspace[coordinate_index] = (
|
||||
self._read_effort_anchor(assignment)
|
||||
- assignment.anchor.evaluate()
|
||||
evaluate = (
|
||||
assignment.anchor.causal_evaluate
|
||||
if assignment.anchor.causal_evaluate is not None
|
||||
else assignment.anchor.evaluate
|
||||
)
|
||||
try:
|
||||
workspace[coordinate_index] = (
|
||||
self._read_effort_anchor(assignment)
|
||||
- evaluate()
|
||||
)
|
||||
except MemoryError:
|
||||
raise
|
||||
except Exception:
|
||||
if assignment.anchor.causal_evaluate is None:
|
||||
raise
|
||||
return False
|
||||
for evaluation in stage.component_evaluations:
|
||||
equation_values = evaluation.evaluate()
|
||||
for target in evaluation.targets:
|
||||
@@ -975,7 +1039,19 @@ class PressureFlowSolver:
|
||||
return False
|
||||
for assignment in assignments:
|
||||
anchor = assignment.anchor
|
||||
target = anchor.unknown.read() - anchor.evaluate()
|
||||
evaluate = (
|
||||
anchor.causal_evaluate
|
||||
if anchor.causal_evaluate is not None
|
||||
else anchor.evaluate
|
||||
)
|
||||
try:
|
||||
target = anchor.unknown.read() - evaluate()
|
||||
except MemoryError:
|
||||
raise
|
||||
except Exception:
|
||||
if anchor.causal_evaluate is None:
|
||||
raise
|
||||
return False
|
||||
if not isfinite(target) or (
|
||||
variable == "p" and target <= PRESSURE_LOWER_BOUND_PA
|
||||
):
|
||||
@@ -1431,6 +1507,9 @@ class PressureFlowSolver:
|
||||
unknown=unknown,
|
||||
evaluate=self._equation_value_reader(equation),
|
||||
equation_id=equation.id,
|
||||
causal_evaluate=(
|
||||
self._causal_direct_equation_readers.get(equation.id)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1851,6 +1930,45 @@ class PressureFlowSolver:
|
||||
|
||||
return read_component_equation
|
||||
|
||||
def _build_causal_direct_equation_readers(
|
||||
self,
|
||||
) -> dict[str, Callable[[], float]]:
|
||||
"""Compile exact-class scalar residual capabilities, failing closed."""
|
||||
|
||||
if not self._causal_direct_equation_readers_environment_enabled:
|
||||
return {}
|
||||
|
||||
readers: dict[str, Callable[[], float]] = {}
|
||||
for evaluation in self._component_equation_plan:
|
||||
component = evaluation.component
|
||||
# A subclass must repeat the declaration after changing equation
|
||||
# semantics; inherited purity promises are deliberately ignored.
|
||||
declared = type(component).__dict__.get(
|
||||
"pressure_flow_equation_value_readers"
|
||||
)
|
||||
if not callable(declared):
|
||||
continue
|
||||
try:
|
||||
component_readers = declared(component)
|
||||
except MemoryError:
|
||||
raise
|
||||
except Exception:
|
||||
continue
|
||||
if not isinstance(component_readers, Mapping):
|
||||
continue
|
||||
template_ids = frozenset(
|
||||
template.id for template in evaluation.templates
|
||||
)
|
||||
if any(
|
||||
not isinstance(equation_id, str)
|
||||
or equation_id not in template_ids
|
||||
or not callable(reader)
|
||||
for equation_id, reader in component_readers.items()
|
||||
):
|
||||
continue
|
||||
readers.update(component_readers)
|
||||
return readers
|
||||
|
||||
def _connection_flow_target_reader(
|
||||
self,
|
||||
equation,
|
||||
@@ -1870,6 +1988,69 @@ class PressureFlowSolver:
|
||||
f"Connection flow equation {equation.id} does not contain {unknown.id}."
|
||||
)
|
||||
|
||||
def _sum_to_zero_flow_target_reader(
|
||||
self,
|
||||
equation,
|
||||
unknown: AlgebraicUnknown,
|
||||
) -> Callable[[], float]:
|
||||
"""Compile the exact target of a declared flow sum without callbacks."""
|
||||
|
||||
if equation.relation != "sumToZero":
|
||||
raise ValueError(
|
||||
f"Equation {equation.id} is not a sum-to-zero relation."
|
||||
)
|
||||
if len(set(equation.variables)) != len(equation.variables):
|
||||
raise ValueError(
|
||||
f"Equation {equation.id} repeats a sum-to-zero variable."
|
||||
)
|
||||
equation_unknowns: list[AlgebraicUnknown] = []
|
||||
for variable in equation.variables:
|
||||
current = self._unknowns_by_id.get(variable)
|
||||
if current is None or current.role != "flow":
|
||||
raise ValueError(
|
||||
f"Equation {equation.id} has a non-flow variable."
|
||||
)
|
||||
equation_unknowns.append(current)
|
||||
if not any(current is unknown for current in equation_unknowns):
|
||||
raise ValueError(
|
||||
f"Equation {equation.id} does not contain {unknown.id}."
|
||||
)
|
||||
compiled_unknowns = tuple(equation_unknowns)
|
||||
if len(compiled_unknowns) == 2:
|
||||
first, second = compiled_unknowns
|
||||
return lambda: 0.0 - (first.read() + second.read())
|
||||
|
||||
def target() -> float:
|
||||
# The target unknown is already zeroed by the stage executor. Keep
|
||||
# its slot in the sum so the floating-point operation order matches
|
||||
# the declared built-in residual, including near cancellation.
|
||||
return 0.0 - sum(current.read() for current in compiled_unknowns)
|
||||
|
||||
return target
|
||||
|
||||
@staticmethod
|
||||
def _component_declares_exact_sum_to_zero_equation(
|
||||
component: object,
|
||||
equation_id: str,
|
||||
) -> bool:
|
||||
"""Accept only an exact concrete-class promise for callback bypass."""
|
||||
|
||||
declared_suffixes = type(component).__dict__.get(
|
||||
"PRESSURE_FLOW_EXACT_SUM_TO_ZERO_EQUATION_SUFFIXES"
|
||||
)
|
||||
if not isinstance(declared_suffixes, frozenset) or any(
|
||||
not isinstance(suffix, str) or not suffix or ":" in suffix
|
||||
for suffix in declared_suffixes
|
||||
):
|
||||
return False
|
||||
component_name = getattr(component, "name", None)
|
||||
if not isinstance(component_name, str):
|
||||
return False
|
||||
return any(
|
||||
equation_id == f"{component_name}:{suffix}"
|
||||
for suffix in declared_suffixes
|
||||
)
|
||||
|
||||
def _pressure_flow_equation_values(self) -> tuple[float, ...]:
|
||||
"""Evaluate live equation values through the compiled topology."""
|
||||
|
||||
@@ -1918,6 +2099,29 @@ class PressureFlowSolver:
|
||||
|
||||
evaluation = self._component_equation_plans_by_id[equation.owner_id]
|
||||
component = evaluation.component
|
||||
if (
|
||||
self._causal_direct_sum_assignments_environment_enabled
|
||||
and equation.relation == "sumToZero"
|
||||
and self._component_declares_exact_sum_to_zero_equation(
|
||||
component,
|
||||
equation.id,
|
||||
)
|
||||
):
|
||||
try:
|
||||
evaluate = self._sum_to_zero_flow_target_reader(
|
||||
equation,
|
||||
unknown,
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self._causal_direct_sum_flow_assignment_count += 1
|
||||
return ExplicitFlowAssignment(
|
||||
equation_id=equation.id,
|
||||
unknown=unknown,
|
||||
evaluate=evaluate,
|
||||
)
|
||||
|
||||
equations = evaluation.templates
|
||||
equation_ids = tuple(current.id for current in equations)
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user