Karana.KUtils.kdpy_loops
========================

.. py:module:: Karana.KUtils.kdpy_loops

.. autoapi-nested-parse::

   Analyze, report, and safely edit kdFlex closed-loop mobility.

   The module follows a staged workflow: discover coupled loop groups, inspect
   their constraint Jacobians, explore connected closure-feasible motion, classify
   coordinate participation, and only then apply an explicitly requested edit.
   Read-only analysis and serialized-model mutation have separate class APIs.



Attributes
----------

.. autoapisummary::

   Karana.KUtils.kdpy_loops.INERT_SUBHINGE_TOLERANCE


Exceptions
----------

.. autoapisummary::

   Karana.KUtils.kdpy_loops.LockedLoopError
   Karana.KUtils.kdpy_loops.ZeroLocalMobilityError
   Karana.KUtils.kdpy_loops.IndirectCoordinateChartError
   Karana.KUtils.kdpy_loops.FrozenLoopCoordinateError
   Karana.KUtils.kdpy_loops.RangeLimitComputationError


Classes
-------

.. autoapisummary::

   Karana.KUtils.kdpy_loops.MobilityRequest
   Karana.KUtils.kdpy_loops.LoopGroup
   Karana.KUtils.kdpy_loops.MobilityCandidate
   Karana.KUtils.kdpy_loops.MobilityChange
   Karana.KUtils.kdpy_loops.GeometryChange
   Karana.KUtils.kdpy_loops.SerializedSubhingeRef
   Karana.KUtils.kdpy_loops.FeasibleRangeLimit
   Karana.KUtils.kdpy_loops.CoordinateMobilityEvidence
   Karana.KUtils.kdpy_loops.BallReductionHint
   Karana.KUtils.kdpy_loops.LoopMobilityStatus
   Karana.KUtils.kdpy_loops.LoopAnalyzer
   Karana.KUtils.kdpy_loops.LoopModelEditor


Functions
---------

.. autoapisummary::

   Karana.KUtils.kdpy_loops.mobilityRequests
   Karana.KUtils.kdpy_loops.normalizedLoopOptions
   Karana.KUtils.kdpy_loops.mobilityCheckSelections
   Karana.KUtils.kdpy_loops.discoverLoopGroups
   Karana.KUtils.kdpy_loops.optimizeLoopGeometry
   Karana.KUtils.kdpy_loops.recommendMobilityAdditions
   Karana.KUtils.kdpy_loops.synchronizeSerializedRangeLimits
   Karana.KUtils.kdpy_loops.loadSimplifiedModel
   Karana.KUtils.kdpy_loops.loadTransformedModel
   Karana.KUtils.kdpy_loops.feasibleRangeLimit
   Karana.KUtils.kdpy_loops.updateSubhingeRangeLimits
   Karana.KUtils.kdpy_loops.updateRangeLimits
   Karana.KUtils.kdpy_loops.updateLoopRangeLimits
   Karana.KUtils.kdpy_loops.checkRangeLimits
   Karana.KUtils.kdpy_loops.detectInertDofs
   Karana.KUtils.kdpy_loops.findReducibleBallJoints
   Karana.KUtils.kdpy_loops.mobilityCheck
   Karana.KUtils.kdpy_loops.loopMobilityInfo
   Karana.KUtils.kdpy_loops.writeLoopMobilityInfo
   Karana.KUtils.kdpy_loops.loopStoredRangesInfo
   Karana.KUtils.kdpy_loops.writeLoopStoredRangesInfo


Module Contents
---------------

.. py:data:: INERT_SUBHINGE_TOLERANCE
   :value: 2e-07


.. py:class:: MobilityRequest

   Define the target nullity and admissible drivers for one loop group.


   .. py:attribute:: anchor
      :type:  str


   .. py:attribute:: target_mobility
      :type:  int


   .. py:attribute:: drivers
      :type:  tuple[str, Ellipsis]


   .. py:attribute:: allowed_components
      :type:  tuple[str, Ellipsis]


