The ``active_path.py`` module
=============================
.. py:module:: ansys.fluent.mcp.solve.lib.active_path
Summary
-------
.. py:currentmodule:: active_path
.. tab-set::
.. tab-item:: Classes
.. list-table::
:header-rows: 0
:widths: auto
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.SolverMode`
- A snapshot of the live solver mode facets that gate path selection.
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.PathInfo`
- Classification of a concrete path within a multi-path group.
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.RerouteResult`
- Result of checking a proposed path against the live mode.
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.WriteTarget`
- Canonical resolution of "where should a write to ``requested_path`` land".
.. tab-item:: Enums
.. list-table::
:header-rows: 0
:widths: auto
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.UrfFamily`
- The active under-relaxation family for a given solver mode.
* - :py:obj:`~ansys.fluent.mcp.solve.lib.active_path.PathGroup`
- The multi-path class a concrete path belongs to.
.. tab-item:: Functions
.. list-table::
:header-rows: 0
:widths: auto
* - :py:obj:`~is_coupled_scheme`
- Return True for coupled-family pressure-velocity schemes (hyphen-tolerant).
* - :py:obj:`~active_urf_family`
- Return the URF family that is ACTIVE under ``mode``.
* - :py:obj:`~urf_path`
- Return the ACTIVE concrete URF path for ``equation`` under ``mode``.
* - :py:obj:`~classify_path`
- Classify ``path`` into a known multi-path group, or ``None``.
* - :py:obj:`~reroute`
- Check ``path`` against ``mode`` and, if inactive, point to the fix.
* - :py:obj:`~path_family`
- Return the :class:`PathFamily` translator for ``mode``.
* - :py:obj:`~bc_root`
- Active boundary-condition namespace root.
* - :py:obj:`~cz_root`
- Active cell-zone namespace root.
* - :py:obj:`~interface_root`
- Active mesh-interface namespace root.
* - :py:obj:`~bc_path`
- Active indexed BC node path (e.g. ``...wall['hw']``).
* - :py:obj:`~cz_path`
- Active indexed cell-zone node path.
* - :py:obj:`~run_calculation_path`
- Active run-calculation command path (steady vs transient).
* - :py:obj:`~multiphase_volume_fraction_path`
- Per-phase BC volume-fraction path under the active namespace.
* - :py:obj:`~multiphase_bc_phase_path`
- Return the per-phase BC leaf path under the active namespace.
* - :py:obj:`~classify_methods_gate`
- Return ``(facet_name, human_description)`` for a mode-gated methods prefix.
* - :py:obj:`~resolve_write_target`
- Return the canonical :class:`WriteTarget` for a proposed write.
* - :py:obj:`~describe_mode`
- Return a compact JSON-safe summary of ``mode``.
* - :py:obj:`~write_target_hints`
- Return canonical write-target answers for the CURRENT ``mode``.
* - :py:obj:`~format_mode_summary`
- Return a short human-readable summary of ``mode`` for prompts.
.. tab-item:: Constants
.. list-table::
:header-rows: 0
:widths: auto
* - :py:obj:`~MULTI_PATH_CLASSES`
-
.. toctree::
:titlesonly:
:maxdepth: 1
:hidden:
SolverMode
PathInfo
RerouteResult
WriteTarget
.. toctree::
:titlesonly:
:maxdepth: 1
:hidden:
UrfFamily
PathGroup
Description
-----------
Active-path resolver — one deterministic primitive for mode-dependent paths.
Fluent exposes the *same logical setting* at MULTIPLE physical paths whose
ACTIVE one depends on the live solver mode. Writing the wrong sibling is
silently rejected (``InactiveObjectError`` / "the object cannot be edited").
The canonical example is under-relaxation factors (URFs), whose family
depends on the pressure-velocity coupling scheme:
* segregated (SIMPLE / SIMPLEC / PISO / Fractional Step) ->
``solution.controls.under_relaxation['']`` (a NamedObject family)
* coupled, no pseudo-transient ->
``solution.controls.p_v_controls.explicit_*_under_relaxation`` (scalars)
+ ``solution.controls.relaxation_factor['']``
* coupled + pseudo-transient ->
``solution.controls.pseudo_time_explicit_relaxation_factor
.global_dt_pseudo_relax['']``
Historically this knowledge was scattered across recipe URF stagers
(``stage_under_relaxation_auto``), the agent ``resolve_active_path`` tool,
the validator's ``inactive_path`` diagnostic, and per-recipe ``is_utl``
branches. This module is the SINGLE source of truth, shared by the OSS
leaf and the agent monorepo.
Two cooperating capabilities:
1. **Forward resolution** -- given a :class:`SolverMode` + a logical
setting (e.g. a URF equation, a BC family/name, the run command),
return the concrete *active* path. See :func:`urf_path`,
:func:`bc_path`, :func:`run_calculation_path`, ...
2. **Reverse routing** -- given ANY proposed path + a :class:`SolverMode`,
say whether it is active and, if not, what the correct active sibling
is. See :func:`reroute`. This is what lets the validator hard-block a
wrong-path write *before* Apply and hand clients a deterministic fix
instead of letting the executor silently skip the step.
The module has NO dependency on the agent loop or live backend; it is pure,
deterministic Fluent-path knowledge. Build the :class:`SolverMode` from a
live state snapshot (:meth:`SolverMode.from_state`) and pass it in.
..
!! processed by numpydoc !!
Module detail
-------------
.. py:function:: is_coupled_scheme(flow_scheme: str | None) -> bool
Return True for coupled-family pressure-velocity schemes (hyphen-tolerant).
..
!! processed by numpydoc !!
.. py:function:: active_urf_family(mode: SolverMode) -> UrfFamily
Return the URF family that is ACTIVE under ``mode``.
* coupled + pseudo-transient -> per-equation pseudo-time family
* coupled (no pseudo) -> explicit scalar + relaxation_factor
* segregated -> under_relaxation NamedObject family
..
!! processed by numpydoc !!
.. py:function:: urf_path(mode: SolverMode, equation: str) -> str
Return the ACTIVE concrete URF path for ``equation`` under ``mode``.
.. rubric:: Examples
>>> urf_path(SolverMode(), "pressure")
"solution.controls.under_relaxation['pressure']"
>>> urf_path(SolverMode(flow_scheme="Coupled"), "momentum")
'solution.controls.p_v_controls.explicit_momentum_under_relaxation'
>>> urf_path(SolverMode(flow_scheme="Coupled", pseudo_transient=True), "k")
"solution.controls.pseudo_time_explicit_relaxation_factor.global_dt_pseudo_relax['k']"
..
!! processed by numpydoc !!
.. py:function:: classify_path(path: str) -> PathInfo | None
Classify ``path`` into a known multi-path group, or ``None``.
``None`` means the path is not a known mode-dependent setting, so
callers should treat it as "we can't reason about activation here"
and NOT block it.
..
!! processed by numpydoc !!
.. py:function:: reroute(path: str, mode: SolverMode) -> RerouteResult
Check ``path`` against ``mode`` and, if inactive, point to the fix.
Deterministic and conservative: returns ``active=True`` for any path
that is not a recognized multi-path setting, so it never false-blocks
an ordinary write.
..
!! processed by numpydoc !!
.. py:function:: path_family(mode: SolverMode) -> ansys.fluent.mcp.solve.lib.utl.PathFamily
Return the :class:`PathFamily` translator for ``mode``.
..
!! processed by numpydoc !!
.. py:function:: bc_root(mode: SolverMode) -> str
Active boundary-condition namespace root.
..
!! processed by numpydoc !!
.. py:function:: cz_root(mode: SolverMode) -> str
Active cell-zone namespace root.
..
!! processed by numpydoc !!
.. py:function:: interface_root(mode: SolverMode) -> str
Active mesh-interface namespace root.
..
!! processed by numpydoc !!
.. py:function:: bc_path(mode: SolverMode, family: str, name: str) -> str
Active indexed BC node path (e.g. ``...wall['hw']``).
..
!! processed by numpydoc !!
.. py:function:: cz_path(mode: SolverMode, family: str, name: str) -> str
Active indexed cell-zone node path.
..
!! processed by numpydoc !!
.. py:function:: run_calculation_path(mode: SolverMode) -> str
Active run-calculation command path (steady vs transient).
..
!! processed by numpydoc !!
.. py:function:: multiphase_volume_fraction_path(mode: SolverMode, bc_family: str, bc_name: str, phase_key: str, *, leaf: str = 'volume_fraction') -> str
Per-phase BC volume-fraction path under the active namespace.
``leaf`` defaults to the inlet ``volume_fraction`` value leaf; pass a
different scoped leaf (e.g. ``backflow_volume_fraction``) as needed.
Kept for backward compatibility; new callers should prefer the more
general :func:`multiphase_bc_phase_path`.
..
!! processed by numpydoc !!
.. py:function:: multiphase_bc_phase_path(mode: SolverMode, bc_family: str, bc_name: str, phase_key: str, *, leaf: str, scope: str = 'multiphase', value_suffix: bool = True) -> str
Return the per-phase BC leaf path under the active namespace.
On multiphase cases Fluent splits BC leaves into two tiers:
* **Mixture-level** leaves (``momentum.*``, ``turbulence.*``,
``dpm.*``, ...) live directly on the BC's top namespace and are
set once per BC — call :func:`bc_path` for those.
* **Per-phase** leaves (``volume_fraction``, ``granular_temperature``,
per-phase turbulence / DPM / species knobs, ...) live under
``phase['']..`` on the BC. Writing them
under a NON-matching phase key is silently rejected.
This helper composes the per-phase path for ANY leaf (not just
``volume_fraction`` as the legacy helper did) so multiphase-aware
callers do not need to string-format the path themselves. It also
handles the ``.value`` suffix that Fluent applies to numeric leaves
(``volume_fraction.value``) while allowing structural leaves that
do NOT carry the suffix (``turbulence.turb_intensity``) via
``value_suffix=False``.
:Parameters:
**mode**
Live solver mode (drives UTL vs standard root selection).
**bc_family**
BC family (e.g. ``"velocity_inlet"``, ``"pressure_outlet"``,
``"mass_flow_inlet"``, ``"wall"``).
**bc_name**
Named-object key on the BC family (e.g. ``"inlet1"``).
**phase_key**
Phase-name key as stored on ``setup.models.multiphase.phases``
(typically a user-visible string like ``"water-liquid"`` or
Fluent's default ``"phase-1"``).
**leaf**
The per-phase leaf name relative to ```` (e.g.
``"volume_fraction"``, ``"granular_temperature"``,
``"turb_intensity"``, ``"backflow_volume_fraction"``).
**scope**
Container under which the leaf lives on the phase branch;
Fluent uses ``"multiphase"`` for VOF / mixture / eulerian
volume-fraction / granular knobs (the default). Turbulence
per-phase leaves live under ``"turbulence"``; DPM per-phase
under ``"dpm"``.
**value_suffix**
Append ``.value`` to the path. True for numeric quantities
(the common case), False for structural / enum leaves.
:Returns:
:class:`python:str`
The concrete active per-phase BC path.
.. rubric:: Examples
>>> multiphase_bc_phase_path(
... SolverMode(),
... "velocity_inlet",
... "inlet1",
... "water-liquid",
... leaf="volume_fraction",
... )
"setup.boundary_conditions.velocity_inlet['inlet1'].phase['water-liquid'].multiphase.volume_fraction.value"
>>> multiphase_bc_phase_path(
... SolverMode(utl=True),
... "wall",
... "hw",
... "primary",
... leaf="turb_intensity",
... scope="turbulence",
... value_suffix=False,
... )
"setup.physics.boundaries.wall['hw'].phase['primary'].turbulence.turb_intensity"
..
!! processed by numpydoc !!
.. py:function:: classify_methods_gate(path: str) -> tuple[str, str] | None
Return ``(facet_name, human_description)`` for a mode-gated methods prefix.
If ``path`` starts with a known ``solution.methods.*`` mode-gated
prefix, return the facet + description tuple; otherwise return
``None``.
Consumers should call :func:`_methods_facet_active` (or replicate
its logic) to decide whether the gate is currently satisfied. This
is complementary to :func:`classify_path` — the latter handles
URF / BC / CZ / interface / run families and returns a
:class:`PathInfo`; this helper covers ``solution.methods.*`` where
the "correct sibling" concept does not apply (the path is either
active or the mode is wrong).
..
!! processed by numpydoc !!
.. py:function:: resolve_write_target(mode: SolverMode, path: str) -> WriteTarget
Return the canonical :class:`WriteTarget` for a proposed write.
Combines :func:`classify_path` + :func:`reroute` (multi-path
families) with :func:`classify_methods_gate` (``solution.methods.*``
mode-gated leaves) behind one call so every consumer produces the
same answer. When the path is not a recognized multi-path or
mode-gated setting, returns a "pass-through" :class:`WriteTarget`
with ``needs_reroute=False`` and ``classified=False`` — that is
the signal to fall back to the default schema-only check.
:Parameters:
**mode**
Live solver mode facets (build via :meth:`SolverMode.from_state`).
**path**
Proposed write path (bracket-indexed forms accepted).
:Returns:
:obj:`WriteTarget`
Canonical resolution. When ``needs_reroute`` is True,
``active_path`` carries the sibling to write instead (or,
if a deterministic active path cannot be derived, equals
``requested_path`` and the caller should surface the
``reason`` verbatim rather than silently proceeding).
..
!! processed by numpydoc !!
.. py:function:: describe_mode(mode: SolverMode) -> dict[str, Any]
Return a compact JSON-safe summary of ``mode``.
Suitable for embedding in a system prompt or a debug payload.
Fields:
* ``time`` — ``"steady"`` or ``"transient"``.
* ``solver_type`` — ``"pressure-based"`` or ``"density-based"``.
* ``flow_scheme`` — the raw p-v coupling string (may be None).
* ``coupled`` — bool.
* ``pseudo_transient`` — bool.
* ``urf_family`` — active URF family enum value.
* ``bc_namespace`` / ``cz_namespace`` / ``interface_namespace``
— ``"utl"`` or ``"standard"``.
* ``multiphase`` — ``None`` for single-phase, else
``{model, phases}``.
* ``turbulence_model`` / ``energy`` — passthrough.
* ``nita`` — bool.
..
!! processed by numpydoc !!
.. py:function:: write_target_hints(mode: SolverMode) -> dict[str, Any]
Return canonical write-target answers for the CURRENT ``mode``.
Emits a small dict that any authoring surface (agent-loop system prompt,
`describe_path` fallback, external host preflight) can turn into
a short "when you write X, use path Y" guide. Contains ONLY facts
derivable from ``mode`` — no live tool calls, no schema probes.
Keys:
* ``urf_family`` — active URF family enum value.
* ``urf_root`` — the container path (segregated NamedObject root,
coupled scalar prefix, or pseudo-time NamedObject root).
* ``urf_key_template`` — how to index into that root
(``"['']"`` for NamedObject families, ``""`` for
scalar leaves under the coupled-explicit family).
* ``run_calculation_path`` — active iterate command.
* ``bc_root`` / ``cz_root`` / ``interface_root`` — active namespace
roots.
* ``multiphase`` — ``None`` for single-phase, else a per-phase
write-guidance dict:
* ``model`` — VOF / Mixture / Eulerian.
* ``phases`` — ordered list.
* ``mixture_level_bc_template`` —
``".['']."``.
* ``per_phase_bc_template`` —
``".[''].phase[''].multiphase."``.
* ``per_phase_bc_note`` — "which BC leaves are per-phase vs mixture-level
varies by model — call describe_path to confirm".
..
!! processed by numpydoc !!
.. py:function:: format_mode_summary(mode: SolverMode) -> str
Return a short human-readable summary of ``mode`` for prompts.
Two lines: one for the mode identity, one for the write-target
hints (URF family, BC namespace, run command, multiphase state).
Deliberately compact — the full JSON summary is available via
:func:`describe_mode` and :func:`write_target_hints` when a caller
needs to inspect it.
..
!! processed by numpydoc !!
.. py:data:: MULTI_PATH_CLASSES
:type: tuple[dict[str, Any], Ellipsis]