.. py:class:: LoopGroup

   Identify constraint rows and velocity columns coupled into one loop.


   .. py:attribute:: constraints
      :type:  tuple[str, Ellipsis]


   .. py:attribute:: rows
      :type:  tuple[int, Ellipsis]


   .. py:attribute:: columns
      :type:  tuple[int, Ellipsis]


   .. py:attribute:: coordinate_ids
      :type:  tuple[int, Ellipsis]


   .. py:attribute:: coordinate_names
      :type:  tuple[str, Ellipsis]


.. py:class:: MobilityCandidate

   Describe one canonical cut-joint coordinate considered for insertion.


   .. py:attribute:: constraint
      :type:  str


   .. py:attribute:: component
      :type:  str


   .. py:attribute:: subhinge_type
      :type:  str


   .. py:attribute:: axis
      :type:  tuple[float, float, float]


   .. py:attribute:: column
      :type:  tuple[float, Ellipsis]


.. py:class:: MobilityChange

   Summarize an accepted connected-loop mobility augmentation.


   .. py:attribute:: anchor
      :type:  str


   .. py:attribute:: constraints
      :type:  tuple[str, Ellipsis]


   .. py:attribute:: before_rank
      :type:  int


   .. py:attribute:: before_nullity
      :type:  int


   .. py:attribute:: target_mobility
      :type:  int


   .. py:attribute:: additions
      :type:  tuple[MobilityCandidate, Ellipsis]


   .. py:attribute:: predicted_rank
      :type:  int


   .. py:attribute:: predicted_nullity
      :type:  int


   .. py:attribute:: before_smallest_singular
      :type:  float


   .. py:attribute:: predicted_smallest_singular
      :type:  float


.. py:class:: GeometryChange

   Summarize one accepted or rejected weak-mode axis reconciliation.


   .. py:attribute:: constraints
      :type:  tuple[str, Ellipsis]


   .. py:attribute:: accepted
      :type:  bool


   .. py:attribute:: before_rank
      :type:  int


   .. py:attribute:: after_rank
      :type:  int


   .. py:attribute:: before_nullity
      :type:  int


   .. py:attribute:: after_nullity
      :type:  int


   .. py:attribute:: before_weak_singular
      :type:  float


   .. py:attribute:: after_weak_singular
      :type:  float


   .. py:attribute:: axis_changes
      :type:  tuple[tuple[str, float], Ellipsis]


   .. py:attribute:: validation_spans
      :type:  tuple[tuple[str, float, float, str], Ellipsis]
      :value: ()



   .. py:attribute:: rejection_reason
      :type:  str | None
      :value: None



.. py:function:: mobilityRequests(loop_options: dict[str, Any] | None) -> tuple[MobilityRequest, Ellipsis]

   Validate and merge repeatable four-field DOF requests by loop anchor.

   Kclick returns one record per ``--add-dofs`` occurrence. Multiple records
   may contribute drivers to the same loop, but target mobility and allowed
   component families must agree. The result is canonical input for the
   Jacobian-based recommendation phase.


.. py:function:: normalizedLoopOptions(options: dict[str, Any]) -> dict[str, Any]

   Return concrete loop defaults in place of Kclick omission sentinels.

   Kclick distinguishes an uninvoked chained command from one whose options
   hold ordinary false or empty values. Downstream loop code consumes stable
   Python values and does not depend on sentinel identity.


.. py:function:: mobilityCheckSelections(values: tuple[str, Ellipsis]) -> tuple[tuple[str, int], Ellipsis] | None

   Parse repeatable ``SUBHINGE[/OFFSET]`` mobility-check selectors.

   ``None`` represents the special all-coordinate selection. A missing suffix
   selects offset zero, while an explicit suffix must be a nonnegative integer.


.. py:function:: discoverLoopGroups(subgraph: Karana.Dynamics.SubGraph, *, activity_tolerance: float = 1e-10) -> tuple[LoopGroup, Ellipsis]

   Discover connected cut-joint groups through shared active coordinates.

   Two constraints belong to the same group when their Jacobian rows touch a
   common active coordinate, including transitive connections. Grouping first
   prevents kdpy from making an invalid independent decision for each cut
   joint when several closures are algebraically coupled.


.. py:function:: optimizeLoopGeometry(subgraph: Karana.Dynamics.SubGraph, group: LoopGroup, *, weak_relative_tolerance: float = 0.001, exact_relative_tolerance: float = 1e-09, maximum_axis_parameter: float = 0.001) -> GeometryChange | None

   Remove weak nonzero modes using bounded finite-motion axis corrections.

   Small nonzero singular values often indicate nominally aligned hinge axes
   that differ by modeling noise. Candidate axes are adjusted only within the
   configured bound and evaluated at predictor poses along the weak modes. A
   change is accepted only when the corrected mechanism then closes through a
   finite two-sided range and retains the intended rank there; otherwise the
   original geometry is restored.


.. py:function:: recommendMobilityAdditions(subgraph: Karana.Dynamics.SubGraph, group: LoopGroup, request: MobilityRequest, states: tuple[numpy.ndarray, Ellipsis]) -> MobilityChange

   Select the smallest well-conditioned coordinate set meeting a request.

   Candidate combinations are evaluated against every retained closed state.
   A valid result reaches the requested nullity, preserves independent driver
   projections, and avoids weaker singular modes than same-size alternatives.


.. py:class:: SerializedSubhingeRef

   Locate one serialized subhinge and the object that owns its hinge.


   .. py:attribute:: name
      :type:  str


   .. py:attribute:: owner
      :type:  Any


   .. py:attribute:: owner_attribute
      :type:  str


   .. py:attribute:: subhinge_index
      :type:  int


.. py:function:: synchronizeSerializedRangeLimits(subgraph: Karana.Dynamics.SubGraph, model_target: Any) -> None

   Copy live Q-coordinate range limits into a retained BasicPrefab specification.

   SubGraph outputs serialize their live object directly.  BasicPrefab loading,
   however, retains the input DataStruct so that its wrapper, assets, and model
   metadata survive output.  Range analysis changes the instantiated
   Multibody, so this synchronization step transfers each coordinate result back
   to the matching serialized subhinge immediately before ``--to-model``.


.. py:function:: loadSimplifiedModel(model_file: pathlib.Path, remove_inert: tuple[str, Ellipsis], reduce_ball: tuple[str, Ellipsis], reduce_spherical: tuple[str, Ellipsis]) -> tuple[Karana.KUtils.kdpy_model_io.LoadedModel, tuple[str, Ellipsis], tuple[str, Ellipsis]]

   Apply validated hinge reductions and load the rebuilt root model.


.. py:function:: loadTransformedModel(model_file: pathlib.Path, optimize_mobility: bool, requests: tuple[MobilityRequest, Ellipsis]) -> tuple[Karana.KUtils.kdpy_model_io.LoadedModel, tuple[GeometryChange, Ellipsis], tuple[MobilityChange, Ellipsis]]

   Optimize geometry, add requested DOFs, and load a rebuilt SubGraph.


.. py:class:: FeasibleRangeLimit

   Record one connected feasible interval and its evidence quality.

   Limit flags distinguish a closure boundary from a configured search bound.
   ``worst_feasible_residual`` records the largest accepted pose error, while
   ``range_limits_supported`` states whether native limits can store the result.


   .. py:attribute:: coordinate
      :type:  str


   .. py:attribute:: current
      :type:  float


   .. py:attribute:: lower
      :type:  float


   .. py:attribute:: upper
      :type:  float


   .. py:attribute:: lower_limited
      :type:  bool


   .. py:attribute:: upper_limited
      :type:  bool


   .. py:attribute:: worst_feasible_residual
      :type:  float


   .. py:attribute:: old_limits
      :type:  tuple[float, float] | None
      :value: None



   .. py:attribute:: dof_index
      :type:  int
      :value: 0



   .. py:attribute:: range_limits_supported
      :type:  bool
      :value: True



.. py:class:: CoordinateMobilityEvidence

   Summarize range-wide evidence for one candidate inert DOF.

   Global inertness requires complete connected-range coverage and acceptable
   participation and displacement at every retained closed state.


   .. py:attribute:: coordinate
      :type:  str


   .. py:attribute:: dof_index
      :type:  int


   .. py:attribute:: classification
      :type:  str


   .. py:attribute:: sampled_states
      :type:  int


   .. py:attribute:: max_participation
      :type:  float


   .. py:attribute:: max_displacement
      :type:  float


   .. py:attribute:: complete_connected_range
      :type:  bool


.. py:class:: BallReductionHint

   Describe a BALL coordinate's local active rotational subspace.

   The axes are candidates for fixed-axis gimbal validation, not proof that a
   rotational component can be removed.


   .. py:attribute:: coordinate
      :type:  str


   .. py:attribute:: participation_rank
      :type:  int


   .. py:attribute:: coordinate_dofs
      :type:  int


   .. py:attribute:: axes
      :type:  tuple[tuple[float, float, float], Ellipsis]


.. py:class:: LoopMobilityStatus

   Provide one presentation-ready loop-coordinate classification record.


   .. py:attribute:: coordinate
      :type:  str


   .. py:attribute:: coordinate_type
      :type:  str


   .. py:attribute:: dof_index
      :type:  int


   .. py:attribute:: mobility
      :type:  str


   .. py:attribute:: feasible_range
      :type:  tuple[float, float] | None


   .. py:attribute:: details
      :type:  str


.. py:exception:: LockedLoopError

   Bases: :py:obj:`ValueError`


   Indicate that a loop coordinate cannot be retained as independent.


.. py:exception:: ZeroLocalMobilityError

   Bases: :py:obj:`LockedLoopError`


   Indicate that a coordinate is zero in every local nullspace vector.


.. py:exception:: IndirectCoordinateChartError

   Bases: :py:obj:`LockedLoopError`


   Indicate that scalar Q continuation cannot represent one velocity DOF.


.. py:exception:: FrozenLoopCoordinateError

   Bases: :py:obj:`ValueError`


   Indicate that a loop coordinate was frozen before range exploration.


.. py:exception:: RangeLimitComputationError

   Bases: :py:obj:`RuntimeError`


   Indicate that numerical closure did not establish a range limit.


.. py:function:: feasibleRangeLimit(subgraph: Karana.Dynamics.SubGraph, subhinge: Karana.Dynamics.PhysicalSubhinge, *, dof_index: int = 0, initial_step: float | None = None, maximum_step: float | None = None, maximum_distance: float | None = None, closure_tolerance: float = 1e-09, rank_tolerance: float = 1e-09, boundary_tolerance: float = 1e-07, sample_callback: Callable[[numpy.ndarray], None] | None = None) -> FeasibleRangeLimit

   Find the connected projected feasible interval around one subhinge DOF.

   For every tested value of the selected coordinate, all remaining direct
   position coordinates are optimization variables.  Consequently the test
   answers ``exists q_other: C(q) = 0`` and does not impose the artificial
   path or frozen-coordinate partition used by the older continuation scan.
   Scanning stops at the first infeasible value in each direction, retaining
   only the connected interval containing the initial configuration.


.. py:function:: updateSubhingeRangeLimits(subgraph: Karana.Dynamics.SubGraph, subhinge: Karana.Dynamics.PhysicalSubhinge, coordinate_offset: int) -> bool

   Measure and store one physical-subhinge coordinate's feasible range.

   Finite values denote established feasibility boundaries, infinities denote
   open search directions, and NaNs denote a failed numerical measurement.
   Frozen coordinates and unsupported indirect charts are left unchanged.

   :param subgraph: Graph supplying closure constraints and its CK solver.
   :type subgraph: kd.SubGraph
   :param subhinge: Physical subhinge whose range-limit row is updated.
   :type subhinge: kd.PhysicalSubhinge
   :param coordinate_offset: Local Q row to measure and store.
   :type coordinate_offset: int

   :returns: ``True`` when the stored endpoint pair changed; otherwise ``False``.
   :rtype: bool


.. py:function:: updateRangeLimits(subgraph: Karana.Dynamics.SubGraph, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None) -> None

   Update feasible ranges for physical coordinates in a subgraph.

   When ``selected_dofs`` is supplied, only the named coordinate offsets are
   processed. Otherwise every physical hinge and cut-joint coordinate in the
   graph is updated once.

   :param subgraph: Graph whose physical-subhinge range metadata is updated.
   :type subgraph: kd.SubGraph
   :param selected_dofs: Optional coordinate-name and local-offset pairs. ``None``
                         updates every physical coordinate in the graph.
   :type selected_dofs: tuple[tuple[str, int], ...] | None


.. py:function:: updateLoopRangeLimits(subgraph: Karana.Dynamics.SubGraph, selected_names: tuple[str, Ellipsis] = ('all', )) -> None

   Find, apply, and report Q ranges for loop-active physical subhinges.

   :param subgraph: Graph whose physical range metadata is updated.
   :type subgraph: kd.SubGraph
   :param selected_names: Requested subhinge names, or ``("all",)``.
                          Defaults to ``("all",)``.
   :type selected_names: tuple[str, ...]


.. py:function:: checkRangeLimits(multibody: Karana.Dynamics.SubGraph, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None, *, report_progress: bool = False, _analysis: _MobilityAnalysisResult | None = None) -> tuple[tuple[FeasibleRangeLimit, Ellipsis], tuple[LoopMobilityStatus, Ellipsis]]

   Check feasible ranges without applying them to the multibody.

   ``multibody`` is first so this operation can move to :class:`SubGraph`
   without changing its user-facing arguments. The returned statuses include
   unresolved or immobile coordinates for which no interval was measured.

   :param multibody: SubGraph-compatible multibody to analyze.
   :type multibody: kd.SubGraph
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None
   :param report_progress: Whether to emit continuation progress.
                           Defaults to ``False``.
   :type report_progress: bool
   :param _analysis: Optional shared internal result supplied by ``mobilityCheck``.
   :type _analysis: _MobilityAnalysisResult | None

   :returns: The completed range measurements and report-ready coordinate statuses.
   :rtype: tuple[tuple[FeasibleRangeLimit, ...], tuple[LoopMobilityStatus, ...]]


.. py:function:: detectInertDofs(multibody: Karana.Dynamics.SubGraph, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None, *, tolerance: float = 2e-07, update_ranges: bool = True) -> tuple[CoordinateMobilityEvidence, Ellipsis]

   Update ranges and return physical coordinates with finite zero spans.

   Only two finite endpoints can establish inertness. Open intervals and
   failed measurements therefore remain non-inert without auxiliary flags.

   :param multibody: SubGraph-compatible multibody whose ranges are inspected.
   :type multibody: kd.SubGraph
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None
   :param tolerance: Largest finite interval width classified as inert.
                     Defaults to 2.0e-7.
   :type tolerance: float
   :param update_ranges: Whether to recompute stored ranges before classification.
                         Defaults to ``True``.
   :type update_ranges: bool

   :returns: Deterministically ordered evidence for coordinates classified inert.
   :rtype: tuple[CoordinateMobilityEvidence, ...]


.. py:function:: findReducibleBallJoints(multibody: Karana.Dynamics.SubGraph, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None, *, report_progress: bool = False, _analysis: _MobilityAnalysisResult | None = None) -> tuple[BallReductionHint, Ellipsis]

   Return BALL joints whose local rotational subspace merits reduction.

   Results are candidates rather than topology edits; range-wide fixed-axis
   validation remains required before a BALL is replaced. This separation
   keeps a future ``SubGraph`` method non-mutating and independently usable.

   :param multibody: SubGraph-compatible multibody to analyze.
   :type multibody: kd.SubGraph
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None
   :param report_progress: Whether to emit continuation progress.
                           Defaults to ``False``.
   :type report_progress: bool
   :param _analysis: Optional shared internal result supplied by ``mobilityCheck``.
   :type _analysis: _MobilityAnalysisResult | None

   :returns: Advisory spherical-joint reduction candidates.
   :rtype: tuple[BallReductionHint, ...]


.. py:function:: mobilityCheck(multibody: Karana.Dynamics.SubGraph, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None, *, report_progress: bool = False) -> tuple[tuple[CoordinateMobilityEvidence, Ellipsis], tuple[BallReductionHint, Ellipsis], tuple[LoopMobilityStatus, Ellipsis]]

   Update ranges and combine inertness, BALL hints, and status records.

   :param multibody: SubGraph-compatible multibody to analyze.
   :type multibody: kd.SubGraph
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None
   :param report_progress: Whether to emit continuation progress.
                           Defaults to False.
   :type report_progress: bool

   :returns: * *tuple[* -- tuple[CoordinateMobilityEvidence, ...],
               tuple[BallReductionHint, ...],
               tuple[LoopMobilityStatus, ...],
             * *]* -- Inert-coordinate evidence, spherical reduction hints, and report-ready
               coordinate statuses.


.. py:function:: loopMobilityInfo(subgraph: Karana.Dynamics.SubGraph, source: pathlib.Path, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None, *, report_progress: bool = False) -> str

   Update ranges and return a Markdown loop-coordinate mobility report.

   The report combines continuation status, feasible spans, and advisory BALL
   hints without editing topology. Optional selections restrict projection
   work while retaining the same report schema.

   :param subgraph: Graph to analyze and update.
   :type subgraph: kd.SubGraph
   :param source: Source-model path displayed in the report.
   :type source: Path
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None
   :param report_progress: Whether to emit continuation progress.
                           Defaults to False.
   :type report_progress: bool

   :returns: Markdown mobility report text.
   :rtype: str


.. py:function:: writeLoopMobilityInfo(subgraph: Karana.Dynamics.SubGraph, source: pathlib.Path, destination: str, selected_dofs: tuple[tuple[str, int], Ellipsis] | None = None) -> None

   Write a loop-mobility Markdown report to a file or standard output.

   :param subgraph: Graph to analyze and update.
   :type subgraph: kd.SubGraph
   :param source: Source-model path displayed in the report.
   :type source: Path
   :param destination: Output filename, or ``-`` for standard output.
   :type destination: str
   :param selected_dofs: Optional coordinate-name and local-offset selections.
   :type selected_dofs: tuple[tuple[str, int], ...] | None


.. py:function:: loopStoredRangesInfo(subgraph: Karana.Dynamics.SubGraph, source: pathlib.Path) -> str

   Return stored range limits for every physical subhinge Q coordinate.

   This inventory performs no closure solve and does not infer feasibility. It
   reports exactly what the currently loaded model stores, making it suitable
   for quickly checking the result of ``loops --ranges-update``.

   :param subgraph: Graph whose stored physical-subhinge limits are inventoried.
   :type subgraph: kd.SubGraph
   :param source: Source-model path displayed in the report.
   :type source: Path

   :returns: Markdown inventory of stored range-limit rows.
   :rtype: str


.. py:function:: writeLoopStoredRangesInfo(subgraph: Karana.Dynamics.SubGraph, source: pathlib.Path, destination: str) -> None

   Write the stored Q-coordinate range inventory to a destination.

   :param subgraph: Graph whose stored ranges are inventoried.
   :type subgraph: kd.SubGraph
   :param source: Source-model path displayed in the report.
   :type source: Path
   :param destination: Output filename, or ``-`` for standard output.
   :type destination: str


.. py:class:: LoopAnalyzer(subgraph: Karana.Dynamics.SubGraph)

   Provide reusable analysis services for one live closed-loop graph.

   The class owns no native object and caches no configuration. Each operation
   delegates to the functional analysis pipeline, which restores the
   caller-visible graph state after its temporary continuation solves.


   .. py:attribute:: subgraph


   .. py:method:: scan(selected_names: tuple[str, Ellipsis] = ('all', )) -> tuple[tuple[FeasibleRangeLimit, Ellipsis], tuple[CoordinateMobilityEvidence, Ellipsis], tuple[BallReductionHint, Ellipsis], tuple[LoopMobilityStatus, Ellipsis]]

      Return ranges, inertness evidence, BALL hints, and status records.

      ``scan`` selects the non-mutating pipeline used by advisory tooling. The
      four result collections are kept separate so callers can choose their
      own presentation without parsing Markdown output.

      :param selected_names: Requested subhinge names, or ``("all",)``.
                             Defaults to ``("all",)``.
      :type selected_names: tuple[str, ...]

      :returns: * *tuple[* -- tuple[FeasibleRangeLimit, ...],
                  tuple[CoordinateMobilityEvidence, ...],
                  tuple[BallReductionHint, ...],
                  tuple[LoopMobilityStatus, ...],
                * *]* -- Range measurements, inertness evidence, spherical reduction hints,
                  and report-ready statuses.



   .. py:method:: updateRanges(selected_names: tuple[str, Ellipsis] = ('all', )) -> None

      Measure, report, and store Q range limits on the bound graph.

      :param selected_names: Requested subhinge names, or ``("all",)``.
                             Defaults to ``("all",)``.
      :type selected_names: tuple[str, ...]



   .. py:method:: writeMobilityCheck(source: pathlib.Path, destination: str, selected_dofs: tuple[tuple[str, int], Ellipsis] | None) -> None

      Update ranges and write the resulting mobility-analysis report.

      :param source: Source-model path displayed in the report.
      :type source: Path
      :param destination: Output filename, or ``-`` for standard output.
      :type destination: str
      :param selected_dofs: Optional coordinate-name and offset selections.
      :type selected_dofs: tuple[tuple[str, int], ...] | None



   .. py:method:: writeRangesInfo(source: pathlib.Path, destination: str) -> None

      Write stored Q-coordinate ranges without running analysis.

      :param source: Source-model path displayed in the report.
      :type source: Path
      :param destination: Output filename, or ``-`` for standard output.
      :type destination: str



.. py:class:: LoopModelEditor(model_file: pathlib.Path)

   Apply validated topology and geometry changes to one serialized model.

   Keeping mutation behind this class makes launcher control flow explicit and
   prevents report-only operations from accidentally selecting an editing
   path. Calls reload ``model_file`` and return a live model plus an audit
   summary of accepted changes.


   .. py:attribute:: model_file


   .. py:method:: simplify(remove_inert: tuple[str, Ellipsis], reduce_ball: tuple[str, Ellipsis], reduce_spherical: tuple[str, Ellipsis]) -> tuple[Karana.KUtils.kdpy_model_io.LoadedModel, tuple[str, Ellipsis], tuple[str, Ellipsis]]

      Remove proven inert coordinates and reduce validated spherical hinges.

      :param remove_inert: Requested scalar subhinge names to remove.
      :type remove_inert: tuple[str, ...]
      :param reduce_ball: Requested spherical subhinge names to reduce.
      :type reduce_ball: tuple[str, ...]
      :param reduce_spherical: Requested spherical subhinge names to reduce in any parent hinge.
      :type reduce_spherical: tuple[str, ...]

      :returns: Reloaded model, applied geometry changes, and mobility changes.
      :rtype: tuple[LoadedModel, tuple[str, ...], tuple[str, ...]]



   .. py:method:: transform(optimize_mobility: bool, requests: tuple[MobilityRequest, Ellipsis]) -> tuple[Karana.KUtils.kdpy_model_io.LoadedModel, tuple[GeometryChange, Ellipsis], tuple[MobilityChange, Ellipsis]]

      Optimize loop geometry and apply requested mobility additions.

      :param optimize_mobility: Whether to optimize loop-frame geometry first.
      :type optimize_mobility: bool
      :param requests: Explicit mobility additions to apply.
      :type requests: tuple[MobilityRequest, ...]

      :returns: Reloaded model, accepted geometry changes, and accepted mobility
                changes.
      :rtype: tuple[LoadedModel, tuple[GeometryChange, ...], tuple[MobilityChange, ...]]



